From 3205a57c32b4d837f397e6da0d50ff2c20452a1c Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 12 Aug 2026 13:25:18 +0200 Subject: [PATCH] [BUGFIX] Do not lose hidden records from localize summary RecordSummaryForLocalization queried tt_content with the default enable-column restrictions, so hidden records were silently excluded. This broke two cases: - A child of a hidden, already-translated container could not be resolved at all: fetchOneRecord() returned null for the hidden container and the listener threw an exception. - A hidden top-level element that still needed to be localized was dropped from the summary without any error, so editors never saw it needed translation. Both queries now only exclude deleted records, keeping hidden/ start-/endtime-disabled records visible to the listener. --- .../Listener/RecordSummaryForLocalization.php | 4 ++ .../Listener/Fixtures/hidden_element.csv | 7 +++ ...ze_container_child_in_hidden_container.csv | 9 ++++ .../Listener/RecordSummaryForLocalization.php | 52 ++++++++++++++++++- 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 Tests/Functional/Listener/Fixtures/hidden_element.csv create mode 100644 Tests/Functional/Listener/Fixtures/localize_container_child_in_hidden_container.csv diff --git a/Classes/Listener/RecordSummaryForLocalization.php b/Classes/Listener/RecordSummaryForLocalization.php index 3c715269..dee261ae 100644 --- a/Classes/Listener/RecordSummaryForLocalization.php +++ b/Classes/Listener/RecordSummaryForLocalization.php @@ -17,7 +17,9 @@ use TYPO3\CMS\Core\Attribute\AsEventListener; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; use TYPO3\CMS\Core\Information\Typo3Version; +use TYPO3\CMS\Core\Utility\GeneralUtility; #[AsEventListener(identifier: 'tx-container-record-summary-for-localization')] class RecordSummaryForLocalization @@ -96,6 +98,7 @@ protected function resolveRecordColPos(array $record, $localizeRecords): int protected function fetchOneRecord(int $uid): ?array { $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content'); + $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); $row = $queryBuilder->select('*') ->from('tt_content') ->where( @@ -112,6 +115,7 @@ protected function fetchOneRecord(int $uid): ?array protected function fetchAllRecords(array $uids): array { $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content'); + $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); $rows = $queryBuilder->select('*') ->from('tt_content') ->where( diff --git a/Tests/Functional/Listener/Fixtures/hidden_element.csv b/Tests/Functional/Listener/Fixtures/hidden_element.csv new file mode 100644 index 00000000..263dd8a6 --- /dev/null +++ b/Tests/Functional/Listener/Fixtures/hidden_element.csv @@ -0,0 +1,7 @@ +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l18n_parent","hidden" +,"41","1","header","hidden element","256","0","0","0","0","1" +"pages" +,"uid","pid","sys_language_uid","l10n_parent" +,"1","0","0","0" +,"2","0","1","1" diff --git a/Tests/Functional/Listener/Fixtures/localize_container_child_in_hidden_container.csv b/Tests/Functional/Listener/Fixtures/localize_container_child_in_hidden_container.csv new file mode 100644 index 00000000..50132a48 --- /dev/null +++ b/Tests/Functional/Listener/Fixtures/localize_container_child_in_hidden_container.csv @@ -0,0 +1,9 @@ +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l18n_parent","hidden" +,"5","1","b13-2cols-with-header-container","Hidden Container","256","0","0","0","0","1" +,"4","1","header","Child Of Hidden Container","288","0","200","5","0","0" +,"38","1","b13-2cols-with-header-container","Translated Hidden Container","256","1","0","0","5","0" +"pages" +,"uid","pid","sys_language_uid","l10n_parent" +,"1","0","0","0" +,"2","0","1","1" diff --git a/Tests/Functional/Listener/RecordSummaryForLocalization.php b/Tests/Functional/Listener/RecordSummaryForLocalization.php index 055be252..a6214e4e 100644 --- a/Tests/Functional/Listener/RecordSummaryForLocalization.php +++ b/Tests/Functional/Listener/RecordSummaryForLocalization.php @@ -28,7 +28,7 @@ class RecordSummaryForLocalization extends FunctionalTestCase public function childrenIsMovedIntoBackendLayoutColPosIfContainerIsAlreadyTranslated(): void { if ((new Typo3Version())->getMajorVersion() < 14) { - self::markTestSkipped('tested by RecordLocalizeSummaryModifierTest Unit Test'); + self::markTestSkipped('only >= v14 related'); } $records = [ 0 => [ @@ -55,6 +55,56 @@ public function childrenIsMovedIntoBackendLayoutColPosIfContainerIsAlreadyTransl self::assertSame($expected, $records); } + #[Test] + public function childOfHiddenContainerIsKeptInSummaryWhenContainerIsHiddenAndAlreadyTranslated(): void + { + if ((new Typo3Version())->getMajorVersion() < 14) { + self::markTestSkipped('only >= v14 related'); + } + $records = [ + 200 => [ + 0 => ['uid' => 4, 'title' => 'Child Of Hidden Container'], + ], + ]; + $columns = [0 => 'Normal']; + $event = new AfterRecordSummaryForLocalizationEvent($records, $columns); + $this->importCSVDataSet(__DIR__ . '/Fixtures/localize_container_child_in_hidden_container.csv'); + $listener = $this->getContainer()->get(\B13\Container\Listener\RecordSummaryForLocalization::class); + $listener($event); + $records = $event->getRecords(); + $expected = [ + 0 => [ + 0 => ['uid' => 4, 'title' => 'Child Of Hidden Container'], + ], + ]; + self::assertSame($expected, $records); + } + + #[Test] + public function hiddenElementThatShouldBeLocalizedIsKeptInSummary(): void + { + if ((new Typo3Version())->getMajorVersion() < 14) { + self::markTestSkipped('tested by RecordLocalizeSummaryModifierTest Unit Test'); + } + $records = [ + 0 => [ + 0 => ['uid' => 41, 'title' => 'hidden element'], + ], + ]; + $columns = [0 => 'Normal']; + $event = new AfterRecordSummaryForLocalizationEvent($records, $columns); + $this->importCSVDataSet(__DIR__ . '/Fixtures/hidden_element.csv'); + $listener = $this->getContainer()->get(\B13\Container\Listener\RecordSummaryForLocalization::class); + $listener($event); + $records = $event->getRecords(); + $expected = [ + 0 => [ + 0 => ['uid' => 41, 'title' => 'hidden element'], + ], + ]; + self::assertSame($expected, $records); + } + #[Test] public function childrenIsNotMovedIntoBackendLayoutColPosIfContainerShouldBeTranslated(): void {