$globalVal) { $output .= "global \$$globalName;\n\$$globalName = " . var_export($globalVal, true) . ";\n\n"; } foreach($manifestInfo['require_once'] as $requireItem) { $output .= 'require_once("' . addslashes($requireItem) . "\");\n"; } return $output; } /** * Parse the $manifestInfo array, updating the appropriate globals and loading the appropriate _config files. */ static function process_manifest($manifestInfo) { foreach($manifestInfo['globals'] as $globalName => $globalVal) { global $$globalName; $$globalName = $globalVal; } foreach($manifestInfo['require_once'] as $requireItem) { require_once("$requireItem"); } } /** * Get themes from a particular directory. * * @param string $baseDir Optional: Absolute path to theme directory for testing e.g. "/Users/sharvey/Sites/test24/themes" * @param boolean $includeSubThemes If set to TRUE, sub-themes such as "blackcandy_blog" are included too * @return array Listing of theme directories */ public static function get_themes($baseDir = null, $includeSubThemes = false) { // If no base directory specified, the default is the project root if(!$baseDir) $baseDir = BASE_PATH . DIRECTORY_SEPARATOR . THEMES_DIR; $themes = array(); if(!file_exists($baseDir)) return $themes; $handle = opendir($baseDir); if($handle) { while(false !== ($file = readdir($handle))) { $fullPath = $baseDir . DIRECTORY_SEPARATOR . $file; if(strpos($file, '.') === false && is_dir($fullPath)) { $include = $includeSubThemes ? true : false; if(strpos($file, '_') === false) { $include = true; } if($include) $themes[$file] = $file; } } closedir($handle); } return $themes; } /** * Return an array containing information for the manifest * @param $baseDir The root directory to analyse * @param $excludedFolders An array folder names to exclude. These don't care about where the * folder appears in the hierarchy, so be careful */ static function get_manifest_info($baseDir, $excludedFolders = array()) { // locate and include the exclude files $topLevel = scandir($baseDir); foreach($topLevel as $file) { if($file[0] == '.') continue $fullPath = ''; $fullPath = $baseDir . '/' . $file; if(@is_dir($fullPath . '/') && file_exists($fullPath . '/_exclude.php')) { require_once($fullPath . '/_exclude.php'); } } // Project - used to give precedence to template files $project = null; // Class, CSS, template manifest $allPhpFiles = array(); $templateManifest = array(); $cssManifest = array(); if(is_array(self::$restrict_to_modules) && count(self::$restrict_to_modules)) { // $restrict_to_modules is set, so we include only those specified // modules foreach(self::$restrict_to_modules as $module) ManifestBuilder::get_all_php_files($baseDir . '/' . $module, $excludedFolders, $allPhpFiles); } else { // Include all directories which have an _config.php file but don't // have an _manifest_exclude file $topLevel = scandir($baseDir); foreach($topLevel as $filename) { if($filename[0] == '.') continue; if($filename == 'themes') continue; if($filename == 'assets') continue; if(in_array($filename, $excludedFolders)) continue; if(@is_dir("$baseDir/$filename") && file_exists("$baseDir/$filename/_config.php") && !file_exists("$baseDir/$filename/_manifest_exclude")) { // Get classes, templates, and CSS files ManifestBuilder::get_all_php_files("$baseDir/$filename", $excludedFolders, $allPhpFiles); ManifestBuilder::getTemplateManifest($baseDir, $filename, $excludedFolders, $templateManifest, $cssManifest); // List the _config.php files $manifestInfo["require_once"][] = "$baseDir/$filename/_config.php"; // Find the $project variable in the relevant config file without having to execute the config file if(preg_match("/\\\$project\s*=\s*[^\n\r]+[\n\r]/", file_get_contents("$baseDir/$filename/_config.php"), $parts)) { eval($parts[0]); } } } } // Get themes if(file_exists("$baseDir/themes")) { $themeDirs = self::get_themes("$baseDir/themes", true); foreach($themeDirs as $themeDir) { $themeName = strtok($themeDir, '_'); ManifestBuilder::getTemplateManifest($baseDir, "themes/$themeDir", $excludedFolders, $templateManifest, $cssManifest, $themeName); } } // Build class-info array from class manifest $allClasses = ManifestBuilder::allClasses($allPhpFiles); // Pull the class filenames out $classManifest = $allClasses['file']; unset($allClasses['file']); // Ensure that any custom templates get favoured if(!$project) user_error("\$project isn't set", E_USER_WARNING); else if(!file_exists("$baseDir/$project")) user_error("\$project is set to '$project' but no such folder exists.", E_USER_WARNING); else ManifestBuilder::getTemplateManifest($baseDir, $project, $excludedFolders, $templateManifest, $cssManifest); $manifestInfo["globals"]["_CLASS_MANIFEST"] = $classManifest; $manifestInfo["globals"]["_ALL_CLASSES"] = $allClasses; $manifestInfo["globals"]["_TEMPLATE_MANIFEST"] = $templateManifest; $manifestInfo["globals"]["_CSS_MANIFEST"] = $cssManifest; return $manifestInfo; } /** * Generates a list of all the PHP files that should be analysed by the manifest builder. * * @param string $folder The folder to traverse (recursively) * @param array $classMap The already built class map */ private static function get_all_php_files($folder, $excludedFolders, &$allPhpFiles) { $items = scandir($folder); 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; // 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 directories with _manifest_exlude file 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")) { // Folder exclusion - used to skip over tests/ folders if(in_array($item, $excludedFolders)) continue; // recurse into directories (if not in $ignore_folders) ManifestBuilder::get_all_php_files("$folder/$item", $excludedFolders, $allPhpFiles); } else { $allPhpFiles[] = "$folder/$item"; } } } /** * Generates the template manifest - a list of all the .SS files in the * application */ private static function getTemplateManifest($baseDir, $folder, $excludedFolders, &$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")) { // Folder exclusion - used to skip over tests/ folders if(in_array($item, $excludedFolders)) continue; ManifestBuilder::getTemplateManifest($baseDir, "$folder/$item", $excludedFolders, $templateManifest, $cssManifest, $themeName); } } } /** * Include everything, so that actually *all* classes are available and * build a map of classes and their subclasses * * @param $classManifest An array of all Sapphire classes; keys are class names and values are filenames * * @return array Returns an array that holds all class relevant * information. */ private static function allClasses($classManifest) { self::$classArray = array(); self::$extendsArray = array(); self::$implementsArray = array(); // Include everything, so we actually have *all* classes foreach($classManifest as $file) { $b = basename($file); if($b != 'cli-script.php' && $b != 'main.php') self::parse_file($file); } $allClasses["parents"] = self::find_parents(); $allClasses["children"] = self::find_children(); $allClasses["implementors"] = self::$implementsArray; foreach(self::$classArray as $class => $info) { $allClasses['exists'][$class] = $class; $allClasses['file'][$class] = $info['file']; } // Build a map of classes and their subclasses $_classes = get_declared_classes(); foreach($_classes as $class) { $allClasses['exists'][$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; } /** * 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 === null) user_error("ManifestBuilder::parse_file(): Couldn't open $filename", E_USER_ERROR); if(!$file) return; // We cache the parse results of each file, since only a few files will have changed between flushings // And, although it's accurate, TokenisedRegularExpression isn't particularly fast. // We use an MD5 of the file as a part of the cache key because using datetime caused problems when users // were upgrading their sites $fileMD5 = md5($file); $parseCacheFile = TEMP_FOLDER . "/manifestClassParse-" . str_replace(array("/",":", "\\"),"_", realpath($filename)) . "-$fileMD5"; if(!file_exists($parseCacheFile)) { $tokens = token_get_all($file); $classes = self::getClassDefParser()->findAll($tokens); $interfaces = self::getInterfaceDefParser()->findAll($tokens); $cacheContent = ' T_CLASS, 1 => T_WHITESPACE, 2 => array(T_STRING, 'can_jump_to' => array(7, 14), 'save_to' => 'className'), 3 => T_WHITESPACE, 4 => T_EXTENDS, 5 => T_WHITESPACE, 6 => array(T_STRING, 'save_to' => 'extends', 'can_jump_to' => 14), 7 => T_WHITESPACE, 8 => T_IMPLEMENTS, 9 => T_WHITESPACE, 10 => array(T_STRING, 'can_jump_to' => 14, 'save_to' => 'interfaces[]'), 11 => array(T_WHITESPACE, 'optional' => true), 12 => array(',', 'can_jump_to' => 10), 13 => array(T_WHITESPACE, 'can_jump_to' => 10), 14 => array(T_WHITESPACE, 'optional' => true), 15 => '{', )); } /** * Returns a {@link TokenisedRegularExpression} object that will parse class definitions * @return TokenisedRegularExpression */ public static function getInterfaceDefParser() { require_once('core/TokenisedRegularExpression.php'); return new TokenisedRegularExpression(array( 0 => T_INTERFACE, 1 => T_WHITESPACE, 2 => array(T_STRING, 'can_jump_to' => 7, 'save_to' => 'interfaceName'), 3 => T_WHITESPACE, 4 => T_EXTENDS, 5 => T_WHITESPACE, 6 => array(T_STRING, 'save_to' => 'extends'), 7 => array(T_WHITESPACE, 'optional' => true), 8 => '{', )); } /** * Moves through self::$classArray and creates an array containing parent data * * @return array */ private static function find_parents() { $parentArray = array(); foreach(self::$classArray as $class => $info) { $extendArray = array(); $parent = $info["extends"]; 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 if the Manifest has been included * * @return Boolean */ static function has_been_included() { global $_CLASS_MANIFEST, $_TEMPLATE_MANIFEST, $_CSS_MANIFEST, $_ALL_CLASSES; return (bool)!(empty($_CLASS_MANIFEST) && empty($_TEMPLATE_MANIFEST) && empty($_CSS_MANIFEST) && empty($_ALL_CLASSES)); } /** * 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;; } } ?>