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