/**
  * @see DataExtension::augmentSQL()
  */
 public function augmentSQL(SQLQuery &$query)
 {
     $select = $query->getSelect();
     if (empty($select) || $query->getDelete() || in_array("COUNT(*)", $select) || in_array("count(*)", $select)) {
         return;
     }
     if (!isset(self::$sortTables[$this->owner->class])) {
         $classes = array_reverse(ClassInfo::dataClassesFor($this->owner->class));
         $class = null;
         foreach ($classes as $cls) {
             if (DataObject::has_own_table($cls) && ($fields = DataObject::database_fields($cls)) && isset($fields['SortOrder'])) {
                 $class = $cls;
                 break;
             }
         }
         self::$sortTables[$this->owner->class] = $class;
     } else {
         $class = self::$sortTables[$this->owner->class];
     }
     if ($class) {
         $query->addOrderBy("\"{$class}\".\"SortOrder\" " . self::$sort_dir);
     } else {
         $query->addOrderBy("\"SortOrder\" " . self::$sort_dir);
     }
 }
 /**
  * fieldsInExtraTables function.
  *
  * @access public
  * @param mixed $suffix
  * @return array
  */
 public function fieldsInExtraTables($suffix)
 {
     $fields = array();
     //$fields['db'] = DataObject::database_fields($this->owner->class);
     $fields['indexes'] = $this->owner->databaseIndexes();
     $fields['db'] = array_merge(DataObject::database_fields($this->owner->class));
     return $fields;
 }
 public static function allFieldsForClass($class)
 {
     $dataClasses = ClassInfo::dataClassesFor($class);
     $fields = array();
     foreach ($dataClasses as $dataClass) {
         $fields = array_merge($fields, array_keys(DataObject::database_fields($dataClass)));
     }
     return array_combine($fields, $fields);
 }
 /**
  * Constructor method for MemberTableField.
  * 
  * @param Controller $controller Controller class which created this field
  * @param string $name Name of the field (e.g. "Members")
  * @param mixed $group Can be the ID of a Group instance, or a Group instance itself
  * @param DataObjectSet $members Optional set of Members to set as the source items for this field
  * @param boolean $hidePassword Hide the password field or not in the summary?
  */
 function __construct($controller, $name, $group = null, $members = null, $hidePassword = true)
 {
     $sourceClass = self::$data_class;
     $SNG_member = singleton($sourceClass);
     $fieldList = $SNG_member->summaryFields();
     $memberDbFields = DataObject::database_fields('Member');
     $csvFieldList = array();
     foreach ($memberDbFields as $field => $dbFieldType) {
         $csvFieldList[$field] = $field;
     }
     if ($group) {
         if (is_object($group)) {
             $this->group = $group;
         } elseif (is_numeric($group)) {
             $this->group = DataObject::get_by_id('Group', $group);
         }
     } else {
         if (isset($_REQUEST['ctf'][$this->Name()]["ID"]) && is_numeric($_REQUEST['ctf'][$this->Name()]["ID"])) {
             $this->group = DataObject::get_by_id('Group', $_REQUEST['ctf'][$this->Name()]["ID"]);
         }
     }
     if (!$hidePassword) {
         $fieldList["SetPassword"] = "******";
     }
     $this->hidePassword = $hidePassword;
     // @todo shouldn't this use $this->group? It's unclear exactly
     // what group it should be customising the custom Member set with.
     if ($members && $group) {
         $this->setCustomSourceItems($this->memberListWithGroupID($members, $group));
     }
     parent::__construct($controller, $name, $sourceClass, $fieldList);
     $SQL_search = isset($_REQUEST['MemberSearch']) ? Convert::raw2sql($_REQUEST['MemberSearch']) : null;
     if (!empty($_REQUEST['MemberSearch'])) {
         $searchFilters = array();
         foreach ($SNG_member->searchableFields() as $fieldName => $fieldSpec) {
             if (strpos($fieldName, '.') === false) {
                 $searchFilters[] = "\"{$fieldName}\" LIKE '%{$SQL_search}%'";
             }
         }
         $this->sourceFilter[] = '(' . implode(' OR ', $searchFilters) . ')';
     }
     if ($this->group) {
         $groupIDs = array($this->group->ID);
         if ($this->group->AllChildren()) {
             $groupIDs = array_merge($groupIDs, $this->group->AllChildren()->column('ID'));
         }
         $this->sourceFilter[] = sprintf('"Group_Members"."GroupID" IN (%s)', implode(',', $groupIDs));
     }
     $this->sourceJoin = " INNER JOIN \"Group_Members\" ON \"MemberID\"=\"Member\".\"ID\"";
     $this->setFieldListCsv($csvFieldList);
     $this->setPageSize($this->stat('page_size'));
 }
 function onBeforeWrite()
 {
     parent::onBeforeWrite();
     if (!$this->isInDb()) {
         if ($this->Type) {
             $this->ClassName = $this->Type;
         }
         foreach (DataObject::database_fields($this->ClassName) as $fieldName => $fieldType) {
             $this->{$fieldName} = $this->record[$fieldName . "-for-" . $this->ClassName];
         }
         $this->Label = $this->record["Label-for-" . $this->ClassName];
     }
 }
 /**
  * Gets an array of elastic field definitions.
  *
  * @return array
  */
 public function getElasticaFields()
 {
     $db = \DataObject::database_fields(get_class($this->owner));
     $fields = $this->owner->searchableFields();
     $result = array();
     foreach ($fields as $name => $params) {
         $type = null;
         $spec = array();
         if (array_key_exists($name, $db)) {
             $class = $db[$name];
             if ($pos = strpos($class, '(')) {
                 $class = substr($class, 0, $pos);
             }
             if (array_key_exists($class, self::$mappings)) {
                 $spec['type'] = self::$mappings[$class];
             }
         }
         $result[$name] = $spec;
     }
     $result['LastEdited'] = array('type' => 'date');
     $result['Created'] = array('type' => 'date');
     $result['ID'] = array('type' => 'integer');
     $result['ParentID'] = array('type' => 'integer');
     $result['Sort'] = array('type' => 'integer');
     $result['Name'] = array('type' => 'string');
     $result['MenuTitle'] = array('type' => 'string');
     $result['ShowInSearch'] = array('type' => 'integer');
     $result['ClassName'] = array('type' => 'string');
     $result['ClassNameHierarchy'] = array('type' => 'string');
     // fix up dates
     foreach ($result as $field => $spec) {
         if (isset($spec['type']) && $spec['type'] == 'date') {
             $spec['format'] = 'yyyy-MM-dd HH:mm:ss';
             $result[$field] = $spec;
         }
     }
     if (isset($result['Content']) && count($result['Content'])) {
         $spec = $result['Content'];
         $spec['store'] = false;
         $result['Content'] = $spec;
     }
     if (method_exists($this->owner, 'updateElasticMappings')) {
         $this->owner->updateElasticMappings($result);
     }
     $this->owner->extend('updateElasticMappings', $result);
     return $result;
 }
 public function sendUpdateNotification($data)
 {
     $name = $data['FirstName'] . " " . $data['Surname'];
     $body = "{$name} has updated their details via the website. Here is the new information:<br/>";
     $notifyOnFields = Member::config()->frontend_update_notification_fields ?: DataObject::database_fields('Member');
     $changedFields = $this->member->getChangedFields(true, 2);
     $send = false;
     foreach ($changedFields as $key => $field) {
         if (in_array($key, $notifyOnFields)) {
             $body .= "<br/><strong>{$key}:</strong><br/>" . "<strike style='color:red;'>" . $field['before'] . "</strike><br/>" . "<span style='color:green;'>" . $field['after'] . "</span><br/>";
             $send = true;
         }
     }
     if ($send) {
         $email = new Email(Email::config()->admin_email, Email::config()->admin_email, "Member details update: {$name}", $body);
         $email->send();
     }
 }
 /**
  * Gets an array of elastic field definitions.
  *
  * @return array
  */
 public function getElasticaFields()
 {
     $db = \DataObject::database_fields(get_class($this->owner));
     $fields = $this->owner->searchableFields();
     $result = array();
     foreach ($fields as $name => $params) {
         $type = null;
         $spec = array();
         if (array_key_exists($name, $db)) {
             $class = $db[$name];
             if ($pos = strpos($class, '(')) {
                 $class = substr($class, 0, $pos);
             }
             if (array_key_exists($class, self::$mappings)) {
                 $spec['type'] = self::$mappings[$class];
             }
         }
         $result[$name] = $spec;
     }
     return $result;
 }
 /**
  * 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
     DataObject::clear_classname_spec_cache();
     $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
     $this->assertEquals("Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')", $fields['ClassName']);
     // Test with instance of subclass
     $item1 = new DataObjectSchemaGenerationTest_IndexDO();
     $item1->write();
     DataObject::clear_classname_spec_cache();
     $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
     $this->assertEquals("Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')", $fields['ClassName']);
     $item1->delete();
     // Test with instance of main class
     $item2 = new DataObjectSchemaGenerationTest_DO();
     $item2->write();
     DataObject::clear_classname_spec_cache();
     $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
     $this->assertEquals("Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')", $fields['ClassName']);
     $item2->delete();
     // Test with instances of both classes
     $item1 = new DataObjectSchemaGenerationTest_IndexDO();
     $item1->write();
     $item2 = new DataObjectSchemaGenerationTest_DO();
     $item2->write();
     DataObject::clear_classname_spec_cache();
     $fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
     $this->assertEquals("Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')", $fields['ClassName']);
     $item1->delete();
     $item2->delete();
 }
 /**
  * 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;
     }
     $requiredTables = ClassInfo::dataClassesFor('Member');
     $requiredTables[] = 'Group';
     $requiredTables[] = 'Permission';
     foreach ($requiredTables as $table) {
         // Skip test classes, as not all test classes are scaffolded at once
         if (is_subclass_of($table, 'TestOnly')) {
             continue;
         }
         // if any of the tables aren't created in the database
         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($table);
         // 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($table, false);
         $missingFields = array_diff_key($objFields, $dbFields);
         if ($missingFields) {
             return false;
         }
     }
     self::$database_is_ready = true;
     return true;
 }
 /**
  * Entry point for being called from a template.
  * 
  * This gets the aggregate function 
  * 
  */
 public function XML_val($name, $args = null, $cache = false)
 {
     $func = strtoupper(strpos($name, 'get') === 0 ? substr($name, 3) : $name);
     $attribute = $args ? $args[0] : 'ID';
     $table = null;
     foreach (ClassInfo::ancestry($this->type, true) as $class) {
         $fields = DataObject::database_fields($class);
         if (array_key_exists($attribute, $fields)) {
             $table = $class;
             break;
         }
     }
     if (!$table) {
         user_error("Couldn't find table for field {$attribute} in type {$this->type}", E_USER_ERROR);
     }
     $query = $this->query("{$func}(\"{$table}\".\"{$attribute}\")");
     // Cache results of this specific SQL query until flushCache() is triggered.
     $cachekey = sha1($query->sql());
     $cache = self::cache();
     if (!($result = $cache->load($cachekey))) {
         $result = (string) $query->execute()->value();
         if (!$result) {
             $result = '0';
         }
         $cache->save($result, null, array('aggregate', preg_replace('/[^a-zA-Z0-9_]/', '_', $this->type)));
     }
     return $result;
 }
Example #12
0
 /**
  * Remove invalid records from tables - that is, records that don't have
  * corresponding records in their parent class tables.
  */
 public function cleanup()
 {
     $allClasses = get_declared_classes();
     foreach ($allClasses as $class) {
         if (get_parent_class($class) == 'DataObject') {
             $baseClasses[] = $class;
         }
     }
     foreach ($baseClasses as $baseClass) {
         // Get data classes
         $subclasses = ClassInfo::subclassesFor($baseClass);
         unset($subclasses[0]);
         foreach ($subclasses as $k => $subclass) {
             if (DataObject::database_fields($subclass)) {
                 unset($subclasses[$k]);
             }
         }
         if ($subclasses) {
             $records = DB::query("SELECT * FROM \"{$baseClass}\"");
             foreach ($subclasses as $subclass) {
                 $recordExists[$subclass] = DB::query("SELECT \"ID\" FROM \"{$subclass}\"")->keyedColumn();
             }
             foreach ($records as $record) {
                 foreach ($subclasses as $subclass) {
                     $id = $record['ID'];
                     if ($record['ClassName'] != $subclass && !is_subclass_of($record['ClassName'], $subclass) && isset($recordExists[$subclass][$id])) {
                         $sql = "DELETE FROM \"{$subclass}\" WHERE \"ID\" = {$record['ID']}";
                         echo "<li>{$sql}";
                         DB::query($sql);
                     }
                 }
             }
         }
     }
 }
 /**
  *	Recursively return the relationships for a given data object map.
  */
 private function recursiveRelationships(&$object, $attributeVisibility = false, $cache = array())
 {
     $output = array();
     // Cache relationship data objects to prevent infinite recursion.
     if (!in_array("{$object['ClassName']} {$object['ID']}", $cache)) {
         $cache[] = "{$object['ClassName']} {$object['ID']}";
         foreach ($object as $attribute => $value) {
             if ($attribute !== 'ClassName' && $attribute !== 'RecordClassName') {
                 // Grab the name of a relationship.
                 $relationship = substr($attribute, strlen($attribute) - 2) === 'ID' && strlen($attribute) > 2 ? substr($attribute, 0, -2) : null;
                 if ($relationship && ($relationObject = DataObject::get_by_id($object['ClassName'], $object['ID'])) && $relationObject->hasMethod($relationship) && $value != 0) {
                     // Grab the relationship.
                     $relationObject = $relationObject->{$relationship}();
                     // Make sure recursive relationships are enabled.
                     if (!$this->recursiveRelationships) {
                         $output[$relationship] = array($relationObject->ClassName => array('ID' => (string) $relationObject->ID));
                         continue;
                     }
                     $temporaryMap = $relationObject->toMap();
                     if ($attributeVisibility) {
                         // Grab the attribute visibility.
                         $class = is_subclass_of($relationObject->ClassName, 'SiteTree') ? 'SiteTree' : (is_subclass_of($relationObject->ClassName, 'File') ? 'File' : $relationObject->ClassName);
                         $relationConfiguration = DataObjectOutputConfiguration::get_one('DataObjectOutputConfiguration', "IsFor = '" . Convert::raw2sql($class) . "'");
                         $relationVisibility = $relationConfiguration && $relationConfiguration->APIwesomeVisibility ? explode(',', $relationConfiguration->APIwesomeVisibility) : null;
                         $columns = array();
                         foreach (ClassInfo::subclassesFor($class) as $subclass) {
                             // Prepend the table names.
                             $subclassColumns = array();
                             foreach (DataObject::database_fields($subclass) as $column => $type) {
                                 $subclassColumns["{$subclass}.{$column}"] = $type;
                             }
                             $columns = array_merge($columns, $subclassColumns);
                         }
                         array_shift($columns);
                         // Make sure this relationship has visibility customisation.
                         if (is_null($relationVisibility) || count($relationVisibility) !== count($columns) || !in_array('1', $relationVisibility)) {
                             $output[$relationship] = array($relationObject->ClassName => array('ID' => (string) $relationObject->ID));
                             continue;
                         }
                         // Grab all data object visible attributes.
                         $select = array('ClassName' => $relationObject->ClassName, 'ID' => $relationObject->ID);
                         $iteration = 0;
                         foreach ($columns as $relationshipAttribute => $relationshipType) {
                             if (isset($relationVisibility[$iteration]) && $relationVisibility[$iteration]) {
                                 $split = explode('.', $relationshipAttribute);
                                 $relationshipAttribute = count($split) === 2 ? $split[1] : $relationshipAttribute;
                                 if (isset($temporaryMap[$relationshipAttribute]) && $temporaryMap[$relationshipAttribute]) {
                                     // Retrieve the relationship value, and compose any asset file paths.
                                     $relationshipValue = $temporaryMap[$relationshipAttribute];
                                     $select[$relationshipAttribute] = (strpos(strtolower($relationshipAttribute), 'file') !== false || strpos(strtolower($relationshipAttribute), 'image') !== false) && strpos($relationshipValue, 'assets/') !== false ? Director::absoluteURL($relationshipValue) : (is_integer($relationshipValue) ? (string) $relationshipValue : $relationshipValue);
                                 }
                             }
                             $iteration++;
                         }
                     } else {
                         $select = $temporaryMap;
                     }
                     // Check the corresponding relationship.
                     $output[$relationship] = array($relationObject->ClassName => $this->recursiveRelationships($select, $attributeVisibility, $cache));
                 } else {
                     // Compose any asset file paths.
                     $output[$attribute] = (strpos(strtolower($attribute), 'file') !== false || strpos(strtolower($attribute), 'image') !== false) && strpos($value, 'assets/') !== false ? Director::absoluteURL($value) : (is_integer($value) ? (string) $value : $value);
                 }
             }
         }
     } else {
         // This relationship has previously been cached.
         $output['ID'] = $object['ID'];
     }
     // Return the visible relationship attributes.
     return $output;
 }
 function augmentDatabase()
 {
     $classTable = $this->owner->class;
     $isRootClass = $this->owner->class == ClassInfo::baseDataClass($this->owner->class);
     // Build a list of suffixes whose tables need versioning
     $allSuffixes = array();
     foreach (Versioned::$versionableExtensions as $versionableExtension => $suffixes) {
         if ($this->owner->hasExtension($versionableExtension)) {
             $allSuffixes = array_merge($allSuffixes, (array) $suffixes);
             foreach ((array) $suffixes as $suffix) {
                 $allSuffixes[$suffix] = $versionableExtension;
             }
         }
     }
     // Add the default table with an empty suffix to the list (table name = class name)
     array_push($allSuffixes, '');
     foreach ($allSuffixes as $key => $suffix) {
         // check that this is a valid suffix
         if (!is_int($key)) {
             continue;
         }
         if ($suffix) {
             $table = "{$classTable}_{$suffix}";
         } else {
             $table = $classTable;
         }
         if ($fields = DataObject::database_fields($this->owner->class)) {
             $indexes = $this->owner->databaseIndexes();
             if ($suffix && ($ext = $this->owner->getExtensionInstance($allSuffixes[$suffix]))) {
                 if (!$ext->isVersionedTable($table)) {
                     continue;
                 }
                 $ext->setOwner($this->owner);
                 $fields = $ext->fieldsInExtraTables($suffix);
                 $ext->clearOwner();
                 $indexes = $fields['indexes'];
                 $fields = $fields['db'];
             }
             // Create tables for other stages
             foreach ($this->stages as $stage) {
                 // Extra tables for _Live, etc.
                 if ($stage != $this->defaultStage) {
                     DB::requireTable("{$table}_{$stage}", $fields, $indexes, false);
                 }
                 // Version fields on each root table (including Stage)
                 /*
                 if($isRootClass) {
                 	$stageTable = ($stage == $this->defaultStage) ? $table : "{$table}_$stage";
                 	$parts=Array('datatype'=>'int', 'precision'=>11, 'null'=>'not null', 'default'=>(int)0);
                 	$values=Array('type'=>'int', 'parts'=>$parts);
                 	DB::requireField($stageTable, 'Version', $values);
                 }
                 */
             }
             if ($isRootClass) {
                 // Create table for all versions
                 $versionFields = array_merge(self::$db_for_versions_table, (array) $fields);
                 $versionIndexes = array_merge(self::$indexes_for_versions_table, (array) $indexes);
             } else {
                 // Create fields for any tables of subclasses
                 $versionFields = array_merge(array("RecordID" => "Int", "Version" => "Int"), (array) $fields);
                 $versionIndexes = array_merge(array('RecordID_Version' => array('type' => 'unique', 'value' => 'RecordID,Version'), 'RecordID' => true, 'Version' => true), (array) $indexes);
             }
             if (DB::getConn()->hasTable("{$table}_versions")) {
                 // Fix data that lacks the uniqueness constraint (since this was added later and
                 // bugs meant that the constraint was validated)
                 $duplications = DB::query("SELECT MIN(\"ID\") AS \"ID\", \"RecordID\", \"Version\" \n\t\t\t\t\t\tFROM \"{$table}_versions\" GROUP BY \"RecordID\", \"Version\" \n\t\t\t\t\t\tHAVING COUNT(*) > 1");
                 foreach ($duplications as $dup) {
                     DB::alteration_message("Removing {$table}_versions duplicate data for " . "{$dup['RecordID']}/{$dup['Version']}", "deleted");
                     DB::query("DELETE FROM \"{$table}_versions\" WHERE \"RecordID\" = {$dup['RecordID']}\n\t\t\t\t\t\t\tAND \"Version\" = {$dup['Version']} AND \"ID\" != {$dup['ID']}");
                 }
                 // Remove junk which has no data in parent classes. Only needs to run the following
                 // when versioned data is spread over multiple tables
                 if (!$isRootClass && ($versionedTables = ClassInfo::dataClassesFor($table))) {
                     foreach ($versionedTables as $child) {
                         if ($table == $child) {
                             break;
                         }
                         // only need subclasses
                         $count = DB::query("\n\t\t\t\t\t\t\t\tSELECT COUNT(*) FROM \"{$table}_versions\"\n\t\t\t\t\t\t\t\tLEFT JOIN \"{$child}_versions\" \n\t\t\t\t\t\t\t\t\tON \"{$child}_versions\".\"RecordID\" = \"{$table}_versions\".\"RecordID\"\n\t\t\t\t\t\t\t\t\tAND \"{$child}_versions\".\"Version\" = \"{$table}_versions\".\"Version\"\n\t\t\t\t\t\t\t\tWHERE \"{$child}_versions\".\"ID\" IS NULL\n\t\t\t\t\t\t\t")->value();
                         if ($count > 0) {
                             DB::alteration_message("Removing orphaned versioned records", "deleted");
                             $effectedIDs = DB::query("\n\t\t\t\t\t\t\t\t\tSELECT \"{$table}_versions\".\"ID\" FROM \"{$table}_versions\"\n\t\t\t\t\t\t\t\t\tLEFT JOIN \"{$child}_versions\" \n\t\t\t\t\t\t\t\t\t\tON \"{$child}_versions\".\"RecordID\" = \"{$table}_versions\".\"RecordID\"\n\t\t\t\t\t\t\t\t\t\tAND \"{$child}_versions\".\"Version\" = \"{$table}_versions\".\"Version\"\n\t\t\t\t\t\t\t\t\tWHERE \"{$child}_versions\".\"ID\" IS NULL\n\t\t\t\t\t\t\t\t")->column();
                             if (is_array($effectedIDs)) {
                                 foreach ($effectedIDs as $key => $value) {
                                     DB::query("DELETE FROM \"{$table}_versions\" WHERE \"{$table}_versions\".\"ID\" = '{$value}'");
                                 }
                             }
                         }
                     }
                 }
             }
             DB::requireTable("{$table}_versions", $versionFields, $versionIndexes);
         } else {
             DB::dontRequireTable("{$table}_versions");
             foreach ($this->stages as $stage) {
                 if ($stage != $this->defaultStage) {
                     DB::dontrequireTable("{$table}_{$stage}");
                 }
             }
         }
     }
 }
 /**
  * @param $objects
  * @param $class
  * @param $table
  * @param bool $setID
  * @param bool $update
  * @throws Exception
  */
 private function writeClassTable($objects, $class, $table, $setID = false, $update = false)
 {
     $fields = DataObject::database_fields($class);
     $singleton = singleton($class);
     $fields = array_filter(array_keys($fields), function ($field) use($singleton) {
         return $singleton->hasOwnTableDatabaseField($field);
     });
     if ($setID || $update) {
         array_unshift($fields, 'ID');
     }
     $typeLookup = array('ID' => 'i');
     foreach ($fields as $field) {
         $dbObject = $singleton->dbObject($field);
         if ($dbObject instanceof Boolean || $dbObject instanceof Int) {
             $typeLookup[$field] = 'i';
         } else {
             if ($dbObject instanceof Float || $dbObject instanceof Decimal || $dbObject instanceof Money) {
                 $typeLookup[$field] = 'd';
             } else {
                 $typeLookup[$field] = 's';
             }
         }
     }
     $inserts = array();
     $insert = '(' . implode(',', array_fill(0, count($fields), '?')) . ')';
     $types = '';
     $params = array();
     foreach ($objects as $obj) {
         $record = $this->dataObjectRecordProperty->getValue($obj);
         foreach ($fields as $field) {
             $type = $typeLookup[$field];
             $types .= $type;
             $value = isset($record[$field]) ? $record[$field] : $obj->getField($field);
             if (is_bool($value)) {
                 $value = (int) $value;
             }
             if ($type != 's' && !$value) {
                 $value = 0;
             }
             $params[] = $value;
         }
         $inserts[] = $insert;
     }
     array_unshift($params, $types);
     $columns = implode(', ', array_map(function ($name) {
         return "`{$name}`";
     }, $fields));
     $inserts = implode(',', $inserts);
     $sql = "INSERT INTO `{$table}` ({$columns}) VALUES {$inserts}";
     if ($update) {
         $mappings = array();
         foreach ($fields as $field) {
             if ($field !== 'ID') {
                 $mappings[] = "`{$field}` = VALUES(`{$field}`)";
             }
         }
         $mappings = implode(',', $mappings);
         $sql .= " ON DUPLICATE KEY UPDATE {$mappings}";
     }
     $this->executeQuery($sql, $params);
 }
 /**
  *	Display CMS JSON/XML output visibility configuration.
  */
 public function getCMSFields()
 {
     $fields = parent::getCMSFields();
     // Hide the data object name and output visibility associated with this configuration.
     $fields->removeByName('IsFor');
     $fields->removeByName('APIwesomeVisibility');
     // Grab a single data object.
     Requirements::css(APIWESOME_PATH . '/css/apiwesome.css');
     if (DataObject::get_one($this->IsFor)) {
         // Grab the appropriate attributes for this data object.
         $class = is_subclass_of($this->IsFor, 'SiteTree') ? 'SiteTree' : (is_subclass_of($this->IsFor, 'File') ? 'File' : $this->IsFor);
         $columns = array();
         foreach (ClassInfo::subclassesFor($class) as $subclass) {
             // Prepend the table names.
             $subclassColumns = array();
             foreach (DataObject::database_fields($subclass) as $column => $type) {
                 $subclassColumns["{$subclass}.{$column}"] = $type;
             }
             $columns = array_merge($columns, $subclassColumns);
         }
         array_shift($columns);
         $visibility = $this->APIwesomeVisibility ? explode(',', $this->APIwesomeVisibility) : null;
         // Display the check box fields for JSON/XML output visibility.
         $configuration = FieldGroup::create('Visibility')->addExtraClass('visibility');
         $iteration = 0;
         foreach ($columns as $name => $type) {
             // Print the attribute name, including any relationships.
             $split = explode('.', $name);
             $printName = substr($name, strlen($name) - 2) === 'ID' && count($split) === 2 && ClassInfo::exists($split[0]) && Singleton($split[0])->hasMethod(substr($split[1], 0, -2)) ? substr($name, 0, -2) : $name;
             $printName = ltrim(preg_replace(array('/([A-Z][a-z]+)/', '/([A-Z]{2,})/', '/([_.0-9]+)/'), ' $0', $printName));
             // Set an already existing attribute visibility.
             $configuration->push(CheckboxField::create(str_replace('.', '-', "{$name}.APIwesomeVisibility"), "Display <strong>{$printName}</strong>?", count($visibility) === count($columns) && isset($visibility[$iteration]) ? $visibility[$iteration] : 0));
             $iteration++;
         }
         $fields->addFieldToTab('Root.Main', $configuration);
     } else {
         // Display a notice that data objects should first be created.
         $fields->removeByName('CallbackFunction');
         $name = $this->getTitle();
         $fields->addFieldToTab('Root.Main', LiteralField::create('ConfigurationNotice', "<p class='apiwesome notice'><strong>No {$name}s Found</strong></p>"));
     }
     $this->extend('updateDataObjectOutputConfigurationCMSFields', $fields);
     return $fields;
 }
Example #17
0
 /**
  * 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;
     }
     $requiredTables = ClassInfo::dataClassesFor('Member');
     $requiredTables[] = 'Group';
     $requiredTables[] = 'Permission';
     foreach ($requiredTables as $table) {
         // if any of the tables aren't created in the database
         if (!ClassInfo::hasTable($table)) {
             return false;
         }
         // if any of the tables don't have all fields mapped as table columns
         $dbFields = DB::fieldList($table);
         if (!$dbFields) {
             return false;
         }
         $objFields = DataObject::database_fields($table);
         $missingFields = array_diff_key($objFields, $dbFields);
         if ($missingFields) {
             return false;
         }
     }
     return true;
 }
 /**
  * Generates a ($table)_version DB manipulation and injects it into the current $manipulation
  *
  * @param array $manipulation Source manipulation data
  * @param string $table Name of table
  * @param int $recordID ID of record to version
  */
 protected function augmentWriteVersioned(&$manipulation, $table, $recordID)
 {
     $baseDataClass = ClassInfo::baseDataClass($table);
     // Set up a new entry in (table)_versions
     $newManipulation = array("command" => "insert", "fields" => isset($manipulation[$table]['fields']) ? $manipulation[$table]['fields'] : null);
     // 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($table);
         if (is_array($fields)) {
             $data = array_intersect_key($data, $fields);
             foreach ($data as $k => $v) {
                 if (!isset($newManipulation['fields'][$k])) {
                     $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 \"{$baseDataClass}_versions\" WHERE \"RecordID\" = ?", array($recordID))->value();
     }
     $nextVersion = $nextVersion ?: 1;
     // Add the version number to this data
     $manipulation[$table]['fields']['Version'] = $nextVersion;
     $newManipulation['fields']['Version'] = $nextVersion;
     // Write AuthorID for baseclass
     if ($table === $baseDataClass) {
         $userID = Member::currentUser() ? Member::currentUser()->ID : 0;
         $newManipulation['fields']['AuthorID'] = $userID;
     }
     $manipulation["{$table}_versions"] = $newManipulation;
 }
Example #19
0
	/**
	 * Update the SELECT clause of the query with the columns from the given table
	 */
	protected function selectAllFromTable(SQLQuery &$query, $tableClass) {
    	// Add SQL for multi-value fields
    	$databaseFields = DataObject::database_fields($tableClass);
    	$compositeFields = DataObject::composite_fields($tableClass, false);
    	if($databaseFields) foreach($databaseFields as $k => $v) {
    		if(!isset($compositeFields[$k])) {
    			// Update $collidingFields if necessary
    			if(isset($query->select[$k])) {
    				if(!isset($this->collidingFields[$k])) $this->collidingFields[$k] = array($query->select[$k]);
    				$this->collidingFields[$k][] = "\"$tableClass\".\"$k\"";
				
    			} else {
    				$query->select[$k] = "\"$tableClass\".\"$k\"";
    			}
    		}
    	}
    	if($compositeFields) foreach($compositeFields as $k => $v) {
			if($v) {
			    $dbO = Object::create_from_string($v, $k);
    		    $dbO->addToQuery($query);
		    }
    	}
	}
 /**
  * Update the SELECT clause of the query with the columns from the given table
  */
 protected function selectColumnsFromTable(SQLQuery &$query, $tableClass, $columns = null)
 {
     // Add SQL for multi-value fields
     $databaseFields = DataObject::database_fields($tableClass);
     $compositeFields = DataObject::composite_fields($tableClass, false);
     if ($databaseFields) {
         foreach ($databaseFields as $k => $v) {
             if ((is_null($columns) || in_array($k, $columns)) && !isset($compositeFields[$k])) {
                 // Update $collidingFields if necessary
                 if ($expressionForField = $query->expressionForField($k)) {
                     if (!isset($this->collidingFields[$k])) {
                         $this->collidingFields[$k] = array($expressionForField);
                     }
                     $this->collidingFields[$k][] = "\"{$tableClass}\".\"{$k}\"";
                 } else {
                     $query->selectField("\"{$tableClass}\".\"{$k}\"", $k);
                 }
             }
         }
     }
     if ($compositeFields) {
         foreach ($compositeFields as $k => $v) {
             if ((is_null($columns) || in_array($k, $columns)) && $v) {
                 $dbO = Object::create_from_string($v, $k);
                 $dbO->addToQuery($query);
             }
         }
     }
 }
 /**
  * Generate export fields for CSV.
  *
  * @param GridField $gridField
  * @return array
  */
 public function generateExportFileData($gridField)
 {
     $separator = $this->csvSeparator;
     $singl = singleton($gridField->getModelClass());
     if ($singl->hasMethod('exportedFields')) {
         $fallbackColumns = $singl->exportedFields();
     } else {
         $fields = array_keys(DataObject::database_fields($gridField->getModelClass()));
         $fallbackColumns = array_combine($fields, $fields);
     }
     $csvColumns = $this->exportColumns ? $this->exportColumns : $fallbackColumns;
     $fileData = '';
     $columnData = array();
     $fieldItems = new ArrayList();
     if ($this->csvHasHeader) {
         $headers = array();
         // determine the CSV headers. If a field is callable (e.g. anonymous function) then use the
         // source name as the header instead
         foreach ($csvColumns as $columnSource => $columnHeader) {
             $headers[] = !is_string($columnHeader) && is_callable($columnHeader) ? utf8_decode($columnSource) : utf8_decode($columnHeader);
         }
         $fileData .= "\"" . implode("\"{$separator}\"", array_values($headers)) . "\"";
         $fileData .= "\n";
     }
     $items = $gridField->getList();
     $count = $items->count();
     $fastMode = false;
     $veryFastMode = true;
     // If you export too much, you need some boost!
     if ($count > 1500) {
         $fastMode = true;
     }
     if ($count > 7500) {
         $veryFastMode = true;
     }
     foreach ($items as $item) {
         if ($fastMode || !$item->hasMethod('canView') || $item->canView()) {
             $columnData = array();
             foreach ($csvColumns as $columnSource => $columnHeader) {
                 if (!$veryFastMode && !is_string($columnHeader) && is_callable($columnHeader)) {
                     if ($item->hasMethod($columnSource)) {
                         $relObj = $item->{$columnSource}();
                     } else {
                         $relObj = $item->relObject($columnSource);
                     }
                     $value = $columnHeader($relObj);
                 } else {
                     if ($veryFastMode) {
                         $value = $item->{$columnSource};
                     } else {
                         $value = $gridField->getDataFieldValue($item, $columnSource);
                         if (!$value) {
                             $value = $gridField->getDataFieldValue($item, $columnHeader);
                         }
                     }
                 }
                 $value = str_replace(array("\r", "\n"), "\n", $value);
                 $columnData[] = '"' . str_replace('"', '""', utf8_decode($value)) . '"';
             }
             $fileData .= implode($separator, $columnData);
             $fileData .= "\n";
         }
         if ($item->hasMethod('destroy')) {
             $item->destroy();
         }
     }
     return $fileData;
 }
 /**
  * Add all database-backed text fields as fulltext searchable fields.
  *
  * For every class included in the index, examines those classes and all subclasses looking for "Text" database
  * fields (Varchar, Text, HTMLText, etc) and adds them all as fulltext searchable fields.
  */
 public function addAllFulltextFields($includeSubclasses = true)
 {
     foreach ($this->getClasses() as $class => $options) {
         foreach (SearchIntrospection::hierarchy($class, $includeSubclasses, true) as $dataclass) {
             $fields = DataObject::database_fields($dataclass);
             foreach ($fields as $field => $type) {
                 if (preg_match('/^(\\w+)\\(/', $type, $match)) {
                     $type = $match[1];
                 }
                 if (is_subclass_of($type, 'StringField')) {
                     $this->addFulltextField($field);
                 }
             }
         }
     }
 }
 /**
  *	Determine the search engine specific selectable fields, primarily for sorting.
  *
  *	@return array(string, string)
  */
 public function getSelectableFields()
 {
     // Instantiate some default selectable fields, just in case the search engine does not provide any.
     $selectable = array('LastEdited' => 'Last Edited', 'ID' => 'Created', 'ClassName' => 'Type');
     // Determine the search engine that has been selected.
     if ($this->SearchEngine !== 'Full-Text' && ClassInfo::exists($this->SearchEngine)) {
         // Determine the search engine specific selectable fields.
         foreach ($this->extension_instances as $instance) {
             if (get_class($instance) === $this->SearchEngine) {
                 $instance->setOwner($this);
                 $fields = method_exists($instance, 'getSelectableFields') ? $instance->getSelectableFields() : array();
                 return $fields + $selectable;
             }
         }
     } else {
         if ($this->SearchEngine === 'Full-Text' && is_array($classes = Config::inst()->get('FulltextSearchable', 'searchable_classes')) && count($classes) > 0) {
             // Determine the full-text specific selectable fields.
             $selectable = array('Relevance' => 'Relevance') + $selectable;
             foreach ($classes as $class) {
                 $fields = DataObject::database_fields($class);
                 // Determine the most appropriate fields, primarily for sorting.
                 if (isset($fields['Title'])) {
                     $selectable['Title'] = 'Title';
                 }
                 if (isset($fields['MenuTitle'])) {
                     $selectable['MenuTitle'] = 'Navigation Title';
                 }
                 if (isset($fields['Sort'])) {
                     $selectable['Sort'] = 'Display Order';
                 }
                 // This is specific to file searching.
                 if (isset($fields['Name'])) {
                     $selectable['Name'] = 'File Name';
                 }
             }
         }
     }
     // Allow extension customisation, so custom fields may be selectable.
     $this->extend('updateExtensibleSearchPageSelectableFields', $selectable);
     return $selectable;
 }
 /**
  * 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');
     $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');
     $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');
     $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');
     $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
  */
 function testFieldInheritance()
 {
     $teamInstance = $this->objFromFixture('DataObjectTest_Team', 'team1');
     $subteamInstance = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
     $this->assertEquals(array_keys($teamInstance->inheritedDatabaseFields()), array('Title', 'DatabaseField', 'DecoratedDatabaseField', 'CaptainID', 'HasOneRelationshipID', 'DecoratedHasOneRelationshipID'), 'inheritedDatabaseFields() contains all fields defined on instance, including base fields, decorated fields and foreign keys');
     $this->assertEquals(array_keys(DataObject::database_fields('DataObjectTest_Team')), array('ClassName', 'Created', 'LastEdited', 'Title', 'DatabaseField', 'DecoratedDatabaseField', 'CaptainID', 'HasOneRelationshipID', 'DecoratedHasOneRelationshipID'), 'databaseFields() contains only fields defined on instance, including base fields, decorated fields and foreign keys');
     $this->assertEquals(array_keys($subteamInstance->inheritedDatabaseFields()), array('SubclassDatabaseField', 'Title', 'DatabaseField', 'DecoratedDatabaseField', 'CaptainID', 'HasOneRelationshipID', 'DecoratedHasOneRelationshipID'), 'inheritedDatabaseFields() on subclass contains all fields defined on instance, including base fields, decorated fields and foreign keys');
     $this->assertEquals(array_keys(DataObject::database_fields('DataObjectTest_SubTeam')), array('SubclassDatabaseField'), 'databaseFields() on subclass contains only fields defined on instance');
 }
 protected function obsoletefields($deleteSafeOnes = false, $fixBrokenDataObject = false, $deleteAll = false)
 {
     increase_time_limit_to(600);
     $dataClasses = ClassInfo::subclassesFor('DataObject');
     $notCheckedArray = array();
     $canBeSafelyDeleted = array();
     //remove dataobject
     array_shift($dataClasses);
     $rows = DB::query("SHOW TABLES;");
     $actualTables = array();
     if ($rows) {
         foreach ($rows as $key => $item) {
             foreach ($item as $table) {
                 $actualTables[$table] = $table;
             }
         }
     }
     echo "<h1>Report of fields that may not be required.</h1>";
     echo "<p>NOTE: it may contain fields that are actually required (e.g. versioning or many-many relationships) and it may also leave out some obsolete fields.  Use as a guide only.</p>";
     foreach ($dataClasses as $dataClass) {
         // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness
         if (class_exists($dataClass)) {
             $dataObject = $dataClass::create();
             if (!$dataObject instanceof TestOnly) {
                 $requiredFields = $this->swapArray(DataObject::database_fields($dataObject->ClassName));
                 if (count($requiredFields)) {
                     foreach ($requiredFields as $field) {
                         if (!$dataObject->hasOwnTableDatabaseField($field)) {
                             DB::alteration_message("  **** {$dataClass}.{$field} DOES NOT EXIST BUT IT SHOULD BE THERE!", "deleted");
                         }
                     }
                     $actualFields = $this->swapArray(DB::fieldList($dataClass));
                     if ($actualFields) {
                         foreach ($actualFields as $actualField) {
                             if ($deleteAll) {
                                 $link = " !!!!!!!!!!! DELETED !!!!!!!!!";
                             } else {
                                 $warning = Config::inst()->get("DataIntegrityTest", "warning");
                                 $link = "<a href=\"" . Director::absoluteBaseURL() . "dev/tasks/DataIntegrityTest/?do=deleteonefield/" . $dataClass . "/" . $actualField . "/\" onclick=\"return confirm('" . $warning . "');\">delete field</a>";
                             }
                             if (!in_array($actualField, array("ID", "Version"))) {
                                 if (!in_array($actualField, $requiredFields)) {
                                     $distinctCount = DB::query("SELECT COUNT(DISTINCT \"{$actualField}\") FROM \"{$dataClass}\" WHERE \"{$actualField}\" IS NOT NULL AND \"{$actualField}\" <> '' AND \"{$actualField}\" <> '0';")->value();
                                     DB::alteration_message("<br /><br />\n\n{$dataClass}.{$actualField} {$link} - unique entries: {$distinctCount}", "deleted");
                                     if ($distinctCount) {
                                         $rows = DB::query("\r\n\t\t\t\t\t\t\t\t\t\t\t\tSELECT \"{$actualField}\" as N, COUNT(\"{$actualField}\") as C\r\n\t\t\t\t\t\t\t\t\t\t\t\tFROM \"{$dataClass}\"\r\n\t\t\t\t\t\t\t\t\t\t\t\tGROUP BY \"{$actualField}\"\r\n\t\t\t\t\t\t\t\t\t\t\t\tORDER BY C DESC\r\n\t\t\t\t\t\t\t\t\t\t\t\tLIMIT 7");
                                         if ($rows) {
                                             foreach ($rows as $row) {
                                                 DB::alteration_message(" &nbsp; &nbsp; &nbsp; " . $row["C"] . ": " . $row["N"]);
                                             }
                                         }
                                     } else {
                                         if (!isset($canBeSafelyDeleted[$dataClass])) {
                                             $canBeSafelyDeleted[$dataClass] = array();
                                         }
                                         $canBeSafelyDeleted[$dataClass][$actualField] = "{$dataClass}.{$actualField}";
                                     }
                                     if ($deleteAll || $deleteSafeOnes && $distinctCount == 0) {
                                         $this->deleteField($dataClass, $actualField);
                                     }
                                 }
                             }
                             if ($actualField == "Version" && !in_array($actualField, $requiredFields)) {
                                 $versioningPresent = $dataObject->hasVersioning();
                                 if (!$versioningPresent) {
                                     DB::alteration_message("{$dataClass}.{$actualField} {$link}", "deleted");
                                     if ($deleteAll) {
                                         $this->deleteField($dataClass, $actualField);
                                     }
                                 }
                             }
                         }
                     }
                     $rawCount = DB::query("SELECT COUNT(\"ID\") FROM \"{$dataClass}\"")->value();
                     Versioned::set_reading_mode("Stage.Stage");
                     $realCount = 0;
                     $allSubClasses = array_unique(array($dataClass) + ClassInfo::subclassesFor($dataClass));
                     $objects = $dataClass::get()->filter(array("ClassName" => $allSubClasses));
                     if ($objects->count()) {
                         $realCount = $objects->count();
                     }
                     if ($rawCount != $realCount) {
                         echo "<hr />";
                         $sign = " > ";
                         if ($rawCount < $realCount) {
                             $sign = " < ";
                         }
                         DB::alteration_message("The DB Table Row Count does not seem to match the DataObject Count for <strong>{$dataClass} ({$rawCount} {$sign} {$realCount})</strong>.  This could indicate an error as generally these numbers should match.", "deleted");
                         if ($fixBrokenDataObject) {
                             $objects = $dataClass::get()->where("LinkedTable.ID IS NULL")->leftJoin($dataClass, "{$dataClass}.ID = LinkedTable.ID", "LinkedTable");
                             if ($objects->count() > 500) {
                                 DB::alteration_message("It is recommended that you manually fix the difference in real vs object count in {$dataClass}. There are more than 500 records so it would take too long to do it now.", "deleted");
                             } else {
                                 DB::alteration_message("Now trying to recreate missing items... COUNT = " . $objects->count(), "created");
                                 foreach ($objects as $object) {
                                     if (DB::query("SELECT COUNT(\"ID\") FROM \"{$dataClass}\" WHERE \"ID\" = " . $object->ID . ";")->value() != 1) {
                                         Config::inst()->update('DataObject', 'validation_enabled', false);
                                         $object->write(true, false, true, false);
                                         Config::inst()->update('DataObject', 'validation_enabled', true);
                                     }
                                 }
                                 $objectCount = $dataClass::get()->count();
                                 DB::alteration_message("Consider deleting superfluous records from table {$dataClass} .... COUNT =" . ($rawCount - $objectCount));
                                 $ancestors = ClassInfo::ancestry($dataClass, true);
                                 if ($ancestors && is_array($ancestors) && count($ancestors)) {
                                     foreach ($ancestors as $ancestor) {
                                         if ($ancestor != $dataClass) {
                                             echo "DELETE `{$dataClass}`.* FROM `{$dataClass}` LEFT JOIN `{$ancestor}` ON `{$dataClass}`.`ID` = `{$ancestor}`.`ID` WHERE `{$ancestor}`.`ID` IS NULL;";
                                             DB::query("DELETE `{$dataClass}`.* FROM `{$dataClass}` LEFT JOIN `{$ancestor}` ON `{$dataClass}`.`ID` = `{$ancestor}`.`ID` WHERE `{$ancestor}`.`ID` IS NULL;");
                                         }
                                     }
                                 }
                             }
                         }
                         echo "<hr />";
                     }
                     unset($actualTables[$dataClass]);
                 } else {
                     $db = DB::getConn();
                     if ($db->hasTable($dataClass)) {
                         DB::alteration_message("  **** The {$dataClass} table exists, but according to the data-scheme it should not be there ", "deleted");
                     } else {
                         $notCheckedArray[] = $dataClass;
                     }
                 }
             }
         }
     }
     if (count($canBeSafelyDeleted)) {
         DB::alteration_message("<h2>Can be safely deleted: </h2>");
         foreach ($canBeSafelyDeleted as $table => $fields) {
             DB::alteration_message($table . ": " . implode(", ", $fields));
         }
     }
     if (count($notCheckedArray)) {
         echo "<h3>Did not check the following classes as no fields appear to be required and hence there is no database table.</h3>";
         foreach ($notCheckedArray as $table) {
             if (DB::query("SHOW TABLES LIKE '" . $table . "'")->value()) {
                 DB::alteration_message($table . " - NOTE: a table exists for this Class, this is an unexpected result", "deleted");
             } else {
                 DB::alteration_message($table, "created");
             }
         }
     }
     if (count($actualTables)) {
         echo "<h3>Other Tables in Database not directly linked to a Silverstripe DataObject:</h3>";
         foreach ($actualTables as $table) {
             $remove = true;
             if (class_exists($table)) {
                 $classExistsMessage = " a PHP class with this name exists.";
                 $obj = singleton($table);
                 //not sure why we have this.
                 if ($obj instanceof DataExtension) {
                     $remove = false;
                 } elseif (class_exists("Versioned") && $obj->hasExtension("Versioned")) {
                     $remove = false;
                 }
             } else {
                 $classExistsMessage = " NO PHP class with this name exists.";
                 if (substr($table, -5) == "_Live") {
                     $remove = false;
                 }
                 if (substr($table, -9) == "_versions") {
                     $remove = false;
                 }
                 //many 2 many tables...
                 if (strpos($table, "_")) {
                     $class = explode("_", $table);
                     $manyManyClass = substr($table, 0, strrpos($table, '_'));
                     $manyManyExtension = substr($table, strrpos($table, '_') + 1 - strlen($table));
                     if (class_exists($manyManyClass)) {
                         $manyManys = Config::inst()->get($manyManyClass, "many_many");
                         if (isset($manyManys[$manyManyExtension])) {
                             $remove = false;
                         }
                     }
                 }
             }
             if ($remove) {
                 if (substr($table, 0, strlen("_obsolete_")) != "_obsolete_") {
                     $rowCount = DB::query("SELECT COUNT(*) FROM {$table}")->value();
                     DB::alteration_message($table . ", rows " . $rowCount);
                     $obsoleteTableName = "_obsolete_" . $table;
                     if (!$this->tableExists($obsoleteTableName)) {
                         DB::alteration_message("We recommend deleting {$table} or making it obsolete by renaming it to " . $obsoleteTableName, "deleted");
                         if ($deleteAll) {
                             DB::getConn()->renameTable($table, $obsoleteTableName);
                         } else {
                             DB::alteration_message($table . " - " . $classExistsMessage . " It can be moved to _obsolete_" . $table . ".", "created");
                         }
                     } else {
                         DB::alteration_message("I'd recommend to move <strong>{$table}</strong> to <strong>" . $obsoleteTableName . "</strong>, but that table already exists", "deleted");
                     }
                 }
             }
         }
     }
     echo "<a href=\"" . Director::absoluteURL("/dev/tasks/DataIntegrityTest/") . "\">back to main menu.</a>";
 }