Index: Classes/Persistence/Backend.php =================================================================== --- Classes/Persistence/Backend.php (revision 3926) +++ Classes/Persistence/Backend.php (working copy) @@ -704,13 +704,18 @@ $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(), $columnMap->getChildKeyFieldName() => (int)$object->getUid(), $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0 - ); - $relationTableName = $columnMap->getRelationTableName(); - // FIXME Reenable support for tablenames - // $childTableName = $columnMap->getChildTableName(); - // if (isset($childTableName)) { - // $row['tablenames'] = $childTableName; - // } + ); + + // support for "tablenames" column + if ($columnMap->getRelationTableRelatedTableColumnName() !== NULL) { + if ($columnMap->isMultiColumnRelation() === TRUE) { + $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName(); + } else { + $tableName = $dataMap->getTableName(); + } + $row[$columnMap->getRelationTableRelatedTableColumnName()] = $tableName; + } + if ($columnMap->getRelationTablePageIdColumnName() !== NULL) { $row[$columnMap->getRelationTablePageIdColumnName()] = $this->determineStoragePageIdForNewRecord(); } @@ -722,6 +727,7 @@ } } + $relationTableName = $columnMap->getRelationTableName(); $res = $this->storageBackend->addRow( $relationTableName, $row, @@ -745,10 +751,7 @@ $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid() ); - $relationTableMatchFields = $columnMap->getRelationTableMatchFields(); - if (is_array($relationTableMatchFields) && count($relationTableMatchFields) > 0) { - $relationMatchFields = array_merge($relationTableMatchFields,$relationMatchFields); - } + $this->addDefaultMatchFieldsToRelationMatchFields($columnMap, $relationMatchFields); $res = $this->storageBackend->removeRow( $relationTableName, @@ -769,12 +772,17 @@ $dataMap = $this->dataMapper->getDataMap(get_class($parentObject)); $columnMap = $dataMap->getColumnMap($parentPropertyName); $relationTableName = $columnMap->getRelationTableName(); + + $relationMatchFields = array( + $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(), + $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid(), + ); + + $this->addDefaultMatchFieldsToRelationMatchFields($columnMap, $relationMatchFields); + $res = $this->storageBackend->removeRow( $relationTableName, - array( - $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(), - $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid(), - ), + $relationMatchFields, FALSE); return $res; } @@ -809,11 +817,11 @@ } /** - * Returns a table row to be inserted or updated in the database + * Adds common DB fields like creationDate, storagePids etc. to a database row * - * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The appropriate data map representing a database table - * @param array $properties The properties of the object - * @return array A single row to be inserted in the database + * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The related domain object the fields should be added for + * @param array $row The database row the fields should get added to + * @return void */ protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) { $className = get_class($object); @@ -833,6 +841,25 @@ } /** + * Adds default matchFields to the passed relationRow array which will be used in queries + * + * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map representing the related database column + * @param array $relationRow The relation row the fields should get added to + * @return void + */ + protected function addDefaultMatchFieldsToRelationMatchFields(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, array &$relationMatchFields) { + $relationTableMatchFields = $columnMap->getRelationTableMatchFields(); + if (is_array($relationTableMatchFields) + && count($relationTableMatchFields) > 0) { + $relationMatchFields = array_merge($relationTableMatchFields, $relationMatchFields); + } + // support for "tablenames" column + if ($columnMap->getRelationTableRelatedTableColumnName() !== NULL) { + $relationMatchFields[$columnMap->getRelationTableRelatedTableColumnName()] = $dataMap->getTableName(); + } + } + + /** * Iterate over deleted aggregate root objects and process them * * @return void Index: Classes/Persistence/Mapper/ColumnMap.php =================================================================== --- Classes/Persistence/Mapper/ColumnMap.php (revision 3926) +++ Classes/Persistence/Mapper/ColumnMap.php (working copy) @@ -120,6 +120,20 @@ protected $relationTablePageIdColumnName; /** + * Flag if the current relation is a multiColumn relation + * + * @var boolean + **/ + protected $isMultiColumnRelation; + + /** + * The name of the column of the relation table holding the name of the related table + * + * @var string + **/ + protected $relationTableRelatedTableColumnName; + + /** * An array of field => value pairs to both insert and match against when writing/reading MM relations * * @var array @@ -238,6 +252,26 @@ return $this->relationTablePageIdColumnName; } + public function setRelationTableRelatedTableColumnName($relationTableRelatedTableColumnName) { + $this->relationTableRelatedTableColumnName = $relationTableRelatedTableColumnName; + } + + public function getRelationTableRelatedTableColumnName() { + return $this->relationTableRelatedTableColumnName; + } + + public function setIsMultiColumnRelation($isMultiColumnRelation) { + $this->isMultiColumnRelation = (boolean) $isMultiColumnRelation; + } + + public function getIsMultiColumnRelation() { + return $this->isMultiColumnRelation; + } + + public function isMultiColumnRelation() { + return self::getIsMultiColumnRelation(); + } + public function setRelationTableMatchFields(array $relationTableMatchFields) { $this->relationTableMatchFields = $relationTableMatchFields; } Index: Classes/Persistence/Mapper/DataMapFactory.php =================================================================== --- Classes/Persistence/Mapper/DataMapFactory.php (revision 3926) +++ Classes/Persistence/Mapper/DataMapFactory.php (working copy) @@ -188,7 +188,7 @@ * @return void */ protected function setRelations(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration, $propertyMetaData) { - if (isset($columnConfiguration)) { + if (isset($columnConfiguration) && $this->isSupportedRelationConfiguration($columnConfiguration, $propertyMetaData)) { if (isset($columnConfiguration['MM']) || isset($columnConfiguration['foreign_selector'])) { $columnMap = $this->setManyToManyRelation($columnMap, $columnConfiguration); } elseif (isset($propertyMetaData['elementType'])) { @@ -214,11 +214,15 @@ */ protected function setOneToOneRelation(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration) { $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE); - $columnMap->setChildTableName($columnConfiguration['foreign_table']); + $columnMap->setChildTableName($this->resolveDefaultChildTableName($columnConfiguration)); $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']); - $columnMap->setChildSortbyFieldName($columnConfiguration['foreign_sortby']); - $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']); - $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']); + + if($columnConfiguration['type'] != 'group') { + $columnMap->setChildKeyFieldName($columnConfiguration['foreign_key_field']); + $columnMap->setChildSortbyFieldName($columnConfiguration['foreign_sortby']); + $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']); + $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']); + } return $columnMap; } @@ -232,11 +236,15 @@ */ protected function setOneToManyRelation(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration) { $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY); - $columnMap->setChildTableName($columnConfiguration['foreign_table']); + $columnMap->setChildTableName($this->resolveDefaultChildTableName($columnConfiguration)); $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']); - $columnMap->setChildSortbyFieldName($columnConfiguration['foreign_sortby']); - $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']); - $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']); + + if($columnConfiguration['type'] != 'group') { + $columnMap->setChildKeyFieldName($columnConfiguration['foreign_key_field']); + $columnMap->setChildSortbyFieldName($columnConfiguration['foreign_sortby']); + $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']); + $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']); + } return $columnMap; } @@ -251,25 +259,58 @@ protected function setManyToManyRelation(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration) { $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY); if (isset($columnConfiguration['MM'])) { - $columnMap->setChildTableName($columnConfiguration['foreign_table']); + $foreignTable = $this->resolveDefaultChildTableName($columnConfiguration); + + $columnMap->setChildTableName($foreignTable); $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']); $columnMap->setRelationTableName($columnConfiguration['MM']); - if (is_array($columnConfiguration['MM_match_fields'])) { - $columnMap->setRelationTableMatchFields($columnConfiguration['MM_match_fields']); + + $matchFields = is_array($columnConfiguration['MM_match_fields']) ? $columnConfiguration['MM_match_fields'] : array(); + $insertFields = is_array($columnConfiguration['MM_insert_fields']) ? array_merge($matchFields, $columnConfiguration['MM_insert_fields']) : $matchFields; + if (count($matchFields)) { + $columnMap->setRelationTableMatchFields($matchFields); } - if (is_array($columnConfiguration['MM_insert_fields'])) { - $columnMap->setRelationTableInsertFields($columnConfiguration['MM_insert_fields']); + if (count($insertFields)) { + $columnMap->setRelationTableInsertFields($insertFields); } + $columnMap->setRelationTableWhereStatement($columnConfiguration['MM_table_where']); + + $useRelationTableRelatedTableName = FALSE; + if (!empty($columnConfiguration['MM_opposite_field'])) { $columnMap->setParentKeyFieldName('uid_foreign'); $columnMap->setChildKeyFieldName('uid_local'); $columnMap->setChildSortByFieldName('sorting_foreign'); + + $foreignTable = $this->resolveDefaultChildTableName($columnConfiguration); + $foreignTableDefinitions = $this->getColumnsDefinition($foreignTable); + $oppositeFieldConfiguration = $foreignTableDefinitions[$columnConfiguration['MM_opposite_field']]; + + // don't set the columnMap to multiColumnRelation here, because the relation itself is not treated as such + // all we need is to define the relationTableRelatedTableColumnName here in order to build the correct MM relations + if ($this->isMultiColumnRelation($oppositeFieldConfiguration)) { + $useRelationTableRelatedTableName = TRUE; + } } else { $columnMap->setParentKeyFieldName('uid_local'); $columnMap->setChildKeyFieldName('uid_foreign'); $columnMap->setChildSortByFieldName('sorting'); + $columnMap->setIsMultiColumnRelation( $this->isMultiColumnRelation($columnConfiguration) ); } + + // automatically set required 'tablenames' fields on the relation table, just like TYPO3 core does on certain MM relations + // @see function writeMM of class class.t3lib_loaddbgroup.php + if (($columnConfiguration['type'] === 'group' && $columnConfiguration['prepend_tname']) + || $columnConfiguration['neg_foreign_table']) { + if (!isset($matchFields['tablenames'])) { + $useRelationTableRelatedTableName = TRUE; + } + } + if ($useRelationTableRelatedTableName === TRUE + || $columnMap->isMultiColumnRelation() === TRUE) { + $columnMap->setRelationTableRelatedTableColumnName('tablenames'); + } } elseif (isset($columnConfiguration['foreign_selector'])) { $columns = $this->getColumnsDefinition($columnConfiguration['foreign_table']); $childKeyFieldName = $columnConfiguration['foreign_selector']; @@ -287,4 +328,59 @@ return $columnMap; } + /** + * Checks if the given columnConfiguration belongs to a multiTable relation + * + * @param array $columnConfiguration The column configuration from $GLOBALS['TCA'] + * @return boolean + * @see class.t3lib_loaddbgroup.php function readMM and writeMM + */ + private function isMultiColumnRelation($columnConfiguration) { + if ( is_array($columnConfiguration) + && isset($columnConfiguration['allowed']) ) { + $allowedTables = t3lib_div::trimExplode(',', $columnConfiguration['allowed']); + if (count($allowedTables) > 1) { + throw new Tx_Extbase_Persistence_Exception_UnsupportedRelation('Extbase currently doesn\'t support multiTable relations. You current TCA configuration tries to create a multiTable relation to these tables: ' . implode(', ', $tableNameArray), 1295571070); + #return TRUE; + } + } + return FALSE; + } + + /** + * Is resolving the correct childTableName from the given columnConfiguration. + * The main purpose of this method is to return the default table name that should be used + * to build the database relations in case it's a multitable relation like it's possible + * with TCA type "group". + * + * @param array $columnConfiguration The column configuration from $GLOBALS['TCA'] + * @return string The name of the child table + */ + private function resolveDefaultChildTableName($columnConfiguration) { + $tableName = $columnConfiguration['allowed'] ? $columnConfiguration['allowed'] : $columnConfiguration['foreign_table']; + $tableNameArray = t3lib_div::trimExplode(',', $tableName); + if (count($tableNameArray) > 1) { + $tableName = current($tableNameArray); + } + return $tableName; + } + + /** + * Check is the given configuration matches supported relation configurations + * + * @param array $columnConfiguration The column configuration from $GLOBALS['TCA'] + * @param array $propertyMetaData The property metadata as delivered by the reflection service + * @return boolean + */ + private function isSupportedRelationConfiguration($columnConfiguration, $propertyMetaData) { + if (isset($columnConfiguration['foreign_table'])) { + return TRUE; + } + if ($columnConfiguration['type'] == 'group' + && $columnConfiguration['internal_type'] == 'db' + && isset($columnConfiguration['allowed'])) { + return TRUE; + } + return FALSE; + } } \ No newline at end of file Index: Classes/Persistence/Mapper/DataMapper.php =================================================================== --- Classes/Persistence/Mapper/DataMapper.php (revision 3926) +++ Classes/Persistence/Mapper/DataMapper.php (working copy) @@ -387,13 +387,14 @@ * @return Tx_Extbase_Persistence_QOM_ConstraintInterface $constraint */ protected function getConstraint(Tx_Extbase_Persistence_QueryInterface $query, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $relationTableMatchFields = array()) { - $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName); + $dataMap = $this->getDataMap(get_class($parentObject)); + $columnMap = $dataMap->getColumnMap($propertyName); if ($columnMap->getParentKeyFieldName() !== NULL) { $constraint = $query->equals($columnMap->getParentKeyFieldName(), $parentObject); if($columnMap->getParentTableFieldName() !== NULL) { $constraint = $query->logicalAnd( $constraint, - $query->equals($columnMap->getParentTableFieldName(), $this->getDataMap(get_class($parentObject))->getTableName()) + $query->equals($columnMap->getParentTableFieldName(), $dataMap->getTableName()) ); } } else { @@ -404,6 +405,10 @@ $constraint = $query->logicalAnd($constraint, $query->equals($relationTableMatchFieldName, $relationTableMatchFieldValue)); } } + // support for "tablenames" column + if ($columnMap->getRelationTableRelatedTableColumnName() !== NULL) { + $constraint = $query->logicalAnd($constraint, $query->equals($columnMap->getRelationTableRelatedTableColumnName(), $dataMap->getTableName())); + } return $constraint; } Index: Classes/Persistence/Storage/Typo3DbBackend.php =================================================================== --- Classes/Persistence/Storage/Typo3DbBackend.php (revision 3926) +++ Classes/Persistence/Storage/Typo3DbBackend.php (working copy) @@ -673,6 +673,17 @@ $sql['unions'][$relationTableName] = 'LEFT JOIN ' . $relationTableName . ' ON ' . $tableName . '.uid=' . $relationTableName . '.uid_local'; $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $relationTableName . '.uid_foreign=' . $childTableName . '.uid'; $className = $this->dataMapper->getType($className, $propertyName); + + $relationTableMatchFields = $columnMap->getRelationTableMatchFields(); + if (is_array($relationTableMatchFields) && count($relationTableMatchFields)) { + foreach ($relationTableMatchFields as $matchFieldName => $matchFieldValue) { + $sql['unions'][$relationTableName] .= ' AND ' . $relationTableName . '.' . $matchFieldName . '=' . $this->databaseHandle->fullQuoteStr($matchFieldValue, 'foo'); + } + } + // support for "tablenames" column + if ($columnMap->getRelationTableRelatedTableColumnName() !== NULL) { + $sql['unions'][$relationTableName] .= ' AND ' . $columnMap->getRelationTableRelatedTableColumnName() . '=' . $this->databaseHandle->fullQuoteStr($tableName, 'foo'); + } } else { throw new Tx_Extbase_Persistence_Exception('Could not determine type of relation.', 1252502725); }