/**
  * Checks the database is in a state to perform security checks.
  * See {@link DatabaseAdmin->init()} for more information.
  *
  * @return bool
  */
 public static function database_is_ready()
 {
     // Used for unit tests
     if (self::$force_database_is_ready !== null) {
         return self::$force_database_is_ready;
     }
     if (self::$database_is_ready) {
         return self::$database_is_ready;
     }
     $requiredClasses = ClassInfo::dataClassesFor(Member::class);
     $requiredClasses[] = Group::class;
     $requiredClasses[] = Permission::class;
     $schema = DataObject::getSchema();
     foreach ($requiredClasses as $class) {
         // Skip test classes, as not all test classes are scaffolded at once
         if (is_a($class, TestOnly::class, true)) {
             continue;
         }
         // if any of the tables aren't created in the database
         $table = $schema->tableName($class);
         if (!ClassInfo::hasTable($table)) {
             return false;
         }
         // HACK: DataExtensions aren't applied until a class is instantiated for
         // the first time, so create an instance here.
         singleton($class);
         // if any of the tables don't have all fields mapped as table columns
         $dbFields = DB::field_list($table);
         if (!$dbFields) {
             return false;
         }
         $objFields = $schema->databaseFields($class, false);
         $missingFields = array_diff_key($objFields, $dbFields);
         if ($missingFields) {
             return false;
         }
     }
     self::$database_is_ready = true;
     return true;
 }
 /**
  * @covers SilverStripe\Core\ClassInfo::dataClassesFor()
  */
 public function testDataClassesFor()
 {
     $expect = array('ClassInfoTest_BaseDataClass' => 'ClassInfoTest_BaseDataClass', 'ClassInfoTest_HasFields' => 'ClassInfoTest_HasFields', 'ClassInfoTest_WithRelation' => 'ClassInfoTest_WithRelation', 'ClassInfoTest_WithCustomTable' => 'ClassInfoTest_WithCustomTable');
     $classes = array('ClassInfoTest_BaseDataClass', 'ClassInfoTest_NoFields', 'ClassInfoTest_HasFields');
     ClassInfo::reset_db_cache();
     $this->assertEquals($expect, ClassInfo::dataClassesFor($classes[0]));
     ClassInfo::reset_db_cache();
     $this->assertEquals($expect, ClassInfo::dataClassesFor(strtoupper($classes[0])));
     ClassInfo::reset_db_cache();
     $this->assertEquals($expect, ClassInfo::dataClassesFor($classes[1]));
     $expect = array('ClassInfoTest_BaseDataClass' => 'ClassInfoTest_BaseDataClass', 'ClassInfoTest_HasFields' => 'ClassInfoTest_HasFields');
     ClassInfo::reset_db_cache();
     $this->assertEquals($expect, ClassInfo::dataClassesFor($classes[2]));
     ClassInfo::reset_db_cache();
     $this->assertEquals($expect, ClassInfo::dataClassesFor(strtolower($classes[2])));
 }
 /**
  * @todo move to SQLSelect
  * @todo fix hack
  */
 protected function applyBaseTableFields()
 {
     $classes = ClassInfo::dataClassesFor($this->modelClass);
     $baseTable = DataObject::getSchema()->baseDataTable($this->modelClass);
     $fields = array("\"{$baseTable}\".*");
     if ($this->modelClass != $classes[0]) {
         $fields[] = '"' . $classes[0] . '".*';
     }
     //$fields = array_keys($model->db());
     $fields[] = '"' . $classes[0] . '".\\"ClassName\\" AS "RecordClassName"';
     return $fields;
 }
 /**
  * Ensure that the query is ready to execute.
  *
  * @param array|null $queriedColumns Any columns to filter the query by
  * @return SQLSelect The finalised sql query
  */
 public function getFinalisedQuery($queriedColumns = null)
 {
     if (!$queriedColumns) {
         $queriedColumns = $this->queriedColumns;
     }
     if ($queriedColumns) {
         $queriedColumns = array_merge($queriedColumns, array('Created', 'LastEdited', 'ClassName'));
     }
     $query = clone $this->query;
     // Apply manipulators before finalising query
     foreach ($this->getDataQueryManipulators() as $manipulator) {
         $manipulator->beforeGetFinalisedQuery($this, $queriedColumns, $query);
     }
     $schema = DataObject::getSchema();
     $baseDataClass = $schema->baseDataClass($this->dataClass());
     $baseIDColumn = $schema->sqlColumnForField($baseDataClass, 'ID');
     $ancestorClasses = ClassInfo::ancestry($this->dataClass(), true);
     // Generate the list of tables to iterate over and the list of columns required
     // by any existing where clauses. This second step is skipped if we're fetching
     // the whole dataobject as any required columns will get selected regardless.
     if ($queriedColumns) {
         // Specifying certain columns allows joining of child tables
         $tableClasses = ClassInfo::dataClassesFor($this->dataClass);
         // Ensure that any filtered columns are included in the selected columns
         foreach ($query->getWhereParameterised($parameters) as $where) {
             // Check for any columns in the form '"Column" = ?' or '"Table"."Column"' = ?
             if (preg_match_all('/(?:"(?<table>[^"]+)"\\.)?"(?<column>[^"]+)"(?:[^\\.]|$)/', $where, $matches, PREG_SET_ORDER)) {
                 foreach ($matches as $match) {
                     $column = $match['column'];
                     if (!in_array($column, $queriedColumns)) {
                         $queriedColumns[] = $column;
                     }
                 }
             }
         }
     } else {
         $tableClasses = $ancestorClasses;
     }
     // Iterate over the tables and check what we need to select from them. If any selects are made (or the table is
     // required for a select)
     foreach ($tableClasses as $tableClass) {
         // Determine explicit columns to select
         $selectColumns = null;
         if ($queriedColumns) {
             // Restrict queried columns to that on the selected table
             $tableFields = $schema->databaseFields($tableClass, false);
             unset($tableFields['ID']);
             $selectColumns = array_intersect($queriedColumns, array_keys($tableFields));
         }
         // If this is a subclass without any explicitly requested columns, omit this from the query
         if (!in_array($tableClass, $ancestorClasses) && empty($selectColumns)) {
             continue;
         }
         // Select necessary columns (unless an explicitly empty array)
         if ($selectColumns !== array()) {
             $this->selectColumnsFromTable($query, $tableClass, $selectColumns);
         }
         // Join if not the base table
         if ($tableClass !== $baseDataClass) {
             $tableName = $schema->tableName($tableClass);
             $query->addLeftJoin($tableName, "\"{$tableName}\".\"ID\" = {$baseIDColumn}", $tableName, 10);
         }
     }
     // Resolve colliding fields
     if ($this->collidingFields) {
         foreach ($this->collidingFields as $collisionField => $collisions) {
             $caseClauses = array();
             foreach ($collisions as $collision) {
                 if (preg_match('/^"(?<table>[^"]+)"\\./', $collision, $matches)) {
                     $collisionTable = $matches['table'];
                     $collisionClass = $schema->tableClass($collisionTable);
                     if ($collisionClass) {
                         $collisionClassColumn = $schema->sqlColumnForField($collisionClass, 'ClassName');
                         $collisionClasses = ClassInfo::subclassesFor($collisionClass);
                         $collisionClassesSQL = implode(', ', Convert::raw2sql($collisionClasses, true));
                         $caseClauses[] = "WHEN {$collisionClassColumn} IN ({$collisionClassesSQL}) THEN {$collision}";
                     }
                 } else {
                     user_error("Bad collision item '{$collision}'", E_USER_WARNING);
                 }
             }
             $query->selectField("CASE " . implode(" ", $caseClauses) . " ELSE NULL END", $collisionField);
         }
     }
     if ($this->filterByClassName) {
         // If querying the base class, don't bother filtering on class name
         if ($this->dataClass != $baseDataClass) {
             // Get the ClassName values to filter to
             $classNames = ClassInfo::subclassesFor($this->dataClass);
             $classNamesPlaceholders = DB::placeholders($classNames);
             $baseClassColumn = $schema->sqlColumnForField($baseDataClass, 'ClassName');
             $query->addWhere(array("{$baseClassColumn} IN ({$classNamesPlaceholders})" => $classNames));
         }
     }
     // Select ID
     $query->selectField($baseIDColumn, "ID");
     // Select RecordClassName
     $baseClassColumn = $schema->sqlColumnForField($baseDataClass, 'ClassName');
     $query->selectField("\n\t\t\tCASE WHEN {$baseClassColumn} IS NOT NULL THEN {$baseClassColumn}\n\t\t\tELSE " . Convert::raw2sql($baseDataClass, true) . " END", "RecordClassName");
     // TODO: Versioned, Translatable, SiteTreeSubsites, etc, could probably be better implemented as subclasses
     // of DataQuery
     $obj = Injector::inst()->get($this->dataClass);
     $obj->extend('augmentSQL', $query, $this);
     $this->ensureSelectContainsOrderbyColumns($query);
     // Apply post-finalisation manipulations
     foreach ($this->getDataQueryManipulators() as $manipulator) {
         $manipulator->afterGetFinalisedQuery($this, $queriedColumns, $query);
     }
     return $query;
 }