[BUGFIX] Make Extbase translation handling consistent with TypoScript 74/53974/116
authorTymoteusz Motylewski <t.motylewski@gmail.com>
Mon, 16 Jul 2018 16:02:09 +0000 (18:02 +0200)
committerTymoteusz Motylewski <t.motylewski@gmail.com>
Sun, 30 Sep 2018 13:08:41 +0000 (15:08 +0200)
Make Extbase fetch records in the same way as in TypoScript rendering,
making QuerySettings options mean the same as corresponding settings
from TypoScript.

This change allows e.g. to filter and sort by translated values
of the aggregate root.

To make upgrades easier a feature switch is introduced which
enables the new behavior. It can be disabled with TypoScript if
somebody relies on the old behavior.

The feature switch is enabled by default in v9 and will be gone in v10
so in version 10 the fixed way will be the only way to fetch records
in Extbase.

Releases: master
Resolves: #82363
Resolves: #84011
Change-Id: I58d0a24c1f73debc6b9251efea9c28c9cf09d6d0
Reviewed-on: https://review.typo3.org/53974
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Tested-by: TYPO3com <no-reply@typo3.com>
23 files changed:
typo3/sysext/core/Classes/Context/LanguageAspect.php
typo3/sysext/core/Classes/Database/Schema/ConnectionMigrator.php
typo3/sysext/core/Documentation/Changelog/master/Important-82363-MakeExtBaseTranslationHandlingConsistentWithTyposcript.rst [new file with mode: 0644]
typo3/sysext/extbase/Classes/Domain/Model/FileReference.php
typo3/sysext/extbase/Classes/Persistence/Generic/LazyLoadingProxy.php
typo3/sysext/extbase/Classes/Persistence/Generic/LazyObjectStorage.php
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMap.php
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapFactory.php
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapper.php
typo3/sysext/extbase/Classes/Persistence/Generic/Query.php
typo3/sysext/extbase/Classes/Persistence/Generic/QueryResult.php
typo3/sysext/extbase/Classes/Persistence/Generic/QuerySettingsInterface.php
typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php
typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbQueryParser.php
typo3/sysext/extbase/Classes/Persistence/Generic/Typo3QuerySettings.php
typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/translatedBlogExampleData.csv
typo3/sysext/extbase/Tests/Functional/Persistence/QueryLocalizedDataLegacyTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Persistence/QueryLocalizedDataTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/TranslatedContentLegacyTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Persistence/TranslatedContentTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/TranslationLegacyTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Persistence/TranslationTest.php
typo3/sysext/extbase/ext_typoscript_setup.typoscript

index f45b7e8..430b642 100644 (file)
@@ -47,7 +47,7 @@ use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
  * - usually you fetch language 0 and -1, then take the "contentId" and "overlay" them
  *    - here you have two choices
  *          1. "on" if there is no overlay, do not render the default language records ("hideNonTranslated")
- *          2. "mixed" - if there no overlay, just keep the default language, possibility to have mixed languages - config.sys_language_overlay = 1
+ *          2. "mixed" - if there is no overlay, just keep the default language, possibility to have mixed languages - config.sys_language_overlay = 1
  *          3. "off" - do not do overlay, only fetch records available in the current "contentId" (see above), and do not care about overlays or fallbacks - fallbacks could be an option here, actually that is placed on top
  *          4. "includeFloating" - on + includeRecordsWithoutDefaultTranslation
  */
index 68b1343..3fae5cb 100644 (file)
@@ -1037,7 +1037,7 @@ class ConnectionMigrator
     {
         $connectionNames = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionNames();
 
-        if (array_key_exists($tableName, (array)$GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'])) {
+        if (isset($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName])) {
             return in_array($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName], $connectionNames, true)
                 ? $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName]
                 : ConnectionPool::DEFAULT_CONNECTION_NAME;
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Important-82363-MakeExtBaseTranslationHandlingConsistentWithTyposcript.rst b/typo3/sysext/core/Documentation/Changelog/master/Important-82363-MakeExtBaseTranslationHandlingConsistentWithTyposcript.rst
new file mode 100644 (file)
index 0000000..659b99d
--- /dev/null
@@ -0,0 +1,229 @@
+.. include:: ../../Includes.txt
+
+================================================================================
+Important: #82363 - Make Extbase translation handling consistent with TypoScript
+================================================================================
+
+See :issue:`82363`
+
+Description
+===========
+
+Extbase now renders the translated records in the same way TypoScript rendering does.
+The new behaviour is controlled by the Extbase feature switch `consistentTranslationOverlayHandling`.
+
+.. code-block:: typoscript
+
+     config.tx_extbase.features.consistentTranslationOverlayHandling = 1
+
+The new behaviour is default in v9. The feature switch will be removed in v10, so there will be just
+one way of fetching records.
+You can override the setting using normal TypoScript.
+
+
+Impact
+======
+
+Users relying on the old behaviour can disable the feature switch.
+
+The change modifies how Extbase interprets the TypoScript settings
+:ts:`config.sys_language_mode` and :ts:`config.sys_language_overlay` and the
+:php:`Typo3QuerySettings` properties :php:`languageOverlayMode` and :php:`languageMode`.
+
+Changes in the rendering:
+
+1) Setting :php:`Typo3QuerySettings->languageMode` does **not** influence how Extbase queries records anymore.
+   The corresponding TypoScript setting :ts:`config.sys_language_mode` is used by the core
+   to decide what to do when a page is not translated to the given language (display 404, or try page with different language).
+   Users who used to set :php:`Typo3QuerySettings->languageMode` to `strict` should use
+   :php:`Typo3QuerySettings->setLanguageOverlayMode('hideNonTranslated')` to get translated records only.
+
+   The old behavior was confusing, because `languageMode` had different meaning and accepted different
+   values in TS context and in Extbase context.
+
+2) Setting :php:`Typo3QuerySettings->languageOverlayMode` to :php:`true` makes Extbase fetch records
+   from default language and overlay them with translated values. So e.g. when a record is hidden in
+   the default language, it will not be shown. Also records without translation parents will not be shown.
+   For relations, Extbase reads relations from a translated record (so it’s not possible to inherit
+   a field value from translation source) and then passes the related records through :php:`$pageRepository->getRecordOverlay()`.
+   So e.g. when you have a translated `tt_content` with FAL relation, Extbase will show only those
+   `sys_file_reference` records which are connected to the translated record (not caring whether some of
+   these files have `l10n_parent` set).
+
+   Previously :php:`Typo3QuerySettings->languageOverlayMode` had no effect.
+   Extbase always performed an overlay process on the result set.
+
+3) Setting :php:`Typo3QuerySettings->languageOverlayMode` to :php:`false` makes Extbase fetch aggregate
+   root records from a given language only. Extbase will follow relations (child records) as they are,
+   without checking their `sys_language_uid` fields, and then it will pass these records through
+   :php:`$pageRepository->getRecordOverlay()`.
+   This way the aggregate root record's sorting and visibility doesn't depend on default language records.
+   Moreover, the relations of a record, which are often stored using default language uids,
+   are translated in the final result set (so overlay happens).
+
+   For example:
+   Given a translated `tt_content` having relation to 2 categories (in the mm table translated
+   tt_content record is connected to category uid in default language), and one of the categories is translated.
+   Extbase will return a `tt_content` model with both categories.
+   If you want to have just translated category shown, remove the relation in the translated `tt_content`
+   record in the TYPO3 Backend.
+
+Note that by default :php:`Typo3QuerySettings` uses the global TypoScript configuration like
+:ts:`config.sys_language_overlay` and :php:`$GLOBALS['TSFE']->sys_language_content`
+(calculated based on :ts:`config.sys_language_uid` and :ts:`config.sys_language_mode`).
+So you need to change :php:`Typo3QuerySettings` manually only if your Extbase code should
+behave different than other `tt_content` rendering.
+
+Setting :php:`setLanguageOverlayMode()` on a query influences **only** fetching of the aggregate root. Relations are always
+fetched with :php:`setLanguageOverlayMode(true)`.
+
+When querying data in translated language, and having :php:`setLanguageOverlayMode(true)`, the relations
+(child objects) are overlaid even if aggregate root is not translated.
+See :php:`QueryLocalizedDataTest->queryFirst5Posts()`.
+
+Following examples show how to query data in Extbase in different scenarios, independent of the global TS settings:
+
+1) Fetch records from the language uid=1 only, with no overlays.
+   Previously (:ts:`consistentTranslationOverlayHandling = 0`):
+
+   It was not possible.
+
+
+   Now (:ts:`consistentTranslationOverlayHandling = 1`):
+
+   ::
+
+      $querySettings = $query->getQuerySettings();
+      $querySettings->setLanguageUid(1);
+      $querySettings->setLanguageOverlayMode(false);
+
+2) Fetch records from the language uid=1, with overlay, but hide non-translated records
+   Previously (:ts:`consistentTranslationOverlayHandling = 0`):
+
+   ::
+
+      $querySettings = $query->getQuerySettings();
+      $querySettings->setLanguageUid(1);
+      $querySettings->setLanguageMode('strict');
+
+   Now (:ts:`consistentTranslationOverlayHandling = 1`):
+
+   ::
+
+      $querySettings = $query->getQuerySettings();
+      $querySettings->setLanguageUid(1);
+      $querySettings->setLanguageOverlayMode('hideNonTranslated');
+
+
++------------------------+-------------------------------------------------------------------------------------------------+----------------------------------------------+------------------------------+
+| QuerySettings property | old behaviour                                                                                   | new behaviour                                | default value (TSFE|Extbase) |
++------------------------+-------------------------------------------------------------------------------------------------+----------------------------------------------+------------------------------+
+| languageUid            |                                                                                                 | same                                         | 0                            |
++------------------------+-------------------------------------------------------------------------------------------------+----------------------------------------------+------------------------------+
+| respectSysLanguage     |                                                                                                 | same                                         | `true`                       |
++------------------------+-------------------------------------------------------------------------------------------------+----------------------------------------------+------------------------------+
+| languageOverlayMode    | not used                                                                                        | values: `true`, `false`, `hideNonTranslated` | 0|`true`                     |
+|                        |                                                                                                 |                                              |                              |
++------------------------+-------------------------------------------------------------------------------------------------+----------------------------------------------+------------------------------+
+| languageMode           | documented values: `null`, `content_fallback`, `strict` or `ignore`.                            | not used                                     | `null`                       |
+|                        | Only `strict` was evaluated. Setting `LanguageMode` to `strict`                                 |                                              |                              |
+|                        | caused passing `hideNonTranslated` param to `getRecordOverlay` in :php:`Typo3DbBackend`         |                                              |                              |
+|                        | and changing the query to work similar to TypoScript `sys_language_overlay = hideNonTranslated` |                                              |                              |
++------------------------+-------------------------------------------------------------------------------------------------+----------------------------------------------+------------------------------+
+
+
+Identifiers
+-----------
+
+Domain models have a main identifier `uid` and two additional properties `_localizedUid` and `_versionedUid`.
+Depending on whether the `languageOverlayMode` mode is enabled (`true` or `'hideNonTranslated'`) or disabled (`false`),
+the identifier contains different values.
+When `languageOverlayMode` is enabled then `uid` property contains `uid` value of the default language record,
+the `uid` of the translated record is kept in the `_localizedUid`.
+
++----------------------------------------------------------+-------------------------+---------------------------+
+| Context                                                  | Record in language 0    | Translated record         |
++==========================================================+=========================+===========================+
+| Database                                                 | uid:2                   | uid:11, l10n_parent:2     |
++----------------------------------------------------------+-------------------------+---------------------------+
+| Domain Object values with `languageOverlayMode` enabled  | uid:2, _localizedUid:2  | uid:2, _localizedUid:11   |
++----------------------------------------------------------+-------------------------+---------------------------+
+| Domain Object values with `languageOverlayMode` disabled | uid:2, _localizedUid:2  | uid:11, _localizedUid:11  |
++----------------------------------------------------------+-------------------------+---------------------------+
+
+See tests in :file:`extbase/Tests/Functional/Persistence/QueryLocalizedDataTest.php`.
+
+The :php:`$repository->findByUid()` method takes current rendering language into account (e.g. L=1). It does not take
+`defaultQuerySetting` set on the repository into account.
+The result of this method also depends on whether `languageOverlayMode` is set or not.
+Values in brackets show previous behaviour (disabled flag) if different than current.
+
+The bottom line is that with the feature flag on, you can now use  :php:`findByUid()` using translated record uid to get
+translated content independently from language set in global context.
+
++-------------------+----------------+----------------------+----------------------+----------------------+----------------------+
+|                   |                | L=0                                         | L=1                                         |
++-------------------+----------------+----------------------+----------------------+----------------------+----------------------+
+| repository method | property       | Overlay              | No overlay           | Overlay              | No overlay           |
++===================+================+======================+======================+======================+======================+
+| findByUid(2)      | title          | Post 2               | Post 2               | Post 2 (Post 2 - DK) | Post 2 (Post 2 - DK) |
++-------------------+----------------+----------------------+----------------------+----------------------+----------------------+
+|                   | uid            | 2                    | 2                    | 2                    | 2                    |
++-------------------+----------------+----------------------+----------------------+----------------------+----------------------+
+|                   | _localizedUid  | 2                    | 2                    | 2 (11)               | 2 (11)               |
++-------------------+----------------+----------------------+----------------------+----------------------+----------------------+
+| findByUid(11)     | title          | Post 2 - DK (Post 2) | Post 2 - DK (Post 2) | Post 2 - DK          | Post 2 - DK          |
++-------------------+----------------+----------------------+----------------------+----------------------+----------------------+
+|                   | uid            | 2 (2)                | 11 (2)               | 2                    | 11 (2)               |
++-------------------+----------------+----------------------+----------------------+----------------------+----------------------+
+|                   | _localizedUid  | 11 (2)               | 11 (2)               | 11                   | 11                   |
++-------------------+----------------+----------------------+----------------------+----------------------+----------------------+
+
+.. note::
+
+   Note that :php:`$repository->findByUid()` behaves differently than a custom query by an `uid`
+   like :php:`$query->matching($query->equals('uid', 11));` The custom query will return `null` if passed `uid` doesn't match
+   language set in the :php:`$querySettings->setLanguageUid()` method.
+
+Filtering & sorting
+-------------------
+
+When filtering by aggregate root property like `Post->title`,
+both filtering and sorting takes translated values into account and you will get correct results, also with pagination.
+
+When filtering or ordering by child object property, then Extbase does a left join between aggregate root
+table and child record table.
+Then the filter is applied as where clause. This means that filtering or ordering by child record property
+only takes values from child records which uids are stored in db (in most cases its default language record).
+See :php:`TranslationTest::fetchingTranslatedPostByBlogTitle()`
+
+This limitation also applies to Extbase with feature flag being disabled.
+
+Summary of the important code changes
+=====================================
+
+1) :php:`DataMapper` gets a :php:`Query` as a constructor parameter. This allows to use aggregate root :php:`QuerySettings` (language)
+   when fetching child records/relations. Later, in a separate patch we can pass other settings too e.g. :php:`setIgnoreEnableFields`
+   to fix issue around this setting. See :php:`DataMapper->getPreparedQuery` method.
+2) :php:`DataMapper` is passed to  :php:`LazyLoadingProxy` and  :php:`LazyObjectStorage`, so the settings don't get lost when fetching data lazily.
+3) :php:`Query` object gets a new property `parentQuery` which is useful to detect whether we're fetching aggregate root or child object.
+4) Extbase model for  :php:`FileReference` uses `_localizedUid` for fetching `OriginalResource`
+5) :php:`DataMapper` forces child records to be fetched using  :php:`setLanguageOverlayMode(true)`.
+6) When getRespectSysLanguage is set,  :php:`DataMapper` uses aggregate root language to overlay child records to correct language.
+7) The `where` clause used for finding translated records in overlay mode (`true`, `hideNonTranslated`) has been fixed.
+   It filters out the non translated records on db side in case `hideNonTranslated` is set.
+   It allows for filtering and sorting by translated values. See :php:`Typo3DbQueryParser->getLanguageStatement()`
+
+
+Most important known issues (ones this patch doesn't solve)
+===========================================================
+
+- Persistence session uses the same key for default language record and the translation - https://forge.typo3.org/issues/59992
+- Extbase allows to fetch deleted/hidden records - https://forge.typo3.org/issues/86307
+
+
+For more information about rendering please refer to the TypoScript reference_.
+
+.. _reference: https://docs.typo3.org/typo3cms/TyposcriptReference/Setup/Config/Index.html?highlight=sys_language_mode#sys-language-overlay
+
+.. index:: Database, TCA, TypoScript, ext:extbase
index 62d1532..b825f98 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Extbase\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
+
 /**
  * A file reference object (File Abstraction Layer)
  *
@@ -30,6 +32,19 @@ class FileReference extends \TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder
     protected $uidLocal;
 
     /**
+     * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
+     */
+    protected $configurationManager;
+
+    /**
+     * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
+     */
+    public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
+    {
+        $this->configurationManager = $configurationManager;
+    }
+
+    /**
      * @param \TYPO3\CMS\Core\Resource\ResourceInterface $originalResource
      */
     public function setOriginalResource(\TYPO3\CMS\Core\Resource\ResourceInterface $originalResource)
@@ -44,7 +59,11 @@ class FileReference extends \TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder
     public function getOriginalResource()
     {
         if ($this->originalResource === null) {
-            $this->originalResource = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFileReferenceObject($this->getUid());
+            $uid = $this->getUid();
+            if ($this->configurationManager->isFeatureEnabled('consistentTranslationOverlayHandling')) {
+                $uid = $this->_localizedUid;
+            }
+            $this->originalResource = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFileReferenceObject($uid);
         }
 
         return $this->originalResource;
index 677001e..21cf921 100644 (file)
@@ -25,7 +25,7 @@ use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
 class LazyLoadingProxy implements \Iterator, \TYPO3\CMS\Extbase\Persistence\Generic\LoadingStrategyInterface
 {
     /**
-     * @var DataMapper
+     * @var ?DataMapper
      */
     protected $dataMapper;
 
@@ -69,12 +69,14 @@ class LazyLoadingProxy implements \Iterator, \TYPO3\CMS\Extbase\Persistence\Gene
      * @param DomainObjectInterface $parentObject The object instance this proxy is part of
      * @param string $propertyName The name of the proxied property in it's parent
      * @param mixed $fieldValue The raw field value.
+     * @param ?DataMapper $dataMapper
      */
-    public function __construct($parentObject, $propertyName, $fieldValue)
+    public function __construct($parentObject, $propertyName, $fieldValue, ?DataMapper $dataMapper = null)
     {
         $this->parentObject = $parentObject;
         $this->propertyName = $propertyName;
         $this->fieldValue = $fieldValue;
+        $this->dataMapper = $dataMapper;
     }
 
     /**
@@ -82,7 +84,9 @@ class LazyLoadingProxy implements \Iterator, \TYPO3\CMS\Extbase\Persistence\Gene
      */
     public function initializeObject()
     {
-        $this->dataMapper = $this->objectManager->get(DataMapper::class);
+        if (!$this->dataMapper) {
+            $this->dataMapper = $this->objectManager->get(DataMapper::class);
+        }
     }
 
     /**
index f367995..0c58a66 100644 (file)
@@ -35,7 +35,7 @@ class LazyObjectStorage extends \TYPO3\CMS\Extbase\Persistence\ObjectStorage imp
     private $warning = 'You should never see this warning. If you do, you probably used PHP array functions like current() on the TYPO3\\CMS\\Extbase\\Persistence\\Generic\\LazyObjectStorage. To retrieve the first result, you can use the rewind() and current() methods.';
 
     /**
-     * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
+     * @var ?DataMapper
      */
     protected $dataMapper;
 
@@ -94,13 +94,15 @@ class LazyObjectStorage extends \TYPO3\CMS\Extbase\Persistence\ObjectStorage imp
      * @param DomainObjectInterface $parentObject The object instance this proxy is part of
      * @param string $propertyName The name of the proxied property in it's parent
      * @param mixed $fieldValue The raw field value.
+     * @param ?DataMapper $dataMapper
      */
-    public function __construct($parentObject, $propertyName, $fieldValue)
+    public function __construct($parentObject, $propertyName, $fieldValue, ?DataMapper $dataMapper = null)
     {
         $this->parentObject = $parentObject;
         $this->propertyName = $propertyName;
         $this->fieldValue = $fieldValue;
         reset($this->storage);
+        $this->dataMapper = $dataMapper;
     }
 
     /**
@@ -108,7 +110,9 @@ class LazyObjectStorage extends \TYPO3\CMS\Extbase\Persistence\ObjectStorage imp
      */
     public function initializeObject()
     {
-        $this->dataMapper = $this->objectManager->get(DataMapper::class);
+        if (!$this->dataMapper) {
+            $this->dataMapper = $this->objectManager->get(DataMapper::class);
+        }
     }
 
     /**
index 046fd6b..fc995a8 100644 (file)
@@ -243,7 +243,7 @@ class DataMap
      */
     public function getColumnMap($propertyName)
     {
-        return $this->columnMaps[$propertyName];
+        return $this->columnMaps[$propertyName] ?? null;
     }
 
     /**
index 90cb1cf..9f4fa09 100644 (file)
@@ -137,7 +137,7 @@ class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface
         $tableName = $this->resolveTableName($className);
         $columnMapping = [];
         $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
-        $classSettings = $frameworkConfiguration['persistence']['classes'][$className];
+        $classSettings = $frameworkConfiguration['persistence']['classes'][$className] ?? null;
         if ($classSettings !== null) {
             if (isset($classSettings['subclasses']) && is_array($classSettings['subclasses'])) {
                 $subclasses = $this->resolveSubclassesRecursive($frameworkConfiguration['persistence']['classes'], $classSettings['subclasses']);
@@ -424,7 +424,7 @@ class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface
         $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby'] ?? null);
         $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field'] ?? null);
         $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field'] ?? null);
-        if (is_array($columnConfiguration['foreign_match_fields'])) {
+        if (is_array($columnConfiguration['foreign_match_fields'] ?? null)) {
             $columnMap->setRelationTableMatchFields($columnConfiguration['foreign_match_fields']);
         }
         return $columnMap;
index cb0f7d1..147d68f 100644 (file)
@@ -15,10 +15,12 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper;
  */
 
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
+use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
 use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
 use TYPO3\CMS\Extbase\Object\Exception\CannotReconstituteObjectException;
 use TYPO3\CMS\Extbase\Persistence;
 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException;
+use TYPO3\CMS\Extbase\Persistence\QueryInterface;
 use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
 
 /**
@@ -69,6 +71,25 @@ class DataMapper
     protected $signalSlotDispatcher;
 
     /**
+     * @var ConfigurationManagerInterface
+     */
+    protected $configurationManager;
+
+    /**
+     * @var ?QueryInterface
+     */
+    protected $query;
+
+    /**
+     * DataMapper constructor.
+     * @param ?QueryInterface $query
+     */
+    public function __construct(?QueryInterface $query = null)
+    {
+        $this->query = $query;
+    }
+
+    /**
      * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
      */
     public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
@@ -125,6 +146,14 @@ class DataMapper
     }
 
     /**
+     * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
+     */
+    public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
+    {
+        $this->configurationManager = $configurationManager;
+    }
+
+    /**
      * Maps the given rows on objects
      *
      * @param string $className The name of the class
@@ -347,12 +376,12 @@ class DataMapper
         $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
         if ($enableLazyLoading === true && $propertyMetaData['annotations']['lazy']) {
             if ($propertyMetaData['type'] === Persistence\ObjectStorage::class) {
-                $result = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage::class, $parentObject, $propertyName, $fieldValue);
+                $result = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage::class, $parentObject, $propertyName, $fieldValue, $this);
             } else {
                 if (empty($fieldValue)) {
                     $result = null;
                 } else {
-                    $result = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy::class, $parentObject, $propertyName, $fieldValue);
+                    $result = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy::class, $parentObject, $propertyName, $fieldValue, $this);
                 }
             }
         } else {
@@ -408,11 +437,32 @@ class DataMapper
      */
     protected function getPreparedQuery(DomainObjectInterface $parentObject, $propertyName, $fieldValue = '')
     {
-        $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
+        $dataMap = $this->getDataMap(get_class($parentObject));
+        $columnMap = $dataMap->getColumnMap($propertyName);
         $type = $this->getType(get_class($parentObject), $propertyName);
         $query = $this->queryFactory->create($type);
+        if ($this->query) {
+            $query->setParentQuery($this->query);
+        }
         $query->getQuerySettings()->setRespectStoragePage(false);
         $query->getQuerySettings()->setRespectSysLanguage(false);
+
+        if ($this->configurationManager->isFeatureEnabled('consistentTranslationOverlayHandling')) {
+            //we always want to overlay relations as most of the time they are stored in db using default lang uids
+            $query->getQuerySettings()->setLanguageOverlayMode(true);
+            if ($this->query) {
+                $query->getQuerySettings()->setLanguageUid($this->query->getQuerySettings()->getLanguageUid());
+
+                if ($dataMap->getLanguageIdColumnName() !== null && !$this->query->getQuerySettings()->getRespectSysLanguage()) {
+                    //pass language of parent record to child objects, so they can be overlaid correctly in case
+                    //e.g. findByUid is used.
+                    //the languageUid is used for getRecordOverlay later on, despite RespectSysLanguage being false
+                    $languageUid = (int)$parentObject->_getProperty('_languageUid');
+                    $query->getQuerySettings()->setLanguageUid($languageUid);
+                }
+            }
+        }
+
         if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_MANY) {
             if ($columnMap->getChildSortByFieldName() !== null) {
                 $query->setOrderings([$columnMap->getChildSortByFieldName() => Persistence\QueryInterface::ORDER_ASCENDING]);
index 3d7b91d..a74d4a5 100644 (file)
@@ -106,6 +106,12 @@ class Query implements QueryInterface
     protected $querySettings;
 
     /**
+     * @var ?QueryInterface
+     * @internal
+     */
+    protected $parentQuery;
+
+    /**
      * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
      */
     public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
@@ -148,6 +154,24 @@ class Query implements QueryInterface
     }
 
     /**
+     * @return ?QueryInterface
+     * @internal
+     */
+    public function getParentQuery(): ?QueryInterface
+    {
+        return $this->parentQuery;
+    }
+
+    /**
+     * @param ?QueryInterface $parentQuery
+     * @internal
+     */
+    public function setParentQuery(?QueryInterface $parentQuery): void
+    {
+        $this->parentQuery = $parentQuery;
+    }
+
+    /**
      * Sets the Query Settings. These Query settings must match the settings expected by
      * the specific Storage Backend.
      *
index dcd66da..d478915 100644 (file)
@@ -86,7 +86,7 @@ class QueryResult implements QueryResultInterface
      */
     public function initializeObject()
     {
-        $this->dataMapper = $this->objectManager->get(DataMapper::class);
+        $this->dataMapper = $this->objectManager->get(DataMapper::class, $this->query);
     }
 
     /**
index 4d938a4..89da71a 100644 (file)
@@ -81,6 +81,8 @@ interface QuerySettingsInterface
     public function getLanguageOverlayMode();
 
     /**
+     * Language Mode is NOT used when consistentTranslationOverlayHandling is enabled
+     *
      * @param string $languageMode NULL, "content_fallback", "strict" or "ignore"
      * @return \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface instance of $this to allow method chaining
      * @api
@@ -88,6 +90,8 @@ interface QuerySettingsInterface
     public function setLanguageMode($languageMode);
 
     /**
+     * Language Mode is NOT used when consistentTranslationOverlayHandling is enabled
+     *
      * @return string NULL, "content_fallback", "strict" or "ignore"
      */
     public function getLanguageMode();
index 654d222..accdecd 100644 (file)
@@ -374,7 +374,12 @@ class Typo3DbBackend implements BackendInterface, SingletonInterface
             }
         }
 
-        $rows = $this->doLanguageAndWorkspaceOverlay($query->getSource(), $rows, $query->getQuerySettings());
+        if ($this->configurationManager->isFeatureEnabled('consistentTranslationOverlayHandling') && !empty($rows)) {
+            $rows = $this->overlayLanguageAndWorkspace($query->getSource(), $rows, $query);
+        } else {
+            $rows = $this->doLanguageAndWorkspaceOverlay($query->getSource(), $rows, $query->getQuerySettings());
+        }
+
         return $rows;
     }
 
@@ -527,6 +532,8 @@ class Typo3DbBackend implements BackendInterface, SingletonInterface
      * Performs workspace and language overlay on the given row array. The language and workspace id is automatically
      * detected (depending on FE or BE context). You can also explicitly set the language/workspace id.
      *
+     * This method performs overlay in a legacy way (when consistentTranslationOverlayHandling flag is disabled)
+     *
      * @param Qom\SourceInterface $source The source (selector od join)
      * @param array $rows
      * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
@@ -566,7 +573,8 @@ class Typo3DbBackend implements BackendInterface, SingletonInterface
         ) {
             $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
             $queryBuilder->getRestrictions()->removeAll();
-            $movePlaceholder = $queryBuilder->select($tableName . '.*')
+            $movePlaceholder = $queryBuilder
+                ->select($tableName . '.*')
                 ->from($tableName)
                 ->where(
                     $queryBuilder->expr()->eq('t3ver_state', $queryBuilder->createNamedParameter(3, \PDO::PARAM_INT)),
@@ -587,41 +595,128 @@ class Typo3DbBackend implements BackendInterface, SingletonInterface
             if (isset($tableName) && isset($GLOBALS['TCA'][$tableName])
                 && isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
                 && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
+                && isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
+                && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
             ) {
-                if (isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
-                    && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
-                ) {
-                    $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
-                    $queryBuilder->getRestrictions()->removeAll();
-                    $row = $queryBuilder->select($tableName . '.*')
-                        ->from($tableName)
-                        ->where(
-                            $queryBuilder->expr()->eq(
-                                $tableName . '.uid',
-                                $queryBuilder->createNamedParameter(
-                                    $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
-                                    \PDO::PARAM_INT
-                                )
-                            ),
-                            $queryBuilder->expr()->eq(
-                                $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
-                                $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
+                $queryBuilder->getRestrictions()->removeAll();
+                $row = $queryBuilder
+                    ->select($tableName . '.*')
+                    ->from($tableName)
+                    ->where(
+                        $queryBuilder->expr()->eq(
+                            $tableName . '.uid',
+                            $queryBuilder->createNamedParameter(
+                                $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
+                                \PDO::PARAM_INT
                             )
+                        ),
+                        $queryBuilder->expr()->eq(
+                            $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
+                            $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
                         )
-                        ->setMaxResults(1)
-                        ->execute()
-                        ->fetch();
-                }
+                    )
+                    ->setMaxResults(1)
+                    ->execute()
+                    ->fetch();
             }
             $pageRepository->versionOL($tableName, $row, true);
             if ($tableName === 'pages') {
                 $row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid());
             } elseif (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
-                      && $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] !== ''
+                && $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] !== ''
+                && in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], [-1, 0])
             ) {
-                if (in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], [-1, 0])) {
-                    $overlayMode = $querySettings->getLanguageMode() === 'strict' ? 'hideNonTranslated' : '';
-                    $row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getLanguageUid(), $overlayMode);
+                $overlayMode = $querySettings->getLanguageMode() === 'strict' ? 'hideNonTranslated' : '';
+                $row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getLanguageUid(), $overlayMode);
+            }
+            if (is_array($row)) {
+                $overlaidRows[] = $row;
+            }
+        }
+        return $overlaidRows;
+    }
+
+    /**
+     * Performs workspace and language overlay on the given row array. The language and workspace id is automatically
+     * detected (depending on FE or BE context). You can also explicitly set the language/workspace id.
+     *
+     * @param Qom\SourceInterface $source The source (selector or join)
+     * @param array $rows
+     * @param QueryInterface $querySettings The TYPO3 CMS specific query settings
+     * @param int|null $workspaceUid
+     * @return array
+     */
+    protected function overlayLanguageAndWorkspace(Qom\SourceInterface $source, array $rows, QueryInterface $query, int $workspaceUid = null): array
+    {
+        if ($source instanceof Qom\SelectorInterface) {
+            $tableName = $source->getSelectorName();
+        } elseif ($source instanceof Qom\JoinInterface) {
+            $tableName = $source->getRight()->getSelectorName();
+        } else {
+            // No proper source, so we do not have a table name here
+            // we cannot do an overlay and return the original rows instead.
+            return $rows;
+        }
+
+        $context = $this->objectManager->get(Context::class);
+        if ($workspaceUid === null) {
+            $workspaceUid = $context->getPropertyFromAspect('workspace', 'id');
+        } else {
+            // A custom query is needed, so a custom context is cloned
+            $workspaceUid = (int)$workspaceUid;
+            $context = clone $context;
+            $context->setAspect('workspace', $this->objectManager->get(WorkspaceAspect::class, $workspaceUid));
+        }
+        $pageRepository = $this->objectManager->get(PageRepository::class, $context);
+
+        // Fetches the move-placeholder in case it is supported
+        // by the table and if there's only one row in the result set
+        // (applying this to all rows does not work, since the sorting
+        // order would be destroyed and possible limits not met anymore)
+        if (!empty($workspaceUid)
+            && BackendUtility::isTableWorkspaceEnabled($tableName)
+            && count($rows) === 1
+        ) {
+            $versionId = $workspaceUid;
+            $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
+            $queryBuilder->getRestrictions()->removeAll();
+            $movePlaceholder = $queryBuilder
+                ->select($tableName . '.*')
+                ->from($tableName)
+                ->where(
+                    $queryBuilder->expr()->eq('t3ver_state', $queryBuilder->createNamedParameter(3, \PDO::PARAM_INT)),
+                    $queryBuilder->expr()->eq('t3ver_wsid', $queryBuilder->createNamedParameter($versionId, \PDO::PARAM_INT)),
+                    $queryBuilder->expr()->eq('t3ver_move_id', $queryBuilder->createNamedParameter($rows[0]['uid'], \PDO::PARAM_INT))
+                )
+                ->setMaxResults(1)
+                ->execute()
+                ->fetchAll();
+            if (!empty($movePlaceholder)) {
+                $rows = $movePlaceholder;
+            }
+        }
+        $overlaidRows = [];
+        foreach ($rows as $row) {
+            $pageRepository->versionOL($tableName, $row, true);
+            $querySettings = $query->getQuerySettings();
+            if (is_array($row) && $querySettings->getLanguageOverlayMode()) {
+                if ($tableName === 'pages') {
+                    $row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid());
+                } else {
+                    $languageUid = (int)$querySettings->getLanguageUid();
+                    if (!$querySettings->getRespectSysLanguage()
+                        && isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']]) > 0
+                        && !$query->getParentQuery()) {
+                        //we're mapping the aggregate root.
+                        $languageUid = $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']];
+                    }
+                    if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']) && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0) {
+                        //force overlay
+                        $row['uid'] = $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']];
+                        $row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] = 0;
+                    }
+                    $row = $pageRepository->getRecordOverlay($tableName, $row, $languageUid, (string)$querySettings->getLanguageOverlayMode());
                 }
             }
             if ($row !== null && is_array($row)) {
@@ -644,9 +739,7 @@ class Typo3DbBackend implements BackendInterface, SingletonInterface
     protected function clearPageCache($tableName, $uid)
     {
         $frameworkConfiguration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
-        if (isset($frameworkConfiguration['persistence']['enableAutomaticCacheClearing']) && $frameworkConfiguration['persistence']['enableAutomaticCacheClearing'] === '1') {
-        } else {
-            // if disabled, return
+        if (empty($frameworkConfiguration['persistence']['enableAutomaticCacheClearing'])) {
             return;
         }
         $pageIdsToClear = [];
index 7cd508c..65c44fa 100644 (file)
@@ -22,6 +22,8 @@ use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
+use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
 use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
 use TYPO3\CMS\Extbase\Persistence\Generic\Exception;
 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\InconsistentQuerySettingsException;
@@ -61,6 +63,11 @@ class Typo3DbQueryParser
     protected $environmentService;
 
     /**
+     * @var ConfigurationManagerInterface
+     */
+    protected $configurationManager;
+
+    /**
      * Instance of the Doctrine query builder
      *
      * @var QueryBuilder
@@ -132,6 +139,14 @@ class Typo3DbQueryParser
     }
 
     /**
+     * @param ConfigurationManagerInterface $configurationManager
+     */
+    public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
+    {
+        $this->configurationManager = $configurationManager;
+    }
+
+    /**
      * Whether using a distinct query is suggested.
      * This information is defined during parsing of the current query
      * for RELATION_HAS_MANY & RELATION_HAS_AND_BELONGS_TO_MANY relations.
@@ -305,8 +320,16 @@ class Typo3DbQueryParser
      */
     protected function addTypo3Constraints(QueryInterface $query)
     {
+        $index = 0;
         foreach ($this->tableAliasMap as $tableAlias => $tableName) {
-            $additionalWhereClauses = $this->getAdditionalWhereClause($query->getQuerySettings(), $tableName, $tableAlias);
+            if ($index === 0 || !$this->configurationManager->isFeatureEnabled('consistentTranslationOverlayHandling')) {
+                // With the new behaviour enabled, we only add the pid and language check for the first table (aggregate root).
+                // We know the first table is always the main table for the current query run.
+                $additionalWhereClauses = $this->getAdditionalWhereClause($query->getQuerySettings(), $tableName, $tableAlias);
+            } else {
+                $additionalWhereClauses = [];
+            }
+            $index++;
             $statement = $this->getVisibilityConstraintStatement($query->getQuerySettings(), $tableName, $tableAlias);
             if ($statement !== '') {
                 $additionalWhereClauses[] = $statement;
@@ -536,7 +559,16 @@ class Typo3DbQueryParser
      */
     protected function createTypedNamedParameter($value, int $forceType = null): string
     {
-        $plainValue = $this->dataMapper->getPlainValue($value);
+        $consistentHandlingEnabled = $this->configurationManager->isFeatureEnabled('consistentTranslationOverlayHandling');
+        if ($consistentHandlingEnabled
+            && $value instanceof DomainObjectInterface
+            && $value->_hasProperty('_localizedUid')
+            && $value->_getProperty('_localizedUid') > 0
+        ) {
+            $plainValue = (int)$value->_getProperty('_localizedUid');
+        } else {
+            $plainValue = $this->dataMapper->getPlainValue($value);
+        }
         $parameterType = $forceType ?? $this->getParameterType($plainValue);
         $placeholder = $this->queryBuilder->createNamedParameter($plainValue, $parameterType);
 
@@ -659,7 +691,12 @@ class Typo3DbQueryParser
     {
         $whereClause = [];
         if ($querySettings->getRespectSysLanguage()) {
-            $systemLanguageStatement = $this->getSysLanguageStatement($tableName, $tableAlias, $querySettings);
+            if ($this->configurationManager->isFeatureEnabled('consistentTranslationOverlayHandling')) {
+                $systemLanguageStatement = $this->getLanguageStatement($tableName, $tableAlias, $querySettings);
+            } else {
+                $systemLanguageStatement = $this->getSysLanguageStatement($tableName, $tableAlias, $querySettings);
+            }
+
             if (!empty($systemLanguageStatement)) {
                 $whereClause[] = $systemLanguageStatement;
             }
@@ -686,7 +723,7 @@ class Typo3DbQueryParser
     protected function getVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableName, $tableAlias)
     {
         $statement = '';
-        if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
+        if (is_array($GLOBALS['TCA'][$tableName]['ctrl'] ?? null)) {
             $ignoreEnableFields = $querySettings->getIgnoreEnableFields();
             $enableFieldsToBeIgnored = $querySettings->getEnableFieldsToBeIgnored();
             $includeDeleted = $querySettings->getIncludeDeleted();
@@ -753,7 +790,7 @@ class Typo3DbQueryParser
     }
 
     /**
-     * Builds the language field statement
+     * Builds the language field statement in a legacy way (when consistentTranslationOverlayHandling flag is disabled)
      *
      * @param string $tableName The database table name
      * @param string $tableAlias The table alias used in the query.
@@ -835,6 +872,92 @@ class Typo3DbQueryParser
     }
 
     /**
+     * Builds the language field statement
+     *
+     * @param string $tableName The database table name
+     * @param string $tableAlias The table alias used in the query.
+     * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
+     * @return string
+     */
+    protected function getLanguageStatement($tableName, $tableAlias, QuerySettingsInterface $querySettings)
+    {
+        if (empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
+            return '';
+        }
+
+        // Select all entries for the current language
+        // If any language is set -> get those entries which are not translated yet
+        // They will be removed by \TYPO3\CMS\Frontend\Page\PageRepository::getRecordOverlay if not matching overlay mode
+        $languageField = $GLOBALS['TCA'][$tableName]['ctrl']['languageField'];
+
+        $transOrigPointerField = $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] ?? '';
+        if (!$transOrigPointerField || !$querySettings->getLanguageUid()) {
+            return $this->queryBuilder->expr()->in(
+                $tableAlias . '.' . $languageField,
+                [(int)$querySettings->getLanguageUid(), -1]
+            );
+        }
+
+        $mode = $querySettings->getLanguageOverlayMode();
+        if (!$mode) {
+            return $this->queryBuilder->expr()->in(
+                $tableAlias . '.' . $languageField,
+                [(int)$querySettings->getLanguageUid(), -1]
+            );
+        }
+
+        $defLangTableAlias = $tableAlias . '_dl';
+        $defaultLanguageRecordsSubSelect = $this->queryBuilder->getConnection()->createQueryBuilder();
+        $defaultLanguageRecordsSubSelect
+            ->select($defLangTableAlias . '.uid')
+            ->from($tableName, $defLangTableAlias)
+            ->where(
+                $defaultLanguageRecordsSubSelect->expr()->andX(
+                    $defaultLanguageRecordsSubSelect->expr()->eq($defLangTableAlias . '.' . $transOrigPointerField, 0),
+                    $defaultLanguageRecordsSubSelect->expr()->eq($defLangTableAlias . '.' . $languageField, 0)
+                )
+            );
+
+        $andConditions = [];
+        // records in language 'all'
+        $andConditions[] = $this->queryBuilder->expr()->eq($tableAlias . '.' . $languageField, -1);
+        // translated records where a default translation exists
+        $andConditions[] = $this->queryBuilder->expr()->andX(
+            $this->queryBuilder->expr()->eq($tableAlias . '.' . $languageField, (int)$querySettings->getLanguageUid()),
+            $this->queryBuilder->expr()->in(
+                $tableAlias . '.' . $transOrigPointerField,
+                $defaultLanguageRecordsSubSelect->getSQL()
+            )
+        );
+        if ($mode !== 'hideNonTranslated') {
+            // $mode = TRUE
+            // returns records from current language which have default translation
+            // together with not translated default language records
+            $translatedOnlyTableAlias = $tableAlias . '_to';
+            $queryBuilderForSubselect = $this->queryBuilder->getConnection()->createQueryBuilder();
+            $queryBuilderForSubselect
+                ->select($translatedOnlyTableAlias . '.' . $transOrigPointerField)
+                ->from($tableName, $translatedOnlyTableAlias)
+                ->where(
+                    $queryBuilderForSubselect->expr()->andX(
+                        $queryBuilderForSubselect->expr()->gt($translatedOnlyTableAlias . '.' . $transOrigPointerField, 0),
+                        $queryBuilderForSubselect->expr()->eq($translatedOnlyTableAlias . '.' . $languageField, (int)$querySettings->getLanguageUid())
+                    )
+                );
+            // records in default language, which do not have a translation
+            $andConditions[] = $this->queryBuilder->expr()->andX(
+                $this->queryBuilder->expr()->eq($tableAlias . '.' . $languageField, 0),
+                $this->queryBuilder->expr()->notIn(
+                    $tableAlias . '.uid',
+                    $queryBuilderForSubselect->getSQL()
+                )
+            );
+        }
+
+        return $this->queryBuilder->expr()->orX(...$andConditions);
+    }
+
+    /**
      * Builds the page ID checking statement
      *
      * @param string $tableName The database table name
index ca32719..f0be6e8 100644 (file)
@@ -82,6 +82,8 @@ class Typo3QuerySettings implements QuerySettingsInterface
     protected $languageOverlayMode = true;
 
     /**
+     * Language Mode is NOT used when consistentTranslationOverlayHandling is enabled
+     *
      * Representing sys_language_mode only valid for current context
      *
      * @var string
@@ -221,6 +223,8 @@ class Typo3QuerySettings implements QuerySettingsInterface
     }
 
     /**
+     * Language Mode is NOT used when consistentTranslationOverlayHandling is enabled
+     *
      * @param string $languageMode NULL, "content_fallback", "strict" or "ignore"
      * @return QuerySettingsInterface instance of $this to allow method chaining
      * @api
@@ -232,6 +236,8 @@ class Typo3QuerySettings implements QuerySettingsInterface
     }
 
     /**
+     * Language Mode is NOT used when consistentTranslationOverlayHandling is enabled
+     *
      * @return string NULL, "content_fallback", "strict" or "ignore"
      */
     public function getLanguageMode()
diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/QueryLocalizedDataLegacyTest.php b/typo3/sysext/extbase/Tests/Functional/Persistence/QueryLocalizedDataLegacyTest.php
new file mode 100644 (file)
index 0000000..58855fb
--- /dev/null
@@ -0,0 +1,1160 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use PHPUnit\Framework\MockObject\MockObject;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
+use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
+use TYPO3\CMS\Extbase\Persistence\QueryInterface;
+use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
+use TYPO3\CMS\Extbase\Service\EnvironmentService;
+use TYPO3\CMS\Frontend\Page\PageRepository;
+
+/**
+ * This test proves there is no regression between how Extbase query localized records in v8 and v9
+ * The feature flag `consistentTranslationOverlayHandling` is off.
+ */
+class QueryLocalizedDataLegacyTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
+{
+    /**
+     * @var array
+     */
+    protected $testExtensionsToLoad = ['typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example'];
+
+    /**
+     * @var array
+     */
+    protected $coreExtensionsToLoad = ['extbase', 'fluid'];
+
+    /**
+     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface The object manager
+     */
+    protected $objectManager;
+
+    /**
+     * @var \TYPO3\CMS\Extbase\Persistence\Repository
+     */
+    protected $postRepository;
+
+    /**
+     * @var PersistenceManager;
+     */
+    protected $persistenceManager;
+
+    /**
+     * Sets up this test suite.
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->importCSVDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/translatedBlogExampleData.csv');
+        $this->setUpBasicFrontendEnvironment();
+
+        $this->objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
+        $configuration = [
+            'features' => ['consistentTranslationOverlayHandling' => 0],
+            'persistence' => [
+                'storagePid' => 20,
+                'classes' => [
+                    'TYPO3\CMS\Extbase\Domain\Model\Category' => [
+                        'mapping' => ['tableName' => 'sys_category']
+                    ]
+                ]
+            ]
+        ];
+        $configurationManager = $this->objectManager->get(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::class);
+        $configurationManager->setConfiguration($configuration);
+        $this->postRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PostRepository::class);
+        $this->persistenceManager = $this->objectManager->get(PersistenceManager::class);
+    }
+
+    /**
+     * Minimal frontend environment to satisfy Extbase Typo3DbBackend
+     */
+    protected function setUpBasicFrontendEnvironment()
+    {
+        /** @var MockObject|EnvironmentService $environmentServiceMock */
+        $environmentServiceMock = $this->createMock(EnvironmentService::class);
+        $environmentServiceMock
+            ->expects($this->atLeast(1))
+            ->method('isEnvironmentInFrontendMode')
+            ->willReturn(true);
+        GeneralUtility::setSingletonInstance(EnvironmentService::class, $environmentServiceMock);
+
+        $context = GeneralUtility::makeInstance(Context::class);
+        $context->setAspect('language', new LanguageAspect(0, 0, LanguageAspect::OVERLAYS_ON, []));
+
+        $pageRepositoryFixture = new PageRepository();
+        $frontendControllerMock = $this->createMock(\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class);
+        $frontendControllerMock->sys_page = $pageRepositoryFixture;
+        $GLOBALS['TSFE'] = $frontendControllerMock;
+    }
+
+    /**
+     * Test in default language
+     *
+     * With overlays enabled it doesn't make a difference whether you call findByUid with translated record uid or
+     * default language record uid.
+     *
+     * @test
+     */
+    public function findByUidOverlayModeOnDefaultLanguage()
+    {
+        $context = GeneralUtility::makeInstance(Context::class);
+        $context->setAspect('language', new LanguageAspect(0, 0, LanguageAspect::OVERLAYS_ON));
+
+        $post2 = $this->postRepository->findByUid(2);
+
+        $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
+            $post2->getTitle(),
+            $post2->getUid(),
+            $post2->_getProperty('_localizedUid'),
+            $post2->getBlog()->getTitle(),
+            $post2->getBlog()->getUid(),
+            $post2->getBlog()->_getProperty('_localizedUid'),
+            $post2->getAuthor()->getFirstname(),
+            $post2->getAuthor()->getUid(),
+            $post2->getAuthor()->_getProperty('_localizedUid')
+        ]);
+
+        //this is needed because of https://forge.typo3.org/issues/59992
+        $this->persistenceManager->clearState();
+
+        $post2translated = $this->postRepository->findByUid(11);
+        $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
+            $post2translated->getTitle(),
+            $post2translated->getUid(),
+            $post2translated->_getProperty('_localizedUid'),
+            $post2translated->getBlog()->getTitle(),
+            $post2translated->getBlog()->getUid(),
+            $post2translated->getBlog()->_getProperty('_localizedUid'),
+            $post2translated->getAuthor()->getFirstname(),
+            $post2translated->getAuthor()->getUid(),
+            $post2translated->getAuthor()->_getProperty('_localizedUid')
+        ]);
+    }
+
+    /**
+     * Test in default language, overlays disabled
+     *
+     * @test
+     */
+    public function findByUidNoOverlaysDefaultLanguage()
+    {
+        $context = GeneralUtility::makeInstance(Context::class);
+        $context->setAspect('language', new LanguageAspect(0, 0, LanguageAspect::OVERLAYS_OFF));
+
+        $post2 = $this->postRepository->findByUid(2);
+        $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
+            $post2->getTitle(),
+            $post2->getUid(),
+            $post2->_getProperty('_localizedUid'),
+            $post2->getBlog()->getTitle(),
+            $post2->getBlog()->getUid(),
+            $post2->getBlog()->_getProperty('_localizedUid'),
+            $post2->getAuthor()->getFirstname(),
+            $post2->getAuthor()->getUid(),
+            $post2->getAuthor()->_getProperty('_localizedUid')
+        ]);
+
+        //this is needed because of https://forge.typo3.org/issues/59992
+        $this->persistenceManager->clearState();
+
+        $post2translated = $this->postRepository->findByUid(11);
+        $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
+            $post2translated->getTitle(),
+            $post2translated->getUid(),
+            $post2translated->_getProperty('_localizedUid'),
+            $post2translated->getBlog()->getTitle(),
+            $post2translated->getBlog()->getUid(),
+            $post2translated->getBlog()->_getProperty('_localizedUid'),
+            $post2translated->getAuthor()->getFirstname(),
+            $post2translated->getAuthor()->getUid(),
+            $post2translated->getAuthor()->_getProperty('_localizedUid')
+        ]);
+    }
+
+    /**
+     * Test in language uid:1, overlays enabled
+     *
+     * @test
+     */
+    public function findByUidOverlayModeOnLanguage()
+    {
+        $context = GeneralUtility::makeInstance(Context::class);
+        $context->setAspect('language', new LanguageAspect(1, 1, LanguageAspect::OVERLAYS_ON));
+
+        $post2 = $this->postRepository->findByUid(2);
+        //this is needed because of https://forge.typo3.org/issues/59992
+        $this->persistenceManager->clearState();
+        $post2translated = $this->postRepository->findByUid(11);
+
+        foreach ([$post2, $post2translated] as $post) {
+            $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
+                $post->getTitle(),
+                $post->getUid(),
+                $post->_getProperty('_localizedUid'),
+                $post->getBlog()->getTitle(),
+                $post->getBlog()->getUid(),
+                $post->getBlog()->_getProperty('_localizedUid'),
+                $post->getAuthor()->getFirstname(),
+                $post->getAuthor()->getUid(),
+                $post->getAuthor()->_getProperty('_localizedUid')
+            ]);
+        }
+    }
+
+    /**
+     * Test in language uid:1, overlays disabled
+     *
+     * @test
+     */
+    public function findByUidNoOverlaysLanguage()
+    {
+        $context = GeneralUtility::makeInstance(Context::class);
+        $context->setAspect('language', new LanguageAspect(1, 1, LanguageAspect::OVERLAYS_OFF));
+
+        $post2 = $this->postRepository->findByUid(2);
+        $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
+            $post2->getTitle(),
+            $post2->getUid(),
+            $post2->_getProperty('_localizedUid'),
+            $post2->getBlog()->getTitle(),
+            $post2->getBlog()->getUid(),
+            $post2->getBlog()->_getProperty('_localizedUid'),
+            $post2->getAuthor()->getFirstname(),
+            $post2->getAuthor()->getUid(),
+            $post2->getAuthor()->_getProperty('_localizedUid')
+        ]);
+
+        //this is needed because of https://forge.typo3.org/issues/59992
+        $this->persistenceManager->clearState();
+
+        $post2translated = $this->postRepository->findByUid(11);
+        $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
+            $post2translated->getTitle(),
+            $post2translated->getUid(),
+            $post2translated->_getProperty('_localizedUid'),
+            $post2translated->getBlog()->getTitle(),
+            $post2translated->getBlog()->getUid(),
+            $post2translated->getBlog()->_getProperty('_localizedUid'),
+            $post2translated->getAuthor()->getFirstname(),
+            $post2translated->getAuthor()->getUid(),
+            $post2translated->getAuthor()->_getProperty('_localizedUid')
+        ]);
+    }
+
+    /**
+     * This tests shows what query by uid returns depending on the language,
+     * and used uid (default language record or translated record uid).
+     * All with overlay mode enabled.
+     *
+     * The post with uid 2 is translated to language 1, and there has uid 11.
+     *
+     * @test
+     */
+    public function customFindByUidOverlayEnabled()
+    {
+        // we're in default lang and fetching by uid of the record in default language
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageUid(0);
+        $querySettings->setLanguageOverlayMode(true);
+        $query->matching($query->equals('uid', 2));
+        $post2 = $query->execute()->getFirst();
+
+        $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
+            $post2->getTitle(),
+            $post2->getUid(),
+            $post2->_getProperty('_localizedUid'),
+            $post2->getBlog()->getTitle(),
+            $post2->getBlog()->getUid(),
+            $post2->getBlog()->_getProperty('_localizedUid'),
+            $post2->getAuthor()->getFirstname(),
+            $post2->getAuthor()->getUid(),
+            $post2->getAuthor()->_getProperty('_localizedUid')
+        ]);
+
+        //this is needed because of https://forge.typo3.org/issues/59992
+        $this->persistenceManager->clearState();
+
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageUid(0);
+        $querySettings->setLanguageOverlayMode(true);
+        $query->matching($query->equals('uid', 11));
+        $post2 = $query->execute()->getFirst();
+
+        $this->assertNull($post2);
+
+        //this is needed because of https://forge.typo3.org/issues/59992
+        $this->persistenceManager->clearState();
+
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageUid(1);
+        $querySettings->setLanguageOverlayMode(true);
+        $query->matching($query->equals('uid', 2));
+        $post2 = $query->execute()->getFirst();
+
+        $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1', 1, 1, 'John', 1, 1], [
+            $post2->getTitle(),
+            $post2->getUid(),
+            $post2->_getProperty('_localizedUid'),
+            $post2->getBlog()->getTitle(),
+            $post2->getBlog()->getUid(),
+            $post2->getBlog()->_getProperty('_localizedUid'),
+            $post2->getAuthor()->getFirstname(),
+            $post2->getAuthor()->getUid(),
+            $post2->getAuthor()->_getProperty('_localizedUid')
+        ]);
+
+        //this is needed because of https://forge.typo3.org/issues/59992
+        $this->persistenceManager->clearState();
+
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageUid(1);
+        $querySettings->setLanguageOverlayMode(true);
+        $query->matching($query->equals('uid', 11));
+        $post2 = $query->execute()->getFirst();
+
+        $this->assertNull($post2);
+    }
+
+    /**
+     * This tests shows what query by uid returns depending on the language,
+     * and used uid (default language record or translated record uid).
+     * All with overlay mode disabled.
+     *
+     * The post with uid 2 is translated to language 1, and there has uid 11.
+     *
+     * @test
+     */
+    public function customFindByUidOverlayDisabled()
+    {
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageUid(0);
+        $querySettings->setLanguageOverlayMode(false);
+        $query->matching($query->equals('uid', 2));
+        $post2 = $query->execute()->getFirst();
+
+        $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
+            $post2->getTitle(),
+            $post2->getUid(),
+            $post2->_getProperty('_localizedUid'),
+            $post2->getBlog()->getTitle(),
+            $post2->getBlog()->getUid(),
+            $post2->getBlog()->_getProperty('_localizedUid'),
+            $post2->getAuthor()->getFirstname(),
+            $post2->getAuthor()->getUid(),
+            $post2->getAuthor()->_getProperty('_localizedUid')
+        ]);
+
+        //this is needed because of https://forge.typo3.org/issues/59992
+        $this->persistenceManager->clearState();
+
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageUid(0);
+        $querySettings->setLanguageOverlayMode(false);
+        $query->matching($query->equals('uid', 11));
+        $post2 = $query->execute()->getFirst();
+
+        $this->assertNull($post2);
+
+        //this is needed because of https://forge.typo3.org/issues/59992
+        $this->persistenceManager->clearState();
+
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageUid(1);
+        $querySettings->setLanguageOverlayMode(false);
+        $query->matching($query->equals('uid', 2));
+        $post2 = $query->execute()->getFirst();
+
+        //the john is not translated but it should, see next query in this test
+        $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1', 1, 1, 'John', 1, 1], [
+            $post2->getTitle(),
+            $post2->getUid(),
+            $post2->_getProperty('_localizedUid'),
+            $post2->getBlog()->getTitle(),
+            $post2->getBlog()->getUid(),
+            $post2->getBlog()->_getProperty('_localizedUid'),
+            $post2->getAuthor()->getFirstname(),
+            $post2->getAuthor()->getUid(),
+            $post2->getAuthor()->_getProperty('_localizedUid')
+        ]);
+
+        //this is needed because of https://forge.typo3.org/issues/59992
+        $this->persistenceManager->clearState();
+
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageUid(1);
+        $querySettings->setLanguageOverlayMode(false);
+        $query->matching($query->equals('uid', 11));
+        $post2 = $query->execute()->getFirst();
+
+        $this->assertNull($post2);
+
+        //We're setting global context here to show that the result is different then one above.
+        //this means that language which is set in global context influences overlays of relations
+        $context = GeneralUtility::makeInstance(Context::class);
+        $context->setAspect('language', new LanguageAspect(1, 1, LanguageAspect::OVERLAYS_ON));
+
+        //this is needed because of https://forge.typo3.org/issues/59992
+        $this->persistenceManager->clearState();
+
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageUid(1);
+        $querySettings->setLanguageOverlayMode(false);
+        $query->matching($query->equals('uid', 11));
+        $post2 = $query->execute()->getFirst();
+
+        $this->assertNull($post2);
+    }
+
+    public function queryFirst5PostsDataProvider()
+    {
+        //put it to variable to make cases with the same expected values explicit
+        $lang0Expected = [
+            [
+                'title' => 'Post 4',
+                'uid' => 4,
+                '_localizedUid' => 4,
+                'content' => 'A - content',
+                'blog.title' => 'Blog 1',
+                'blog.uid' => 1,
+                'blog._localizedUid' => 1,
+                'author.firstname' => 'John',
+                'author.uid' => 1,
+                'author._localizedUid' => 1,
+                'secondAuthor.firstname' => 'John',
+                'secondAuthor.uid' => 1,
+                'secondAuthor._localizedUid' => 1,
+                'tags' => [],
+            ],
+            [
+                'title' => 'Post 2',
+                'uid' => 2,
+                '_localizedUid' => 2,
+                'content' => 'B - content',
+                'blog.title' => 'Blog 1',
+                'blog.uid' => 1,
+                'blog._localizedUid' => 1,
+                'author.firstname' => 'John',
+                'author.uid' => 1,
+                'author._localizedUid' => 1,
+                'secondAuthor.firstname' => 'John',
+                'secondAuthor.uid' => 1,
+                'secondAuthor._localizedUid' => 1,
+                'tags.0.name' => 'Tag2',
+                'tags.0.uid' => 2,
+                'tags.0._localizedUid' => 2,
+                'tags.1.name' => 'Tag3',
+                'tags.1.uid' => 3,
+                'tags.1._localizedUid' => 3,
+                'tags.2.name' => 'Tag4',
+                'tags.2.uid' => 4,
+                'tags.2._localizedUid' => 4,
+            ],
+            [
+                'title' => 'Post 7',
+                'uid' => 7,
+                '_localizedUid' => 7,
+                'content' => 'C - content',
+                'blog.title' => 'Blog 1',
+                'blog.uid' => 1,
+                'blog._localizedUid' => 1,
+                'author.firstname' => 'John',
+                'author.uid' => 1,
+                'author._localizedUid' => 1,
+                'secondAuthor.firstname' => 'John',
+                'secondAuthor.uid' => 1,
+                'secondAuthor._localizedUid' => 1,
+                'tags' => [],
+            ],
+            [
+                'title' => 'Post 6',
+                'uid' => 6,
+                '_localizedUid' => 6,
+                'content' => 'F - content',
+                'blog.title' => 'Blog 1',
+                'blog.uid' => 1,
+                'blog._localizedUid' => 1,
+                'author.firstname' => 'John',
+                'author.uid' => 1,
+                'author._localizedUid' => 1,
+                'secondAuthor.firstname' => 'John',
+                'secondAuthor.uid' => 1,
+                'secondAuthor._localizedUid' => 1,
+                'tags' => [],
+            ],
+            [
+                'title' => 'Post 1 - not translated',
+                'uid' => 1,
+                '_localizedUid' => 1,
+                'content' => 'G - content',
+                'blog.title' => 'Blog 1',
+                'blog.uid' => 1,
+                'blog._localizedUid' => 1,
+                'author.firstname' => 'John',
+                'author.uid' => 1,
+                'author._localizedUid' => 1,
+                'secondAuthor.firstname' => 'Never translate me henry',
+                'secondAuthor.uid' => 3,
+                'secondAuthor._localizedUid' => 3,
+                'tags.0.name' => 'Tag1',
+                'tags.0.uid' => 1,
+                'tags.0._localizedUid' => 1,
+                'tags.1.name' => 'Tag2',
+                'tags.1.uid' => 2,
+                'tags.1._localizedUid' => 2,
+                'tags.2.name' => 'Tag3',
+                'tags.2.uid' => 3,
+                'tags.2._localizedUid' => 3,
+            ],
+        ];
+        return [
+            [
+                'language' => 0,
+                'overlay' => true,
+                'expected' => $lang0Expected
+            ],
+            [
+                'language' => 0,
+                'overlay' => false,
+                'expected' => $lang0Expected
+            ],
+            [
+                'language' => 1,
+                'overlay' => true,
+                'expected' => [
+                    [
+                        'title' => 'Post 4 - DK',
+                        'uid' => 4,
+                        '_localizedUid' => 12,
+                        'content' => 'U - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+                    ],
+                    [
+                        'title' => 'Post 2 - DK',
+                        'uid' => 2,
+                        '_localizedUid' => 11,
+                        'content' => 'C - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags.0.name' => 'Tag2',
+                        'tags.0.uid' => 2,
+                        'tags.0._localizedUid' => 2,
+                        'tags.1.name' => 'Tag3',
+                        'tags.1.uid' => 3,
+                        'tags.1._localizedUid' => 3,
+                        'tags.2.name' => 'Tag4',
+                        'tags.2.uid' => 4,
+                        'tags.2._localizedUid' => 4,
+                    ],
+                    [
+                        'title' => 'Post DK only',
+                        'uid' => 15,
+                        '_localizedUid' => 15,
+                        'content' => 'B - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+                    ],
+                    [
+                        'title' => 'Post 7 - DK',
+                        'uid' => 7,
+                        '_localizedUid' => 14,
+                        'content' => 'S - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+                    ],
+                    [
+                        'title' => 'Post 5 - DK',
+                        'uid' => 5,
+                        '_localizedUid' => 13,
+                        'content' => 'A - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+                    ],
+                ],
+            ],
+            [
+                'language' => 1,
+                'overlay' => 'hideNonTranslated',
+                'expected' => [
+                    [
+                        'title' => 'Post 4 - DK',
+                        'uid' => 4,
+                        '_localizedUid' => 12,
+                        'content' => 'U - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+                    ],
+                    [
+                        'title' => 'Post 2 - DK',
+                        'uid' => 2,
+                        '_localizedUid' => 11,
+                        'content' => 'C - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags.0.name' => 'Tag2',
+                        'tags.0.uid' => 2,
+                        'tags.0._localizedUid' => 2,
+                        'tags.1.name' => 'Tag3',
+                        'tags.1.uid' => 3,
+                        'tags.1._localizedUid' => 3,
+                        'tags.2.name' => 'Tag4',
+                        'tags.2.uid' => 4,
+                        'tags.2._localizedUid' => 4,
+                    ],
+                    [
+                        'title' => 'Post DK only',
+                        'uid' => 15,
+                        '_localizedUid' => 15,
+                        'content' => 'B - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+                    ],
+                    [
+                        'title' => 'Post 7 - DK',
+                        'uid' => 7,
+                        '_localizedUid' => 14,
+                        'content' => 'S - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+
+                    ],
+                    [
+                        'title' => 'Post 5 - DK',
+                        'uid' => 5,
+                        '_localizedUid' => 13,
+                        'content' => 'A - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+                    ],
+                ],
+            ],
+            [
+                'language' => 1,
+                'overlay' => false,
+                'expected' => [
+                    [
+                        'title' => 'Post 4 - DK',
+                        'uid' => 4,
+                        '_localizedUid' => 12,
+                        'content' => 'U - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+                    ],
+                    [
+                        'title' => 'Post 2 - DK',
+                        'uid' => 2,
+                        '_localizedUid' => 11,
+                        'content' => 'C - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags.0.name' => 'Tag2',
+                        'tags.0.uid' => 2,
+                        'tags.0._localizedUid' => 2,
+                        'tags.1.name' => 'Tag3',
+                        'tags.1.uid' => 3,
+                        'tags.1._localizedUid' => 3,
+                        'tags.2.name' => 'Tag4',
+                        'tags.2.uid' => 4,
+                        'tags.2._localizedUid' => 4,
+                    ],
+                    [
+                        'title' => 'Post DK only',
+                        'uid' => 15,
+                        '_localizedUid' => 15,
+                        'content' => 'B - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+                    ],
+                    [
+                        'title' => 'Post 7 - DK',
+                        'uid' => 7,
+                        '_localizedUid' => 14,
+                        'content' => 'S - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+                    ],
+                    [
+                        'title' => 'Post 5 - DK',
+                        'uid' => 5,
+                        '_localizedUid' => 13,
+                        'content' => 'A - content',
+                        'blog.title' => 'Blog 1',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 1,
+                        'author.firstname' => 'John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 1,
+                        'secondAuthor.firstname' => 'John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 1,
+                        'tags' => [],
+                    ],
+                ],
+            ],
+        ];
+    }
+
+    /**
+     * This test check posts returned by repository, when changing language and languageOverlayMode
+     * It also sets limit, offset to validate there are no "gaps" in pagination
+     * and sorting (on a posts property)
+     *
+     * @test
+     * @dataProvider queryFirst5PostsDataProvider
+     *
+     * @param int $languageUid
+     * @param bool $overlay
+     * @param array $expected
+     */
+    public function queryFirst5Posts($languageUid, $overlay, $expected)
+    {
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageUid($languageUid);
+        $querySettings->setLanguageOverlayMode($overlay);
+
+        $query->setOrderings([
+            'content' => QueryInterface::ORDER_ASCENDING,
+            'uid' => QueryInterface::ORDER_ASCENDING
+        ]);
+        $query->setLimit(5);
+        $query->setOffset(0);
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(5, $posts);
+        $this->assertObjectsProperties($posts, $expected);
+    }
+
+    public function queryPostsByPropertyDataProvider()
+    {
+        $lang0Expected = [
+            [
+                'title' => 'Post 5',
+                'uid' => 5,
+                '_localizedUid' => 5,
+                'content' => 'Z - content',
+                'blog.title' => 'Blog 1',
+                'blog.uid' => 1,
+                'blog._localizedUid' => 1,
+                'author.firstname' => 'John',
+                'author.uid' => 1,
+                'author._localizedUid' => 1,
+                'secondAuthor.firstname' => 'John',
+                'secondAuthor.uid' => 1,
+                'secondAuthor._localizedUid' => 1,
+            ],
+            [
+                'title' => 'Post 6',
+                'uid' => 6,
+                '_localizedUid' => 6,
+                'content' => 'F - content',
+                'blog.title' => 'Blog 1',
+                'blog.uid' => 1,
+                'blog._localizedUid' => 1,
+                'author.firstname' => 'John',
+                'author.uid' => 1,
+                'author._localizedUid' => 1,
+                'secondAuthor.firstname' => 'John',
+                'secondAuthor.uid' => 1,
+                'secondAuthor._localizedUid' => 1,
+                'tags' => [],
+            ]
+        ];
+        $lang1Expected = [
+            [
+                'title' => 'Post 5 - DK',
+                'uid' => 5,
+                '_localizedUid' => 13,
+                'content' => 'A - content',
+                'blog.title' => 'Blog 1',
+                'blog.uid' => 1,
+                'blog._localizedUid' => 1,
+                'author.firstname' => 'John',
+                'author.uid' => 1,
+                'author._localizedUid' => 1,
+                'secondAuthor.firstname' => 'John',
+                'secondAuthor.uid' => 1,
+                'secondAuthor._localizedUid' => 1,
+            ],
+            [
+                'title' => 'Post DK only',
+                'uid' => 15,
+                '_localizedUid' => 15,
+                'content' => 'B - content',
+                'blog.title' => 'Blog 1',
+                'blog.uid' => 1,
+                'blog._localizedUid' => 1,
+                'author.firstname' => 'John',
+                'author.uid' => 1,
+                'author._localizedUid' => 1,
+                'secondAuthor.firstname' => 'John',
+                'secondAuthor.uid' => 1,
+                'secondAuthor._localizedUid' => 1,
+            ],
+
+        ];
+        return [
+            [
+                'language' => 0,
+                'overlay' => true,
+                'expected' => $lang0Expected
+            ],
+            [
+                'language' => 0,
+                'overlay' => 'hideNonTranslated',
+                'expected' => $lang0Expected
+            ],
+                    [
+                'language' => 0,
+                'overlay' => false,
+                'expected' => $lang0Expected
+            ],
+            [
+                'language' => 1,
+                'overlay' => true,
+                'expected' => $lang1Expected
+            ],
+            [
+                'language' => 1,
+                'overlay' => 'hideNonTranslated',
+                'expected' => $lang1Expected,
+            ],
+            [
+                'language' => 1,
+                'overlay' => false,
+                'expected' => $lang1Expected,
+            ],
+        ];
+    }
+
+    /**
+     * This test check posts returned by repository, when filtering by property
+     *
+     * "Post 6" is not translated
+     * "Post 5" is translated as "Post 5 - DK"
+     * "Post DK only" has no translation parent
+     *
+     *
+     *
+     * @test
+     * @dataProvider queryPostsByPropertyDataProvider
+     *
+     * @param int $languageUid
+     * @param bool $overlay
+     * @param array $expected
+     */
+    public function queryPostsByProperty($languageUid, $overlay, $expected)
+    {
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageUid($languageUid);
+        $querySettings->setLanguageOverlayMode($overlay);
+
+        $query->matching(
+            $query->logicalOr(
+                $query->like('title', 'Post 5%'),
+                $query->like('title', 'Post 6%'),
+                $query->like('title', 'Post DK only')
+            )
+        );
+        $query->setOrderings(['uid' => QueryInterface::ORDER_ASCENDING]);
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(count($expected), $posts);
+        $this->assertObjectsProperties($posts, $expected);
+    }
+
+    public function postsWithoutRespectingSysLanguageDataProvider()
+    {
+        $lang0Expected = [
+             [
+                 'title' => 'Blog 1',
+                 'uid' => 1,
+                 '_localizedUid' => 1,
+             ],
+             [
+                 'title' => 'Blog 1',
+                 'uid' => 1,
+                 '_localizedUid' => 1,
+             ],
+         ];
+        $lang1Expected = [
+             [
+                 'title' => 'Blog 1 DK',
+                 'uid' => 1,
+                 '_localizedUid' => 2,
+             ],
+             [
+                 'title' => 'Blog 1 DK',
+                 'uid' => 1,
+                 '_localizedUid' => 2,
+             ],
+         ];
+        return [
+             [
+                 'language' => 0,
+                 'overlay' => LanguageAspect::OVERLAYS_ON,
+                 'mode' => null,
+                 'expected' => $lang0Expected
+             ],
+             [
+                 'language' => 0,
+                 'overlay' => LanguageAspect::OVERLAYS_ON,
+                 'mode' => 'strict',
+                 'expected' => $lang0Expected
+             ],
+             [
+                 'language' => 0,
+                 'overlay' => LanguageAspect::OVERLAYS_OFF,
+                 'mode' => null,
+                 'expected' => $lang0Expected
+             ],
+             [
+                 'language' => 0,
+                 'overlay' => LanguageAspect::OVERLAYS_OFF,
+                 'mode' => 'strict',
+                 'expected' => $lang0Expected
+             ],
+             [
+                 'language' => 1,
+                 'overlay' => LanguageAspect::OVERLAYS_ON,
+                 'mode' => null,
+                 'expected' => $lang1Expected
+             ],
+             [
+                 'language' => 1,
+                 'overlay' => LanguageAspect::OVERLAYS_ON,
+                 'mode' => 'strict',
+                 'expected' => $lang1Expected
+             ],
+             [
+                 'language' => 1,
+                 'overlay' => LanguageAspect::OVERLAYS_OFF,
+                 'mode' => null,
+                 'expected' => $lang1Expected
+             ],
+             [
+                 'language' => 1,
+                 'overlay' => LanguageAspect::OVERLAYS_OFF,
+                 'mode' => 'strict',
+                 'expected' => $lang1Expected
+             ],
+         ];
+    }
+
+    /**
+     * This test demonstrates how query behaves when setRespectSysLanguage is set to false.
+     * The test now documents the wrong behaviour described in https://forge.typo3.org/issues/45873
+     * and is connected with https://forge.typo3.org/issues/59992
+     *
+     * The expected state is that when setRespectSysLanguage is false, then both: default language record,
+     * and translated language record should be returned. Now we're getting same record twice.
+     *
+     * @test
+     * @dataProvider postsWithoutRespectingSysLanguageDataProvider
+     * @param int $languageUid
+     * @param string|bool $overlay
+     * @param string $languageMode
+     * @param array $expected
+     */
+    public function postsWithoutRespectingSysLanguage($languageUid, $overlay, $languageMode, $expected)
+    {
+        $context = GeneralUtility::makeInstance(Context::class);
+        $context->setAspect('language', new LanguageAspect($languageUid, $languageUid, $overlay));
+
+        $blogRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\BlogRepository::class);
+        $query = $blogRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setLanguageMode($languageMode);
+        $querySettings->setRespectSysLanguage(false);
+
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(count($expected), $posts);
+        $this->assertObjectsProperties($posts, $expected);
+    }
+
+    /**
+     * Compares array of domain objects with array containing properties values
+     *
+     * @param array $objects
+     * @param array $expected array of expected property values [ ['property' => 'value'], ['property' => 'value2']]
+     */
+    protected function assertObjectsProperties($objects, $expected)
+    {
+        $actual = [];
+        foreach ($objects as $key => $post) {
+            $actualPost = [];
+            $propertiesToCheck = array_keys($expected[$key]);
+            foreach ($propertiesToCheck as $propertyPath) {
+                $actualPost[$propertyPath] = self::getPropertyPath($post, $propertyPath);
+            }
+            $actual[] = $actualPost;
+            $this->assertEquals($expected[$key], $actual[$key], 'Assertion of the $expected[' . $key . '] failed');
+        }
+        $this->assertEquals($expected, $actual);
+    }
+
+    /**
+     * This is a copy of the ObjectAccess::getPropertyPath, but with third argument of getPropertyInternal set as true,
+     * to access protected properties, and iterator_to_array added.
+     *
+     * @param mixed $subject Object or array to get the property path from
+     * @param string $propertyPath
+     *
+     * @return mixed Value of the property
+     */
+    protected static function getPropertyPath($subject, $propertyPath)
+    {
+        $propertyPathSegments = explode('.', $propertyPath);
+        try {
+            foreach ($propertyPathSegments as $pathSegment) {
+                $subject = ObjectAccess::getPropertyInternal($subject, $pathSegment, true);
+                if ($subject instanceof \SplObjectStorage || $subject instanceof ObjectStorage) {
+                    $subject = iterator_to_array(clone $subject, false);
+                }
+            }
+        } catch (\TYPO3\CMS\Extbase\Reflection\Exception\PropertyNotAccessibleException $error) {
+            return null;
+        }
+        return $subject;
+    }
+}
index 00837c6..ce37acf 100644 (file)
@@ -65,6 +65,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
 
         $this->objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
         $configuration = [
+            'features' => ['consistentTranslationOverlayHandling' => 1],
             'persistence' => [
                 'storagePid' => 20,
                 'classes' => [
@@ -108,6 +109,9 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
      * With overlays enabled it doesn't make a difference whether you call findByUid with translated record uid or
      * default language record uid.
      *
+     * Note that with feature flag disabled, you'll get same result (not translated record) for both calls ->findByUid(2)
+     * and ->findByUid(11)
+     *
      * @test
      */
     public function findByUidOverlayModeOnDefaultLanguage()
@@ -132,8 +136,9 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         //this is needed because of https://forge.typo3.org/issues/59992
         $this->persistenceManager->clearState();
 
+        // with feature flag disable, you'll get default language object here too (Post 2).
         $post2translated = $this->postRepository->findByUid(11);
-        $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
+        $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
             $post2translated->getTitle(),
             $post2translated->getUid(),
             $post2translated->_getProperty('_localizedUid'),
@@ -173,7 +178,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $this->persistenceManager->clearState();
 
         $post2translated = $this->postRepository->findByUid(11);
-        $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
+        $this->assertEquals(['Post 2 - DK', 11, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
             $post2translated->getTitle(),
             $post2translated->getUid(),
             $post2translated->_getProperty('_localizedUid'),
@@ -189,9 +194,6 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
     /**
      * Test in language uid:1, overlays enabled
      *
-     * With overlays enabled it doesn't make a difference whether you call findByUid with translated record uid or
-     * default language record uid. Of course we're in the &L=1 and record uid 2 has translation (uid 11).
-     *
      * @test
      */
     public function findByUidOverlayModeOnLanguage()
@@ -200,23 +202,32 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $context->setAspect('language', new LanguageAspect(1, 1, LanguageAspect::OVERLAYS_ON));
 
         $post2 = $this->postRepository->findByUid(2);
+        $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
+            $post2->getTitle(),
+            $post2->getUid(),
+            $post2->_getProperty('_localizedUid'),
+            $post2->getBlog()->getTitle(),
+            $post2->getBlog()->getUid(),
+            $post2->getBlog()->_getProperty('_localizedUid'),
+            $post2->getAuthor()->getFirstname(),
+            $post2->getAuthor()->getUid(),
+            $post2->getAuthor()->_getProperty('_localizedUid')
+        ]);
+
         //this is needed because of https://forge.typo3.org/issues/59992
         $this->persistenceManager->clearState();
         $post2translated = $this->postRepository->findByUid(11);
-
-        foreach ([$post2, $post2translated] as $post) {
-            $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
-                $post->getTitle(),
-                $post->getUid(),
-                $post->_getProperty('_localizedUid'),
-                $post->getBlog()->getTitle(),
-                $post->getBlog()->getUid(),
-                $post->getBlog()->_getProperty('_localizedUid'),
-                $post->getAuthor()->getFirstname(),
-                $post->getAuthor()->getUid(),
-                $post->getAuthor()->_getProperty('_localizedUid')
-            ]);
-        }
+        $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
+            $post2translated->getTitle(),
+            $post2translated->getUid(),
+            $post2translated->_getProperty('_localizedUid'),
+            $post2translated->getBlog()->getTitle(),
+            $post2translated->getBlog()->getUid(),
+            $post2translated->getBlog()->_getProperty('_localizedUid'),
+            $post2translated->getAuthor()->getFirstname(),
+            $post2translated->getAuthor()->getUid(),
+            $post2translated->getAuthor()->_getProperty('_localizedUid')
+        ]);
     }
 
     /**
@@ -230,7 +241,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $context->setAspect('language', new LanguageAspect(1, 1, LanguageAspect::OVERLAYS_OFF));
 
         $post2 = $this->postRepository->findByUid(2);
-        $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
+        $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
             $post2->getTitle(),
             $post2->getUid(),
             $post2->_getProperty('_localizedUid'),
@@ -246,7 +257,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $this->persistenceManager->clearState();
 
         $post2translated = $this->postRepository->findByUid(11);
-        $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
+        $this->assertEquals(['Post 2 - DK', 11, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
             $post2translated->getTitle(),
             $post2translated->getUid(),
             $post2translated->_getProperty('_localizedUid'),
@@ -278,6 +289,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $query->matching($query->equals('uid', 2));
         $post2 = $query->execute()->getFirst();
 
+        //the expected state is the same with and without feature flag
         $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
             $post2->getTitle(),
             $post2->getUid(),
@@ -300,6 +312,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $query->matching($query->equals('uid', 11));
         $post2 = $query->execute()->getFirst();
 
+        //this assertion is true for both enabled and disabled flag
         $this->assertNull($post2);
 
         //this is needed because of https://forge.typo3.org/issues/59992
@@ -312,17 +325,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $query->matching($query->equals('uid', 2));
         $post2 = $query->execute()->getFirst();
 
-        $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1', 1, 1, 'John', 1, 1], [
-            $post2->getTitle(),
-            $post2->getUid(),
-            $post2->_getProperty('_localizedUid'),
-            $post2->getBlog()->getTitle(),
-            $post2->getBlog()->getUid(),
-            $post2->getBlog()->_getProperty('_localizedUid'),
-            $post2->getAuthor()->getFirstname(),
-            $post2->getAuthor()->getUid(),
-            $post2->getAuthor()->_getProperty('_localizedUid')
-        ]);
+        $this->assertNull($post2);
 
         //this is needed because of https://forge.typo3.org/issues/59992
         $this->persistenceManager->clearState();
@@ -334,7 +337,17 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $query->matching($query->equals('uid', 11));
         $post2 = $query->execute()->getFirst();
 
-        $this->assertNull($post2);
+        $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
+            $post2->getTitle(),
+            $post2->getUid(),
+            $post2->_getProperty('_localizedUid'),
+            $post2->getBlog()->getTitle(),
+            $post2->getBlog()->getUid(),
+            $post2->getBlog()->_getProperty('_localizedUid'),
+            $post2->getAuthor()->getFirstname(),
+            $post2->getAuthor()->getUid(),
+            $post2->getAuthor()->_getProperty('_localizedUid')
+        ]);
     }
 
     /**
@@ -377,6 +390,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $query->matching($query->equals('uid', 11));
         $post2 = $query->execute()->getFirst();
 
+        //this assertion is true for both enabled and disabled flag
         $this->assertNull($post2);
 
         //this is needed because of https://forge.typo3.org/issues/59992
@@ -389,36 +403,8 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $query->matching($query->equals('uid', 2));
         $post2 = $query->execute()->getFirst();
 
-        //the john is not translated but it should, see next query in this test
-        $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1', 1, 1, 'John', 1, 1], [
-            $post2->getTitle(),
-            $post2->getUid(),
-            $post2->_getProperty('_localizedUid'),
-            $post2->getBlog()->getTitle(),
-            $post2->getBlog()->getUid(),
-            $post2->getBlog()->_getProperty('_localizedUid'),
-            $post2->getAuthor()->getFirstname(),
-            $post2->getAuthor()->getUid(),
-            $post2->getAuthor()->_getProperty('_localizedUid')
-        ]);
-
-        //this is needed because of https://forge.typo3.org/issues/59992
-        $this->persistenceManager->clearState();
-
-        $query = $this->postRepository->createQuery();
-        $querySettings = $query->getQuerySettings();
-        $querySettings->setLanguageUid(1);
-        $querySettings->setLanguageOverlayMode(false);
-        $query->matching($query->equals('uid', 11));
-        $post2 = $query->execute()->getFirst();
-
         $this->assertNull($post2);
 
-        //We're setting global context here to show that the result is different then one above.
-        //this means that language which is set in global context influences overlays of relations
-        $context = GeneralUtility::makeInstance(Context::class);
-        $context->setAspect('language', new LanguageAspect(1, 1, LanguageAspect::OVERLAYS_ON));
-
         //this is needed because of https://forge.typo3.org/issues/59992
         $this->persistenceManager->clearState();
 
@@ -429,7 +415,17 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $query->matching($query->equals('uid', 11));
         $post2 = $query->execute()->getFirst();
 
-        $this->assertNull($post2);
+        $this->assertEquals(['Post 2 - DK', 11, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
+            $post2->getTitle(),
+            $post2->getUid(),
+            $post2->_getProperty('_localizedUid'),
+            $post2->getBlog()->getTitle(),
+            $post2->getBlog()->getUid(),
+            $post2->getBlog()->_getProperty('_localizedUid'),
+            $post2->getAuthor()->getFirstname(),
+            $post2->getAuthor()->getUid(),
+            $post2->getAuthor()->_getProperty('_localizedUid')
+        ]);
     }
 
     public function queryFirst5PostsDataProvider()
@@ -440,6 +436,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                 'title' => 'Post 4',
                 'uid' => 4,
                 '_localizedUid' => 4,
+                'content' => 'A - content',
                 'blog.title' => 'Blog 1',
                 'blog.uid' => 1,
                 'blog._localizedUid' => 1,
@@ -455,6 +452,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                 'title' => 'Post 2',
                 'uid' => 2,
                 '_localizedUid' => 2,
+                'content' => 'B - content',
                 'blog.title' => 'Blog 1',
                 'blog.uid' => 1,
                 'blog._localizedUid' => 1,
@@ -478,6 +476,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                 'title' => 'Post 7',
                 'uid' => 7,
                 '_localizedUid' => 7,
+                'content' => 'C - content',
                 'blog.title' => 'Blog 1',
                 'blog.uid' => 1,
                 'blog._localizedUid' => 1,
@@ -493,6 +492,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                 'title' => 'Post 6',
                 'uid' => 6,
                 '_localizedUid' => 6,
+                'content' => 'F - content',
                 'blog.title' => 'Blog 1',
                 'blog.uid' => 1,
                 'blog._localizedUid' => 1,
@@ -508,6 +508,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                 'title' => 'Post 1 - not translated',
                 'uid' => 1,
                 '_localizedUid' => 1,
+                'content' => 'G - content',
                 'blog.title' => 'Blog 1',
                 'blog.uid' => 1,
                 'blog._localizedUid' => 1,
@@ -544,86 +545,106 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                 'overlay' => true,
                 'expected' => [
                     [
-                        'title' => 'Post 4 - DK',
-                        'uid' => 4,
-                        '_localizedUid' => 12,
-                        'blog.title' => 'Blog 1',
+                        'title' => 'Post 5 - DK',
+                        'uid' => 5,
+                        '_localizedUid' => 13,
+                        'content' => 'A - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
+                        'secondAuthor._localizedUid' => 2,
                         'tags' => [],
                     ],
                     [
                         'title' => 'Post 2 - DK',
                         'uid' => 2,
                         '_localizedUid' => 11,
-                        'blog.title' => 'Blog 1',
+                        'content' => 'C - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
-                        'tags.0.name' => 'Tag2',
-                        'tags.0.uid' => 2,
-                        'tags.0._localizedUid' => 2,
-                        'tags.1.name' => 'Tag3',
-                        'tags.1.uid' => 3,
-                        'tags.1._localizedUid' => 3,
-                        'tags.2.name' => 'Tag4',
-                        'tags.2.uid' => 4,
-                        'tags.2._localizedUid' => 4,
+                        'secondAuthor._localizedUid' => 2,
+                        'tags.0.name' => 'Tag 3 DK',
+                        'tags.0.uid' => 3,
+                        'tags.0._localizedUid' => 18,
+                        'tags.1.name' => 'Tag4',
+                        'tags.1.uid' => 4,
+                        'tags.1._localizedUid' => 4,
+                        'tags.2.name' => 'Tag5',
+                        'tags.2.uid' => 5,
+                        'tags.2._localizedUid' => 5,
+                        'tags.3.name' => 'Tag 6 DK',
+                        'tags.3.uid' => 6,
+                        'tags.3._localizedUid' => 19,
+                        'tags.4.name' => 'Tag7',
+                        'tags.4.uid' => 7,
+                        'tags.4._localizedUid' => 7,
                     ],
                     [
-                        'title' => 'Post DK only',
-                        'uid' => 15,
-                        '_localizedUid' => 15,
-                        'blog.title' => 'Blog 1',
+                        'title' => 'Post 6',
+                        'uid' => 6,
+                        '_localizedUid' => 6,
+                        'content' => 'F - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
+                        'secondAuthor._localizedUid' => 2,
                         'tags' => [],
                     ],
                     [
-                        'title' => 'Post 7 - DK',
-                        'uid' => 7,
-                        '_localizedUid' => 14,
-                        'blog.title' => 'Blog 1',
+                        'title' => 'Post 1 - not translated',
+                        'uid' => 1,
+                        '_localizedUid' => 1,
+                        'content' => 'G - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
-                        'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
-                        'tags' => [],
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Never translate me henry',
+                        'secondAuthor.uid' => 3,
+                        'secondAuthor._localizedUid' => 3,
+                        'tags.0.name' => 'Tag 1 DK',
+                        'tags.0.uid' => 1,
+                        'tags.0._localizedUid' => 16,
+                        'tags.1.name' => 'Tag 2 DK',
+                        'tags.1.uid' => 2,
+                        'tags.1._localizedUid' => 17,
+                        'tags.2.name' => 'Tag 3 DK',
+                        'tags.2.uid' => 3,
+                        'tags.2._localizedUid' => 18,
+
                     ],
                     [
-                        'title' => 'Post 5 - DK',
-                        'uid' => 5,
-                        '_localizedUid' => 13,
-                        'blog.title' => 'Blog 1',
+                        'title' => 'Post 3',
+                        'uid' => 3,
+                        '_localizedUid' => 3,
+                        'content' => 'I - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
+                        'secondAuthor._localizedUid' => 2,
                         'tags' => [],
                     ],
                 ],
@@ -631,89 +652,85 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
             [
                 'language' => 1,
                 'overlay' => 'hideNonTranslated',
+                // here we have only 4 items instead of 5 as post "Post DK only" uid:15 has no language 0 parent,
+                // so with overlay enabled it's not shown
                 'expected' => [
                     [
-                        'title' => 'Post 4 - DK',
-                        'uid' => 4,
-                        '_localizedUid' => 12,
-                        'blog.title' => 'Blog 1',
+                        'title' => 'Post 5 - DK',
+                        'uid' => 5,
+                        '_localizedUid' => 13,
+                        'content' => 'A - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
+                        'secondAuthor._localizedUid' => 2,
                         'tags' => [],
                     ],
                     [
                         'title' => 'Post 2 - DK',
                         'uid' => 2,
                         '_localizedUid' => 11,
-                        'blog.title' => 'Blog 1',
+                        'content' => 'C - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
-                        'tags.0.name' => 'Tag2',
-                        'tags.0.uid' => 2,
-                        'tags.0._localizedUid' => 2,
-                        'tags.1.name' => 'Tag3',
-                        'tags.1.uid' => 3,
-                        'tags.1._localizedUid' => 3,
-                        'tags.2.name' => 'Tag4',
-                        'tags.2.uid' => 4,
-                        'tags.2._localizedUid' => 4,
-                    ],
-                    [
-                        'title' => 'Post DK only',
-                        'uid' => 15,
-                        '_localizedUid' => 15,
-                        'blog.title' => 'Blog 1',
-                        'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
-                        'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
-                        'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
-                        'tags' => [],
+                        'secondAuthor._localizedUid' => 2,
+                        'tags.0.name' => 'Tag 3 DK',
+                        'tags.0.uid' => 3,
+                        'tags.0._localizedUid' => 18,
+                        'tags.1.name' => 'Tag4',
+                        'tags.1.uid' => 4,
+                        'tags.1._localizedUid' => 4,
+                        'tags.2.name' => 'Tag5',
+                        'tags.2.uid' => 5,
+                        'tags.2._localizedUid' => 5,
+                        'tags.3.name' => 'Tag 6 DK',
+                        'tags.3.uid' => 6,
+                        'tags.3._localizedUid' => 19,
+                        'tags.4.name' => 'Tag7',
+                        'tags.4.uid' => 7,
+                        'tags.4._localizedUid' => 7,
                     ],
                     [
                         'title' => 'Post 7 - DK',
                         'uid' => 7,
                         '_localizedUid' => 14,
-                        'blog.title' => 'Blog 1',
+                        'content' => 'S - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
+                        'secondAuthor._localizedUid' => 2,
                         'tags' => [],
-
                     ],
                     [
-                        'title' => 'Post 5 - DK',
-                        'uid' => 5,
-                        '_localizedUid' => 13,
-                        'blog.title' => 'Blog 1',
+                        'title' => 'Post 4 - DK',
+                        'uid' => 4,
+                        '_localizedUid' => 12,
+                        'content' => 'U - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
+                        'secondAuthor._localizedUid' => 2,
                         'tags' => [],
                     ],
                 ],
@@ -723,86 +740,97 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                 'overlay' => false,
                 'expected' => [
                     [
-                        'title' => 'Post 4 - DK',
-                        'uid' => 4,
-                        '_localizedUid' => 12,
-                        'blog.title' => 'Blog 1',
+                        'title' => 'Post 5 - DK',
+                        'uid' => 13,
+                        '_localizedUid' => 13,
+                        'content' => 'A - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
+                        'secondAuthor._localizedUid' => 2,
                         'tags' => [],
                     ],
                     [
-                        'title' => 'Post 2 - DK',
-                        'uid' => 2,
-                        '_localizedUid' => 11,
-                        'blog.title' => 'Blog 1',
+                        'title' => 'Post DK only',
+                        'uid' => 15,
+                        '_localizedUid' => 15,
+                        'content' => 'B - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
-                        'tags.0.name' => 'Tag2',
-                        'tags.0.uid' => 2,
-                        'tags.0._localizedUid' => 2,
-                        'tags.1.name' => 'Tag3',
-                        'tags.1.uid' => 3,
-                        'tags.1._localizedUid' => 3,
-                        'tags.2.name' => 'Tag4',
-                        'tags.2.uid' => 4,
-                        'tags.2._localizedUid' => 4,
+                        'secondAuthor._localizedUid' => 2,
+                        'tags' => [],
                     ],
                     [
-                        'title' => 'Post DK only',
-                        'uid' => 15,
-                        '_localizedUid' => 15,
-                        'blog.title' => 'Blog 1',
+                        'title' => 'Post 2 - DK',
+                        'uid' => 11,
+                        '_localizedUid' => 11,
+                        'content' => 'C - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
-                        'tags' => [],
+                        'secondAuthor._localizedUid' => 2,
+                        'tags.0.name' => 'Tag 3 DK',
+                        'tags.0.uid' => 3,
+                        'tags.0._localizedUid' => 18,
+                        'tags.1.name' => 'Tag4',
+                        'tags.1.uid' => 4,
+                        'tags.1._localizedUid' => 4,
+                        'tags.2.name' => 'Tag5',
+                        'tags.2.uid' => 5,
+                        'tags.2._localizedUid' => 5,
+                        'tags.3.name' => 'Tag 6 DK',
+                        'tags.3.uid' => 6,
+                        'tags.3._localizedUid' => 19,
+                        'tags.4.name' => 'Tag7',
+                        'tags.4.uid' => 7,
+                        'tags.4._localizedUid' => 7,
                     ],
                     [
                         'title' => 'Post 7 - DK',
-                        'uid' => 7,
+                        'uid' => 14,
                         '_localizedUid' => 14,
-                        'blog.title' => 'Blog 1',
+                        'content' => 'S - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
+                        'secondAuthor._localizedUid' => 2,
                         'tags' => [],
                     ],
                     [
-                        'title' => 'Post 5 - DK',
-                        'uid' => 5,
-                        '_localizedUid' => 13,
-                        'blog.title' => 'Blog 1',
+                        'title' => 'Post 4 - DK',
+                        'uid' => 12,
+                        '_localizedUid' => 12,
+                        'content' => 'U - content',
+                        'blog.title' => 'Blog 1 DK',
                         'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
                         'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
                         'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
+                        'secondAuthor._localizedUid' => 2,
                         'tags' => [],
                     ],
                 ],
@@ -837,7 +865,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $query->setOffset(0);
         $posts = $query->execute()->toArray();
 
-        $this->assertCount(5, $posts);
+        $this->assertCount(count($expected), $posts);
         $this->assertObjectsProperties($posts, $expected);
     }
 
@@ -848,6 +876,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                 'title' => 'Post 5',
                 'uid' => 5,
                 '_localizedUid' => 5,
+                'content' => 'Z - content',
                 'blog.title' => 'Blog 1',
                 'blog.uid' => 1,
                 'blog._localizedUid' => 1,
@@ -862,6 +891,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                 'title' => 'Post 6',
                 'uid' => 6,
                 '_localizedUid' => 6,
+                'content' => 'F - content',
                 'blog.title' => 'Blog 1',
                 'blog.uid' => 1,
                 'blog._localizedUid' => 1,
@@ -874,37 +904,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                 'tags' => [],
             ]
         ];
-        $lang1Expected = [
-                    [
-                        'title' => 'Post 5 - DK',
-                        'uid' => 5,
-                        '_localizedUid' => 13,
-                        'blog.title' => 'Blog 1',
-                        'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
-                        'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
-                        'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
-                    ],
-                    [
-                        'title' => 'Post DK only',
-                        'uid' => 15,
-                        '_localizedUid' => 15,
-                        'blog.title' => 'Blog 1',
-                        'blog.uid' => 1,
-                        'blog._localizedUid' => 1,
-                        'author.firstname' => 'John',
-                        'author.uid' => 1,
-                        'author._localizedUid' => 1,
-                        'secondAuthor.firstname' => 'John',
-                        'secondAuthor.uid' => 1,
-                        'secondAuthor._localizedUid' => 1,
-                    ],
 
-        ];
         return [
             [
                 'language' => 0,
@@ -916,7 +916,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                 'overlay' => 'hideNonTranslated',
                 'expected' => $lang0Expected
             ],
-                    [
+            [
                 'language' => 0,
                 'overlay' => false,
                 'expected' => $lang0Expected
@@ -924,17 +924,95 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
             [
                 'language' => 1,
                 'overlay' => true,
-                'expected' => $lang1Expected
+                'expected' => [
+                    [
+                        'title' => 'Post 6',
+                        'uid' => 6,
+                        '_localizedUid' => 6,
+                        'content' => 'F - content',
+                        'blog.title' => 'Blog 1 DK',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 2,
+                    ],
+                    [
+                        'title' => 'Post 5 - DK',
+                        'uid' => 5,
+                        '_localizedUid' => 13,
+                        'content' => 'A - content',
+                        'blog.title' => 'Blog 1 DK',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 2,
+                    ],
+                ],
             ],
             [
                 'language' => 1,
                 'overlay' => 'hideNonTranslated',
-                'expected' => $lang1Expected,
+                'expected' => [
+                    [
+                        'title' => 'Post 5 - DK',
+                        'uid' => 5,
+                        '_localizedUid' => 13,
+                        'content' => 'A - content',
+                        'blog.title' => 'Blog 1 DK',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 2,
+                    ],
+                ],
             ],
             [
                 'language' => 1,
                 'overlay' => false,
-                'expected' => $lang1Expected,
+                'expected' => [
+                    [
+                        'title' => 'Post 5 - DK',
+                        'uid' => 13,
+                        '_localizedUid' => 13,
+                        'content' => 'A - content',
+                        'blog.title' => 'Blog 1 DK',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 2,
+                    ],
+                    [
+                        'title' => 'Post DK only',
+                        'uid' => 15,
+                        '_localizedUid' => 15,
+                        'content' => 'B - content',
+                        'blog.title' => 'Blog 1 DK',
+                        'blog.uid' => 1,
+                        'blog._localizedUid' => 2,
+                        'author.firstname' => 'Translated John',
+                        'author.uid' => 1,
+                        'author._localizedUid' => 2,
+                        'secondAuthor.firstname' => 'Translated John',
+                        'secondAuthor.uid' => 1,
+                        'secondAuthor._localizedUid' => 2,
+                    ],
+                ],
             ],
         ];
     }
@@ -946,8 +1024,6 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
      * "Post 5" is translated as "Post 5 - DK"
      * "Post DK only" has no translation parent
      *
-     *
-     *
      * @test
      * @dataProvider queryPostsByPropertyDataProvider
      *
@@ -990,15 +1066,15 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                  '_localizedUid' => 1,
              ],
          ];
-        $lang1Expected = [
+        $mixed = [
              [
-                 'title' => 'Blog 1 DK',
+                 'title' => 'Blog 1',
                  'uid' => 1,
-                 '_localizedUid' => 2,
+                 '_localizedUid' => 1,
              ],
              [
                  'title' => 'Blog 1 DK',
-                 'uid' => 1,
+                 'uid' => 2,
                  '_localizedUid' => 2,
              ],
          ];
@@ -1019,44 +1095,44 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
                  'language' => 0,
                  'overlay' => LanguageAspect::OVERLAYS_OFF,
                  'mode' => null,
-                 'expected' => $lang0Expected
+                 'expected' => $mixed
              ],
              [
                  'language' => 0,
                  'overlay' => LanguageAspect::OVERLAYS_OFF,
                  'mode' => 'strict',
-                 'expected' => $lang0Expected
+                 'expected' => $mixed
              ],
              [
                  'language' => 1,
                  'overlay' => LanguageAspect::OVERLAYS_ON,
                  'mode' => null,
-                 'expected' => $lang1Expected
+                 'expected' => $lang0Expected
              ],
              [
                  'language' => 1,
                  'overlay' => LanguageAspect::OVERLAYS_ON,
                  'mode' => 'strict',
-                 'expected' => $lang1Expected
+                 'expected' => $lang0Expected
              ],
              [
                  'language' => 1,
                  'overlay' => LanguageAspect::OVERLAYS_OFF,
                  'mode' => null,
-                 'expected' => $lang1Expected
+                 'expected' => $mixed
              ],
              [
                  'language' => 1,
                  'overlay' => LanguageAspect::OVERLAYS_OFF,
                  'mode' => 'strict',
-                 'expected' => $lang1Expected
+                 'expected' => $mixed
              ],
          ];
     }
 
     /**
      * This test demonstrates how query behaves when setRespectSysLanguage is set to false.
-     * The test now documents the wrong behaviour described in https://forge.typo3.org/issues/45873
+     * The test now documents the WRONG behaviour described in https://forge.typo3.org/issues/45873
      * and is connected with https://forge.typo3.org/issues/59992
      *
      * The expected state is that when setRespectSysLanguage is false, then both: default language record,
@@ -1079,6 +1155,7 @@ class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\Fun
         $querySettings = $query->getQuerySettings();
         $querySettings->setLanguageMode($languageMode);
         $querySettings->setRespectSysLanguage(false);
+        $query->setOrderings(['uid' => QueryInterface::ORDER_ASCENDING]);
 
         $posts = $query->execute()->toArray();
 
diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/TranslatedContentLegacyTest.php b/typo3/sysext/extbase/Tests/Functional/Persistence/TranslatedContentLegacyTest.php
new file mode 100644 (file)
index 0000000..cf77d34
--- /dev/null
@@ -0,0 +1,1244 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Tests\Functional\DataHandling\AbstractDataHandlerActionTestCase;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
+
+/**
+ * This test is an Extbase version of the \TYPO3\CMS\Frontend\Tests\Functional\Rendering\LocalizedContentRenderingTest
+ * scenarios are the same, just a way of fetching content is different
+ *
+ * This test documents current behaviour of extbase which is inconsistent with TypoScript rendering of tt_content.
+ */
+class TranslatedContentLegacyTest extends AbstractDataHandlerActionTestCase
+{
+    const VALUE_PageId = 89;
+    const TABLE_Content = 'tt_content';
+    const TABLE_Pages = 'pages';
+
+    /**
+     * @var string
+     */
+    protected $scenarioDataSetDirectory = 'typo3/sysext/frontend/Tests/Functional/Rendering/DataSet/';
+
+    /**
+     * @var array
+     */
+    protected $testExtensionsToLoad = ['typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example'];
+
+    /**
+     * @var array
+     */
+    protected $coreExtensionsToLoad = ['extbase', 'fluid'];
+
+    /**
+     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface The object manager
+     */
+    protected $objectManager;
+
+    /**
+     * @var \ExtbaseTeam\BlogExample\Domain\Repository\TtContentRepository
+     */
+    protected $contentRepository;
+
+    /**
+     * Custom 404 handler returning valid json is registered so the $this->getFrontendResponse()
+     * does not fail on 404 pages
+     *
+     * @var array
+     */
+    protected $configurationToUseInTestInstance = [
+        'FE' => [
+            'pageNotFound_handling' => 'READFILE:typo3/sysext/frontend/Tests/Functional/Rendering/DataSet/404Template.html'
+        ]
+    ];
+
+    /**
+     * @var array
+     */
+    protected $pathsToLinkInTestInstance = [
+        'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/AdditionalConfiguration.php' => 'typo3conf/AdditionalConfiguration.php',
+        'typo3/sysext/frontend/Tests/Functional/Fixtures/Images' => 'fileadmin/user_upload'
+    ];
+
+    protected function setUp()
+    {
+        parent::setUp();
+        $this->importScenarioDataSet('LiveDefaultPages');
+        $this->importScenarioDataSet('LiveDefaultElements');
+
+        $this->backendUser->workspace = 0;
+        $this->objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
+        $this->contentRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\TtContentRepository::class);
+        $this->setUpFrontendRootPage(1, [
+            'typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Configuration/TypoScript/setup.typoscript',
+            'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/Frontend/ContentJsonRenderer.typoscript'
+
+        ]);
+        $this->addTypoScriptToTemplateRecord(
+            1,
+            'config.tx_extbase.features.consistentTranslationOverlayHandling = 0'
+        );
+    }
+
+    protected function tearDown()
+    {
+        unset($this->objectManager);
+        unset($this->contentRepository);
+        parent::tearDown();
+    }
+
+    public function defaultLanguageConfigurationDataProvider(): array
+    {
+        return [
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode =',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = content_fallback',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = content_fallback;1,0',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = strict',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = ignore',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode =',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = content_fallback',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = content_fallback;1,0',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = strict',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = ignore',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode =',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = content_fallback',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = content_fallback;1,0',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = strict',
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = ignore',
+            ],
+        ];
+    }
+
+    /**
+     * For the default language all combination of language settings should give the same result,
+     * regardless of TypoScript settings, if the requested language is "0" then no TypoScript settings apply.
+     *
+     * @test
+     * @dataProvider defaultLanguageConfigurationDataProvider
+     *
+     * @param string $typoScript
+     */
+    public function onlyEnglishContentIsRenderedForDefaultLanguage(string $typoScript)
+    {
+        $this->addTypoScriptToTemplateRecord(1, $typoScript);
+
+        $frontendResponse = $this->getFrontendResponse(self::VALUE_PageId, 0);
+        $responseSections = $frontendResponse->getResponseSections('Extbase:list()');
+        $visibleHeaders = ['Regular Element #1', 'Regular Element #2', 'Regular Element #3'];
+        $this->assertThat(
+            $responseSections,
+            $this->getRequestSectionHasRecordConstraint()
+                 ->setTable(self::TABLE_Content)
+                 ->setField('header')
+                 ->setValues(...$visibleHeaders)
+        );
+        $this->assertThat(
+            $responseSections,
+            $this->getRequestSectionDoesNotHaveRecordConstraint()
+                 ->setTable(self::TABLE_Content)
+                 ->setField('header')
+                 ->setValues(...$this->getNonVisibleHeaders($visibleHeaders))
+        );
+
+        //assert FAL relations
+        $visibleFiles = ['T3BOARD'];
+        $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
+                                                  ->setRecordIdentifier(self::TABLE_Content . ':297')->setRecordField('image')
+                                                  ->setTable('sys_file_reference')->setField('title')->setValues(...$visibleFiles));
+
+        $this->assertThat($responseSections, $this->getRequestSectionStructureDoesNotHaveRecordConstraint()
+                                                  ->setRecordIdentifier(self::TABLE_Content . ':297')->setRecordField('image')
+                                                  ->setTable('sys_file_reference')->setField('title')->setValues(...$this->getNonVisibleFileTitles($visibleFiles)));
+
+        $visibleFiles = ['Kasper2'];
+        $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
+                                                  ->setRecordIdentifier(self::TABLE_Content . ':298')->setRecordField('image')
+                                                  ->setTable('sys_file_reference')->setField('title')->setValues(...$visibleFiles));
+
+        $this->assertThat($responseSections, $this->getRequestSectionStructureDoesNotHaveRecordConstraint()
+                                                  ->setRecordIdentifier(self::TABLE_Content . ':298')->setRecordField('image')
+                                                  ->setTable('sys_file_reference')->setField('title')->setValues(...$this->getNonVisibleFileTitles($visibleFiles)));
+
+        //assert Categories
+        $visibleCategories = ['Category 1', 'Category 3 - not translated'];
+        $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
+                                                  ->setRecordIdentifier(self::TABLE_Content . ':297')->setRecordField('categories')
+                                                  ->setTable('sys_category')->setField('title')->setValues(...$visibleCategories));
+
+        $this->assertThat($responseSections, $this->getRequestSectionStructureDoesNotHaveRecordConstraint()
+                                                  ->setRecordIdentifier(self::TABLE_Content . ':297')->setRecordField('categories')
+                                                  ->setTable('sys_category')->setField('title')->setValues(...$this->getNonVisibleCategoryTitles($visibleCategories)));
+    }
+
+    /**
+     * Dutch language has pages record and some content elements are translated
+     *
+     * @return array
+     */
+    public function dutchDataProvider(): array
+    {
+        //Expected behaviour:
+        //Page is translated to Dutch, so changing sys_language_mode does NOT change the results
+        //Page title is always [DK]Page, and both sys_language_content and sys_language_uid are always 1
+        return [
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                    config.sys_language_mode =',
+                'visibleRecords' => [
+                    // todo all uids are wrong, these should be the uids of the translations
+                    // todo the images are wrong as these should be translated
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper'],
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = content_fallback',
+                'visibleRecords' => [
+                    // todo as above, the wrong elements are shown and the images are not translated
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = content_fallback;1,0',
+                'visibleRecords' => [
+                    // todo just wrong see other cases
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = strict',
+                'visibleRecords' => [
+                    // todo just wrong see other cases
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = ignore',
+                'visibleRecords' => [
+                    // todo just wrong see other cases
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            5 => [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode =',
+                'visibleRecords' => [
+                    // todo just wrong see other cases
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            // Expected behaviour:
+            // Not translated element #2 is shown because sys_language_overlay = 1 (with sys_language_overlay = hideNonTranslated, it would be hidden)
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = content_fallback',
+                'visibleRecords' => [
+                    // todo just wrong see other cases
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            // Expected behaviour:
+            // Same as config.sys_language_mode = content_fallback because we're requesting language 1, so no additional fallback possible
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = content_fallback;1,0',
+                'visibleRecords' => [
+                    // todo just wrong see other cases
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = strict',
+                'visibleRecords' => [
+                    // todo just wrong see other cases
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = ignore',
+                'visibleRecords' => [
+                    // todo just wrong see other cases
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            // Expected behaviour:
+            // Non translated default language elements are not shown, because of hideNonTranslated.
+            // Here we see it's not working - Regular Element #2 is still shown despite it's not translated to Dansk
+            // The same with relations (images and categories)
+            10 => [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode =',
+                'visibleRecords' => [
+                    // todo too many records and wrong images
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = content_fallback',
+                'visibleRecords' => [
+                    // todo same as #10
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = content_fallback;1,0',
+                'visibleRecords' => [
+                    // todo same as #10
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            // Expected behaviour: Setting sys_language_mode = strict has the same effect as previous data sets,
+            // because the translation of the page exists
+            // This is not true in Extbase unfortunately. As visible here: sys_language_mode = strict, works like overlay = 0 in TypoScript rendering.
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = strict',
+                'visibleRecords' => [
+                    // todo same as #10
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = ignore',
+                'visibleRecords' => [
+                    // todo same as #10
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider dutchDataProvider
+     *
+     * @param string $typoScript
+     * @param array $visibleRecords
+     */
+    public function renderingOfDutchLanguage(string $typoScript, array $visibleRecords)
+    {
+        $this->addTypoScriptToTemplateRecord(1, $typoScript);
+        $frontendResponse = $this->getFrontendResponse(self::VALUE_PageId, 1);
+        $responseSections = $frontendResponse->getResponseSections('Extbase:list()');
+        $visibleHeaders = array_map(function ($element) {
+            return $element['header'];
+        }, $visibleRecords);
+
+        $this->assertThat(
+            $responseSections,
+            $this->getRequestSectionHasRecordConstraint()
+                 ->setTable(self::TABLE_Content)
+                 ->setField('header')
+                 ->setValues(...$visibleHeaders)
+        );
+        $this->assertThat(
+            $responseSections,
+            $this->getRequestSectionDoesNotHaveRecordConstraint()
+                 ->setTable(self::TABLE_Content)
+                 ->setField('header')
+                 ->setValues(...$this->getNonVisibleHeaders($visibleHeaders))
+        );
+
+        foreach ($visibleRecords as $ttContentUid => $properties) {
+            $visibleFileTitles = $properties['image'];
+            if (!empty($visibleFileTitles)) {
+                $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
+                                                          ->setRecordIdentifier(self::TABLE_Content . ':' . $ttContentUid)->setRecordField('image')
+                                                          ->setTable('sys_file_reference')->setField('title')->setValues(...$visibleFileTitles));
+            }
+            $this->assertThat($responseSections, $this->getRequestSectionStructureDoesNotHaveRecordConstraint()
+                                                      ->setRecordIdentifier(self::TABLE_Content . ':' . $ttContentUid)->setRecordField('image')
+                                                      ->setTable('sys_file_reference')->setField('title')->setValues(...$this->getNonVisibleFileTitles($visibleFileTitles)));
+
+            $visibleCategoryTitles = $properties['categories'] ?? [];
+            if (!empty($visibleCategoryTitles)) {
+                $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
+                                                          ->setRecordIdentifier(self::TABLE_Content . ':' . $ttContentUid)->setRecordField('categories')
+                                                          ->setTable('sys_category')->setField('title')->setValues(...$visibleCategoryTitles));
+            }
+            $this->assertThat($responseSections, $this->getRequestSectionStructureDoesNotHaveRecordConstraint()
+                                                      ->setRecordIdentifier(self::TABLE_Content . ':' . $ttContentUid)->setRecordField('categories')
+                                                      ->setTable('sys_category')->setField('title')->setValues(...$this->getNonVisibleCategoryTitles($visibleCategoryTitles)));
+        }
+    }
+
+    public function contentOnNonTranslatedPageDataProvider(): array
+    {
+        //Expected behaviour:
+        //the page is NOT translated so setting sys_language_mode to different values changes the results
+        //- setting sys_language_mode to empty value makes TYPO3 return default language records
+        //- setting it to strict throws 404, independently from other settings
+        //Setting config.sys_language_overlay = 0
+        return [
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode =',
+                'visibleRecords' => [
+                    297 => [
+                        'header' => 'Regular Element #1',
+                        'image' => ['T3BOARD'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => 'Regular Element #3',
+                        'image' => ['Kasper'],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = content_fallback',
+                'visibleRecords' => [
+                    297 => [
+                        'header' => 'Regular Element #1',
+                        'image' => ['T3BOARD'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => 'Regular Element #3',
+                        'image' => ['Kasper'],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = content_fallback;1,0',
+                'visibleRecords' => [
+                    // todo there is something wrong with the result
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = strict',
+                'visibleRecords' => [],
+                'statusCode' => 404,
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = ignore',
+                'visibleRecords' => [
+                    // todo there is something wrong with the result
+                    297 => [
+                        'header' => '[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => 'Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    304 => [
+                        'header' => '[DE] Without default language',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            5 => [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode =',
+                'visibleRecords' => [
+                    297 => [
+                        'header' => 'Regular Element #1',
+                        'image' => ['T3BOARD'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => 'Regular Element #3',
+                        'image' => ['Kasper'],
+                    ],
+                ],
+            ],
+            //falling back to default language
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = content_fallback',
+                'visibleRecords' => [
+                    297 => [
+                        'header' => 'Regular Element #1',
+                        'image' => ['T3BOARD'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => 'Regular Element #3',
+                        'image' => ['Kasper'],
+                    ],
+                ],
+            ],
+            //Dutch elements are shown because of the content fallback 1,0 - first Dutch, then default language
+            //note that '[DK] Without default language' is NOT shown - due to overlays (fetch default language and overlay it with translations)
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = content_fallback;1,0',
+                'visibleRecords' => [
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'], // todo this is wrong and should contain both translated images for #1
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper'] // todo this is wrong and should contain both translated images for #3
+                    ],
+                    // todo those records shouldn't be here at all
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = strict',
+                'visibleRecords' => [],
+                'statusCode' => 404
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = ignore',
+                'visibleRecords' => [
+                    297 => [
+                        'header' => '[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => 'Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    304 => [
+                        'header' => '[DE] Without default language',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            10 => [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode =',
+                'visibleRecords' => [
+                    297 => [
+                        'header' => 'Regular Element #1',
+                        'image' => ['T3BOARD'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => 'Regular Element #3',
+                        'image' => ['Kasper'],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = content_fallback',
+                'visibleRecords' => [
+                    297 => [
+                        'header' => 'Regular Element #1',
+                        'image' => ['T3BOARD'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => 'Regular Element #3',
+                        'image' => ['Kasper'],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = content_fallback;1,0',
+                'visibleRecords' => [
+                    // todo this is totally different to TS rendering, we shall see default content only
+                    // because the page is not translated and we have content_fallback active
+                    297 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    303 => [
+                        'header' => '[DK] Without default language',
+                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                    ],
+                    307 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => [],
+                    ],
+                ],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = strict',
+                'visibleRecords' => [],
+                'statusCode' => 404,
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = ignore',
+                'visibleRecords' => [
+                    297 => [
+                        'header' => '[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1',
+                        'image' => ['T3BOARD'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
+                    ],
+                    299 => [
+                        'header' => 'Regular Element #3',
+                        'image' => ['Kasper']
+                    ],
+                    304 => [
+                        'header' => '[DE] Without default language',
+                        'image' => [],
+                    ],
+                ],
+            ],
+        ];
+    }
+
+    /**
+     * Page uid 89 is NOT translated to german
+     *
+     * @test
+     * @dataProvider contentOnNonTranslatedPageDataProvider
+     *
+     * @param string $typoScript
+     * @param array $visibleRecords
+     * @param int $statusCode '200' or '404'
+     */
+    public function contentOnNonTranslatedPageGerman(string $typoScript, array $visibleRecords, int $statusCode = 200)
+    {
+        $this->addTypoScriptToTemplateRecord(1, $typoScript);
+        $visibleHeaders = array_column($visibleRecords, 'header');
+
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())
+                ->withPageId(self::VALUE_PageId)
+                ->withLanguageId(2)
+        );
+
+        if ($statusCode === 200) {
+            $responseSections = ResponseContent::fromString((string)$response->getBody())
+                                               ->getSections('Extbase:list()');
+            $this->assertThat(
+                $responseSections,
+                $this->getRequestSectionHasRecordConstraint()
+                     ->setTable(self::TABLE_Content)
+                     ->setField('header')
+                     ->setValues(...$visibleHeaders)
+            );
+            $this->assertThat(
+                $responseSections,
+                $this->getRequestSectionDoesNotHaveRecordConstraint()
+                     ->setTable(self::TABLE_Content)
+                     ->setField('header')
+                     ->setValues(...$this->getNonVisibleHeaders($visibleHeaders))
+            );
+
+            foreach ($visibleRecords as $ttContentUid => $properties) {
+                $visibleFileTitles = $properties['image'];
+                if (!empty($visibleFileTitles)) {
+                    $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
+                                                              ->setRecordIdentifier(self::TABLE_Content . ':' . $ttContentUid)->setRecordField('image')
+                                                              ->setTable('sys_file_reference')->setField('title')->setValues(...$visibleFileTitles));
+                }
+                $this->assertThat($responseSections, $this->getRequestSectionStructureDoesNotHaveRecordConstraint()
+                                                          ->setRecordIdentifier(self::TABLE_Content . ':' . $ttContentUid)->setRecordField('image')
+                                                          ->setTable('sys_file_reference')->setField('title')->setValues(...$this->getNonVisibleFileTitles($visibleFileTitles)));
+            }
+        }
+
+        $this->assertEquals($statusCode, $response->getStatusCode());
+    }
+
+    public function contentOnPartiallyTranslatedPageDataProvider(): array
+    {
+
+        //Expected behaviour:
+        //Setting sys_language_mode to different values doesn't influence the result as the requested page is translated to Polish,
+        //Page title is always [PL]Page, and both sys_language_content and sys_language_uid are always 3
+        return [
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode =',
+                'visibleRecordHeaders' => ['Regular Element #3', '[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = content_fallback',
+                'visibleRecordHeaders' => ['Regular Element #3', '[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = content_fallback;1,0',
+                'visibleRecordHeaders' => ['Regular Element #3', '[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = strict',
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 0
+                                config.sys_language_mode = ignore',
+                'visibleRecordHeaders' => ['Regular Element #3', '[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
+            ],
+            5 => [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode =',
+                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1', 'Regular Element #3'],
+            ],
+            // Expected behaviour:
+            // Not translated element #2 is shown because sys_language_overlay = 1 (with sys_language_overlay = hideNonTranslated, it would be hidden)
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = content_fallback',
+                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1', 'Regular Element #3'],
+            ],
+//             Expected behaviour:
+//             Element #3 is not translated in PL and it is translated in DK. It's not shown as content_fallback is not related to single CE level
+//             but on page level - and this page is translated to Polish, so no fallback is happening
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = content_fallback;1,0',
+                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1', 'Regular Element #3'],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = strict',
+                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1'],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = 1
+                                config.sys_language_mode = ignore',
+                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1', 'Regular Element #3'],
+            ],
+            // Expected behaviour:
+            // Non translated default language elements are not shown, because of hideNonTranslated
+            10 => [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode =',
+                'visibleRecordHeaders' => ['[PL] Without default language', 'Regular Element #3', '[Translate to Polski:] Regular Element #1'],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = content_fallback',
+                'visibleRecordHeaders' => ['[PL] Without default language', 'Regular Element #3', '[Translate to Polski:] Regular Element #1'],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = content_fallback;1,0',
+                'visibleRecordHeaders' => ['[PL] Without default language', 'Regular Element #3', '[Translate to Polski:] Regular Element #1'],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = strict',
+                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1'],
+            ],
+            [
+                'typoScript' => 'config.sys_language_overlay = hideNonTranslated
+                                config.sys_language_mode = ignore',
+                'visibleRecordHeaders' => ['[PL] Without default language', 'Regular Element #3', '[Translate to Polski:] Regular Element #1'],
+            ]
+        ];
+    }
+
+    /**
+     * Page uid 89 is translated to to Polish, but not all CE are translated
+     *
+     * @test
+     * @dataProvider contentOnPartiallyTranslatedPageDataProvider
+     *
+     * @param string $typoScript
+     * @param array $visibleHeaders
+     */
+    public function contentOnPartiallyTranslatedPage(string $typoScript, array $visibleHeaders)
+    {
+        $this->addTypoScriptToTemplateRecord(1, $typoScript);
+
+        $frontendResponse = $this->getFrontendResponse(self::VALUE_PageId, 3);
+        $this->assertEquals('success', $frontendResponse->getStatus());
+        $responseSections = $frontendResponse->getResponseSections('Extbase:list()');
+
+        $this->assertThat(
+            $responseSections,
+            $this->getRequestSectionHasRecordConstraint()
+                 ->setTable(self::TABLE_Content)
+                 ->setField('header')
+                 ->setValues(...$visibleHeaders)
+        );
+        $this->assertThat(
+            $responseSections,
+            $this->getRequestSectionDoesNotHaveRecordConstraint()
+                 ->setTable(self::TABLE_Content)
+                 ->setField('header')
+                 ->setValues(...$this->getNonVisibleHeaders($visibleHeaders))
+        );
+    }
+
+    /**
+     * Helper function to ease asserting that rest of the data set is not visible
+     *
+     * @param array $visibleHeaders
+     * @return array
+     */
+    protected function getNonVisibleHeaders(array $visibleHeaders): array
+    {
+        $allElements = [
+            'Regular Element #1',
+            'Regular Element #2',
+            'Regular Element #3',
+            'Hidden Element #4',
+            '[Translate to Dansk:] Regular Element #1',
+            '[Translate to Dansk:] Regular Element #3',
+            '[DK] Without default language',
+            '[DK] UnHidden Element #4',
+            '[DE] Without default language',
+            '[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1',
+            '[Translate to Polski:] Regular Element #1',
+            '[PL] Without default language',
+            '[PL] Hidden Regular Element #2'
+        ];
+        return array_diff($allElements, $visibleHeaders);
+    }
+
+    /**
+     * Helper function to ease asserting that rest of the data set is not visible
+     *
+     * @param array $visibleTitles
+     * @return array
+     */
+    protected function getNonVisibleFileTitles(array $visibleTitles): array
+    {
+        $allElements = [
+            'T3BOARD',
+            'Kasper',
+            '[Kasper] Image translated to Dansk',
+            '[T3BOARD] Image added in Dansk (without parent)',
+            '[T3BOARD] Image added to DK element without default language',
+            '[T3BOARD] image translated to DE from DK',
+            'Kasper2'
+        ];
+        return array_diff($allElements, $visibleTitles);
+    }
+
+    /**
+     * Helper function to ease asserting that rest of the data set is not visible
+     *
+     * @param array $visibleTitles
+     * @return array
+     */
+    protected function getNonVisibleCategoryTitles(array $visibleTitles): array
+    {
+        $allElements = [
+            'Category 1',
+            '[Translate to Dansk:] Category 1',
+            'Category 3 - not translated',
+            'Category 4',
+        ];
+        return array_diff($allElements, $visibleTitles);
+    }
+}
index 24213c0..4507d67 100644 (file)
@@ -22,10 +22,13 @@ use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
 
 /**
- * This test is an Extbase version of the \TYPO3\CMS\Frontend\Tests\Functional\Rendering\LocalizedContentRenderingTest
- * scenarios are the same, just a way of fetching content is different
+ * Test case documenting an Extbase translation handling of tt_content consistent with TypoScript.
  *
- * This test documents current behaviour of extbase which is inconsistent with TypoScript rendering of tt_content.
+ * The old inconsistent handling is tested here:
+ * @see \TYPO3\CMS\Extbase\Tests\Functional\Persistence\TranslatedContentLegacyTest
+ *
+ * This test has the same scenarios as in the TypoScript version:
+ * @see \TYPO3\CMS\Frontend\Tests\Functional\Rendering\LocalizedContentRenderingTest
  */
 class TranslatedContentTest extends AbstractDataHandlerActionTestCase
 {
@@ -92,6 +95,10 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
             'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/Frontend/ContentJsonRenderer.typoscript'
 
         ]);
+        $this->addTypoScriptToTemplateRecord(
+            1,
+            'config.tx_extbase.features.consistentTranslationOverlayHandling = 1'
+        );
     }
 
     protected function tearDown()
@@ -243,28 +250,22 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 0
                                     config.sys_language_mode =',
                 'visibleRecords' => [
-                    // todo all uids are wrong, these should be the uids of the translations
-                    // todo the images are wrong as these should be translated
-                    297 => [
-                        'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
-                    ],
-                    299 => [
+                    300 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper'],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
+                    ],
+                    301 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     303 => [
                         'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                        'image' => ['[T3BOARD] Image added to DK element without default language']
                     ],
-                    307 => [
+                    308 => [
                         'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => []
                     ],
                 ],
             ],
@@ -272,27 +273,22 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 0
                                 config.sys_language_mode = content_fallback',
                 'visibleRecords' => [
-                    // todo as above, the wrong elements are shown and the images are not translated
-                    297 => [
-                        'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
-                    ],
-                    299 => [
+                    300 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
+                    ],
+                    301 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     303 => [
                         'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                        'image' => ['[T3BOARD] Image added to DK element without default language']
                     ],
-                    307 => [
+                    308 => [
                         'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => []
                     ],
                 ],
             ],
@@ -300,27 +296,22 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 0
                                 config.sys_language_mode = content_fallback;1,0',
                 'visibleRecords' => [
-                    // todo just wrong see other cases
-                    297 => [
-                        'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
-                    ],
-                    299 => [
+                    300 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
+                    ],
+                    301 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     303 => [
                         'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                        'image' => ['[T3BOARD] Image added to DK element without default language']
                     ],
-                    307 => [
+                    308 => [
                         'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => []
                     ],
                 ],
             ],
@@ -328,19 +319,22 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 0
                                 config.sys_language_mode = strict',
                 'visibleRecords' => [
-                    // todo just wrong see other cases
-                    297 => [
+                    300 => [
+                        'header' => '[Translate to Dansk:] Regular Element #3',
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
+                    ],
+                    301 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
                         'image' => [],
-                        'categories' => ['[Translate to Dansk:] Category 1'],
-                    ],
-                    299 => [
-                        'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     303 => [
                         'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                        'image' => ['[T3BOARD] Image added to DK element without default language']
+                    ],
+                    308 => [
+                        'header' => '[DK] UnHidden Element #4',
+                        'image' => []
                     ],
                 ],
             ],
@@ -348,27 +342,22 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 0
                                 config.sys_language_mode = ignore',
                 'visibleRecords' => [
-                    // todo just wrong see other cases
-                    297 => [
-                        'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
-                    ],
-                    299 => [
+                    300 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
+                    ],
+                    301 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     303 => [
                         'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                        'image' => ['[T3BOARD] Image added to DK element without default language']
                     ],
-                    307 => [
+                    308 => [
                         'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => []
                     ],
                 ],
             ],
@@ -376,12 +365,10 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 1
                                 config.sys_language_mode =',
                 'visibleRecords' => [
-                    // todo just wrong see other cases
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
-
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     298 => [
                         'header' => 'Regular Element #2',
@@ -389,15 +376,7 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
-                    ],
-                    307 => [
-                        'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
@@ -407,12 +386,10 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 1
                                 config.sys_language_mode = content_fallback',
                 'visibleRecords' => [
-                    // todo just wrong see other cases
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
-
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     298 => [
                         'header' => 'Regular Element #2',
@@ -420,15 +397,7 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
-                    ],
-                    307 => [
-                        'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
@@ -438,11 +407,10 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 1
                                 config.sys_language_mode = content_fallback;1,0',
                 'visibleRecords' => [
-                    // todo just wrong see other cases
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     298 => [
                         'header' => 'Regular Element #2',
@@ -450,15 +418,7 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
-                    ],
-                    307 => [
-                        'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
@@ -466,19 +426,18 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 1
                                 config.sys_language_mode = strict',
                 'visibleRecords' => [
-                    // todo just wrong see other cases
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
                         'image' => [],
-                        'categories' => ['[Translate to Dansk:] Category 1'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
+                    ],
+                    298 => [
+                        'header' => 'Regular Element #2',
+                        'image' => ['Kasper2'],
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
@@ -486,11 +445,10 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 1
                                 config.sys_language_mode = ignore',
                 'visibleRecords' => [
-                    // todo just wrong see other cases
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     298 => [
                         'header' => 'Regular Element #2',
@@ -498,47 +456,24 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
-                    ],
-                    307 => [
-                        'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
             // Expected behaviour:
             // Non translated default language elements are not shown, because of hideNonTranslated.
-            // Here we see it's not working - Regular Element #2 is still shown despite it's not translated to Dansk
-            // The same with relations (images and categories)
             10 => [
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode =',
                 'visibleRecords' => [
-                    // todo too many records and wrong images
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
-                    ],
-                    307 => [
-                        'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
@@ -546,27 +481,14 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode = content_fallback',
                 'visibleRecords' => [
-                    // todo same as #10
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
-                    ],
-                    307 => [
-                        'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
@@ -574,27 +496,14 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode = content_fallback;1,0',
                 'visibleRecords' => [
-                    // todo same as #10
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
-                    ],
-                    307 => [
-                        'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
@@ -605,19 +514,14 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode = strict',
                 'visibleRecords' => [
-                    // todo same as #10
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
                         'image' => [],
-                        'categories' => ['[Translate to Dansk:] Category 1'],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
@@ -625,27 +529,14 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode = ignore',
                 'visibleRecords' => [
-                    // todo same as #10
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 3 - not translated'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
+                        'image' => [],
+                        'categories' => ['[Translate to Dansk:] Category 1', 'Category 4'],
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
-                    ],
-                    307 => [
-                        'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
@@ -754,24 +645,19 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 0
                                 config.sys_language_mode = content_fallback;1,0',
                 'visibleRecords' => [
-                    // todo there is something wrong with the result
-                    297 => [
-                        'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
-                    ],
-                    299 => [
+                    300 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
+                    ],
+                    301 => [
+                        'header' => '[Translate to Dansk:] Regular Element #1',
+                        'image' => [],
                     ],
                     303 => [
                         'header' => '[DK] Without default language',
                         'image' => ['[T3BOARD] Image added to DK element without default language'],
                     ],
-                    307 => [
+                    308 => [
                         'header' => '[DK] UnHidden Element #4',
                         'image' => [],
                     ],
@@ -787,18 +673,9 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 0
                                 config.sys_language_mode = ignore',
                 'visibleRecords' => [
-                    // todo there is something wrong with the result
-                    297 => [
+                    302 => [
                         'header' => '[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
-                    ],
-                    299 => [
-                        'header' => 'Regular Element #3',
-                        'image' => ['Kasper']
+                        'image' => ['[T3BOARD] image translated to DE from DK'],
                     ],
                     304 => [
                         'header' => '[DE] Without default language',
@@ -851,7 +728,7 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'visibleRecords' => [
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'], // todo this is wrong and should contain both translated images for #1
+                        'image' => [],
                     ],
                     298 => [
                         'header' => 'Regular Element #2',
@@ -859,16 +736,7 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper'] // todo this is wrong and should contain both translated images for #3
-                    ],
-                    // todo those records shouldn't be here at all
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
-                    ],
-                    307 => [
-                        'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
@@ -884,7 +752,7 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'visibleRecords' => [
                     297 => [
                         'header' => '[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
+                        'image' => ['[T3BOARD] image translated to DE from DK'],
                     ],
                     298 => [
                         'header' => 'Regular Element #2',
@@ -892,11 +760,7 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                     ],
                     299 => [
                         'header' => 'Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    304 => [
-                        'header' => '[DE] Without default language',
-                        'image' => [],
+                        'image' => ['Kasper'],
                     ],
                 ],
             ],
@@ -940,27 +804,13 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode = content_fallback;1,0',
                 'visibleRecords' => [
-                    // todo this is totally different to TS rendering, we shall see default content only
-                    // because the page is not translated and we have content_fallback active
                     297 => [
                         'header' => '[Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
+                        'image' => [],
                     ],
                     299 => [
                         'header' => '[Translate to Dansk:] Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    303 => [
-                        'header' => '[DK] Without default language',
-                        'image' => ['[T3BOARD] Image added to DK element without default language'],
-                    ],
-                    307 => [
-                        'header' => '[DK] UnHidden Element #4',
-                        'image' => [],
+                        'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
                     ],
                 ],
             ],
@@ -976,19 +826,7 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'visibleRecords' => [
                     297 => [
                         'header' => '[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1',
-                        'image' => ['T3BOARD'],
-                    ],
-                    298 => [
-                        'header' => 'Regular Element #2',
-                        'image' => ['Kasper2'],
-                    ],
-                    299 => [
-                        'header' => 'Regular Element #3',
-                        'image' => ['Kasper']
-                    ],
-                    304 => [
-                        'header' => '[DE] Without default language',
-                        'image' => [],
+                        'image' => ['[T3BOARD] image translated to DE from DK'],
                     ],
                 ],
             ],
@@ -1060,17 +898,17 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
             [
                 'typoScript' => 'config.sys_language_overlay = 0
                                 config.sys_language_mode =',
-                'visibleRecordHeaders' => ['Regular Element #3', '[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = 0
                                 config.sys_language_mode = content_fallback',
-                'visibleRecordHeaders' => ['Regular Element #3', '[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = 0
                                 config.sys_language_mode = content_fallback;1,0',
-                'visibleRecordHeaders' => ['Regular Element #3', '[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = 0
@@ -1080,64 +918,64 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
             [
                 'typoScript' => 'config.sys_language_overlay = 0
                                 config.sys_language_mode = ignore',
-                'visibleRecordHeaders' => ['Regular Element #3', '[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
             ],
             5 => [
                 'typoScript' => 'config.sys_language_overlay = 1
                                 config.sys_language_mode =',
-                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1', 'Regular Element #3'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', 'Regular Element #2', 'Regular Element #3'],
             ],
             // Expected behaviour:
             // Not translated element #2 is shown because sys_language_overlay = 1 (with sys_language_overlay = hideNonTranslated, it would be hidden)
             [
                 'typoScript' => 'config.sys_language_overlay = 1
                                 config.sys_language_mode = content_fallback',
-                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1', 'Regular Element #3'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', 'Regular Element #2', 'Regular Element #3'],
             ],
-//             Expected behaviour:
-//             Element #3 is not translated in PL and it is translated in DK. It's not shown as content_fallback is not related to single CE level
-//             but on page level - and this page is translated to Polish, so no fallback is happening
+            // Expected behaviour:
+            // Element #3 is not translated in PL and it is translated in DK. It's not shown as content_fallback is not related to single CE level
+            // but on page level - and this page is translated to Polish, so no fallback is happening
             [
                 'typoScript' => 'config.sys_language_overlay = 1
                                 config.sys_language_mode = content_fallback;1,0',
-                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1', 'Regular Element #3'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', 'Regular Element #2', 'Regular Element #3'],
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = 1
                                 config.sys_language_mode = strict',
-                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', 'Regular Element #2', 'Regular Element #3'],
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = 1
                                 config.sys_language_mode = ignore',
-                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1', 'Regular Element #3'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', 'Regular Element #2', 'Regular Element #3'],
             ],
             // Expected behaviour:
             // Non translated default language elements are not shown, because of hideNonTranslated
             10 => [
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode =',
-                'visibleRecordHeaders' => ['[PL] Without default language', 'Regular Element #3', '[Translate to Polski:] Regular Element #1'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1'],
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode = content_fallback',
-                'visibleRecordHeaders' => ['[PL] Without default language', 'Regular Element #3', '[Translate to Polski:] Regular Element #1'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1'],
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode = content_fallback;1,0',
-                'visibleRecordHeaders' => ['[PL] Without default language', 'Regular Element #3', '[Translate to Polski:] Regular Element #1'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1'],
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode = strict',
-                'visibleRecordHeaders' => ['[PL] Without default language', '[Translate to Polski:] Regular Element #1'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1'],
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode = ignore',
-                'visibleRecordHeaders' => ['[PL] Without default language', 'Regular Element #3', '[Translate to Polski:] Regular Element #1'],
+                'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1'],
             ]
         ];
     }
diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/TranslationLegacyTest.php b/typo3/sysext/extbase/Tests/Functional/Persistence/TranslationLegacyTest.php
new file mode 100644 (file)
index 0000000..b99c059
--- /dev/null
@@ -0,0 +1,418 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use ExtbaseTeam\BlogExample\Domain\Model\Post;
+use PHPUnit\Framework\MockObject\MockObject;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Persistence\QueryInterface;
+use TYPO3\CMS\Extbase\Service\EnvironmentService;
+use TYPO3\CMS\Frontend\Page\PageRepository;
+
+class TranslationLegacyTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
+{
+    /**
+     * @var array
+     */
+    protected $testExtensionsToLoad = ['typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example'];
+
+    /**
+     * @var array
+     */
+    protected $coreExtensionsToLoad = ['extbase', 'fluid'];
+
+    /**
+     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface The object manager
+     */
+    protected $objectManager;
+
+    /**
+     * @var \TYPO3\CMS\Extbase\Persistence\Repository
+     */
+    protected $postRepository;
+
+    /**
+     * Sets up this test suite.
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+        /*
+         * Posts Dataset for the tests:
+         *
+         * Post1
+         *   -> EN: Post1
+         *   -> GR: Post1
+         * Post2
+         *   -> EN: Post2
+         * Post3
+         * Post10 [hidden]
+         *   -> GR: Post10 [hidden]
+         */
+        $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/pages.xml');
+        $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/blogs.xml');
+        $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/translated-posts.xml');
+        $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/post-tag-mm.xml');
+        $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags.xml');
+
+        $this->setUpBasicFrontendEnvironment();
+
+        $this->objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
+        $configuration = [
+            'features' => ['consistentTranslationOverlayHandling' => 0]
+        ];
+        $configurationManager = $this->objectManager->get(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::class);
+        $configurationManager->setConfiguration($configuration);
+
+        $this->postRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PostRepository::class);
+    }
+
+    /**
+     * Minimal frontend environment to satisfy Extbase Typo3DbBackend
+     */
+    protected function setUpBasicFrontendEnvironment()
+    {
+        // in v9 overlay and language mode has different default values, so we need to set them here explicitely
+        // to match v8 behaviour
+        $context = GeneralUtility::makeInstance(Context::class);
+        $context->setAspect('language', new LanguageAspect(0, 0, LanguageAspect::OVERLAYS_OFF, ['off']));
+
+        /** @var MockObject|EnvironmentService $environmentServiceMock */
+        $environmentServiceMock = $this->createMock(EnvironmentService::class);
+        $environmentServiceMock
+            ->expects($this->atLeast(1))
+            ->method('isEnvironmentInFrontendMode')
+            ->willReturn(true);
+        GeneralUtility::setSingletonInstance(EnvironmentService::class, $environmentServiceMock);
+
+        $pageRepositoryFixture = new PageRepository();
+        $frontendControllerMock = $this->createMock(\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class);
+        $frontendControllerMock->sys_page = $pageRepositoryFixture;
+        $GLOBALS['TSFE'] = $frontendControllerMock;
+    }
+
+    /**
+     * Tests if repository returns correct number of posts in the default language
+     *
+     * @test
+     */
+    public function countReturnsCorrectNumberOfPosts()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(0);
+
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $postCount = $query->execute()->count();
+        $this->assertSame(3, $postCount);
+    }
+
+    /**
+     * This test shows the difference between old and new rendering
+     * languageMode is now ignored, overlay is `false`, so this test is the same
+     * as countReturnsCorrectNumberOfPostsInEnglishLanguage
+     *
+     * @test
+     */
+    public function countReturnsCorrectNumberOfPostsInEnglishLanguageForStrictMode()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(1);
+        $querySettings->setLanguageMode('strict');
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+
+        $postCount = $query->execute()->count();
+        $this->assertSame(2, $postCount);
+    }
+
+    /**
+     * Test for fetching records with disabled overlay
+     *
+     * @test
+     */
+    public function countReturnsCorrectNumberOfPostsInEnglishLanguage()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(1);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $postCount = $query->execute()->count();
+        $this->assertSame(3, $postCount);
+    }
+
+    /**
+     * @test
+     */
+    public function countReturnsCorrectNumberOfPostsInGreekLanguage()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(2);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $postCount = $query->execute()->count();
+
+        $this->assertSame(3, $postCount);
+    }
+
+    /**
+     * @test
+     */
+    public function fetchingPostsReturnsEnglishPostsWithFallback()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(1);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
+
+        /** @var Post[]|array $posts */
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(3, $posts);
+        $this->assertSame('A EN:Post2', $posts[0]->getTitle());
+        $this->assertSame('B EN:Post1', $posts[1]->getTitle());
+        $this->assertSame('Post3', $posts[2]->getTitle());
+    }
+
+    /**
+     * This tests shows overlays in action
+     *
+     * @test
+     */
+    public function fetchingPostsReturnsGreekPostsWithFallback()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(2);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
+
+        /** @var Post[]|array $posts */
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(3, $posts);
+        $this->assertSame('GR:Post1', $posts[0]->getTitle());
+        $this->assertSame('Post2', $posts[1]->getTitle());
+        $this->assertSame('Post3', $posts[2]->getTitle());
+    }
+
+    /**
+     * This tests shows overlay 'hideNonTranslated' in action
+     *
+     * @test
+     */
+    public function fetchingPostsReturnsGreekPostsWithHideNonTranslated()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(2);
+        $querySettings->setLanguageOverlayMode('hideNonTranslated');
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
+
+        /** @var Post[]|array $posts */
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(3, $posts);
+        $this->assertSame('GR:Post1', $posts[0]->getTitle());
+        $this->assertSame('Post2', $posts[1]->getTitle());
+        $this->assertSame('Post3', $posts[2]->getTitle());
+    }
+
+    /**
+     * @test
+     */
+    public function orderingByTitleRespectsEnglishTitles()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(1);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
+
+        /** @var Post[]|array $posts */
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(3, $posts);
+        $this->assertSame('A EN:Post2', $posts[0]->getTitle());
+        $this->assertSame('B EN:Post1', $posts[1]->getTitle());
+        $this->assertSame('Post3', $posts[2]->getTitle());
+    }
+
+    /**
+     * This test checks whether setIgnoreEnableFields(true) affects the query
+     * It's expected that when ignoring enable fields, the hidden record is also returned.
+     * This is related to https://forge.typo3.org/issues/68672
+     *
+     * @test
+     */
+    public function fetchingHiddenPostsWithIgnoreEnableField()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setIgnoreEnableFields(true);
+        $querySettings->setLanguageUid(0);
+        //we need it to have stable results on pgsql
+        $query->setOrderings(['uid' => QueryInterface::ORDER_ASCENDING]);
+
+        /** @var Post[] $posts */
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(4, $posts);
+        $this->assertSame('Post10', $posts[3]->getTitle());
+    }
+
+    /**
+     * This test checks whether setIgnoreEnableFields(true) affects translated record too.
+     * It's expected that when ignoring enable fields, the hidden translated record is shown.
+     * This is related to https://forge.typo3.org/issues/68672
+     *
+     * This tests documents current, buggy behaviour!
+     *
+     * @test
+     */
+    public function fetchingHiddenPostsReturnsHiddenOverlay()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setIgnoreEnableFields(true);
+        $querySettings->setLanguageUid(2);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        //we need it to have stable results on pgsql
+        $query->setOrderings(['uid' => QueryInterface::ORDER_ASCENDING]);
+
+        /** @var Post[] $posts */
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(4, $posts);
+        //This assertion is wrong, once the issue https://forge.typo3.org/issues/68672
+        //is fixed, the following assertion should be used.
+        $this->assertSame('Post10', $posts[3]->getTitle());
+        //$this->assertSame('GR:Post10', $posts[3]->getTitle());
+    }
+
+    /**
+     * Test checking if we can query db records by translated fields
+     *
+     * @test
+     */
+    public function fetchingTranslatedPostByTitle()
+    {
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(2);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
+        $query->matching($query->equals('title', 'GR:Post1'));
+        /** @var Post[]|array $posts */
+        $posts = $query->execute()->toArray();
+        $this->assertCount(1, $posts);
+        $this->assertSame('GR:Post1', $posts[0]->getTitle());
+    }
+
+    /**
+     * @test
+     */
+    public function fetchingPostByTagName()
+    {
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setRespectStoragePage(false);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(0);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
+        $query->matching($query->equals('tags.name', 'Tag1'));
+        /** @var Post[]|array $posts */
+        $posts = $query->execute()->toArray();
+        $this->assertCount(3, $posts);
+        $this->assertSame('Post1', $posts[0]->getTitle());
+    }
+
+    /**
+     * @test
+     */
+    public function fetchingTranslatedPostByTagName()
+    {
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setRespectStoragePage(false);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(1);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
+        $query->matching($query->equals('tags.name', 'Tag1'));
+        /** @var Post[]|array $posts */
+        $posts = $query->execute()->toArray();
+        $this->assertCount(3, $posts);
+        $this->assertSame('A EN:Post2', $posts[0]->getTitle());
+        $this->assertSame('Post3', $posts[2]->getTitle());
+    }
+}
index cab6b47..26dfa88 100644 (file)
@@ -1,4 +1,5 @@
 <?php
+declare(strict_types = 1);
 namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
 
 /*
@@ -15,10 +16,12 @@ namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
  */
 
 use ExtbaseTeam\BlogExample\Domain\Model\Post;
+use PHPUnit\Framework\MockObject\MockObject;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
+use TYPO3\CMS\Extbase\Service\EnvironmentService;
 use TYPO3\CMS\Frontend\Page\PageRepository;
 
 class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
@@ -64,10 +67,17 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
         $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/pages.xml');
         $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/blogs.xml');
         $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/translated-posts.xml');
+        $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/post-tag-mm.xml');
+        $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags.xml');
 
         $this->setUpBasicFrontendEnvironment();
 
         $this->objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
+        $configuration = [
+            'features' => ['consistentTranslationOverlayHandling' => 1]
+        ];
+        $configurationManager = $this->objectManager->get(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::class);
+        $configurationManager->setConfiguration($configuration);
         $this->postRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PostRepository::class);
     }
 
@@ -81,12 +91,13 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
         $context = GeneralUtility::makeInstance(Context::class);
         $context->setAspect('language', new LanguageAspect(0, 0, LanguageAspect::OVERLAYS_OFF, ['off']));
 
-        $environmentServiceMock = $this->createMock(\TYPO3\CMS\Extbase\Service\EnvironmentService::class);
+        /** @var MockObject|EnvironmentService $environmentServiceMock */
+        $environmentServiceMock = $this->createMock(EnvironmentService::class);
         $environmentServiceMock
             ->expects($this->atLeast(1))
             ->method('isEnvironmentInFrontendMode')
             ->willReturn(true);
-        GeneralUtility::setSingletonInstance(\TYPO3\CMS\Extbase\Service\EnvironmentService::class, $environmentServiceMock);
+        GeneralUtility::setSingletonInstance(EnvironmentService::class, $environmentServiceMock);
 
         $pageRepositoryFixture = new PageRepository();
         $frontendControllerMock = $this->createMock(\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class);
@@ -95,6 +106,8 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
     }
 
     /**
+     * Tests if repository returns correct number of posts in the default language
+     *
      * @test
      */
     public function countReturnsCorrectNumberOfPosts()
@@ -106,11 +119,18 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
         $querySettings->setRespectSysLanguage(true);
         $querySettings->setLanguageUid(0);
 
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
         $postCount = $query->execute()->count();
         $this->assertSame(3, $postCount);
     }
 
     /**
+     * This test shows the difference between old and new rendering
+     * languageMode is now ignored, overlay is `false`, so this test is the same
+     * as countReturnsCorrectNumberOfPostsInEnglishLanguage
+     *
      * @test
      */
     public function countReturnsCorrectNumberOfPostsInEnglishLanguageForStrictMode()
@@ -122,12 +142,15 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
         $querySettings->setRespectSysLanguage(true);
         $querySettings->setLanguageUid(1);
         $querySettings->setLanguageMode('strict');
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
 
         $postCount = $query->execute()->count();
         $this->assertSame(2, $postCount);
     }
 
     /**
+     * Test for fetching records with disabled overlay
+     *
      * @test
      */
     public function countReturnsCorrectNumberOfPostsInEnglishLanguage()
@@ -139,8 +162,11 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
         $querySettings->setRespectSysLanguage(true);
         $querySettings->setLanguageUid(1);
 
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
         $postCount = $query->execute()->count();
-        $this->assertSame(3, $postCount);
+        $this->assertSame(2, $postCount);
     }
 
     /**
@@ -154,9 +180,12 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
         $querySettings->setStoragePageIds([1]);
         $querySettings->setRespectSysLanguage(true);
         $querySettings->setLanguageUid(2);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
         $postCount = $query->execute()->count();
 
-        $this->assertSame(3, $postCount);
+        $this->assertSame(1, $postCount);
     }
 
     /**
@@ -170,19 +199,22 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
         $querySettings->setStoragePageIds([1]);
         $querySettings->setRespectSysLanguage(true);
         $querySettings->setLanguageUid(1);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
 
         $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
 
-        /** @var Post[] $posts */
+        /** @var Post[]|array $posts */
         $posts = $query->execute()->toArray();
 
-        $this->assertCount(3, $posts);
+        $this->assertCount(2, $posts);
         $this->assertSame('A EN:Post2', $posts[0]->getTitle());
         $this->assertSame('B EN:Post1', $posts[1]->getTitle());
-        $this->assertSame('Post3', $posts[2]->getTitle());
     }
 
     /**
+     * This tests shows overlays in action
+     *
      * @test
      */
     public function fetchingPostsReturnsGreekPostsWithFallback()
@@ -193,16 +225,41 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
         $querySettings->setStoragePageIds([1]);
         $querySettings->setRespectSysLanguage(true);
         $querySettings->setLanguageUid(2);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
 
         $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
 
-        /** @var Post[] $posts */
+        /** @var Post[]|array $posts */
         $posts = $query->execute()->toArray();
 
-        $this->assertCount(3, $posts);
+        $this->assertCount(1, $posts);
+        $this->assertSame('GR:Post1', $posts[0]->getTitle());
+    }
+
+    /**
+     * This tests shows overlay 'hideNonTranslated' in action
+     *
+     * @test
+     */
+    public function fetchingPostsReturnsGreekPostsWithHideNonTranslated()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(2);
+        $querySettings->setLanguageOverlayMode('hideNonTranslated');
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
+
+        /** @var Post[]|array $posts */
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(1, $posts);
         $this->assertSame('GR:Post1', $posts[0]->getTitle());
-        $this->assertSame('Post2', $posts[1]->getTitle());
-        $this->assertSame('Post3', $posts[2]->getTitle());
     }
 
     /**
@@ -216,16 +273,47 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
         $querySettings->setStoragePageIds([1]);
         $querySettings->setRespectSysLanguage(true);
         $querySettings->setLanguageUid(1);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
 
         $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
 
-        /** @var Post[] $posts */
+        /** @var Post[]|array $posts */
         $posts = $query->execute()->toArray();
 
-        $this->assertCount(3, $posts);
+        $this->assertCount(2, $posts);
         $this->assertSame('A EN:Post2', $posts[0]->getTitle());
         $this->assertSame('B EN:Post1', $posts[1]->getTitle());
-        $this->assertSame('Post3', $posts[2]->getTitle());
+    }
+
+    /**
+     * This test shows that ordering by blog title works
+     * however the default language blog title is used
+     *
+     * @test
+     */
+    public function orderingByBlogTitle()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(1);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $query->setOrderings([
+            'blog.title' => QueryInterface::ORDER_ASCENDING,
+            'uid' => QueryInterface::ORDER_ASCENDING
+        ]);
+
+        /** @var Post[]|array $posts */
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(2, $posts);
+        $this->assertSame('B EN:Post1', $posts[0]->getTitle());
+        $this->assertSame('A EN:Post2', $posts[1]->getTitle());
     }
 
     /**
@@ -257,13 +345,41 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
     /**
      * This test checks whether setIgnoreEnableFields(true) affects translated record too.
      * It's expected that when ignoring enable fields, the hidden translated record is shown.
+     *
+     * @test
+     */
+    public function fetchingHiddenPostsReturnsHiddenOverlay()
+    {
+        $query = $this->postRepository->createQuery();
+
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setIgnoreEnableFields(true);
+        $querySettings->setLanguageUid(2);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        //we need it to have stable results on pgsql
+        $query->setOrderings(['uid' => QueryInterface::ORDER_ASCENDING]);
+
+        /** @var Post[] $posts */
+        $posts = $query->execute()->toArray();
+
+        $this->assertCount(2, $posts);
+
+        $this->assertSame('GR:Post1', $posts[0]->getTitle());
+        $this->assertSame('GR:Post10', $posts[1]->getTitle());
+    }
+
+    /**
+     * This test checks whether setIgnoreEnableFields(true) affects translated record too.
+     * It's expected that when ignoring enable fields, the hidden translated record is shown.
      * This is related to https://forge.typo3.org/issues/68672
      *
      * This tests documents current, buggy behaviour!
      *
      * @test
      */
-    public function fetchingHiddenPostsReturnsHiddenOverlay()
+    public function fetchingHiddenPostsReturnsHiddenOverlayOverlayEnabled()
     {
         $query = $this->postRepository->createQuery();
 
@@ -272,6 +388,7 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
         $querySettings->setRespectSysLanguage(true);
         $querySettings->setIgnoreEnableFields(true);
         $querySettings->setLanguageUid(2);
+        $querySettings->setLanguageOverlayMode(true);
         //we need it to have stable results on pgsql
         $query->setOrderings(['uid' => QueryInterface::ORDER_ASCENDING]);
 
@@ -279,9 +396,103 @@ class TranslationTest extends \TYPO3\TestingFramework\Core\Functional\Functional
         $posts = $query->execute()->toArray();
 
         $this->assertCount(4, $posts);
-        //This assertion is wrong, once the issue https://forge.typo3.org/issues/68672
-        //is fixed, the following assertion should be used.
+
+        $this->assertSame('GR:Post1', $posts[0]->getTitle());
+        $this->assertSame('Post2', $posts[1]->getTitle());
+        $this->assertSame('Post3', $posts[2]->getTitle());
+        // once the issue is fixed this assertion should be GR:Post10
         $this->assertSame('Post10', $posts[3]->getTitle());
-        //$this->assertSame('GR:Post10', $posts[3]->getTitle());
+    }
+
+    /**
+     * Test checking if we can query db records by translated fields
+     *
+     * @test
+     */
+    public function fetchingTranslatedPostByTitle()
+    {
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(2);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
+        $query->matching($query->equals('title', 'GR:Post1'));
+        /** @var Post[]|array $posts */
+        $posts = $query->execute()->toArray();
+        $this->assertCount(1, $posts);
+        $this->assertSame('GR:Post1', $posts[0]->getTitle());
+    }
+
+    /**
+     * Test checking if we can query db records by value of the child object
+     * Note that only child objects from language 0 are taken into account
+     *
+     * @test
+     */
+    public function fetchingTranslatedPostByBlogTitle()
+    {
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setStoragePageIds([1]);
+        $querySettings->setRespectSysLanguage(true);
+        $querySettings->setLanguageUid(2);
+        $this->assertFalse($querySettings->getLanguageOverlayMode());
+        $this->assertNull($querySettings->getLanguageMode());
+
+        $query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
+        $query->matching($query->equals('blog.title', 'Blog1'));
+        /** @var Post[]|array $posts */
+        $posts = $query->execute()->toArray();
+        $this->assertCount(1, $posts);
+        $this->assertSame('GR:Post1', $posts[0]->getTitle());
+    }
+
+    /**
+     * @test
+     */
+    public function fetchingPostByTagName()
+    {
+        $query = $this->postRepository->createQuery();
+        $querySettings = $query->getQuerySettings();
+        $querySettings->setRespectStoragePage(false);
+        $querySettings->setRespectSysLanguag