[BUGFIX] allowLanguageSynchronization fails for 2nd level translations 44/51644/5
authorOliver Hader <oliver@typo3.org>
Sun, 12 Feb 2017 17:04:07 +0000 (18:04 +0100)
committerGeorg Ringer <georg.ringer@gmail.com>
Tue, 14 Feb 2017 04:44:58 +0000 (05:44 +0100)
2nd level translations are not considered yet with the feature to
synchronize languages either from parent or source language records.
This change modifies the creation of the language state as well as
switches "custom" states to "source" states of a new second level
translation is being created.

Resolves: #79755
Releases: master
Change-Id: I860d87a1b305966a9caa5cbd17bc5bf7229f5704
Reviewed-on: https://review.typo3.org/51644
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
typo3/sysext/backend/Classes/Form/FieldWizard/LocalizationStateSelector.php
typo3/sysext/core/Classes/DataHandling/Localization/DataMapItem.php
typo3/sysext/core/Classes/DataHandling/Localization/DataMapProcessor.php
typo3/sysext/core/Classes/DataHandling/Localization/State.php
typo3/sysext/core/Tests/Functional/DataHandling/Regular/AbstractActionTestCase.php
typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/ActionTest.php
typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/localizeContentFromNonDefaultLanguageWSynchronizationDefault.csv [new file with mode: 0644]

index a7da8bd..8dd0f62 100644 (file)
@@ -53,7 +53,7 @@ class LocalizationStateSelector extends AbstractNode
 
         $sourceLanguageTitle = '';
         $fieldValueInParentRow = '';
-        $fieldValueInSourceRow = '';
+        $fieldValueInSourceRow = null;
         if ($l10nParentFieldName && $this->data['databaseRow'][$l10nParentFieldName] > 0) {
             if ($l10nSourceFieldName && $this->data['databaseRow'][$l10nSourceFieldName] > 0) {
                 $languageField = $this->data['processedTca']['ctrl']['languageField'] ?? null;
@@ -111,7 +111,7 @@ class LocalizationStateSelector extends AbstractNode
         $html[] =           $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:localizationStateSelector.defaultLanguageValue');
         $html[] =       '</label>';
         $html[] =   '</div>';
-        if ($fieldValueInSourceRow) {
+        if ($fieldValueInSourceRow !== null) {
             $html[] = '<div class="radio radio-inline">';
             $html[] =   '<label>';
             $html[] =       '<input';
index 161aac6..91f9305 100644 (file)
@@ -312,13 +312,7 @@ class DataMapItem
     public function getState(): State
     {
         if ($this->state === null && !$this->isParentType()) {
-            $this->state = State::fromJSON(
-                $this->tableName,
-                $this->persistedValues['l10n_state'] ?? null
-            );
-            $this->state->update(
-                $this->suggestedValues['l10n_state'] ?? []
-            );
+            $this->state = $this->buildState();
         }
         return $this->state;
     }
@@ -427,4 +421,38 @@ class DataMapItem
         $scopes[] = static::SCOPE_EXCLUDE;
         return $scopes;
     }
+
+    /**
+     * @return null|State
+     */
+    protected function buildState()
+    {
+        // build from persisted states
+        if (!$this->isNew()) {
+            $state = State::fromJSON(
+                $this->tableName,
+                $this->persistedValues['l10n_state'] ?? null
+            );
+        // use provided states for a new and copied element
+        } elseif (is_string($this->suggestedValues['l10n_state'] ?? null)) {
+            $state = State::fromJSON(
+                $this->tableName,
+                $this->suggestedValues['l10n_state']
+            );
+        // provide the default states
+        } else {
+            $state = State::create($this->tableName);
+        }
+        // switch "custom" to "source" state for 2nd level translations
+        if ($this->isNew() && $this->isGrandChildType()) {
+            $state->updateStates(State::STATE_CUSTOM, State::STATE_SOURCE);
+        }
+        // apply any provided updates to the states
+        if (is_array($this->suggestedValues['l10n_state'] ?? null)) {
+            $state->update(
+                $this->suggestedValues['l10n_state'] ?? []
+            );
+        }
+        return $state;
+    }
 }
index e720384..999e39b 100644 (file)
@@ -92,8 +92,10 @@ class DataMapProcessor
         foreach ($this->dataMap as $tableName => $idValues) {
             $this->collectItems($tableName, $idValues);
         }
-        $this->sanitize();
-        $this->enrich();
+        if (!empty($this->items)) {
+            $this->sanitize();
+            $this->enrich();
+        }
         return $this->dataMap;
     }
 
@@ -488,10 +490,10 @@ class DataMapProcessor
             $localDataHandler->process_cmdmap();
             // update copied or localized ids
             foreach ($createAncestorIds as $createAncestorId) {
-                if (empty($localDataHandler->copyMappingArray[$foreignTableName][$createAncestorId])) {
+                if (empty($localDataHandler->copyMappingArray_merged[$foreignTableName][$createAncestorId])) {
                     throw new \RuntimeException('Child record was not processed', 1486233164);
                 }
-                $newLocalizationId = $localDataHandler->copyMappingArray[$foreignTableName][$createAncestorId];
+                $newLocalizationId = $localDataHandler->copyMappingArray_merged[$foreignTableName][$createAncestorId];
                 $newLocalizationId = $localDataHandler->getAutoVersionId($foreignTableName, $newLocalizationId) ?? $newLocalizationId;
                 $desiredLocalizationIdMap[$createAncestorId] = $newLocalizationId;
             }
@@ -597,6 +599,7 @@ class DataMapProcessor
                 $dependencyMap[$dependentItem->getParent()][State::STATE_PARENT][] = $dependentItem;
             }
             if ($dependentItem->isGrandChildType()) {
+                $dependencyMap[$dependentItem->getParent()][State::STATE_PARENT][] = $dependentItem;
                 $dependencyMap[$dependentItem->getSource()][State::STATE_SOURCE][] = $dependentItem;
             }
         }
@@ -665,20 +668,21 @@ class DataMapProcessor
             ->removeAll()
             ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
 
+        $zeroParameter = $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT);
+        $idsParameter = $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY);
+
         $predicates = [
             $queryBuilder->expr()->in(
                 $fieldNames['parent'],
-                $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
+                $idsParameter
             )
         ];
 
         if (!empty($fieldNames['source'])) {
-            $predicates = [
-                $queryBuilder->expr()->in(
-                    $fieldNames['source'],
-                    $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
-                )
-            ];
+            $predicates[] = $queryBuilder->expr()->in(
+                $fieldNames['source'],
+                $idsParameter
+            );
         }
 
         $statement = $queryBuilder
@@ -688,12 +692,12 @@ class DataMapProcessor
                 // must be any kind of localization
                 $queryBuilder->expr()->gt(
                     $fieldNames['language'],
-                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    $zeroParameter
                 ),
                 // must be in connected mode
                 $queryBuilder->expr()->gt(
                     $fieldNames['parent'],
-                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    $zeroParameter
                 ),
                 // any parent or source pointers
                 $queryBuilder->expr()->orX(...$predicates)
index f985c10..e3b4976 100644 (file)
@@ -164,6 +164,23 @@ class State
     }
 
     /**
+     * Updates field names having a particular state to a target state.
+     *
+     * @param string $currentState
+     * @param string $targetState
+     */
+    public function updateStates(string $currentState, string $targetState)
+    {
+        $states = [];
+        foreach ($this->filterFieldNames($currentState) as $fieldName) {
+            $states[$fieldName] = $targetState;
+        }
+        if (!empty($states)) {
+            $this->update($states);
+        }
+    }
+
+    /**
      * @return string|null
      */
     public function export()
index f487ff3..145a75b 100644 (file)
@@ -134,13 +134,15 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
      */
     public function localizeContent()
     {
-        $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdSecond, self::VALUE_LanguageId);
+        $localizedTableIds = $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdSecond, self::VALUE_LanguageId);
+        $this->recordIds['localizedContentId'] = $localizedTableIds[self::TABLE_Content][self::VALUE_ContentIdSecond];
     }
 
     public function localizeContentWithLanguageSynchronization()
     {
         $GLOBALS['TCA']['tt_content']['columns']['header']['config']['behaviour']['allowLanguageSynchronization'] = true;
-        $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdSecond, self::VALUE_LanguageId);
+        $localizedTableIds = $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdSecond, self::VALUE_LanguageId);
+        $this->recordIds['localizedContentId'] = $localizedTableIds[self::TABLE_Content][self::VALUE_ContentIdSecond];
         $this->actionService->modifyRecord(self::TABLE_Content, self::VALUE_ContentIdSecond, ['header' => 'Testing #1']);
     }
 
@@ -150,7 +152,16 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
      */
     public function localizeContentFromNonDefaultLanguage()
     {
-        $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdThirdLocalized, self::VALUE_LanguageIdSecond);
+        $localizedTableIds = $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdThirdLocalized, self::VALUE_LanguageIdSecond);
+        $this->recordIds['localizedContentId'] = $localizedTableIds[self::TABLE_Content][self::VALUE_ContentIdThirdLocalized];
+    }
+
+    public function localizeContentFromNonDefaultLanguageWithLanguageSynchronizationDefault()
+    {
+        $GLOBALS['TCA']['tt_content']['columns']['header']['config']['behaviour']['allowLanguageSynchronization'] = true;
+        $localizedTableIds = $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdThirdLocalized, self::VALUE_LanguageIdSecond);
+        $this->recordIds['localizedContentId'] = $localizedTableIds[self::TABLE_Content][self::VALUE_ContentIdThirdLocalized];
+        $this->actionService->modifyRecord(self::TABLE_Content, self::VALUE_ContentIdThird, ['header' => 'Testing #1']);
     }
 
     /**
index 25e13f9..133a6b3 100644 (file)
@@ -195,6 +195,21 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\Regular\A
 
     /**
      * @test
+     * @see DataSet/localizeContentFromNonDefaultLanguageWSynchronizationDefault.csv
+     */
+    public function localizeContentFromNonDefaultLanguageWithLanguageSynchronizationDefault()
+    {
+        parent::localizeContentFromNonDefaultLanguageWithLanguageSynchronizationDefault();
+
+        $this->assertAssertionDataSet('localizeContentFromNonDefaultLanguageWSynchronizationDefault');
+
+        $responseSections = $this->getFrontendResponse(self::VALUE_PageId, self::VALUE_LanguageIdSecond)->getResponseSections();
+        $this->assertThat($responseSections, $this->getRequestSectionHasRecordConstraint()
+            ->setTable(self::TABLE_Content)->setField('header')->setValues('[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1', 'Testing #1'));
+    }
+
+    /**
+     * @test
      * @see DataSet/changeContentRecordSorting.csv
      */
     public function changeContentSorting()
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/localizeContentFromNonDefaultLanguageWSynchronizationDefault.csv b/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/localizeContentFromNonDefaultLanguageWSynchronizationDefault.csv
new file mode 100644 (file)
index 0000000..d78c544
--- /dev/null
@@ -0,0 +1,9 @@
+tt_content,,,,,,,,,,,,,,
+,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,l10n_source,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,header,l10n_state
+,297,89,256,0,0,0,0,0,0,0,0,0,0,Regular Element #1,
+,298,89,512,0,0,0,0,0,0,0,0,0,0,Regular Element #2,
+,299,89,768,0,0,0,0,0,0,0,0,0,0,Testing #1,
+,300,89,1024,0,1,299,299,299,0,0,0,0,0,Testing #1,\NULL
+,301,89,384,0,1,297,297,297,0,0,0,0,0,[Translate to Dansk:] Regular Element #1,
+,302,89,448,0,2,297,301,301,0,0,0,0,0,[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1,
+,303,89,1280,0,2,299,300,300,0,0,0,0,0,Testing #1,"{""header"":""parent""}"