/** * 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; }