Merge pull request #576 from eileenmcnaughton/CRM-12053
[civicrm-core.git] / xml / GenCode.php
1 <?php
2 ini_set('include_path', '.' . PATH_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'packages' . PATH_SEPARATOR . '..');
3 ini_set('memory_limit', '512M');
4 date_default_timezone_set('UTC'); // avoid php warnings if timezone is not set - CRM-10844
5
6 define('CIVICRM_UF', 'Drupal');
7
8 require_once 'CRM/Core/ClassLoader.php';
9 CRM_Core_ClassLoader::singleton()->register();
10
11 $genCode = new CRM_GenCode_Main('../CRM/Core/DAO/', '../sql/', '../', '../templates/');
12 $genCode->main(
13 @$argv[2],
14 @$argv[3],
15 empty($argv[1]) ? 'schema/Schema.xml' : $argv[1]
16 );
17
18 class CRM_GenCode_Util_File {
19 static function createDir($dir, $perm = 0755) {
20 if (!is_dir($dir)) {
21 mkdir($dir, $perm, TRUE);
22 }
23 }
24
25 static function removeDir($dir) {
26 foreach (glob("$dir/*") as $tempFile) {
27 unlink($tempFile);
28 }
29 rmdir($dir);
30 }
31
32 static function createTempDir($prefix) {
33 if (isset($_SERVER['TMPDIR'])) {
34 $tempDir = $_SERVER['TMPDIR'];
35 }
36 else {
37 $tempDir = '/tmp';
38 }
39
40 $newTempDir = $tempDir . '/' . $prefix . rand(1, 10000);
41
42 if (file_exists($newTempDir)) {
43 self::removeDir($newTempDir);
44 }
45 self::createDir($newTempDir);
46
47 return $newTempDir;
48 }
49 }
50
51 class CRM_GenCode_Main {
52 var $buildVersion;
53 var $compileDir;
54 var $classNames;
55
56 var $CoreDAOCodePath;
57 var $sqlCodePath;
58 var $phpCodePath;
59 var $tplCodePath;
60
61 var $smarty;
62
63 function __construct($CoreDAOCodePath, $sqlCodePath, $phpCodePath, $tplCodePath) {
64 $this->CoreDAOCodePath = $CoreDAOCodePath;
65 $this->sqlCodePath = $sqlCodePath;
66 $this->phpCodePath = $phpCodePath;
67 $this->tplCodePath = $tplCodePath;
68
69 require_once 'Smarty/Smarty.class.php';
70 $this->smarty = new Smarty();
71 $this->smarty->template_dir = './templates';
72 $this->smarty->plugins_dir = array('../packages/Smarty/plugins', '../CRM/Core/Smarty/plugins');
73 $this->compileDir = CRM_GenCode_Util_File::createTempDir('templates_c_');
74 $this->smarty->compile_dir = $this->compileDir;
75 $this->smarty->clear_all_cache();
76
77 // CRM-5308 / CRM-3507 - we need {localize} to work in the templates
78 require_once 'CRM/Core/Smarty/plugins/block.localize.php';
79 $this->smarty->register_block('localize', 'smarty_block_localize');
80
81 require_once 'PHP/Beautifier.php';
82 // create a instance
83 $this->beautifier = new PHP_Beautifier();
84 $this->beautifier->addFilter('ArrayNested');
85 // add one or more filters
86 $this->beautifier->addFilter('Pear');
87 // add one or more filters
88 $this->beautifier->addFilter('NewLines', array('after' => 'class, public, require, comment'));
89 $this->beautifier->setIndentChar(' ');
90 $this->beautifier->setIndentNumber(2);
91 $this->beautifier->setNewLine("\n");
92
93 CRM_GenCode_Util_File::createDir($this->sqlCodePath);
94 }
95
96 function __destruct() {
97 CRM_GenCode_Util_File::removeDir($this->compileDir);
98 }
99
100 /**
101 * Automatically generate a variety of files
102 *
103 * @param $argVersion string, optional
104 * @param $argCms string, optional; "drupal" or "joomla"
105 * @param $file, the path to the XML schema file
106 */
107 function main($argVersion, $argCms, $file) {
108 $versionFile = "version.xml";
109 $versionXML = &$this->parseInput($versionFile);
110 $db_version = $versionXML->version_no;
111 $this->buildVersion = preg_replace('/^(\d{1,2}\.\d{1,2})\.(\d{1,2}|\w{4,7})$/i', '$1', $db_version);
112 if (isset($argVersion)) {
113 // change the version to that explicitly passed, if any
114 $db_version = $argVersion;
115 }
116 echo "\ncivicrm_domain.version := $db_version\n\n";
117 if ($this->buildVersion < 1.1) {
118 echo "The Database is not compatible for this version";
119 exit();
120 }
121
122 if (substr(phpversion(), 0, 1) != 5) {
123 echo phpversion() . ', ' . substr(phpversion(), 0, 1) . "\n";
124 echo "
125 CiviCRM requires a PHP Version >= 5
126 Please upgrade your php / webserver configuration
127 Alternatively you can get a version of CiviCRM that matches your PHP version
128 ";
129 exit();
130 }
131
132 $this->generateTemplateVersion($argVersion);
133
134 $this->setupCms($argCms, $db_version);
135
136 echo "Parsing input file $file\n";
137 $dbXML = $this->parseInput($file);
138 // print_r( $dbXML );
139
140 echo "Extracting database information\n";
141 $database = &$this->getDatabase($dbXML);
142 // print_r( $database );
143
144 $this->classNames = array();
145
146 echo "Extracting table information\n";
147 $tables = &$this->getTables($dbXML, $database);
148
149 $this->resolveForeignKeys($tables, $this->classNames);
150 $tables = $this->orderTables($tables);
151
152 // add archive tables here
153 $archiveTables = array( );
154 foreach ($tables as $name => $table ) {
155 if ( $table['archive'] == 'true' ) {
156 $name = 'archive_' . $table['name'];
157 $table['name'] = $name;
158 $table['archive'] = 'false';
159 if ( isset($table['foreignKey']) ) {
160 foreach ($table['foreignKey'] as $fkName => $fkValue) {
161 if ($tables[$fkValue['table']]['archive'] == 'true') {
162 $table['foreignKey'][$fkName]['table'] = 'archive_' . $table['foreignKey'][$fkName]['table'];
163 $table['foreignKey'][$fkName]['uniqName'] =
164 str_replace( 'FK_', 'FK_archive_', $table['foreignKey'][$fkName]['uniqName'] );
165 }
166 }
167 $archiveTables[$name] = $table;
168 }
169 }
170 }
171
172 $this->generateListAll($tables);
173 $this->generateCiviTestTruncate($tables);
174 $this->generateCreateSql($database, $tables, 'civicrm.mysql');
175 $this->generateDropSql($tables, 'civicrm_drop.mysql');
176
177 // also create the archive tables
178 // $this->generateCreateSql($database, $archiveTables, 'civicrm_archive.mysql' );
179 // $this->generateDropSql($archiveTables, 'civicrm_archive_drop.mysql');
180
181 $this->generateNavigation();
182 $this->generateLocalDataSql($db_version, $this->findLocales());
183 $this->generateSample();
184 $this->generateInstallLangs();
185 $this->generateDAOs($tables);
186 $this->generateSchemaStructure($tables);
187 }
188
189 function generateListAll($tables) {
190 $this->smarty->clear_all_assign();
191 $this->smarty->assign('tables', $tables);
192 file_put_contents($this->CoreDAOCodePath . "AllCoreTables.php", $this->smarty->fetch('listAll.tpl'));
193 }
194
195 function generateCiviTestTruncate($tables) {
196 echo "Generating tests truncate file\n";
197
198 $truncate = '<?xml version="1.0" encoding="UTF-8" ?>
199 <!-- Truncate all tables that will be used in the tests -->
200 <dataset>';
201 $tbls = array_keys($tables);
202 foreach ($tbls as $d => $t) {
203 $truncate = $truncate . "\n <$t />\n";
204 }
205
206 $truncate = $truncate . "</dataset>\n";
207 file_put_contents($this->sqlCodePath . "../tests/phpunit/CiviTest/truncate.xml", $truncate);
208 unset($truncate);
209 }
210
211 function generateCreateSql($database, $tables, $fileName = 'civicrm.mysql') {
212 echo "Generating sql file\n";
213 $this->reset_smarty_assignments();
214 $this->smarty->assign_by_ref('database', $database);
215 $this->smarty->assign_by_ref('tables', $tables);
216 $dropOrder = array_reverse(array_keys($tables));
217 $this->smarty->assign_by_ref('dropOrder', $dropOrder);
218 $this->smarty->assign('mysql', 'modern');
219 file_put_contents($this->sqlCodePath . $fileName, $this->smarty->fetch('schema.tpl'));
220 }
221
222 function generateDropSql($tables, $fileName = 'civicrm_drop.mysql') {
223 echo "Generating sql drop tables file\n";
224 $dropOrder = array_reverse(array_keys($tables));
225 $this->smarty->assign_by_ref('dropOrder', $dropOrder);
226 file_put_contents($this->sqlCodePath . $fileName, $this->smarty->fetch('drop.tpl'));
227 }
228
229 function generateNavigation() {
230 echo "Generating navigation file\n";
231 $this->reset_smarty_assignments();
232 file_put_contents($this->sqlCodePath . "civicrm_navigation.mysql", $this->smarty->fetch('civicrm_navigation.tpl'));
233 }
234
235 function generateLocalDataSql($db_version, $locales) {
236 $this->reset_smarty_assignments();
237
238 global $tsLocale;
239 $oldTsLocale = $tsLocale;
240 foreach ($locales as $locale) {
241 echo "Generating data files for $locale\n";
242 $tsLocale = $locale;
243 $this->smarty->assign('locale', $locale);
244
245 $data = array();
246 $data[] = $this->smarty->fetch('civicrm_country.tpl');
247 $data[] = $this->smarty->fetch('civicrm_state_province.tpl');
248 $data[] = $this->smarty->fetch('civicrm_currency.tpl');
249 $data[] = $this->smarty->fetch('civicrm_data.tpl');
250 $data[] = $this->smarty->fetch('civicrm_navigation.tpl');
251
252 $data[] = " UPDATE civicrm_domain SET version = '$db_version';";
253
254 $data = implode("\n", $data);
255
256 $ext = ($locale != 'en_US' ? ".$locale" : '');
257 // write the initialize base-data sql script
258 file_put_contents($this->sqlCodePath . "civicrm_data$ext.mysql", $data);
259
260 // write the acl sql script
261 file_put_contents($this->sqlCodePath . "civicrm_acl$ext.mysql", $this->smarty->fetch('civicrm_acl.tpl'));
262 }
263 $tsLocale = $oldTsLocale;
264 }
265
266 function generateSample() {
267 $this->reset_smarty_assignments();
268 $sample = $this->smarty->fetch('civicrm_sample.tpl');
269 $sample .= $this->smarty->fetch('civicrm_acl.tpl');
270 file_put_contents($this->sqlCodePath . 'civicrm_sample.mysql', $sample);
271 }
272
273 function generateInstallLangs() {
274 // CRM-7161: generate install/langs.php from the languages template
275 // grep it for enabled languages and create a 'xx_YY' => 'Language name' $langs mapping
276 $matches = array();
277 preg_match_all('/, 1, \'([a-z][a-z]_[A-Z][A-Z])\', \'..\', \{localize\}\'\{ts escape="sql"\}(.+)\{\/ts\}\'\{\/localize\}, /', file_get_contents('templates/languages.tpl'), $matches);
278 $langs = array();
279 for ($i = 0; $i < count($matches[0]); $i++) {
280 $langs[$matches[1][$i]] = $matches[2][$i];
281 }
282 file_put_contents('../install/langs.php', "<?php \$langs = unserialize('" . serialize($langs) . "');");
283 }
284
285 function generateDAOs($tables) {
286 foreach (array_keys($tables) as $name) {
287 $this->smarty->clear_all_cache();
288 echo "Generating $name as " . $tables[$name]['fileName'] . "\n";
289 $this->reset_smarty_assignments();
290
291 $this->smarty->assign_by_ref('table', $tables[$name]);
292 $php = $this->smarty->fetch('dao.tpl');
293
294 $this->beautifier->setInputString($php);
295
296 if (empty($tables[$name]['base'])) {
297 echo "No base defined for $name, skipping output generation\n";
298 continue;
299 }
300
301 $directory = $this->phpCodePath . $tables[$name]['base'];
302 CRM_GenCode_Util_File::createDir($directory);
303 $this->beautifier->setOutputFile($directory . $tables[$name]['fileName']);
304 // required
305 $this->beautifier->process();
306
307 $this->beautifier->save();
308 }
309 }
310
311 function generateSchemaStructure($tables) {
312 echo "Generating CRM_Core_I18n_SchemaStructure...\n";
313 $columns = array();
314 $indices = array();
315 foreach ($tables as $table) {
316 if ($table['localizable']) {
317 $columns[$table['name']] = array();
318 }
319 else {
320 continue;
321 }
322 foreach ($table['fields'] as $field) {
323 if ($field['localizable']) {
324 $columns[$table['name']][$field['name']] = $field['sqlType'];
325 }
326 }
327 if (isset($table['index'])) {
328 foreach ($table['index'] as $index) {
329 if ($index['localizable']) {
330 $indices[$table['name']][$index['name']] = $index;
331 }
332 }
333 }
334 }
335
336 $this->reset_smarty_assignments();
337 $this->smarty->assign_by_ref('columns', $columns);
338 $this->smarty->assign_by_ref('indices', $indices);
339
340 $this->beautifier->setInputString($this->smarty->fetch('schema_structure.tpl'));
341 $this->beautifier->setOutputFile($this->phpCodePath . "/CRM/Core/I18n/SchemaStructure.php");
342 $this->beautifier->process();
343 $this->beautifier->save();
344 }
345
346 function generateTemplateVersion($argVersion) {
347 // add the Subversion revision to templates
348 // use svnversion if the version was not specified explicitely on the commandline
349 if (isset($argVersion) and $argVersion != '') {
350 $svnversion = $argVersion;
351 }
352 else {
353 $svnversion = `svnversion .`;
354 }
355 file_put_contents($this->tplCodePath . "/CRM/common/version.tpl", $svnversion);
356 }
357
358 function findLocales() {
359 require_once 'CRM/Core/Config.php';
360 $config = CRM_Core_Config::singleton(FALSE);
361 $locales = array();
362 if (substr($config->gettextResourceDir, 0, 1) === '/') {
363 $localeDir = $config->gettextResourceDir;
364 }
365 else {
366 $localeDir = '../' . $config->gettextResourceDir;
367 }
368 if (file_exists($localeDir)) {
369 $config->gettextResourceDir = $localeDir;
370 $locales = preg_grep('/^[a-z][a-z]_[A-Z][A-Z]$/', scandir($localeDir));
371 }
372
373 $localesMask = getenv('CIVICRM_LOCALES');
374 if (!empty($localesMask)) {
375 $mask = explode(',', $localesMask);
376 $locales = array_intersect($locales, $mask);
377 }
378
379 if (!in_array('en_US', $locales)) {
380 array_unshift($locales, 'en_US');
381 }
382
383 return $locales;
384 }
385
386 function setupCms($argCms, $db_version) {
387 // default cms is 'drupal', if not specified
388 $cms = isset($argCms) ? strtolower($argCms) : 'drupal';
389 if (!in_array($cms, array(
390 'drupal', 'joomla'))) {
391 echo "Config file for '{$cms}' not known.";
392 exit();
393 }
394 elseif ($cms !== 'joomla') {
395 echo "Generating civicrm.config.php\n";
396 copy("../{$cms}/civicrm.config.php.{$cms}", '../civicrm.config.php');
397 }
398
399 echo "Generating civicrm-version file\n";
400 $this->smarty->assign('db_version', $db_version);
401 $this->smarty->assign('cms', ucwords($cms));
402 file_put_contents($this->phpCodePath . "civicrm-version.php", $this->smarty->fetch('civicrm_version.tpl'));
403 }
404
405 // -----------------------------
406 // ---- Schema manipulation ----
407 // -----------------------------
408 function &parseInput($file) {
409 $dom = new DomDocument();
410 $dom->load($file);
411 $dom->xinclude();
412 $dbXML = simplexml_import_dom($dom);
413 return $dbXML;
414 }
415
416 function &getDatabase(&$dbXML) {
417 $database = array('name' => trim((string ) $dbXML->name));
418
419 $attributes = '';
420 $this->checkAndAppend($attributes, $dbXML, 'character_set', 'DEFAULT CHARACTER SET ', '');
421 $this->checkAndAppend($attributes, $dbXML, 'collate', 'COLLATE ', '');
422 $database['attributes'] = $attributes;
423
424 $tableAttributes_modern = $tableAttributes_simple = '';
425 $this->checkAndAppend($tableAttributes_modern, $dbXML, 'table_type', 'ENGINE=', '');
426 $this->checkAndAppend($tableAttributes_simple, $dbXML, 'table_type', 'TYPE=', '');
427 $database['tableAttributes_modern'] = trim($tableAttributes_modern . ' ' . $attributes);
428 $database['tableAttributes_simple'] = trim($tableAttributes_simple);
429
430 $database['comment'] = $this->value('comment', $dbXML, '');
431
432 return $database;
433 }
434
435 function &getTables(&$dbXML, &$database) {
436 $tables = array();
437 foreach ($dbXML->tables as $tablesXML) {
438 foreach ($tablesXML->table as $tableXML) {
439 if ($this->value('drop', $tableXML, 0) > 0 and $this->value('drop', $tableXML, 0) <= $this->buildVersion) {
440 continue;
441 }
442
443 if ($this->value('add', $tableXML, 0) <= $this->buildVersion) {
444 $this->getTable($tableXML, $database, $tables);
445 }
446 }
447 }
448
449 return $tables;
450 }
451
452 function resolveForeignKeys(&$tables, &$classNames) {
453 foreach (array_keys($tables) as $name) {
454 $this->resolveForeignKey($tables, $classNames, $name);
455 }
456 }
457
458 function resolveForeignKey(&$tables, &$classNames, $name) {
459 if (!array_key_exists('foreignKey', $tables[$name])) {
460 return;
461 }
462
463 foreach (array_keys($tables[$name]['foreignKey']) as $fkey) {
464 $ftable = $tables[$name]['foreignKey'][$fkey]['table'];
465 if (!array_key_exists($ftable, $classNames)) {
466 echo "$ftable is not a valid foreign key table in $name\n";
467 continue;
468 }
469 $tables[$name]['foreignKey'][$fkey]['className'] = $classNames[$ftable];
470 $tables[$name]['foreignKey'][$fkey]['fileName'] = str_replace('_', '/', $classNames[$ftable]) . '.php';
471 $tables[$name]['fields'][$fkey]['FKClassName'] = $classNames[$ftable];
472 }
473 }
474
475 function orderTables(&$tables) {
476 $ordered = array();
477
478 while (!empty($tables)) {
479 foreach (array_keys($tables) as $name) {
480 if ($this->validTable($tables, $ordered, $name)) {
481 $ordered[$name] = $tables[$name];
482 unset($tables[$name]);
483 }
484 }
485 }
486 return $ordered;
487 }
488
489 function validTable(&$tables, &$valid, $name) {
490 if (!array_key_exists('foreignKey', $tables[$name])) {
491 return TRUE;
492 }
493
494 foreach (array_keys($tables[$name]['foreignKey']) as $fkey) {
495 $ftable = $tables[$name]['foreignKey'][$fkey]['table'];
496 if (!array_key_exists($ftable, $valid) && $ftable !== $name) {
497 return FALSE;
498 }
499 }
500 return TRUE;
501 }
502
503 function getTable($tableXML, &$database, &$tables) {
504 $name = trim((string ) $tableXML->name);
505 $klass = trim((string ) $tableXML->class);
506 $base = $this->value('base', $tableXML);
507 $sourceFile = "xml/schema/{$base}/{$klass}.xml";
508 $daoPath = "{$base}/DAO/";
509 $pre = str_replace('/', '_', $daoPath);
510 $this->classNames[$name] = $pre . $klass;
511
512 $localizable = FALSE;
513 foreach ($tableXML->field as $fieldXML) {
514 if ($fieldXML->localizable) {
515 $localizable = TRUE;
516 break;
517 }
518 }
519
520 $table = array(
521 'name' => $name,
522 'base' => $daoPath,
523 'sourceFile' => $sourceFile,
524 'fileName' => $klass . '.php',
525 'objectName' => $klass,
526 'labelName' => substr($name, 8),
527 'className' => $this->classNames[$name],
528 'attributes_simple' => trim($database['tableAttributes_simple']),
529 'attributes_modern' => trim($database['tableAttributes_modern']),
530 'comment' => $this->value('comment', $tableXML),
531 'localizable' => $localizable,
532 'log' => $this->value('log', $tableXML, 'false'),
533 'archive' => $this->value('archive', $tableXML, 'false'),
534 );
535
536 $fields = array();
537 foreach ($tableXML->field as $fieldXML) {
538 if ($this->value('drop', $fieldXML, 0) > 0 and $this->value('drop', $fieldXML, 0) <= $this->buildVersion) {
539 continue;
540 }
541
542 if ($this->value('add', $fieldXML, 0) <= $this->buildVersion) {
543 $this->getField($fieldXML, $fields);
544 }
545 }
546
547 $table['fields'] = &$fields;
548 $table['hasEnum'] = FALSE;
549 foreach ($table['fields'] as $field) {
550 if ($field['crmType'] == 'CRM_Utils_Type::T_ENUM') {
551 $table['hasEnum'] = TRUE;
552 break;
553 }
554 }
555
556 if ($this->value('primaryKey', $tableXML)) {
557 $this->getPrimaryKey($tableXML->primaryKey, $fields, $table);
558 }
559
560 // some kind of refresh?
561 CRM_Core_Config::singleton(FALSE);
562 if ($this->value('index', $tableXML)) {
563 $index = array();
564 foreach ($tableXML->index as $indexXML) {
565 if ($this->value('drop', $indexXML, 0) > 0 and $this->value('drop', $indexXML, 0) <= $this->buildVersion) {
566 continue;
567 }
568
569 $this->getIndex($indexXML, $fields, $index);
570 }
571 $table['index'] = &$index;
572 }
573
574 if ($this->value('foreignKey', $tableXML)) {
575 $foreign = array();
576 foreach ($tableXML->foreignKey as $foreignXML) {
577 // print_r($foreignXML);
578
579 if ($this->value('drop', $foreignXML, 0) > 0 and $this->value('drop', $foreignXML, 0) <= $this->buildVersion) {
580 continue;
581 }
582 if ($this->value('add', $foreignXML, 0) <= $this->buildVersion) {
583 $this->getForeignKey($foreignXML, $fields, $foreign, $name);
584 }
585 }
586 $table['foreignKey'] = &$foreign;
587 }
588
589 if ($this->value('dynamicForeignKey', $tableXML)) {
590 $dynamicForeign = array();
591 foreach ($tableXML->dynamicForeignKey as $foreignXML) {
592 if ($this->value('drop', $foreignXML, 0) > 0 and $this->value('drop', $foreignXML, 0) <= $this->buildVersion) {
593 continue;
594 }
595 if ($this->value('add', $foreignXML, 0) <= $this->buildVersion) {
596 $this->getDynamicForeignKey($foreignXML, $dynamicForeign, $name);
597 }
598 }
599 $table['dynamicForeignKey'] = $dynamicForeign;
600 }
601
602 $tables[$name] = &$table;
603 return;
604 }
605
606 function getField(&$fieldXML, &$fields) {
607 $name = trim((string ) $fieldXML->name);
608 $field = array('name' => $name, 'localizable' => $fieldXML->localizable);
609 $type = (string ) $fieldXML->type;
610 switch ($type) {
611 case 'varchar':
612 case 'char':
613 $field['length'] = (int) $fieldXML->length;
614 $field['sqlType'] = "$type({$field['length']})";
615 $field['phpType'] = 'string';
616 $field['crmType'] = 'CRM_Utils_Type::T_STRING';
617 $field['size'] = $this->getSize($fieldXML);
618 break;
619
620 case 'enum':
621 $value = (string ) $fieldXML->values;
622 $field['sqlType'] = 'enum(';
623 $field['values'] = array();
624 $field['enumValues'] = $value;
625 $values = explode(',', $value);
626 $first = TRUE;
627 foreach ($values as $v) {
628 $v = trim($v);
629 $field['values'][] = $v;
630
631 if (!$first) {
632 $field['sqlType'] .= ', ';
633 }
634 $first = FALSE;
635 $field['sqlType'] .= "'$v'";
636 }
637 $field['sqlType'] .= ')';
638 $field['phpType'] = $field['sqlType'];
639 $field['crmType'] = 'CRM_Utils_Type::T_ENUM';
640 break;
641
642 case 'text':
643 $field['sqlType'] = $field['phpType'] = $type;
644 $field['crmType'] = 'CRM_Utils_Type::T_' . strtoupper($type);
645 $field['rows'] = $this->value('rows', $fieldXML);
646 $field['cols'] = $this->value('cols', $fieldXML);
647 break;
648
649 case 'datetime':
650 $field['sqlType'] = $field['phpType'] = $type;
651 $field['crmType'] = 'CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME';
652 break;
653
654 case 'boolean':
655 // need this case since some versions of mysql do not have boolean as a valid column type and hence it
656 // is changed to tinyint. hopefully after 2 yrs this case can be removed.
657 $field['sqlType'] = 'tinyint';
658 $field['phpType'] = $type;
659 $field['crmType'] = 'CRM_Utils_Type::T_' . strtoupper($type);
660 break;
661
662 case 'decimal':
663 $length = $fieldXML->length ? $fieldXML->length : '20,2';
664 $field['sqlType'] = 'decimal(' . $length . ')';
665 $field['phpType'] = 'float';
666 $field['crmType'] = 'CRM_Utils_Type::T_MONEY';
667 break;
668
669 case 'float':
670 $field['sqlType'] = 'double';
671 $field['phpType'] = 'float';
672 $field['crmType'] = 'CRM_Utils_Type::T_FLOAT';
673 break;
674
675 default:
676 $field['sqlType'] = $field['phpType'] = $type;
677 if ($type == 'int unsigned') {
678 $field['crmType'] = 'CRM_Utils_Type::T_INT';
679 }
680 else {
681 $field['crmType'] = 'CRM_Utils_Type::T_' . strtoupper($type);
682 }
683 break;
684 }
685
686 $field['required'] = $this->value('required', $fieldXML);
687 $field['comment'] = $this->value('comment', $fieldXML);
688 $field['default'] = $this->value('default', $fieldXML);
689 $field['import'] = $this->value('import', $fieldXML);
690 if ($this->value('export', $fieldXML)) {
691 $field['export'] = $this->value('export', $fieldXML);
692 }
693 else {
694 $field['export'] = $this->value('import', $fieldXML);
695 }
696 $field['rule'] = $this->value('rule', $fieldXML);
697 $field['title'] = $this->value('title', $fieldXML);
698 if (!$field['title']) {
699 $field['title'] = $this->composeTitle($name);
700 }
701 $field['headerPattern'] = $this->value('headerPattern', $fieldXML);
702 $field['dataPattern'] = $this->value('dataPattern', $fieldXML);
703 $field['uniqueName'] = $this->value('uniqueName', $fieldXML);
704 $field['pseudoconstant'] = $this->value('pseudoconstant', $fieldXML);
705 if(!empty($fieldXML->pseudoconstant)){
706 //ok this is a bit long-winded but it gets there & is consistent with above approach
707 $field['pseudoconstant'] = array();
708 $validOptions = array('name', 'optionGroupName', 'table', 'keyColumn', 'labelColumn','class');
709 foreach ($validOptions as $pseudoOption){
710 if(!empty($fieldXML->pseudoconstant->$pseudoOption)){
711 $field['pseudoconstant'][$pseudoOption] = $this->value($pseudoOption, $fieldXML->pseudoconstant);
712 }
713 }
714 }
715 $fields[$name] = &$field;
716 }
717
718 function composeTitle($name) {
719 $names = explode('_', strtolower($name));
720 $title = '';
721 for ($i = 0; $i < count($names); $i++) {
722 if ($names[$i] === 'id' || $names[$i] === 'is') {
723 // id's do not get titles
724 return NULL;
725 }
726
727 if ($names[$i] === 'im') {
728 $names[$i] = 'IM';
729 }
730 else {
731 $names[$i] = ucfirst(trim($names[$i]));
732 }
733
734 $title = $title . ' ' . $names[$i];
735 }
736 return trim($title);
737 }
738
739 function getPrimaryKey(&$primaryXML, &$fields, &$table) {
740 $name = trim((string ) $primaryXML->name);
741
742 /** need to make sure there is a field of type name */
743 if (!array_key_exists($name, $fields)) {
744 echo "primary key $name in $table->name does not have a field definition, ignoring\n";
745 return;
746 }
747
748 // set the autoincrement property of the field
749 $auto = $this->value('autoincrement', $primaryXML);
750 $fields[$name]['autoincrement'] = $auto;
751 $primaryKey = array(
752 'name' => $name,
753 'autoincrement' => $auto,
754 );
755 $table['primaryKey'] = &$primaryKey;
756 }
757
758 function getIndex(&$indexXML, &$fields, &$indices) {
759 //echo "\n\n*******************************************************\n";
760 //echo "entering getIndex\n";
761
762 $index = array();
763 // empty index name is fine
764 $indexName = trim((string)$indexXML->name);
765 $index['name'] = $indexName;
766 $index['field'] = array();
767
768 // populate fields
769 foreach ($indexXML->fieldName as $v) {
770 $fieldName = (string)($v);
771 $length = (string)($v['length']);
772 if (strlen($length) > 0) {
773 $fieldName = "$fieldName($length)";
774 }
775 $index['field'][] = $fieldName;
776 }
777
778 $index['localizable'] = FALSE;
779 foreach ($index['field'] as $fieldName) {
780 if (isset($fields[$fieldName]) and $fields[$fieldName]['localizable']) {
781 $index['localizable'] = TRUE;
782 break;
783 }
784 }
785
786 // check for unique index
787 if ($this->value('unique', $indexXML)) {
788 $index['unique'] = TRUE;
789 }
790
791 //echo "\$index = \n";
792 //print_r($index);
793
794 // field array cannot be empty
795 if (empty($index['field'])) {
796 echo "No fields defined for index $indexName\n";
797 return;
798 }
799
800 // all fieldnames have to be defined and should exist in schema.
801 foreach ($index['field'] as $fieldName) {
802 if (!$fieldName) {
803 echo "Invalid field defination for index $indexName\n";
804 return;
805 }
806 $parenOffset = strpos($fieldName, '(');
807 if ($parenOffset > 0) {
808 $fieldName = substr($fieldName, 0, $parenOffset);
809 }
810 if (!array_key_exists($fieldName, $fields)) {
811 echo "Table does not contain $fieldName\n";
812 print_r($fields);
813 CRM_GenCode_Util_File::removeDir($this->compileDir);
814 exit();
815 }
816 }
817 $indices[$indexName] = &$index;
818 }
819
820 function getForeignKey(&$foreignXML, &$fields, &$foreignKeys, &$currentTableName) {
821 $name = trim((string ) $foreignXML->name);
822
823 /** need to make sure there is a field of type name */
824 if (!array_key_exists($name, $fields)) {
825 echo "foreign $name in $currentTableName does not have a field definition, ignoring\n";
826 return;
827 }
828
829 /** need to check for existence of table and key **/
830 $table = trim($this->value('table', $foreignXML));
831 $foreignKey = array(
832 'name' => $name,
833 'table' => $table,
834 'uniqName' => "FK_{$currentTableName}_{$name}",
835 'key' => trim($this->value('key', $foreignXML)),
836 'import' => $this->value('import', $foreignXML, FALSE),
837 'export' => $this->value('import', $foreignXML, FALSE),
838 // we do this matching in a seperate phase (resolveForeignKeys)
839 'className' => NULL,
840 'onDelete' => $this->value('onDelete', $foreignXML, FALSE),
841 );
842 $foreignKeys[$name] = &$foreignKey;
843 }
844
845 function getDynamicForeignKey(&$foreignXML, &$dynamicForeignKeys) {
846 $foreignKey = array(
847 'idColumn' => trim($foreignXML->idColumn),
848 'typeColumn' => trim($foreignXML->typeColumn),
849 'key' => trim($this->value('key', $foreignXML)),
850 );
851 $dynamicForeignKeys[] = $foreignKey;
852 }
853
854 protected function value($key, &$object, $default = NULL) {
855 if (isset($object->$key)) {
856 return (string ) $object->$key;
857 }
858 return $default;
859 }
860
861 protected function checkAndAppend(&$attributes, &$object, $name, $pre = NULL, $post = NULL) {
862 if (!isset($object->$name)) {
863 return;
864 }
865
866 $value = $pre . trim($object->$name) . $post;
867 $this->append($attributes, ' ', trim($value));
868 }
869
870 protected function append(&$str, $delim, $name) {
871 if (empty($name)) {
872 return;
873 }
874
875 if (is_array($name)) {
876 foreach ($name as $n) {
877 if (empty($n)) {
878 continue;
879 }
880 if (empty($str)) {
881 $str = $n;
882 }
883 else {
884 $str .= $delim . $n;
885 }
886 }
887 }
888 else {
889 if (empty($str)) {
890 $str = $name;
891 }
892 else {
893 $str .= $delim . $name;
894 }
895 }
896 }
897
898 /**
899 * Sets the size property of a textfield
900 * See constants defined in CRM_Utils_Type for possible values
901 */
902 protected function getSize($fieldXML) {
903 // Extract from <size> tag if supplied
904 if ($this->value('size', $fieldXML)) {
905 $const = 'CRM_Utils_Type::' . strtoupper($fieldXML->size);
906 if (defined($const)) {
907 return $const;
908 }
909 }
910 // Infer from <length> tag if <size> was not explicitly set or was invalid
911
912 // This map is slightly different from CRM_Core_Form_Renderer::$_sizeMapper
913 // Because we usually want fields to render as smaller than their maxlength
914 $sizes = array(
915 2 => 'TWO',
916 4 => 'FOUR',
917 6 => 'SIX',
918 8 => 'EIGHT',
919 16 => 'TWELVE',
920 32 => 'MEDIUM',
921 64 => 'BIG',
922 );
923 foreach ($sizes as $length => $name) {
924 if ($fieldXML->length <= $length) {
925 return "CRM_Utils_Type::$name";
926 }
927 }
928 return 'CRM_Utils_Type::HUGE';
929 }
930
931 /**
932 * Clear the smarty cache and assign default values
933 */
934 function reset_smarty_assignments() {
935 $this->smarty->clear_all_assign();
936 $this->smarty->clear_all_cache();
937 $this->smarty->assign('generated', "DO NOT EDIT. Generated by " . basename(__FILE__));
938 }
939 }