diff --git a/cli/CliController.php b/cli/CliController.php index 4d0f71b35..b1db8270c 100755 --- a/cli/CliController.php +++ b/cli/CliController.php @@ -11,10 +11,6 @@ abstract class CliController extends Controller { } function index() { - // Always re-compile the manifest (?flush=1) - ManifestBuilder::update_db_tables(DB::getConn()->tableList(), $_ALL_CLASSES); - ManifestBuilder::write_manifest(); - foreach( ClassInfo::subclassesFor( $this->class ) as $subclass ) { echo $subclass; diff --git a/core/ClassInfo.php b/core/ClassInfo.php index dd8d5c899..0af7490ae 100755 --- a/core/ClassInfo.php +++ b/core/ClassInfo.php @@ -8,15 +8,7 @@ * @subpackage core */ class ClassInfo { - /** - * Returns true if the manifest has actually been built. - */ - static function ready() { - global $_ALL_CLASSES; - return $_ALL_CLASSES && $_ALL_CLASSES['hastable']; - } - - /** + /** * @todo Improve documentation */ static function allClasses() { @@ -33,11 +25,11 @@ class ClassInfo { } /** - * @todo Improve documentation + * @todo Move this to Database or DB */ static function hasTable($class) { - global $_ALL_CLASSES; - return isset($_ALL_CLASSES['hastable'][$class]) ? $_ALL_CLASSES['hastable'][$class] : null; + $SQL_table = Convert::raw2sql($class); + return (bool)(DB::query("SHOW TABLES LIKE '$SQL_table'")->value()); } /** @@ -60,6 +52,7 @@ class ClassInfo { * to those with DB tables * * @param mixed $class string of the classname or instance of the class + * @todo Move this into data object * @return array */ static function dataClassesFor($class) { @@ -70,20 +63,15 @@ class ClassInfo { if(!$_ALL_CLASSES['parents'][$class]) user_error("ClassInfo::dataClassesFor() no parents for $class", E_USER_WARNING); foreach($_ALL_CLASSES['parents'][$class] as $subclass) { - if(isset($_ALL_CLASSES['hastable'][$subclass])){ - $dataClasses[] = $subclass; - } + if(DataObject::has_own_table($subclass)) $dataClasses[] = $subclass; } - if(isset($_ALL_CLASSES['hastable'][$class])) $dataClasses[] = $class; + if(DataObject::has_own_table($class)) $dataClasses[] = $class; if(isset($_ALL_CLASSES['children'][$class])) foreach($_ALL_CLASSES['children'][$class] as $subclass) { - if(isset($_ALL_CLASSES['hastable'][$subclass])) - { - $dataClasses[] = $subclass; - } + if(DataObject::has_own_table($subclass)) $dataClasses[] = $subclass; } return $dataClasses; @@ -134,7 +122,7 @@ class ClassInfo { $items = $_ALL_CLASSES['parents'][$class]; $items[$class] = $class; if($onlyWithTables) foreach($items as $item) { - if(!isset($_ALL_CLASSES['hastable'][$item]) || !$_ALL_CLASSES['hastable'][$item]) unset($items[$item]); + if(!DataObject::has_own_table($item)) unset($items[$item]); } return $items; } @@ -174,5 +162,17 @@ class ClassInfo { return $matchedClasses; } + + ///////////////////////////////////////////////////////////////////////////// + // DEPRECATED + + /** + * @deprecated Use Security::database_is_ready() instead. + */ + static function ready() { + user_error("ClassInfo::ready() deprectaed - use Security::database_is_ready()", E_USER_NOTICE); + return Security::database_is_ready(); + } + } -?> +?> \ No newline at end of file diff --git a/core/Core.php b/core/Core.php index 68efc67d0..7ef6d6072 100755 --- a/core/Core.php +++ b/core/Core.php @@ -158,13 +158,9 @@ require_once("core/Session.php"); // MANIFEST /** - * Build the manifest + * Include the manifest */ -if(ManifestBuilder::staleManifest()){ - ManifestBuilder::compileManifest(); -} - -require_once(MANIFEST_FILE); +ManifestBuilder::include_manifest(); /** * ?debugmanifest=1 hook diff --git a/core/ManifestBuilder.php b/core/ManifestBuilder.php index c98cdb509..92c6e53f8 100644 --- a/core/ManifestBuilder.php +++ b/core/ManifestBuilder.php @@ -41,7 +41,7 @@ class ManifestBuilder { 'install.php', 'index.php', 'check-php.php', - 'rewritetest.php' + 'rewritetest.php', ); /** @@ -56,54 +56,44 @@ class ManifestBuilder { ); /** - * @var int $cache_expiry_mins Specifies the time (in minutes) until a rebuild - * of the manifest cache is forced. - * @usedby self::staleManifest() + * Include the manifest, regenerating it if necessary */ - public static $cache_expiry_mins = 60; - - /** - * Returns true if the manifest file should be regenerated, - * by asserting one of the following conditions: - * - Manifest cache file doesn't exist - * - The modification time of the webroot folder is newer than the cache file - * - The cache file is older than {@link self::$cache_expiry_mins} - * - A cache rebuild is forced by the "?flush=1" URL parameter - * - * Checked on every request handled by SilverStripe in main.php or cli-script.php. - * - * @return bool Returns TRUE if the manifest file should be regenerated, otherwise FALSE. - */ - static function staleManifest() { - $lastEdited = filemtime(BASE_PATH); - - return ( - !file_exists(MANIFEST_FILE) - || (filemtime(MANIFEST_FILE) < $lastEdited) - || (filemtime(MANIFEST_FILE) < time() - 60 * self::$cache_expiry_mins) - || isset($_GET['flush']) - ); + static function include_manifest() { + if(isset($_REQUEST['usetestmanifest'])) { + self::load_test_manifest(); + } else { + if(!file_exists(MANIFEST_FILE) || (filemtime(MANIFEST_FILE) < filemtime(BASE_PATH)) || isset($_GET['flush'])) { + self::create_manifest_file(); + } + require_once(MANIFEST_FILE); + } } + /** + * Load a copy of the manifest with tests/ folders included. + * Only loads the ClassInfo and __autoload() globals; this assumes that _config.php files are already included. + */ + static function load_test_manifest() { + // Build the complete manifest + $manifestInfo = self::get_manifest_info(BASE_PATH); + // Load it into the current session. + self::process_manifest($manifestInfo); + } + + /** + * Loads all PHP class files - actually opening them and executing them. + */ + static function load_all_classes() { + global $_CLASS_MANIFEST; + foreach($_CLASS_MANIFEST as $classFile) require_once($classFile); + } /** * Generates a new manifest file and saves it to {@link MANIFEST_FILE}. */ - static function compileManifest() { - // Config manifest - $baseDir = dirname($_SERVER['SCRIPT_FILENAME']) . "/.."; - $baseDir = ereg_replace("/[^/]+/\\.\\.", "", $baseDir); - $baseDir = preg_replace("/\\\\/", "/", $baseDir); - - $manifestInfo = self::get_manifest_info($baseDir); - - // Connect to the database and get the database config - global $databaseConfig; - DB::connect($databaseConfig); - if(DB::isActive()) { - $tableList = DB::getConn()->tableList(); - self::update_db_tables($tableList, $manifestInfo['globals']['_ALL_CLASSES']); - } + private static function create_manifest_file() { + // Build the manifest, ignoring the tests/ folders + $manifestInfo = self::get_manifest_info(BASE_PATH, array("tests")); $manifest = self::generate_php_file($manifestInfo); if($fh = fopen(MANIFEST_FILE, "w")) { @@ -122,7 +112,7 @@ class ManifestBuilder { $output = " $globalVal) { - $output .= "\$$globalName = " . var_export($globalVal, true) . ";\n\n"; + $output .= "global \$$globalName;\n\$$globalName = " . var_export($globalVal, true) . ";\n\n"; } foreach($manifestInfo['require_once'] as $requireItem) { $output .= "require_once(\"$requireItem\");\n"; @@ -130,12 +120,28 @@ class ManifestBuilder { 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"); + } + } /** * Return an array containing information for the manifest - * @param $baseDir A + * @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, $tableList = null) { + static function get_manifest_info($baseDir, $excludedFolders = array()) { // locate and include the exclude files $topLevel = scandir($baseDir); foreach($topLevel as $file) { @@ -148,73 +154,49 @@ class ManifestBuilder { require_once($fullPath . '/_exclude.php'); } } + + // Project - used to give precedence to template files + $project = null; - // Class manifest + // Class, CSS, template manifest $classManifest = 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::getClassManifest($baseDir . '/' . $module, - $classManifest); + ManifestBuilder::getClassManifest($baseDir . '/' . $module, $excludedFolders, $classManifest); } 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(in_array($filename, $excludedFolders)) continue; + if(@is_dir("$baseDir/$filename") && file_exists("$baseDir/$filename/_config.php") && !file_exists("$baseDir/$filename/_manifest_exclude")) { - ManifestBuilder::getClassManifest("$baseDir/$filename", - $classManifest); + + // Get classes, templates, and CSS files + ManifestBuilder::getClassManifest("$baseDir/$filename", $excludedFolders, $classManifest); + 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]); + } + } } } - $manifestInfo["globals"]["_CLASS_MANIFEST"] = $classManifest; - - // Load the manifest in, so that the autoloader works - global $_CLASS_MANIFEST; - $_CLASS_MANIFEST = $classManifest; - - // Load in a temporary all-classes array for using while building the manifest - // @todo Manifestbuilder is tightly convoluted and gets really hard to debug. We have catch-22s betweeen - // db connection, value of project(), and ClassInfo responses... It needs to be untangled. - global $_ALL_CLASSES; - $allClasses = ManifestBuilder::allClasses($classManifest, array()); - $_ALL_CLASSES = $allClasses; - - // _config.php manifest - $topLevel = scandir($baseDir); - foreach($topLevel as $filename) { - if($filename[0] == '.') continue; - if(@is_dir("$baseDir/$filename/") && - file_exists("$baseDir/$filename/_config.php") && - !file_exists("$baseDir/$filename/_manifest_exclude")) { - $manifestInfo["require_once"][] = "$baseDir/$filename/_config.php"; - // 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($filename[0] == '.') 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"); @@ -222,24 +204,22 @@ class ManifestBuilder { 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); + ManifestBuilder::getTemplateManifest($baseDir, "themes/$themeDir", $excludedFolders, $templateManifest, $cssManifest, $themeName); } } - // Ensure that any custom templates get favoured - ManifestBuilder::getTemplateManifest($baseDir, project(), $templateManifest, $cssManifest); + // Build class-info array from class manifest + $allClasses = ManifestBuilder::allClasses($classManifest); + // Ensure that any custom templates get favoured + if(!$project) user_error("\$project isn't set", E_USER_WARNING); + 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; - // Database manifest - $allClasses = ManifestBuilder::allClasses($classManifest, $tableList); - - $manifestInfo["globals"]["_ALL_CLASSES"] = $allClasses; - - global $_ALL_CLASSES; - $_ALL_CLASSES = $allClasses; - return $manifestInfo; } @@ -251,7 +231,7 @@ class ManifestBuilder { * @param string $folder The folder to traverse (recursively) * @param array $classMap The already built class map */ - private static function getClassManifest($folder, &$classMap) { + private static function getClassManifest($folder, $excludedFolders, &$classMap) { $items = scandir($folder); if($items) foreach($items as $item) { // Skip some specific PHP files @@ -276,8 +256,11 @@ class ManifestBuilder { 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::getClassManifest("$folder/$item", $classMap); + ManifestBuilder::getClassManifest("$folder/$item", $excludedFolders, $classMap); } else { // include item in the manifest $itemCode = substr($item,0,-4); @@ -307,7 +290,7 @@ class ManifestBuilder { * Generates the template manifest - a list of all the .SS files in the * application */ - private static function getTemplateManifest($baseDir, $folder, &$templateManifest, &$cssManifest, $themeName = null) { + 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; @@ -334,7 +317,10 @@ class ManifestBuilder { } else if(@is_dir("$baseDir/$folder/$item")) { - ManifestBuilder::getTemplateManifest($baseDir, "$folder/$item", $templateManifest, $cssManifest, $themeName); + // Folder exclusion - used to skip over tests/ folders + if(in_array($item, $excludedFolders)) continue; + + ManifestBuilder::getTemplateManifest($baseDir, "$folder/$item", $excludedFolders, $templateManifest, $cssManifest, $themeName); } } } @@ -342,16 +328,14 @@ class ManifestBuilder { /** * Include everything, so that actually *all* classes are available and - * build a map of classes and their subclasses and the information if - * the class has a database table + * build a map of classes and their subclasses * * @param $classManifest An array of all Sapphire classes; keys are class names and values are filenames - * @param $tables An array of the tables that exist in the database * * @return array Returns an array that holds all class relevant * information. */ - private static function allClasses($classManifest, $tables = null) { + private static function allClasses($classManifest) { self::$classArray = array(); self::$extendsArray = array(); self::$implementsArray = array(); @@ -369,7 +353,6 @@ class ManifestBuilder { foreach(self::$classArray as $class => $info) { $allClasses['exists'][$class] = $class; - if(isset($tables[strtolower($class)])) $allClasses['hastable'][$class] = $class; } // Build a map of classes and their subclasses @@ -377,7 +360,6 @@ class ManifestBuilder { 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; @@ -387,7 +369,7 @@ class ManifestBuilder { return $allClasses; } -/** + /** * Parses a php file and adds any class or interface information into self::$classArray * * @param string $filename @@ -449,6 +431,8 @@ class ManifestBuilder { * @return TokenisedRegularExpression */ public static function getClassDefParser() { + require_once('core/TokenisedRegularExpression.php'); + return new TokenisedRegularExpression(array( 0 => T_CLASS, 1 => T_WHITESPACE, @@ -474,6 +458,8 @@ class ManifestBuilder { * @return TokenisedRegularExpression */ public static function getInterfaceDefParser() { + require_once('core/TokenisedRegularExpression.php'); + return new TokenisedRegularExpression(array( 0 => T_INTERFACE, 1 => T_WHITESPACE, @@ -550,68 +536,6 @@ class ManifestBuilder { } return $results;; } - - /** - * Updates the active table list in the class info in the manifest, but leaves everything else as-is. - * Much quicker to run than compileManifest :-) - * - * @param $tableList The list of tables to load into the manifest - * @param $allClassesArray The $_ALL_CLASSES array that should be updated - */ - static function update_db_tables($tableList, &$allClassesArray) { - if(!isset($allClassesArray['exists'])) return; - - $allClassesArray['hastable'] = array(); - - $tables = array(); - foreach($tableList as $table) $tables[$table] = true; - - // We need to iterate through the full class lists, because the table names come out in lowercase - foreach($allClassesArray['exists'] as $class) { - if(isset($tables[strtolower($class)])) $allClassesArray['hastable'][$class] = $class; - } - } - - /** - * 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); - $baseDir = preg_replace("/\\\\/", "/", $baseDir); - $topLevel = scandir($baseDir); - - foreach($topLevel as $filename) { - if($filename[0] == '.') continue; - 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); - } - } - - static function includeEverything() { - global $_CLASS_MANIFEST; - foreach($_CLASS_MANIFEST as $classFile) require_once($classFile); - } - } diff --git a/core/control/Controller.php b/core/control/Controller.php index b93df37f2..5162c5986 100644 --- a/core/control/Controller.php +++ b/core/control/Controller.php @@ -76,15 +76,16 @@ class Controller extends RequestHandler { */ function init() { // Test and development sites should be secured, via basic-auth - if(ClassInfo::hasTable("Group") && ClassInfo::hasTable("Member") && Director::isTest() && $this->basicAuthEnabled) { + if(Director::isTest() && $this->basicAuthEnabled && Security::database_is_ready()) { BasicAuth::requireLogin("SilverStripe test website. Use your CMS login", "ADMIN"); } // Cookie::set("PastVisitor", true); - // ClassInfo::hasTable() called to ensure that we're not in a very-first-setup stage - if(ClassInfo::hasTable("Group") && ClassInfo::hasTable("Member") && ($member = Member::currentUser())) { + // Directly access the session variable just in case the Group or Member tables don't yet exist + if(Session::get('loggedInAs')) { + $member = Member::currentUser(); Cookie::set("PastMember", true); DB::query("UPDATE Member SET LastVisited = NOW() WHERE ID = $member->ID", null); } diff --git a/core/control/Director.php b/core/control/Director.php index 3fd651d7d..de4072715 100644 --- a/core/control/Director.php +++ b/core/control/Director.php @@ -546,7 +546,8 @@ class Director { * @return boolean */ public static function is_cli() { - return preg_match('/cli-script\.php/', $_SERVER['SCRIPT_NAME']); + return (!isset($_SERVER['HTTP_HOST']) && preg_match('/install\.php/', $_SERVER['SCRIPT_NAME'])) + || preg_match('/cli-script\.php/', $_SERVER['SCRIPT_NAME']); } //////////////////////////////////////////////////////////////////////////////////////////// @@ -680,7 +681,7 @@ class Director { // Use ?isDev=1 to get development access on the live server if(isset($_GET['isDev'])) { - if(ClassInfo::ready()) { + if(Security::database_is_ready()) { BasicAuth::requireLogin("SilverStripe developer access. Use your CMS login", "ADMIN"); $_SESSION['isDev'] = $_GET['isDev']; } else { diff --git a/core/model/DataObject.php b/core/model/DataObject.php index 35b0f9c9a..665849305 100644 --- a/core/model/DataObject.php +++ b/core/model/DataObject.php @@ -693,7 +693,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP // No changes made if($this->changed) { foreach($this->getClassAncestry() as $ancestor) { - if(ClassInfo::hasTable($ancestor)) + if(self::has_own_table($ancestor)) $ancestry[] = $ancestor; } @@ -758,7 +758,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP } // In cases where there are no fields, this 'stub' will get picked up on - if(ClassInfo::hasTable($class)) { + if(self::has_own_table($class)) { $manipulation[$class]['command'] = $dbCommand; $manipulation[$class]['id'] = $this->record['ID']; } else { @@ -846,7 +846,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP user_error("$this->class has a broken onBeforeDelete() function. Make sure that you call parent::onBeforeDelete().", E_USER_ERROR); } foreach($this->getClassAncestry() as $ancestor) { - if(ClassInfo::hastable($ancestor)) { + if(self::has_own_table($ancestor)) { $sql = new SQLQuery(); $sql->delete = true; $sql->from[$ancestor] = "`$ancestor`"; @@ -1748,6 +1748,26 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP return isset($fieldMap[$field]) ? strtok($fieldMap[$field],'(') : null; } + /** + * Returns true if given class has its own table. + * Uses the rules for whether the table should exist rather than actually looking in the database. + */ + public function has_own_table($dataClass) { + if(!is_subclass_of($dataClass,'DataObject')) return false; + if(!isset(self::$cache_has_own_table[$dataClass])) { + if(get_parent_class($dataClass) == 'DataObject') { + self::$cache_has_own_table[$dataClass] = true; + } else { + $sng = singleton($dataClass); + self::$cache_has_own_table[$dataClass] = $sng->uninherited('db',true) || $sng->uninherited('has_one',true); + } + } + return self::$cache_has_own_table[$dataClass]; + } + + private static $cache_has_own_table = array(); + + /** * Returns true if the member is allowed to do the given action. * diff --git a/core/model/DatabaseAdmin.php b/core/model/DatabaseAdmin.php index 0579cc09c..8e9a06700 100644 --- a/core/model/DatabaseAdmin.php +++ b/core/model/DatabaseAdmin.php @@ -162,10 +162,6 @@ class DatabaseAdmin extends Controller { } } $conn->endSchemaUpdate(); - - global $_ALL_CLASSES; - ManifestBuilder::update_db_tables(DB::getConn()->tableList(), $_ALL_CLASSES); - if(!$testMode) ManifestBuilder::write_manifest(); if($populate) { if(!$quiet) { diff --git a/dev/TestRunner.php b/dev/TestRunner.php index b2816898e..d41c6220f 100644 --- a/dev/TestRunner.php +++ b/dev/TestRunner.php @@ -51,6 +51,7 @@ class TestRunner extends Controller { function init() { parent::init(); + ManifestBuilder::load_test_manifest(); if (!self::$default_reporter) self::set_reporter(Director::is_cli() ? 'CliDebugView' : 'DebugView'); } @@ -95,7 +96,7 @@ class TestRunner extends Controller { function coverage() { if(hasPhpUnit()) { - ManifestBuilder::includeEverything(); + ManifestBuilder::load_all_classes(); $tests = ClassInfo::subclassesFor('SapphireTest'); array_shift($tests); unset($tests['FunctionalTest']); diff --git a/tests/DataObjectTest.php b/tests/DataObjectTest.php index 136ad4853..9512a5592 100644 --- a/tests/DataObjectTest.php +++ b/tests/DataObjectTest.php @@ -525,6 +525,24 @@ class DataObjectTest extends SapphireTest { $this->assertEquals("1001", DB::query("SELECT ID FROM DataObjectTest_SubTeam WHERE SubclassDatabaseField = 'asdfasdf'")->value()); $this->assertEquals("1001", DB::query("SELECT ID FROM DataObjectTest_Team WHERE Title = 'asdfasdf'")->value()); } + + public function TestHasOwnTable() { + /* Test DataObject::has_own_table() returns true if the object has $has_one or $db values */ + $this->assertTrue(DataObject::has_own_table("DataObjectTest_Player")); + $this->assertTrue(DataObject::has_own_table("DataObjectTest_Team")); + $this->assertTrue(DataObject::has_own_table("DataObjectTest_FunnyFieldNames")); + + /* Root DataObject that always have a table, even if they lack both $db and $has_one */ + $this->assertTrue(DataObject::has_own_table("DataObjectTest_FieldlessTable")); + + /* Subclasses without $db or $has_one don't have a table */ + $this->assertFalse(DataObject::has_own_table("DataObjectTest_FieldlessSubTable")); + + /* Return false if you don't pass it a subclass of DataObject */ + $this->assertFalse(DataObject::has_own_table("DataObject")); + $this->assertFalse(DataObject::has_own_table("ViewableData")); + $this->assertFalse(DataObject::has_own_table("ThisIsntADataObject")); + } } class DataObjectTest_Player extends Member implements TestOnly { @@ -574,6 +592,13 @@ class DataObjectTest_SubTeam extends DataObjectTest_Team implements TestOnly { ); } +class DataObjectTest_FieldlessTable extends DataObject implements TestOnly { +} + +class DataObjectTest_FieldlessSubTable extends DataObjectTest_Team implements TestOnly { +} + + class DataObjectTest_Team_Decorator extends DataObjectDecorator implements TestOnly { function extraStatics() { diff --git a/tests/ManifestBuilderTest.php b/tests/ManifestBuilderTest.php index 6d215bca8..a0f168486 100644 --- a/tests/ManifestBuilderTest.php +++ b/tests/ManifestBuilderTest.php @@ -6,7 +6,7 @@ class ManifestBuilderTest extends SapphireTest { function testManifest() { $baseFolder = TEMP_FOLDER . '/manifest-test'; - $manifestInfo = ManifestBuilder::get_manifest_info($baseFolder, DB::getConn()->tableList()); + $manifestInfo = ManifestBuilder::get_manifest_info($baseFolder); global $project; $this->assertEquals("$baseFolder/sapphire/MyClass.php", $manifestInfo['globals']['_CLASS_MANIFEST']['MyClass']); @@ -36,42 +36,37 @@ class ManifestBuilderTest extends SapphireTest { $baseFolder = TEMP_FOLDER . '/manifest-test'; global $project; - $manifestInfo = ManifestBuilder::get_manifest_info($baseFolder, DB::getConn()->tableList()); + $manifestInfo = ManifestBuilder::get_manifest_info($baseFolder); /* Our fixture defines the class MyClass_InComment inside a comment, so it shouldn't be included in the class manifest. */ $this->assertNotContains('MyClass_InComment', array_keys($manifestInfo['globals']['_CLASS_MANIFEST'])); $this->assertNotContains('MyClass_InComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists'])); $this->assertNotContains('MyClass_InComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents'])); - $this->assertNotContains('MyClass_InComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['hastable'])); /* Our fixture defines the class MyClass_InSlashSlashComment inside a //-style comment, so it shouldn't be included in the class manifest. */ $this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_CLASS_MANIFEST'])); $this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists'])); $this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents'])); - $this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['hastable'])); } function testManifestIgnoresClassesInStrings() { $baseFolder = TEMP_FOLDER . '/manifest-test'; - $manifestInfo = ManifestBuilder::get_manifest_info($baseFolder, DB::getConn()->tableList()); + $manifestInfo = ManifestBuilder::get_manifest_info($baseFolder); /* If a class defintion is listed in a single quote string, then it shouldn't be inlcuded. Here we have put a class definition for MyClass_InSingleQuoteString inside a single-quoted string */ $this->assertNotContains('MyClass_InSingleQuoteString', array_keys($manifestInfo['globals']['_CLASS_MANIFEST'])); $this->assertNotContains('MyClass_InSingleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists'])); $this->assertNotContains('MyClass_InSingleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents'])); - $this->assertNotContains('MyClass_InSingleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['hastable'])); /* Ditto for double quotes. Here we have put a class definition for MyClass_InDoubleQuoteString inside a double-quoted string. */ $this->assertNotContains('MyClass_InDoubleQuoteString', array_keys($manifestInfo['globals']['_CLASS_MANIFEST'])); $this->assertNotContains('MyClass_InDoubleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists'])); $this->assertNotContains('MyClass_InDoubleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents'])); - $this->assertNotContains('MyClass_InDoubleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['hastable'])); /* Finally, we need to ensure that class definitions inside heredoc strings aren't included. Here, we have defined the class MyClass_InHeredocString inside a heredoc string. */ $this->assertNotContains('MyClass_InHeredocString', array_keys($manifestInfo['globals']['_CLASS_MANIFEST'])); $this->assertNotContains('MyClass_InHeredocString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists'])); $this->assertNotContains('MyClass_InHeredocString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents'])); - $this->assertNotContains('MyClass_InHeredocString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['hastable'])); } diff --git a/tests/api/RestfulServiceTest.php b/tests/api/RestfulServiceTest.php index 9c863bedb..9a7018965 100644 --- a/tests/api/RestfulServiceTest.php +++ b/tests/api/RestfulServiceTest.php @@ -9,7 +9,7 @@ class RestfulServiceTest extends SapphireTest { 'test1c' => 'And now for a string test' // string test ); $connection->setQueryString($test1params); - $test1 = $connection->request('RestfulServiceTest_Controller')->getBody(); + $test1 = $connection->request('RestfulServiceTest_Controller?usetestmanifest=1')->getBody(); foreach ($test1params as $key => $value) { $this->assertContains("$value", $test1); $this->assertContains("$value", $test1); @@ -20,7 +20,7 @@ class RestfulServiceTest extends SapphireTest { 'test2b' => '%\'"@?=;:/,$', // special character checks 'test2c' => 'And now for a string test', // string test ); - $test2suburl = 'RestfulServiceTest_Controller/?'; + $test2suburl = 'RestfulServiceTest_Controller/?usetestmanifest=1&'; foreach ($test2params as $key=>$value) { $test2suburl .= "$key=$value&"; } @@ -46,7 +46,7 @@ class RestfulServiceTest extends SapphireTest { 'test1b' => mt_rand(), 'test1c' => 'And now for a string test' ); - $test1 = $connection->request('RestfulServiceTest_Controller', 'POST', $test1params)->getBody(); + $test1 = $connection->request('RestfulServiceTest_Controller/?usetestmanifest=1', 'POST', $test1params)->getBody(); foreach ($test1params as $key => $value) { $this->assertContains("$value", $test1); $this->assertContains("$value", $test1);