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);