From 2c7d0452cb5b1be528293914792806dd35990e85 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Thu, 6 Aug 2026 21:54:40 +0200 Subject: [PATCH 1/2] [BUGFIX] keep hidden state of container children on copy/move DataHandler::copyRecord() forces hidden=1 on every copied record unless the backend user has neverHideAtCopy enabled. Our own copy/move hook for container children relied on that default, so all children ended up hidden after copying a container, regardless of their original visibility. Pass the source child's hidden value explicitly as an override in the cmdmap's update array, which takes precedence over DataHandler's automatic hideAtCopy logic. Relates to: #400 --- .../CommandMapPostProcessingHook.php | 2 ++ .../DefaultLanguage/ContainerTest.php | 22 +++++++++++++++++++ ...opyContainerKeepsHiddenStateOfChildren.csv | 9 ++++++++ ...tainerKeepsHiddenStateOfChildrenResult.csv | 12 ++++++++++ 4 files changed, 45 insertions(+) create mode 100644 Tests/Functional/Datahandler/DefaultLanguage/Fixtures/Container/CopyContainerKeepsHiddenStateOfChildren.csv create mode 100644 Tests/Functional/Datahandler/DefaultLanguage/Fixtures/Container/CopyContainerKeepsHiddenStateOfChildrenResult.csv diff --git a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php index b4e80719..da434de8 100644 --- a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php +++ b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php @@ -144,6 +144,8 @@ protected function copyOrMoveChildren(int $origUid, int $newId, int $containerId 'update' => [ 'tx_container_parent' => $containerId, 'colPos' => $record['colPos'], + // keep the original hidden state instead of DataHandler's hideAtCopy default + 'hidden' => (int)$record['hidden'], ], ], ], diff --git a/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php b/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php index 29551388..8e79b212 100644 --- a/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php +++ b/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php @@ -244,6 +244,28 @@ public function copyContainerKeepsSortingOfChildren(): void self::assertCSVDataSet(__DIR__ . '/Fixtures/Container/CopyContainerKeepsSortingOfChildrenResult.csv'); } + #[Test] + public function copyContainerKeepsHiddenStateOfChildren(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/Container/CopyContainerKeepsHiddenStateOfChildren.csv'); + $cmdmap = [ + 'tt_content' => [ + 1 => [ + 'copy' => [ + 'action' => 'paste', + 'target' => 3, + 'update' => [ + 'colPos' => 0, + ], + ], + ], + ], + ]; + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_cmdmap(); + self::assertCSVDataSet(__DIR__ . '/Fixtures/Container/CopyContainerKeepsHiddenStateOfChildrenResult.csv'); + } + #[Test] public function moveContainerOtherPageOnTop(): void { diff --git a/Tests/Functional/Datahandler/DefaultLanguage/Fixtures/Container/CopyContainerKeepsHiddenStateOfChildren.csv b/Tests/Functional/Datahandler/DefaultLanguage/Fixtures/Container/CopyContainerKeepsHiddenStateOfChildren.csv new file mode 100644 index 00000000..b5ed6d84 --- /dev/null +++ b/Tests/Functional/Datahandler/DefaultLanguage/Fixtures/Container/CopyContainerKeepsHiddenStateOfChildren.csv @@ -0,0 +1,9 @@ +"pages" +,"uid","pid","title" +,1,0,"page-1" +,3,0,"page-2" +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l18n_parent","hidden" +,1,1,"b13-2cols-with-header-container","container-default",56,0,0,0,0,0 +,2,1,"header","header-default",128,0,200,1,0,1 +,5,1,"header","second element",256,0,200,1,0,0 diff --git a/Tests/Functional/Datahandler/DefaultLanguage/Fixtures/Container/CopyContainerKeepsHiddenStateOfChildrenResult.csv b/Tests/Functional/Datahandler/DefaultLanguage/Fixtures/Container/CopyContainerKeepsHiddenStateOfChildrenResult.csv new file mode 100644 index 00000000..fb9269cf --- /dev/null +++ b/Tests/Functional/Datahandler/DefaultLanguage/Fixtures/Container/CopyContainerKeepsHiddenStateOfChildrenResult.csv @@ -0,0 +1,12 @@ +"pages" +,"uid","pid","title" +,1,0,"page-1" +,3,0,"page-2" +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l18n_parent","hidden" +,1,1,"b13-2cols-with-header-container","container-default",56,0,0,0,0,0 +,2,1,"header","header-default",128,0,200,1,0,1 +,5,1,"header","second element",256,0,200,1,0,0 +,6,3,"b13-2cols-with-header-container","container-default",256,0,0,0,0,1 +,7,3,"header","second element",512,0,200,6,0,0 +,8,3,"header","header-default",384,0,200,6,0,1 From d5ab7490f672a0c12f247e4535dabd1a48f05893 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Thu, 6 Aug 2026 22:05:05 +0200 Subject: [PATCH 2/2] [BUGFIX] keep hidden state of container children on localize/copyToLanguage Same issue as the copy/move fix, but 'localize' and 'copyToLanguage' cmdmap commands don't support an 'update' override array, so the hidden value can't be passed through the cmdmap directly. Instead, restore it explicitly via a follow-up datamap call once the new child record's uid is known. Fixes: #400 --- .../CommandMapPostProcessingHook.php | 18 +++++++++++ ...erToLanguageKeepsHiddenStateOfChildren.csv | 8 +++++ ...nguageKeepsHiddenStateOfChildrenResult.csv | 10 ++++++ ...izeContainerKeepsHiddenStateOfChildren.csv | 8 +++++ ...tainerKeepsHiddenStateOfChildrenResult.csv | 10 ++++++ .../Datahandler/Localization/LocalizeTest.php | 32 +++++++++++++++++++ 6 files changed, 86 insertions(+) create mode 100644 Tests/Functional/Datahandler/Localization/Fixtures/Localize/CopyContainerToLanguageKeepsHiddenStateOfChildren.csv create mode 100644 Tests/Functional/Datahandler/Localization/Fixtures/Localize/CopyContainerToLanguageKeepsHiddenStateOfChildrenResult.csv create mode 100644 Tests/Functional/Datahandler/Localization/Fixtures/Localize/LocalizeContainerKeepsHiddenStateOfChildren.csv create mode 100644 Tests/Functional/Datahandler/Localization/Fixtures/Localize/LocalizeContainerKeepsHiddenStateOfChildrenResult.csv diff --git a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php index da434de8..6ebb68aa 100644 --- a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php +++ b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php @@ -75,6 +75,7 @@ protected function copyToLanguageChildren(int $uid, int $language, string $comma if ($newId === null) { continue; } + $this->keepHiddenState($newId, $record, $dataHandler); $cmd = ['tt_content' => [$newId=> [ 'move' => [ 'target' => -$last, @@ -106,12 +107,29 @@ protected function localizeChildren(int $uid, int $language, string $command, Da $localDataHandler->enableLogging = $dataHandler->enableLogging; $localDataHandler->start([], $cmd, $dataHandler->BE_USER); $localDataHandler->process_cmdmap(); + $newId = $localDataHandler->copyMappingArray['tt_content'][$record['uid']] ?? null; + if ($newId !== null) { + $this->keepHiddenState($newId, $record, $dataHandler); + } } } catch (Exception $e) { // nothing todo } } + /** + * Localize/copyToLanguage always hide the new record unless the source was already hidden + * (TcaSchemaCapability::HideRecordsAtCopy), losing the original per-child visibility. Restore it explicitly, + * since neither 'localize' nor 'copyToLanguage' cmdmap commands support an 'update' override array. + */ + protected function keepHiddenState(int $newId, array $record, DataHandler $dataHandler): void + { + $hiddenStateDataHandler = GeneralUtility::makeInstance(DataHandler::class); + $hiddenStateDataHandler->enableLogging = $dataHandler->enableLogging; + $hiddenStateDataHandler->start(['tt_content' => [$newId => ['hidden' => (int)$record['hidden']]]], [], $dataHandler->BE_USER); + $hiddenStateDataHandler->process_datamap(); + } + protected function copyOrMoveChildren(int $origUid, int $newId, int $containerId, string $command, DataHandler $dataHandler): void { try { diff --git a/Tests/Functional/Datahandler/Localization/Fixtures/Localize/CopyContainerToLanguageKeepsHiddenStateOfChildren.csv b/Tests/Functional/Datahandler/Localization/Fixtures/Localize/CopyContainerToLanguageKeepsHiddenStateOfChildren.csv new file mode 100644 index 00000000..efc7a365 --- /dev/null +++ b/Tests/Functional/Datahandler/Localization/Fixtures/Localize/CopyContainerToLanguageKeepsHiddenStateOfChildren.csv @@ -0,0 +1,8 @@ +"pages" +,"uid","pid","title","slug","sys_language_uid","l10n_parent","l10n_source" +,1,0,"page-1","/",,, +,2,0,"page-1-language-1","/",1,1,1 +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","hidden" +,1,1,"b13-2cols-with-header-container","container-language-1",128,0,0,0,0 +,2,1,"header","header-language-1",256,0,200,1,0 diff --git a/Tests/Functional/Datahandler/Localization/Fixtures/Localize/CopyContainerToLanguageKeepsHiddenStateOfChildrenResult.csv b/Tests/Functional/Datahandler/Localization/Fixtures/Localize/CopyContainerToLanguageKeepsHiddenStateOfChildrenResult.csv new file mode 100644 index 00000000..409065b2 --- /dev/null +++ b/Tests/Functional/Datahandler/Localization/Fixtures/Localize/CopyContainerToLanguageKeepsHiddenStateOfChildrenResult.csv @@ -0,0 +1,10 @@ +"pages" +,"uid","pid","title" +,1,0,"page-1" +,2,0,"page-1-language-1" +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l18n_parent","l10n_source","hidden" +,1,1,"b13-2cols-with-header-container","container-language-1",128,0,0,0,0,0,0 +,2,1,"header","header-language-1",256,0,200,1,0,0,0 +,3,1,"b13-2cols-with-header-container","[Translate to german:] container-language-1",512,1,0,0,0,1,1 +,4,1,"header","[Translate to german:] header-language-1",640,1,200,3,0,2,0 diff --git a/Tests/Functional/Datahandler/Localization/Fixtures/Localize/LocalizeContainerKeepsHiddenStateOfChildren.csv b/Tests/Functional/Datahandler/Localization/Fixtures/Localize/LocalizeContainerKeepsHiddenStateOfChildren.csv new file mode 100644 index 00000000..efc7a365 --- /dev/null +++ b/Tests/Functional/Datahandler/Localization/Fixtures/Localize/LocalizeContainerKeepsHiddenStateOfChildren.csv @@ -0,0 +1,8 @@ +"pages" +,"uid","pid","title","slug","sys_language_uid","l10n_parent","l10n_source" +,1,0,"page-1","/",,, +,2,0,"page-1-language-1","/",1,1,1 +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","hidden" +,1,1,"b13-2cols-with-header-container","container-language-1",128,0,0,0,0 +,2,1,"header","header-language-1",256,0,200,1,0 diff --git a/Tests/Functional/Datahandler/Localization/Fixtures/Localize/LocalizeContainerKeepsHiddenStateOfChildrenResult.csv b/Tests/Functional/Datahandler/Localization/Fixtures/Localize/LocalizeContainerKeepsHiddenStateOfChildrenResult.csv new file mode 100644 index 00000000..45a075a3 --- /dev/null +++ b/Tests/Functional/Datahandler/Localization/Fixtures/Localize/LocalizeContainerKeepsHiddenStateOfChildrenResult.csv @@ -0,0 +1,10 @@ +"pages" +,"uid","pid","title" +,1,0,"page-1" +,2,0,"page-1-language-1" +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l18n_parent","l10n_source","hidden" +,1,1,"b13-2cols-with-header-container","container-language-1",128,0,0,0,0,0,0 +,2,1,"header","header-language-1",256,0,200,1,0,0,0 +,3,1,"b13-2cols-with-header-container","[Translate to german:] container-language-1",192,1,0,0,1,1,1 +,4,1,"header","[Translate to german:] header-language-1",512,1,200,1,2,2,0 diff --git a/Tests/Functional/Datahandler/Localization/LocalizeTest.php b/Tests/Functional/Datahandler/Localization/LocalizeTest.php index 13711dcd..deae19dd 100644 --- a/Tests/Functional/Datahandler/Localization/LocalizeTest.php +++ b/Tests/Functional/Datahandler/Localization/LocalizeTest.php @@ -69,6 +69,38 @@ public function localizeContainerLocalizeChildren(): void self::assertCSVDataSet(__DIR__ . '/Fixtures/Localize/LocalizeContainerLocalizeChildrenResult.csv'); } + #[Test] + public function localizeContainerKeepsHiddenStateOfChildren(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/Localize/LocalizeContainerKeepsHiddenStateOfChildren.csv'); + $cmdmap = [ + 'tt_content' => [ + 1 => [ + 'localize' => 1, + ], + ], + ]; + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_cmdmap(); + self::assertCSVDataSet(__DIR__ . '/Fixtures/Localize/LocalizeContainerKeepsHiddenStateOfChildrenResult.csv'); + } + + #[Test] + public function copyContainerToLanguageKeepsHiddenStateOfChildren(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/Localize/CopyContainerToLanguageKeepsHiddenStateOfChildren.csv'); + $cmdmap = [ + 'tt_content' => [ + 1 => [ + 'copyToLanguage' => 1, + ], + ], + ]; + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_cmdmap(); + self::assertCSVDataSet(__DIR__ . '/Fixtures/Localize/CopyContainerToLanguageKeepsHiddenStateOfChildrenResult.csv'); + } + #[Test] public function localizeNestedContainerKeepsDefaultLanguageParent(): void {