diff --git a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php index 28b7b57b..a63bcd15 100644 --- a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php +++ b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php @@ -15,6 +15,7 @@ use B13\Container\Domain\Factory\ContainerFactory; use B13\Container\Domain\Factory\Exception; use B13\Container\Domain\Service\ContainerService; +use B13\Container\Tca\Registry; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\DataHandling\DataHandler; @@ -24,8 +25,11 @@ #[Autoconfigure(public: true)] class CommandMapPostProcessingHook { - public function __construct(protected ContainerFactory $containerFactory, protected ContainerService $containerService) - { + public function __construct( + protected ContainerFactory $containerFactory, + protected ContainerService $containerService, + protected Registry $tcaRegistry + ) { } public function processCmdmap_postProcess(string $command, string $table, $id, $value, DataHandler $dataHandler, $pasteUpdate, $pasteDatamap): void @@ -145,7 +149,17 @@ protected function copyOrMoveChildren(int $origUid, int $newId, int $containerId && isset($allChildren[0]) && (int)$targetLanguage !== (int)$allChildren[0]['sys_language_uid']; $children = []; + // the target of a column is the last child of the previous column (see + // ContainerService::getNewContentElementAtTopTargetInColumn()), so the columns have to be + // processed in the configured order: the children are ordered by sorting, and a column + // whose child has a low sorting must not be processed before the column it targets. + // children with a colPos that is not configured for the container are moved last $colPosVals = $container->getChildrenColPos(); + $availableColPosVals = $this->tcaRegistry->getAllAvailableColumnsColPos($container->getCType()); + $colPosVals = array_values(array_merge( + array_intersect($availableColPosVals, $colPosVals), + array_diff($colPosVals, $availableColPosVals) + )); foreach ($colPosVals as $colPos) { $childrenByColPos = $container->getChildrenByColPos($colPos); if (!$chainAfterPrevious) { diff --git a/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php b/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php index 8e79b212..eccd2277 100644 --- a/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php +++ b/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php @@ -414,4 +414,35 @@ public function copyContainerWithLanguageAsStringKeepsCopiedChildrenSorting(): v $this->dataHandler->process_cmdmap(); self::assertCSVDataSet(__DIR__ . '/Fixtures/Container/CopyContainerKeepsSortingOfChildrenResult.csv'); } + + /** + * children are moved column by column, the target of a column is the last child of the previous + * column. the children are fetched ordered by sorting, so if the child with the lowest sorting is + * not in the first column, the target of the following columns is a child that has not been moved + * yet and the child ends up on the source page. + * + * such a sorting is inconsistent and is reported by the container:sorting command, but it can be + * the result of copy and move operations or of an import, so a container has to be moved completely + * in that case, too. + * + * @see https://github.com/b13/container/issues/291 + * @see https://github.com/b13/container/issues/738 + */ + #[Test] + public function moveContainerOtherPageOnTopWithChildSortingBeforePreviousColumn(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/Container/MoveContainerOtherPageOnTop.csv'); + // child in the second column gets a sorting lower than the children of the first column + $this->getConnectionPool()->getConnectionForTable('tt_content')->update('tt_content', ['sorting' => 96], ['uid' => 4]); + $cmdmap = [ + 'tt_content' => [ + 1 => [ + 'move' => 3, + ], + ], + ]; + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_cmdmap(); + self::assertCSVDataSet(__DIR__ . '/Fixtures/Container/MoveContainerOtherPageOnTopWithChildSortingBeforePreviousColumnResult.csv'); + } } diff --git a/Tests/Functional/Datahandler/DefaultLanguage/Fixtures/Container/MoveContainerOtherPageOnTopWithChildSortingBeforePreviousColumnResult.csv b/Tests/Functional/Datahandler/DefaultLanguage/Fixtures/Container/MoveContainerOtherPageOnTopWithChildSortingBeforePreviousColumnResult.csv new file mode 100644 index 00000000..75322627 --- /dev/null +++ b/Tests/Functional/Datahandler/DefaultLanguage/Fixtures/Container/MoveContainerOtherPageOnTopWithChildSortingBeforePreviousColumnResult.csv @@ -0,0 +1,11 @@ +"pages" +,"uid","pid","title" +,1,0,"" +,3,0,"" +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent","l18n_parent" +,1,3,"b13-2cols-with-header-container","container",256,0,0,0,0 +,2,3,"header","col1-first",320,0,200,1,0 +,3,3,"header","col1-second",384,0,200,1,0 +,4,3,"header","col2-first",448,0,201,1,0 +,10,3,"header","outside",512,0,0,0,0