diff --git a/Classes/Utility/BackendUtility.php b/Classes/Utility/BackendUtility.php
index 4b3dd71..f214e5e 100644
@@ -28,6 +28,7 @@
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
@@ -308,10 +309,7 @@
$queryBuilder->select('*')
->from($table)
->where(
- $queryBuilder->expr()->eq(
- $languageCapability->hasTranslationSourceField() ? $languageCapability->getTranslationSourceField()->getName() : $languageCapability->getTranslationOriginPointerField()->getName(),
- $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
- ),
+ self::createTranslationParentConstraint($queryBuilder, $languageCapability, (int)$uid),
$queryBuilder->expr()->eq(
$languageCapability->getLanguageField()->getName(),
$queryBuilder->createNamedParameter((int)$language, Connection::PARAM_INT)
@@ -329,6 +327,43 @@
return $recordLocalization;
}
+ /**
+ * Constraint matching all translations of the given record.
+ *
+ * `translationSource` (l10n_source) is preferred, since it is the more precise information for
+ * translation chains. It is however not maintained by all writes - a DataHandler datamap which
+ * creates a translation by only setting the language field and the transOrigPointerField leaves
+ * it empty - so such records are matched by their transOrigPointerField (l10n_parent) instead.
+ * Without that, valid translations would be invisible for callers like
+ * `DataHandler::localize()`, which uses this lookup to prevent duplicate translations.
+ */
+ protected static function createTranslationParentConstraint(
+ QueryBuilder $queryBuilder,
+ LanguageAwareSchemaCapability $languageCapability,
+ int $uid
+ ): CompositeExpression|string {
+ $uidParameter = $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT);
+ $translationOriginPointerConstraint = $queryBuilder->expr()->eq(
+ $languageCapability->getTranslationOriginPointerField()->getName(),
+ $uidParameter
+ );
+ if (!$languageCapability->hasTranslationSourceField()) {
+ return $translationOriginPointerConstraint;
+ }
+ $translationSourceFieldName = $languageCapability->getTranslationSourceField()->getName();
+
+ return $queryBuilder->expr()->or(
+ $queryBuilder->expr()->eq($translationSourceFieldName, $uidParameter),
+ $queryBuilder->expr()->and(
+ $queryBuilder->expr()->eq(
+ $translationSourceFieldName,
+ $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
+ ),
+ $translationOriginPointerConstraint
+ )
+ );
+ }
+
/*******************************************
*
* Page tree, TCA related
diff --git a/Classes/Domain/Repository/Localization/LocalizationRepository.php b/Classes/Domain/Repository/Localization/LocalizationRepository.php
index bfb5755..bc497f7 100644
@@ -23,11 +23,14 @@
use TYPO3\CMS\Core\Context\LanguageAspect;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
use TYPO3\CMS\Core\Domain\RawRecord;
use TYPO3\CMS\Core\Domain\RecordFactory;
use TYPO3\CMS\Core\Domain\RecordInterface;
+use TYPO3\CMS\Core\Schema\Capability\LanguageAwareSchemaCapability;
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
use TYPO3\CMS\Core\Schema\TcaSchema;
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
@@ -174,19 +177,11 @@
}
$queryBuilder->getRestrictions()->add(new WorkspaceRestriction($workspaceId));
- // Prefer translationSourceField (l10n_source) over transOrigPointerField (l10n_parent)
- $parentPointerField = $languageCapability->hasTranslationSourceField()
- ? $languageCapability->getTranslationSourceField()->getName()
- : $languageCapability->getTranslationOriginPointerField()->getName();
-
$queryBuilder
->select('*')
->from($table)
->where(
- $queryBuilder->expr()->eq(
- $parentPointerField,
- $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
- ),
+ $this->createTranslationParentConstraint($queryBuilder, $languageCapability, $uid),
$queryBuilder->expr()->eq(
$languageCapability->getLanguageField()->getName(),
$queryBuilder->createNamedParameter($languageId, Connection::PARAM_INT)
@@ -268,11 +263,6 @@
$languageCapability = $schema->getCapability(TcaSchemaCapability::Language);
$languageFieldName = $languageCapability->getLanguageField()->getName();
- // Prefer translationSourceField (l10n_source) over transOrigPointerField (l10n_parent)
- $parentPointerField = $languageCapability->hasTranslationSourceField()
- ? $languageCapability->getTranslationSourceField()->getName()
- : $languageCapability->getTranslationOriginPointerField()->getName();
-
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()->removeAll();
@@ -285,10 +275,7 @@
->select('*')
->from($table)
->where(
- $queryBuilder->expr()->eq(
- $parentPointerField,
- $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
- ),
+ $this->createTranslationParentConstraint($queryBuilder, $languageCapability, $uid),
$queryBuilder->expr()->gt(
$languageFieldName,
$queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
@@ -393,4 +380,42 @@
return $records;
}
+
+ /**
+ * Constraint matching all translations of the given record.
+ *
+ * `translationSource` (l10n_source) is preferred, since it is the more precise information for
+ * translation chains. It is however not maintained by all writes - a DataHandler datamap which
+ * creates a translation by only setting the language field and the transOrigPointerField leaves
+ * it empty - so such records are matched by their transOrigPointerField (l10n_parent) instead.
+ * Without that, valid translations would be invisible for callers like
+ * `DataHandler::localize()`, which uses this lookup to prevent duplicate translations.
+ */
+ protected function createTranslationParentConstraint(
+ QueryBuilder $queryBuilder,
+ LanguageAwareSchemaCapability $languageCapability,
+ int $uid
+ ): CompositeExpression|string {
+ $uidParameter = $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT);
+ $translationOriginPointerConstraint = $queryBuilder->expr()->eq(
+ $languageCapability->getTranslationOriginPointerField()->getName(),
+ $uidParameter
+ );
+ if (!$languageCapability->hasTranslationSourceField()) {
+ return $translationOriginPointerConstraint;
+ }
+ $translationSourceFieldName = $languageCapability->getTranslationSourceField()->getName();
+
+ return $queryBuilder->expr()->or(
+ $queryBuilder->expr()->eq($translationSourceFieldName, $uidParameter),
+ $queryBuilder->expr()->and(
+ $queryBuilder->expr()->eq(
+ $translationSourceFieldName,
+ $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
+ ),
+ $translationOriginPointerConstraint
+ )
+ );
+ }
+
}