1 <?php
2 3 4
5 if(!defined('MANIFEST_FILE')) define("MANIFEST_FILE", TEMP_FOLDER . "/manifest-" . basename($_SERVER['SCRIPT_FILENAME']));
6
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
24 class ManifestBuilder {
25
26 static $restrict_to_modules = array();
27 static $extendsArray = array();
28 static $classArray = array();
29 static $implementsArray = array();
30
31 32 33 34
35 public static $ignore_files = array(
36 'main.php',
37 'cli-script.php',
38 'install.php',
39 'index.php',
40 );
41
42 43 44 45
46 public static $ignore_folders = array(
47 'mysql',
48 'assets',
49 'shortstat',
50 'HTML',
51 );
52
53 54 55
56 static function include_manifest() {
57 if(isset($_REQUEST['usetestmanifest'])) {
58 self::load_test_manifest();
59 } else {
60
61 if (
62 !file_exists(MANIFEST_FILE)
63 || isset($_GET['flush'])
64 || (isset($_REQUEST['url']) && ($_REQUEST['url'] == 'dev/build' || $_REQUEST['url'] == BASE_URL . '/dev/build'))
65 ) {
66 self::create_manifest_file();
67 }
68 require_once(MANIFEST_FILE);
69 }
70 }
71
72 73 74 75
76 static function load_test_manifest() {
77 $testManifestFile = MANIFEST_FILE . '-test';
78
79
80 if(!file_exists($testManifestFile)
81 || (filemtime($testManifestFile) < filemtime(BASE_PATH))
82 || isset($_GET['flush'])) {
83
84
85 $manifestInfo = self::get_manifest_info(BASE_PATH);
86 $manifest = self::generate_php_file($manifestInfo);
87 if($fh = fopen($testManifestFile, 'wb')) {
88 fwrite($fh, $manifest);
89 fclose($fh);
90 } else {
91 user_error("Cannot write manifest file! Check permissions of " . MANIFEST_FILE, E_USER_ERROR);
92 }
93 }
94
95 require($testManifestFile);
96 }
97
98 99 100
101 static function load_all_classes() {
102 global $_CLASS_MANIFEST;
103 foreach($_CLASS_MANIFEST as $classFile) require_once($classFile);
104 }
105
106 107 108
109 static function create_manifest_file() {
110
111 $manifestInfo = self::get_manifest_info(BASE_PATH, array("tests"));
112
113 $manifest = self::generate_php_file($manifestInfo);
114 if($fh = fopen(MANIFEST_FILE, 'wb')) {
115 fwrite($fh, $manifest);
116 fclose($fh);
117 } else {
118 user_error("Cannot write manifest file! Check permissions of " . MANIFEST_FILE, E_USER_ERROR);
119 }
120 }
121
122 123 124
125 static function generate_php_file($manifestInfo) {
126 $output = "<?php\n";
127
128 foreach($manifestInfo['globals'] as $globalName => $globalVal) {
129 $output .= "global \$$globalName;\n\$$globalName = " . var_export($globalVal, true) . ";\n\n";
130 }
131 foreach($manifestInfo['require_once'] as $requireItem) {
132 $output .= "require_once('" . addslashes($requireItem) . "');\n";
133 }
134
135 return preg_replace("!'".BASE_PATH."!", "BASE_PATH . '", $output);
136 }
137
138
139 140 141
142 static function process_manifest($manifestInfo) {
143 foreach($manifestInfo['globals'] as $globalName => $globalVal) {
144 global $$globalName;
145 $$globalName = $globalVal;
146 }
147 foreach($manifestInfo['require_once'] as $requireItem) {
148 require_once("$requireItem");
149 }
150 }
151
152 153 154 155 156 157 158
159 public static function get_themes($baseDir = null, $includeSubThemes = false) {
160
161 if(!$baseDir) $baseDir = BASE_PATH . DIRECTORY_SEPARATOR . THEMES_DIR;
162 $themes = array();
163 if(!file_exists($baseDir)) return $themes;
164
165 $handle = opendir($baseDir);
166 if($handle) {
167 while(false !== ($file = readdir($handle))) {
168 $fullPath = $baseDir . DIRECTORY_SEPARATOR . $file;
169 if(strpos($file, '.') === false && is_dir($fullPath)) {
170 $include = $includeSubThemes ? true : false;
171 if(strpos($file, '_') === false) {
172 $include = true;
173 }
174 if($include) $themes[$file] = $file;
175 }
176 }
177 closedir($handle);
178 }
179 return $themes;
180 }
181
182 183 184 185 186 187
188 static function get_manifest_info($baseDir, $excludedFolders = array()) {
189 global $project;
190
191 $topLevel = scandir($baseDir);
192 foreach($topLevel as $file) {
193 if($file[0] == '.') continue;
194
195 $fullPath = '';
196 $fullPath = $baseDir . '/' . $file;
197
198 if(@is_dir($fullPath . '/') && file_exists($fullPath . '/_exclude.php')) {
199 require_once($fullPath . '/_exclude.php');
200 }
201 }
202
203
204 if (!isset($project) || !$project) $project = 'site';
205
206
207 $allPhpFiles = array();
208 $templateManifest = array();
209 $cssManifest = array();
210
211
212 if(is_array(self::$restrict_to_modules) && count(self::$restrict_to_modules)) {
213
214
215 foreach(self::$restrict_to_modules as $module)
216 ManifestBuilder::get_all_php_files($baseDir . '/' . $module, $excludedFolders, $allPhpFiles);
217 } else {
218
219
220 $topLevel = scandir($baseDir);
221 foreach($topLevel as $filename) {
222 if($filename[0] == '.') continue;
223 if($filename == 'themes') continue;
224 if($filename == 'assets') continue;
225 if(in_array($filename, $excludedFolders)) continue;
226
227 if(@is_dir("$baseDir/$filename") &&
228 file_exists("$baseDir/$filename/_config.php") &&
229 !file_exists("$baseDir/$filename/_manifest_exclude")) {
230
231
232 ManifestBuilder::get_all_php_files("$baseDir/$filename", $excludedFolders, $allPhpFiles);
233 ManifestBuilder::getTemplateManifest($baseDir, $filename, $excludedFolders, $templateManifest, $cssManifest);
234
235
236 if ($filename != $project) {
237 $manifestInfo["require_once"][] = "$baseDir/$filename/_config.php";
238
239 if (file_exists("$baseDir/$filename/_00config.php"))
240 $manifestInfo["require_once00"][] = "$baseDir/$filename/_00config.php";
241 }
242 }
243 }
244 }
245 if (array_key_exists("require_once00", $manifestInfo) && is_array($manifestInfo["require_once00"]) && count($manifestInfo["require_once00"])) {
246 $manifestInfo["require_once"] = array_merge($manifestInfo["require_once00"],$manifestInfo["require_once"]);
247 }
248
249 if (file_exists("$baseDir/$project/_00config.php"))
250 array_unshift($manifestInfo["require_once"], "$baseDir/$project/_00config.php");
251
252 $manifestInfo["require_once"][] = "$baseDir/$project/_config.php";
253
254
255 if(file_exists("$baseDir/themes")) {
256 $themeDirs = self::get_themes("$baseDir/themes", true);
257 foreach($themeDirs as $themeDir) {
258 $themeName = strtok($themeDir, '_');
259 ManifestBuilder::getTemplateManifest($baseDir, "themes/$themeDir", $excludedFolders, $templateManifest, $cssManifest, $themeName);
260 }
261 }
262
263
264 $allClasses = ManifestBuilder::allClasses($allPhpFiles);
265
266
267 $classManifest = $allClasses['file'];
268 unset($allClasses['file']);
269
270
271 if(!$project) user_error("\$project isn't set", E_USER_WARNING);
272 else if(!file_exists("$baseDir/$project")) user_error("\$project is set to '$project' but no such folder exists.", E_USER_WARNING);
273 else ManifestBuilder::getTemplateManifest($baseDir, $project, $excludedFolders, $templateManifest, $cssManifest);
274
275 $manifestInfo["globals"]["_CLASS_MANIFEST"] = $classManifest;
276 $manifestInfo["globals"]["_ALL_CLASSES"] = $allClasses;
277 $manifestInfo["globals"]["_TEMPLATE_MANIFEST"] = $templateManifest;
278 $manifestInfo["globals"]["_CSS_MANIFEST"] = $cssManifest;
279
280 return $manifestInfo;
281 }
282
283
284 285 286 287 288 289
290 private static function get_all_php_files($folder, $excludedFolders, &$allPhpFiles) {
291 $items = scandir($folder);
292 if($items) foreach($items as $item) {
293
294 if(in_array($item, self::$ignore_files)) continue;
295
296
297 if(substr($item,0,1) == '.') continue;
298
299
300 if(substr($item,-4) != '.php' && !@is_dir("$folder/$item")) continue;
301
302
303 if(substr($item,0,1) == '_') continue;
304
305
306 if(@is_dir("$folder/$item") && in_array($item, self::$ignore_folders)) continue;
307
308
309 if(@is_dir("$folder/$item") && file_exists("$folder/$item/_manifest_exclude")) continue;
310
311
312 if($item == 'lang' && @is_dir("$folder/$item") && ereg_replace("/[^/]+/\\.\\.","",$folder.'/..') == Director::baseFolder()) continue;
313
314 if(@is_dir("$folder/$item")) {
315
316 if(in_array($item, $excludedFolders)) continue;
317
318
319 ManifestBuilder::get_all_php_files("$folder/$item", $excludedFolders, $allPhpFiles);
320 } else {
321 $allPhpFiles[] = "$folder/$item";
322 }
323
324 }
325 }
326
327
328 329 330 331
332 private static function getTemplateManifest($baseDir, $folder, $excludedFolders, &$templateManifest, &$cssManifest, $themeName = null) {
333 $items = scandir("$baseDir/$folder");
334 if($items) foreach($items as $item) {
335 if(substr($item,0,1) == '.') continue;
336 if(substr($item,-3) == '.ss') {
337 $templateName = substr($item, 0, -3);
338 $templateType = substr($folder,strrpos($folder,'/')+1);
339 if($templateType == "templates") $templateType = "main";
340
341 if($themeName) {
342 $templateManifest[$templateName]['themes'][$themeName][$templateType] = "$baseDir/$folder/$item";
343 } else {
344 $templateManifest[$templateName][$templateType] = "$baseDir/$folder/$item";
345 }
346
347 } else if(substr($item,-4) == '.css') {
348 $cssName = substr($item, 0, -4);
349
350
351 if($themeName) {
352 $cssManifest[$cssName]['themes'][$themeName] = "$folder/$item";
353 } else {
354 $cssManifest[$cssName]['unthemed'] = "$folder/$item";
355 }
356
357
358 } else if(@is_dir("$baseDir/$folder/$item")) {
359
360 if(in_array($item, $excludedFolders)) continue;
361
362 ManifestBuilder::getTemplateManifest($baseDir, "$folder/$item", $excludedFolders, $templateManifest, $cssManifest, $themeName);
363 }
364 }
365 }
366
367
368 369 370 371 372 373 374 375 376
377 private static function allClasses($classManifest) {
378 self::$classArray = array();
379 self::$extendsArray = array();
380 self::$implementsArray = array();
381
382
383 foreach($classManifest as $file) {
384 $b = basename($file);
385 if($b != 'cli-script.php' && $b != 'main.php')
386 self::parse_file($file);
387 }
388
389 $allClasses["parents"] = self::find_parents();
390 $allClasses["children"] = self::find_children();
391 $allClasses["implementors"] = self::$implementsArray;
392
393 foreach(self::$classArray as $class => $info) {
394 $allClasses['exists'][$class] = $class;
395 $allClasses['file'][strtolower($class)] = $info['file'];
396 }
397
398
399 $_classes = get_declared_classes();
400
401 foreach($_classes as $class) {
402 $allClasses['exists'][$class] = $class;
403 foreach($_classes as $subclass) {
404 if(is_subclass_of($class, $subclass)) $allClasses['parents'][$class][$subclass] = $subclass;
405 if(is_subclass_of($subclass, $class)) $allClasses['children'][$class][$subclass] = $subclass;
406 }
407 }
408
409 return $allClasses;
410 }
411
412 413 414 415 416
417 private static function parse_file($filename) {
418 $file = file_get_contents($filename);
419
420 $implements = "";
421 $extends = "";
422 $class="";
423
424 if($file === null) user_error("ManifestBuilder::parse_file(): Couldn't open $filename", E_USER_ERROR);
425 if(!$file) return;
426
427
428
429
430
431 $fileMD5 = md5($file);
432 $parseCacheFile = TEMP_FOLDER . "/manifestClassParse-" . str_replace(array("/", ":", "\\", "."), "_", basename($filename)) . "-${fileMD5}.php";
433 if(file_exists($parseCacheFile)) {
434 include($parseCacheFile);
435
436 if(!isset($classes) || !isset($interfaces) || !is_array($classes) || !is_array($interfaces)) {
437 unset($classes);
438 unset($interfaces);
439 }
440 }
441
442
443 if(!isset($classes)) {
444 $tokens = token_get_all($file);
445 $classes = (array)self::getClassDefParser()->findAll($tokens);
446 $interfaces = (array)self::getInterfaceDefParser()->findAll($tokens);
447
448 $cacheContent = '<?php
449 $classes = ' . var_export($classes,true) . ';
450 $interfaces = ' . var_export($interfaces,true) . ';';
451
452 if($fh = fopen($parseCacheFile, 'wb')) {
453 fwrite($fh, $cacheContent);
454 fclose($fh);
455 }
456 }
457
458 foreach($classes as $class) {
459 $className = $class['className'];
460 unset($class['className']);
461 $class['file'] = $filename;
462 if(!isset($class['extends'])) $class['extends'] = null;
463
464 if($class['extends']) self::$extendsArray[$class['extends']][$className] = $className;
465 if(isset($class['interfaces'])) foreach($class['interfaces'] as $interface) {
466 self::$implementsArray[$interface][$className] = $className;
467 }
468
469 if(isset(self::$classArray[$className])) {
470 $file1 = self::$classArray[$className]['file'];
471 $file2 = $class['file'];
472 user_error("There are two files both containing the same class: '$file1' and " .
473 "'$file2'. This might mean that the wrong code is being used.", E_USER_WARNING);
474 }
475
476 self::$classArray[$className] = $class;
477 }
478
479 foreach($interfaces as $interface) {
480 $className = $interface['interfaceName'];
481 unset($interface['interfaceName']);
482 $interface['file'] = $filename;
483 if(!isset($interface['extends'])) $interface['extends'] = null;
484
485 if(isset(self::$classArray[$className])) {
486 $file1 = self::$classArray[$className]['file'];
487 $file2 = $interface[$className];
488 user_error("There are two files both containing the same class: '$file1' and " .
489 "'$file2'. This might mean that the wrong code is being used.", E_USER_WARNING);
490 }
491
492 self::$classArray[$className] = $interface;
493 }
494 }
495
496 497 498 499
500 public static function getClassDefParser() {
501 require_once('core/TokenisedRegularExpression.php');
502
503 return new TokenisedRegularExpression(array(
504 0 => T_CLASS,
505 1 => T_WHITESPACE,
506 2 => array(T_STRING, 'can_jump_to' => array(7, 14), 'save_to' => 'className'),
507 3 => T_WHITESPACE,
508 4 => T_EXTENDS,
509 5 => T_WHITESPACE,
510 6 => array(T_STRING, 'save_to' => 'extends', 'can_jump_to' => 14),
511 7 => T_WHITESPACE,
512 8 => T_IMPLEMENTS,
513 9 => T_WHITESPACE,
514 10 => array(T_STRING, 'can_jump_to' => 14, 'save_to' => 'interfaces[]'),
515 11 => array(T_WHITESPACE, 'optional' => true),
516 12 => array(',', 'can_jump_to' => 10),
517 13 => array(T_WHITESPACE, 'can_jump_to' => 10),
518 14 => array(T_WHITESPACE, 'optional' => true),
519 15 => '{',
520 ));
521 }
522
523 524 525 526
527 public static function getInterfaceDefParser() {
528 require_once('core/TokenisedRegularExpression.php');
529
530 return new TokenisedRegularExpression(array(
531 0 => T_INTERFACE,
532 1 => T_WHITESPACE,
533 2 => array(T_STRING, 'can_jump_to' => 7, 'save_to' => 'interfaceName'),
534 3 => T_WHITESPACE,
535 4 => T_EXTENDS,
536 5 => T_WHITESPACE,
537 6 => array(T_STRING, 'save_to' => 'extends'),
538 7 => array(T_WHITESPACE, 'optional' => true),
539 8 => '{',
540 ));
541 }
542
543
544 545 546 547 548
549 private static function find_parents() {
550 $parentArray = array();
551 foreach(self::$classArray as $class => $info) {
552 $extendArray = array();
553
554 $parent = $info["extends"];
555
556 while($parent) {
557 $extendArray[$parent] = $parent;
558 $parent = isset(self::$classArray[$parent]["extends"]) ? self::$classArray[$parent]["extends"] : null;
559 }
560 $parentArray[$class] = array_reverse($extendArray);
561 }
562 return $parentArray;
563 }
564
565 566 567 568 569
570 private static function find_children() {
571 $childrenArray = array();
572 foreach(self::$extendsArray as $class => $children) {
573 $allChildren = $children;
574 foreach($children as $childName) {
575 $allChildren = array_merge($allChildren, self::up_children($childName));
576 }
577 $childrenArray[$class] = $allChildren;
578 }
579 return $childrenArray;
580 }
581
582 583 584 585 586 587
588 private static function get_children($class) {
589 return isset(self::$extendsArray[$class]) ? self::$extendsArray[$class] : array();
590 }
591
592 593 594 595 596
597 static function has_been_included() {
598 global $_CLASS_MANIFEST, $_TEMPLATE_MANIFEST, $_CSS_MANIFEST, $_ALL_CLASSES;
599 return (bool)!(empty($_CLASS_MANIFEST) && empty($_TEMPLATE_MANIFEST) && empty($_CSS_MANIFEST) && empty($_ALL_CLASSES));
600 }
601
602 603 604 605 606 607
608 static function up_children($class) {
609 $children = self::get_Children($class);
610 $results = $children;
611 foreach($children as $className) {
612 $results = array_merge($results, self::up_children($className));
613 }
614 return $results;;
615 }
616 }
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-