From 41e290769539f8e6d5efa0a988690a0716d9d5e5 Mon Sep 17 00:00:00 2001 From: Matthias Vogel Date: Mon, 16 Mar 2026 13:57:13 +0100 Subject: [PATCH] [FEATURE] add ContentAreaProcessor for TYPO3 v14 f:render.contentArea --- .../DataProcessing/ContentAreaProcessor.php | 121 ++++++++++++++++++ Classes/Tca/Registry.php | 13 ++ .../Frontend/DefaultLanguageTest.php | 28 ++++ .../Fixtures/default_language_ContentArea.csv | 8 ++ 4 files changed, 170 insertions(+) create mode 100644 Classes/DataProcessing/ContentAreaProcessor.php create mode 100644 Tests/Functional/Frontend/Fixtures/default_language_ContentArea.csv diff --git a/Classes/DataProcessing/ContentAreaProcessor.php b/Classes/DataProcessing/ContentAreaProcessor.php new file mode 100644 index 00000000..3171e005 --- /dev/null +++ b/Classes/DataProcessing/ContentAreaProcessor.php @@ -0,0 +1,121 @@ + + * + */ +#[Autoconfigure(public: true)] +readonly class ContentAreaProcessor implements DataProcessorInterface +{ + + public function __construct( + protected ContentDataProcessor $contentDataProcessor, + protected Context $context, + protected FrontendContainerFactory $frontendContainerFactory, + protected Registry $tcaRegistry, + protected RecordFactory $recordFactory, + protected Typo3Version $typo3Version, + protected LoggerInterface $logger, + ) {} + + public function process( + ContentObjectRenderer $cObj, + array $contentObjectConfiguration, + array $processorConfiguration, + array $processedData, + ): array { + if (((float)$this->typo3Version->getBranch()) <= 14.1) { + $this->logger->error(ContentAreaProcessor::class . ' requires TYPO3 v14.2 or higher. Please check your configuration.'); + + return $processedData; + } + + $record = $cObj->data; + + $CType = $record['CType'] ?? ''; + if (!$this->tcaRegistry->isContainerElement($CType)) { + return $processedData; + } + + $columnsColPos = $this->tcaRegistry->getAllAvailableColumnsColPos($CType); + + $container = null; + + $areas = []; + foreach ($columnsColPos as $colPos) { + $areas[$colPos] = new ContentAreaClosure( + function () use (&$container, $CType, $cObj, $record, $colPos): ContentArea { + $container ??= $this->frontendContainerFactory->buildContainer($cObj, $this->context, (int)$record['uid']); + + $contentDefenderConfiguration = $this->tcaRegistry->getContentDefenderConfiguration($CType, $colPos); + + $rows = $container->getChildrenByColPos($colPos); + + $records = array_map(fn($row) => $this->recordFactory->createFromDatabaseRow('tt_content', $row), $rows); + return new ContentArea( + (string)$colPos, + $this->tcaRegistry->getColPosName($record['CType'], $colPos), + $colPos, + ContentSlideMode::None, + GeneralUtility::trimExplode(',', $contentDefenderConfiguration['allowedContentTypes'] ?? '', true), + GeneralUtility::trimExplode(',', $contentDefenderConfiguration['disallowedContentTypes'] ?? '', true), + [ + 'container' => $container, + ], + $records, + ); + }, + ); + } + + $processedData[$processedConfiguration['as'] ?? 'content'] = new ContentAreaCollection($areas); + return $processedData; + } +} diff --git a/Classes/Tca/Registry.php b/Classes/Tca/Registry.php index 72ad3c83..941cf19a 100644 --- a/Classes/Tca/Registry.php +++ b/Classes/Tca/Registry.php @@ -187,6 +187,19 @@ public function getContainerLabel(string $cType): string return $GLOBALS['TCA']['tt_content']['containerConfiguration'][$cType]['label'] ?? $cType; } + public function getColPosName(string $cType, int $colPos): ?string + { + $grid = $this->getGrid($cType); + foreach ($grid as $row) { + foreach ($row as $column) { + if ($column['colPos'] === $colPos) { + return (string)$column['name']; + } + } + } + return null; + } + public function getAvailableColumns(string $cType): array { $columns = []; diff --git a/Tests/Functional/Frontend/DefaultLanguageTest.php b/Tests/Functional/Frontend/DefaultLanguageTest.php index 7b0a0e58..0ab829d0 100644 --- a/Tests/Functional/Frontend/DefaultLanguageTest.php +++ b/Tests/Functional/Frontend/DefaultLanguageTest.php @@ -12,6 +12,7 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest; class DefaultLanguageTest extends AbstractFrontend @@ -39,6 +40,33 @@ public function childrenAreRendered(): void self::assertStringContainsString('

left-side-default

', $body); } + #[Test] + #[Group('frontend')] + public function childrenAreRenderedContentArea(): void + { + if (((float)(new Typo3Version())->getBranch()) < 14.2) { + $this->markTestSkipped('Content area rendering is only supported in TYPO3 v14.2 and above'); + } + + $this->importCSVDataSet(__DIR__ . '/Fixtures/default_language_ContentArea.csv'); + $this->setUpFrontendRootPage( + 1, + [ + 'constants' => ['EXT:container/Tests/Functional/Frontend/Fixtures/TypoScript/constants.typoscript'], + 'setup' => [ + 'EXT:container/Tests/Functional/Frontend/Fixtures/TypoScript/setup.typoscript', + 'EXT:container_example/Configuration/Sets/ContainerExample/TypoScript/2ColsContentArea/setup.typoscript', + ], + ] + ); + $response = $this->executeFrontendRequestWrapper(new InternalRequest('http://localhost/')); + $body = (string)$response->getBody(); + $body = $this->prepareContent($body); + // rendered content + self::assertStringContainsString('

left-side-default

', $body); + self::assertStringContainsString('

right-side-default

', $body); + } + #[Test] #[Group('frontend')] public function childrenAreRenderedAsSorted(): void diff --git a/Tests/Functional/Frontend/Fixtures/default_language_ContentArea.csv b/Tests/Functional/Frontend/Fixtures/default_language_ContentArea.csv new file mode 100644 index 00000000..bde8fa79 --- /dev/null +++ b/Tests/Functional/Frontend/Fixtures/default_language_ContentArea.csv @@ -0,0 +1,8 @@ +"pages" +,"uid","pid","title","slug" +,1,0,"page-1","/" +"tt_content" +,"uid","pid","CType","header","sorting","sys_language_uid","colPos","tx_container_parent" +,1,1,"b13-2cols-content-area","container-default",256,0,, +,2,1,"header","left-side-default",128,0,200,1 +,3,1,"header","right-side-default",64,0,201,1