From 2ada9266bfad939d2255d98c8ecd735b4d11f5f0 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Tue, 3 Nov 2026 11:09:21 +0100 Subject: [PATCH] [BUGFIX] Pass colPos when moving copyToLanguage children After copyToLanguage of a container, the copied container and its children are moved into place by separate move commands. These commands used "action: paste" without "update.colPos". Content restriction hooks (EXT:content_defender and the ContentElementRestriction hook of EXT:container) only use the paste target if "update.colPos" is set. Otherwise they cast the whole command value to int, which results in 1, and resolve the backend layout of page uid 1. If this page does not exist, an exception is thrown and copyToLanguage fails. The move commands now pass the colPos of the container and of each child. The target record is used to resolve page and column, and the regular container restrictions apply to the translated children. Tests: - Add a content_defender test for copyToLanguage on an installation without page uid 1 - Run localizeWithMultipleNestedElements with content_defender as well, with nested containers placed in a column that allows them - Move the nested containers in the localizeWithMultipleNestedElements fixtures from colPos 200 to 201, because colPos 200 does not allow them and the restrictions now apply --- .../CommandMapPostProcessingHook.php | 7 ++- .../CopyToLanguageSortingTest.php | 48 +++++++++++++++++++ .../CopyToLanguageWithNoPageUid1Test.php | 48 +++++++++++++++++++ .../LocalizeWithMultipleNestedElements.csv | 14 ++++++ ...calizeWithMultipleNestedElementsResult.csv | 21 ++++++++ .../Fixtures/CopyToLanguageWithNoPageUid1.csv | 9 ++++ .../CopyToLanguageWithNoPageUid1Result.csv | 11 +++++ .../LocalizeWithMultipleNestedElements.csv | 8 ++-- ...calizeWithMultipleNestedElementsResult.csv | 16 +++---- 9 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 Tests/Functional/Datahandler/ContentDefender/CopyToLanguageSortingTest.php create mode 100644 Tests/Functional/Datahandler/ContentDefender/CopyToLanguageWithNoPageUid1Test.php create mode 100644 Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElements.csv create mode 100644 Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElementsResult.csv create mode 100644 Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageWithNoPageUid1.csv create mode 100644 Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageWithNoPageUid1Result.csv diff --git a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php index 28b7b57b..d19b8afe 100644 --- a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php +++ b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php @@ -53,11 +53,15 @@ protected function copyToLanguageChildren(int $uid, int $language, string $comma $containerId = $last; $pos = $this->containerService->getAfterContainerElementTarget($container); // move next record after last child + // colPos is required in the update array, otherwise content restriction hooks + // (EXT:content_defender, EXT:container) cast the command value to int and use 1 as page uid $cmd = ['tt_content' => [$last => [ 'move' => [ 'target' => $pos, 'action' => 'paste', - 'update' => [], + 'update' => [ + 'colPos' => (int)$container->getContainerRecord()['colPos'], + ], ], ]]]; $localDataHandler = GeneralUtility::makeInstance(DataHandler::class); @@ -82,6 +86,7 @@ protected function copyToLanguageChildren(int $uid, int $language, string $comma 'action' => 'paste', 'update' => [ 'tx_container_parent' => $containerId, + 'colPos' => (int)$record['colPos'], ], ], ]]]; diff --git a/Tests/Functional/Datahandler/ContentDefender/CopyToLanguageSortingTest.php b/Tests/Functional/Datahandler/ContentDefender/CopyToLanguageSortingTest.php new file mode 100644 index 00000000..41383fd7 --- /dev/null +++ b/Tests/Functional/Datahandler/ContentDefender/CopyToLanguageSortingTest.php @@ -0,0 +1,48 @@ +getMajorVersion() < 14) { + $this->testExtensionsToLoad[] = 'typo3conf/ext/content_defender'; + } + parent::setUp(); + } + + #[Test] + #[Group('content_defender')] + public function localizeWithMultipleNestedElements(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElements.csv'); + $cmdmap = [ + 'tt_content' => [ + 1 => [ + 'copyToLanguage' => 4, + ], + ], + ]; + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_cmdmap(); + self::assertCSVDataSet(__DIR__ . '/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElementsResult.csv'); + self::assertFalse((GeneralUtility::makeInstance(DatahandlerProcess::class))->areContentElementRestrictionsLooked()); + } +} diff --git a/Tests/Functional/Datahandler/ContentDefender/CopyToLanguageWithNoPageUid1Test.php b/Tests/Functional/Datahandler/ContentDefender/CopyToLanguageWithNoPageUid1Test.php new file mode 100644 index 00000000..a01cac94 --- /dev/null +++ b/Tests/Functional/Datahandler/ContentDefender/CopyToLanguageWithNoPageUid1Test.php @@ -0,0 +1,48 @@ +getMajorVersion() < 14) { + $this->testExtensionsToLoad[] = 'typo3conf/ext/content_defender'; + } + parent::setUp(); + } + + #[Test] + #[Group('content_defender')] + public function copyToLanguage(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/CopyToLanguageWithNoPageUid1.csv'); + $cmdmap = [ + 'tt_content' => [ + 6 => [ + 'copyToLanguage' => 4, + ], + ], + ]; + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_cmdmap(); + self::assertCSVDataSet(__DIR__ . '/Fixtures/CopyToLanguageWithNoPageUid1Result.csv'); + self::assertFalse((GeneralUtility::makeInstance(DatahandlerProcess::class))->areContentElementRestrictionsLooked()); + } +} diff --git a/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElements.csv b/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElements.csv new file mode 100644 index 00000000..cbab91de --- /dev/null +++ b/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElements.csv @@ -0,0 +1,14 @@ +"pages" +,"uid","pid","title" +,"1","0","page-1" +,"4","0","[Translate to de:] page-1" +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l10n_source" +,"1","1","b13-2cols-with-header-container","1","256","0","0","0","0" +,"2","1","b13-2cols-with-header-container","2","512","0","201","1","0" +,"3","1","b13-2cols-with-header-container","3","768","0","201","2","0" +,"4","1","b13-2cols-with-header-container","4","1024","0","201","3","0" +,"5","1","b13-2cols-with-header-container","5","1280","0","201","3","0" +,"6","1","b13-2cols-with-header-container","6","1152","0","201","4","0" +,"7","1","header","7","1216","0","200","6","0" + diff --git a/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElementsResult.csv b/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElementsResult.csv new file mode 100644 index 00000000..6ebd4d87 --- /dev/null +++ b/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElementsResult.csv @@ -0,0 +1,21 @@ +"pages" +,"uid","pid","title" +,"1","0","page-1" +,"4","0","[Translate to de:] page-1" +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l10n_source" +,"1","1","b13-2cols-with-header-container","1","256","0","0","0","0" +,"2","1","b13-2cols-with-header-container","2","512","0","201","1","0" +,"3","1","b13-2cols-with-header-container","3","768","0","201","2","0" +,"4","1","b13-2cols-with-header-container","4","1024","0","201","3","0" +,"5","1","b13-2cols-with-header-container","5","1280","0","201","3","0" +,"6","1","b13-2cols-with-header-container","6","1152","0","201","4","0" +,"7","1","header","7","1216","0","200","6","0" +,"8","1","b13-2cols-with-header-container","[Translate to English-Free:] 1","1536","4","0","0","1" +,"9","1","b13-2cols-with-header-container","[Translate to English-Free:] 2","1792","4","201","8","2" +,"10","1","b13-2cols-with-header-container","[Translate to English-Free:] 3","2048","4","201","9","3" +,"11","1","b13-2cols-with-header-container","[Translate to English-Free:] 4","2176","4","201","10","4" +,"12","1","b13-2cols-with-header-container","[Translate to English-Free:] 6","2240","4","201","11","6" +,"13","1","header","[Translate to English-Free:] 7","2272","4","200","12","7" +,"14","1","b13-2cols-with-header-container","[Translate to English-Free:] 5","2304","4","201","10","5" + diff --git a/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageWithNoPageUid1.csv b/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageWithNoPageUid1.csv new file mode 100644 index 00000000..e915fe05 --- /dev/null +++ b/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageWithNoPageUid1.csv @@ -0,0 +1,9 @@ +"pages" +,"uid","pid","title" +,"2","0","page-1" +,"4","0","[Translate to de:] page-1" +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l10n_source" +,"6","2","b13-2cols-with-header-container","6","1152","0","200","0","0" +,"7","2","header","7","1216","0","200","6","0" + diff --git a/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageWithNoPageUid1Result.csv b/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageWithNoPageUid1Result.csv new file mode 100644 index 00000000..e0268190 --- /dev/null +++ b/Tests/Functional/Datahandler/ContentDefender/Fixtures/CopyToLanguageWithNoPageUid1Result.csv @@ -0,0 +1,11 @@ +"pages" +,"uid","pid","title" +,"2","0","page-1" +,"4","0","[Translate to de:] page-1" +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l10n_source" +,"6","2","b13-2cols-with-header-container","6","1152","0","200","0","0" +,"7","2","header","7","1216","0","200","6","0" +,"8","2","b13-2cols-with-header-container","[Translate to English-Free:] 6","1472","4","200","0","6" +,"9","2","header","[Translate to English-Free:] 7","1600","4","200","8","7" + diff --git a/Tests/Functional/Datahandler/Localization/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElements.csv b/Tests/Functional/Datahandler/Localization/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElements.csv index 8abea6ad..cbab91de 100644 --- a/Tests/Functional/Datahandler/Localization/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElements.csv +++ b/Tests/Functional/Datahandler/Localization/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElements.csv @@ -5,10 +5,10 @@ "tt_content" ,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l10n_source" ,"1","1","b13-2cols-with-header-container","1","256","0","0","0","0" -,"2","1","b13-2cols-with-header-container","2","512","0","200","1","0" +,"2","1","b13-2cols-with-header-container","2","512","0","201","1","0" ,"3","1","b13-2cols-with-header-container","3","768","0","201","2","0" -,"4","1","b13-2cols-with-header-container","4","1024","0","200","3","0" -,"5","1","b13-2cols-with-header-container","5","1280","0","200","3","0" -,"6","1","b13-2cols-with-header-container","6","1152","0","200","4","0" +,"4","1","b13-2cols-with-header-container","4","1024","0","201","3","0" +,"5","1","b13-2cols-with-header-container","5","1280","0","201","3","0" +,"6","1","b13-2cols-with-header-container","6","1152","0","201","4","0" ,"7","1","header","7","1216","0","200","6","0" diff --git a/Tests/Functional/Datahandler/Localization/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElementsResult.csv b/Tests/Functional/Datahandler/Localization/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElementsResult.csv index ce89ae6d..6ebd4d87 100644 --- a/Tests/Functional/Datahandler/Localization/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElementsResult.csv +++ b/Tests/Functional/Datahandler/Localization/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElementsResult.csv @@ -5,17 +5,17 @@ "tt_content" ,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l10n_source" ,"1","1","b13-2cols-with-header-container","1","256","0","0","0","0" -,"2","1","b13-2cols-with-header-container","2","512","0","200","1","0" +,"2","1","b13-2cols-with-header-container","2","512","0","201","1","0" ,"3","1","b13-2cols-with-header-container","3","768","0","201","2","0" -,"4","1","b13-2cols-with-header-container","4","1024","0","200","3","0" -,"5","1","b13-2cols-with-header-container","5","1280","0","200","3","0" -,"6","1","b13-2cols-with-header-container","6","1152","0","200","4","0" +,"4","1","b13-2cols-with-header-container","4","1024","0","201","3","0" +,"5","1","b13-2cols-with-header-container","5","1280","0","201","3","0" +,"6","1","b13-2cols-with-header-container","6","1152","0","201","4","0" ,"7","1","header","7","1216","0","200","6","0" ,"8","1","b13-2cols-with-header-container","[Translate to English-Free:] 1","1536","4","0","0","1" -,"9","1","b13-2cols-with-header-container","[Translate to English-Free:] 2","1792","4","200","8","2" +,"9","1","b13-2cols-with-header-container","[Translate to English-Free:] 2","1792","4","201","8","2" ,"10","1","b13-2cols-with-header-container","[Translate to English-Free:] 3","2048","4","201","9","3" -,"11","1","b13-2cols-with-header-container","[Translate to English-Free:] 4","2176","4","200","10","4" -,"12","1","b13-2cols-with-header-container","[Translate to English-Free:] 6","2240","4","200","11","6" +,"11","1","b13-2cols-with-header-container","[Translate to English-Free:] 4","2176","4","201","10","4" +,"12","1","b13-2cols-with-header-container","[Translate to English-Free:] 6","2240","4","201","11","6" ,"13","1","header","[Translate to English-Free:] 7","2272","4","200","12","7" -,"14","1","b13-2cols-with-header-container","[Translate to English-Free:] 5","2304","4","200","10","5" +,"14","1","b13-2cols-with-header-container","[Translate to English-Free:] 5","2304","4","201","10","5"