/**
  * Adds table identifier to the every column.
  * Columns must have table identifier to prevent duplicate column name error.
  *
  * @return string
  */
 protected function prepareColumns($columns)
 {
     $cols = preg_split('/"?\\s*,\\s*"?/', trim($columns, '(") '));
     $class = ClassInfo::table_for_object_field($this->model, current($cols));
     $cols = array_map(function ($col) use($class) {
         return sprintf('"%s"."%s"', $class, $col);
     }, $cols);
     return implode(',', $cols);
 }
Beispiel #2
0
 /**
  * Assign a sort number when object is written
  * @see DataExtension::onBeforeWrite()
  */
 public function onBeforeWrite()
 {
     if (!$this->owner->exists() || !$this->owner->SortOrder) {
         // get the table in the ancestry that has the SortOrder field
         $table = ClassInfo::table_for_object_field($this->owner->class, 'SortOrder');
         $sql = new SQLQuery('MAX("SortOrder")', $table);
         $val = $sql->execute()->value();
         $this->owner->SortOrder = is_numeric($val) ? $val + 1 : 1;
     }
 }
 public function testTableForObjectField()
 {
     $this->assertEquals('ClassInfoTest_WithRelation', ClassInfo::table_for_object_field('ClassInfoTest_WithRelation', 'RelationID'));
     $this->assertEquals('ClassInfoTest_BaseDataClass', ClassInfo::table_for_object_field('ClassInfoTest_BaseDataClass', 'Title'));
     $this->assertEquals('ClassInfoTest_BaseDataClass', ClassInfo::table_for_object_field('ClassInfoTest_HasFields', 'Title'));
     $this->assertEquals('ClassInfoTest_BaseDataClass', ClassInfo::table_for_object_field('ClassInfoTest_NoFields', 'Title'));
     $this->assertEquals('ClassInfoTest_HasFields', ClassInfo::table_for_object_field('ClassInfoTest_HasFields', 'Description'));
     // existing behaviour fallback to DataObject? Should be null.
     $this->assertEquals('DataObject', ClassInfo::table_for_object_field('ClassInfoTest_BaseClass', 'Nonexist'));
     $this->assertNull(ClassInfo::table_for_object_field('SomeFakeClassHere', 'Title'));
     $this->assertNull(ClassInfo::table_for_object_field('Object', 'Title'));
     $this->assertNull(ClassInfo::table_for_object_field(null, null));
 }
 public function run($request)
 {
     $db = DB::tableList();
     foreach (self::GetVersionedClass() as $class) {
         $table = ClassInfo::table_for_object_field($class, 'ID');
         $vTable = $table . '_versions';
         if (!in_array($vTable, $db)) {
             continue;
         }
         echo "Clear records for class {$class} <br/>";
         // TODO: WE SHOULD CLEAR BY ID TO AVOID INCONSISTENT DB
         // Keep 50 last records
         DB::query("DELETE FROM {$vTable}\n  WHERE id <= (\n    SELECT id\n    FROM (\n      SELECT id\n      FROM {$vTable}\n      ORDER BY id DESC\n      LIMIT 1 OFFSET 50\n    ) selection\n  )");
         $this->vacuumTable($vTable);
     }
 }
 /**
  * Traverse the relationship fields, and add the table
  * mappings to the query object state. This has to be called
  * in any overloaded {@link SearchFilter->apply()} methods manually.
  * 
  * @param String|array $relation The array/dot-syntax relation to follow
  * @return The model class of the related item
  */
 public function applyRelation($relation)
 {
     // NO-OP
     if (!$relation) {
         return $this->dataClass;
     }
     if (is_string($relation)) {
         $relation = explode(".", $relation);
     }
     $modelClass = $this->dataClass;
     foreach ($relation as $rel) {
         $model = singleton($modelClass);
         if ($component = $model->has_one($rel)) {
             if (!$this->query->isJoinedTo($component)) {
                 $foreignKey = $rel;
                 $realModelClass = ClassInfo::table_for_object_field($modelClass, "{$foreignKey}ID");
                 $this->query->addLeftJoin($component, "\"{$component}\".\"ID\" = \"{$realModelClass}\".\"{$foreignKey}ID\"");
                 /**
                  * add join clause to the component's ancestry classes so that the search filter could search on
                  * its ancestor fields.
                  */
                 $ancestry = ClassInfo::ancestry($component, true);
                 if (!empty($ancestry)) {
                     $ancestry = array_reverse($ancestry);
                     foreach ($ancestry as $ancestor) {
                         if ($ancestor != $component) {
                             $this->query->addInnerJoin($ancestor, "\"{$component}\".\"ID\" = \"{$ancestor}\".\"ID\"");
                         }
                     }
                 }
             }
             $modelClass = $component;
         } elseif ($component = $model->has_many($rel)) {
             if (!$this->query->isJoinedTo($component)) {
                 $ancestry = $model->getClassAncestry();
                 $foreignKey = $model->getRemoteJoinField($rel);
                 $this->query->addLeftJoin($component, "\"{$component}\".\"{$foreignKey}\" = \"{$ancestry[0]}\".\"ID\"");
                 /**
                  * add join clause to the component's ancestry classes so that the search filter could search on
                  * its ancestor fields.
                  */
                 $ancestry = ClassInfo::ancestry($component, true);
                 if (!empty($ancestry)) {
                     $ancestry = array_reverse($ancestry);
                     foreach ($ancestry as $ancestor) {
                         if ($ancestor != $component) {
                             $this->query->addInnerJoin($ancestor, "\"{$component}\".\"ID\" = \"{$ancestor}\".\"ID\"");
                         }
                     }
                 }
             }
             $modelClass = $component;
         } elseif ($component = $model->many_many($rel)) {
             list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component;
             $parentBaseClass = ClassInfo::baseDataClass($parentClass);
             $componentBaseClass = ClassInfo::baseDataClass($componentClass);
             $this->query->addInnerJoin($relationTable, "\"{$relationTable}\".\"{$parentField}\" = \"{$parentBaseClass}\".\"ID\"");
             $this->query->addLeftJoin($componentBaseClass, "\"{$relationTable}\".\"{$componentField}\" = \"{$componentBaseClass}\".\"ID\"");
             if (ClassInfo::hasTable($componentClass)) {
                 $this->query->addLeftJoin($componentClass, "\"{$relationTable}\".\"{$componentField}\" = \"{$componentClass}\".\"ID\"");
             }
             $modelClass = $componentClass;
         }
     }
     return $modelClass;
 }
 protected function overrideField($obj, $fieldName, $value, $fixtures = null)
 {
     $table = ClassInfo::table_for_object_field(get_class($obj), $fieldName);
     $value = $this->parseValue($value, $fixtures);
     DB::manipulate(array($table => array("command" => "update", "id" => $obj->ID, "fields" => array($fieldName => $value))));
     $obj->{$fieldName} = $value;
 }
 /**
  * Given a field or relation name, apply it safely to this datalist.
  *
  * Unlike getRelationName, this is immutable and will fallback to the quoted field
  * name if not a relation.
  *
  * @param string $field Name of field or relation to apply
  * @param string &$columnName Quoted column name
  * @param bool $linearOnly Set to true to restrict to linear relations only. Set this
  * if this relation will be used for sorting, and should not include duplicate rows.
  * @return DataList DataList with this relation applied
  */
 public function applyRelation($field, &$columnName = null, $linearOnly = false)
 {
     // If field is invalid, return it without modification
     if (!$this->isValidRelationName($field)) {
         $columnName = $field;
         return $this;
     }
     // Simple fields without relations are mapped directly
     if (strpos($field, '.') === false) {
         $columnName = '"' . $field . '"';
         return $this;
     }
     return $this->alterDataQuery(function (DataQuery $query, DataList $list) use($field, &$columnName, $linearOnly) {
         $relations = explode('.', $field);
         $fieldName = array_pop($relations);
         // Apply
         $relationModelName = $query->applyRelation($field, $linearOnly);
         // Find the db field the relation belongs to
         $className = ClassInfo::table_for_object_field($relationModelName, $fieldName);
         if (empty($className)) {
             $className = $relationModelName;
         }
         $columnName = '"' . $className . '"."' . $fieldName . '"';
     });
 }
 /**
  * Normalizes the field name to table mapping.
  *
  * @return string
  */
 public function getDbName()
 {
     // Special handler for "NULL" relations
     if ($this->name == "NULL") {
         return $this->name;
     }
     $candidateClass = ClassInfo::table_for_object_field($this->model, $this->name);
     if ($candidateClass == 'DataObject') {
         // fallback to the provided name in the event of a joined column
         // name (as the candidate class doesn't check joined records)
         $parts = explode('.', $this->fullName);
         return '"' . implode('"."', $parts) . '"';
     }
     return "\"{$candidateClass}\".\"{$this->name}\"";
 }
 /**
  * Returns the manipulated (sorted) DataList. Field names will simply add an 
  * 'ORDER BY' clause, relation names will add appropriate joins to the
  * {@link DataQuery} first.
  * 
  * @param GridField
  * @param SS_List
  * @return SS_List
  */
 public function getManipulatedData(GridField $gridField, SS_List $dataList)
 {
     if (!$this->checkDataType($dataList)) {
         return $dataList;
     }
     $state = $gridField->State->GridFieldSortableHeader;
     if ($state->SortColumn == "") {
         return $dataList;
     }
     $column = $state->SortColumn;
     // if we have a relation column with dot notation
     if (strpos($column, '.') !== false) {
         $lastAlias = $dataList->dataClass();
         $tmpItem = singleton($lastAlias);
         $parts = explode('.', $state->SortColumn);
         for ($idx = 0; $idx < sizeof($parts); $idx++) {
             $methodName = $parts[$idx];
             // If we're not on the last item, we're looking at a relation
             if ($idx !== sizeof($parts) - 1) {
                 // Traverse to the relational list
                 $tmpItem = $tmpItem->{$methodName}();
                 $joinClass = ClassInfo::table_for_object_field($lastAlias, $methodName . "ID");
                 // if the field isn't in the object tree then it is likely
                 // been aliased. In that event, assume what the user has
                 // provided is the correct value
                 if (!$joinClass) {
                     $joinClass = $lastAlias;
                 }
                 $dataList = $dataList->leftJoin($tmpItem->class, '"' . $methodName . '"."ID" = "' . $joinClass . '"."' . $methodName . 'ID"', $methodName);
                 // Store the last 'alias' name as it'll be used for the next
                 // join, or the 'sort' column
                 $lastAlias = $methodName;
             } else {
                 // Change relation.relation.fieldname to alias.fieldname
                 $column = $lastAlias . '.' . $methodName;
             }
         }
     }
     // We need to manually create our ORDER BY "Foo"."Bar" string for relations,
     // as ->sort() won't do it by itself. Blame PostgreSQL for making this necessary
     $pieces = explode('.', $column);
     $column = '"' . implode('"."', $pieces) . '"';
     return $dataList->sort($column, $state->SortDirection);
 }
 public static function merge($records)
 {
     $all = array();
     $all_but_oldest = array();
     $all_but_latest = array();
     $latest = null;
     $oldest = null;
     foreach ($records as $r) {
         if (!is_object($r)) {
             $r = (object) $r;
         }
         if (!$r instanceof Member) {
             $r = Member::get()->byID($r->ID);
         }
         if (!$latest) {
             $latest = $r;
         } else {
             if (strtotime($r->LastEdited) > strtotime($latest->LastEdited)) {
                 $latest = $r;
             }
         }
         if (!$oldest) {
             $oldest = $r;
         } else {
             if ($r->ID < $oldest->ID) {
                 $oldest = $r->ID;
             }
         }
         $all[] = $r;
     }
     foreach ($all as $a) {
         if ($a->ID == $oldest->ID) {
             continue;
         }
         $all_but_oldest[] = $a;
     }
     foreach ($all as $a) {
         if ($a->ID == $latest->ID) {
             continue;
         }
         $all_but_latest[] = $a;
     }
     if (class_exists('Subsite')) {
         Subsite::$disable_subsite_filter = true;
     }
     Config::inst()->update('DataObject', 'validation_enabled', false);
     // Rewrite all relations so everything is pointing to oldest
     // For some reason, the code in merge fails to do this properly
     $tables = DB::tableList();
     $objects = ClassInfo::subclassesFor('DataObject');
     foreach ($objects as $o) {
         $config = $o::config();
         if ($config->has_one) {
             foreach ($config->has_one as $name => $class) {
                 if ($class == 'Member') {
                     $table = ClassInfo::table_for_object_field($o, $name . 'ID');
                     if ($table && in_array(strtolower($table), $tables)) {
                         foreach ($all_but_oldest as $a) {
                             $sql = "UPDATE {$table} SET " . $name . 'ID = ' . $oldest->ID . ' WHERE ' . $name . 'ID = ' . $a->ID;
                             DB::alteration_message($sql);
                             DB::query($sql);
                         }
                     }
                 }
             }
         }
         if ($config->has_many) {
             foreach ($config->has_many as $name => $class) {
                 if ($class == 'Member') {
                     $table = ClassInfo::table_for_object_field($o, $name . 'ID');
                     if ($table && in_array(strtolower($table), $tables)) {
                         foreach ($all_but_oldest as $a) {
                             $sql = "UPDATE {$table} SET " . $name . 'ID = ' . $oldest->ID . ' WHERE ' . $name . 'ID = ' . $a->ID;
                             DB::alteration_message($sql);
                             DB::query($sql);
                         }
                     }
                 }
             }
         }
         if ($config->many_many) {
             foreach ($config->many_many as $name => $class) {
                 if ($class == 'Member') {
                     $table = ClassInfo::table_for_object_field($o, $name . 'ID');
                     if ($table && in_array(strtolower($table), $tables)) {
                         foreach ($all_but_oldest as $a) {
                             $sql = "UPDATE {$table} SET " . $name . 'ID = ' . $oldest->ID . ' WHERE ' . $name . 'ID = ' . $a->ID;
                             DB::alteration_message($sql);
                             DB::query($sql);
                         }
                     }
                 }
             }
         }
     }
     // Now, we update to oldest record with the latest info
     $orgOldest = $oldest;
     $oldest->merge($latest, 'right', false);
     foreach ($all_but_oldest as $a) {
         $a->delete();
     }
     try {
         $oldest->write();
     } catch (Exception $ex) {
         $orgOldest->write();
     }
 }
 /**
  * Join the given class to this query with the given key
  *
  * @param string $localClass Name of class that has the has_one to the joined class
  * @param string $localField Name of the has_one relationship to joi
  * @param string $foreignClass Class to join
  */
 protected function joinHasOneRelation($localClass, $localField, $foreignClass)
 {
     if (!$foreignClass) {
         throw new InvalidArgumentException("Could not find a has_one relationship {$localField} on {$localClass}");
     }
     if ($foreignClass === 'DataObject') {
         throw new InvalidArgumentException("Could not join polymorphic has_one relationship {$localField} on {$localClass}");
     }
     // Skip if already joined
     if ($this->query->isJoinedTo($foreignClass)) {
         return;
     }
     $realModelClass = ClassInfo::table_for_object_field($localClass, "{$localField}ID");
     $foreignBase = ClassInfo::baseDataClass($foreignClass);
     $this->query->addLeftJoin($foreignBase, "\"{$foreignBase}\".\"ID\" = \"{$realModelClass}\".\"{$localField}ID\"");
     /**
      * add join clause to the component's ancestry classes so that the search filter could search on
      * its ancestor fields.
      */
     $ancestry = ClassInfo::ancestry($foreignClass, true);
     if (!empty($ancestry)) {
         $ancestry = array_reverse($ancestry);
         foreach ($ancestry as $ancestor) {
             if ($ancestor != $foreignBase) {
                 $this->query->addLeftJoin($ancestor, "\"{$foreignBase}\".\"ID\" = \"{$ancestor}\".\"ID\"");
             }
         }
     }
 }
Beispiel #12
0
 /**
  * Normalizes the field name to table mapping.
  *
  * @return string
  */
 public function getDbName()
 {
     // Special handler for "NULL" relations
     if ($this->name == "NULL") {
         return $this->name;
     }
     // Ensure that we're dealing with a DataObject.
     if (!is_subclass_of($this->model, 'DataObject')) {
         throw new InvalidArgumentException("Model supplied to " . get_class($this) . " should be an instance of DataObject.");
     }
     $candidateClass = ClassInfo::table_for_object_field($this->model, $this->name);
     if ($candidateClass == 'DataObject') {
         // fallback to the provided name in the event of a joined column
         // name (as the candidate class doesn't check joined records)
         $parts = explode('.', $this->fullName);
         return '"' . implode('"."', $parts) . '"';
     }
     return sprintf('"%s"."%s"', $candidateClass, $this->name);
 }