/** * 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('SilverStripe\\Security\\Member'); $requiredClasses[] = 'SilverStripe\\Security\\Group'; $requiredClasses[] = 'SilverStripe\\Security\\Permission'; foreach ($requiredClasses as $class) { // Skip test classes, as not all test classes are scaffolded at once if (is_subclass_of($class, 'TestOnly')) { continue; } // if any of the tables aren't created in the database $table = DataObject::getSchema()->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 = DataObject::database_fields($class); $missingFields = array_diff_key($objFields, $dbFields); if ($missingFields) { return false; } } self::$database_is_ready = true; return true; }
/** * Update the SELECT clause of the query with the columns from the given table * * @param SQLSelect $query * @param string $tableClass Class to select from * @param array $columns */ protected function selectColumnsFromTable(SQLSelect &$query, $tableClass, $columns = null) { // Add SQL for multi-value fields $databaseFields = DataObject::database_fields($tableClass); $compositeFields = DataObject::composite_fields($tableClass, false); unset($databaseFields['ID']); foreach ($databaseFields as $k => $v) { if ((is_null($columns) || in_array($k, $columns)) && !isset($compositeFields[$k])) { // Update $collidingFields if necessary $expressionForField = $query->expressionForField($k); $quotedField = DataObject::getSchema()->sqlColumnForField($tableClass, $k); if ($expressionForField) { if (!isset($this->collidingFields[$k])) { $this->collidingFields[$k] = array($expressionForField); } $this->collidingFields[$k][] = $quotedField; } else { $query->selectField($quotedField, $k); } } } foreach ($compositeFields as $k => $v) { if ((is_null($columns) || in_array($k, $columns)) && $v) { $tableName = DataObject::getSchema()->tableName($tableClass); $dbO = Object::create_from_string($v, $k); $dbO->setTable($tableName); $dbO->addToQuery($query); } } }
/** * Generates a ($table)_version DB manipulation and injects it into the current $manipulation * * @param array $manipulation Source manipulation data * @param string $class Class * @param string $table Table Table for this class * @param int $recordID ID of record to version */ protected function augmentWriteVersioned(&$manipulation, $class, $table, $recordID) { $baseDataClass = DataObject::getSchema()->baseDataClass($class); $baseDataTable = DataObject::getSchema()->tableName($baseDataClass); // Set up a new entry in (table)_versions $newManipulation = array("command" => "insert", "fields" => isset($manipulation[$table]['fields']) ? $manipulation[$table]['fields'] : [], "class" => $class); // Add any extra, unchanged fields to the version record. $data = DB::prepared_query("SELECT * FROM \"{$table}\" WHERE \"ID\" = ?", array($recordID))->record(); if ($data) { $fields = DataObject::database_fields($class); if (is_array($fields)) { $data = array_intersect_key($data, $fields); foreach ($data as $k => $v) { // If the value is not set at all in the manipulation currently, use the existing value from the database if (!array_key_exists($k, $newManipulation['fields'])) { $newManipulation['fields'][$k] = $v; } } } } // Ensure that the ID is instead written to the RecordID field $newManipulation['fields']['RecordID'] = $recordID; unset($newManipulation['fields']['ID']); // Generate next version ID to use $nextVersion = 0; if ($recordID) { $nextVersion = DB::prepared_query("SELECT MAX(\"Version\") + 1\n\t\t\t\tFROM \"{$baseDataTable}_versions\" WHERE \"RecordID\" = ?", array($recordID))->value(); } $nextVersion = $nextVersion ?: 1; if ($class === $baseDataClass) { // Write AuthorID for baseclass $userID = Member::currentUser() ? Member::currentUser()->ID : 0; $newManipulation['fields']['AuthorID'] = $userID; // Update main table version if not previously known $manipulation[$table]['fields']['Version'] = $nextVersion; } // Update _versions table manipulation $newManipulation['fields']['Version'] = $nextVersion; $manipulation["{$table}_versions"] = $newManipulation; }
/** * Tests the generation of the ClassName spec and ensure it's not unnecessarily influenced * by the order of classnames of existing records */ public function testClassNameSpecGeneration() { // Test with blank entries DBClassName::clear_classname_cache(); $do1 = new DataObjectSchemaGenerationTest_DO(); $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO'); /** @skipUpgrade */ $this->assertEquals("DBClassName", $fields['ClassName']); $this->assertEquals(array('DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO', 'DataObjectSchemaGenerationTest_IndexDO' => 'DataObjectSchemaGenerationTest_IndexDO'), $do1->dbObject('ClassName')->getEnum()); // Test with instance of subclass $item1 = new DataObjectSchemaGenerationTest_IndexDO(); $item1->write(); DBClassName::clear_classname_cache(); $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO'); /** @skipUpgrade */ $this->assertEquals("DBClassName", $fields['ClassName']); $this->assertEquals(array('DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO', 'DataObjectSchemaGenerationTest_IndexDO' => 'DataObjectSchemaGenerationTest_IndexDO'), $item1->dbObject('ClassName')->getEnum()); $item1->delete(); // Test with instance of main class $item2 = new DataObjectSchemaGenerationTest_DO(); $item2->write(); DBClassName::clear_classname_cache(); $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO'); /** @skipUpgrade */ $this->assertEquals("DBClassName", $fields['ClassName']); $this->assertEquals(array('DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO', 'DataObjectSchemaGenerationTest_IndexDO' => 'DataObjectSchemaGenerationTest_IndexDO'), $item2->dbObject('ClassName')->getEnum()); $item2->delete(); // Test with instances of both classes $item1 = new DataObjectSchemaGenerationTest_IndexDO(); $item1->write(); $item2 = new DataObjectSchemaGenerationTest_DO(); $item2->write(); DBClassName::clear_classname_cache(); $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO'); /** @skipUpgrade */ $this->assertEquals("DBClassName", $fields['ClassName']); $this->assertEquals(array('DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO', 'DataObjectSchemaGenerationTest_IndexDO' => 'DataObjectSchemaGenerationTest_IndexDO'), $item1->dbObject('ClassName')->getEnum()); $item1->delete(); $item2->delete(); }
/** * @todo Re-enable all test cases for field inheritance aggregation after behaviour has been fixed */ public function testFieldInheritance() { $teamInstance = $this->objFromFixture('DataObjectTest_Team', 'team1'); $subteamInstance = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1'); $this->assertEquals(array('ID', 'ClassName', 'LastEdited', 'Created', 'Title', 'DatabaseField', 'ExtendedDatabaseField', 'CaptainID', 'FounderID', 'HasOneRelationshipID', 'ExtendedHasOneRelationshipID'), array_keys($teamInstance->db()), 'inheritedDatabaseFields() contains all fields defined on instance: base, extended and foreign keys'); $this->assertEquals(array('ID', 'ClassName', 'LastEdited', 'Created', 'Title', 'DatabaseField', 'ExtendedDatabaseField', 'CaptainID', 'FounderID', 'HasOneRelationshipID', 'ExtendedHasOneRelationshipID'), array_keys(DataObject::database_fields('DataObjectTest_Team', false)), 'databaseFields() contains only fields defined on instance, including base, extended and foreign keys'); $this->assertEquals(array('ID', 'ClassName', 'LastEdited', 'Created', 'Title', 'DatabaseField', 'ExtendedDatabaseField', 'CaptainID', 'FounderID', 'HasOneRelationshipID', 'ExtendedHasOneRelationshipID', 'SubclassDatabaseField', 'ParentTeamID'), array_keys($subteamInstance->db()), 'inheritedDatabaseFields() on subclass contains all fields, including base, extended and foreign keys'); $this->assertEquals(array('ID', 'SubclassDatabaseField', 'ParentTeamID'), array_keys(DataObject::database_fields('DataObjectTest_SubTeam')), 'databaseFields() on subclass contains only fields defined on instance'); }