From 45eba09e0fe84fa13687d5303fcd3f34eeef7179 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 28 Jan 2026 23:18:09 +0100 Subject: [PATCH 1/3] [FEATURE] core content element restriction https://review.typo3.org/c/Packages/TYPO3.CMS/+/92260 this replace EXT:content_defender integratrion Note: we keep the maxitems feature Note: we replace the core datahandler hooks complete (may be this will change in future) --- .github/workflows/ci.yml | 2 +- Build/phpstan11-7.4.neon | 2 + Build/phpstan11.neon | 2 + Build/phpstan12.neon | 2 + Build/phpstan13.neon | 2 + .../CommandMapPostProcessingHook.php | 2 + .../DataHandlerHook.php | 222 ++++++++++++++++++ .../Hooks/Datahandler/DatahandlerProcess.php | 19 ++ ...ackendLayoutColPosConfigurationForPage.php | 89 +++++++ Classes/Tca/Registry.php | 8 + Configuration/Services.yaml | 4 + .../Backend/ContentDefenderCest.php | 46 ++-- .../ContentDefender/CopyContainerTest.php | 6 +- .../ContentDefender/DefaultLanguageTest.php | 6 +- .../ContentDefender/LocalizationTest.php | 6 +- .../ContentDefender/MaxItemsTest.php | 47 +++- .../CopyToLanguageSortingTest.php | 3 + ext_localconf.php | 5 + 18 files changed, 436 insertions(+), 37 deletions(-) create mode 100644 Classes/Hooks/Datahandler/ContentElementRestriction/DataHandlerHook.php create mode 100644 Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ab54624..453f9576 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,7 +58,7 @@ jobs: run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -t ${{ matrix.TYPO3 }} -s acceptance -- --fail-fast if: matrix.TYPO3 != '14' - name: Acceptance Tests 14 - run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -t ${{ matrix.TYPO3 }} -s acceptance -- --fail-fast --skip-group=content_defender + run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -t ${{ matrix.TYPO3 }} -s acceptance -- --fail-fast if: matrix.TYPO3 == '14' - name: Archive acceptance tests results uses: actions/upload-artifact@v4 diff --git a/Build/phpstan11-7.4.neon b/Build/phpstan11-7.4.neon index 100d9fed..9e1b785f 100644 --- a/Build/phpstan11-7.4.neon +++ b/Build/phpstan11-7.4.neon @@ -17,4 +17,6 @@ parameters: - %currentWorkingDirectory%/Classes/Listener/PageTsConfig.php - %currentWorkingDirectory%/Classes/Listener/PageContentPreviewRendering.php - %currentWorkingDirectory%/Classes/Domain/Core/RecordWithRenderedGrid.php + - %currentWorkingDirectory%/Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php + - %currentWorkingDirectory%/Classes/Hooks/Datahandler/ContentElementRestriction diff --git a/Build/phpstan11.neon b/Build/phpstan11.neon index d8abcf8e..81a0120a 100644 --- a/Build/phpstan11.neon +++ b/Build/phpstan11.neon @@ -17,4 +17,6 @@ parameters: - %currentWorkingDirectory%/Classes/Listener/PageTsConfig.php - %currentWorkingDirectory%/Classes/Listener/PageContentPreviewRendering.php - %currentWorkingDirectory%/Classes/Domain/Core/RecordWithRenderedGrid.php + - %currentWorkingDirectory%/Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php + - %currentWorkingDirectory%/Classes/Hooks/Datahandler/ContentElementRestriction diff --git a/Build/phpstan12.neon b/Build/phpstan12.neon index 31ff873e..af3d136c 100644 --- a/Build/phpstan12.neon +++ b/Build/phpstan12.neon @@ -14,5 +14,7 @@ parameters: - %currentWorkingDirectory%/Tests/Unit/Hooks/UsedRecordsTest.php - %currentWorkingDirectory%/Classes/Domain/Core/RecordWithRenderedGrid.php - %currentWorkingDirectory%/Classes/Listener/PageContentPreviewRendering.php + - %currentWorkingDirectory%/Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php + - %currentWorkingDirectory%/Classes/Hooks/Datahandler/ContentElementRestriction diff --git a/Build/phpstan13.neon b/Build/phpstan13.neon index 0e3bc701..af4cc0c3 100644 --- a/Build/phpstan13.neon +++ b/Build/phpstan13.neon @@ -15,4 +15,6 @@ parameters: - %currentWorkingDirectory%/Classes/Hooks/WizardItems.php - %currentWorkingDirectory%/Classes/Listener/LegacyPageTsConfig.php - %currentWorkingDirectory%/Classes/Listener/PageContentPreviewRendering.php + - %currentWorkingDirectory%/Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php + - %currentWorkingDirectory%/Classes/Hooks/Datahandler/ContentElementRestriction diff --git a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php index 6d0d358b..d3b0635f 100644 --- a/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php +++ b/Classes/Hooks/Datahandler/CommandMapPostProcessingHook.php @@ -125,6 +125,7 @@ protected function copyOrMoveChildren(int $origUid, int $newId, int $containerId // when moving or copy a container into other language the other language is returned $container = $this->containerFactory->buildContainer($origUid); (GeneralUtility::makeInstance(DatahandlerProcess::class))->startContainerProcess($origUid); + (GeneralUtility::makeInstance(DatahandlerProcess::class))->lockContentElementRestrictions(); $children = []; $colPosVals = $container->getChildrenColPos(); foreach ($colPosVals as $colPos) { @@ -177,6 +178,7 @@ protected function copyOrMoveChildren(int $origUid, int $newId, int $containerId } } (GeneralUtility::makeInstance(DatahandlerProcess::class))->endContainerProcess($origUid); + (GeneralUtility::makeInstance(DatahandlerProcess::class))->unlockContentElementRestrictions(); } catch (Exception $e) { // nothing todo } diff --git a/Classes/Hooks/Datahandler/ContentElementRestriction/DataHandlerHook.php b/Classes/Hooks/Datahandler/ContentElementRestriction/DataHandlerHook.php new file mode 100644 index 00000000..13cdac75 --- /dev/null +++ b/Classes/Hooks/Datahandler/ContentElementRestriction/DataHandlerHook.php @@ -0,0 +1,222 @@ +cmdmap; + if (isset($cmdmap['pages'])) { + $this->lockDatamapHook = true; + } + if (empty($cmdmap['tt_content']) || $dataHandler->bypassAccessCheckForRecords) { + return; + } + $this->lockDatamapHook = true; + if ($this->datahandlerProcess->areContentElementRestrictionsLooked()) { + return; + } + foreach ($cmdmap['tt_content'] as $id => $incomingFieldArray) { + foreach ($incomingFieldArray as $command => $value) { + if (!in_array($command, ['copy', 'move'], true)) { + continue; + } + $currentRecord = BackendUtility::getRecord('tt_content', $id); + + // EXT:container start + if ( + (!empty($value['update'])) && + isset($value['update']['colPos']) && + $value['update']['colPos'] > 0 && + isset($value['update']['tx_container_parent']) && + $value['update']['tx_container_parent'] > 0 && + MathUtility::canBeInterpretedAsInteger($id) + ) { + $colPos = (int)$value['update']['colPos']; + if (!empty($currentRecord['CType'])) { + if ($this->checkContainerCType((int)$value['update']['tx_container_parent'], $currentRecord['CType'], (int)$value['update']['colPos']) === false) { + // Not allowed to move or copy to target. Unset this command and create a log entry which may be turned into a notification when called by BE. + unset($dataHandler->cmdmap['tt_content'][$id]); + $dataHandler->log('tt_content', $id, 1, null, 1, 'The command "%s" for record "tt_content:%s" with CType "%s" to colPos "%s" couldn\'t be executed due to disallowed value(s).', null, [$command, $id, $currentRecord['CType'], $colPos]); + } + } + $useChildId = null; + if ($command === 'move') { + $useChildId = $id; + } + if ($this->checkContainerMaxItems((int)$value['update']['tx_container_parent'], (int)$value['update']['colPos'], $useChildId)) { + unset($dataHandler->cmdmap['tt_content'][$id]); + $dataHandler->log('tt_content', $id, 1, null, 1, 'The command "%s" for record "tt_content:%s" to colPos "%s" couldn\'t be executed due to maxitems reached.', null, [$command, $id, $colPos]); + } + return; + } + // EXT:container end + + if (empty($currentRecord['CType'] ?? '')) { + continue; + } + if (is_array($value) && !empty($value['action']) && $value['action'] === 'paste' && isset($value['update']['colPos'])) { + // Moving / pasting to a new colPos on a potentially different page + $pageId = (int)$value['target']; + $colPos = (int)$value['update']['colPos']; + } else { + $pageId = (int)$value; + $colPos = (int)$currentRecord['colPos']; + } + if ($pageId < 0) { + $targetRecord = BackendUtility::getRecord('tt_content', abs($pageId)); + $pageId = (int)$targetRecord['pid']; + $colPos = (int)$targetRecord['colPos']; + } + + $backendLayout = $this->backendLayoutView->getBackendLayoutForPage($pageId); + $columnConfiguration = $this->backendLayoutView->getColPosConfigurationForPage($backendLayout, $colPos, $pageId); + $allowedContentElementsInTargetColPos = GeneralUtility::trimExplode(',', $columnConfiguration['allowedContentTypes'] ?? '', true); + $disallowedContentElementsInTargetColPos = GeneralUtility::trimExplode(',', $columnConfiguration['disallowedContentTypes'] ?? '', true); + if ((!empty($allowedContentElementsInTargetColPos) && !in_array($currentRecord['CType'], $allowedContentElementsInTargetColPos, true)) + || (!empty($disallowedContentElementsInTargetColPos) && in_array($currentRecord['CType'], $disallowedContentElementsInTargetColPos, true)) + ) { + // Not allowed to move or copy to target. Unset this command and create a log entry which may be turned into a notification when called by BE. + unset($dataHandler->cmdmap['tt_content'][$id]); + $dataHandler->log('tt_content', $id, 1, null, 1, 'The command "%s" for record "tt_content:%s" with CType "%s" to colPos "%s" couldn\'t be executed due to disallowed value(s).', null, [$command, $id, $currentRecord['CType'], $colPos]); + } + } + } + } + + protected function checkContainerMaxItems(int $containerId, int $colPos, ?int $childUid = null): bool + { + try { + $container = $this->containerFactory->buildContainer($containerId); + $columnConfiguration = $this->registry->getContentDefenderConfiguration($container->getCType(), $colPos); + if (($columnConfiguration['maxitems'] ?? 0) === 0) { + return false; + } + $childrenOfColumn = $container->getChildrenByColPos($colPos); + $count = count($childrenOfColumn); + if ($childUid !== null && $container->hasChildInColPos($colPos, $childUid)) { + $count--; + } + return $count >= $columnConfiguration['maxitems']; + } catch (Exception) { + // not a container; + } + return false; + } + + protected function checkContainerCType(int $containerId, string $cType, int $colPos): bool + { + try { + $container = $this->containerFactory->buildContainer($containerId); + $columnConfiguration = $this->registry->getContentDefenderConfiguration($container->getCType(), $colPos); + $allowedContentElementsInTargetColPos = GeneralUtility::trimExplode(',', $columnConfiguration['allowedContentTypes'] ?? '', true); + $disallowedContentElementsInTargetColPos = GeneralUtility::trimExplode(',', $columnConfiguration['disallowedContentTypes'] ?? '', true); + if ((!empty($allowedContentElementsInTargetColPos) && !in_array($cType, $allowedContentElementsInTargetColPos, true)) + || (!empty($disallowedContentElementsInTargetColPos) && in_array($cType, $disallowedContentElementsInTargetColPos, true)) + ) { + return false; + } + } catch (Exception) { + // not a container; + } + return true; + } + + public function processCmdmap_postProcess(string $command, string $table, $id, $value, DataHandler $dataHandler, $pasteUpdate, $pasteDatamap): void + { + $this->lockDatamapHook = false; + } + + public function processDatamap_beforeStart(DataHandler $dataHandler): void + { + if ($this->lockDatamapHook === true) { + return; + } + $datamap = $dataHandler->datamap; + if (empty($datamap['tt_content']) || $dataHandler->bypassAccessCheckForRecords) { + return; + } + foreach ($datamap['tt_content'] as $id => $incomingFieldArray) { + if (MathUtility::canBeInterpretedAsInteger($id)) { + $record = BackendUtility::getRecord('tt_content', $id); + if (!is_array($record)) { + // Skip this if the record could not be determined for whatever reason + continue; + } + $recordData = array_merge($record, $incomingFieldArray); + } else { + $recordData = array_merge($dataHandler->defaultValues['tt_content'] ?? [], $incomingFieldArray); + } + // EXT:container start + if ((int)($recordData['tx_container_parent'] ?? 0) > 0 && (int)($recordData['colPos'] ?? 0) > 0) { + if ($this->checkContainerMaxItems((int)$recordData['tx_container_parent'], (int)$recordData['colPos'])) { + if (MathUtility::canBeInterpretedAsInteger($id)) { + // edit + continue; + } + unset($dataHandler->datamap['tt_content'][$id]); + $dataHandler->log('tt_content', $id, 1, null, 1, 'The command "%s" for record "tt_content:%s" to colPos "%s" couldn\'t be executed due to maxitems reached.', null, [$id, $recordData['colPos']]); + } + } + // EXT:container end + if (empty($recordData['CType']) || !array_key_exists('colPos', $recordData)) { + // No idea what happened here, but we stop with this record if there is no CType or colPos + continue; + } + $pageId = (int)$recordData['pid']; + if ($pageId < 0) { + $previousRecord = BackendUtility::getRecord('tt_content', abs($pageId), 'pid'); + if ($previousRecord === null) { + // Broken target data. Stop here and let DH handle this mess. + continue; + } + $pageId = (int)$previousRecord['pid']; + } + $colPos = (int)$recordData['colPos']; + $backendLayout = $this->backendLayoutView->getBackendLayoutForPage($pageId); + $columnConfiguration = $this->backendLayoutView->getColPosConfigurationForPage($backendLayout, $colPos, $pageId); + $allowedContentElementsInTargetColPos = GeneralUtility::trimExplode(',', $columnConfiguration['allowedContentTypes'] ?? '', true); + $disallowedContentElementsInTargetColPos = GeneralUtility::trimExplode(',', $columnConfiguration['disallowedContentTypes'] ?? '', true); + if ((!empty($allowedContentElementsInTargetColPos) && !in_array($recordData['CType'], $allowedContentElementsInTargetColPos, true)) + || (!empty($disallowedContentElementsInTargetColPos) && in_array($recordData['CType'], $disallowedContentElementsInTargetColPos, true)) + ) { + // Not allowed to create in this colPos on this page. Unset this command and create a log entry which may be turned into a notification when called by BE. + unset($dataHandler->datamap['tt_content'][$id]); + $dataHandler->log('tt_content', $id, 1, null, 1, 'The record "tt_content:%s" with CType "%s" in colPos "%s" couldn\'t be saved due to disallowed value(s).', null, [$id, $recordData['CType'], $colPos]); + } + } + } +} diff --git a/Classes/Hooks/Datahandler/DatahandlerProcess.php b/Classes/Hooks/Datahandler/DatahandlerProcess.php index 6c8b0e3f..b67ca8db 100644 --- a/Classes/Hooks/Datahandler/DatahandlerProcess.php +++ b/Classes/Hooks/Datahandler/DatahandlerProcess.php @@ -18,6 +18,25 @@ class DatahandlerProcess implements SingletonInterface { protected $containerInProcess = []; + protected $contentElementRestrictionsLook = false; + + protected $stack = 0; + + public function areContentElementRestrictionsLooked(): bool + { + return $this->stack > 0; + } + + public function unlockContentElementRestrictions(): void + { + $this->stack--; + } + + public function lockContentElementRestrictions(): void + { + $this->stack++; + } + public function isContainerInProcess(int $containerId): bool { return in_array($containerId, $this->containerInProcess, true); diff --git a/Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php b/Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php new file mode 100644 index 00000000..51936988 --- /dev/null +++ b/Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php @@ -0,0 +1,89 @@ +containerFactory = $containerFactory; + $this->tcaRegistry = $tcaRegistry; + } + + public function __invoke(ManipulateBackendLayoutColPosConfigurationForPageEvent $e) + { + $parent = $this->getParentUid($e->request); + if ($parent === null) { + return; + } + + try { + $container = $this->containerFactory->buildContainer($parent); + } catch (Exception $e) { + // not a container + return; + } + $cType = $container->getCType(); + $configuration = $this->tcaRegistry->getContentDefenderConfiguration($cType, $e->colPos); + $e->configuration = [ + 'allowedContentTypes' => $configuration['allowedContentTypes'], + 'disallowedContentTypes' => $configuration['disallowedContentTypes'], + ]; + } + + private function getParentUid(?ServerRequestInterface $request): ?int + { + if ($request === null) { + return null; + } + $queryParams = $request->getQueryParams(); + if (isset($queryParams['tx_container_parent']) && $queryParams['tx_container_parent'] > 0) { + // new content elemment wizard + return (int)$queryParams['tx_container_parent']; + } + if ( + isset($queryParams['defVals']['tt_content']['tx_container_parent']) && + $queryParams['defVals']['tt_content']['tx_container_parent'] > 0 + ) { + // TcaCTypeItems: new record + return (int)$queryParams['defVals']['tt_content']['tx_container_parent']; + } + if (isset($queryParams['edit']['tt_content'])) { + $recordUid = array_keys($queryParams['edit']['tt_content'])[0]; + $recordUid = (int)abs($recordUid); + // TcaCTypeItems: edit record + $record = BackendUtility::getRecord('tt_content', $recordUid, 'tx_container_parent'); + if (isset($record['tx_container_parent'])) { + return (int)$record['tx_container_parent']; + } + } + return null; + } +} diff --git a/Classes/Tca/Registry.php b/Classes/Tca/Registry.php index 86df6478..c099c896 100644 --- a/Classes/Tca/Registry.php +++ b/Classes/Tca/Registry.php @@ -124,9 +124,17 @@ public function getContentDefenderConfiguration(string $cType, int $colPos): arr foreach ($rows as $columns) { foreach ($columns as $column) { if ((int)$column['colPos'] === $colPos) { + $contentDefenderConfiguration['allowedContentTypes'] = $column['allowedContentTypes'] ?? ''; + $contentDefenderConfiguration['disallowedContentTypes'] = $column['disallowedContentTypes'] ?? ''; $contentDefenderConfiguration['allowed.'] = $column['allowed'] ?? []; $contentDefenderConfiguration['disallowed.'] = $column['disallowed'] ?? []; $contentDefenderConfiguration['maxitems'] = $column['maxitems'] ?? 0; + if ($contentDefenderConfiguration['allowedContentTypes'] === '' && $contentDefenderConfiguration['allowed.'] !== []) { + $contentDefenderConfiguration['allowedContentTypes'] = $contentDefenderConfiguration['allowed.']['CType'] ?? ''; + } + if ($contentDefenderConfiguration['disallowedContentTypes'] === '' && $contentDefenderConfiguration['disallowed.'] !== []) { + $contentDefenderConfiguration['disallowedContentTypes'] = $contentDefenderConfiguration['disallowed.']['CType'] ?? ''; + } return $contentDefenderConfiguration; } } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index a6d455d2..554b7fd0 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -57,6 +57,10 @@ services: tags: - name: event.listener identifier: 'tx-container-content-used-on-page' + B13\Container\Listener\ManipulateBackendLayoutColPosConfigurationForPage: + tags: + - name: event.listener + identifier: 'tx-container-ManipulateBackendLayoutColPosConfigurationForPage' B13\Container\Listener\ModifyNewContentElementWizardItems: tags: - name: event.listener diff --git a/Tests/Acceptance/Backend/ContentDefenderCest.php b/Tests/Acceptance/Backend/ContentDefenderCest.php index 1eafa7ff..49f364c5 100644 --- a/Tests/Acceptance/Backend/ContentDefenderCest.php +++ b/Tests/Acceptance/Backend/ContentDefenderCest.php @@ -15,8 +15,6 @@ use B13\Container\Tests\Acceptance\Support\BackendTester; use B13\Container\Tests\Acceptance\Support\PageTree; use B13\Container\Tests\Acceptance\Support\PageTreeV13; -use TYPO3\CMS\Core\Information\Typo3Version; -use TYPO3\CMS\Core\Utility\GeneralUtility; class ContentDefenderCest { @@ -34,7 +32,7 @@ public function _before(BackendTester $I) public function canCreateChildIn2ColsContainerWithNoContentDefenderRestrictionsDefined(BackendTester $I, PageTree $pageTree, PageTreeV13 $pageTreeV13): void { $I->clickLayoutModuleButton(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + if ($I->getTypo3MajorVersion() < 13) { $I->waitForElement('#typo3-pagetree-tree .nodes .node'); $pageTree->openPath(['home', 'pageWithDifferentContainers']); } else { @@ -47,7 +45,7 @@ public function canCreateChildIn2ColsContainerWithNoContentDefenderRestrictionsD $I->clickNewContentElement($colPosSelector); $I->switchToIFrame(); $I->waitForModal(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() > 12) { + if ($I->getTypo3MajorVersion() > 12) { $I->executeJS("document.querySelector('" . $I->getNewRecordWizardSelector() . "').shadowRoot.querySelector('button[data-identifier=\"default\"]').click()"); } $I->waitForText('Header Only'); @@ -61,7 +59,7 @@ public function canCreateChildIn2ColsContainerWithNoContentDefenderRestrictionsD public function doNotSeeNotAllowedContentElementsInNewContentElementWizard(BackendTester $I, PageTree $pageTree, PageTreeV13 $pageTreeV13): void { $I->clickLayoutModuleButton(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + if ($I->getTypo3MajorVersion() < 13) { $I->waitForElement('#typo3-pagetree-tree .nodes .node'); $pageTree->openPath(['home', 'pageWithContainer-3']); } else { @@ -74,7 +72,7 @@ public function doNotSeeNotAllowedContentElementsInNewContentElementWizard(Backe $I->clickNewContentElement($colPosSelector); $I->switchToIFrame(); $I->waitForModal(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() > 12) { + if ($I->getTypo3MajorVersion() > 12) { $I->executeJS("document.querySelector('" . $I->getNewRecordWizardSelector() . "').shadowRoot.querySelector('button[data-identifier=\"default\"]').click()"); } $I->waitForText('Header Only'); @@ -87,7 +85,7 @@ public function doNotSeeNotAllowedContentElementsInNewContentElementWizard(Backe public function doNotSeeNotAllowedContentElementsInNewContentElementWizardTriggeredByContextMenu(BackendTester $I, PageTree $pageTree, PageTreeV13 $pageTreeV13): void { $I->clickLayoutModuleButton(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + if ($I->getTypo3MajorVersion() < 13) { $I->waitForElement('#typo3-pagetree-tree .nodes .node'); $pageTree->openPath(['home', 'pageWithContainer-3']); } else { @@ -95,15 +93,14 @@ public function doNotSeeNotAllowedContentElementsInNewContentElementWizardTrigge } $I->wait(0.5); $I->switchToContentFrame(); - $typo3Version = GeneralUtility::makeInstance(Typo3Version::class); - if ($typo3Version->getMajorVersion() < 12) { + if ($I->getTypo3MajorVersion() < 12) { $I->waitForElement('#element-tt_content-810 a.t3js-contextmenutrigger'); $I->click('#element-tt_content-810 a.t3js-contextmenutrigger'); } else { $I->waitForElement('#element-tt_content-800 [data-contextmenu-uid="810"]'); $I->click('#element-tt_content-800 [data-contextmenu-uid="810"]'); } - switch ($typo3Version->getMajorVersion()) { + switch ($I->getTypo3MajorVersion()) { case 11: $I->waitForText('More options...'); $I->click('.list-group-item-submenu'); @@ -128,7 +125,7 @@ public function doNotSeeNotAllowedContentElementsInNewContentElementWizardTrigge $I->switchToIFrame(); $I->waitForModal(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() > 12) { + if ($I->getTypo3MajorVersion() > 12) { $I->executeJS("document.querySelector('" . $I->getNewRecordWizardSelector() . "').shadowRoot.querySelector('button[data-identifier=\"default\"]').click()"); } $I->waitForText('Header Only'); @@ -141,7 +138,7 @@ public function doNotSeeNotAllowedContentElementsInNewContentElementWizardTrigge public function doNotSeeNotAllowedContentElementsInCTypeSelectBoxWhenCreateNewElement(BackendTester $I, PageTree $pageTree, PageTreeV13 $pageTreeV13) { $I->clickLayoutModuleButton(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + if ($I->getTypo3MajorVersion() < 13) { $I->waitForElement('#typo3-pagetree-tree .nodes .node'); $pageTree->openPath(['home', 'pageWithContainer-4']); } else { @@ -154,15 +151,14 @@ public function doNotSeeNotAllowedContentElementsInCTypeSelectBoxWhenCreateNewEl $I->clickNewContentElement($colPosSelector); $I->switchToIFrame(); $I->waitForModal(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() > 12) { + if ($I->getTypo3MajorVersion() > 12) { $I->executeJS("document.querySelector('" . $I->getNewRecordWizardSelector() . "').shadowRoot.querySelector('button[data-identifier=\"default\"]').click()"); } $I->waitForText('Header Only'); - $typo3Version = GeneralUtility::makeInstance(Typo3Version::class); - if ($typo3Version->getMajorVersion() < 12) { + if ($I->getTypo3MajorVersion() < 12) { $I->click('Header Only'); } else { - if ($typo3Version->getMajorVersion() < 13) { + if ($I->getTypo3MajorVersion() < 13) { $I->executeJS("document.querySelector('" . $I->getNewRecordWizardSelector() . "').shadowRoot.querySelector('button[data-identifier=\"common_header\"]').click()"); } else { $I->executeJS("document.querySelector('" . $I->getNewRecordWizardSelector() . "').shadowRoot.querySelector('button[data-identifier=\"default_header\"]').click()"); @@ -180,7 +176,7 @@ public function doNotSeeNotAllowedContentElementsInCTypeSelectBoxWhenCreateNewEl public function doNotSeeNotAllowedContentElementsInCTypeSelectBoxWhenEditAnElement(BackendTester $I, PageTree $pageTree, PageTreeV13 $pageTreeV13) { $I->clickLayoutModuleButton(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + if ($I->getTypo3MajorVersion() < 13) { $I->waitForElement('#typo3-pagetree-tree .nodes .node'); $pageTree->openPath(['home', 'contentTCASelectCtype']); } else { @@ -201,7 +197,7 @@ public function doNotSeeNotAllowedContentElementsInCTypeSelectBoxWhenEditAnEleme public function canSeeNewContentButtonIfMaxitemsIsNotReached(BackendTester $I, PageTree $pageTree, PageTreeV13 $pageTreeV13) { $I->clickLayoutModuleButton(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + if ($I->getTypo3MajorVersion() < 13) { $I->waitForElement('#typo3-pagetree-tree .nodes .node'); $pageTree->openPath(['home', 'contentDefenderMaxitems']); } else { @@ -220,7 +216,7 @@ public function canSeeNewContentButtonIfMaxitemsIsNotReached(BackendTester $I, P public function canNotSeeNewContentButtonIfMaxitemsIsReached(BackendTester $I, PageTree $pageTree, PageTreeV13 $pageTreeV13) { $I->clickLayoutModuleButton(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + if ($I->getTypo3MajorVersion() < 13) { $I->waitForElement('#typo3-pagetree-tree .nodes .node'); $pageTree->openPath(['home', 'contentDefenderMaxitems']); } else { @@ -240,7 +236,7 @@ public function canNotSeeNewContentButtonIfMaxitemsIsReached(BackendTester $I, P public function canCreateNewChildInContainerIfMaxitemsIsReachedInOtherContainer(BackendTester $I, PageTree $pageTree, PageTreeV13 $pageTreeV13) { $I->clickLayoutModuleButton(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + if ($I->getTypo3MajorVersion() < 13) { $I->waitForElement('#typo3-pagetree-tree .nodes .node'); $pageTree->openPath(['home', 'contentDefenderMaxitems']); } else { @@ -250,18 +246,18 @@ public function canCreateNewChildInContainerIfMaxitemsIsReachedInOtherContainer( $I->switchToContentFrame(); $dataColPos = $I->getDataColPos(402, 202); $colPosSelector = '#element-tt_content-402 [data-colpos="' . $dataColPos . '"]'; + $I->scrollTo($colPosSelector); $I->clickNewContentElement($colPosSelector); $I->switchToIFrame(); $I->waitForModal(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() > 12) { + if ($I->getTypo3MajorVersion() > 12) { $I->executeJS("document.querySelector('" . $I->getNewRecordWizardSelector() . "').shadowRoot.querySelector('button[data-identifier=\"default\"]').click()"); } $I->waitForText('Header Only'); - $typo3Version = GeneralUtility::makeInstance(Typo3Version::class); - if ($typo3Version->getMajorVersion() < 12) { + if ($I->getTypo3MajorVersion() < 12) { $I->click('Header Only'); } else { - if ($typo3Version->getMajorVersion() < 13) { + if ($I->getTypo3MajorVersion() < 13) { $I->executeJS("document.querySelector('" . $I->getNewRecordWizardSelector() . "').shadowRoot.querySelector('button[data-identifier=\"common_header\"]').click()"); } else { $I->executeJS("document.querySelector('" . $I->getNewRecordWizardSelector() . "').shadowRoot.querySelector('button[data-identifier=\"default_header\"]').click()"); @@ -278,7 +274,7 @@ public function canCreateNewChildInContainerIfMaxitemsIsReachedInOtherContainer( public function seeEditDocumentWhenAddingChildrenToColposWhereOnlyHeaderIsAllowed(BackendTester $I, PageTree $pageTree, PageTreeV13 $pageTreeV13) { $I->clickLayoutModuleButton(); - if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + if ($I->getTypo3MajorVersion() < 13) { $I->waitForElement('#typo3-pagetree-tree .nodes .node'); $pageTree->openPath(['home', 'pageWithDifferentContainers']); } else { diff --git a/Tests/Functional/Datahandler/ContentDefender/CopyContainerTest.php b/Tests/Functional/Datahandler/ContentDefender/CopyContainerTest.php index a36f866b..8662c214 100644 --- a/Tests/Functional/Datahandler/ContentDefender/CopyContainerTest.php +++ b/Tests/Functional/Datahandler/ContentDefender/CopyContainerTest.php @@ -12,6 +12,8 @@ * of the License, or any later version. */ +use TYPO3\CMS\Core\Information\Typo3Version; + class CopyContainerTest extends AbstractContentDefender { /** @@ -20,11 +22,13 @@ class CopyContainerTest extends AbstractContentDefender protected array $testExtensionsToLoad = [ 'typo3conf/ext/container', 'typo3conf/ext/container_example', - 'typo3conf/ext/content_defender', ]; protected function setUp(): void { + if ((new Typo3Version())->getMajorVersion() < 14) { + $this->testExtensionsToLoad[] = 'typo3conf/ext/content_defender'; + } parent::setUp(); $this->importCSVDataSet(__DIR__ . '/Fixtures/copy_container.csv'); } diff --git a/Tests/Functional/Datahandler/ContentDefender/DefaultLanguageTest.php b/Tests/Functional/Datahandler/ContentDefender/DefaultLanguageTest.php index 116216ad..4be109a9 100644 --- a/Tests/Functional/Datahandler/ContentDefender/DefaultLanguageTest.php +++ b/Tests/Functional/Datahandler/ContentDefender/DefaultLanguageTest.php @@ -12,6 +12,8 @@ * of the License, or any later version. */ +use TYPO3\CMS\Core\Information\Typo3Version; + class DefaultLanguageTest extends AbstractContentDefender { /** @@ -20,11 +22,13 @@ class DefaultLanguageTest extends AbstractContentDefender protected array $testExtensionsToLoad = [ 'typo3conf/ext/container', 'typo3conf/ext/container_example', - 'typo3conf/ext/content_defender', ]; protected function setUp(): void { + if ((new Typo3Version())->getMajorVersion() < 14) { + $this->testExtensionsToLoad[] = 'typo3conf/ext/content_defender'; + } parent::setUp(); $this->importCSVDataSet(__DIR__ . '/Fixtures/DefaultLanguage/setup.csv'); } diff --git a/Tests/Functional/Datahandler/ContentDefender/LocalizationTest.php b/Tests/Functional/Datahandler/ContentDefender/LocalizationTest.php index 9c337fa3..ca2b5319 100644 --- a/Tests/Functional/Datahandler/ContentDefender/LocalizationTest.php +++ b/Tests/Functional/Datahandler/ContentDefender/LocalizationTest.php @@ -12,6 +12,8 @@ * of the License, or any later version. */ +use TYPO3\CMS\Core\Information\Typo3Version; + class LocalizationTest extends AbstractContentDefender { /** @@ -20,11 +22,13 @@ class LocalizationTest extends AbstractContentDefender protected array $testExtensionsToLoad = [ 'typo3conf/ext/container', 'typo3conf/ext/container_example', - 'typo3conf/ext/content_defender', ]; protected function setUp(): void { + if ((new Typo3Version())->getMajorVersion() < 14) { + $this->testExtensionsToLoad[] = 'typo3conf/ext/content_defender'; + } parent::setUp(); $this->importCSVDataSet(__DIR__ . '/Fixtures/Localization/setup.csv'); } diff --git a/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php b/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php index 7702755c..d2949c0d 100644 --- a/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php +++ b/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php @@ -12,6 +12,7 @@ * of the License, or any later version. */ +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Utility\StringUtility; class MaxItemsTest extends AbstractContentDefender @@ -22,11 +23,13 @@ class MaxItemsTest extends AbstractContentDefender protected array $testExtensionsToLoad = [ 'typo3conf/ext/container', 'typo3conf/ext/container_example', - 'typo3conf/ext/content_defender', ]; protected function setUp(): void { + if ((new Typo3Version())->getMajorVersion() < 14) { + $this->testExtensionsToLoad[] = 'typo3conf/ext/content_defender'; + } parent::setUp(); $this->linkSiteConfigurationIntoTestInstance(); } @@ -87,7 +90,11 @@ public function cannotMoveElementIntoContainerIfMaxitemsIsReached(): void $this->dataHandler->process_datamap(); $this->dataHandler->process_cmdmap(); self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/CannotMoveElementIntoContainerIfMaxitemsIsReachedResult.csv'); - self::assertNotEmpty($this->dataHandler->errorLog, 'dataHander error log is empty'); + if ((new Typo3Version())->getMajorVersion() < 14) { + self::assertNotEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + } else { + self::assertTrue($this->dataHandler->errorLog !== [], 'dataHander error log is not empty'); + } } /** @@ -119,7 +126,11 @@ public function cannotCopyElementIntoContainerIfMaxitemsIsReachedAfterIntoContai $this->dataHandler->process_datamap(); $this->dataHandler->process_cmdmap(); self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/CannotCopyElementIntoContainerIfMaxitemsIsReachedAfterIntoContainerResult.csv'); - self::assertNotEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + if ((new Typo3Version())->getMajorVersion() < 14) { + self::assertNotEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + } else { + self::assertTrue($this->dataHandler->errorLog !== [], 'dataHander error log is not empty'); + } } /** @@ -140,7 +151,11 @@ public function cannotCopyElementIntoContainerIfMaxitemsIsReachedAfterElement(): $this->dataHandler->process_datamap(); $this->dataHandler->process_cmdmap(); self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/CannotCopyElementIntoContainerIfMaxitemsIsReachedAfterElementResult.csv'); - self::assertNotEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + if ((new Typo3Version())->getMajorVersion() < 14) { + self::assertNotEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + } else { + self::assertTrue($this->dataHandler->errorLog !== [], 'dataHander error log is not empty'); + } } /** @@ -356,7 +371,11 @@ public function cannotMoveElementInsideContainerColumnIfMaxitemsIsReached(): voi $this->dataHandler->start([], $cmdmap, $this->backendUser); $this->dataHandler->process_datamap(); $this->dataHandler->process_cmdmap(); - self::assertNotEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + if ((new Typo3Version())->getMajorVersion() < 14) { + self::assertNotEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + } else { + self::assertTrue($this->dataHandler->errorLog !== [], 'dataHander error log is not empty'); + } } /** @@ -375,7 +394,11 @@ public function canTranslateChildIfContainerOfDefaultLanguageMaxitemsIsReached() $this->dataHandler->process_datamap(); $this->dataHandler->process_cmdmap(); self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/CanTranslateChildIfContainerOfDefaultLanguageMaxitemsIsReachedResult.csv'); - self::assertEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + if ((new Typo3Version())->getMajorVersion() < 14) { + self::assertEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + } else { + self::assertTrue($this->dataHandler->errorLog === [], 'dataHander error log is not empty'); + } } /** @@ -394,7 +417,11 @@ public function canCopyToLanguageChildIfContainerOfDefaultLanguageMaxitemsIsReac $this->dataHandler->process_datamap(); $this->dataHandler->process_cmdmap(); self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/CanCopyToLanguageChildIfContainerOfDefaultLanguageMaxitemsIsReachedResult.csv'); - self::assertEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + if ((new Typo3Version())->getMajorVersion() < 14) { + self::assertEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + } else { + self::assertTrue($this->dataHandler->errorLog === [], 'dataHander error log is not empty'); + } } /** @@ -421,7 +448,11 @@ public function canSaveChildInDefaultLanguageWhenTranslatedAndMaxitemsIsReached( $this->dataHandler->start($datamap, [], $this->backendUser); $this->dataHandler->process_datamap(); self::assertCSVDataSet(__DIR__ . '/Fixtures/Maxitems/CanSaveChildInDefaultLanguageWhenTranslatedAndMaxitemsIsReachedResult.csv'); - self::assertEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + if ((new Typo3Version())->getMajorVersion() < 14) { + self::assertEmpty($this->dataHandler->errorLog, 'dataHander error log is not empty'); + } else { + self::assertTrue($this->dataHandler->errorLog === [], 'dataHander error log is not empty'); + } } /** diff --git a/Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php b/Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php index 0665546a..f72a35e3 100644 --- a/Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php +++ b/Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php @@ -12,7 +12,9 @@ * of the License, or any later version. */ +use B13\Container\Hooks\Datahandler\DatahandlerProcess; use B13\Container\Tests\Functional\Datahandler\AbstractDatahandler; +use TYPO3\CMS\Core\Utility\GeneralUtility; class CopyToLanguageSortingTest extends AbstractDatahandler { @@ -119,5 +121,6 @@ public function localizeWithMultipleNestedElements(): void $this->dataHandler->start([], $cmdmap, $this->backendUser); $this->dataHandler->process_cmdmap(); self::assertCSVDataSet(__DIR__ . '/Fixtures/CopyToLanguageSorting/LocalizeWithMultipleNestedElementsResult.csv'); + self::assertFalse((GeneralUtility::makeInstance(DatahandlerProcess::class))->areContentElementRestrictionsLooked()); } } diff --git a/ext_localconf.php b/ext_localconf.php index 52f87fb4..ec68da51 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -63,4 +63,9 @@ if (TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(TYPO3\CMS\Core\Information\Typo3Version::class)->getMajorVersion() < 12) { $GLOBALS['TYPO3_CONF_VARS']['BE']['ContextMenu']['ItemProviders'][1729106358] = \B13\Container\Backend\ContextMenu\RecordContextMenuItemProvider::class; } + + if ($typo3Version->getMajorVersion() > 13) { + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['contentElementRestriction'] = \B13\Container\Hooks\Datahandler\ContentElementRestriction\DataHandlerHook::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['contentElementRestriction'] = \B13\Container\Hooks\Datahandler\ContentElementRestriction\DataHandlerHook::class; + } }); From 999ffb7abea5eb26886d8dd10a141faa8813a7b4 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 25 Feb 2026 23:13:27 +0100 Subject: [PATCH 2/3] [TASK] make ci happy --- .../Datahandler/ContentDefender/CopyContainerTest.php | 2 +- .../Datahandler/ContentDefender/DefaultLanguageTest.php | 2 +- .../Functional/Datahandler/ContentDefender/LocalizationTest.php | 2 +- Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php | 2 +- .../Datahandler/Localization/CopyToLanguageSortingTest.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/Functional/Datahandler/ContentDefender/CopyContainerTest.php b/Tests/Functional/Datahandler/ContentDefender/CopyContainerTest.php index f17fe0a4..cebde641 100644 --- a/Tests/Functional/Datahandler/ContentDefender/CopyContainerTest.php +++ b/Tests/Functional/Datahandler/ContentDefender/CopyContainerTest.php @@ -12,9 +12,9 @@ * of the License, or any later version. */ -use TYPO3\CMS\Core\Information\Typo3Version; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Information\Typo3Version; class CopyContainerTest extends AbstractContentDefender { diff --git a/Tests/Functional/Datahandler/ContentDefender/DefaultLanguageTest.php b/Tests/Functional/Datahandler/ContentDefender/DefaultLanguageTest.php index 1d82e5b9..a39ddb33 100644 --- a/Tests/Functional/Datahandler/ContentDefender/DefaultLanguageTest.php +++ b/Tests/Functional/Datahandler/ContentDefender/DefaultLanguageTest.php @@ -12,9 +12,9 @@ * of the License, or any later version. */ -use TYPO3\CMS\Core\Information\Typo3Version; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Information\Typo3Version; class DefaultLanguageTest extends AbstractContentDefender { diff --git a/Tests/Functional/Datahandler/ContentDefender/LocalizationTest.php b/Tests/Functional/Datahandler/ContentDefender/LocalizationTest.php index 7f7fa44c..202b645b 100644 --- a/Tests/Functional/Datahandler/ContentDefender/LocalizationTest.php +++ b/Tests/Functional/Datahandler/ContentDefender/LocalizationTest.php @@ -12,9 +12,9 @@ * of the License, or any later version. */ -use TYPO3\CMS\Core\Information\Typo3Version; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Information\Typo3Version; class LocalizationTest extends AbstractContentDefender { diff --git a/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php b/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php index 031fcb67..6b5cd82c 100644 --- a/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php +++ b/Tests/Functional/Datahandler/ContentDefender/MaxItemsTest.php @@ -12,9 +12,9 @@ * of the License, or any later version. */ -use TYPO3\CMS\Core\Information\Typo3Version; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Utility\StringUtility; class MaxItemsTest extends AbstractContentDefender diff --git a/Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php b/Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php index f3fd0773..1b88143d 100644 --- a/Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php +++ b/Tests/Functional/Datahandler/Localization/CopyToLanguageSortingTest.php @@ -14,9 +14,9 @@ use B13\Container\Hooks\Datahandler\DatahandlerProcess; use B13\Container\Tests\Functional\Datahandler\AbstractDatahandler; -use TYPO3\CMS\Core\Utility\GeneralUtility; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Utility\GeneralUtility; class CopyToLanguageSortingTest extends AbstractDatahandler { From 1b42c7dc5fd145bcec96fb91256cd2e0e977c563 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 25 Feb 2026 23:30:42 +0100 Subject: [PATCH 3/3] [TASK] as event listener --- ...teBackendLayoutColPosConfigurationForPage.php | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php b/Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php index 51936988..be42c5f6 100644 --- a/Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php +++ b/Classes/Listener/ManipulateBackendLayoutColPosConfigurationForPage.php @@ -18,23 +18,13 @@ use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Backend\View\Event\ManipulateBackendLayoutColPosConfigurationForPageEvent; +use TYPO3\CMS\Core\Attribute\AsEventListener; +#[AsEventListener(identifier: 'tx-container-manipulate-backend-layout-col-pos-configuration-for-page')] class ManipulateBackendLayoutColPosConfigurationForPage { - /** - * @var Registry - */ - protected $tcaRegistry; - - /** - * @var ContainerFactory - */ - protected $containerFactory; - - public function __construct(ContainerFactory $containerFactory, Registry $tcaRegistry) + public function __construct(protected ContainerFactory $containerFactory, protected Registry $tcaRegistry) { - $this->containerFactory = $containerFactory; - $this->tcaRegistry = $tcaRegistry; } public function __invoke(ManipulateBackendLayoutColPosConfigurationForPageEvent $e)