diff --git a/core/ClassInfo.php b/core/ClassInfo.php index c72f370c3..5831ce421 100755 --- a/core/ClassInfo.php +++ b/core/ClassInfo.php @@ -10,7 +10,7 @@ class ClassInfo { */ static function ready() { global $_ALL_CLASSES; - return $_ALL_CLASSES && isset($_ALL_CLASSES['hastable']) && $_ALL_CLASSES['hastable']; + return $_ALL_CLASSES && $_ALL_CLASSES['hastable']; } static function allClasses() { global $_ALL_CLASSES; @@ -102,30 +102,12 @@ class ClassInfo { } /** - * Return all the class names implementing the given interface. - * Note: this method is slow; if that becomes a problem you may need to reimplement this to cache results in - * the manifest. - * @return array A self-keyed array of class names + * @return array A self-keyed array of class names. Note that this is only available with Silverstripe + * classes and not built-in PHP classes. */ - protected static $implementors_of = array(); static function implementorsOf($interfaceName) { - if(array_key_exists($interfaceName, self::$implementors_of)) return self::$implementors_of[$interfaceName]; - - $matchingClasses = array(); - - $classes = self::allClasses(); - foreach($classes as $potentialClass) { - if(class_exists($potentialClass)) { - $refl = new ReflectionClass($potentialClass); - if($refl->implementsInterface($interfaceName)) { - $matchingClasses[$potentialClass] = $potentialClass; - } - } - } - - self::$implementors_of[$interfaceName] = $matchingClasses; - - return $matchingClasses; + global $_ALL_CLASSES; + return $_ALL_CLASSES['implementors'][$interfaceName]; } } ?> \ No newline at end of file diff --git a/core/ManifestBuilder.php b/core/ManifestBuilder.php index 6d3c653e2..28c953b2d 100644 --- a/core/ManifestBuilder.php +++ b/core/ManifestBuilder.php @@ -31,6 +31,9 @@ define("MANIFEST_FILE", TEMP_FOLDER . "/manifest" . str_replace(array("/",":", " class ManifestBuilder { static $restrict_to_modules = array(); + static $extendsArray = array(); + static $classArray = array(); + static $implementsArray = array(); /** * @var array $ignore_files Full filenames (without directory-path) which @@ -50,12 +53,12 @@ class ManifestBuilder { * should be ignored by the manifest. */ public static $ignore_folders = array( - 'mysql', - 'assets', - 'shortstat', + 'mysql', + 'assets', + 'shortstat', 'HTML', ); - + /** * Returns true if the manifest file should be regenerated @@ -90,7 +93,7 @@ class ManifestBuilder { foreach($topLevel as $file) { $fullPath = $baseDir . '/' . $file; - if(is_dir($fullPath . '/') && file_exists($fullPath . '/_exclude.php')) + if(@is_dir($fullPath . '/') && file_exists($fullPath . '/_exclude.php')) require_once($fullPath . '/_exclude.php'); } @@ -108,9 +111,9 @@ class ManifestBuilder { // have an _manifest_exclude file $topLevel = scandir($baseDir); foreach($topLevel as $filename) { - if(is_dir("$baseDir/$filename") && - file_exists("$baseDir/$filename/_config.php") && - !file_exists("$baseDir/$filename/_manifest_exclude")) { + if(@is_dir("$baseDir/$filename") && + file_exists("$baseDir/$filename/_config.php") && + !file_exists("$baseDir/$filename/_manifest_exclude")) { ManifestBuilder::getClassManifest("$baseDir/$filename", $classManifest); } @@ -130,8 +133,8 @@ class ManifestBuilder { global $databaseConfig; $topLevel = scandir($baseDir); foreach($topLevel as $filename) { - if(is_dir("$baseDir/$filename/") && - file_exists("$baseDir/$filename/_config.php") && + if(@is_dir("$baseDir/$filename/") && + file_exists("$baseDir/$filename/_config.php") && !file_exists("$baseDir/$filename/_manifest_exclude")) { $manifest .= "require_once(\"$baseDir/$filename/_config.php\");\n"; // Include this so that we're set up for connecting to the database @@ -151,11 +154,11 @@ class ManifestBuilder { $topLevel = scandir($baseDir); foreach($topLevel as $filename) { if(substr($filename,0,1) == '.') continue; - if($filename != 'themes' && is_dir("$baseDir/$filename") && file_exists("$baseDir/$filename/_config.php")) { + if($filename != 'themes' && @is_dir("$baseDir/$filename") && file_exists("$baseDir/$filename/_config.php")) { ManifestBuilder::getTemplateManifest($baseDir, $filename, $templateManifest, $cssManifest); } } - + // Get themes if(file_exists("$baseDir/themes")) { $themeDirs = scandir("$baseDir/themes"); @@ -173,12 +176,12 @@ class ManifestBuilder { $manifest .= "\$_TEMPLATE_MANIFEST = " . var_export($templateManifest, true) . ";\n"; $manifest .= "\$_CSS_MANIFEST = " . var_export($cssManifest, true) . ";\n"; DB::connect($databaseConfig); - + // Database manifest $allClasses = ManifestBuilder::allClasses($classManifest); - + $manifest .= "\$_ALL_CLASSES = " . var_export($allClasses, true) . ";\n"; - + global $_ALL_CLASSES; $_ALL_CLASSES = $allClasses; @@ -207,27 +210,27 @@ class ManifestBuilder { if($items) foreach($items as $item) { // Skip some specific PHP files if(in_array($item, self::$ignore_files)) continue; - + // ignore hidden files and folders if(substr($item,0,1) == '.') continue; - + // ignore files without php-extension - if(substr($item,-4) != '.php' && !is_dir("$folder/$item")) continue; - + if(substr($item,-4) != '.php' && !@is_dir("$folder/$item")) continue; + // ignore files and folders with underscore-prefix if(substr($item,0,1) == '_') continue; - - // ignore certain directories - if(is_dir("$folder/$item") && in_array($item, self::$ignore_folders)) continue; + + // ignore certain directories + if(@is_dir("$folder/$item") && in_array($item, self::$ignore_folders)) continue; // ignore directories with _manifest_exlude file - if(is_dir("$folder/$item") && file_exists("$folder/$item/_manifest_exclude")) continue; - + if(@is_dir("$folder/$item") && file_exists("$folder/$item/_manifest_exclude")) continue; + // i18n: ignore language files (loaded on demand) - if($item == 'lang' && is_dir("$folder/$item") && ereg_replace("/[^/]+/\\.\\.","",$folder.'/..') == Director::baseFolder()) continue; - - if(is_dir("$folder/$item")) { - // recurse into directories (if not in $ignore_folders) + if($item == 'lang' && @is_dir("$folder/$item") && ereg_replace("/[^/]+/\\.\\.","",$folder.'/..') == Director::baseFolder()) continue; + + if(@is_dir("$folder/$item")) { + // recurse into directories (if not in $ignore_folders) ManifestBuilder::getClassManifest("$folder/$item", $classMap); } else { // include item in the manifest @@ -236,13 +239,13 @@ class ManifestBuilder { if($classMap && array_key_exists($itemCode, $classMap)) { $regex = '/class\s' . $itemCode .'/'; if( - preg_match($regex, file_get_contents("$folder/$item")) + preg_match($regex, file_get_contents("$folder/$item")) && preg_match($regex, file_get_contents($classMap[$itemCode])) ) { - user_error("Warning: there are two '$itemCode' files both containing the same class: '$folder/$item' and '{$classMap[$itemCode]}'. + user_error("Warning: there are two '$itemCode' files both containing the same class: '$folder/$item' and '{$classMap[$itemCode]}'. This might mean that the wrong code is being used.", E_USER_WARNING); } else { - user_error("Warning: there are two '$itemCode' files with the same filename: '$folder/$item' and '{$classMap[$itemCode]}'. + user_error("Warning: there are two '$itemCode' files with the same filename: '$folder/$item' and '{$classMap[$itemCode]}'. This might mean that the wrong code is being used.", E_USER_NOTICE); } } else { @@ -266,11 +269,11 @@ class ManifestBuilder { $templateName = substr($item, 0, -3); $templateType = substr($folder,strrpos($folder,'/')+1); if($templateType == "templates") $templateType = "main"; - + if($themeName) { - $templateManifest[$templateName]['themes'][$themeName][$templateType] = "$baseDir/$folder/$item"; + $templateManifest[$templateName]['themes'][$themeName][$templateType] = "$baseDir/$folder/$item"; } else { - $templateManifest[$templateName][$templateType] = "$baseDir/$folder/$item"; + $templateManifest[$templateName][$templateType] = "$baseDir/$folder/$item"; } } else if(substr($item,-4) == '.css') { @@ -278,13 +281,13 @@ class ManifestBuilder { // Debug::message($item); if($themeName) { - $cssManifest[$cssName]['themes'][$themeName] = "$folder/$item"; + $cssManifest[$cssName]['themes'][$themeName] = "$folder/$item"; } else { - $cssManifest[$cssName]['unthemed'] = "$folder/$item"; + $cssManifest[$cssName]['unthemed'] = "$folder/$item"; } - } else if(is_dir("$baseDir/$folder/$item")) { + } else if(@is_dir("$baseDir/$folder/$item")) { ManifestBuilder::getTemplateManifest($baseDir, "$folder/$item", $templateManifest, $cssManifest, $themeName); } } @@ -305,51 +308,148 @@ class ManifestBuilder { foreach($classManifest as $file) { $b = basename($file); if($b != 'cli-script.php' && $b != 'main.php') - include_once($file); + self::parse_file($file); } - - if(DB::isActive()) { - $tables = DB::getConn()->tableList(); - } else { - $tables = array(); + + $tables = DB::getConn()->tableList(); + + $allClasses["parents"] = self::find_parents(); + $allClasses["children"] = self::find_children(); + $allClasses["implementors"] = self::$implementsArray; + + foreach(self::$classArray as $class => $info) { + $allClasses['exists'][$class] = $class; + if(isset($tables[strtolower($class)])) $allClasses['hastable'][$class] = $class; } - - $allClasses['hastable'] = array(); - + // Build a map of classes and their subclasses $_classes = get_declared_classes(); + foreach($_classes as $class) { $allClasses['exists'][$class] = $class; - - if(isset($tables[strtolower($class)])) - $allClasses['hastable'][$class] = $class; - + if(isset($tables[strtolower($class)])) $allClasses['hastable'][$class] = $class; foreach($_classes as $subclass) { - if(is_subclass_of($class, $subclass)) - $allClasses['parents'][$class][$subclass] = $subclass; - - if(is_subclass_of($subclass, $class)) - $allClasses['children'][$class][$subclass] = $subclass; + if(is_subclass_of($class, $subclass)) $allClasses['parents'][$class][$subclass] = $subclass; + if(is_subclass_of($subclass, $class)) $allClasses['children'][$class][$subclass] = $subclass; } } return $allClasses; } +/** + * Parses a php file and adds any class or interface information into self::$classArray + * + * @param string $filename + */ + private static function parse_file($filename) { + $file = file_get_contents($filename); + + $implements = ""; + $extends = ""; + $class=""; + + if(!$file) die("Couldn't open $filename
"); + + $classes = array(); + $size = preg_match_all('/class (.*){/', $file, $classes); + + for($i=0; $i < $size; $i++) { + //we have a class + $args = split("implements", $classes[1][$i]); + $implements = isset($args[1]) ? $args[1] : null; + + $interfaces = explode(",", $implements); + $args = split("extends", $args[0]); + $extends = trim(isset($args[1]) ? $args[1] : null); + $class = trim($args[0]); + if($extends) self::$extendsArray[$extends][$class] = $class; + + foreach($interfaces as $interface) { + self::$implementsArray[$interface][$class] = $class; + } + + self::$classArray[$class] = array( + "interfaces" => $interfaces, + "extends" => $extends, + "file" => $filename + ); + } + + $interfaces = array(); + $size = preg_match_all('/interface (.*){/', $file, $interfaces); + + for($i=0;$i<$size;$i++) { + $class = trim($interfaces[1][$i]); + self::$classArray[$class] = array( + "interfaces"=>array(), + "extends" => "", + "isinterface"=>true + ); + } + } /** - * Include all files of the class manifest so that that actually *all* - * classes are available + * Moves through self::$classArray and creates an array containing parent data + * + * @return array */ - static function includeEverything() { - global $_CLASS_MANIFEST; + private static function find_parents() { + $parentArray = array(); + foreach(self::$classArray as $class => $info) { + $extendArray = array(); - foreach($_CLASS_MANIFEST as $filename) { - if(preg_match('/.*cli-script\.php$/', $filename)) - continue; + $parent = $info["extends"]; - require_once($filename); + while($parent) { + $extendArray[$parent] = $parent; + $parent = isset(self::$classArray[$parent]["extends"]) ? self::$classArray[$parent]["extends"] : null; + } + $parentArray[$class] = array_reverse($extendArray); } + return $parentArray; + } + + /** + * Iterates through self::$classArray and returns an array with any descendant data + * + * @return array + */ + private static function find_children() { + $childrenArray = array(); + foreach(self::$extendsArray as $class => $children) { + $allChildren = $children; + foreach($children as $childName) { + $allChildren = array_merge($allChildren, self::up_children($childName)); + } + $childrenArray[$class] = $allChildren; + } + return $childrenArray; + } + + /** + * Helper function to find all children of give class + * + * @param string $class + * @return array + */ + private static function get_children($class) { + return isset(self::$extendsArray[$class]) ? self::$extendsArray[$class] : array(); + } + + /** + * Returns a flat array with all children of a given class + * + * @param string $class + * @param array $results + */ + function up_children($class) { + $children = self::get_Children($class); + $results = $children; + foreach($children as $className) { + $results = array_merge($results, self::up_children($className)); + } + return $results;; } /** @@ -361,15 +461,15 @@ class ManifestBuilder { $_ALL_CLASSES['hastable'] = array(); $tables = DB::getConn()->tableList(); - + // We need to iterate through the full class lists, because the table names come out in lowercase foreach($_ALL_CLASSES['exists'] as $class) { if(isset($tables[strtolower($class)])) $_ALL_CLASSES['hastable'][$class] = $class; } - + self::write_manifest(); } - + /** * Write the manifest file, containing the updated values in the applicable globals */ @@ -379,12 +479,12 @@ class ManifestBuilder { $manifest = "\$_CLASS_MANIFEST = " . var_export($_CLASS_MANIFEST, true) . ";\n"; // Config manifest - $baseDir = dirname($_SERVER['SCRIPT_FILENAME']) . "/.."; + $baseDir = dirname($_SERVER['SCRIPT_FILENAME']) . "/.."; $baseDir = ereg_replace("/[^/]+/\\.\\.","",$baseDir); $topLevel = scandir($baseDir); foreach($topLevel as $filename) { - if(is_dir("$baseDir/$filename/") && file_exists("$baseDir/$filename/_config.php")) { + if(@is_dir("$baseDir/$filename/") && file_exists("$baseDir/$filename/_config.php")) { $manifest .= "require_once(\"$baseDir/$filename/_config.php\");\n"; } } @@ -394,7 +494,7 @@ class ManifestBuilder { $manifest .= "\$_ALL_CLASSES = " . var_export($_ALL_CLASSES, true) . ";\n"; $manifest = ""; - if($fh = fopen(MANIFEST_FILE,"w")) { + if($fh = fopen(MANIFEST_FILE,"w")) { fwrite($fh, $manifest); fclose($fh);