From 0dfac264de6cb24cf16a29dc955a6e2105c98f97 Mon Sep 17 00:00:00 2001
From: Thomas Hohn <thomas@hohn.dk>
Date: Mon, 6 Feb 2017 12:01:25 +0100
Subject: [PATCH] [BUGFIX] Coalesce hook calls in
 DataHandler::processRemapStack()

DataHandler's hook processDatamap_afterDatabaseOperations is processed
in two ways. In case modifications do not contain any new relation that
just has been created, the hook is executed directly. If that's not the
case, executing this hook is deferred and will happen after the remap
stack has been processed.

Calling the hook directly happens exactly once for each modified record,
where invocations in DataHandler::processRemapStack() might happen more
than once, depending on the amount of relation fields that contain new
references and have been remapped.

This change coalesces these invocations which results that the hooks
processDatamap_afterDatabaseOperations is exactly called once for each
modified record - which is the expected behavior.

Change-Id: Ib7e65ce170c8f9ba8f7577b79073b1ed9213a0b9
Resolves: #79635
Releases: master, 7.6
Reviewed-on: https://review.typo3.org/51552
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Thomas Hohn <thomas@hohn.dk>
Tested-by: Thomas Hohn <thomas@hohn.dk>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
---
 .../core/Classes/DataHandling/DataHandler.php | 36 ++++++++++++++-----
 .../DataHandling/DataHandler/HookTest.php     |  7 ----
 2 files changed, 27 insertions(+), 16 deletions(-)

diff --git a/typo3/sysext/core/Classes/DataHandling/DataHandler.php b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
index 3a669f405b59..ecb3f7476d92 100644
--- a/typo3/sysext/core/Classes/DataHandling/DataHandler.php
+++ b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
@@ -6076,6 +6076,7 @@ class DataHandler
         // Processes the remap stack:
         if (is_array($this->remapStack)) {
             $remapFlexForms = [];
+            $hookPayload = [];
 
             foreach ($this->remapStack as $remapAction) {
                 // If no position index for the arguments was set, skip this remap action:
@@ -6151,18 +6152,18 @@ class DataHandler
 
                     $remapFlexForms[$flexFormId][$flexFormPath] = $newValue;
                 }
-                // Process waiting Hook: processDatamap_afterDatabaseOperations:
+
+                // Collect elements that shall trigger processDatamap_afterDatabaseOperations
                 if (isset($this->remapStackRecords[$table][$rawId]['processDatamap_afterDatabaseOperations'])) {
                     $hookArgs = $this->remapStackRecords[$table][$rawId]['processDatamap_afterDatabaseOperations'];
-                    // Update field with remapped data:
-                    $hookArgs['fieldArray'][$field] = $newValue;
-                    // Process waiting hook objects:
-                    $hookObjectsArr = $hookArgs['hookObjectsArr'];
-                    foreach ($hookObjectsArr as $hookObj) {
-                        if (method_exists($hookObj, 'processDatamap_afterDatabaseOperations')) {
-                            $hookObj->processDatamap_afterDatabaseOperations($hookArgs['status'], $table, $rawId, $hookArgs['fieldArray'], $this);
-                        }
+                    if (!isset($hookPayload[$table][$rawId])) {
+                        $hookPayload[$table][$rawId] = [
+                            'status' => $hookArgs['status'],
+                            'fieldArray' => $hookArgs['fieldArray'],
+                            'hookObjects' => $hookArgs['hookObjectsArr'],
+                        ];
                     }
+                    $hookPayload[$table][$rawId]['fieldArray'][$field] = $newValue;
                 }
             }
 
@@ -6171,6 +6172,23 @@ class DataHandler
                     $this->updateFlexFormData($flexFormId, $modifications);
                 }
             }
+
+            foreach ($hookPayload as $tableName => $rawIdPayload) {
+                foreach ($rawIdPayload as $rawId => $payload) {
+                    foreach ($payload['hookObjects'] as $hookObject) {
+                        if (!method_exists($hookObject, 'processDatamap_afterDatabaseOperations')) {
+                            continue;
+                        }
+                        $hookObject->processDatamap_afterDatabaseOperations(
+                            $payload['status'],
+                            $tableName,
+                            $rawId,
+                            $payload['fieldArray'],
+                            $this
+                        );
+                    }
+                }
+            }
         }
         // Processes the remap stack actions:
         if ($this->remapStackActions) {
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/HookTest.php b/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/HookTest.php
index 6c72ea72ea49..ee32d0e14641 100644
--- a/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/HookTest.php
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/HookTest.php
@@ -204,13 +204,6 @@ class HookTest extends AbstractDataHandlerActionTestCase
                     'fieldArray' => [
                         'header' => 'Testing #1',
                         self::FIELD_ContentHotel => 1,
-                    ],
-                ],
-                // @todo Fix the double invocation for this tt_content record
-                [
-                    'table' => self::TABLE_Content,
-                    'fieldArray' => [
-                        'header' => 'Testing #1',
                         self::FIELD_Categories => 1,
                     ],
                 ],
-- 
GitLab