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