diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapper.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapper.php index a941523f4905a5fadeb81bbcaa1fc4cc79ea0c8a..bb5d87f320ddafd14d3a5d30d317c5a11508ee77 100644 --- a/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapper.php +++ b/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapper.php @@ -16,7 +16,9 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper; use Psr\EventDispatcher\EventDispatcherInterface; +use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Database\Query\QueryHelper; +use TYPO3\CMS\Core\Database\RelationHandler; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface; use TYPO3\CMS\Extbase\Event\Persistence\AfterObjectThawedEvent; @@ -443,13 +445,49 @@ class DataMapper */ protected function getConstraint(QueryInterface $query, DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $relationTableMatchFields = []) { - $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName); - if ($columnMap->getParentKeyFieldName() !== null) { - $constraint = $query->equals($columnMap->getParentKeyFieldName(), $parentObject); + $dataMap = $this->getDataMap(get_class($parentObject)); + $columnMap = $dataMap->getColumnMap($propertyName); + $workspaceId = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'id'); + if ($columnMap && $workspaceId > 0) { + $resolvedRelationIds = $this->resolveRelationValuesOfField($dataMap, $columnMap, $parentObject, $fieldValue, $workspaceId); + } else { + $resolvedRelationIds = []; + } + // Work with the UIDs directly in a workspace + if (!empty($resolvedRelationIds)) { + if ($query->getSource() instanceof Persistence\Generic\Qom\JoinInterface) { + $constraint = $query->in($query->getSource()->getJoinCondition()->getProperty1Name(), $resolvedRelationIds); + // When querying MM relations directly, Typo3DbQueryParser uses enableFields and thus, filters + // out versioned records by default. However, we directly query versioned UIDs here, so we want + // to include the versioned records explicitly. + if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) { + $query->getQuerySettings()->setEnableFieldsToBeIgnored(['pid']); + $query->getQuerySettings()->setIgnoreEnableFields(true); + } + } else { + $constraint = $query->in('uid', $resolvedRelationIds); + } + if ($columnMap->getParentTableFieldName() !== null) { + $constraint = $query->logicalAnd( + $constraint, + $query->equals($columnMap->getParentTableFieldName(), $dataMap->getTableName()) + ); + } + } elseif ($columnMap->getParentKeyFieldName() !== null) { + $value = $parentObject; + // If this a MM relation, and MM relations do not know about workspaces, the MM relations always point to the + // versioned record, so this must be taken into account here and the versioned record's UID must be used. + if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) { + // The versioned UID is used ideally the version ID of a translated record, so this takes precedence over the localized UID + if ($value->_hasProperty('_versionedUid') && $value->_getProperty('_versionedUid') > 0 && $value->_getProperty('_versionedUid') !== $value->getUid()) { + $value = (int)$value->_getProperty('_versionedUid'); + } + } + $constraint = $query->equals($columnMap->getParentKeyFieldName(), $value); 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 { @@ -463,6 +501,58 @@ class DataMapper return $constraint; } + /** + * This resolves relations via RelationHandler and returns their UIDs respectively, and works for MM/ForeignField/CSV in IRRE + Select + Group. + * + * Note: This only happens for resolving properties for models. When limiting a parentQuery, the Typo3DbQueryParser is taking care of it. + * + * By using the RelationHandler, the localized, deleted and moved records turn out to be properly resolved + * without having to build intermediate queries. + * + * This is currently only used in workspaces' context, as it is 1 additional DB query needed. + * + * @param DataMap $dataMap + * @param ColumnMap $columnMap + * @param DomainObjectInterface $parentObject + * @param string $fieldValue + * @param int $workspaceId + * @return array|false|mixed + */ + protected function resolveRelationValuesOfField(DataMap $dataMap, ColumnMap $columnMap, DomainObjectInterface $parentObject, $fieldValue, int $workspaceId) + { + $parentId = $parentObject->getUid(); + // versionedUid in a multi-language setup is the overlaid versioned AND translated ID + if ($parentObject->_hasProperty('_versionedUid') && $parentObject->_getProperty('_versionedUid') > 0 && $parentObject->_getProperty('_versionedUid') !== $parentId) { + $parentId = $parentObject->_getProperty('_versionedUid'); + } elseif ($parentObject->_hasProperty('_languageUid') && $parentObject->_getProperty('_languageUid') > 0) { + $parentId = $parentObject->_getProperty('_localizedUid'); + } + $relationHandler = GeneralUtility::makeInstance(RelationHandler::class); + $relationHandler->setWorkspaceId($workspaceId); + $relationHandler->setUseLiveReferenceIds(true); + $relationHandler->setUseLiveParentIds(true); + $tableName = $dataMap->getTableName(); + $fieldName = $columnMap->getColumnName(); + $fieldConfiguration = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'] ?? null; + if (!is_array($fieldConfiguration)) { + return []; + } + $relationHandler->start( + $fieldValue, + $fieldConfiguration['allowed'] ?? $fieldConfiguration['foreign_table'] ?? '', + $fieldConfiguration['MM'] ?? '', + $parentId, + $tableName, + $fieldConfiguration + ); + $relationHandler->processDeletePlaceholder(); + $relatedUids = []; + if (!empty($relationHandler->tableArray)) { + $relatedUids = reset($relationHandler->tableArray); + } + return $relatedUids; + } + /** * Builds and returns the source to build a join for a m:n relation. * diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/WorkspaceTest.php b/typo3/sysext/extbase/Tests/Functional/Persistence/WorkspaceTest.php index 81f6e207ad4cfb6e55f23974335c5da074f019c0..aa9651e7342036d382ffa03f179dc12e06c2a7cd 100644 --- a/typo3/sysext/extbase/Tests/Functional/Persistence/WorkspaceTest.php +++ b/typo3/sysext/extbase/Tests/Functional/Persistence/WorkspaceTest.php @@ -193,8 +193,7 @@ class WorkspaceTest extends FunctionalTestCase /** @var Blog $blog */ $blog = $query->execute()->getFirst(); self::assertEquals('WorkspaceOverlay Blog1', $blog->getTitle()); - // @todo: this is wrong and will be fixed with https://review.typo3.org/c/Packages/TYPO3.CMS/+/68913 - self::assertCount(3, $blog->getCategories()); + self::assertCount(2, $blog->getCategories()); } /**