• all classes
  • *
  • all templates
  • *
  • all _config.php files
  • * Traversing the filesystem to collect this information on everypage * This information is cached so that it need not be regenerated on every pageview. */ define("MANIFEST_FILE", TEMP_FOLDER . "/manifest" . str_replace(array("/",":", "\\"),"_", $_SERVER['SCRIPT_FILENAME'])); class ManifestBuilder { static $restrict_to_modules = array(); /** * @var array $ignore_files Full filenames (without directory-path) which * should be ignored by the manifest. */ public static $ignore_files = array( 'main.php', 'cli-script.php', 'install.php', 'index.php', 'check-php.php', 'rewritetest.php' ); /** * @var array $ignore_folders Foldernames (without path) which * should be ignored by the manifest. */ public static $ignore_folders = array( 'mysql', 'assets', 'shortstat', 'pear', ); /** * Returns true if the manifest file should be regenerated */ static function staleManifest() { /*if(Director::isDev() || Director::isTest()) $lastEdited = Filesystem::folderModTime(".", array('ss','php')); else*/ $lastEdited = filemtime("../"); return !file_exists(MANIFEST_FILE) || (filemtime(MANIFEST_FILE) < $lastEdited) || (filemtime(MANIFEST_FILE) < time() - 3600) || isset($_GET['buildmanifest']) || isset($_GET['flush']); } /** * Generates a new manifest file and saves it to MANIFEST_FILE */ static function compileManifest() { // Config manifest $baseDir = dirname($_SERVER['SCRIPT_FILENAME']) . "/.."; $baseDir = ereg_replace("/[^/]+/\\.\\.","",$baseDir); // locate the exclude file $topLevel = scandir( $baseDir ); foreach($topLevel as $file) { $fullPath = $baseDir . '/' . $file; // echo $fullPath . '
    '; if( is_dir($fullPath . '/') && file_exists($fullPath . '/_exclude.php') ) require_once($fullPath . '/_exclude.php'); } $classManifest = array(); // Class manifest if( is_array(self::$restrict_to_modules) && count(self::$restrict_to_modules) ) { foreach(self::$restrict_to_modules as $module) ManifestBuilder::getClassManifest($baseDir.'/'.$module, $classManifest); } else { // Only include directories if they have an _config.php file $topLevel = scandir($baseDir); foreach($topLevel as $filename) { if(is_dir("$baseDir/$filename") && file_exists("$baseDir/$filename/_config.php")) { ManifestBuilder::getClassManifest("$baseDir/$filename", $classManifest); } } } $manifest = "\$_CLASS_MANIFEST = " . var_export($classManifest, true) . ";\n"; // Load the manifest in, so that the autoloader works global $_CLASS_MANIFEST; $_CLASS_MANIFEST = $classManifest; // _config.php manifest global $databaseConfig; $topLevel = scandir($baseDir); foreach($topLevel as $filename) { if(is_dir("$baseDir/$filename/") && file_exists("$baseDir/$filename/_config.php")) { $manifest .= "require_once(\"$baseDir/$filename/_config.php\");\n"; // Include this so that we're set up for connecting to the database in the rest of the manifest builder require_once("$baseDir/$filename/_config.php"); } } if(!project()) user_error("\$project isn't set", E_USER_WARNING); // Template & CSS manifest $templateManifest = array(); $cssManifest = array(); // Only include directories if they have an _config.php file $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")) { ManifestBuilder::getTemplateManifest($baseDir, $filename, $templateManifest, $cssManifest); } } // Get themes if(file_exists("$baseDir/themes")) { $themeDirs = scandir("$baseDir/themes"); foreach($themeDirs as $themeDir) { if(substr($themeDir,0,1) == '.') continue; // The theme something_forum is understood as being a part of the theme something $themeName = strtok($themeDir, '_'); ManifestBuilder::getTemplateManifest($baseDir, "themes/$themeDir", $templateManifest, $cssManifest, $themeName); } } // Ensure that any custom templates get favoured ManifestBuilder::getTemplateManifest($baseDir, project(), $templateManifest, $cssManifest); $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; // Write manifest to disk $manifest = ""; if($fh = fopen(MANIFEST_FILE,"w")) { fwrite($fh, $manifest); fclose($fh); } else { die("Cannot write manifest file! Check permissions of " . MANIFEST_FILE); } } /** * Generates the class manifest - a list of all the PHP files in the application */ private static function getClassManifest($folder, &$classMap) { $items = scandir($folder); if($items) foreach($items as $item) { // ignore files such as index.php 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; // 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; if(is_dir("$folder/$item")) { // recurse into directories (if not in $ignore_folders) ManifestBuilder::getClassManifest("$folder/$item", $classMap); } else { // include item in the manifest $itemCode = substr($item,0,-4); // if $itemCode is already in manifest, check if the two files do really contain the same class 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($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]}'. This might mean that the wrong code is being used.", E_USER_WARNING); } } else { $classMap[$itemCode] = "$folder/$item"; } } } } /** * Generates the template/css manifest - a list of all the .SS & .CSS files in the application */ private static function getTemplateManifest($baseDir, $folder, &$templateManifest, &$cssManifest, $themeName = null) { $items = scandir("$baseDir/$folder"); if($items) foreach($items as $item) { if(substr($item,0,1) == '.') continue; if(substr($item,-3) == '.ss') { $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"; } else { $templateManifest[$templateName][$templateType] = "$baseDir/$folder/$item"; } } else if(substr($item,-4) == '.css') { $cssName = substr($item, 0, -4); // Debug::message($item); if($themeName) { $cssManifest[$cssName]['themes'][$themeName] = "$folder/$item"; } else { $cssManifest[$cssName]['unthemed'] = "$folder/$item"; } } else if(is_dir("$baseDir/$folder/$item")) { ManifestBuilder::getTemplateManifest($baseDir, "$folder/$item", $templateManifest, $cssManifest, $themeName); } } } private static function allClasses($classManifest) { // Include everything, so we actually have *all* classes foreach($classManifest as $file) { $b = basename($file); if($b != 'cli-script.php' && $b != 'main.php') include_once($file); } if(DB::isActive()) { $tables = DB::getConn()->tableList(); } else { $tables = array(); } $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; 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; } } return $allClasses; } static function includeEverything() { global $_CLASS_MANIFEST; foreach($_CLASS_MANIFEST as $filename) { if( preg_match( '/.*cli-script\.php$/', $filename ) ) continue; require_once($filename); } } /** * Updates the active table list in the class info in the manifest, but leaves everything else as-is. * Much quicker to run than compileManifest :-) */ static function update_db_tables() { global $_ALL_CLASSES; $_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 */ static function write_manifest() { global $_CLASS_MANIFEST, $_TEMPLATE_MANIFEST, $_CSS_MANIFEST, $_ALL_CLASSES; $manifest = "\$_CLASS_MANIFEST = " . var_export($_CLASS_MANIFEST, true) . ";\n"; // Config manifest $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")) { $manifest .= "require_once(\"$baseDir/$filename/_config.php\");\n"; } } $manifest .= "\$_TEMPLATE_MANIFEST = " . var_export($_TEMPLATE_MANIFEST, true) . ";\n"; $manifest .= "\$_CSS_MANIFEST = " . var_export($_CSS_MANIFEST, true) . ";\n"; $manifest .= "\$_ALL_CLASSES = " . var_export($_ALL_CLASSES, true) . ";\n"; $manifest = ""; if($fh = fopen(MANIFEST_FILE,"w")) { fwrite($fh, $manifest); fclose($fh); } else { die("Cannot write manifest file! Check permissions of " . MANIFEST_FILE); } } } ?>