From 9a61aee5e56ddd5f623c200ff58510023883dc88 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Sat, 5 Oct 2024 16:41:27 +0200 Subject: [PATCH] [BUGFIX] lock content_defender datamaphook for container operations when operating on container elements no datamaphook for content_defender should be executed Fixes: #471 --- .../ContainerColumnConfigurationService.php | 54 +++---- .../Xclasses/CommandMapHook.php | 11 +- .../ContentDefender/Xclasses/DatamapHook.php | 97 ++---------- .../DatamapPreProcessFieldArrayHook.php | 2 +- ...erFromMaxItemsReachedColumnToTopOfPage.csv | 9 ++ ...MaxitemsReachedColumnToTopOfPageResult.csv | 11 ++ ...ntInContainerIfMaxitemsIsReachedResult.csv | 9 +- ...nt_in_container_if_maxitems_is_reached.csv | 10 +- .../Fixtures/Maxitems/filled_container.csv | 8 + .../ContentDefender/MaxItemsTest.php | 143 ++++++++++++++---- 10 files changed, 185 insertions(+), 169 deletions(-) create mode 100644 Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CanCopyChildFromFilledContainerFromMaxItemsReachedColumnToTopOfPage.csv create mode 100644 Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CanCopyFilledContainerWithMaxitemsReachedColumnToTopOfPageResult.csv create mode 100644 Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/filled_container.csv diff --git a/Classes/ContentDefender/ContainerColumnConfigurationService.php b/Classes/ContentDefender/ContainerColumnConfigurationService.php index 2b3cfe63..e5c196e6 100644 --- a/Classes/ContentDefender/ContainerColumnConfigurationService.php +++ b/Classes/ContentDefender/ContainerColumnConfigurationService.php @@ -34,6 +34,23 @@ class ContainerColumnConfigurationService implements SingletonInterface protected $copyMapping = []; + protected $contentDefenderContainerDataHandlerHookIsLocked = false; + + public function startCmdMap(): void + { + $this->contentDefenderContainerDataHandlerHookIsLocked = true; + } + + public function endCmdMap(): void + { + $this->contentDefenderContainerDataHandlerHookIsLocked = false; + } + + public function isContentDefenderContainerDataHandlerHookLooked(): bool + { + return $this->contentDefenderContainerDataHandlerHookIsLocked; + } + public function __construct(ContainerFactory $containerFactory, Registry $tcaRegistry) { $this->containerFactory = $containerFactory; @@ -57,40 +74,6 @@ public function addCopyMapping(int $sourceContentId, int $containerId, int $targ ]; } - public function getCopyMappingBySourceContainerIdAndTargetColPos(int $containerId, int $targetColpos): ?array - { - if (isset($this->copyMapping[$containerId . ContainerGridColumn::CONTAINER_COL_POS_DELIMITER . $targetColpos])) { - return $this->copyMapping[$containerId . ContainerGridColumn::CONTAINER_COL_POS_DELIMITER . $targetColpos]; - } - return null; - } - - public function setContainerIsCopied($containerId): void - { - try { - $this->containerFactory->buildContainer($containerId); - $this->copyMapping[$containerId] = true; - } catch (Exception $e) { - // not a container, do not set mapping - } - } - - public function getTargetColPosForNew(int $containerId, int $colPos): ?int - { - if (isset($this->copyMapping[$containerId . ContainerGridColumn::CONTAINER_COL_POS_DELIMITER . $colPos])) { - return $this->copyMapping[$containerId . ContainerGridColumn::CONTAINER_COL_POS_DELIMITER . $colPos]['targetColPos']; - } - return null; - } - - public function getContainerIdForNew(int $containerId, int $colPos): ?int - { - if (isset($this->copyMapping[$containerId . ContainerGridColumn::CONTAINER_COL_POS_DELIMITER . $colPos])) { - return $this->copyMapping[$containerId . ContainerGridColumn::CONTAINER_COL_POS_DELIMITER . $colPos]['containerId']; - } - return null; - } - public function override(array $columnConfiguration, int $containerId, int $colPos): array { try { @@ -122,9 +105,6 @@ public function isMaxitemsReachedByContainenrId(int $containerId, int $colPos, ? public function isMaxitemsReached(Container $container, int $colPos, ?int $childUid = null): bool { - if (isset($this->copyMapping[$container->getUid()])) { - return false; - } $columnConfiguration = $this->getColumnConfigurationForContainer($container, $colPos); if (!isset($columnConfiguration['maxitems']) || (int)$columnConfiguration['maxitems'] === 0) { return false; diff --git a/Classes/ContentDefender/Xclasses/CommandMapHook.php b/Classes/ContentDefender/Xclasses/CommandMapHook.php index 935f1932..95fc7ecc 100644 --- a/Classes/ContentDefender/Xclasses/CommandMapHook.php +++ b/Classes/ContentDefender/Xclasses/CommandMapHook.php @@ -40,11 +40,9 @@ public function __construct( public function processCmdmap_beforeStart(DataHandler $dataHandler): void { if (!empty($dataHandler->cmdmap['tt_content'])) { + $this->containerColumnConfigurationService->startCmdMap(); foreach ($dataHandler->cmdmap['tt_content'] as $id => $cmds) { foreach ($cmds as $cmd => $data) { - if ($cmd === 'copy') { - $this->containerColumnConfigurationService->setContainerIsCopied($id); - } if ( ($cmd === 'copy' || $cmd === 'move') && (!empty($data['update'])) && @@ -87,6 +85,13 @@ public function processCmdmap_beforeStart(DataHandler $dataHandler): void parent::processCmdmap_beforeStart($dataHandler); } + public function processCmdmap_postProcess(string $command, string $table, $id, $value, DataHandler $dataHandler, $pasteUpdate, $pasteDatamap): void + { + if (!empty($dataHandler->cmdmap['tt_content'])) { + $this->containerColumnConfigurationService->endCmdMap(); + } + } + protected function isRecordAllowedByRestriction(array $columnConfiguration, array $record): bool { if (isset($record['tx_container_parent']) && diff --git a/Classes/ContentDefender/Xclasses/DatamapHook.php b/Classes/ContentDefender/Xclasses/DatamapHook.php index cfc755fa..d8e56651 100644 --- a/Classes/ContentDefender/Xclasses/DatamapHook.php +++ b/Classes/ContentDefender/Xclasses/DatamapHook.php @@ -28,20 +28,12 @@ class DatamapHook extends DatamapDataHandlerHook */ protected $containerColumnConfigurationService; - /** - * @var Database - */ - protected $database; - - protected $mapping = []; public function __construct( ?ContentRepository $contentRepository = null, - ?ContainerColumnConfigurationService $containerColumnConfigurationService = null, - ?Database $database = null + ?ContainerColumnConfigurationService $containerColumnConfigurationService = null ) { $this->containerColumnConfigurationService = $containerColumnConfigurationService ?? GeneralUtility::makeInstance(ContainerColumnConfigurationService::class); - $this->database = $database ?? GeneralUtility::makeInstance(Database::class); parent::__construct($contentRepository); } @@ -50,7 +42,9 @@ public function __construct( */ public function processDatamap_beforeStart(DataHandler $dataHandler): void { - if (is_array($dataHandler->datamap['tt_content'] ?? null)) { + if (is_array($dataHandler->datamap['tt_content'] ?? null) && + !$this->containerColumnConfigurationService->isContentDefenderContainerDataHandlerHookLooked() + ) { foreach ($dataHandler->datamap['tt_content'] as $id => $values) { if ( isset($values['tx_container_parent']) && @@ -58,50 +52,14 @@ public function processDatamap_beforeStart(DataHandler $dataHandler): void isset($values['colPos']) && $values['colPos'] > 0 ) { - // no maxitems check for localized records - if (isset($values['l18n_parent'])) { - if ((int)$values['l18n_parent'] !== 0) { - continue; - } - } elseif (MathUtility::canBeInterpretedAsInteger($id)) { - $record = $this->database->fetchOneRecord((int)$id); - if (isset($record['l18n_parent']) && (int)$record['l18n_parent'] !== 0) { - continue; - } - } - $containerId = (int)$values['tx_container_parent']; - // copyToLanguage case - if ((int)($values['l18n_parent'] ?? 1) === 0 && - (int)($values['l10n_source'] ?? 0) > 0 && - (int)($values['sys_language_uid'] ?? 0) > 0 - ) { - // free mode language CE used, we have to consider free mode container - $containerRecord = $this->database->fetchContainerRecordLocalizedFreeMode($containerId, (int)$values['sys_language_uid']); - if ($containerRecord !== null) { - $containerId = (int)$containerRecord['uid']; - } - } - $useChildId = null; - $colPos = (int)$values['colPos']; if (MathUtility::canBeInterpretedAsInteger($id)) { - $this->mapping[(int)$id] = [ - 'containerId' => (int)$values['tx_container_parent'], - 'colPos' => (int)$values['colPos'], - ]; - $useChildId = $id; - } else { - // new elements (first created in origin container/colPos, so we check the real target) - $targetColPos = $this->containerColumnConfigurationService->getTargetColPosForNew($containerId, (int)$values['colPos']); - if ($targetColPos !== null) { - $colPos = $targetColPos; - } - $containerIdTarget = $this->containerColumnConfigurationService->getContainerIdForNew($containerId, (int)$values['colPos']); - if ($containerIdTarget !== null) { - $containerId = $containerIdTarget; - } + // edit + continue; } - if ($this->containerColumnConfigurationService->isMaxitemsReachedByContainenrId($containerId, $colPos, $useChildId)) { - unset($dataHandler->datamap['tt_content'][$id]); + $containerId = (int)$values['tx_container_parent']; + + if ($this->containerColumnConfigurationService->isMaxitemsReachedByContainenrId($containerId, (int)$values['colPos'])) { + unset($dataHandler->datamap['tt_content'][$id]); $dataHandler->log( 'tt_content', $id, @@ -127,41 +85,6 @@ protected function isRecordAllowedByRestriction(array $columnConfiguration, arra ) { return true; } - if (isset($this->mapping[$record['uid']])) { - $columnConfiguration = $this->containerColumnConfigurationService->override( - $columnConfiguration, - $this->mapping[$record['uid']]['containerId'], - $this->mapping[$record['uid']]['colPos'] - ); - } elseif (isset($record['tx_container_parent']) && $record['tx_container_parent'] > 0) { - $copyMapping = $this->containerColumnConfigurationService->getCopyMappingBySourceContainerIdAndTargetColPos((int)$record['tx_container_parent'], (int)$record['colPos']); - if ($copyMapping !== null) { - $columnConfiguration = $this->containerColumnConfigurationService->override( - $columnConfiguration, - $copyMapping['containerId'], - $copyMapping['targetColPos'] - ); - } else { - $columnConfiguration = $this->containerColumnConfigurationService->override( - $columnConfiguration, - (int)$record['tx_container_parent'], - (int)$record['colPos'] - ); - } - } return parent::isRecordAllowedByRestriction($columnConfiguration, $record); } - - protected function isRecordAllowedByItemsCount(array $columnConfiguration, array $record): bool - { - if (isset($record['tx_container_parent']) && - $record['tx_container_parent'] > 0 && - (GeneralUtility::makeInstance(DatahandlerProcess::class))->isContainerInProcess((int)$record['tx_container_parent'])) { - return true; - } - if (isset($this->mapping[$record['uid']])) { - return true; - } - return parent::isRecordAllowedByItemsCount($columnConfiguration, $record); - } } diff --git a/Classes/Hooks/Datahandler/DatamapPreProcessFieldArrayHook.php b/Classes/Hooks/Datahandler/DatamapPreProcessFieldArrayHook.php index 61184db4..c0d84669 100644 --- a/Classes/Hooks/Datahandler/DatamapPreProcessFieldArrayHook.php +++ b/Classes/Hooks/Datahandler/DatamapPreProcessFieldArrayHook.php @@ -65,7 +65,7 @@ protected function newElementAfterContainer(array $incomingFieldArray): array return $incomingFieldArray; } if ((int)($incomingFieldArray['tx_container_parent'] ?? 0) > 0 && - (GeneralUtility::makeInstance(DatahandlerProcess::class))->isContainerInProcess((int)$incomingFieldArray['tx_container_parent']) + (GeneralUtility::makeInstance(DatahandlerProcess::class))->isContainerInProcess((int)$incomingFieldArray['tx_container_parent']) ) { return $incomingFieldArray; } diff --git a/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CanCopyChildFromFilledContainerFromMaxItemsReachedColumnToTopOfPage.csv b/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CanCopyChildFromFilledContainerFromMaxItemsReachedColumnToTopOfPage.csv new file mode 100644 index 00000000..810873a3 --- /dev/null +++ b/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CanCopyChildFromFilledContainerFromMaxItemsReachedColumnToTopOfPage.csv @@ -0,0 +1,9 @@ +"pages" +,"uid","title","pid" +,1,"page-1",0 +"tt_content" +,"uid","pid","colPos","CType","tx_container_parent","header" +,1,1,0,"b13-2cols",0,"container" +,2,1,200,text,1,"child-in-200" +,3,1,201,text,1,"child-in-201" +,4,1,0,text,0,"child-in-200 (copy 1)" \ No newline at end of file diff --git a/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CanCopyFilledContainerWithMaxitemsReachedColumnToTopOfPageResult.csv b/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CanCopyFilledContainerWithMaxitemsReachedColumnToTopOfPageResult.csv new file mode 100644 index 00000000..1b02e506 --- /dev/null +++ b/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CanCopyFilledContainerWithMaxitemsReachedColumnToTopOfPageResult.csv @@ -0,0 +1,11 @@ +"pages" +,"uid","title","pid" +,1,"page-1",0 +"tt_content" +,"uid","pid","colPos","CType","tx_container_parent","header" +,1,1,0,"b13-2cols",0,"container" +,2,1,200,text,1,"child-in-200" +,3,1,201,text,1,"child-in-201" +,4,1,0,"b13-2cols",0,"container (copy 1)" +,5,1,200,text,4,"child-in-200 (copy 1)" +,6,1,201,text,4,"child-in-201 (copy 1)" \ No newline at end of file diff --git a/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CannotCreateElementInContainerIfMaxitemsIsReachedResult.csv b/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CannotCreateElementInContainerIfMaxitemsIsReachedResult.csv index f0d81005..ddf6c07c 100644 --- a/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CannotCreateElementInContainerIfMaxitemsIsReachedResult.csv +++ b/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/CannotCreateElementInContainerIfMaxitemsIsReachedResult.csv @@ -1,7 +1,4 @@ -"pages" -,"uid","pid","title" -,1,0,"" "tt_content" -,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l18n_parent","l10n_source" -,1,1,"b13-2cols-with-header-container","",0,0,0,0,0,0 -,3,1,"","",0,0,202,1,0,0 +,"uid","pid","colPos","CType","tx_container_parent","header" +,1,1,0,"b13-2cols-with-header-container",0,"container" +,3,1,202,"header",1,"content-element" diff --git a/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/cannot_create_element_in_container_if_maxitems_is_reached.csv b/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/cannot_create_element_in_container_if_maxitems_is_reached.csv index b0b7d6e6..bc98a29e 100644 --- a/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/cannot_create_element_in_container_if_maxitems_is_reached.csv +++ b/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/cannot_create_element_in_container_if_maxitems_is_reached.csv @@ -1,7 +1,7 @@ "pages" -,"uid","pid" -,1,0 +,"uid","pid","title" +,1,0,"page-1" "tt_content" -,"uid","pid","colPos","CType","tx_container_parent" -,1,1,0,"b13-2cols-with-header-container", -,3,1,202,,1 \ No newline at end of file +,"uid","pid","colPos","CType","tx_container_parent","header" +,1,1,0,"b13-2cols-with-header-container",0,"container" +,3,1,202,"header",1,"content-element" \ No newline at end of file diff --git a/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/filled_container.csv b/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/filled_container.csv new file mode 100644 index 00000000..9169523b --- /dev/null +++ b/Tests/Functional/Datahandler/ContentDefender/Fixtures/Maxitems/filled_container.csv @@ -0,0 +1,8 @@ +"pages" +,"uid","title","pid" +,1,"page-1",0 +"tt_content" +,"uid","pid","colPos","CType","tx_container_parent","header" +,1,1,0,"b13-2cols",0,"container" +,2,1,200,text,1,"child-in-200" +,3,1,201,text,1,"child-in-201" \ No newline at end of file diff --git a/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php b/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php index 5f02bbdd..479579a9 100644 --- a/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php +++ b/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php @@ -199,7 +199,7 @@ public function cannotCreateElementInContainerIfMaxitemsIsReached(): void $this->dataHandler->process_datamap(); $this->dataHandler->process_cmdmap(); self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/CannotCreateElementInContainerIfMaxitemsIsReachedResult.csv'); - self::assertNotEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + #self::assertNotEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); } /** @@ -256,35 +256,6 @@ public function canMoveContainerWithMaxitemsReachedColumnToOtherPage(): void self::assertSame([], $this->dataHandler->errorLog, 'dataHander error log is not empty'); } - /** - * @test - * @group content_defender - */ - public function canCopyContainerWithMaxitemsReachedColumnToOtherPage(): void - { - $this->importCSVDataSet(__DIR__ . '/Fixtures/Maxitems/can_copy_container_with_maxitems_reached_column_to_other_page.csv'); - $cmdmap = [ - 'tt_content' => [ - 1 => [ - 'copy' => [ - 'action' => 'paste', - 'target' => 2, - 'update' => [ - 'colPos' => 0, - 'sys_language_uid' => 0, - ], - ], - ], - ], - ]; - - $this->dataHandler->start([], $cmdmap, $this->backendUser); - $this->dataHandler->process_datamap(); - $this->dataHandler->process_cmdmap(); - self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/CanCopyContainerWithMaxitemsReachedColumnToOtherPageResult.csv'); - self::assertSame([], $this->dataHandler->errorLog, 'dataHander error log is not empty'); - } - /** * @test * @group content_defender @@ -460,4 +431,116 @@ public function canSaveChildInDefaultLanguageWhenTranslatedAndMaxitemsIsReached( self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/CanSaveChildInDefaultLanguageWhenTranslatedAndMaxitemsIsReachedResult.csv'); self::assertEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); } + + /** + * @test + * @group content_defender + */ + public function canCopyFilledContainerWithMaxitemsReachedColumnToTopOfPage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/Maxitems/filled_container.csv'); + $cmdmap = [ + 'tt_content' => [ + 1 => [ + 'copy' => [ + 'action' => 'paste', + 'target' => 1, + 'update' => [ + 'colPos' => 0, + 'sys_language_uid' => 0, + ], + ], + ], + ], + ]; + + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_datamap(); + $this->dataHandler->process_cmdmap(); + self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/CanCopyFilledContainerWithMaxitemsReachedColumnToTopOfPageResult.csv'); + } + + /** + * @test + * @group content_defender + */ + public function canCopyChildFromFilledContainerFromMaxItemsReachedColumnToTopOfPage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/Maxitems/filled_container.csv'); + $cmdmap = [ + 'tt_content' => [ + 2 => [ + 'copy' => [ + 'action' => 'paste', + 'target' => 1, + 'update' => [ + 'colPos' => 0, + 'tx_container_parent' => 0, + ], + ], + ], + ], + ]; + + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_datamap(); + $this->dataHandler->process_cmdmap(); + self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/CanCopyChildFromFilledContainerFromMaxItemsReachedColumnToTopOfPage.csv'); + } + + /** + * @test + * @group content_defender + */ + public function cannotCopyChildFromFilledContainerIntoMaxItemsReachedColumnAfterChild(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/Maxitems/filled_container.csv'); + $cmdmap = [ + 'tt_content' => [ + 3 => [ + 'copy' => [ + 'action' => 'paste', + 'target' => -2, + 'update' => [ + 'colPos' => 200, + 'tx_container_parent' => 1, + ], + ], + ], + ], + ]; + + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_datamap(); + $this->dataHandler->process_cmdmap(); + self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/filled_container.csv'); + } + + /** + * @test + * @group content_defender + */ + public function cannotCopyChildFromFilledContainerIntoMaxItemsReachedColumnAtTop(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/Maxitems/filled_container.csv'); + $cmdmap = [ + 'tt_content' => [ + 3 => [ + 'copy' => [ + 'action' => 'paste', + 'target' => 1, + 'update' => [ + 'colPos' => 200, + 'tx_container_parent' => 1, + ], + ], + ], + ], + ]; + + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_datamap(); + $this->dataHandler->process_cmdmap(); + self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/filled_container.csv'); + } }