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