From a1cc4de40d8d8bd005b0f21d2605dda9f1cdb000 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Sat, 27 Nov 2021 16:24:39 +0100 Subject: [PATCH] [BUGFIX] !!! sorting of content elements behaviour of sorting is changed in two ways: * element at first position in container column * old: pid was used as target (leeds to broken sorting) * new: use -uid of container element for first column * new: use -uid of previous column child (if exists), (else -uid of container) * element after a container * old: -uid of container is used (leeds to broken sorting) * new: -uid of last child in containers last column is used we shift a migration command to fix broken sorting: dry-run: container:sorting run: container:sorting 0 must be called multiple for nested containers Fixes: #149 --- Classes/Backend/Grid/ContainerGridColumn.php | 14 +- .../Preview/ContainerPreviewRenderer.php | 20 +- Classes/Command/SortingCommand.php | 60 ++++++ .../ContainerColumnConfigurationService.php | 1 - .../ContentDefender/Xclasses/DatamapHook.php | 6 + Classes/Domain/Service/ContainerService.php | 72 +++++++ .../Datahandler/CommandMapAfterFinishHook.php | 2 +- .../Datahandler/CommandMapBeforeStartHook.php | 94 +++++++++- .../CommandMapPostProcessingHook.php | 1 + Classes/Hooks/Datahandler/Database.php | 30 ++- .../Datahandler/DatamapBeforeStartHook.php | 22 ++- .../DatamapPreProcessFieldArrayHook.php | 128 +++++++++++++ Classes/Integrity/Database.php | 65 ++++++- Classes/Integrity/Sorting.php | 176 ++++++++++++++++++ Classes/Tca/Registry.php | 10 + Classes/View/ContainerLayoutView.php | 16 +- Configuration/Commands.php | 3 + Configuration/Services.yaml | 6 + README.md | 2 +- .../Datahandler/DatahandlerTest.php | 2 +- .../DefaultLanguage/ContainerTest.php | 12 ++ .../CopyContainerInContainerTest.php | 5 + .../CopyElementClipboardOtherPageTest.php | 58 ++++++ .../CopyElementClipboardTest.php | 4 + .../MoveElementClipboardOtherPageTest.php | 57 ++++++ .../MoveElementClipboardTest.php | 4 + .../DefaultLanguage/NewElementTest.php | 42 +++++ .../CopyToLanguageSortingTest.php | 114 ++++++++++++ .../Localization/FreeMode/ContainerTest.php | 7 + .../CopyElementClipboardOtherPageTest.php | 4 + .../FreeMode/CopyElementClipboardTest.php | 4 + .../MoveElementClipboardOtherPageTest.php | 4 + .../FreeMode/MoveElementClipboardTest.php | 4 + .../Localization/FreeMode/NewElementTest.php | 43 +++++ .../Datahandler/Workspace/ContainerTest.php | 3 +- .../localize_child_after_child.xml | 66 +++++++ .../localize_child_at_top.xml | 66 +++++++ .../localize_containers.xml | 64 +++++++ .../Fixtures/copy_container_in_container.xml | 4 +- .../Fixtures/new_element_after_container.xml | 28 +++ .../new_element_after_container_free_mode.xml | 39 ++++ ...tt_content_default_language_other_page.xml | 4 +- .../tt_content_translations_free_mode.xml | 4 +- ...tent_translations_free_mode_other_page.xml | 4 +- .../Domain/Service/ContainerServiceTest.php | 77 ++++++++ .../CommandMapBeforeStartHookTest.php | 55 ++++++ ext_localconf.php | 1 + 47 files changed, 1473 insertions(+), 34 deletions(-) create mode 100644 Classes/Command/SortingCommand.php create mode 100644 Classes/Domain/Service/ContainerService.php create mode 100644 Classes/Hooks/Datahandler/DatamapPreProcessFieldArrayHook.php create mode 100644 Classes/Integrity/Sorting.php create mode 100644 Tests/Functional/Datahandler/DefaultLanguage/NewElementTest.php create mode 100644 Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php create mode 100644 Tests/Functional/Datahandler/Localization/FreeMode/NewElementTest.php create mode 100644 Tests/Functional/Fixtures/CopyToLanguageSorting/localize_child_after_child.xml create mode 100644 Tests/Functional/Fixtures/CopyToLanguageSorting/localize_child_at_top.xml create mode 100644 Tests/Functional/Fixtures/CopyToLanguageSorting/localize_containers.xml create mode 100644 Tests/Functional/Fixtures/new_element_after_container.xml create mode 100644 Tests/Functional/Fixtures/new_element_after_container_free_mode.xml create mode 100644 Tests/Unit/Domain/Service/ContainerServiceTest.php diff --git a/Classes/Backend/Grid/ContainerGridColumn.php b/Classes/Backend/Grid/ContainerGridColumn.php index c57256ad..1fd89e43 100644 --- a/Classes/Backend/Grid/ContainerGridColumn.php +++ b/Classes/Backend/Grid/ContainerGridColumn.php @@ -24,11 +24,19 @@ class ContainerGridColumn extends GridColumn protected $allowNewContentElements = true; - public function __construct(PageLayoutContext $context, array $columnDefinition, Container $container, bool $allowNewContentElements = true) - { + protected $newContentElementAtTopTarget; + + public function __construct( + PageLayoutContext $context, + array $columnDefinition, + Container $container, + int $newContentElementAtTopTarget, + bool $allowNewContentElements = true + ) { parent::__construct($context, $columnDefinition); $this->container = $container; $this->allowNewContentElements = $allowNewContentElements; + $this->newContentElementAtTopTarget = $newContentElementAtTopTarget; } public function getContainerUid(): int @@ -63,7 +71,7 @@ public function getNewContentUrl(): string 'sys_language_uid' => $this->container->getLanguage(), 'colPos' => $this->getColumnNumber(), 'tx_container_parent' => $this->container->getUidOfLiveWorkspace(), - 'uid_pid' => $pageId, + 'uid_pid' => $this->newContentElementAtTopTarget, 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI'), ]; $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); diff --git a/Classes/Backend/Preview/ContainerPreviewRenderer.php b/Classes/Backend/Preview/ContainerPreviewRenderer.php index fad1272d..c5c7f9f1 100644 --- a/Classes/Backend/Preview/ContainerPreviewRenderer.php +++ b/Classes/Backend/Preview/ContainerPreviewRenderer.php @@ -17,6 +17,7 @@ use B13\Container\ContentDefender\ContainerColumnConfigurationService; use B13\Container\Domain\Factory\Exception; use B13\Container\Domain\Factory\PageView\Backend\ContainerFactory; +use B13\Container\Domain\Service\ContainerService; use B13\Container\Tca\Registry; use TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer; use TYPO3\CMS\Backend\Utility\BackendUtility; @@ -44,11 +45,21 @@ class ContainerPreviewRenderer extends StandardContentPreviewRenderer */ protected $containerColumnConfigurationService; - public function __construct(Registry $tcaRegistry = null, ContainerFactory $containerFactory = null, ContainerColumnConfigurationService $containerColumnConfigurationService = null) - { + /** + * @var ContainerService + */ + protected $containerService; + + public function __construct( + Registry $tcaRegistry = null, + ContainerFactory $containerFactory = null, + ContainerColumnConfigurationService $containerColumnConfigurationService = null, + ContainerService $containerService = null + ) { $this->tcaRegistry = $tcaRegistry ?? GeneralUtility::makeInstance(Registry::class); $this->containerFactory = $containerFactory ?? GeneralUtility::makeInstance(ContainerFactory::class); $this->containerColumnConfigurationService = $containerColumnConfigurationService ?? GeneralUtility::makeInstance(ContainerColumnConfigurationService::class); + $this->containerService = $containerService ?? GeneralUtility::makeInstance(ContainerService::class); } public function renderPageModulePreviewContent(GridColumnItem $item): string @@ -67,10 +78,11 @@ public function renderPageModulePreviewContent(GridColumnItem $item): string foreach ($containerGrid as $row => $cols) { $rowObject = GeneralUtility::makeInstance(GridRow::class, $context); foreach ($cols as $col) { + $newContentElementAtTopTarget = $this->containerService->getNewContentElementAtTopTargetInColumn($container, $col['colPos']); if ($this->containerColumnConfigurationService->isMaxitemsReached($container, $col['colPos'])) { - $columnObject = GeneralUtility::makeInstance(ContainerGridColumn::class, $context, $col, $container, false); + $columnObject = GeneralUtility::makeInstance(ContainerGridColumn::class, $context, $col, $container, $newContentElementAtTopTarget, false); } else { - $columnObject = GeneralUtility::makeInstance(ContainerGridColumn::class, $context, $col, $container); + $columnObject = GeneralUtility::makeInstance(ContainerGridColumn::class, $context, $col, $container, $newContentElementAtTopTarget); } $rowObject->addColumn($columnObject); if (isset($col['colPos'])) { diff --git a/Classes/Command/SortingCommand.php b/Classes/Command/SortingCommand.php new file mode 100644 index 00000000..240a9c24 --- /dev/null +++ b/Classes/Command/SortingCommand.php @@ -0,0 +1,60 @@ +addArgument('dryrun', InputArgument::OPTIONAL, 'do not execute queries', true); + } + + public function __construct(string $name = null, Sorting $sorting = null) + { + parent::__construct($name); + $this->sorting = $sorting ?? GeneralUtility::makeInstance(Sorting::class); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + */ + public function execute(InputInterface $input, OutputInterface $output): int + { + $dryrun = (bool)$input->getArgument('dryrun'); + Bootstrap::initializeBackendAuthentication(); + Bootstrap::initializeLanguageObject(); + $errors = $this->sorting->run($dryrun); + foreach ($errors as $error) { + $output->writeln($error); + } + if (empty($errors)) { + $output->writeln('migration finished'); + } + return 0; + } +} diff --git a/Classes/ContentDefender/ContainerColumnConfigurationService.php b/Classes/ContentDefender/ContainerColumnConfigurationService.php index cd739e8c..2e619f41 100644 --- a/Classes/ContentDefender/ContainerColumnConfigurationService.php +++ b/Classes/ContentDefender/ContainerColumnConfigurationService.php @@ -73,7 +73,6 @@ public function setContainerIsCopied($containerId): void public function getTargetColPosForNew(int $containerId, int $colPos): ?int { - //var_dump($containerId); if (isset($this->copyMapping[$containerId . '-' . $colPos])) { return $this->copyMapping[$containerId . '-' . $colPos]['targetColPos']; } diff --git a/Classes/ContentDefender/Xclasses/DatamapHook.php b/Classes/ContentDefender/Xclasses/DatamapHook.php index 88eb29fc..f89ce82e 100644 --- a/Classes/ContentDefender/Xclasses/DatamapHook.php +++ b/Classes/ContentDefender/Xclasses/DatamapHook.php @@ -101,6 +101,12 @@ protected function isRecordAllowedByRestriction(array $columnConfiguration, arra $this->mapping[$record['uid']]['containerId'], $this->mapping[$record['uid']]['colPos'] ); + } elseif ($record['tx_container_parent'] > 0) { + $columnConfiguration = $this->containerColumnConfigurationService->override( + $columnConfiguration, + (int)$record['tx_container_parent'], + (int)$record['colPos'] + ); } return parent::isRecordAllowedByRestriction($columnConfiguration, $record); } diff --git a/Classes/Domain/Service/ContainerService.php b/Classes/Domain/Service/ContainerService.php new file mode 100644 index 00000000..9fb0bc60 --- /dev/null +++ b/Classes/Domain/Service/ContainerService.php @@ -0,0 +1,72 @@ +tcaRegistry = $tcaRegistry ?? GeneralUtility::makeInstance(Registry::class); + $this->containerFactory = $containerFactory ?? GeneralUtility::makeInstance(ContainerFactory::class); + } + + public function getNewContentElementAtTopTargetInColumn(Container $container, int $targetColPos): int + { + $target = -$container->getUid(); + $previousRecord = null; + $allColumns = $this->tcaRegistry->getAllAvailableColumnsColPos($container->getCType()); + foreach ($allColumns as $colPos) { + if ($colPos === $targetColPos && $previousRecord !== null) { + $target = -$previousRecord['uid']; + } + $children = $container->getChildrenByColPos($colPos); + if (!empty($children)) { + $last = array_pop($children); + $previousRecord = $last; + } + } + return $target; + } + + public function getAfterContainerElementTarget(Container $container): int + { + $target = -$container->getUid(); + $containerRecord = $container->getContainerRecord(); + $childRecords = $container->getChildRecords(); + if (empty($childRecords)) { + return $target; + } + $lastChild = array_pop($childRecords); + if (!$this->tcaRegistry->isContainerElement($lastChild['CType'])) { + return -$lastChild['uid']; + } + $container = $this->containerFactory->buildContainer($lastChild['uid']); + return $this->getAfterContainerElementTarget($container); + } +} diff --git a/Classes/Hooks/Datahandler/CommandMapAfterFinishHook.php b/Classes/Hooks/Datahandler/CommandMapAfterFinishHook.php index 244a9290..89a4f7bd 100644 --- a/Classes/Hooks/Datahandler/CommandMapAfterFinishHook.php +++ b/Classes/Hooks/Datahandler/CommandMapAfterFinishHook.php @@ -64,7 +64,7 @@ public function processCmdmap_afterFinish(DataHandler $dataHandler): void // copied from non default language (connectecd mode) children if ($copiedFromChild['sys_language_uid'] > 0 && $copiedFromChild['l18n_parent'] > 0) { // fetch orig container - $origContainer = $this->database->fetchOneTranslatedRecord($copiedFromChild['tx_container_parent'], $copiedFromChild['sys_language_uid']); + $origContainer = $this->database->fetchOneTranslatedRecordByl18nParent($copiedFromChild['tx_container_parent'], $copiedFromChild['sys_language_uid']); // should never be null if ($origContainer !== null) { $freeModeContainer = $this->database->fetchContainerRecordLocalizedFreeMode((int)$origContainer['uid'], $copyToLanguage); diff --git a/Classes/Hooks/Datahandler/CommandMapBeforeStartHook.php b/Classes/Hooks/Datahandler/CommandMapBeforeStartHook.php index db13a83b..a867abeb 100644 --- a/Classes/Hooks/Datahandler/CommandMapBeforeStartHook.php +++ b/Classes/Hooks/Datahandler/CommandMapBeforeStartHook.php @@ -13,6 +13,8 @@ */ use B13\Container\Domain\Factory\ContainerFactory; +use B13\Container\Domain\Factory\Exception; +use B13\Container\Domain\Service\ContainerService; use B13\Container\Tca\Registry; use TYPO3\CMS\Core\DataHandling\DataHandler; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -35,20 +37,28 @@ class CommandMapBeforeStartHook */ protected $database; + /** + * @var ContainerService + */ + protected $containerService; + /** * UsedRecords constructor. * @param ContainerFactory|null $containerFactory * @param Registry|null $tcaRegistry * @param Database|null $database + * @param ContainerService|null $containerService */ public function __construct( ContainerFactory $containerFactory = null, Registry $tcaRegistry = null, - Database $database = null + Database $database = null, + ContainerService $containerService = null ) { $this->containerFactory = $containerFactory ?? GeneralUtility::makeInstance(ContainerFactory::class); $this->tcaRegistry = $tcaRegistry ?? GeneralUtility::makeInstance(Registry::class); $this->database = $database ?? GeneralUtility::makeInstance(Database::class); + $this->containerService = $containerService ?? GeneralUtility::makeInstance(ContainerService::class); } /** * @param DataHandler $dataHandler @@ -58,6 +68,86 @@ public function processCmdmap_beforeStart(DataHandler $dataHandler): void $this->unsetInconsistentLocalizeCommands($dataHandler); $dataHandler->cmdmap = $this->rewriteSimpleCommandMap($dataHandler->cmdmap); $dataHandler->cmdmap = $this->extractContainerIdFromColPosOnUpdate($dataHandler->cmdmap); + // previously page id is used for copy/moving element at top of a container colum + // but this leeds to wrong sorting in page context (e.g. List-Module) + $dataHandler->cmdmap = $this->rewriteCommandMapTargetForTopAtContainer($dataHandler->cmdmap); + $dataHandler->cmdmap = $this->rewriteCommandMapTargetForAfterContainer($dataHandler->cmdmap); + } + + protected function rewriteCommandMapTargetForAfterContainer(array $cmdmap): array + { + if (!empty($cmdmap['tt_content'])) { + foreach ($cmdmap['tt_content'] as $id => &$cmd) { + foreach ($cmd as $operation => $value) { + if (in_array($operation, ['copy', 'move'], true) === false) { + continue; + } + if ( + (!isset($value['update']['tx_container_parent']) || (int)$value['update']['tx_container_parent'] === 0) && + ((is_array($value) && $value['target'] < 0) || (int)$value < 0) + ) { + if (is_array($value)) { + $target = -(int)$value['target']; + } else { + // simple command + $target = -(int)$value; + } + $record = $this->database->fetchOneRecord($target); + if ($record['tx_container_parent'] > 0) { + // elements in container have already correct target + continue; + } + if (!$this->tcaRegistry->isContainerElement($record['CType'])) { + continue; + } + try { + $container = $this->containerFactory->buildContainer($record['uid']); + $target = $this->containerService->getAfterContainerElementTarget($container); + if (is_array($value)) { + $cmd[$operation]['target'] = $target; + } else { + // simple command + $cmd[$operation] = $target; + } + } catch (Exception $e) { + continue; + } + } + } + } + } + return $cmdmap; + } + + protected function rewriteCommandMapTargetForTopAtContainer(array $cmdmap): array + { + if (!empty($cmdmap['tt_content'])) { + foreach ($cmdmap['tt_content'] as $id => &$cmd) { + foreach ($cmd as $operation => $value) { + if (in_array($operation, ['copy', 'move'], true) === false) { + continue; + } + + if ( + isset($value['update']) && + isset($value['update']['tx_container_parent']) && + $value['update']['tx_container_parent'] > 0 && + isset($value['update']['colPos']) && + $value['update']['colPos'] > 0 && + $value['target'] > 0 + ) { + try { + $container = $this->containerFactory->buildContainer((int)$value['update']['tx_container_parent']); + $target = $this->containerService->getNewContentElementAtTopTargetInColumn($container, (int)$value['update']['colPos']); + $cmd[$operation]['target'] = $target; + } catch (Exception $e) { + // not a container + } + } + } + } + } + return $cmdmap; } protected function rewriteSimpleCommandMap(array $cmdmap): array @@ -111,7 +201,7 @@ protected function unsetInconsistentLocalizeCommands(DataHandler $dataHandler): // should not happen continue; } - $translatedContainer = $this->database->fetchOneTranslatedRecord($container['uid'], (int)$data); + $translatedContainer = $this->database->fetchOneTranslatedRecordByl18nParent($container['uid'], (int)$data); if ($translatedContainer === null || (int)$translatedContainer['l18n_parent'] === 0) { $dataHandler->log( 'tt_content', diff --git a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php index 86d4427f..3455bb10 100644 --- a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php +++ b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php @@ -64,6 +64,7 @@ protected function localizeOrCopyToLanguage(int $uid, int $language, string $com try { $container = $this->containerFactory->buildContainer($uid); $children = $container->getChildRecords(); + $children = array_reverse($children); $cmd = ['tt_content' => []]; foreach ($children as $colPos => $record) { $cmd['tt_content'][$record['uid']] = [$command => $language]; diff --git a/Classes/Hooks/Datahandler/Database.php b/Classes/Hooks/Datahandler/Database.php index c9a9d969..0dd13fe1 100644 --- a/Classes/Hooks/Datahandler/Database.php +++ b/Classes/Hooks/Datahandler/Database.php @@ -75,12 +75,30 @@ public function fetchOverlayRecords(array $record): array return $records; } - /** - * @param int $uid - * @param int $language - * @return array - */ - public function fetchOneTranslatedRecord(int $uid, int $language): ?array + public function fetchOneTranslatedRecordByl10nSource(int $uid, int $language): ?array + { + $queryBuilder = $this->getQueryBuilder(); + $record = $queryBuilder->select('*') + ->from('tt_content') + ->where( + $queryBuilder->expr()->eq( + 'l10n_source', + $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) + ), + $queryBuilder->expr()->eq( + 'sys_language_uid', + $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT) + ) + ) + ->execute() + ->fetch(); + if ($record === false) { + return null; + } + return $record; + } + + public function fetchOneTranslatedRecordByl18nParent(int $uid, int $language): ?array { $queryBuilder = $this->getQueryBuilder(); $record = $queryBuilder->select('*') diff --git a/Classes/Hooks/Datahandler/DatamapBeforeStartHook.php b/Classes/Hooks/Datahandler/DatamapBeforeStartHook.php index defc3d7e..7025ac11 100644 --- a/Classes/Hooks/Datahandler/DatamapBeforeStartHook.php +++ b/Classes/Hooks/Datahandler/DatamapBeforeStartHook.php @@ -14,6 +14,8 @@ use B13\Container\Domain\Factory\ContainerFactory; use B13\Container\Domain\Factory\Exception; +use B13\Container\Domain\Service\ContainerService; +use B13\Container\Tca\Registry; use TYPO3\CMS\Core\DataHandling\DataHandler; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; @@ -30,14 +32,30 @@ class DatamapBeforeStartHook */ protected $database; + /** + * @var ContainerService + */ + protected $containerService; + + /** + * @var Registry + */ + protected $tcaRegistry; + /** * @param ContainerFactory|null $containerFactory * @param Database|null $database */ - public function __construct(ContainerFactory $containerFactory = null, Database $database = null) - { + public function __construct( + ContainerFactory $containerFactory = null, + Database $database = null, + Registry $tcaRegistry = null, + ContainerService $containerService = null + ) { $this->containerFactory = $containerFactory ?? GeneralUtility::makeInstance(ContainerFactory::class); $this->database = $database ?? GeneralUtility::makeInstance(Database::class); + $this->tcaRegistry = $tcaRegistry ?? GeneralUtility::makeInstance(Registry::class); + $this->containerService = $containerService ?? GeneralUtility::makeInstance(ContainerService::class); } /** diff --git a/Classes/Hooks/Datahandler/DatamapPreProcessFieldArrayHook.php b/Classes/Hooks/Datahandler/DatamapPreProcessFieldArrayHook.php new file mode 100644 index 00000000..ba3f4ee7 --- /dev/null +++ b/Classes/Hooks/Datahandler/DatamapPreProcessFieldArrayHook.php @@ -0,0 +1,128 @@ +containerFactory = $containerFactory ?? GeneralUtility::makeInstance(ContainerFactory::class); + $this->database = $database ?? GeneralUtility::makeInstance(Database::class); + $this->tcaRegistry = $tcaRegistry ?? GeneralUtility::makeInstance(Registry::class); + $this->containerService = $containerService ?? GeneralUtility::makeInstance(ContainerService::class); + } + + protected function newElementAfterContainer(array $incomingFieldArray): array + { + if (isset($incomingFieldArray['tx_container_parent']) && (int)$incomingFieldArray['tx_container_parent'] > 0) { + return $incomingFieldArray; + } + $record = $this->database->fetchOneRecord(-(int)$incomingFieldArray['pid']); + if ($record['tx_container_parent'] > 0) { + // new elements in container have already correct target + return $incomingFieldArray; + } + if (!$this->tcaRegistry->isContainerElement($record['CType'])) { + return $incomingFieldArray; + } + try { + $container = $this->containerFactory->buildContainer($record['uid']); + $incomingFieldArray['pid'] = $this->containerService->getAfterContainerElementTarget($container); + } catch (Exception $e) { + } + return $incomingFieldArray; + } + + protected function copyToLanguageElementInContainer(array $incomingFieldArray): array + { + if (!isset($incomingFieldArray['tx_container_parent']) || (int)$incomingFieldArray['tx_container_parent'] === 0) { + return $incomingFieldArray; + } + if (!isset($incomingFieldArray['l10n_source']) || (int)$incomingFieldArray['l10n_source'] === 0) { + return $incomingFieldArray; + } + if (!isset($incomingFieldArray['l18n_parent']) || (int)$incomingFieldArray['l18n_parent'] > 0) { + return $incomingFieldArray; + } + if (!isset($incomingFieldArray['sys_language_uid']) || (int)$incomingFieldArray['sys_language_uid'] === 0) { + return $incomingFieldArray; + } + $record = $this->database->fetchOneRecord(-$incomingFieldArray['pid']); + $translatedContainerRecord = $this->database->fetchOneTranslatedRecordByl10nSource((int)$incomingFieldArray['tx_container_parent'], (int)$incomingFieldArray['sys_language_uid']); + if ($translatedContainerRecord === null) { + return $incomingFieldArray; + } + try { + $incomingFieldArray['tx_container_parent'] = $translatedContainerRecord['uid']; + $container = $this->containerFactory->buildContainer($translatedContainerRecord['uid']); + if ((int)$record['sys_language_uid'] === 0 || empty($container->getChildrenByColPos((int)$incomingFieldArray['colPos']))) { + $target = $this->containerService->getNewContentElementAtTopTargetInColumn($container, (int)$incomingFieldArray['colPos']); + $incomingFieldArray['pid'] = $target; + } + } catch (Exception $e) { + // not a container + } + return $incomingFieldArray; + } + + public function processDatamap_preProcessFieldArray(array &$incomingFieldArray, string $table, $id, DataHandler $dataHandler): void + { + if ($table !== 'tt_content') { + return; + } + if (MathUtility::canBeInterpretedAsInteger($id)) { + return; + } + if (!isset($incomingFieldArray['pid']) || (int)$incomingFieldArray['pid'] >= 0) { + return; + } + $incomingFieldArray = $this->newElementAfterContainer($incomingFieldArray); + $incomingFieldArray = $this->copyToLanguageElementInContainer($incomingFieldArray); + } +} diff --git a/Classes/Integrity/Database.php b/Classes/Integrity/Database.php index b4c1e605..ba482754 100644 --- a/Classes/Integrity/Database.php +++ b/Classes/Integrity/Database.php @@ -21,7 +21,7 @@ class Database implements SingletonInterface { - private $fields = ['uid', 'pid', 'sys_language_uid', 'CType', 'l18n_parent', 't3_origuid', 'colPos', 'tx_container_parent', 'l10n_source', 'hidden']; + private $fields = ['uid', 'pid', 'sys_language_uid', 'CType', 'l18n_parent', 't3_origuid', 'colPos', 'tx_container_parent', 'l10n_source', 'hidden', 'sorting']; /** * @return QueryBuilder @@ -134,6 +134,34 @@ public function getContainerRecords(array $cTypes): array return $rows; } + public function getContainerRecordsFreeMode(array $cTypes): array + { + $queryBuilder = $this->getQueryBuilder(); + $stm = $queryBuilder + ->select(...$this->fields) + ->from('tt_content') + ->where( + $queryBuilder->expr()->in( + 'CType', + $queryBuilder->createNamedParameter($cTypes, Connection::PARAM_STR_ARRAY) + ), + $queryBuilder->expr()->neq( + 'sys_language_uid', + $queryBuilder->createNamedParameter(0, Connection::PARAM_INT) + ), + $queryBuilder->expr()->eq( + 'l18n_parent', + $queryBuilder->createNamedParameter(0, Connection::PARAM_INT) + ) + ) + ->execute(); + $rows = []; + while ($result = $stm->fetch()) { + $rows[$result['uid']] = $result; + } + return $rows; + } + /** * @return array */ @@ -160,4 +188,39 @@ public function getContainerChildRecords(): array } return $rows; } + + public function getContentElementAfter(array $record): ?array + { + // todo not required + $queryBuilder = $this->getQueryBuilder(); + $row = $queryBuilder + ->select(...$this->fields) + ->from('tt_content') + ->where( + $queryBuilder->expr()->gt( + 'sorting', + $queryBuilder->createNamedParameter($record['sorting'], Connection::PARAM_INT) + ), + $queryBuilder->expr()->eq( + 'sys_language_uid', + $queryBuilder->createNamedParameter($record['sys_language_uid'], Connection::PARAM_INT) + ), + $queryBuilder->expr()->eq( + 'pid', + $queryBuilder->createNamedParameter($record['pid'], Connection::PARAM_INT) + ), + $queryBuilder->expr()->eq( + 'colPos', + $queryBuilder->createNamedParameter($record['colPos'], Connection::PARAM_INT) + ) + ) + ->orderBy('sorting') + ->setMaxResults(1) + ->execute() + ->fetch(); + if ($row === false) { + return null; + } + return $row; + } } diff --git a/Classes/Integrity/Sorting.php b/Classes/Integrity/Sorting.php new file mode 100644 index 00000000..f70f0e89 --- /dev/null +++ b/Classes/Integrity/Sorting.php @@ -0,0 +1,176 @@ +database = $database ?? GeneralUtility::makeInstance(Database::class); + $this->tcaRegistry = $tcaRegistry ?? GeneralUtility::makeInstance(Registry::class); + $this->containerFactory = $containerFactory ?? GeneralUtility::makeInstance(ContainerFactory::class); + } + + public function run(bool $dryRun = true): array + { + $cTypes = $this->tcaRegistry->getRegisteredCTypes(); + $containerRecords = $this->database->getContainerRecords($cTypes); + $containerRecords = array_merge($containerRecords, $this->database->getContainerRecordsFreeMode($cTypes)); + $colPosByCType = []; + foreach ($cTypes as $cType) { + $columns = $this->tcaRegistry->getAvailableColumns($cType); + $colPosByCType[$cType] = []; + foreach ($columns as $column) { + $colPosByCType[$cType][] = $column['colPos']; + } + } + $this->fixChildrenSorting($containerRecords, $colPosByCType, $dryRun); + // todo not required ? + //$this->fixElementAfterContainerSorting($containerRecords, $colPosByCType, $dryRun); + return $this->errors; + } + + protected function fixElementAfterContainerSorting(array $containerRecords, array $colPosByCType, bool $dryRun): void + { + foreach ($containerRecords as $containerRecord) { + $nextElement = $this->database->getContentElementAfter($containerRecord); + if ($nextElement !== null) { + try { + $container = $this->containerFactory->buildContainer($containerRecord['uid']); + } catch (Exception $e) { + // should not happend + continue; + } + $lastChild = null; + foreach ($colPosByCType[$containerRecord['CType']] as $colPos) { + $children = $container->getChildrenByColPos($colPos); + foreach ($children as $child) { + $lastChild = $child; + } + } + if ($lastChild !== null && $lastChild['sorting'] > $nextElement['sorting']) { + $this->errors[] = 'container uid: ' . $containerRecord['uid'] . ', pid ' . $containerRecord['pid'] . ' must be fixed'; + } + } + } + } + + protected function fixChildrenSortingUpdateRequired(Container $container, array $colPosByCType): bool + { + $containerRecord = $container->getContainerRecord(); + $prevSorting = $containerRecord['sorting']; + foreach ($colPosByCType[$containerRecord['CType']] as $colPos) { + $children = $container->getChildrenByColPos($colPos); + foreach ($children as $child) { + if ($child['sorting'] <= $prevSorting) { + $this->errors[] = 'container uid: ' . $containerRecord['uid'] . ', pid ' . $containerRecord['pid'] . ' must be fixed'; + return true; + } + $prevSorting = $child['sorting']; + } + } + return false; + } + + protected function fixChildrenSorting(array $containerRecords, array $colPosByCType, bool $dryRun): void + { + $datahandler = GeneralUtility::makeInstance(DataHandler::class); + $datahandler->enableLogging = false; + foreach ($containerRecords as $containerRecord) { + try { + $container = $this->containerFactory->buildContainer($containerRecord['uid']); + } catch (Exception $e) { + // should not happend + continue; + } + if ($this->fixChildrenSortingUpdateRequired($container, $colPosByCType) === false || $dryRun === true) { + continue; + } + $prevChild = null; + foreach ($colPosByCType[$containerRecord['CType']] as $colPos) { + $children = $container->getChildrenByColPos($colPos); + if (empty($children)) { + continue; + } + foreach ($children as $child) { + if ($prevChild === null) { + $cmdmap = [ + 'tt_content' => [ + $child['uid'] => [ + 'move' => [ + 'action' => 'paste', + 'target' => $container->getPid(), + 'update' => [ + 'colPos' => $container->getUid() . '-' . $child['colPos'], + 'sys_language_uid' => $containerRecord['sys_language_uid'], + + ], + ], + ], + ], + ]; + $datahandler->start([], $cmdmap); + $datahandler->process_datamap(); + $datahandler->process_cmdmap(); + } else { + $cmdmap = [ + 'tt_content' => [ + $child['uid'] => [ + 'move' => [ + 'action' => 'paste', + 'target' => -$prevChild['uid'], + 'update' => [ + 'colPos' => $container->getUid() . '-' . $child['colPos'], + 'sys_language_uid' => $containerRecord['sys_language_uid'], + + ], + ], + ], + ], + ]; + $datahandler->start([], $cmdmap); + $datahandler->process_datamap(); + $datahandler->process_cmdmap(); + } + $prevChild = $child; + } + } + } + } +} diff --git a/Classes/Tca/Registry.php b/Classes/Tca/Registry.php index 1e3a5224..4f7fc09d 100644 --- a/Classes/Tca/Registry.php +++ b/Classes/Tca/Registry.php @@ -157,6 +157,16 @@ public function getContentDefenderConfiguration(string $cType, int $colPos): arr return $contentDefenderConfiguration; } + public function getAllAvailableColumnsColPos(string $cType): array + { + $columns = $this->getAvailableColumns($cType); + $availableColumnsColPos = []; + foreach ($columns as $column) { + $availableColumnsColPos[] = $column['colPos']; + } + return $availableColumnsColPos; + } + /** * @param string $cType * @param int $colPos diff --git a/Classes/View/ContainerLayoutView.php b/Classes/View/ContainerLayoutView.php index e3250275..3556c730 100644 --- a/Classes/View/ContainerLayoutView.php +++ b/Classes/View/ContainerLayoutView.php @@ -13,6 +13,7 @@ use B13\Container\ContentDefender\ContainerColumnConfigurationService; use B13\Container\Domain\Factory\PageView\Backend\ContainerFactory; use B13\Container\Domain\Model\Container; +use B13\Container\Domain\Service\ContainerService; use B13\Container\Tca\Registry; use Psr\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Backend\Routing\UriBuilder; @@ -48,6 +49,11 @@ class ContainerLayoutView extends PageLayoutView */ protected $containerColumnConfigurationService; + /** + * @var ContainerService + */ + protected $containerService; + /** * variable and calls can be dropped on v10 * @var int @@ -70,11 +76,13 @@ public function __construct( EventDispatcherInterface $eventDispatcher = null, ContainerFactory $containerFactory = null, Registry $registry = null, - ContainerColumnConfigurationService $containerColumnConfigurationService = null + ContainerColumnConfigurationService $containerColumnConfigurationService = null, + ContainerService $containerService = null ) { $this->containerFactory = $containerFactory ?? GeneralUtility::makeInstance(ContainerFactory::class); $this->registry = $registry ?? GeneralUtility::makeInstance(Registry::class); $this->containerColumnConfigurationService = $containerColumnConfigurationService ?? GeneralUtility::makeInstance(ContainerColumnConfigurationService::class); + $this->containerService = $containerService ?? GeneralUtility::makeInstance(ContainerService::class); $typo3Version = GeneralUtility::makeInstance(Typo3Version::class); if ($typo3Version->getMajorVersion() === 10) { @@ -126,13 +134,13 @@ protected function initLabels(): void */ protected function buildNewContentElementWizardLinkTop(int $colPos): string { - $containerRecord = $this->container->getContainerRecord(); + $target = $this->containerService->getNewContentElementAtTopTargetInColumn($this->container, $colPos); $urlParameters = [ - 'id' => $containerRecord['pid'], + 'id' => $this->container->getPid(), 'sys_language_uid' => $this->container->getLanguage(), 'tx_container_parent' => $this->container->getUidOfLiveWorkspace(), 'colPos' => $colPos, - 'uid_pid' => $containerRecord['pid'], + 'uid_pid' => $target, 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI'), ]; $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); diff --git a/Configuration/Commands.php b/Configuration/Commands.php index 0f6020ba..d2de1584 100644 --- a/Configuration/Commands.php +++ b/Configuration/Commands.php @@ -13,6 +13,9 @@ 'container:fixLanguageModeCommand' => [ 'class' => \B13\Container\Command\FixLanguageModeCommand::class, ], + 'container:sorting' => [ + 'class' => \B13\Container\Command\SortingCommand::class, + ], 'integrity:run' => [ 'class' => \B13\Container\Command\IntegrityCommand::class, ], diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index cee9aec6..e6569e87 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -42,3 +42,9 @@ services: command: 'container:integrity' schedulable: true description: Checks integrity of containers + B13\Container\Command\SortingCommand: + tags: + - name: 'console.command' + command: 'container:sorting' + schedulable: false + description: Resort Content Elements diff --git a/README.md b/README.md index c5c9a789..0afb3ca9 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ See Tests/README.md how to run the tests local (like github-actions runs the tes To assure coding guidelines are fullfilled: - run ``.Build/bin/phpstan analyse -c Build/phpstan10.neon`` -- run ``.Build/bin/php-cs-fixer fix --config=Build/php_cs.php --dry-run --stop-on-violation --using-cache=no`` +- run ``.Build/bin/php-cs-fixer fix --config=Build/php-cs-fixer.php --dry-run --stop-on-violation --using-cache=no`` ## Credits diff --git a/Tests/Functional/Datahandler/DatahandlerTest.php b/Tests/Functional/Datahandler/DatahandlerTest.php index 74bf4d3c..2325e6c5 100644 --- a/Tests/Functional/Datahandler/DatahandlerTest.php +++ b/Tests/Functional/Datahandler/DatahandlerTest.php @@ -109,7 +109,7 @@ protected function fetchOneRecord(string $field, int $id): array ) ->execute() ->fetch(); - self::assertIsArray($row); + self::assertIsArray($row, 'cannot fetch row for field ' . $field . ' with id ' . $id); return $row; } } diff --git a/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php b/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php index 9907fddf..a33c41cd 100644 --- a/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php +++ b/Tests/Functional/Datahandler/DefaultLanguage/ContainerTest.php @@ -77,6 +77,8 @@ public function moveContainerAjaxToBottomMovesChildren(): void self::assertSame(1, $child['tx_container_parent']); self::assertSame(200, $child['colPos']); self::assertSame(0, $child['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 1); + self::assertTrue($child['sorting'] > $container['sorting'], 'moved child is sorted before container'); } /** @@ -106,6 +108,8 @@ public function moveContainerByClipboardToOtherPageAtTopMovesChildren(): void self::assertSame(1, $child['tx_container_parent']); self::assertSame(200, $child['colPos']); self::assertSame(0, $child['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 1); + self::assertTrue($child['sorting'] > $container['sorting'], 'moved child is sorted before container'); } /** @@ -134,6 +138,7 @@ public function copyContainerToOtherPageAtTopCopiesChildren(): void self::assertSame($copiedRecord['uid'], $child['tx_container_parent']); self::assertSame(200, $child['colPos']); self::assertSame(0, $child['sys_language_uid']); + self::assertTrue($child['sorting'] > $copiedRecord['sorting'], 'copied child is sorted before container'); } /** @@ -162,6 +167,9 @@ public function copyContainerToOtherPageAfterElementCopiesChildren(): void self::assertSame(3, $child['pid']); self::assertSame($copiedRecord['uid'], $child['tx_container_parent']); self::assertSame(200, $child['colPos']); + self::assertTrue($child['sorting'] > $copiedRecord['sorting'], 'copied child is sorted before container'); + $targetElement = $this->fetchOneRecord('uid', 14); + self::assertTrue($child['sorting'] > $targetElement['sorting'], 'copied child is sorted before target element'); } /** @@ -191,6 +199,8 @@ public function moveContainerByClipboardToOtherPageAfterElementMovesChildren(): self::assertSame(3, $child['pid']); self::assertSame(1, $child['tx_container_parent']); self::assertSame(200, $child['colPos']); + $container = $this->fetchOneRecord('uid', 1); + self::assertTrue($child['sorting'] > $container['sorting'], 'moved child is sorted before container'); } /** @@ -216,5 +226,7 @@ public function copyClipboardKeepsSortingOfChildren(): void $child = $this->fetchOneRecord('t3_origuid', 2); $secondChild = $this->fetchOneRecord('t3_origuid', 5); self::assertTrue($child['sorting'] < $secondChild['sorting']); + $container = $this->fetchOneRecord('uid', 1); + self::assertTrue($child['sorting'] > $container['sorting'], 'moved child is sorted before container'); } } diff --git a/Tests/Functional/Datahandler/DefaultLanguage/CopyContainerInContainerTest.php b/Tests/Functional/Datahandler/DefaultLanguage/CopyContainerInContainerTest.php index d16070a3..a26e9ca5 100644 --- a/Tests/Functional/Datahandler/DefaultLanguage/CopyContainerInContainerTest.php +++ b/Tests/Functional/Datahandler/DefaultLanguage/CopyContainerInContainerTest.php @@ -52,5 +52,10 @@ public function copyContainerWithChildContainersCopiesContentInChildContainersIn $copiedContentInChildContainer2 = $this->fetchOneRecord('t3_origuid', 5); self::assertSame($copiedChildContainer1['uid'], $copiedContentInChildContainer1['tx_container_parent']); self::assertSame($copiedChildContainer2['uid'], $copiedContentInChildContainer2['tx_container_parent']); + $container = $this->fetchOneRecord('t3_origuid', 1); + self::assertTrue($container['sorting'] < $copiedChildContainer1['sorting'], 'sorting fail 1'); + self::assertTrue($copiedChildContainer1['sorting'] < $copiedContentInChildContainer1['sorting'], 'sorting fail 2'); + self::assertTrue($copiedContentInChildContainer1['sorting'] < $copiedChildContainer2['sorting'], 'sorting fail 3'); + self::assertTrue($copiedChildContainer2['sorting'] < $copiedContentInChildContainer2['sorting'], 'sorting fail 4'); } } diff --git a/Tests/Functional/Datahandler/DefaultLanguage/CopyElementClipboardOtherPageTest.php b/Tests/Functional/Datahandler/DefaultLanguage/CopyElementClipboardOtherPageTest.php index b3d823cd..aa8eb239 100644 --- a/Tests/Functional/Datahandler/DefaultLanguage/CopyElementClipboardOtherPageTest.php +++ b/Tests/Functional/Datahandler/DefaultLanguage/CopyElementClipboardOtherPageTest.php @@ -118,6 +118,8 @@ public function copyChildElementToOtherColumnTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(3, (int)$row['pid']); self::assertSame(0, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 11); + self::assertTrue($row['sorting'] > $container['sorting'], 'copied element is not sorted after container'); } /** @@ -179,6 +181,9 @@ public function copyElementIntoContainerAtTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(3, (int)$row['pid']); self::assertSame(0, (int)$row['sys_language_uid']); + + $container = $this->fetchOneRecord('uid', 11); + self::assertTrue($row['sorting'] > $container['sorting'], 'copied element is not sorted after container'); } /** @@ -233,4 +238,57 @@ public function copyElementIntoContainerAfterElementWithSimpleCommandMap(): void self::assertSame(3, (int)$row['pid']); self::assertSame(0, (int)$row['sys_language_uid']); } + + /** + * @test + */ + public function copyElementAfterContainerSortElementAfterLastContainerChild(): void + { + $cmdmap = [ + 'tt_content' => [ + 4 => [ + 'copy' => [ + 'action' => 'paste', + 'target' => -11, + 'update' => [ + 'colPos' => 0, + 'sys_language_uid' => 0, + ], + ], + ], + ], + ]; + + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_datamap(); + $this->dataHandler->process_cmdmap(); + $row = $this->fetchOneRecord('t3_origuid', 4); + $lastChild = $this->fetchOneRecord('uid', 13); + $nextElement = $this->fetchOneRecord('uid', 14); + self::assertTrue($row['sorting'] > $lastChild['sorting'], 'copied element is not sorted after last child container'); + self::assertTrue($row['sorting'] < $nextElement['sorting'], 'copied element is not sorted before containers next element'); + } + + /** + * @test + */ + public function copyElementAfterContainerSortElementAfterLastContainerChildSimpleCommand(): void + { + $cmdmap = [ + 'tt_content' => [ + 4 => [ + 'copy' => -11, + ], + ], + ]; + + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_datamap(); + $this->dataHandler->process_cmdmap(); + $row = $this->fetchOneRecord('t3_origuid', 4); + $lastChild = $this->fetchOneRecord('uid', 13); + $nextElement = $this->fetchOneRecord('uid', 14); + self::assertTrue($row['sorting'] > $lastChild['sorting'], 'copied element is not sorted after last child container'); + self::assertTrue($row['sorting'] < $nextElement['sorting'], 'copied element is not sorted before containers next element'); + } } diff --git a/Tests/Functional/Datahandler/DefaultLanguage/CopyElementClipboardTest.php b/Tests/Functional/Datahandler/DefaultLanguage/CopyElementClipboardTest.php index 8b70e2ed..52a746cb 100644 --- a/Tests/Functional/Datahandler/DefaultLanguage/CopyElementClipboardTest.php +++ b/Tests/Functional/Datahandler/DefaultLanguage/CopyElementClipboardTest.php @@ -117,6 +117,8 @@ public function copyChildElementToOtherColumnTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(1, (int)$row['pid']); self::assertSame(0, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 1); + self::assertTrue($row['sorting'] > $container['sorting'], 'copied element is not sorted after container'); } /** @@ -178,6 +180,8 @@ public function copyElementIntoContainerAtTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(1, (int)$row['pid']); self::assertSame(0, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 1); + self::assertTrue($row['sorting'] > $container['sorting'], 'copied element is not sorted after container'); } /** diff --git a/Tests/Functional/Datahandler/DefaultLanguage/MoveElementClipboardOtherPageTest.php b/Tests/Functional/Datahandler/DefaultLanguage/MoveElementClipboardOtherPageTest.php index 9e7a2805..5e6e7b1a 100644 --- a/Tests/Functional/Datahandler/DefaultLanguage/MoveElementClipboardOtherPageTest.php +++ b/Tests/Functional/Datahandler/DefaultLanguage/MoveElementClipboardOtherPageTest.php @@ -118,6 +118,8 @@ public function moveChildElementToOtherColumnTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(3, (int)$row['pid']); self::assertSame(0, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 11); + self::assertTrue($row['sorting'] > $container['sorting'], 'moved element is not sorted after container'); } /** @@ -179,6 +181,8 @@ public function moveElementIntoContainerAtTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(3, (int)$row['pid']); self::assertSame(0, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 11); + self::assertTrue($row['sorting'] > $container['sorting'], 'moved element is not sorted after container'); } /** @@ -233,4 +237,57 @@ public function moveElementIntoContainerAfterElementWithSimpleCommandMap(): void self::assertSame(3, (int)$row['pid']); self::assertSame(0, (int)$row['sys_language_uid']); } + + /** + * @test + */ + public function moveElementAfterContainerSortElementAfterLastContainerChild(): void + { + $cmdmap = [ + 'tt_content' => [ + 4 => [ + 'move' => [ + 'action' => 'paste', + 'target' => -11, + 'update' => [ + 'colPos' => 0, + 'sys_language_uid' => 0, + ], + ], + ], + ], + ]; + + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_datamap(); + $this->dataHandler->process_cmdmap(); + $row = $this->fetchOneRecord('uid', 4); + $lastChild = $this->fetchOneRecord('uid', 13); + $nextElement = $this->fetchOneRecord('uid', 14); + self::assertTrue($row['sorting'] > $lastChild['sorting'], 'copied element is not sorted after last child container'); + self::assertTrue($row['sorting'] < $nextElement['sorting'], 'copied element is not sorted before containers next element'); + } + + /** + * @test + */ + public function moveElementAfterContainerSortElementAfterLastContainerChildSimpleCommand(): void + { + $cmdmap = [ + 'tt_content' => [ + 4 => [ + 'move' => -11, + ], + ], + ]; + + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_datamap(); + $this->dataHandler->process_cmdmap(); + $row = $this->fetchOneRecord('uid', 4); + $lastChild = $this->fetchOneRecord('uid', 13); + $nextElement = $this->fetchOneRecord('uid', 14); + self::assertTrue($row['sorting'] > $lastChild['sorting'], 'copied element is not sorted after last child container'); + self::assertTrue($row['sorting'] < $nextElement['sorting'], 'copied element is not sorted before containers next element'); + } } diff --git a/Tests/Functional/Datahandler/DefaultLanguage/MoveElementClipboardTest.php b/Tests/Functional/Datahandler/DefaultLanguage/MoveElementClipboardTest.php index 183e4222..ad74126f 100644 --- a/Tests/Functional/Datahandler/DefaultLanguage/MoveElementClipboardTest.php +++ b/Tests/Functional/Datahandler/DefaultLanguage/MoveElementClipboardTest.php @@ -117,6 +117,8 @@ public function moveChildElementToOtherColumnTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(1, (int)$row['pid']); self::assertSame(0, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 1); + self::assertTrue($row['sorting'] > $container['sorting'], 'moved element is not sorted after container'); } /** @@ -178,6 +180,8 @@ public function moveElementIntoContainerAtTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(1, (int)$row['pid']); self::assertSame(0, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 1); + self::assertTrue($row['sorting'] > $container['sorting'], 'moved element is not sorted after container'); } /** diff --git a/Tests/Functional/Datahandler/DefaultLanguage/NewElementTest.php b/Tests/Functional/Datahandler/DefaultLanguage/NewElementTest.php new file mode 100644 index 00000000..e897032e --- /dev/null +++ b/Tests/Functional/Datahandler/DefaultLanguage/NewElementTest.php @@ -0,0 +1,42 @@ +importDataSet(ORIGINAL_ROOT . 'typo3conf/ext/container/Tests/Functional/Fixtures/new_element_after_container.xml'); + $newId = StringUtility::getUniqueId('NEW'); + $datamap = [ + 'tt_content' => [ + $newId => [ + 'pid' => -1, + ], + ], + ]; + $this->dataHandler->start($datamap, [], $this->backendUser); + $this->dataHandler->process_datamap(); + $this->dataHandler->process_cmdmap(); + + $newRecord = $this->fetchOneRecord('uid', 3); + $lastChildInContainer = $this->fetchOneRecord('uid', 2); + self::assertTrue($newRecord['sorting'] > $lastChildInContainer['sorting'], 'new element is not sorted after last child in container'); + } +} diff --git a/Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php b/Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php new file mode 100644 index 00000000..6fc70b1f --- /dev/null +++ b/Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php @@ -0,0 +1,114 @@ +linkSiteConfigurationIntoTestInstance(); + $this->importDataSet(ORIGINAL_ROOT . 'typo3conf/ext/container/Tests/Functional/Fixtures/sys_language.xml'); + } + + /** + * @return array + */ + public function localizeKeepsSortingDataProvider(): array + { + return [ + ['cmdmap' => [ + 'tt_content' => [ + 4 => ['copyToLanguage' => 1], + 1 => ['copyToLanguage' => 1], + ], + ]], + ['cmdmap' => [ + 'tt_content' => [ + 1 => ['copyToLanguage' => 1], + 4 => ['copyToLanguage' => 1], + ], + ]], + ]; + } + + /** + * @test + * @dataProvider localizeKeepsSortingDataProvider + */ + public function localizeKeepsSorting(array $cmdmap): void + { + $this->importDataSet(ORIGINAL_ROOT . 'typo3conf/ext/container/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_containers.xml'); + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_cmdmap(); + $translatedContainer1 = $this->fetchOneRecord('t3_origuid', 1); + $translatedChild11 = $this->fetchOneRecord('t3_origuid', 2); + $translatedChild12 = $this->fetchOneRecord('t3_origuid', 3); + $translatedContainer2 = $this->fetchOneRecord('t3_origuid', 4); + $translatedChild21 = $this->fetchOneRecord('t3_origuid', 5); + self::assertTrue($translatedContainer1['sorting'] < $translatedChild11['sorting'], 'child-1-1 is sorted before container-1'); + self::assertTrue($translatedChild11['sorting'] < $translatedChild12['sorting'], 'child-1-2 is sorted before child-1-1'); + self::assertTrue($translatedChild12['sorting'] < $translatedContainer2['sorting'], 'container-2 is sorted before child-1-2'); + self::assertTrue($translatedContainer2['sorting'] < $translatedChild21['sorting'], 'child-2-1 is sorted before container-2'); + } + + /** + * @test + */ + public function localizeChildAtTopOfContainer(): void + { + $this->importDataSet(ORIGINAL_ROOT . 'typo3conf/ext/container/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_child_at_top.xml'); + $cmdmap = [ + 'tt_content' => [ + 2 => [ + 'copyToLanguage' => 1, + ], + ], + ]; + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_cmdmap(); + $translatedContainer1 = $this->fetchOneRecord('uid', 4); + $translatedChild11 = $this->fetchOneRecord('t3_origuid', 2); + $translatedChild12 = $this->fetchOneRecord('uid', 5); + self::assertTrue($translatedContainer1['sorting'] < $translatedChild11['sorting'], 'child-1-1 is sorted before container-1'); + self::assertTrue($translatedChild11['sorting'] < $translatedChild12['sorting'], 'child-1-1 is sorted after child-1-2'); + } + + /** + * @test + */ + public function localizeChildAfterContainerChild(): void + { + $this->importDataSet(ORIGINAL_ROOT . 'typo3conf/ext/container/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_child_after_child.xml'); + $cmdmap = [ + 'tt_content' => [ + 3 => [ + 'copyToLanguage' => 1, + ], + ], + ]; + $this->dataHandler->start([], $cmdmap, $this->backendUser); + $this->dataHandler->process_cmdmap(); + $translatedContainer1 = $this->fetchOneRecord('uid', 4); + $translatedChild11 = $this->fetchOneRecord('uid', 5); + $translatedChild12 = $this->fetchOneRecord('t3_origuid', 3); + self::assertTrue($translatedContainer1['sorting'] < $translatedChild11['sorting'], 'child-1-1 is sorted before container-1'); + self::assertTrue($translatedChild11['sorting'] < $translatedChild12['sorting'], 'child-1-1 is sorted after child-1-2'); + } +} diff --git a/Tests/Functional/Datahandler/Localization/FreeMode/ContainerTest.php b/Tests/Functional/Datahandler/Localization/FreeMode/ContainerTest.php index b9ddade5..fe1aa2d4 100644 --- a/Tests/Functional/Datahandler/Localization/FreeMode/ContainerTest.php +++ b/Tests/Functional/Datahandler/Localization/FreeMode/ContainerTest.php @@ -106,6 +106,8 @@ public function moveContainerClipboardToOtherPageMovesChildren(): void self::assertSame(51, $child['tx_container_parent']); self::assertSame(200, $child['colPos']); self::assertSame(1, $child['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 51); + self::assertTrue($child['sorting'] > $container['sorting'], 'moved child is sorted before container'); } /** @@ -134,6 +136,8 @@ public function copyClipboardCopiesChildren(): void self::assertSame($copiedRecord['uid'], $child['tx_container_parent']); self::assertSame(200, $child['colPos']); self::assertSame(1, $child['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 51); + self::assertTrue($child['sorting'] > $container['sorting'], 'copied child is sorted before container'); } /** @@ -163,6 +167,7 @@ public function copyClipboardToOtherLanguageCopiesChildren(): void self::assertSame($copiedRecord['uid'], $child['tx_container_parent']); self::assertSame(200, $child['colPos']); self::assertSame(0, $child['sys_language_uid']); + self::assertTrue($child['sorting'] > $copiedRecord['sorting'], 'copied child is sorted before container'); } /** @@ -220,6 +225,8 @@ public function moveContainerClipboardToOtherLanguageMovesChildren(): void self::assertSame(51, $child['tx_container_parent']); self::assertSame(200, $child['colPos']); self::assertSame(0, $child['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 51); + self::assertTrue($child['sorting'] > $container['sorting'], 'moved child is sorted before container'); } /** diff --git a/Tests/Functional/Datahandler/Localization/FreeMode/CopyElementClipboardOtherPageTest.php b/Tests/Functional/Datahandler/Localization/FreeMode/CopyElementClipboardOtherPageTest.php index 47517ead..6d44c89f 100644 --- a/Tests/Functional/Datahandler/Localization/FreeMode/CopyElementClipboardOtherPageTest.php +++ b/Tests/Functional/Datahandler/Localization/FreeMode/CopyElementClipboardOtherPageTest.php @@ -119,6 +119,8 @@ public function copyChildElementToOtherColumnTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(3, (int)$row['pid']); self::assertSame(1, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 61); + self::assertTrue($row['sorting'] > $container['sorting'], 'copied element is not sorted after container'); } /** @@ -180,6 +182,8 @@ public function copyElementIntoContainerAtTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(3, (int)$row['pid']); self::assertSame(1, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 61); + self::assertTrue($row['sorting'] > $container['sorting'], 'copied element is not sorted after container'); } /** diff --git a/Tests/Functional/Datahandler/Localization/FreeMode/CopyElementClipboardTest.php b/Tests/Functional/Datahandler/Localization/FreeMode/CopyElementClipboardTest.php index 79c01ba5..2af07351 100644 --- a/Tests/Functional/Datahandler/Localization/FreeMode/CopyElementClipboardTest.php +++ b/Tests/Functional/Datahandler/Localization/FreeMode/CopyElementClipboardTest.php @@ -118,6 +118,8 @@ public function copyChildElementToOtherColumnTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(1, (int)$row['pid']); self::assertSame(1, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 51); + self::assertTrue($row['sorting'] > $container['sorting'], 'copied element is not sorted after container'); } /** @@ -179,6 +181,8 @@ public function copyElementIntoContainerAtTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(1, (int)$row['pid']); self::assertSame(1, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 51); + self::assertTrue($row['sorting'] > $container['sorting'], 'copied element is not sorted after container'); } /** diff --git a/Tests/Functional/Datahandler/Localization/FreeMode/MoveElementClipboardOtherPageTest.php b/Tests/Functional/Datahandler/Localization/FreeMode/MoveElementClipboardOtherPageTest.php index 8ab7a002..848f271a 100644 --- a/Tests/Functional/Datahandler/Localization/FreeMode/MoveElementClipboardOtherPageTest.php +++ b/Tests/Functional/Datahandler/Localization/FreeMode/MoveElementClipboardOtherPageTest.php @@ -119,6 +119,8 @@ public function moveChildElementToOtherColumnTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(3, (int)$row['pid']); self::assertSame(1, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 61); + self::assertTrue($row['sorting'] > $container['sorting'], 'moved element is not sorted after container'); } /** @@ -180,6 +182,8 @@ public function moveElementIntoContainerAtTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(3, (int)$row['pid']); self::assertSame(1, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 61); + self::assertTrue($row['sorting'] > $container['sorting'], 'moved element is not sorted after container'); } /** diff --git a/Tests/Functional/Datahandler/Localization/FreeMode/MoveElementClipboardTest.php b/Tests/Functional/Datahandler/Localization/FreeMode/MoveElementClipboardTest.php index 1cfef312..1ec72936 100644 --- a/Tests/Functional/Datahandler/Localization/FreeMode/MoveElementClipboardTest.php +++ b/Tests/Functional/Datahandler/Localization/FreeMode/MoveElementClipboardTest.php @@ -118,6 +118,8 @@ public function moveChildElementToOtherColumnTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(1, (int)$row['pid']); self::assertSame(1, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 51); + self::assertTrue($row['sorting'] > $container['sorting'], 'moved element is not sorted after container'); } /** @@ -179,6 +181,8 @@ public function moveElementIntoContainerAtTop(): void self::assertSame(201, (int)$row['colPos']); self::assertSame(1, (int)$row['pid']); self::assertSame(1, (int)$row['sys_language_uid']); + $container = $this->fetchOneRecord('uid', 51); + self::assertTrue($row['sorting'] > $container['sorting'], 'moved element is not sorted after container'); } /** diff --git a/Tests/Functional/Datahandler/Localization/FreeMode/NewElementTest.php b/Tests/Functional/Datahandler/Localization/FreeMode/NewElementTest.php new file mode 100644 index 00000000..6f0af920 --- /dev/null +++ b/Tests/Functional/Datahandler/Localization/FreeMode/NewElementTest.php @@ -0,0 +1,43 @@ +importDataSet(ORIGINAL_ROOT . 'typo3conf/ext/container/Tests/Functional/Fixtures/new_element_after_container_free_mode.xml'); + $newId = StringUtility::getUniqueId('NEW'); + $datamap = [ + 'tt_content' => [ + $newId => [ + 'pid' => -1, + 'sys_language_uid' => 1, + ], + ], + ]; + $this->dataHandler->start($datamap, [], $this->backendUser); + $this->dataHandler->process_datamap(); + $this->dataHandler->process_cmdmap(); + + $newRecord = $this->fetchOneRecord('uid', 3); + $lastChildInContainer = $this->fetchOneRecord('uid', 2); + self::assertTrue($newRecord['sorting'] > $lastChildInContainer['sorting'], 'new element is not sorted after last child in container'); + } +} diff --git a/Tests/Functional/Datahandler/Workspace/ContainerTest.php b/Tests/Functional/Datahandler/Workspace/ContainerTest.php index 794a55eb..b05ce40e 100644 --- a/Tests/Functional/Datahandler/Workspace/ContainerTest.php +++ b/Tests/Functional/Datahandler/Workspace/ContainerTest.php @@ -416,6 +416,7 @@ public function moveRecordInColPosCreatesWorkspaceElementInContainer() $workspaceElement = $this->fetchOneRecord('t3ver_oid', 5); } self::assertSame(1, $workspaceElement['tx_container_parent']); - self::assertTrue($workspaceElement['sorting'] < $origFirstElement['sorting']); + self::assertSame(200, $workspaceElement['colPos']); + self::assertTrue($workspaceElement['sorting'] > $origFirstElement['sorting']); } } diff --git a/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_child_after_child.xml b/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_child_after_child.xml new file mode 100644 index 00000000..97920506 --- /dev/null +++ b/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_child_after_child.xml @@ -0,0 +1,66 @@ + + + + 1 + 0 + page-1 + / + + + 2 + 0 + 1 + 1 + 1 + / + page-1-language-1 + + + 1 + 1 + b13-2cols-with-header-container +
container-1
+ 128 + 0 +
+ + 2 + 1 + 200 + 1 + header + 256 +
child-1-1
+ 0 +
+ + 3 + 1 + 200 + 1 + header + 512 +
child-1-2
+ 0 +
+ + 4 + 1 + b13-2cols-with-header-container +
container-2
+ 1024 + 1 + 1 +
+ + 5 + 1 + 200 + 4 + header + 2048 +
child-1-2
+ 2 + 1 +
+
diff --git a/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_child_at_top.xml b/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_child_at_top.xml new file mode 100644 index 00000000..1a772875 --- /dev/null +++ b/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_child_at_top.xml @@ -0,0 +1,66 @@ + + + + 1 + 0 + page-1 + / + + + 2 + 0 + 1 + 1 + 1 + / + page-1-language-1 + + + 1 + 1 + b13-2cols-with-header-container +
container-1
+ 128 + 0 +
+ + 2 + 1 + 200 + 1 + header + 256 +
child-1-1
+ 0 +
+ + 3 + 1 + 200 + 1 + header + 512 +
child-1-2
+ 0 +
+ + 4 + 1 + b13-2cols-with-header-container +
container-2
+ 1024 + 1 + 1 +
+ + 5 + 1 + 200 + 4 + header + 2048 +
child-2-2
+ 3 + 1 +
+
diff --git a/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_containers.xml b/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_containers.xml new file mode 100644 index 00000000..afcb7da7 --- /dev/null +++ b/Tests/Functional/Fixtures/CopyToLanguageSorting/localize_containers.xml @@ -0,0 +1,64 @@ + + + + 1 + 0 + page-1 + / + + + 2 + 0 + 1 + 1 + 1 + / + page-1-language-1 + + + 1 + 1 + b13-2cols-with-header-container +
container-1
+ 128 + 0 +
+ + 2 + 1 + 200 + 1 + header + 256 +
child-1-1
+ 0 +
+ + 3 + 1 + 200 + 1 + header + 512 +
child-1-2
+ 0 +
+ + 4 + 1 + b13-2cols-with-header-container +
container-2
+ 1024 + 0 +
+ + 5 + 1 + 200 + 4 + header + 2048 +
child-2-1
+ 0 +
+
diff --git a/Tests/Functional/Fixtures/copy_container_in_container.xml b/Tests/Functional/Fixtures/copy_container_in_container.xml index 34214b25..e024a12b 100644 --- a/Tests/Functional/Fixtures/copy_container_in_container.xml +++ b/Tests/Functional/Fixtures/copy_container_in_container.xml @@ -6,6 +6,8 @@ 2 + + 1 1 @@ -40,7 +42,7 @@ 1 200 4 -
content-in-child-container-1
+
content-in-child-container-2
diff --git a/Tests/Functional/Fixtures/new_element_after_container.xml b/Tests/Functional/Fixtures/new_element_after_container.xml new file mode 100644 index 00000000..62a10b0d --- /dev/null +++ b/Tests/Functional/Fixtures/new_element_after_container.xml @@ -0,0 +1,28 @@ + + + + 1 + 0 + page-1 + / + + + 1 + 1 + b13-2cols-with-header-container +
container-default
+ 128 + 0 +
+ + 2 + 1 + 200 + 1 + header + 256 +
header-default
+ 0 +
+ +
diff --git a/Tests/Functional/Fixtures/new_element_after_container_free_mode.xml b/Tests/Functional/Fixtures/new_element_after_container_free_mode.xml new file mode 100644 index 00000000..92d72b0b --- /dev/null +++ b/Tests/Functional/Fixtures/new_element_after_container_free_mode.xml @@ -0,0 +1,39 @@ + + + + 1 + 0 + page-1 + / + + + 2 + 0 + 1 + 1 + 1 + / + page-1-language-1 + + + 1 + 1 + b13-2cols-with-header-container +
container-default
+ 128 + 1 + 0 +
+ + 2 + 1 + 200 + 1 + header + 256 +
header-default
+ 1 + 0 +
+ +
diff --git a/Tests/Functional/Fixtures/tt_content_default_language_other_page.xml b/Tests/Functional/Fixtures/tt_content_default_language_other_page.xml index 86e4cb06..daf1a072 100644 --- a/Tests/Functional/Fixtures/tt_content_default_language_other_page.xml +++ b/Tests/Functional/Fixtures/tt_content_default_language_other_page.xml @@ -14,8 +14,8 @@ 200 11 header - 128
header
+ 257 0 @@ -25,7 +25,7 @@ 11 header
left side
- 64 + 258 0
diff --git a/Tests/Functional/Fixtures/tt_content_translations_free_mode.xml b/Tests/Functional/Fixtures/tt_content_translations_free_mode.xml index 4a5e9762..4e1acd9f 100644 --- a/Tests/Functional/Fixtures/tt_content_translations_free_mode.xml +++ b/Tests/Functional/Fixtures/tt_content_translations_free_mode.xml @@ -14,7 +14,7 @@ 200 51 header-language-1 - 128 + 257
header
1
@@ -25,7 +25,7 @@ 51 header
left-side-language-1
- 64 + 258 1 diff --git a/Tests/Functional/Fixtures/tt_content_translations_free_mode_other_page.xml b/Tests/Functional/Fixtures/tt_content_translations_free_mode_other_page.xml index 8904bfe0..60e43b95 100644 --- a/Tests/Functional/Fixtures/tt_content_translations_free_mode_other_page.xml +++ b/Tests/Functional/Fixtures/tt_content_translations_free_mode_other_page.xml @@ -14,7 +14,7 @@ 200 61 header - 128 + 257
header
1
@@ -25,7 +25,7 @@ 61 header
left side
- 64 + 258 1 diff --git a/Tests/Unit/Domain/Service/ContainerServiceTest.php b/Tests/Unit/Domain/Service/ContainerServiceTest.php new file mode 100644 index 00000000..c68420fc --- /dev/null +++ b/Tests/Unit/Domain/Service/ContainerServiceTest.php @@ -0,0 +1,77 @@ + ['uid' => 1, 'CType' => 'myCType'], + 'childRecords' => [], + 'targetColPos' => 200, + 'expectedTarget' => -1, + ], + [ + 'containerRecord' => ['uid' => 1, 'CType' => 'myCType'], + 'childRecords' => [[200 => ['uid' => 10, 'colPos' => 200]]], + 'targetColPos' => 200, + 'expectedTarget' => -1, + ], + [ + 'containerRecord' => ['uid' => 1, 'CType' => 'myCType'], + 'childRecords' => [ + 200 => [['uid' => 10, 'colPos' => 200]], + ], + 'targetColPos' => 201, + 'expectedTarget' => -10, + ], + [ + 'containerRecord' => ['uid' => 1, 'CType' => 'myCType'], + 'childRecords' => [ + 200 => [['uid' => 11, 'colPos' => 200]], + ], + 'targetColPos' => 200, + 'expectedTarget' => -1, + ], + ]; + } + + /** + * @test + * @dataProvider setupDataProvider + */ + public function getFirstNewContentElementTargetInColumnTest(array $containerRecord, array $childRecords, int $targetColPos, int $expectedTarget): void + { + $tcaRegistry = $this->prophesize(Registry::class); + $tcaRegistry->getAllAvailableColumnsColPos('myCType')->willReturn($this->allContainerColumns); + $container = new Container($containerRecord, $childRecords, 0); + $service = GeneralUtility::makeInstance(ContainerService::class, $tcaRegistry->reveal()); + $target = $service->getNewContentElementAtTopTargetInColumn($container, $targetColPos); + self::assertSame($expectedTarget, $target); + } +} diff --git a/Tests/Unit/Hooks/Datahandler/CommandMapBeforeStartHookTest.php b/Tests/Unit/Hooks/Datahandler/CommandMapBeforeStartHookTest.php index df7edd12..eda8177e 100644 --- a/Tests/Unit/Hooks/Datahandler/CommandMapBeforeStartHookTest.php +++ b/Tests/Unit/Hooks/Datahandler/CommandMapBeforeStartHookTest.php @@ -11,6 +11,9 @@ * of the License, or any later version. */ +use B13\Container\Domain\Factory\ContainerFactory; +use B13\Container\Domain\Model\Container; +use B13\Container\Domain\Service\ContainerService; use B13\Container\Hooks\Datahandler\CommandMapBeforeStartHook; use B13\Container\Hooks\Datahandler\Database; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; @@ -19,6 +22,58 @@ class CommandMapBeforeStartHookTest extends UnitTestCase { protected $resetSingletonInstances = true; + /** + * @test + */ + public function rewriteCommandMapTargetForTopAtContainerTest(): void + { + $containerFactory = $this->prophesize(ContainerFactory::class); + $container = new Container([], []); + $containerFactory->buildContainer(3)->willReturn($container); + $containerService = $this->prophesize(ContainerService::class); + $containerService->getNewContentElementAtTopTargetInColumn($container, 2)->willReturn(-4); + $dataHandlerHook = $this->getAccessibleMock( + CommandMapBeforeStartHook::class, + ['foo'], + [ + 'containerFactory' => $containerFactory->reveal(), + 'tcaRegistry' => null, + 'database' => null, + 'containerService' => $containerService->reveal(), ] + ); + $cmdmap = [ + 'tt_content' => [ + 1 => [ + 'copy' => [ + 'action' => 'paste', + 'target' => 1, + 'update' => [ + 'colPos' => 2, + 'tx_container_parent' => 3, + ], + ], + ], + ], + ]; + // should be + $expected = [ + 'tt_content' => [ + 1 => [ + 'copy' => [ + 'action' => 'paste', + 'target' => -4, + 'update' => [ + 'colPos' => 2, + 'tx_container_parent' => 3, + ], + ], + ], + ], + ]; + $rewrittenCommandMap = $dataHandlerHook->_call('rewriteCommandMapTargetForTopAtContainer', $cmdmap); + self::assertSame($expected, $rewrittenCommandMap); + } + /** * @test */ diff --git a/ext_localconf.php b/ext_localconf.php index d273439b..44cebd01 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -75,6 +75,7 @@ $datamapHooks = [ 'tx_container-before-start' => \B13\Container\Hooks\Datahandler\DatamapBeforeStartHook::class, + 'tx_container-pre-process-field-array' => \B13\Container\Hooks\Datahandler\DatamapPreProcessFieldArrayHook::class, ]; // EXT:content_defender