public function scaffoldFormField($title = null, $params = null)
 {
     if (empty($this->object)) {
         return null;
     }
     $relationName = substr($this->name, 0, -2);
     $hasOneClass = DataObject::getSchema()->hasOneComponent(get_class($this->object), $relationName);
     if (empty($hasOneClass)) {
         return null;
     }
     $hasOneSingleton = singleton($hasOneClass);
     if ($hasOneSingleton instanceof File) {
         $field = new UploadField($relationName, $title);
         if ($hasOneSingleton instanceof Image) {
             $field->setAllowedFileCategories('image/supported');
         }
         return $field;
     }
     // Build selector / numeric field
     $titleField = $hasOneSingleton->hasField('Title') ? "Title" : "Name";
     $list = DataList::create($hasOneClass);
     // Don't scaffold a dropdown for large tables, as making the list concrete
     // might exceed the available PHP memory in creating too many DataObject instances
     if ($list->count() < 100) {
         $field = new DropdownField($this->name, $title, $list->map('ID', $titleField));
         $field->setEmptyString(' ');
     } else {
         $field = new NumericField($this->name, $title);
     }
     return $field;
 }
 public function testApplyReplationDeepInheretence()
 {
     //test has_one relation
     $newDQ = new DataQuery('DataQueryTest_E');
     //apply a relation to a relation from an ancestor class
     $newDQ->applyRelation('TestA');
     $this->assertTrue($newDQ->query()->isJoinedTo('DataQueryTest_C'));
     $this->assertContains('"DataQueryTest_A"."ID" = "DataQueryTest_C"."TestAID"', $newDQ->sql($params));
     //test many_many relation
     //test many_many with separate inheritance
     $newDQ = new DataQuery('DataQueryTest_C');
     $baseDBTable = DataObject::getSchema()->baseDataTable('DataQueryTest_C');
     $newDQ->applyRelation('ManyTestAs');
     //check we are "joined" to the DataObject's table (there is no distinction between FROM or JOIN clauses)
     $this->assertTrue($newDQ->query()->isJoinedTo($baseDBTable));
     //check we are explicitly selecting "FROM" the DO's table
     $this->assertContains("FROM \"{$baseDBTable}\"", $newDQ->sql());
     //test many_many with shared inheritance
     $newDQ = new DataQuery('DataQueryTest_E');
     $baseDBTable = DataObject::getSchema()->baseDataTable('DataQueryTest_E');
     //check we are "joined" to the DataObject's table (there is no distinction between FROM or JOIN clauses)
     $this->assertTrue($newDQ->query()->isJoinedTo($baseDBTable));
     //check we are explicitly selecting "FROM" the DO's table
     $this->assertContains("FROM \"{$baseDBTable}\"", $newDQ->sql(), 'The FROM clause is missing from the query');
     $newDQ->applyRelation('ManyTestGs');
     //confirm we are still joined to the base table
     $this->assertTrue($newDQ->query()->isJoinedTo($baseDBTable));
     //double check it is the "FROM" clause
     $this->assertContains("FROM \"{$baseDBTable}\"", $newDQ->sql(), 'The FROM clause has been removed from the query');
     //another (potentially less crude check) for checking "FROM" clause
     $fromTables = $newDQ->query()->getFrom();
     $this->assertEquals('"' . $baseDBTable . '"', $fromTables[$baseDBTable]);
 }
 /**
  * Test DataObject::composite_fields() and DataObject::is_composite_field()
  */
 public function testCompositeFieldMetaDataFunctions()
 {
     $schema = DataObject::getSchema();
     $this->assertEquals('Money', $schema->compositeField(DBCompositeTest_DataObject::class, 'MyMoney'));
     $this->assertNull($schema->compositeField(DBCompositeTest_DataObject::class, 'Title'));
     $this->assertEquals(array('MyMoney' => 'Money', 'OverriddenMoney' => 'Money'), $schema->compositeFields(DBCompositeTest_DataObject::class));
     $this->assertEquals('Money', $schema->compositeField(SubclassedDBFieldObject::class, 'MyMoney'));
     $this->assertEquals('Money', $schema->compositeField(SubclassedDBFieldObject::class, 'OtherMoney'));
     $this->assertNull($schema->compositeField(SubclassedDBFieldObject::class, 'Title'));
     $this->assertNull($schema->compositeField(SubclassedDBFieldObject::class, 'OtherField'));
     $this->assertEquals(array('MyMoney' => 'Money', 'OtherMoney' => 'Money', 'OverriddenMoney' => 'Money'), $schema->compositeFields(SubclassedDBFieldObject::class));
 }
 protected function foreignIDFilter($id = null)
 {
     if ($id === null) {
         $id = $this->getForeignID();
     }
     // Apply relation filter
     $key = DataObject::getSchema()->sqlColumnForField($this->dataClass(), $this->getForeignKey());
     if (is_array($id)) {
         return array("{$key} IN (" . DB::placeholders($id) . ")" => $id);
     } else {
         if ($id !== null) {
             return array($key => $id);
         }
     }
     return null;
 }
 /**
  * Get the base dataclass for the list of subclasses
  *
  * @return string
  */
 public function getBaseClass()
 {
     // Use explicit base class
     if ($this->baseClass) {
         return $this->baseClass;
     }
     // Default to the basename of the record
     $schema = DataObject::getSchema();
     if ($this->record) {
         return $schema->baseDataClass($this->record);
     }
     // During dev/build only the table is assigned
     $tableClass = $schema->tableClass($this->getTable());
     if ($tableClass && ($baseClass = $schema->baseDataClass($tableClass))) {
         return $baseClass;
     }
     // Fallback to global default
     return 'SilverStripe\\ORM\\DataObject';
 }
 /**
  * Check if allowed to upload more than one file
  *
  * @return bool
  */
 public function getIsMultiUpload()
 {
     if (isset($this->multiUpload)) {
         return $this->multiUpload;
     }
     // Guess from record
     $record = $this->getRecord();
     $name = $this->getName();
     // Disabled for has_one components
     if ($record && DataObject::getSchema()->hasOneComponent(get_class($record), $name)) {
         return false;
     }
     return true;
 }
 /**
  * Updates the database schema, creating tables & fields as necessary.
  *
  * @param boolean $quiet Don't show messages
  * @param boolean $populate Populate the database, as well as setting up its schema
  * @param bool $testMode
  */
 public function doBuild($quiet = false, $populate = true, $testMode = false)
 {
     if ($quiet) {
         DB::quiet();
     } else {
         $conn = DB::get_conn();
         // Assumes database class is like "MySQLDatabase" or "MSSQLDatabase" (suffixed with "Database")
         $dbType = substr(get_class($conn), 0, -8);
         $dbVersion = $conn->getVersion();
         $databaseName = method_exists($conn, 'currentDatabase') ? $conn->getSelectedDatabase() : "";
         if (Director::is_cli()) {
             echo sprintf("\n\nBuilding database %s using %s %s\n\n", $databaseName, $dbType, $dbVersion);
         } else {
             echo sprintf("<h2>Building database %s using %s %s</h2>", $databaseName, $dbType, $dbVersion);
         }
     }
     // Set up the initial database
     if (!DB::is_active()) {
         if (!$quiet) {
             echo '<p><b>Creating database</b></p>';
         }
         // Load parameters from existing configuration
         global $databaseConfig;
         if (empty($databaseConfig) && empty($_REQUEST['db'])) {
             user_error("No database configuration available", E_USER_ERROR);
         }
         $parameters = !empty($databaseConfig) ? $databaseConfig : $_REQUEST['db'];
         // Check database name is given
         if (empty($parameters['database'])) {
             user_error("No database name given; please give a value for \$databaseConfig['database']", E_USER_ERROR);
         }
         $database = $parameters['database'];
         // Establish connection and create database in two steps
         unset($parameters['database']);
         DB::connect($parameters);
         DB::create_database($database);
     }
     // Build the database.  Most of the hard work is handled by DataObject
     $dataClasses = ClassInfo::subclassesFor('SilverStripe\\ORM\\DataObject');
     array_shift($dataClasses);
     if (!$quiet) {
         if (Director::is_cli()) {
             echo "\nCREATING DATABASE TABLES\n\n";
         } else {
             echo "\n<p><b>Creating database tables</b></p>\n\n";
         }
     }
     // Initiate schema update
     $dbSchema = DB::get_schema();
     $dbSchema->schemaUpdate(function () use($dataClasses, $testMode, $quiet) {
         foreach ($dataClasses as $dataClass) {
             // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness
             if (!class_exists($dataClass)) {
                 continue;
             }
             // Check if this class should be excluded as per testing conventions
             $SNG = singleton($dataClass);
             if (!$testMode && $SNG instanceof TestOnly) {
                 continue;
             }
             // Log data
             if (!$quiet) {
                 if (Director::is_cli()) {
                     echo " * {$dataClass}\n";
                 } else {
                     echo "<li>{$dataClass}</li>\n";
                 }
             }
             // Instruct the class to apply its schema to the database
             $SNG->requireTable();
         }
     });
     ClassInfo::reset_db_cache();
     if ($populate) {
         if (!$quiet) {
             if (Director::is_cli()) {
                 echo "\nCREATING DATABASE RECORDS\n\n";
             } else {
                 echo "\n<p><b>Creating database records</b></p>\n\n";
             }
         }
         foreach ($dataClasses as $dataClass) {
             // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness
             // Test_ indicates that it's the data class is part of testing system
             if (strpos($dataClass, 'Test_') === false && class_exists($dataClass)) {
                 if (!$quiet) {
                     if (Director::is_cli()) {
                         echo " * {$dataClass}\n";
                     } else {
                         echo "<li>{$dataClass}</li>\n";
                     }
                 }
                 singleton($dataClass)->requireDefaultRecords();
             }
         }
         // Remap obsolete class names
         $schema = DataObject::getSchema();
         foreach ($this->config()->classname_value_remapping as $oldClassName => $newClassName) {
             $badRecordCount = $newClassName::get()->filter(["ClassName" => $oldClassName])->count();
             if ($badRecordCount > 0) {
                 if (Director::is_cli()) {
                     echo " * Correcting {$badRecordCount} obsolete classname values for {$newClassName}\n";
                 } else {
                     echo "<li>Correcting {$badRecordCount} obsolete classname values for {$newClassName}</li>\n";
                 }
                 $table = $schema->baseDataTable($newClassName);
                 DB::prepared_query("UPDATE \"{$table}\" SET \"ClassName\" = ? WHERE \"ClassName\" = ?", [$newClassName, $oldClassName]);
             }
         }
     }
     touch(TEMP_FOLDER . '/database-last-generated-' . str_replace(array('\\', '/', ':'), '.', Director::baseFolder()));
     if (isset($_REQUEST['from_installer'])) {
         echo "OK";
     }
     if (!$quiet) {
         echo Director::is_cli() ? "\n Database build completed!\n\n" : "<p>Database build completed!</p>";
     }
     ClassInfo::reset_db_cache();
 }
 /**
  * Return children in the live site, if it exists.
  *
  * @param bool $showAll              Include all of the elements, even those not shown in the menus. Only
  *                                   applicable when extension is applied to {@link SiteTree}.
  * @param bool $onlyDeletedFromStage Only return items that have been deleted from stage
  * @return DataList
  * @throws Exception
  */
 public function liveChildren($showAll = false, $onlyDeletedFromStage = false)
 {
     if (!$this->owner->hasExtension(Versioned::class)) {
         throw new Exception('Hierarchy->liveChildren() only works with Versioned extension applied');
     }
     $baseClass = $this->owner->baseClass();
     $hide_from_hierarchy = $this->owner->config()->hide_from_hierarchy;
     $hide_from_cms_tree = $this->owner->config()->hide_from_cms_tree;
     $children = $baseClass::get()->filter('ParentID', (int) $this->owner->ID)->exclude('ID', (int) $this->owner->ID)->setDataQueryParam(array('Versioned.mode' => $onlyDeletedFromStage ? 'stage_unique' : 'stage', 'Versioned.stage' => 'Live'));
     if ($hide_from_hierarchy) {
         $children = $children->exclude('ClassName', $hide_from_hierarchy);
     }
     if ($hide_from_cms_tree && $this->showingCMSTree()) {
         $children = $children->exclude('ClassName', $hide_from_cms_tree);
     }
     if (!$showAll && DataObject::getSchema()->fieldSpec($this->owner, 'ShowInMenus')) {
         $children = $children->filter('ShowInMenus', 1);
     }
     return $children;
 }
 /**
  * @todo Better messages for relation checks and duplicate detection
  * Note that columnMap isn't used.
  *
  * @param array $record
  * @param array $columnMap
  * @param BulkLoader_Result $results
  * @param boolean $preview
  *
  * @return int
  */
 protected function processRecord($record, $columnMap, &$results, $preview = false)
 {
     $class = $this->objectClass;
     // find existing object, or create new one
     $existingObj = $this->findExistingObject($record, $columnMap);
     /** @var DataObject $obj */
     $obj = $existingObj ? $existingObj : new $class();
     $schema = DataObject::getSchema();
     // first run: find/create any relations and store them on the object
     // we can't combine runs, as other columns might rely on the relation being present
     foreach ($record as $fieldName => $val) {
         // don't bother querying of value is not set
         if ($this->isNullValue($val)) {
             continue;
         }
         // checking for existing relations
         if (isset($this->relationCallbacks[$fieldName])) {
             // trigger custom search method for finding a relation based on the given value
             // and write it back to the relation (or create a new object)
             $relationName = $this->relationCallbacks[$fieldName]['relationname'];
             /** @var DataObject $relationObj */
             $relationObj = null;
             if ($this->hasMethod($this->relationCallbacks[$fieldName]['callback'])) {
                 $relationObj = $this->{$this->relationCallbacks[$fieldName]['callback']}($obj, $val, $record);
             } elseif ($obj->hasMethod($this->relationCallbacks[$fieldName]['callback'])) {
                 $relationObj = $obj->{$this->relationCallbacks[$fieldName]['callback']}($val, $record);
             }
             if (!$relationObj || !$relationObj->exists()) {
                 $relationClass = $schema->hasOneComponent(get_class($obj), $relationName);
                 $relationObj = new $relationClass();
                 //write if we aren't previewing
                 if (!$preview) {
                     $relationObj->write();
                 }
             }
             $obj->{"{$relationName}ID"} = $relationObj->ID;
             //write if we are not previewing
             if (!$preview) {
                 $obj->write();
                 $obj->flushCache();
                 // avoid relation caching confusion
             }
         } elseif (strpos($fieldName, '.') !== false) {
             // we have a relation column with dot notation
             list($relationName, $columnName) = explode('.', $fieldName);
             // always gives us an component (either empty or existing)
             $relationObj = $obj->getComponent($relationName);
             if (!$preview) {
                 $relationObj->write();
             }
             $obj->{"{$relationName}ID"} = $relationObj->ID;
             //write if we are not previewing
             if (!$preview) {
                 $obj->write();
                 $obj->flushCache();
                 // avoid relation caching confusion
             }
         }
     }
     // second run: save data
     foreach ($record as $fieldName => $val) {
         // break out of the loop if we are previewing
         if ($preview) {
             break;
         }
         // look up the mapping to see if this needs to map to callback
         $mapped = $this->columnMap && isset($this->columnMap[$fieldName]);
         if ($mapped && strpos($this->columnMap[$fieldName], '->') === 0) {
             $funcName = substr($this->columnMap[$fieldName], 2);
             $this->{$funcName}($obj, $val, $record);
         } else {
             if ($obj->hasMethod("import{$fieldName}")) {
                 $obj->{"import{$fieldName}"}($val, $record);
             } else {
                 $obj->update(array($fieldName => $val));
             }
         }
     }
     // write record
     if (!$preview) {
         $obj->write();
     }
     // @todo better message support
     $message = '';
     // save to results
     if ($existingObj) {
         $results->addUpdated($obj, $message);
     } else {
         $results->addCreated($obj, $message);
     }
     $objID = $obj->ID;
     $obj->destroy();
     // memory usage
     unset($existingObj);
     unset($obj);
     return $objID;
 }
 /**
  * Return the specific version of the given id.
  *
  * Caution: The record is retrieved as a DataObject, but saving back
  * modifications via write() will create a new version, rather than
  * modifying the existing one.
  *
  * @param string $class
  * @param int $id
  * @param int $version
  *
  * @return DataObject
  */
 public static function get_version($class, $id, $version)
 {
     $baseClass = DataObject::getSchema()->baseDataClass($class);
     $list = DataList::create($baseClass)->setDataQueryParam(["Versioned.mode" => 'version', "Versioned.version" => $version]);
     return $list->byID($id);
 }
 /**
  * Adds table identifier to the every column.
  * Columns must have table identifier to prevent duplicate column name error.
  *
  * @param array $columns
  * @return string
  */
 protected function prepareColumns($columns)
 {
     $cols = preg_split('/"?\\s*,\\s*"?/', trim($columns, '(") '));
     $table = DataObject::getSchema()->tableForField($this->model, current($cols));
     $cols = array_map(function ($col) use($table) {
         return sprintf('"%s"."%s"', $table, $col);
     }, $cols);
     return implode(',', $cols);
 }
 public function testGetRemoteJoinField()
 {
     $schema = DataObject::getSchema();
     // Company schema
     $staffJoinField = $schema->getRemoteJoinField(DataObjectTest_Company::class, 'CurrentStaff', 'has_many', $polymorphic);
     $this->assertEquals('CurrentCompanyID', $staffJoinField);
     $this->assertFalse($polymorphic, 'DataObjectTest_Company->CurrentStaff is not polymorphic');
     $previousStaffJoinField = $schema->getRemoteJoinField(DataObjectTest_Company::class, 'PreviousStaff', 'has_many', $polymorphic);
     $this->assertEquals('PreviousCompanyID', $previousStaffJoinField);
     $this->assertFalse($polymorphic, 'DataObjectTest_Company->PreviousStaff is not polymorphic');
     // CEO Schema
     $this->assertEquals('CEOID', $schema->getRemoteJoinField(DataObjectTest_CEO::class, 'Company', 'belongs_to', $polymorphic));
     $this->assertFalse($polymorphic, 'DataObjectTest_CEO->Company is not polymorphic');
     $this->assertEquals('PreviousCEOID', $schema->getRemoteJoinField(DataObjectTest_CEO::class, 'PreviousCompany', 'belongs_to', $polymorphic));
     $this->assertFalse($polymorphic, 'DataObjectTest_CEO->PreviousCompany is not polymorphic');
     // Team schema
     $this->assertEquals('Favourite', $schema->getRemoteJoinField(DataObjectTest_Team::class, 'Fans', 'has_many', $polymorphic));
     $this->assertTrue($polymorphic, 'DataObjectTest_Team->Fans is polymorphic');
     $this->assertEquals('TeamID', $schema->getRemoteJoinField(DataObjectTest_Team::class, 'Comments', 'has_many', $polymorphic));
     $this->assertFalse($polymorphic, 'DataObjectTest_Team->Comments is not polymorphic');
 }
 /**
  * @param DataObject|DataObjectInterface $record
  */
 public function saveInto(DataObjectInterface $record)
 {
     if (!isset($_FILES[$this->name])) {
         return;
     }
     $fileClass = File::get_class_for_file_extension(File::get_file_extension($_FILES[$this->name]['name']));
     /** @var File $file */
     if ($this->relationAutoSetting) {
         // assume that the file is connected via a has-one
         $objectClass = DataObject::getSchema()->hasOneComponent(get_class($record), $this->name);
         if ($objectClass === File::class || empty($objectClass)) {
             // Create object of the appropriate file class
             $file = Object::create($fileClass);
         } else {
             // try to create a file matching the relation
             $file = Object::create($objectClass);
         }
     } else {
         if ($record instanceof File) {
             $file = $record;
         } else {
             $file = Object::create($fileClass);
         }
     }
     $this->upload->loadIntoFile($_FILES[$this->name], $file, $this->getFolderName());
     if ($this->upload->isError()) {
         return;
     }
     if ($this->relationAutoSetting) {
         if (empty($objectClass)) {
             return;
         }
         $file = $this->upload->getFile();
         $record->{$this->name . 'ID'} = $file->ID;
     }
 }
 /**
  * @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;
 }
 /**
  * The core search engine, used by this class and its subclasses to do fun stuff.
  * Searches both SiteTree and File.
  *
  * @param array $classesToSearch
  * @param string $keywords Keywords as a string.
  * @param int $start
  * @param int $pageLength
  * @param string $sortBy
  * @param string $extraFilter
  * @param bool $booleanSearch
  * @param string $alternativeFileFilter
  * @param bool $invertedMatch
  * @return PaginatedList
  * @throws Exception
  */
 public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false)
 {
     $pageClass = 'SilverStripe\\CMS\\Model\\SiteTree';
     $fileClass = 'File';
     $pageTable = DataObject::getSchema()->tableName($pageClass);
     $fileTable = DataObject::getSchema()->tableName($fileClass);
     if (!class_exists($pageClass)) {
         throw new Exception('MySQLDatabase->searchEngine() requires "SiteTree" class');
     }
     if (!class_exists($fileClass)) {
         throw new Exception('MySQLDatabase->searchEngine() requires "File" class');
     }
     $keywords = $this->escapeString($keywords);
     $htmlEntityKeywords = htmlentities($keywords, ENT_NOQUOTES, 'UTF-8');
     $extraFilters = array($pageClass => '', $fileClass => '');
     if ($booleanSearch) {
         $boolean = "IN BOOLEAN MODE";
     }
     if ($extraFilter) {
         $extraFilters[$pageClass] = " AND {$extraFilter}";
         if ($alternativeFileFilter) {
             $extraFilters[$fileClass] = " AND {$alternativeFileFilter}";
         } else {
             $extraFilters[$fileClass] = $extraFilters[$pageClass];
         }
     }
     // Always ensure that only pages with ShowInSearch = 1 can be searched
     $extraFilters[$pageClass] .= " AND ShowInSearch <> 0";
     // File.ShowInSearch was added later, keep the database driver backwards compatible
     // by checking for its existence first
     $fields = $this->getSchemaManager()->fieldList($fileTable);
     if (array_key_exists('ShowInSearch', $fields)) {
         $extraFilters[$fileClass] .= " AND ShowInSearch <> 0";
     }
     $limit = $start . ", " . (int) $pageLength;
     $notMatch = $invertedMatch ? "NOT " : "";
     if ($keywords) {
         $match[$pageClass] = "\n\t\t\t\tMATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('{$keywords}' {$boolean})\n\t\t\t\t+ MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('{$htmlEntityKeywords}' {$boolean})\n\t\t\t";
         $fileClassSQL = Convert::raw2sql($fileClass);
         $match[$fileClass] = "MATCH (Name, Title) AGAINST ('{$keywords}' {$boolean}) AND ClassName = '{$fileClassSQL}'";
         // We make the relevance search by converting a boolean mode search into a normal one
         $relevanceKeywords = str_replace(array('*', '+', '-'), '', $keywords);
         $htmlEntityRelevanceKeywords = str_replace(array('*', '+', '-'), '', $htmlEntityKeywords);
         $relevance[$pageClass] = "MATCH (Title, MenuTitle, Content, MetaDescription) " . "AGAINST ('{$relevanceKeywords}') " . "+ MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('{$htmlEntityRelevanceKeywords}')";
         $relevance[$fileClass] = "MATCH (Name, Title) AGAINST ('{$relevanceKeywords}')";
     } else {
         $relevance[$pageClass] = $relevance[$fileClass] = 1;
         $match[$pageClass] = $match[$fileClass] = "1 = 1";
     }
     // Generate initial DataLists and base table names
     $lists = array();
     $baseClasses = array($pageClass => '', $fileClass => '');
     foreach ($classesToSearch as $class) {
         $lists[$class] = DataList::create($class)->where($notMatch . $match[$class] . $extraFilters[$class], "");
         $baseClasses[$class] = '"' . $class . '"';
     }
     $charset = Config::inst()->get('SilverStripe\\ORM\\Connect\\MySQLDatabase', 'charset');
     // Make column selection lists
     $select = array($pageClass => array("ClassName", "{$pageTable}.\"ID\"", "ParentID", "Title", "MenuTitle", "URLSegment", "Content", "LastEdited", "Created", "Name" => "_{$charset}''", "Relevance" => $relevance[$pageClass], "CanViewType"), $fileClass => array("ClassName", "{$fileTable}.\"ID\"", "ParentID", "Title", "MenuTitle" => "_{$charset}''", "URLSegment" => "_{$charset}''", "Content" => "_{$charset}''", "LastEdited", "Created", "Name", "Relevance" => $relevance[$fileClass], "CanViewType" => "NULL"));
     // Process and combine queries
     $querySQLs = array();
     $queryParameters = array();
     $totalCount = 0;
     foreach ($lists as $class => $list) {
         $table = DataObject::getSchema()->tableName($class);
         /** @var SQLSelect $query */
         $query = $list->dataQuery()->query();
         // There's no need to do all that joining
         $query->setFrom($table);
         $query->setSelect($select[$class]);
         $query->setOrderBy(array());
         $querySQLs[] = $query->sql($parameters);
         $queryParameters = array_merge($queryParameters, $parameters);
         $totalCount += $query->unlimitedRowCount();
     }
     $fullQuery = implode(" UNION ", $querySQLs) . " ORDER BY {$sortBy} LIMIT {$limit}";
     // Get records
     $records = $this->preparedQuery($fullQuery, $queryParameters);
     $objects = array();
     foreach ($records as $record) {
         $objects[] = new $record['ClassName']($record);
     }
     $list = new PaginatedList(new ArrayList($objects));
     $list->setPageStart($start);
     $list->setPageLength($pageLength);
     $list->setTotalItems($totalCount);
     // The list has already been limited by the query above
     $list->setLimitItems(false);
     return $list;
 }
 /**
  * Update the permission set associated with $record DataObject
  *
  * @param DataObjectInterface $record
  */
 public function saveInto(DataObjectInterface $record)
 {
     $fieldname = $this->name;
     $managedClass = $this->managedClass;
     // Remove all "privileged" permissions if the currently logged-in user is not an admin
     $privilegedPermissions = Permission::config()->privileged_permissions;
     if (!Permission::check('ADMIN')) {
         foreach ($this->value as $id => $bool) {
             if (in_array($id, $privilegedPermissions)) {
                 unset($this->value[$id]);
             }
         }
     }
     // remove all permissions and re-add them afterwards
     $permissions = $record->{$fieldname}();
     foreach ($permissions as $permission) {
         $permission->delete();
     }
     $schema = DataObject::getSchema();
     if ($fieldname && $record && ($schema->hasManyComponent(get_class($record), $fieldname) || $schema->manyManyComponent(get_class($record), $fieldname))) {
         if (!$record->ID) {
             $record->write();
         }
         // We need a record ID to write permissions
         if ($this->value) {
             foreach ($this->value as $id => $bool) {
                 if ($bool) {
                     $perm = new $managedClass();
                     $perm->{$this->filterField} = $record->ID;
                     $perm->Code = $id;
                     $perm->write();
                 }
             }
         }
     }
 }
 /**
  * @covers DataObjectSchema::baseDataClass()
  */
 public function testBaseDataClass()
 {
     $schema = DataObject::getSchema();
     $this->assertEquals('DataObjectSchemaTest_BaseClass', $schema->baseDataClass('DataObjectSchemaTest_BaseClass'));
     $this->assertEquals('DataObjectSchemaTest_BaseClass', $schema->baseDataClass('DataObjectSchemaTest_baseclass'));
     $this->assertEquals('DataObjectSchemaTest_BaseClass', $schema->baseDataClass('DataObjectSchemaTest_ChildClass'));
     $this->assertEquals('DataObjectSchemaTest_BaseClass', $schema->baseDataClass('DataObjectSchemaTest_CHILDCLASS'));
     $this->assertEquals('DataObjectSchemaTest_BaseClass', $schema->baseDataClass('DataObjectSchemaTest_GrandChildClass'));
     $this->assertEquals('DataObjectSchemaTest_BaseClass', $schema->baseDataClass('DataObjectSchemaTest_GRANDChildClass'));
     $this->setExpectedException('InvalidArgumentException');
     $schema->baseDataClass('SilverStripe\\ORM\\DataObject');
 }
 /**
  * Build item resource from a changesetitem
  *
  * @param ChangeSetItem $changeSetItem
  * @return array
  */
 protected function getChangeSetItemResource(ChangeSetItem $changeSetItem)
 {
     $baseClass = DataObject::getSchema()->baseDataClass($changeSetItem->ObjectClass);
     $baseSingleton = DataObject::singleton($baseClass);
     $thumbnailWidth = (int) $this->config()->thumbnail_width;
     $thumbnailHeight = (int) $this->config()->thumbnail_height;
     $hal = ['_links' => ['self' => ['href' => $this->ItemLink($changeSetItem->ID)]], 'ID' => $changeSetItem->ID, 'Created' => $changeSetItem->Created, 'LastEdited' => $changeSetItem->LastEdited, 'Title' => $changeSetItem->getTitle(), 'ChangeType' => $changeSetItem->getChangeType(), 'Added' => $changeSetItem->Added, 'ObjectClass' => $changeSetItem->ObjectClass, 'ObjectID' => $changeSetItem->ObjectID, 'BaseClass' => $baseClass, 'Singular' => $baseSingleton->i18n_singular_name(), 'Plural' => $baseSingleton->i18n_plural_name(), 'Thumbnail' => $changeSetItem->ThumbnailURL($thumbnailWidth, $thumbnailHeight)];
     // Get preview urls
     $previews = $changeSetItem->getPreviewLinks();
     if ($previews) {
         $hal['_links']['preview'] = $previews;
     }
     // Get edit link
     $editLink = $changeSetItem->CMSEditLink();
     if ($editLink) {
         $hal['_links']['edit'] = ['href' => $editLink];
     }
     // Depending on whether the object was added implicitly or explicitly, set
     // other related objects.
     if ($changeSetItem->Added === ChangeSetItem::IMPLICITLY) {
         $referencedItems = $changeSetItem->ReferencedBy();
         $referencedBy = [];
         foreach ($referencedItems as $referencedItem) {
             $referencedBy[] = ['href' => $this->SetLink($referencedItem->ID)];
         }
         if ($referencedBy) {
             $hal['_links']['referenced_by'] = $referencedBy;
         }
     }
     return $hal;
 }
 /**
  * @param  String $field Select statement identifier, either the unquoted column name,
  * the full composite SQL statement, or the alias set through {@link SQLSelect->selectField()}.
  * @return String The expression used to query this field via this DataQuery
  */
 protected function expressionForField($field)
 {
     // Prepare query object for selecting this field
     $query = $this->getFinalisedQuery(array($field));
     // Allow query to define the expression for this field
     $expression = $query->expressionForField($field);
     if (!empty($expression)) {
         return $expression;
     }
     // Special case for ID, if not provided
     if ($field === 'ID') {
         return DataObject::getSchema()->sqlColumnForField($this->dataClass, 'ID');
     }
     return null;
 }
 /**
  * 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()
 {
     $schema = DataObject::getSchema();
     // Test with blank entries
     DBClassName::clear_classname_cache();
     $do1 = new DataObjectSchemaGenerationTest_DO();
     $fields = $schema->databaseFields(DataObjectSchemaGenerationTest_DO::class, false);
     /** @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();
     $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();
     $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();
     $this->assertEquals(array('DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO', 'DataObjectSchemaGenerationTest_IndexDO' => 'DataObjectSchemaGenerationTest_IndexDO'), $item1->dbObject('ClassName')->getEnum());
     $item1->delete();
     $item2->delete();
 }
 public function testRelationParsing()
 {
     $schema = DataObject::getSchema();
     // Parent components
     $this->assertEquals([ManyManyThroughList::class, ManyManyThroughListTest_Object::class, ManyManyThroughListTest_Item::class, 'ParentID', 'ChildID', ManyManyThroughListTest_JoinObject::class], $schema->manyManyComponent(ManyManyThroughListTest_Object::class, 'Items'));
     // Belongs_many_many is the same, but with parent/child substituted
     $this->assertEquals([ManyManyThroughList::class, ManyManyThroughListTest_Item::class, ManyManyThroughListTest_Object::class, 'ChildID', 'ParentID', ManyManyThroughListTest_JoinObject::class], $schema->manyManyComponent(ManyManyThroughListTest_Item::class, 'Objects'));
 }
 /**
  * Returns the header row providing titles with sort buttons
  *
  * @param GridField $gridField
  * @return array
  */
 public function getHTMLFragments($gridField)
 {
     $list = $gridField->getList();
     if (!$this->checkDataType($list)) {
         return null;
     }
     /** @var Sortable $list */
     $forTemplate = new ArrayData(array());
     $forTemplate->Fields = new ArrayList();
     $state = $gridField->State->GridFieldSortableHeader;
     $columns = $gridField->getColumns();
     $currentColumn = 0;
     $schema = DataObject::getSchema();
     foreach ($columns as $columnField) {
         $currentColumn++;
         $metadata = $gridField->getColumnMetadata($columnField);
         $fieldName = str_replace('.', '-', $columnField);
         $title = $metadata['title'];
         if (isset($this->fieldSorting[$columnField]) && $this->fieldSorting[$columnField]) {
             $columnField = $this->fieldSorting[$columnField];
         }
         $allowSort = $title && $list->canSortBy($columnField);
         if (!$allowSort && strpos($columnField, '.') !== false) {
             // we have a relation column with dot notation
             // @see DataObject::relField for approximation
             $parts = explode('.', $columnField);
             $tmpItem = singleton($list->dataClass());
             for ($idx = 0; $idx < sizeof($parts); $idx++) {
                 $methodName = $parts[$idx];
                 if ($tmpItem instanceof SS_List) {
                     // It's impossible to sort on a HasManyList/ManyManyList
                     break;
                 } elseif (method_exists($tmpItem, 'hasMethod') && $tmpItem->hasMethod($methodName)) {
                     // The part is a relation name, so get the object/list from it
                     $tmpItem = $tmpItem->{$methodName}();
                 } elseif ($tmpItem instanceof DataObject && $schema->fieldSpec($tmpItem, $methodName, DataObjectSchema::DB_ONLY)) {
                     // Else, if we've found a database field at the end of the chain, we can sort on it.
                     // If a method is applied further to this field (E.g. 'Cost.Currency') then don't try to sort.
                     $allowSort = $idx === sizeof($parts) - 1;
                     break;
                 } else {
                     // If neither method nor field, then unable to sort
                     break;
                 }
             }
         }
         if ($allowSort) {
             $dir = 'asc';
             if ($state->SortColumn(null) == $columnField && $state->SortDirection('asc') == 'asc') {
                 $dir = 'desc';
             }
             $field = GridField_FormAction::create($gridField, 'SetOrder' . $fieldName, $title, "sort{$dir}", array('SortColumn' => $columnField))->addExtraClass('grid-field__sort');
             if ($state->SortColumn(null) == $columnField) {
                 $field->addExtraClass('ss-gridfield-sorted');
                 if ($state->SortDirection('asc') == 'asc') {
                     $field->addExtraClass('ss-gridfield-sorted-asc');
                 } else {
                     $field->addExtraClass('ss-gridfield-sorted-desc');
                 }
             }
         } else {
             if ($currentColumn == count($columns) && $gridField->getConfig()->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldFilterHeader')) {
                 $field = new LiteralField($fieldName, '<button type="button" name="showFilter" class="btn font-icon-search btn--no-text btn--icon-large grid-field__filter-open"></button>');
             } else {
                 $field = new LiteralField($fieldName, '<span class="non-sortable">' . $title . '</span>');
             }
         }
         $forTemplate->Fields->push($field);
     }
     $template = SSViewer::get_templates_by_class($this, '_Row', __CLASS__);
     return array('header' => $forTemplate->renderWith($template));
 }
 /**
  * Merges data and relations from another object of same class,
  * without conflict resolution. Allows to specify which
  * dataset takes priority in case its not empty.
  * has_one-relations are just transferred with priority 'right'.
  * has_many and many_many-relations are added regardless of priority.
  *
  * Caution: has_many/many_many relations are moved rather than duplicated,
  * meaning they are not connected to the merged object any longer.
  * Caution: Just saves updated has_many/many_many relations to the database,
  * doesn't write the updated object itself (just writes the object-properties).
  * Caution: Does not delete the merged object.
  * Caution: Does now overwrite Created date on the original object.
  *
  * @param DataObject $rightObj
  * @param string $priority left|right Determines who wins in case of a conflict (optional)
  * @param bool $includeRelations Merge any existing relations (optional)
  * @param bool $overwriteWithEmpty Overwrite existing left values with empty right values.
  *                            Only applicable with $priority='right'. (optional)
  * @return Boolean
  */
 public function merge($rightObj, $priority = 'right', $includeRelations = true, $overwriteWithEmpty = false)
 {
     $leftObj = $this;
     if ($leftObj->ClassName != $rightObj->ClassName) {
         // we can't merge similiar subclasses because they might have additional relations
         user_error("DataObject->merge(): Invalid object class '{$rightObj->ClassName}'\n\t\t\t(expected '{$leftObj->ClassName}').", E_USER_WARNING);
         return false;
     }
     if (!$rightObj->ID) {
         user_error("DataObject->merge(): Please write your merged-in object to the database before merging,\n\t\t\t\tto make sure all relations are transferred properly.').", E_USER_WARNING);
         return false;
     }
     // makes sure we don't merge data like ID or ClassName
     $rightData = DataObject::getSchema()->fieldSpecs(get_class($rightObj));
     foreach ($rightData as $key => $rightSpec) {
         // Don't merge ID
         if ($key === 'ID') {
             continue;
         }
         // Only merge relations if allowed
         if ($rightSpec === 'ForeignKey' && !$includeRelations) {
             continue;
         }
         // don't merge conflicting values if priority is 'left'
         if ($priority == 'left' && $leftObj->{$key} !== $rightObj->{$key}) {
             continue;
         }
         // don't overwrite existing left values with empty right values (if $overwriteWithEmpty is set)
         if ($priority == 'right' && !$overwriteWithEmpty && empty($rightObj->{$key})) {
             continue;
         }
         // TODO remove redundant merge of has_one fields
         $leftObj->{$key} = $rightObj->{$key};
     }
     // merge relations
     if ($includeRelations) {
         if ($manyMany = $this->manyMany()) {
             foreach ($manyMany as $relationship => $class) {
                 $leftComponents = $leftObj->getManyManyComponents($relationship);
                 $rightComponents = $rightObj->getManyManyComponents($relationship);
                 if ($rightComponents && $rightComponents->exists()) {
                     $leftComponents->addMany($rightComponents->column('ID'));
                 }
                 $leftComponents->write();
             }
         }
         if ($hasMany = $this->hasMany()) {
             foreach ($hasMany as $relationship => $class) {
                 $leftComponents = $leftObj->getComponents($relationship);
                 $rightComponents = $rightObj->getComponents($relationship);
                 if ($rightComponents && $rightComponents->exists()) {
                     $leftComponents->addMany($rightComponents->column('ID'));
                 }
                 $leftComponents->write();
             }
         }
     }
     return true;
 }
 /**
  * Determine maximum number of files allowed to be attached
  * Defaults to 1 for has_one and null (unlimited) for
  * many_many and has_many relations.
  *
  * @return integer|null Maximum limit, or null for no limit
  */
 public function getAllowedMaxFileNumber()
 {
     $allowedMaxFileNumber = $this->getConfig('allowedMaxFileNumber');
     // if there is a has_one relation with that name on the record and
     // allowedMaxFileNumber has not been set, it's wanted to be 1
     if (empty($allowedMaxFileNumber)) {
         $record = $this->getRecord();
         $name = $this->getName();
         if ($record && DataObject::getSchema()->hasOneComponent(get_class($record), $name)) {
             return 1;
             // Default for has_one
         } else {
             return null;
             // Default for has_many and many_many
         }
     } else {
         return $allowedMaxFileNumber;
     }
 }
 protected function overrideField($obj, $fieldName, $value, $fixtures = null)
 {
     $class = get_class($obj);
     $table = DataObject::getSchema()->tableForField($class, $fieldName);
     $value = $this->parseValue($value, $fixtures);
     DB::manipulate(array($table => array("command" => "update", "id" => $obj->ID, "class" => $class, "fields" => array($fieldName => $value))));
     $obj->{$fieldName} = $value;
 }
 /**
  * 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;
 }
 /**
  * @deprecated 4.0..5.0
  */
 public static function table_for_object_field($candidateClass, $fieldName)
 {
     Deprecation::notice('5.0', 'Use DataObject::getSchema()->tableForField()');
     return DataObject::getSchema()->tableForField($candidateClass, $fieldName);
 }
 /**
  * Remove all items from this many-many join.  To remove a subset of items,
  * filter it first.
  *
  * @return void
  */
 public function removeAll()
 {
     // Remove the join to the join table to avoid MySQL row locking issues.
     $query = $this->dataQuery();
     $foreignFilter = $query->getQueryParam('Foreign.Filter');
     $query->removeFilterOn($foreignFilter);
     // Select ID column
     $selectQuery = $query->query();
     $dataClassIDColumn = DataObject::getSchema()->sqlColumnForField($this->dataClass(), 'ID');
     $selectQuery->setSelect($dataClassIDColumn);
     $from = $selectQuery->getFrom();
     unset($from[$this->joinTable]);
     $selectQuery->setFrom($from);
     $selectQuery->setOrderBy();
     // ORDER BY in subselects breaks MS SQL Server and is not necessary here
     $selectQuery->setDistinct(false);
     // Use a sub-query as SQLite does not support setting delete targets in
     // joined queries.
     $delete = new SQLDelete();
     $delete->setFrom("\"{$this->joinTable}\"");
     $delete->addWhere($this->foreignIDFilter());
     $subSelect = $selectQuery->sql($parameters);
     $delete->addWhere(array("\"{$this->joinTable}\".\"{$this->localKey}\" IN ({$subSelect})" => $parameters));
     $delete->execute();
 }
 /**
  * Return a list of all tuples attached to this dataobject
  * Note: Variants are excluded
  *
  * @param DataObject $record to search
  * @return array
  */
 protected function findAssets(DataObject $record)
 {
     // Search for dbfile instances
     $files = array();
     $fields = DataObject::getSchema()->fieldSpecs($record);
     foreach ($fields as $field => $db) {
         $fieldObj = $record->{$field};
         if (!$fieldObj instanceof DBFile) {
             continue;
         }
         // Omit variant and merge with set
         $next = $record->dbObject($field)->getValue();
         unset($next['Variant']);
         if ($next) {
             $files[] = $next;
         }
     }
     // De-dupe
     return array_map("unserialize", array_unique(array_map("serialize", $files)));
 }
 /**
  * Remove invalid records from tables - that is, records that don't have
  * corresponding records in their parent class tables.
  */
 public function cleanup()
 {
     $baseClasses = [];
     foreach (ClassInfo::subclassesFor(DataObject::class) as $class) {
         if (get_parent_class($class) == DataObject::class) {
             $baseClasses[] = $class;
         }
     }
     $schema = DataObject::getSchema();
     foreach ($baseClasses as $baseClass) {
         // Get data classes
         $baseTable = $schema->baseDataTable($baseClass);
         $subclasses = ClassInfo::subclassesFor($baseClass);
         unset($subclasses[0]);
         foreach ($subclasses as $k => $subclass) {
             if (!DataObject::getSchema()->classHasTable($subclass)) {
                 unset($subclasses[$k]);
             }
         }
         if ($subclasses) {
             $records = DB::query("SELECT * FROM \"{$baseTable}\"");
             foreach ($subclasses as $subclass) {
                 $subclassTable = $schema->tableName($subclass);
                 $recordExists[$subclass] = DB::query("SELECT \"ID\" FROM \"{$subclassTable}\"")->keyedColumn();
             }
             foreach ($records as $record) {
                 foreach ($subclasses as $subclass) {
                     $subclassTable = $schema->tableName($subclass);
                     $id = $record['ID'];
                     if ($record['ClassName'] != $subclass && !is_subclass_of($record['ClassName'], $subclass) && isset($recordExists[$subclass][$id])) {
                         $sql = "DELETE FROM \"{$subclassTable}\" WHERE \"ID\" = ?";
                         echo "<li>{$sql} [{$id}]</li>";
                         DB::prepared_query($sql, [$id]);
                     }
                 }
             }
         }
     }
 }