[BUGFIX] Avoid invalid references in DataMapProcessor 55/54655/6
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:22:06 +0000 (18:22 +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/54655
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: 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
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizePageWithSynchronizationAndCustomLocalizedHotel.csv

index 33784b2..ba5a597 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
@@ -203,7 +225,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
@@ -253,7 +275,7 @@ class DataMapProcessor
         foreach ($item->getApplicableScopes() as $scope) {
             $fieldNames = array_merge(
                 $fieldNames,
-                $this->getFieldNamesForItemScope($item, $scope, !$item->isNew())
+                $this->getFieldNamesForItemScope($item, $scope, false)
             );
         }
 
@@ -669,7 +691,7 @@ class DataMapProcessor
             $suggestedAncestorIds = $this->mapRelationItemId($relationHandler->itemArray);
         }
 
-        return $suggestedAncestorIds;
+        return array_filter($suggestedAncestorIds);
     }
 
     /**
@@ -701,7 +723,7 @@ class DataMapProcessor
             $persistedIds = $this->mapRelationItemId($relationHandler->itemArray);
         }
 
-        return $persistedIds;
+        return array_filter($persistedIds);
     }
 
     /**
index c4599ca..9c26360 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;
 
@@ -201,6 +202,48 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
     }
 
     /**
+     * @see DataSet/localizeParentContentSynchronization.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/localizeParentContentSynchronization.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'],
+            ]
+        );
+    }
+
+    /**
      * @see DataSet/changeParentContentRecordSorting.csv
      */
     public function changeParentContentSorting()
@@ -506,7 +549,7 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
         $this->actionService->modifyRecords(
             $this->recordIds['localizedPageId'],
             [
-                self::TABLE_Page => ['uid' => $this->recordIds['localizedPageId'], self::FIELD_PageHotel => '6,__nextUid'],
+                self::TABLE_Page => ['uid' => $this->recordIds['localizedPageId'], self::FIELD_PageHotel => '6,__nextUid', 'l10n_state' => [self::FIELD_PageHotel => 'custom']],
                 self::TABLE_Hotel => ['uid' => '__NEW', 'sys_language_uid' => self::VALUE_LanguageId, 'title' => 'Hotel in dansk page only'],
             ]
         );
index 626e87e..8e1ab4e 100644 (file)
@@ -185,6 +185,38 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\Fore
 
     /**
      * @test
+     * @see DataSet/localizeParentContentSynchronization.csv
+     */
+    public function localizeParentContentAndSetInvalidChildReferenceWithLanguageSynchronization()
+    {
+        parent::localizeParentContentAndSetInvalidChildReferenceWithLanguageSynchronization();
+        // the assertion is the same as for localizeParentContentWithLanguageSynchronization()
+        $this->assertAssertionDataSet('localizeParentContentSynchronization');
+
+        $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/localizeParentContentSynchronization.csv
+     */
+    public function localizeParentContentAndSetInvalidChildReferenceWithLateLanguageSynchronization()
+    {
+        parent::localizeParentContentAndSetInvalidChildReferenceWithLateLanguageSynchronization();
+        // the assertion is the same as for localizeParentContentWithLanguageSynchronization()
+        $this->assertAssertionDataSet('localizeParentContentSynchronization');
+
+        $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/changeParentContentRecordSorting.csv
      */
     public function changeParentContentSorting()
index 57676d2..f8e6da3 100644 (file)
@@ -4,7 +4,7 @@ pages
 ,88,1,256,0,0,0,0,0,0,0,0,0,DataHandlerTest,0,
 ,89,88,256,0,0,0,0,0,0,0,0,0,Relations,1,
 ,90,88,512,0,0,0,0,0,0,0,0,0,Target,0,
-,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""tx_irretutorial_hotels"":""parent""}"
+,91,88,256,0,1,89,0,0,0,0,0,0,"[Translate to Dansk:] Relations",2,"{""url"":""parent"",""lastUpdated"":""parent"",""newUntil"":""parent"",""no_search"":""parent"",""shortcut"":""parent"",""shortcut_mode"":""parent"",""author"":""parent"",""author_email"":""parent"",""media"":""parent"",""tx_irretutorial_hotels"":""custom""}"
 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
 ,2,89,512,0,0,0,0,0,0,0,0,0,"Hotel #0",89,pages,,0