[BUGFIX] Allow processing of multiple new record localizations 11/53211/2
authorOliver Hader <oliver@typo3.org>
Tue, 13 Jun 2017 15:40:05 +0000 (17:40 +0200)
committerOliver Hader <oliver.hader@typo3.org>
Wed, 14 Jun 2017 15:23:35 +0000 (17:23 +0200)
The current implementation of DataMapProcessor to determine the necessity
of synchronizing record localizations is too strict since it expects real
persisted database records and cannot resolve new records that have been
handed with the very same data-map to the DataHandler.

This mentioned constraint is resolved. Besides that, an additional value
processing for internal fields is added. This method resolves new record
ids to their real persisted UIDs using the famous remap stack. This only
is executed, if these fields have not been processed in a relation-aware
context and have a meaning to TYPO3 data-structures - for instance this
is the case for 'l10n_source' field defined as TCA type 'passthrough'.

Change-Id: Id1291910d85b3d314af7203314b8696a337fe364
Resolves: #80239
Releases: master, 8.7
Reviewed-on: https://review.typo3.org/52871
Reviewed-on: https://review.typo3.org/53211
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
12 files changed:
typo3/sysext/core/Classes/DataHandling/DataHandler.php
typo3/sysext/core/Classes/DataHandling/Localization/DataMapProcessor.php
typo3/sysext/core/Classes/DataHandling/Localization/State.php
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/ActionTest.php
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizeParentContentNCreateNestedChildrenWLanguageSynchronization.csv [new file with mode: 0644]
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizeParentContentSelectWSynchronization.csv [new file with mode: 0644]
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/createLocalizedContent.csv [new file with mode: 0644]
typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/createLocalizedContentWExclude.csv [new file with mode: 0644]
typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/createLocalizedContentWSynchronization.csv [new file with mode: 0644]

index 1ca9ed7..cd8363f 100644 (file)
@@ -1682,6 +1682,56 @@ class DataHandler
             default:
                 // Do nothing
         }
+        $res = $this->checkValueForInternalReferences($res, $value, $tcaFieldConf, $table, $id, $field);
+        return $res;
+    }
+
+    /**
+     * Checks values that are used for internal references. If the provided $value
+     * is a NEW-identifier, the direct processing is stopped. Instead, the value is
+     * forwarded to the remap-stack to be post-processed and resolved into a proper
+     * UID after all data has been resolved.
+     *
+     * This method considers TCA types that cannot handle and resolve these internal
+     * values directly, like 'passthrough', 'none' or 'user'. Values are only modified
+     * here if the $field is used as 'transOrigPointerField' or 'translationSource'.
+     *
+     * @param array $res The result array. The processed value (if any!) is set in the 'value' key.
+     * @param string $value The value to set.
+     * @param array $tcaFieldConf Field configuration from TCA
+     * @param string $table Table name
+     * @param int $id UID of record
+     * @param string $field The field name
+     * @return array The result array. The processed value (if any!) is set in the "value" key.
+     */
+    protected function checkValueForInternalReferences(array $res, $value, $tcaFieldConf, $table, $id, $field)
+    {
+        $relevantFieldNames = [
+            $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null,
+            $GLOBALS['TCA'][$table]['ctrl']['translationSource'] ?? null,
+        ];
+
+        if (
+            // in case the field is not relevant
+            !in_array($field, $relevantFieldNames)
+            // in case the 'value' index has been unset already
+            || !array_key_exists('value', $res)
+            // in case it's not a NEW-identifier
+            || strpos($value, 'NEW') === false
+        ) {
+            return $res;
+        }
+
+        $valueArray = [$value];
+        $this->remapStackRecords[$table][$id] = ['remapStackIndex' => count($this->remapStack)];
+        $this->addNewValuesToRemapStackChildIds($valueArray);
+        $this->remapStack[] = [
+            'args' => [$valueArray, $tcaFieldConf, $id, $table, $field],
+            'pos' => ['valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 3],
+            'field' => $field
+        ];
+        unset($res['value']);
+
         return $res;
     }
 
@@ -6149,7 +6199,9 @@ class DataHandler
                     $remapAction['args'][$remapAction['pos']['valueArray']] = $valueArray;
                 }
                 // Process the arguments with the defined function:
-                $newValue = call_user_func_array([$this, $remapAction['func']], $remapAction['args']);
+                if (!empty($remapAction['func'])) {
+                    $newValue = call_user_func_array([$this, $remapAction['func']], $remapAction['args']);
+                }
                 // If array is returned, check for maxitems condition, if string is returned this was already done:
                 if (is_array($newValue)) {
                     $newValue = implode(',', $this->checkValue_checkMax($tcaFieldConf, $newValue));
index bf7204c..f71b8af 100644 (file)
@@ -175,10 +175,7 @@ class DataMapProcessor
 
         $dependencies = $this->fetchDependencies(
             $forTableName,
-            $this->filterNewItemIds(
-                $forTableName,
-                $this->filterNumericIds(array_keys($idValues))
-            )
+            $this->filterNewItemIds($forTableName, $idValues)
         );
 
         foreach ($idValues as $id => $values) {
@@ -287,19 +284,25 @@ class DataMapProcessor
      *
      * @param DataMapItem $item
      * @param array $fieldNames
-     * @param int $fromId
+     * @param string|int $fromId
      */
-    protected function synchronizeTranslationItem(DataMapItem $item, array $fieldNames, int $fromId)
+    protected function synchronizeTranslationItem(DataMapItem $item, array $fieldNames, $fromId)
     {
         if (empty($fieldNames)) {
             return;
         }
+
         $fieldNameList = 'uid,' . implode(',', $fieldNames);
-        $fromRecord = BackendUtility::getRecordWSOL(
-            $item->getFromTableName(),
-            $fromId,
-            $fieldNameList
-        );
+
+        $fromRecord = ['uid' => $fromId];
+        if (MathUtility::canBeInterpretedAsInteger($fromId)) {
+            $fromRecord = BackendUtility::getRecordWSOL(
+                $item->getFromTableName(),
+                $fromId,
+                $fieldNameList
+            );
+        }
+
         $forRecord = [];
         if (!$item->isNew()) {
             $forRecord = BackendUtility::getRecordWSOL(
@@ -308,6 +311,7 @@ class DataMapProcessor
                 $fieldNameList
             );
         }
+
         foreach ($fieldNames as $fieldName) {
             $this->synchronizeFieldValues(
                 $item,
@@ -326,10 +330,6 @@ class DataMapProcessor
      */
     protected function populateTranslationItem(DataMapItem $item)
     {
-        if ($item->isNew()) {
-            return;
-        }
-
         foreach ([DataMapItem::SCOPE_PARENT, DataMapItem::SCOPE_SOURCE] as $scope) {
             foreach ($item->findDependencies($scope) as $dependentItem) {
                 // use suggested item, if it was submitted in data-map
@@ -389,10 +389,15 @@ class DataMapProcessor
         }
 
         $fromId = $fromRecord['uid'];
+        // retrieve value from in-memory data-map
         if ($this->isSetInDataMap($item->getFromTableName(), $fromId, $fieldName)) {
             $fromValue = $this->allDataMap[$item->getFromTableName()][$fromId][$fieldName];
-        } else {
+        // retrieve value from record
+        } elseif (array_key_exists($fieldName, $fromRecord)) {
             $fromValue = $fromRecord[$fieldName];
+        // otherwise abort synchronization
+        } else {
+            return;
         }
 
         // plain values
@@ -569,9 +574,9 @@ class DataMapProcessor
             return;
         }
         // In case only missing elements shall be created, re-use previously sanitized
-        // values IF child table cannot be translated, the relation parent item is new
-        // and the count of missing relations equals the count of previously sanitized
-        // relations. This is caused during copy processes, when the child relations
+        // values IF the relation parent item is new and the count of missing relations
+        // equals the count of previously sanitized relations.
+        // This is caused during copy processes, when the child relations
         // already have been cloned in DataHandler::copyRecord_procBasedOnFieldType()
         // without the possibility to resolve the initial connections at this point.
         // Otherwise child relations would superfluously be duplicated again here.
@@ -579,7 +584,7 @@ class DataMapProcessor
         $sanitizedValue = $this->sanitizationMap[$item->getTableName()][$item->getId()][$fieldName] ?? null;
         if (
             !empty($missingAncestorIds) && $item->isNew() && $sanitizedValue !== null
-            && count(GeneralUtility::trimExplode(',', $sanitizedValue)) === count($missingAncestorIds)
+            && count(GeneralUtility::trimExplode(',', $sanitizedValue, true)) === count($missingAncestorIds)
         ) {
             $this->modifyDataMap(
                 $item->getTableName(),
@@ -634,16 +639,17 @@ class DataMapProcessor
             foreach ($populateAncestorIds as $populateAncestorId) {
                 $newLocalizationId = StringUtility::getUniqueId('NEW');
                 $desiredIdMap[$populateAncestorId] = $newLocalizationId;
+                $duplicatedValues = $this->duplicateFromDataMap(
+                    $foreignTableName,
+                    $populateAncestorId,
+                    $item->getLanguage(),
+                    $fieldNames,
+                    !$isLocalizationModeExclude && $isTranslatable
+                );
                 $this->modifyDataMap(
                     $foreignTableName,
                     $newLocalizationId,
-                    $this->duplicateFromDataMap(
-                        $foreignTableName,
-                        $populateAncestorId,
-                        $item->getLanguage(),
-                        $fieldNames,
-                        !$isLocalizationModeExclude && $isTranslatable
-                    )
+                    $duplicatedValues
                 );
             }
         }
@@ -779,7 +785,7 @@ class DataMapProcessor
      * + [7]   -> []                               # since there's nothing
      *
      * @param string $tableName
-     * @param array $ids
+     * @param int[]|string[] $ids
      * @return DataMapItem[][]
      */
     protected function fetchDependencies(string $tableName, array $ids)
@@ -801,8 +807,22 @@ class DataMapProcessor
         if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
             $fieldNames['source'] = $GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
         }
+        $fieldNamesMap = array_combine($fieldNames, $fieldNames);
+
+        $persistedIds = $this->filterNumericIds(array_keys($ids), true);
+        $createdIds = $this->filterNumericIds(array_keys($ids), false);
+        $dependentElements = $this->fetchDependentElements($tableName, $persistedIds, $fieldNames);
 
-        $dependentElements = $this->fetchDependentElements($tableName, $ids, $fieldNames);
+        foreach ($createdIds as $createdId) {
+            $data = $this->allDataMap[$tableName][$createdId] ?? null;
+            if ($data === null) {
+                continue;
+            }
+            $dependentElements[] = array_merge(
+                ['uid' => $createdId],
+                array_intersect_key($data, $fieldNamesMap)
+            );
+        }
 
         $dependencyMap = [];
         foreach ($dependentElements as $dependentElement) {
@@ -1026,7 +1046,7 @@ class DataMapProcessor
      *
      * @param string[]|int[] $ids
      * @param bool $numeric
-     * @return array
+     * @return int[]|string[]
      */
     protected function filterNumericIds(array $ids, bool $numeric = true)
     {
@@ -1153,6 +1173,10 @@ class DataMapProcessor
             // @todo Not sure, whether $id is resolved in DataHandler's remapStack
             $data[$fieldNames['source']] = $fromId;
         }
+        // unset field names that are expected to be handled in this processor
+        foreach ($this->getFieldNamesToBeHandled($tableName) as $fieldName) {
+            unset($data[$fieldName]);
+        }
 
         $prefixFieldNames = array_intersect(
             array_keys($data),
@@ -1242,6 +1266,21 @@ class DataMapProcessor
     }
 
     /**
+     * Gets a list of field names which have to be handled. Basically this
+     * includes fields using allowLanguageSynchronization or l10n_mode=exclude.
+     *
+     * @param string $tableName
+     * @return string[]
+     */
+    protected function getFieldNamesToBeHandled(string $tableName)
+    {
+        return array_merge(
+            State::getFieldNames($tableName),
+            $this->getLocalizationModeExcludeFieldNames($tableName)
+        );
+    }
+
+    /**
      * Field names of TCA table with columns having l10n_mode=prefixLangTitle
      *
      * @param string $tableName
index 8c8f437..9c1026d 100644 (file)
@@ -76,6 +76,25 @@ class State
 
     /**
      * @param string $tableName
+     * @return array
+     */
+    public static function getFieldNames(string $tableName)
+    {
+        return array_keys(
+            array_filter(
+                $GLOBALS['TCA'][$tableName]['columns'],
+                function (array $fieldConfiguration) {
+                    return !empty(
+                        $fieldConfiguration['config']
+                            ['behaviour']['allowLanguageSynchronization']
+                    );
+                }
+            )
+        );
+    }
+
+    /**
+     * @param string $tableName
      * @return bool
      */
     protected static function hasColumns(string $tableName)
@@ -105,25 +124,6 @@ class State
     }
 
     /**
-     * @param string $tableName
-     * @return array
-     */
-    protected static function getFieldNames(string $tableName)
-    {
-        return array_keys(
-            array_filter(
-                $GLOBALS['TCA'][$tableName]['columns'],
-                function (array $fieldConfiguration) {
-                    return !empty(
-                        $fieldConfiguration['config']
-                            ['behaviour']['allowLanguageSynchronization']
-                    );
-                }
-            )
-        );
-    }
-
-    /**
      * @var string
      */
     protected $tableName;
index 4a07afe..c196738 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\ForeignField;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Utility\StringUtility;
+
 /**
  * Functional test for the DataHandler
  */
@@ -35,10 +37,12 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
     const TABLE_Content = 'tt_content';
     const TABLE_Hotel = 'tx_irretutorial_1nff_hotel';
     const TABLE_Offer = 'tx_irretutorial_1nff_offer';
+    const TABLE_Price = 'tx_irretutorial_1nff_price';
 
     const FIELD_PageHotel = 'tx_irretutorial_hotels';
     const FIELD_ContentHotel = 'tx_irretutorial_1nff_hotels';
     const FIELD_HotelOffer = 'offers';
+    const FIELD_OfferPrice = 'prices';
 
     /**
      * @var string
@@ -193,6 +197,22 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
     }
 
     /**
+     * @see DataSet/localizeParentContentSelectWSynchronization.csv
+     */
+    public function localizeParentContentInSelectModeWithLanguageSynchronization()
+    {
+        $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['localizationMode'] = 'select';
+        $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['localizeChildrenAtParentLocalization'] = false;
+        $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['allowLanguageSynchronization'] = true;
+        $GLOBALS['TCA'][self::TABLE_Hotel]['columns'][self::FIELD_HotelOffer]['config']['behaviour']['localizeChildrenAtParentLocalization'] = false;
+        $GLOBALS['TCA'][self::TABLE_Hotel]['columns'][self::FIELD_HotelOffer]['config']['behaviour']['allowLanguageSynchronization'] = true;
+        $GLOBALS['TCA'][self::TABLE_Offer]['columns'][self::FIELD_OfferPrice]['config']['behaviour']['localizeChildrenAtParentLocalization'] = false;
+        $GLOBALS['TCA'][self::TABLE_Offer]['columns'][self::FIELD_OfferPrice]['config']['behaviour']['allowLanguageSynchronization'] = true;
+        $newTableIds = $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdLast, self::VALUE_LanguageId);
+        $this->recordIds['localizedContentId'] = $newTableIds[self::TABLE_Content][self::VALUE_ContentIdLast];
+    }
+
+    /**
      * @see DataSet/localizeParentContentWAllChildrenSelect.csv
      */
     public function localizeParentContentWithAllChildrenInSelectMode()
@@ -252,6 +272,41 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
     }
 
     /**
+     * @see DataSet/Modify/localizeParentContentNCreateNestedChildrenWLanguageSynchronization.csv
+     */
+    public function localizeParentContentAndCreateNestedChildrenWithLanguageSynchronization()
+    {
+        $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['allowLanguageSynchronization'] = true;
+        $GLOBALS['TCA'][self::TABLE_Hotel]['columns'][self::FIELD_HotelOffer]['config']['behaviour']['allowLanguageSynchronization'] = true;
+        $GLOBALS['TCA'][self::TABLE_Offer]['columns'][self::FIELD_OfferPrice]['config']['behaviour']['allowLanguageSynchronization'] = true;
+
+        $newTableIds = $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdLast, self::VALUE_LanguageId);
+        $this->recordIds['localizedContentIdFirst'] = $newTableIds[self::TABLE_Content][self::VALUE_ContentIdLast];
+
+        $newHotelId = StringUtility::getUniqueId('NEW');
+        $newOfferId = StringUtility::getUniqueId('NEW');
+        $newPriceId = StringUtility::getUniqueId('NEW');
+        $dataMap = [
+            self::TABLE_Content => [
+                self::VALUE_ContentIdLast => [self::FIELD_ContentHotel => '5,' . $newHotelId],
+            ],
+            self::TABLE_Hotel => [
+                $newHotelId => ['pid' => self::VALUE_PageId, 'title' => 'New Hotel #1', 'offers' => $newOfferId],
+            ],
+            self::TABLE_Offer => [
+                $newOfferId => ['pid' => self::VALUE_PageId, 'title' => 'New Offer #1.1', 'prices' => $newPriceId],
+            ],
+            self::TABLE_Price => [
+                $newPriceId => ['pid' => self::VALUE_PageId, 'title' => 'New Price #1.1.1'],
+            ],
+        ];
+        $this->actionService->invoke($dataMap, []);
+        $this->recordIds['newHoteId'] = $this->actionService->getDataHandler()->substNEWwithIDs[$newHotelId];
+        $this->recordIds['newOfferId'] = $this->actionService->getDataHandler()->substNEWwithIDs[$newOfferId];
+        $this->recordIds['newPriceId'] = $this->actionService->getDataHandler()->substNEWwithIDs[$newPriceId];
+    }
+
+    /**
      * @see DataSet/changeParentContentRecordSorting.csv
      */
     public function changeParentContentSorting()
index df12b51..4ea1806 100644 (file)
@@ -215,6 +215,21 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\Fore
 
     /**
      * @test
+     * @see DataSet/localizeParentContentSelectWSynchronization.csv
+     */
+    public function localizeParentContentInSelectModeWithLanguageSynchronization()
+    {
+        parent::localizeParentContentInSelectModeWithLanguageSynchronization();
+        $this->assertAssertionDataSet('localizeParentContentSelectWSynchronization');
+
+        $responseSections = $this->getFrontendResponse(self::VALUE_PageId, self::VALUE_LanguageId)->getResponseSections('Default', 'Extbase:list()');
+        $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
+            ->setRecordIdentifier(self::TABLE_Content . ':' . self::VALUE_ContentIdLast)->setRecordField(self::FIELD_ContentHotel)
+            ->setTable(self::TABLE_Hotel)->setField('title')->setValues('[Translate to Dansk:] Hotel #1'));
+    }
+
+    /**
+     * @test
      * @see DataSet/localizeParentContentWAllChildrenSelect.csv
      */
     public function localizeParentContentWithAllChildrenInSelectMode()
@@ -260,6 +275,21 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\Fore
 
     /**
      * @test
+     * @see DataSet/Modify/localizeParentContentNCreateNestedChildrenWLanguageSynchronization.csv
+     */
+    public function localizeParentContentAndCreateNestedChildrenWithLanguageSynchronization()
+    {
+        parent::localizeParentContentAndCreateNestedChildrenWithLanguageSynchronization();
+        $this->assertAssertionDataSet('localizeParentContentNCreateNestedChildrenWLanguageSynchronization');
+
+        $responseSections = $this->getFrontendResponse(self::VALUE_PageId, self::VALUE_LanguageId)->getResponseSections('Default', 'Extbase:list()');
+        $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
+            ->setRecordIdentifier(self::TABLE_Content . ':' . self::VALUE_ContentIdLast)->setRecordField(self::FIELD_ContentHotel)
+            ->setTable(self::TABLE_Hotel)->setField('title')->setValues('[Translate to Dansk:] Hotel #1', '[Translate to Dansk:] New Hotel #1'));
+    }
+
+    /**
+     * @test
      * @see DataSet/changeParentContentRecordSorting.csv
      */
     public function changeParentContentSorting()
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizeParentContentNCreateNestedChildrenWLanguageSynchronization.csv b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizeParentContentNCreateNestedChildrenWLanguageSynchronization.csv
new file mode 100644 (file)
index 0000000..1f078ca
--- /dev/null
@@ -0,0 +1,35 @@
+tt_content
+,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,header,tx_irretutorial_1nff_hotels,l10n_state
+,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",2,\NULL
+,298,89,512,0,0,0,0,0,0,0,0,0,"Regular Element #2",2,\NULL
+,299,89,768,0,1,298,298,0,0,0,0,0,"[Translate to Dansk:] Regular Element #2",2,"{""tx_irretutorial_1nff_hotels"":""parent""}"
+tx_irretutorial_1nff_hotel
+,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier,offers,l10n_state
+,2,89,1024,0,0,0,0,0,0,0,0,0,"Hotel #0",89,pages,,0,\NULL
+,3,89,1280,0,0,0,0,0,0,0,0,0,"Hotel #1",297,tt_content,,2,\NULL
+,4,89,1792,0,0,0,0,0,0,0,0,0,"Hotel #2",297,tt_content,,1,\NULL
+,5,89,1,0,0,0,0,0,0,0,0,0,"Hotel #1",298,tt_content,,1,\NULL
+,6,89,1,0,1,5,5,0,0,0,0,0,"[Translate to Dansk:] Hotel #1",299,tt_content,,1,"{""offers"":""parent""}"
+,7,89,2,0,0,0,0,0,0,0,0,0,"New Hotel #1",298,tt_content,,1,\NULL
+,8,89,2,0,1,7,0,0,0,0,0,0,"[Translate to Dansk:] New Hotel #1",299,tt_content,,1,"{""offers"":""parent""}"
+tx_irretutorial_1nff_offer
+,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier,prices,l10n_state
+,5,89,1024,0,0,0,0,0,0,0,0,0,"Offer #1.1",3,tx_irretutorial_1nff_hotel,,3,\NULL
+,6,89,1792,0,0,0,0,0,0,0,0,0,"Offer #1.2",3,tx_irretutorial_1nff_hotel,,2,\NULL
+,7,89,1280,0,0,0,0,0,0,0,0,0,"Offer #2.1",4,tx_irretutorial_1nff_hotel,,1,\NULL
+,8,89,1536,0,0,0,0,0,0,0,0,0,"Offer #1.1",5,tx_irretutorial_1nff_hotel,,1,\NULL
+,9,89,768,0,1,8,8,0,0,0,0,0,"[Translate to Dansk:] Offer #1.1",6,tx_irretutorial_1nff_hotel,,1,"{""prices"":""parent""}"
+,10,89,1,0,0,0,0,0,0,0,0,0,"New Offer #1.1",7,tx_irretutorial_1nff_hotel,,1,\NULL
+,11,89,1,0,1,10,0,0,0,0,0,0,"[Translate to Dansk:] New Offer #1.1",8,tx_irretutorial_1nff_hotel,,1,"{""prices"":""parent""}"
+tx_irretutorial_1nff_price
+,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier,l10n_state
+,7,89,1024,0,0,0,0,0,0,0,0,0,"Price #1.1.1",5,tx_irretutorial_1nff_offer,,\NULL
+,8,89,2048,0,0,0,0,0,0,0,0,0,"Price #1.1.2",5,tx_irretutorial_1nff_offer,,\NULL
+,9,89,2560,0,0,0,0,0,0,0,0,0,"Price #1.1.3",5,tx_irretutorial_1nff_offer,,\NULL
+,10,89,1280,0,0,0,0,0,0,0,0,0,"Price #1.2.1",6,tx_irretutorial_1nff_offer,,\NULL
+,11,89,2304,0,0,0,0,0,0,0,0,0,"Price #1.2.2",6,tx_irretutorial_1nff_offer,,\NULL
+,12,89,1536,0,0,0,0,0,0,0,0,0,"Price #2.1.1",7,tx_irretutorial_1nff_offer,,\NULL
+,13,89,1792,0,0,0,0,0,0,0,0,0,"Price #1.1.1",8,tx_irretutorial_1nff_offer,,\NULL
+,14,89,768,0,1,13,13,0,0,0,0,0,"[Translate to Dansk:] Price #1.1.1",9,tx_irretutorial_1nff_offer,,\NULL
+,15,89,1,0,0,0,0,0,0,0,0,0,"New Price #1.1.1",10,tx_irretutorial_1nff_offer,,\NULL
+,16,89,1,0,1,15,0,0,0,0,0,0,"[Translate to Dansk:] New Price #1.1.1",11,tx_irretutorial_1nff_offer,,\NULL
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizeParentContentSelectWSynchronization.csv b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizeParentContentSelectWSynchronization.csv
new file mode 100644 (file)
index 0000000..22262d2
--- /dev/null
@@ -0,0 +1,29 @@
+tt_content
+,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,header,tx_irretutorial_1nff_hotels,l10n_state
+,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",2,\NULL
+,298,89,512,0,0,0,0,0,0,0,0,0,"Regular Element #2",1,\NULL
+,299,89,768,0,1,298,298,0,0,0,0,0,"[Translate to Dansk:] Regular Element #2",1,"{""tx_irretutorial_1nff_hotels"":""parent""}"
+tx_irretutorial_1nff_hotel
+,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier,offers,l10n_state
+,2,89,512,0,0,0,0,0,0,0,0,0,"Hotel #0",89,pages,,0,\NULL
+,3,89,768,0,0,0,0,0,0,0,0,0,"Hotel #1",297,tt_content,,2,\NULL
+,4,89,1536,0,0,0,0,0,0,0,0,0,"Hotel #2",297,tt_content,,1,\NULL
+,5,89,1024,0,0,0,0,0,0,0,0,0,"Hotel #1",298,tt_content,,1,\NULL
+,6,89,1,0,1,5,5,0,0,0,0,0,"[Translate to Dansk:] Hotel #1",299,tt_content,,1,"{""offers"":""parent""}"
+tx_irretutorial_1nff_offer
+,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier,prices,l10n_state
+,5,89,512,0,0,0,0,0,0,0,0,0,"Offer #1.1",3,tx_irretutorial_1nff_hotel,,3,\NULL
+,6,89,1536,0,0,0,0,0,0,0,0,0,"Offer #1.2",3,tx_irretutorial_1nff_hotel,,2,\NULL
+,7,89,768,0,0,0,0,0,0,0,0,0,"Offer #2.1",4,tx_irretutorial_1nff_hotel,,1,\NULL
+,8,89,1024,0,0,0,0,0,0,0,0,0,"Offer #1.1",5,tx_irretutorial_1nff_hotel,,1,\NULL
+,9,89,1,0,1,8,8,0,0,0,0,0,"[Translate to Dansk:] Offer #1.1",6,tx_irretutorial_1nff_hotel,,1,"{""prices"":""parent""}"
+tx_irretutorial_1nff_price
+,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,title,parentid,parenttable,parentidentifier,l10n_state
+,7,89,512,0,0,0,0,0,0,0,0,0,"Price #1.1.1",5,tx_irretutorial_1nff_offer,,\NULL
+,8,89,1792,0,0,0,0,0,0,0,0,0,"Price #1.1.2",5,tx_irretutorial_1nff_offer,,\NULL
+,9,89,2304,0,0,0,0,0,0,0,0,0,"Price #1.1.3",5,tx_irretutorial_1nff_offer,,\NULL
+,10,89,768,0,0,0,0,0,0,0,0,0,"Price #1.2.1",6,tx_irretutorial_1nff_offer,,\NULL
+,11,89,2048,0,0,0,0,0,0,0,0,0,"Price #1.2.2",6,tx_irretutorial_1nff_offer,,\NULL
+,12,89,1024,0,0,0,0,0,0,0,0,0,"Price #2.1.1",7,tx_irretutorial_1nff_offer,,\NULL
+,13,89,1280,0,0,0,0,0,0,0,0,0,"Price #1.1.1",8,tx_irretutorial_1nff_offer,,\NULL
+,14,89,1,0,1,13,13,0,0,0,0,0,"[Translate to Dansk:] Price #1.1.1",9,tx_irretutorial_1nff_offer,,\NULL
index 50c2496..cdabe46 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Core\Tests\Functional\DataHandling\Regular;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Utility\StringUtility;
+
 /**
  * Functional test for the DataHandler
  */
@@ -189,6 +191,33 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
         $this->actionService->modifyRecord(self::TABLE_Content, self::VALUE_ContentIdThird, ['header' => 'Testing #1']);
     }
 
+    public function createLocalizedContent()
+    {
+        $newContentIdDefault = StringUtility::getUniqueId('NEW');
+        $newContentIdLocalized = StringUtility::getUniqueId('NEW');
+        $dataMap = [
+            self::TABLE_Content => [
+                $newContentIdDefault => ['pid' => self::VALUE_PageId, 'header' => 'Testing'],
+                $newContentIdLocalized => ['pid' => self::VALUE_PageId, 'header' => 'Localized Testing', 'sys_language_uid' => self::VALUE_LanguageId, 'l18n_parent' => $newContentIdDefault, 'l10n_source' => $newContentIdDefault],
+            ]
+        ];
+        $this->actionService->invoke($dataMap, []);
+        $this->recordIds['newContentIdDefault'] = $this->actionService->getDataHandler()->substNEWwithIDs[$newContentIdDefault];
+        $this->recordIds['newContentIdLocalized'] = $this->actionService->getDataHandler()->substNEWwithIDs[$newContentIdLocalized];
+    }
+
+    public function createLocalizedContentWithLanguageSynchronization()
+    {
+        $GLOBALS['TCA']['tt_content']['columns']['header']['config']['behaviour']['allowLanguageSynchronization'] = true;
+        self::createLocalizedContent();
+    }
+
+    public function createLocalizedContentWithLocalizationExclude()
+    {
+        $GLOBALS['TCA']['tt_content']['columns']['header']['l10n_mode'] = 'exclude';
+        self::createLocalizedContent();
+    }
+
     /**
      * @see DataSet/changeContentRecordSorting.csv
      */
index 1afcd9a..32cd9b1 100644 (file)
@@ -254,6 +254,51 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\Regular\A
 
     /**
      * @test
+     * @see DataSet/createLocalizedContent.csv
+     */
+    public function createLocalizedContent()
+    {
+        parent::createLocalizedContent();
+
+        $this->assertAssertionDataSet('createLocalizedContent');
+
+        $responseSections = $this->getFrontendResponse(self::VALUE_PageId, self::VALUE_LanguageId)->getResponseSections();
+        $this->assertThat($responseSections, $this->getRequestSectionHasRecordConstraint()
+            ->setTable(self::TABLE_Content)->setField('header')->setValues('Localized Testing'));
+    }
+
+    /**
+     * @test
+     * @see DataSet/createLocalizedContentWSynchronization.csv
+     */
+    public function createLocalizedContentWithLanguageSynchronization()
+    {
+        parent::createLocalizedContentWithLanguageSynchronization();
+
+        $this->assertAssertionDataSet('createLocalizedContentWSynchronization');
+
+        $responseSections = $this->getFrontendResponse(self::VALUE_PageId, self::VALUE_LanguageId)->getResponseSections();
+        $this->assertThat($responseSections, $this->getRequestSectionHasRecordConstraint()
+            ->setTable(self::TABLE_Content)->setField('header')->setValues('Testing'));
+    }
+
+    /**
+     * @test
+     * @see DataSet/createLocalizedContentWExclude.csv
+     */
+    public function createLocalizedContentWithLocalizationExclude()
+    {
+        parent::createLocalizedContentWithLocalizationExclude();
+
+        $this->assertAssertionDataSet('createLocalizedContentWExclude');
+
+        $responseSections = $this->getFrontendResponse(self::VALUE_PageId, self::VALUE_LanguageId)->getResponseSections();
+        $this->assertThat($responseSections, $this->getRequestSectionHasRecordConstraint()
+            ->setTable(self::TABLE_Content)->setField('header')->setValues('Testing'));
+    }
+
+    /**
+     * @test
      * @see DataSet/changeContentRecordSorting.csv
      */
     public function changeContentSorting()
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/createLocalizedContent.csv b/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/createLocalizedContent.csv
new file mode 100644 (file)
index 0000000..08a16a3
--- /dev/null
@@ -0,0 +1,11 @@
+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
+,296,88,256,0,0,0,0,0,0,0,0,0,0,"Regular Element #0",
+,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,"Regular Element #3",
+,300,89,1024,0,1,299,299,299,0,0,0,0,0,"[Translate to Dansk:] Regular Element #3",
+,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,128,0,0,0,0,0,0,0,0,0,0,Testing,\NULL
+,304,89,64,0,1,303,303,0,0,0,0,0,0,"Localized Testing",\NULL
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/createLocalizedContentWExclude.csv b/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/createLocalizedContentWExclude.csv
new file mode 100644 (file)
index 0000000..aaa64fe
--- /dev/null
@@ -0,0 +1,11 @@
+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
+,296,88,256,0,0,0,0,0,0,0,0,0,0,"Regular Element #0",
+,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,"Regular Element #3",
+,300,89,1024,0,1,299,299,299,0,0,0,0,0,"[Translate to Dansk:] Regular Element #3",
+,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,128,0,0,0,0,0,0,0,0,0,0,Testing,\NULL
+,304,89,64,0,1,303,303,0,0,0,0,0,0,Testing,\NULL
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/createLocalizedContentWSynchronization.csv b/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/createLocalizedContentWSynchronization.csv
new file mode 100644 (file)
index 0000000..40cb1d6
--- /dev/null
@@ -0,0 +1,11 @@
+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
+,296,88,256,0,0,0,0,0,0,0,0,0,0,"Regular Element #0",
+,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,"Regular Element #3",
+,300,89,1024,0,1,299,299,299,0,0,0,0,0,"[Translate to Dansk:] Regular Element #3",
+,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,128,0,0,0,0,0,0,0,0,0,0,Testing,\NULL
+,304,89,64,0,1,303,303,0,0,0,0,0,0,Testing,"{""header"":""parent""}"