Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | ini_set('include_path', '.' . PATH_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'packages' . PATH_SEPARATOR . '..'); | |
0d035206 | 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 | } | |
6a488035 TO |
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 | ||
e9afc91e TO |
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 | |
6a488035 | 32 | ); |
e9afc91e | 33 | $genCode->main(); |
6a488035 TO |
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; | |
e9afc91e | 70 | var $db_version; |
6a488035 TO |
71 | var $compileDir; |
72 | var $classNames; | |
e9afc91e | 73 | var $cms; // drupal, joomla, wordpress |
6a488035 TO |
74 | |
75 | var $CoreDAOCodePath; | |
76 | var $sqlCodePath; | |
77 | var $phpCodePath; | |
78 | var $tplCodePath; | |
e9afc91e | 79 | var $schemaPath; // ex: schema/Schema.xml |
6a488035 TO |
80 | |
81 | var $smarty; | |
82 | ||
e9afc91e | 83 | function __construct($CoreDAOCodePath, $sqlCodePath, $phpCodePath, $tplCodePath, $smartyPluginDirs, $argCms, $argVersion, $schemaPath) { |
6a488035 TO |
84 | $this->CoreDAOCodePath = $CoreDAOCodePath; |
85 | $this->sqlCodePath = $sqlCodePath; | |
86 | $this->phpCodePath = $phpCodePath; | |
87 | $this->tplCodePath = $tplCodePath; | |
e9afc91e | 88 | $this->cms = $argCms; |
6a488035 TO |
89 | |
90 | require_once 'Smarty/Smarty.class.php'; | |
91 | $this->smarty = new Smarty(); | |
92 | $this->smarty->template_dir = './templates'; | |
e9afc91e | 93 | $this->smarty->plugins_dir = $smartyPluginDirs; |
6a488035 TO |
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); | |
e9afc91e TO |
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; | |
6a488035 TO |
126 | } |
127 | ||
128 | function __destruct() { | |
129 | CRM_GenCode_Util_File::removeDir($this->compileDir); | |
130 | } | |
131 | ||
132 | /** | |
133 | * Automatically generate a variety of files | |
134 | * | |
6a488035 | 135 | */ |
e9afc91e TO |
136 | function main() { |
137 | echo "\ncivicrm_domain.version := ". $this->db_version . "\n\n"; | |
6a488035 TO |
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 | ||
e9afc91e | 153 | $this->generateTemplateVersion(); |
6a488035 | 154 | |
e9afc91e | 155 | $this->setupCms($this->db_version); |
6a488035 | 156 | |
e9afc91e TO |
157 | echo "Parsing input file ".$this->schemaPath."\n"; |
158 | $dbXML = $this->parseInput($this->schemaPath); | |
6a488035 TO |
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(); | |
e9afc91e | 203 | $this->generateLocalDataSql($this->findLocales()); |
6a488035 TO |
204 | $this->generateSample(); |
205 | $this->generateInstallLangs(); | |
206 | $this->generateDAOs($tables); | |
207 | $this->generateSchemaStructure($tables); | |
208 | } | |
209 | ||
210 | function generateListAll($tables) { | |
6b5ff8c9 ARW |
211 | $this->smarty->clear_all_assign(); |
212 | $this->smarty->assign('tables', $tables); | |
4d1d7aca | 213 | file_put_contents($this->CoreDAOCodePath . "AllCoreTables.php", $this->smarty->fetch('listAll.tpl')); |
6a488035 TO |
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"; | |
d5864876 | 234 | $this->reset_smarty_assignments(); |
6a488035 TO |
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"; | |
d5864876 | 252 | $this->reset_smarty_assignments(); |
6a488035 TO |
253 | file_put_contents($this->sqlCodePath . "civicrm_navigation.mysql", $this->smarty->fetch('civicrm_navigation.tpl')); |
254 | } | |
255 | ||
e9afc91e | 256 | function generateLocalDataSql($locales) { |
d5864876 | 257 | $this->reset_smarty_assignments(); |
6a488035 TO |
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 | ||
e9afc91e | 273 | $data[] = " UPDATE civicrm_domain SET version = '" . $this->db_version . "';"; |
6a488035 TO |
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() { | |
d5864876 | 288 | $this->reset_smarty_assignments(); |
6a488035 TO |
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) { | |
6a488035 TO |
307 | foreach (array_keys($tables) as $name) { |
308 | $this->smarty->clear_all_cache(); | |
309 | echo "Generating $name as " . $tables[$name]['fileName'] . "\n"; | |
d5864876 | 310 | $this->reset_smarty_assignments(); |
6a488035 TO |
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 | ||
d5864876 | 357 | $this->reset_smarty_assignments(); |
6a488035 TO |
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 | ||
e9afc91e TO |
367 | function generateTemplateVersion() { |
368 | file_put_contents($this->tplCodePath . "/CRM/common/version.tpl", $this->db_version); | |
6a488035 TO |
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 | ||
e9afc91e | 399 | function setupCms() { |
6a488035 | 400 | // default cms is 'drupal', if not specified |
e9afc91e TO |
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."; | |
6a488035 TO |
405 | exit(); |
406 | } | |
e9afc91e TO |
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 | } | |
6a488035 TO |
415 | } |
416 | ||
417 | echo "Generating civicrm-version file\n"; | |
e9afc91e TO |
418 | $this->smarty->assign('db_version', $this->db_version); |
419 | $this->smarty->assign('cms', ucwords($this->cms)); | |
6a488035 TO |
420 | file_put_contents($this->phpCodePath . "civicrm-version.php", $this->smarty->fetch('civicrm_version.tpl')); |
421 | } | |
422 | ||
e9afc91e TO |
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 | ||
6a488035 TO |
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); | |
d5864876 ARW |
550 | $base = $this->value('base', $tableXML); |
551 | $sourceFile = "xml/schema/{$base}/{$klass}.xml"; | |
552 | $daoPath = "{$base}/DAO/"; | |
553 | $pre = str_replace('/', '_', $daoPath); | |
6a488035 TO |
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, | |
d5864876 ARW |
566 | 'base' => $daoPath, |
567 | 'sourceFile' => $sourceFile, | |
6a488035 TO |
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 | ||
71e5aa5c ARW |
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 | ||
6a488035 TO |
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); | |
04aec59c | 731 | $field['collate'] = $this->value('collate', $fieldXML); |
6a488035 TO |
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); | |
09d15676 | 750 | if(!empty($field['pseudoconstant'])){ |
6a488035 TO |
751 | //ok this is a bit long-winded but it gets there & is consistent with above approach |
752 | $field['pseudoconstant'] = array(); | |
091fe2a8 | 753 | $validOptions = array( |
6d68a4cb CW |
754 | // Fields can specify EITHER optionGroupName OR table, not both |
755 | // (since declaring optionGroupName means we are using the civicrm_option_value table) | |
091fe2a8 CW |
756 | 'optionGroupName', |
757 | 'table', | |
a38a89fc | 758 | // If table is specified, keyColumn and labelColumn are also required |
091fe2a8 CW |
759 | 'keyColumn', |
760 | 'labelColumn', | |
a38a89fc CW |
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) | |
091fe2a8 CW |
764 | 'condition', |
765 | ); | |
09d15676 | 766 | foreach ($validOptions as $pseudoOption) { |
6a488035 TO |
767 | if(!empty($fieldXML->pseudoconstant->$pseudoOption)){ |
768 | $field['pseudoconstant'][$pseudoOption] = $this->value($pseudoOption, $fieldXML->pseudoconstant); | |
769 | } | |
770 | } | |
09d15676 CW |
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 | } | |
6a488035 TO |
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 | ||
71e5aa5c ARW |
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 | ||
6a488035 TO |
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 | } | |
d5864876 ARW |
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 | } | |
6a488035 | 1002 | } |