[BUGFIX] Avoid invalid references in DataMapProcessor 67/54667/2
authorOliver Hader <oliver@typo3.org>
Wed, 15 Nov 2017 15:07:57 +0000 (16:07 +0100)
committerOliver Hader <oliver.hader@typo3.org>
Thu, 16 Nov 2017 17:50:52 +0000 (18:50 +0100)
If DataMapProcessor is called with a non-reference id, e.g.
zero (0), this submission is considered as a reference. Since
there is no database record having UID 0, the synchronization
process fails with the following exeception:

#1486233164: Child record was not processed

To solve this behavior, invalid references (empty/zero) are
not considered anymore to compare references. Besides that,
values for localized records that are configured to be
synchronized are sanitized correctly now.

Resolves: #83009
Releases: master, 8.7
Change-Id: Ie370007521c45dac8bca03978a387b4662952b1d
Reviewed-on: https://review.typo3.org/54667
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
typo3/sysext/core/Classes/DataHandling/Localization/DataMapProcessor.php
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/ActionTest.php

index e697ad5..1311da7 100644 (file)
@@ -130,10 +130,32 @@ class DataMapProcessor
             $this->enrich($this->nextItems);
         }
 
+        $this->allDataMap = $this->purgeDataMap($this->allDataMap);
         return $this->allDataMap;
     }
 
     /**
+     * Purges superfluous empty data-map sections.
+     *
+     * @param array $dataMap
+     * @return array
+     */
+    protected function purgeDataMap(array $dataMap): array
+    {
+        foreach ($dataMap as $tableName => $idValues) {
+            foreach ($idValues as $id => $values) {
+                if (empty($values)) {
+                    unset($dataMap[$tableName][$id]);
+                }
+            }
+            if (empty($dataMap[$tableName])) {
+                unset($dataMap[$tableName]);
+            }
+        }
+        return $dataMap;
+    }
+
+    /**
      * Create data map items of all affected rows
      *
      * @param string $tableName
@@ -217,7 +239,7 @@ class DataMapProcessor
     }
 
     /**
-     * Sanitizes the submitted data-map and removes fields which are not
+     * Sanitizes the submitted data-map items and removes fields which are not
      * defined as custom and thus rely on either parent or source values.
      *
      * @param DataMapItem[] $items
@@ -267,7 +289,7 @@ class DataMapProcessor
         foreach ($item->getApplicableScopes() as $scope) {
             $fieldNames = array_merge(
                 $fieldNames,
-                $this->getFieldNamesForItemScope($item, $scope, !$item->isNew())
+                $this->getFieldNamesForItemScope($item, $scope, false)
             );
         }
 
@@ -683,7 +705,7 @@ class DataMapProcessor
             $suggestedAncestorIds = $this->mapRelationItemId($relationHandler->itemArray);
         }
 
-        return $suggestedAncestorIds;
+        return array_filter($suggestedAncestorIds);
     }
 
     /**
@@ -715,7 +737,7 @@ class DataMapProcessor
             $persistedIds = $this->mapRelationItemId($relationHandler->itemArray);
         }
 
-        return $persistedIds;
+        return array_filter($persistedIds);
     }
 
     /**
index f019a31..0c2d789 100644 (file)
@@ -29,6 +29,7 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
     const VALUE_HotelIdFirst = 3;
     const VALUE_HotelIdSecond = 4;
     const VALUE_HotelIdThird = 5;
+    const VALUE_OfferIdLast = 8;
     const VALUE_LanguageId = 1;
     const VALUE_LanguageIdSecond = 2;
 
@@ -306,6 +307,48 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
         $this->recordIds['newPriceId'] = $this->actionService->getDataHandler()->substNEWwithIDs[$newPriceId];
     }
 
+    /**
+     * @see DataSet/localizeParentContentSelectWSynchronization.csv
+     */
+    public function localizeParentContentAndSetInvalidChildReferenceWithLanguageSynchronization()
+    {
+        $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['localizedContentId'] = $newTableIds[self::TABLE_Content][self::VALUE_ContentIdLast];
+        // modify IRRE relation value of localized record (which should be sanitized and filtered)
+        $this->actionService->modifyRecord(self::TABLE_Content, $this->recordIds['localizedContentId'], [self::FIELD_ContentHotel => '0']);
+    }
+
+    /**
+     * @see DataSet/localizeParentContentSelectWSynchronization.csv
+     */
+    public function localizeParentContentAndSetInvalidChildReferenceWithLateLanguageSynchronization()
+    {
+        // disable language synchronization
+        $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['allowLanguageSynchronization'] = false;
+        $GLOBALS['TCA'][self::TABLE_Hotel]['columns'][self::FIELD_HotelOffer]['config']['behaviour']['allowLanguageSynchronization'] = false;
+        $GLOBALS['TCA'][self::TABLE_Offer]['columns'][self::FIELD_OfferPrice]['config']['behaviour']['allowLanguageSynchronization'] = false;
+        $newTableIds = $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdLast, self::VALUE_LanguageId);
+        $this->recordIds['localizedContentId'] = $newTableIds[self::TABLE_Content][self::VALUE_ContentIdLast];
+        $this->recordIds['localizedHotelId'] = $newTableIds[self::TABLE_Hotel][self::VALUE_HotelIdThird];
+        $this->recordIds['localizedOfferId'] = $newTableIds[self::TABLE_Offer][self::VALUE_OfferIdLast];
+        // now enable language synchronization
+        $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;
+        // modify IRRE relation values of localized records (which should be sanitized and filtered)
+        $this->actionService->modifyRecords(
+            self::VALUE_PageId,
+            [
+                self::TABLE_Content => ['uid' => $this->recordIds['localizedContentId'], self::FIELD_ContentHotel => '0'],
+                self::TABLE_Hotel => ['uid' => $this->recordIds['localizedHotelId'], self::FIELD_ContentHotel => '0'],
+                self::TABLE_Offer => ['uid' => $this->recordIds['localizedOfferId'], self::FIELD_ContentHotel => '0'],
+            ]
+        );
+    }
+
     public function localizeChildrenHavingStandaloneChildrenInSelectModeAndLanguageSynchronization()
     {
         $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['localizationMode'] = 'select';
index 249249a..c7678fe 100644 (file)
@@ -290,6 +290,38 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\Fore
 
     /**
      * @test
+     * @see DataSet/localizeParentContentSelectWSynchronization.csv
+     */
+    public function localizeParentContentAndSetInvalidChildReferenceWithLanguageSynchronization()
+    {
+        parent::localizeParentContentAndSetInvalidChildReferenceWithLanguageSynchronization();
+        // the assertion is the same as for 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/localizeParentContentSelectWSynchronization.csv
+     */
+    public function localizeParentContentAndSetInvalidChildReferenceWithLateLanguageSynchronization()
+    {
+        parent::localizeParentContentAndSetInvalidChildReferenceWithLateLanguageSynchronization();
+        // the assertion is the same as for 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/localizeChildrenHStandaloneChildrenWAllChildrenSelectNLanguageSynchronization.csv
      * @see https://forge.typo3.org/issues/81915
      */