Commit | Line | Data |
---|---|---|
5e434adf ARW |
1 | <?php |
2 | ||
3 | /** | |
4 | * Read the schema specification and parse into internal data structures | |
5 | */ | |
6 | class CRM_Core_CodeGen_Specification { | |
7 | public $tables; | |
8 | public $database; | |
9 | ||
10 | protected $classNames; | |
11 | ||
12 | /** | |
13 | * Read and parse. | |
14 | * | |
77b97be7 | 15 | * @param $schemaPath |
6a0b768e TO |
16 | * @param string $buildVersion |
17 | * Which version of the schema to build. | |
518fa0ee | 18 | * @param bool $verbose |
5e434adf | 19 | */ |
644ba48e | 20 | public function parse($schemaPath, $buildVersion, $verbose = TRUE) { |
5e434adf ARW |
21 | $this->buildVersion = $buildVersion; |
22 | ||
644ba48e TO |
23 | if ($verbose) { |
24 | echo "Parsing schema description " . $schemaPath . "\n"; | |
25 | } | |
5e434adf | 26 | $dbXML = CRM_Core_CodeGen_Util_Xml::parse($schemaPath); |
5e434adf | 27 | |
644ba48e TO |
28 | if ($verbose) { |
29 | echo "Extracting database information\n"; | |
30 | } | |
5e434adf | 31 | $this->database = &$this->getDatabase($dbXML); |
5e434adf | 32 | |
be2fb01f | 33 | $this->classNames = []; |
5e434adf | 34 | |
041ecc95 | 35 | // TODO: peel DAO-specific stuff out of getTables, and spec reading into its own class |
644ba48e TO |
36 | if ($verbose) { |
37 | echo "Extracting table information\n"; | |
38 | } | |
5e434adf ARW |
39 | $this->tables = $this->getTables($dbXML, $this->database); |
40 | ||
41 | $this->resolveForeignKeys($this->tables, $this->classNames); | |
42 | $this->tables = $this->orderTables($this->tables); | |
43 | ||
44 | // add archive tables here | |
481a74f4 TO |
45 | foreach ($this->tables as $name => $table) { |
46 | if ($table['archive'] == 'true') { | |
5e434adf ARW |
47 | $name = 'archive_' . $table['name']; |
48 | $table['name'] = $name; | |
49 | $table['archive'] = 'false'; | |
481a74f4 | 50 | if (isset($table['foreignKey'])) { |
5e434adf ARW |
51 | foreach ($table['foreignKey'] as $fkName => $fkValue) { |
52 | if ($this->tables[$fkValue['table']]['archive'] == 'true') { | |
53 | $table['foreignKey'][$fkName]['table'] = 'archive_' . $table['foreignKey'][$fkName]['table']; | |
acb1052e WA |
54 | $table['foreignKey'][$fkName]['uniqName'] |
55 | = str_replace('FK_', 'FK_archive_', $table['foreignKey'][$fkName]['uniqName']); | |
5e434adf ARW |
56 | } |
57 | } | |
58 | $archiveTables[$name] = $table; | |
59 | } | |
60 | } | |
61 | } | |
62 | } | |
63 | ||
2558c2b0 EM |
64 | /** |
65 | * @param $dbXML | |
66 | * | |
67 | * @return array | |
68 | */ | |
00be9182 | 69 | public function &getDatabase(&$dbXML) { |
be2fb01f | 70 | $database = ['name' => trim((string ) $dbXML->name)]; |
5e434adf ARW |
71 | |
72 | $attributes = ''; | |
73 | $this->checkAndAppend($attributes, $dbXML, 'character_set', 'DEFAULT CHARACTER SET ', ''); | |
74 | $this->checkAndAppend($attributes, $dbXML, 'collate', 'COLLATE ', ''); | |
828c6915 | 75 | $attributes .= ' ROW_FORMAT=DYNAMIC'; |
5e434adf ARW |
76 | $database['attributes'] = $attributes; |
77 | ||
78 | $tableAttributes_modern = $tableAttributes_simple = ''; | |
79 | $this->checkAndAppend($tableAttributes_modern, $dbXML, 'table_type', 'ENGINE=', ''); | |
80 | $this->checkAndAppend($tableAttributes_simple, $dbXML, 'table_type', 'TYPE=', ''); | |
81 | $database['tableAttributes_modern'] = trim($tableAttributes_modern . ' ' . $attributes); | |
82 | $database['tableAttributes_simple'] = trim($tableAttributes_simple); | |
83 | ||
84 | $database['comment'] = $this->value('comment', $dbXML, ''); | |
85 | ||
86 | return $database; | |
87 | } | |
88 | ||
2558c2b0 EM |
89 | /** |
90 | * @param $dbXML | |
91 | * @param $database | |
92 | * | |
93 | * @return array | |
94 | */ | |
00be9182 | 95 | public function getTables($dbXML, &$database) { |
be2fb01f | 96 | $tables = []; |
5e434adf ARW |
97 | foreach ($dbXML->tables as $tablesXML) { |
98 | foreach ($tablesXML->table as $tableXML) { | |
eb59d669 | 99 | if ($this->value('drop', $tableXML, 0) > 0 && version_compare($this->value('drop', $tableXML, 0), $this->buildVersion, '<=')) { |
5e434adf ARW |
100 | continue; |
101 | } | |
102 | ||
eb59d669 | 103 | if (version_compare($this->value('add', $tableXML, 0), $this->buildVersion, '<=')) { |
5e434adf ARW |
104 | $this->getTable($tableXML, $database, $tables); |
105 | } | |
106 | } | |
107 | } | |
108 | ||
109 | return $tables; | |
110 | } | |
111 | ||
2558c2b0 | 112 | /** |
fc846b72 BT |
113 | * @param array $tables |
114 | * @param string[] $classNames | |
2558c2b0 | 115 | */ |
00be9182 | 116 | public function resolveForeignKeys(&$tables, &$classNames) { |
5e434adf ARW |
117 | foreach (array_keys($tables) as $name) { |
118 | $this->resolveForeignKey($tables, $classNames, $name); | |
119 | } | |
120 | } | |
121 | ||
2558c2b0 | 122 | /** |
fc846b72 BT |
123 | * @param array $tables |
124 | * @param string[] $classNames | |
100fef9d | 125 | * @param string $name |
2558c2b0 | 126 | */ |
00be9182 | 127 | public function resolveForeignKey(&$tables, &$classNames, $name) { |
5e434adf ARW |
128 | if (!array_key_exists('foreignKey', $tables[$name])) { |
129 | return; | |
130 | } | |
131 | ||
132 | foreach (array_keys($tables[$name]['foreignKey']) as $fkey) { | |
133 | $ftable = $tables[$name]['foreignKey'][$fkey]['table']; | |
134 | if (!array_key_exists($ftable, $classNames)) { | |
135 | echo "$ftable is not a valid foreign key table in $name\n"; | |
136 | continue; | |
137 | } | |
138 | $tables[$name]['foreignKey'][$fkey]['className'] = $classNames[$ftable]; | |
139 | $tables[$name]['foreignKey'][$fkey]['fileName'] = str_replace('_', '/', $classNames[$ftable]) . '.php'; | |
140 | $tables[$name]['fields'][$fkey]['FKClassName'] = $classNames[$ftable]; | |
141 | } | |
142 | } | |
143 | ||
2558c2b0 | 144 | /** |
fc846b72 | 145 | * @param array $tables |
2558c2b0 EM |
146 | * |
147 | * @return array | |
148 | */ | |
00be9182 | 149 | public function orderTables(&$tables) { |
be2fb01f | 150 | $ordered = []; |
5e434adf ARW |
151 | |
152 | while (!empty($tables)) { | |
153 | foreach (array_keys($tables) as $name) { | |
154 | if ($this->validTable($tables, $ordered, $name)) { | |
155 | $ordered[$name] = $tables[$name]; | |
156 | unset($tables[$name]); | |
157 | } | |
158 | } | |
159 | } | |
160 | return $ordered; | |
161 | } | |
162 | ||
2558c2b0 | 163 | /** |
fc846b72 | 164 | * @param array $tables |
100fef9d CW |
165 | * @param int $valid |
166 | * @param string $name | |
2558c2b0 EM |
167 | * |
168 | * @return bool | |
169 | */ | |
00be9182 | 170 | public function validTable(&$tables, &$valid, $name) { |
5e434adf ARW |
171 | if (!array_key_exists('foreignKey', $tables[$name])) { |
172 | return TRUE; | |
173 | } | |
174 | ||
175 | foreach (array_keys($tables[$name]['foreignKey']) as $fkey) { | |
176 | $ftable = $tables[$name]['foreignKey'][$fkey]['table']; | |
177 | if (!array_key_exists($ftable, $valid) && $ftable !== $name) { | |
178 | return FALSE; | |
179 | } | |
180 | } | |
181 | return TRUE; | |
182 | } | |
183 | ||
2558c2b0 EM |
184 | /** |
185 | * @param $tableXML | |
186 | * @param $database | |
187 | * @param $tables | |
188 | */ | |
00be9182 | 189 | public function getTable($tableXML, &$database, &$tables) { |
5e434adf ARW |
190 | $name = trim((string ) $tableXML->name); |
191 | $klass = trim((string ) $tableXML->class); | |
192 | $base = $this->value('base', $tableXML); | |
193 | $sourceFile = "xml/schema/{$base}/{$klass}.xml"; | |
194 | $daoPath = "{$base}/DAO/"; | |
3cef3c00 | 195 | $baoPath = __DIR__ . '/../../../' . str_replace(' ', '', "{$base}/BAO/"); |
915274c8 | 196 | $useBao = $this->value('useBao', $tableXML, file_exists($baoPath . $klass . '.php')); |
5e434adf ARW |
197 | $pre = str_replace('/', '_', $daoPath); |
198 | $this->classNames[$name] = $pre . $klass; | |
199 | ||
200 | $localizable = FALSE; | |
201 | foreach ($tableXML->field as $fieldXML) { | |
202 | if ($fieldXML->localizable) { | |
203 | $localizable = TRUE; | |
204 | break; | |
205 | } | |
206 | } | |
207 | ||
7b66c3b5 | 208 | $titleFromClass = preg_replace('/([a-z])([A-Z])/', '$1 $2', $klass); |
be2fb01f | 209 | $table = [ |
5e434adf ARW |
210 | 'name' => $name, |
211 | 'base' => $daoPath, | |
212 | 'sourceFile' => $sourceFile, | |
213 | 'fileName' => $klass . '.php', | |
214 | 'objectName' => $klass, | |
7b66c3b5 AH |
215 | 'title' => $tableXML->title ?? $titleFromClass, |
216 | 'titlePlural' => $tableXML->titlePlural ?? CRM_Utils_String::pluralize($tableXML->title ?? $titleFromClass), | |
449c4e6b | 217 | 'icon' => $tableXML->icon ?? NULL, |
8ab43c93 | 218 | 'labelField' => $tableXML->labelField ?? NULL, |
4b4cf875 | 219 | 'add' => $tableXML->add ?? NULL, |
d31fb4e3 | 220 | 'component' => $tableXML->component ?? NULL, |
a7bd99ff | 221 | 'paths' => (array) ($tableXML->paths ?? []), |
5e434adf ARW |
222 | 'labelName' => substr($name, 8), |
223 | 'className' => $this->classNames[$name], | |
915274c8 | 224 | 'bao' => ($useBao ? str_replace('DAO', 'BAO', $this->classNames[$name]) : $this->classNames[$name]), |
587261c3 | 225 | 'entity' => $tableXML->entity ?? $klass, |
5e434adf ARW |
226 | 'attributes_simple' => trim($database['tableAttributes_simple']), |
227 | 'attributes_modern' => trim($database['tableAttributes_modern']), | |
228 | 'comment' => $this->value('comment', $tableXML), | |
2562d09a | 229 | 'description' => $this->value('description', $tableXML), |
5e434adf ARW |
230 | 'localizable' => $localizable, |
231 | 'log' => $this->value('log', $tableXML, 'false'), | |
232 | 'archive' => $this->value('archive', $tableXML, 'false'), | |
be2fb01f | 233 | ]; |
5e434adf | 234 | |
be2fb01f | 235 | $fields = []; |
5e434adf | 236 | foreach ($tableXML->field as $fieldXML) { |
eb59d669 | 237 | if ($this->value('drop', $fieldXML, 0) > 0 && version_compare($this->value('drop', $fieldXML, 0), $this->buildVersion, '<=')) { |
5e434adf ARW |
238 | continue; |
239 | } | |
240 | ||
eb59d669 | 241 | if (version_compare($this->value('add', $fieldXML, 0), $this->buildVersion, '<=')) { |
5e434adf ARW |
242 | $this->getField($fieldXML, $fields); |
243 | } | |
244 | } | |
245 | ||
246 | $table['fields'] = &$fields; | |
5e434adf ARW |
247 | |
248 | if ($this->value('primaryKey', $tableXML)) { | |
249 | $this->getPrimaryKey($tableXML->primaryKey, $fields, $table); | |
250 | } | |
251 | ||
5e434adf | 252 | if ($this->value('index', $tableXML)) { |
be2fb01f | 253 | $index = []; |
5e434adf | 254 | foreach ($tableXML->index as $indexXML) { |
eb59d669 | 255 | if ($this->value('drop', $indexXML, 0) > 0 && version_compare($this->value('drop', $indexXML, 0), $this->buildVersion, '<=')) { |
5e434adf ARW |
256 | continue; |
257 | } | |
258 | ||
259 | $this->getIndex($indexXML, $fields, $index); | |
260 | } | |
6b86d84f | 261 | CRM_Core_BAO_SchemaHandler::addIndexSignature($name, $index); |
5e434adf ARW |
262 | $table['index'] = &$index; |
263 | } | |
264 | ||
265 | if ($this->value('foreignKey', $tableXML)) { | |
be2fb01f | 266 | $foreign = []; |
5e434adf | 267 | foreach ($tableXML->foreignKey as $foreignXML) { |
5e434adf | 268 | |
eb59d669 | 269 | if ($this->value('drop', $foreignXML, 0) > 0 && version_compare($this->value('drop', $foreignXML, 0), $this->buildVersion, '<=')) { |
5e434adf ARW |
270 | continue; |
271 | } | |
eb59d669 | 272 | if (version_compare($this->value('add', $foreignXML, 0), $this->buildVersion, '<=')) { |
5e434adf ARW |
273 | $this->getForeignKey($foreignXML, $fields, $foreign, $name); |
274 | } | |
275 | } | |
1832ba99 SL |
276 | if (!empty($foreign)) { |
277 | $table['foreignKey'] = &$foreign; | |
278 | } | |
5e434adf ARW |
279 | } |
280 | ||
281 | if ($this->value('dynamicForeignKey', $tableXML)) { | |
be2fb01f | 282 | $dynamicForeign = []; |
5e434adf | 283 | foreach ($tableXML->dynamicForeignKey as $foreignXML) { |
eb59d669 | 284 | if ($this->value('drop', $foreignXML, 0) > 0 && version_compare($this->value('drop', $foreignXML, 0), $this->buildVersion, '<=')) { |
5e434adf ARW |
285 | continue; |
286 | } | |
eb59d669 | 287 | if (version_compare($this->value('add', $foreignXML, 0), $this->buildVersion, '<=')) { |
5e434adf ARW |
288 | $this->getDynamicForeignKey($foreignXML, $dynamicForeign, $name); |
289 | } | |
290 | } | |
3053b963 SL |
291 | if (!empty($dynamicForeign)) { |
292 | $table['dynamicForeignKey'] = $dynamicForeign; | |
293 | } | |
5e434adf ARW |
294 | } |
295 | ||
296 | $tables[$name] = &$table; | |
5e434adf ARW |
297 | } |
298 | ||
2558c2b0 EM |
299 | /** |
300 | * @param $fieldXML | |
301 | * @param $fields | |
302 | */ | |
00be9182 | 303 | public function getField(&$fieldXML, &$fields) { |
353ffa53 | 304 | $name = trim((string ) $fieldXML->name); |
be2fb01f | 305 | $field = ['name' => $name, 'localizable' => ((bool) $fieldXML->localizable) ? 1 : 0]; |
fc846b72 | 306 | $type = (string) $fieldXML->type; |
5e434adf ARW |
307 | switch ($type) { |
308 | case 'varchar': | |
309 | case 'char': | |
310 | $field['length'] = (int) $fieldXML->length; | |
311 | $field['sqlType'] = "$type({$field['length']})"; | |
5e434adf ARW |
312 | $field['crmType'] = 'CRM_Utils_Type::T_STRING'; |
313 | $field['size'] = $this->getSize($fieldXML); | |
314 | break; | |
315 | ||
5e434adf | 316 | case 'text': |
fc846b72 | 317 | $field['sqlType'] = $type; |
5e434adf | 318 | $field['crmType'] = 'CRM_Utils_Type::T_' . strtoupper($type); |
5e545f38 CW |
319 | // CRM-13497 see fixme below |
320 | $field['rows'] = isset($fieldXML->html) ? $this->value('rows', $fieldXML->html) : NULL; | |
321 | $field['cols'] = isset($fieldXML->html) ? $this->value('cols', $fieldXML->html) : NULL; | |
322 | break; | |
2aa397bc | 323 | |
5e434adf | 324 | case 'datetime': |
fc846b72 | 325 | $field['sqlType'] = $type; |
5e434adf ARW |
326 | $field['crmType'] = 'CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME'; |
327 | break; | |
328 | ||
329 | case 'boolean': | |
330 | // need this case since some versions of mysql do not have boolean as a valid column type and hence it | |
331 | // is changed to tinyint. hopefully after 2 yrs this case can be removed. | |
332 | $field['sqlType'] = 'tinyint'; | |
5e434adf ARW |
333 | $field['crmType'] = 'CRM_Utils_Type::T_' . strtoupper($type); |
334 | break; | |
335 | ||
336 | case 'decimal': | |
337 | $length = $fieldXML->length ? $fieldXML->length : '20,2'; | |
338 | $field['sqlType'] = 'decimal(' . $length . ')'; | |
5e434adf | 339 | $field['crmType'] = 'CRM_Utils_Type::T_MONEY'; |
fb607354 | 340 | $field['precision'] = $length . ','; |
5e434adf ARW |
341 | break; |
342 | ||
343 | case 'float': | |
344 | $field['sqlType'] = 'double'; | |
5e434adf ARW |
345 | $field['crmType'] = 'CRM_Utils_Type::T_FLOAT'; |
346 | break; | |
347 | ||
348 | default: | |
3ab3ff17 | 349 | $field['sqlType'] = $type; |
e0674c37 | 350 | if ($type == 'int unsigned' || $type == 'tinyint') { |
5e434adf ARW |
351 | $field['crmType'] = 'CRM_Utils_Type::T_INT'; |
352 | } | |
353 | else { | |
3ab3ff17 | 354 | $field['crmType'] = $this->value('crmType', $fieldXML, 'CRM_Utils_Type::T_' . strtoupper($type)); |
5e434adf ARW |
355 | } |
356 | break; | |
357 | } | |
358 | ||
fc846b72 BT |
359 | $field['phpType'] = $this->getPhpType($fieldXML); |
360 | $field['phpNullable'] = $this->getPhpNullable($fieldXML); | |
361 | ||
5e434adf | 362 | $field['required'] = $this->value('required', $fieldXML); |
353ffa53 TO |
363 | $field['collate'] = $this->value('collate', $fieldXML); |
364 | $field['comment'] = $this->value('comment', $fieldXML); | |
365 | $field['default'] = $this->value('default', $fieldXML); | |
366 | $field['import'] = $this->value('import', $fieldXML); | |
5e434adf ARW |
367 | if ($this->value('export', $fieldXML)) { |
368 | $field['export'] = $this->value('export', $fieldXML); | |
369 | } | |
370 | else { | |
371 | $field['export'] = $this->value('import', $fieldXML); | |
372 | } | |
373 | $field['rule'] = $this->value('rule', $fieldXML); | |
374 | $field['title'] = $this->value('title', $fieldXML); | |
375 | if (!$field['title']) { | |
376 | $field['title'] = $this->composeTitle($name); | |
377 | } | |
378 | $field['headerPattern'] = $this->value('headerPattern', $fieldXML); | |
379 | $field['dataPattern'] = $this->value('dataPattern', $fieldXML); | |
34745448 | 380 | $field['readonly'] = $this->value('readonly', $fieldXML); |
5e434adf | 381 | $field['uniqueName'] = $this->value('uniqueName', $fieldXML); |
74db51d3 | 382 | $field['uniqueTitle'] = $this->value('uniqueTitle', $fieldXML); |
2a5c9b4d | 383 | $field['serialize'] = $this->value('serialize', $fieldXML); |
88c31c05 | 384 | $field['component'] = $this->value('component', $fieldXML); |
5e545f38 | 385 | $field['html'] = $this->value('html', $fieldXML); |
f5c0f096 | 386 | $field['contactType'] = $this->value('contactType', $fieldXML); |
1713a0ec CW |
387 | if (isset($fieldXML->permission)) { |
388 | $field['permission'] = trim($this->value('permission', $fieldXML)); | |
389 | $field['permission'] = $field['permission'] ? array_filter(array_map('trim', explode(',', $field['permission']))) : []; | |
390 | if (isset($fieldXML->permission->or)) { | |
391 | $field['permission'][] = array_filter(array_map('trim', explode(',', $fieldXML->permission->or))); | |
392 | } | |
393 | } | |
5e545f38 | 394 | if (!empty($field['html'])) { |
be2fb01f | 395 | $validOptions = [ |
5e545f38 | 396 | 'type', |
24317d89 | 397 | 'formatType', |
80a96508 | 398 | 'label', |
8ada2bfd | 399 | 'controlField', |
5e545f38 CW |
400 | /* Fixme: prior to CRM-13497 these were in a flat structure |
401 | // CRM-13497 moved them to be nested within 'html' but there's no point | |
402 | // making that change in the DAOs right now since we are in the process of | |
403 | // moving to docrtine anyway. | |
404 | // So translating from nested xml back to flat structure for now. | |
405 | 'rows', | |
406 | 'cols', | |
407 | 'size', */ | |
be2fb01f CW |
408 | ]; |
409 | $field['html'] = []; | |
5e545f38 | 410 | foreach ($validOptions as $htmlOption) { |
9b873358 | 411 | if (!empty($fieldXML->html->$htmlOption)) { |
5e545f38 CW |
412 | $field['html'][$htmlOption] = $this->value($htmlOption, $fieldXML->html); |
413 | } | |
414 | } | |
415 | } | |
313df5ee SV |
416 | |
417 | // in multilingual context popup, we need extra information to create appropriate widget | |
418 | if ($fieldXML->localizable) { | |
419 | if (isset($fieldXML->html)) { | |
420 | $field['widget'] = (array) $fieldXML->html; | |
421 | } | |
422 | else { | |
423 | // default | |
be2fb01f | 424 | $field['widget'] = ['type' => 'Text']; |
313df5ee SV |
425 | } |
426 | if (isset($fieldXML->required)) { | |
427 | $field['widget']['required'] = $this->value('required', $fieldXML); | |
428 | } | |
429 | } | |
65c86f7d | 430 | if (isset($fieldXML->localize_context)) { |
431 | $field['localize_context'] = $fieldXML->localize_context; | |
432 | } | |
a9d0587b | 433 | $field['add'] = $this->value('add', $fieldXML); |
5e434adf | 434 | $field['pseudoconstant'] = $this->value('pseudoconstant', $fieldXML); |
9b873358 | 435 | if (!empty($field['pseudoconstant'])) { |
5e434adf | 436 | //ok this is a bit long-winded but it gets there & is consistent with above approach |
be2fb01f CW |
437 | $field['pseudoconstant'] = []; |
438 | $validOptions = [ | |
5e434adf ARW |
439 | // Fields can specify EITHER optionGroupName OR table, not both |
440 | // (since declaring optionGroupName means we are using the civicrm_option_value table) | |
441 | 'optionGroupName', | |
442 | 'table', | |
443 | // If table is specified, keyColumn and labelColumn are also required | |
444 | 'keyColumn', | |
445 | 'labelColumn', | |
446 | // Non-translated machine name for programmatic lookup. Defaults to 'name' if that column exists | |
447 | 'nameColumn', | |
a8fdb24e CW |
448 | // Column to fetch in "abbreviate" context |
449 | 'abbrColumn', | |
76c1631a | 450 | // Where clause snippet (will be joined to the rest of the query with AND operator) |
5e434adf | 451 | 'condition', |
b44e3f84 | 452 | // callback function incase of static arrays |
cc6443c4 | 453 | 'callback', |
14f42e31 CW |
454 | // Path to options edit form |
455 | 'optionEditPath', | |
9b97477d EM |
456 | // Should options for this field be prefetched (for presenting on forms). |
457 | // The default is TRUE, but adding FALSE helps when there could be many options | |
458 | 'prefetch', | |
be2fb01f | 459 | ]; |
5e434adf | 460 | foreach ($validOptions as $pseudoOption) { |
9b873358 | 461 | if (!empty($fieldXML->pseudoconstant->$pseudoOption)) { |
5e434adf ARW |
462 | $field['pseudoconstant'][$pseudoOption] = $this->value($pseudoOption, $fieldXML->pseudoconstant); |
463 | } | |
464 | } | |
14f42e31 CW |
465 | if (!isset($field['pseudoconstant']['optionEditPath']) && !empty($field['pseudoconstant']['optionGroupName'])) { |
466 | $field['pseudoconstant']['optionEditPath'] = 'civicrm/admin/options/' . $field['pseudoconstant']['optionGroupName']; | |
467 | } | |
5e434adf ARW |
468 | // For now, fields that have option lists that are not in the db can simply |
469 | // declare an empty pseudoconstant tag and we'll add this placeholder. | |
470 | // That field's BAO::buildOptions fn will need to be responsible for generating the option list | |
471 | if (empty($field['pseudoconstant'])) { | |
472 | $field['pseudoconstant'] = 'not in database'; | |
473 | } | |
474 | } | |
475 | $fields[$name] = &$field; | |
476 | } | |
477 | ||
fc846b72 BT |
478 | /** |
479 | * Returns the PHPtype used within the DAO object | |
480 | * | |
481 | * @param object $fieldXML | |
482 | * @return string | |
483 | */ | |
484 | private function getPhpType($fieldXML) { | |
485 | $type = $fieldXML->type; | |
486 | $phpType = $this->value('phpType', $fieldXML, 'string'); | |
487 | ||
488 | if ($type == 'int' || $type == 'int unsigned' || $type == 'tinyint') { | |
489 | $phpType = 'int'; | |
490 | } | |
491 | ||
492 | if ($type == 'float' || $type == 'decimal') { | |
493 | $phpType = 'float'; | |
494 | } | |
495 | ||
496 | if ($type == 'boolean') { | |
497 | $phpType = 'bool'; | |
498 | } | |
499 | ||
500 | if ($phpType !== 'string') { | |
501 | // Values are almost always fetched from the database as string | |
502 | $phpType .= '|string'; | |
503 | } | |
504 | ||
505 | return $phpType; | |
506 | } | |
507 | ||
508 | /** | |
509 | * Returns whether the field is nullable in PHP. | |
510 | * Either because: | |
511 | * - The SQL field is nullable | |
512 | * - The field is a primary key, and so is null before new objects are saved | |
513 | * | |
514 | * @param object $fieldXML | |
515 | * @return bool | |
516 | */ | |
517 | private function getPhpNullable($fieldXML) { | |
518 | $required = $this->value('required', $fieldXML); | |
519 | if ($required) { | |
520 | return FALSE; | |
521 | } | |
522 | ||
523 | return TRUE; | |
524 | } | |
525 | ||
2558c2b0 | 526 | /** |
100fef9d | 527 | * @param string $name |
2558c2b0 EM |
528 | * |
529 | * @return string | |
530 | */ | |
00be9182 | 531 | public function composeTitle($name) { |
5e434adf ARW |
532 | $names = explode('_', strtolower($name)); |
533 | $title = ''; | |
534 | for ($i = 0; $i < count($names); $i++) { | |
535 | if ($names[$i] === 'id' || $names[$i] === 'is') { | |
536 | // id's do not get titles | |
537 | return NULL; | |
538 | } | |
539 | ||
540 | if ($names[$i] === 'im') { | |
541 | $names[$i] = 'IM'; | |
542 | } | |
543 | else { | |
544 | $names[$i] = ucfirst(trim($names[$i])); | |
545 | } | |
546 | ||
547 | $title = $title . ' ' . $names[$i]; | |
548 | } | |
549 | return trim($title); | |
550 | } | |
551 | ||
2558c2b0 | 552 | /** |
21ca2cb6 | 553 | * @param object $primaryXML |
554 | * @param array $fields | |
555 | * @param array $table | |
2558c2b0 | 556 | */ |
00be9182 | 557 | public function getPrimaryKey(&$primaryXML, &$fields, &$table) { |
5e434adf ARW |
558 | $name = trim((string ) $primaryXML->name); |
559 | ||
5e434adf ARW |
560 | // set the autoincrement property of the field |
561 | $auto = $this->value('autoincrement', $primaryXML); | |
022785d8 JJ |
562 | if (isset($fields[$name])) { |
563 | $fields[$name]['autoincrement'] = $auto; | |
fc846b72 | 564 | $fields[$name]['phpNullable'] = TRUE; |
022785d8 | 565 | } |
179eeacb | 566 | |
be2fb01f | 567 | $primaryKey = [ |
5e434adf ARW |
568 | 'name' => $name, |
569 | 'autoincrement' => $auto, | |
be2fb01f | 570 | ]; |
022785d8 JJ |
571 | |
572 | // populate fields | |
573 | foreach ($primaryXML->fieldName as $v) { | |
574 | $fieldName = (string) ($v); | |
575 | $length = (string) ($v['length']); | |
576 | if (strlen($length) > 0) { | |
577 | $fieldName = "$fieldName($length)"; | |
578 | } | |
579 | $primaryKey['field'][] = $fieldName; | |
580 | } | |
581 | ||
582 | // when field array is empty set it to the name of the primary key. | |
583 | if (empty($primaryKey['field'])) { | |
584 | $primaryKey['field'][] = $name; | |
585 | } | |
586 | ||
587 | // all fieldnames have to be defined and should exist in schema. | |
588 | foreach ($primaryKey['field'] as $fieldName) { | |
589 | if (!$fieldName) { | |
a23ae008 | 590 | echo "Invalid field definition for index '$name' in table ${table['name']}\n"; |
022785d8 JJ |
591 | return; |
592 | } | |
593 | $parenOffset = strpos($fieldName, '('); | |
594 | if ($parenOffset > 0) { | |
595 | $fieldName = substr($fieldName, 0, $parenOffset); | |
596 | } | |
597 | if (!array_key_exists($fieldName, $fields)) { | |
a23ae008 | 598 | echo "Missing definition of field '$fieldName' for index '$name' in table ${table['name']}\n"; |
022785d8 JJ |
599 | print_r($fields); |
600 | exit(); | |
601 | } | |
602 | } | |
603 | ||
5e434adf ARW |
604 | $table['primaryKey'] = &$primaryKey; |
605 | } | |
606 | ||
2558c2b0 EM |
607 | /** |
608 | * @param $indexXML | |
609 | * @param $fields | |
610 | * @param $indices | |
611 | */ | |
00be9182 | 612 | public function getIndex(&$indexXML, &$fields, &$indices) { |
5e434adf ARW |
613 | //echo "\n\n*******************************************************\n"; |
614 | //echo "entering getIndex\n"; | |
615 | ||
be2fb01f | 616 | $index = []; |
5e434adf | 617 | // empty index name is fine |
353ffa53 TO |
618 | $indexName = trim((string) $indexXML->name); |
619 | $index['name'] = $indexName; | |
be2fb01f | 620 | $index['field'] = []; |
5e434adf ARW |
621 | |
622 | // populate fields | |
623 | foreach ($indexXML->fieldName as $v) { | |
2aa397bc TO |
624 | $fieldName = (string) ($v); |
625 | $length = (string) ($v['length']); | |
5e434adf ARW |
626 | if (strlen($length) > 0) { |
627 | $fieldName = "$fieldName($length)"; | |
628 | } | |
629 | $index['field'][] = $fieldName; | |
630 | } | |
631 | ||
632 | $index['localizable'] = FALSE; | |
633 | foreach ($index['field'] as $fieldName) { | |
634 | if (isset($fields[$fieldName]) and $fields[$fieldName]['localizable']) { | |
635 | $index['localizable'] = TRUE; | |
636 | break; | |
637 | } | |
638 | } | |
639 | ||
640 | // check for unique index | |
641 | if ($this->value('unique', $indexXML)) { | |
642 | $index['unique'] = TRUE; | |
643 | } | |
644 | ||
5e434adf ARW |
645 | // field array cannot be empty |
646 | if (empty($index['field'])) { | |
647 | echo "No fields defined for index $indexName\n"; | |
648 | return; | |
649 | } | |
650 | ||
651 | // all fieldnames have to be defined and should exist in schema. | |
652 | foreach ($index['field'] as $fieldName) { | |
653 | if (!$fieldName) { | |
a23ae008 | 654 | echo "Invalid field definition for index '$indexName'\n"; |
5e434adf ARW |
655 | return; |
656 | } | |
657 | $parenOffset = strpos($fieldName, '('); | |
658 | if ($parenOffset > 0) { | |
659 | $fieldName = substr($fieldName, 0, $parenOffset); | |
660 | } | |
661 | if (!array_key_exists($fieldName, $fields)) { | |
a23ae008 | 662 | echo "Missing definition of field '$fieldName' for index '$indexName'. Fields defined:\n"; |
5e434adf ARW |
663 | print_r($fields); |
664 | exit(); | |
665 | } | |
666 | } | |
667 | $indices[$indexName] = &$index; | |
668 | } | |
669 | ||
2558c2b0 EM |
670 | /** |
671 | * @param $foreignXML | |
672 | * @param $fields | |
673 | * @param $foreignKeys | |
100fef9d | 674 | * @param string $currentTableName |
2558c2b0 | 675 | */ |
00be9182 | 676 | public function getForeignKey(&$foreignXML, &$fields, &$foreignKeys, &$currentTableName) { |
5e434adf ARW |
677 | $name = trim((string ) $foreignXML->name); |
678 | ||
679 | /** need to make sure there is a field of type name */ | |
680 | if (!array_key_exists($name, $fields)) { | |
a23ae008 | 681 | echo "Foreign key '$name' in $currentTableName does not have a field definition, ignoring\n"; |
5e434adf ARW |
682 | return; |
683 | } | |
684 | ||
685 | /** need to check for existence of table and key **/ | |
686 | $table = trim($this->value('table', $foreignXML)); | |
be2fb01f | 687 | $foreignKey = [ |
5e434adf ARW |
688 | 'name' => $name, |
689 | 'table' => $table, | |
690 | 'uniqName' => "FK_{$currentTableName}_{$name}", | |
691 | 'key' => trim($this->value('key', $foreignXML)), | |
692 | 'import' => $this->value('import', $foreignXML, FALSE), | |
693 | 'export' => $this->value('import', $foreignXML, FALSE), | |
b44e3f84 | 694 | // we do this matching in a separate phase (resolveForeignKeys) |
5e434adf ARW |
695 | 'className' => NULL, |
696 | 'onDelete' => $this->value('onDelete', $foreignXML, FALSE), | |
be2fb01f | 697 | ]; |
5e434adf ARW |
698 | $foreignKeys[$name] = &$foreignKey; |
699 | } | |
700 | ||
2558c2b0 EM |
701 | /** |
702 | * @param $foreignXML | |
703 | * @param $dynamicForeignKeys | |
704 | */ | |
00be9182 | 705 | public function getDynamicForeignKey(&$foreignXML, &$dynamicForeignKeys) { |
be2fb01f | 706 | $foreignKey = [ |
5e434adf ARW |
707 | 'idColumn' => trim($foreignXML->idColumn), |
708 | 'typeColumn' => trim($foreignXML->typeColumn), | |
709 | 'key' => trim($this->value('key', $foreignXML)), | |
be2fb01f | 710 | ]; |
5e434adf ARW |
711 | $dynamicForeignKeys[] = $foreignKey; |
712 | } | |
713 | ||
2558c2b0 EM |
714 | /** |
715 | * @param $key | |
716 | * @param $object | |
717 | * @param null $default | |
718 | * | |
719 | * @return null|string | |
720 | */ | |
5e434adf ARW |
721 | protected function value($key, &$object, $default = NULL) { |
722 | if (isset($object->$key)) { | |
723 | return (string ) $object->$key; | |
724 | } | |
725 | return $default; | |
726 | } | |
727 | ||
2558c2b0 EM |
728 | /** |
729 | * @param $attributes | |
730 | * @param $object | |
100fef9d | 731 | * @param string $name |
2558c2b0 EM |
732 | * @param null $pre |
733 | * @param null $post | |
734 | */ | |
5e434adf ARW |
735 | protected function checkAndAppend(&$attributes, &$object, $name, $pre = NULL, $post = NULL) { |
736 | if (!isset($object->$name)) { | |
737 | return; | |
738 | } | |
739 | ||
740 | $value = $pre . trim($object->$name) . $post; | |
741 | $this->append($attributes, ' ', trim($value)); | |
742 | } | |
743 | ||
2558c2b0 EM |
744 | /** |
745 | * @param $str | |
746 | * @param $delim | |
747 | * @param $name | |
748 | */ | |
5e434adf ARW |
749 | protected function append(&$str, $delim, $name) { |
750 | if (empty($name)) { | |
751 | return; | |
752 | } | |
753 | ||
754 | if (is_array($name)) { | |
755 | foreach ($name as $n) { | |
756 | if (empty($n)) { | |
757 | continue; | |
758 | } | |
759 | if (empty($str)) { | |
760 | $str = $n; | |
761 | } | |
762 | else { | |
763 | $str .= $delim . $n; | |
764 | } | |
765 | } | |
766 | } | |
767 | else { | |
768 | if (empty($str)) { | |
769 | $str = $name; | |
770 | } | |
771 | else { | |
772 | $str .= $delim . $name; | |
773 | } | |
774 | } | |
775 | } | |
776 | ||
777 | /** | |
fe482240 | 778 | * Sets the size property of a textfield. |
ea3ddccf | 779 | * |
780 | * @param string $fieldXML | |
781 | * | |
782 | * @return null|string | |
5e434adf ARW |
783 | */ |
784 | protected function getSize($fieldXML) { | |
785 | // Extract from <size> tag if supplied | |
5e545f38 | 786 | if (!empty($fieldXML->html) && $this->value('size', $fieldXML->html)) { |
54f5e87f | 787 | return $this->value('size', $fieldXML->html); |
5e434adf ARW |
788 | } |
789 | // Infer from <length> tag if <size> was not explicitly set or was invalid | |
f53213b2 TM |
790 | // This map is slightly different from CRM_Core_Form_Renderer::$_sizeMapper |
791 | // Because we usually want fields to render as smaller than their maxlength | |
be2fb01f | 792 | $sizes = [ |
f53213b2 TM |
793 | 2 => 'TWO', |
794 | 4 => 'FOUR', | |
795 | 6 => 'SIX', | |
796 | 8 => 'EIGHT', | |
797 | 16 => 'TWELVE', | |
798 | 32 => 'MEDIUM', | |
799 | 64 => 'BIG', | |
be2fb01f | 800 | ]; |
f53213b2 TM |
801 | foreach ($sizes as $length => $name) { |
802 | if ($fieldXML->length <= $length) { | |
803 | return "CRM_Utils_Type::$name"; | |
804 | } | |
805 | } | |
806 | return 'CRM_Utils_Type::HUGE'; | |
5e434adf | 807 | } |
96025800 | 808 | |
5e434adf | 809 | } |