diff --git a/Build/phpstan11-7.4.neon b/Build/phpstan11-7.4.neon index 75fd97dc..be7d44f8 100644 --- a/Build/phpstan11-7.4.neon +++ b/Build/phpstan11-7.4.neon @@ -15,4 +15,5 @@ parameters: - %currentWorkingDirectory%/Tests/Functional/Listener/ContentUsedOnPageTest.php - %currentWorkingDirectory%/Classes/Listener/RecordSummaryForLocalization.php - %currentWorkingDirectory%/Classes/Listener/PageTsConfig.php + - %currentWorkingDirectory%/Classes/Listener/PageContentPreviewRendering.php diff --git a/Build/phpstan11.neon b/Build/phpstan11.neon index b3e9c6fc..443a2014 100644 --- a/Build/phpstan11.neon +++ b/Build/phpstan11.neon @@ -15,4 +15,5 @@ parameters: - %currentWorkingDirectory%/Tests/Functional/Listener/ContentUsedOnPageTest.php - %currentWorkingDirectory%/Classes/Listener/RecordSummaryForLocalization.php - %currentWorkingDirectory%/Classes/Listener/PageTsConfig.php + - %currentWorkingDirectory%/Classes/Listener/PageContentPreviewRendering.php diff --git a/Classes/Backend/Preview/ContainerPreviewRenderer.php b/Classes/Backend/Preview/ContainerPreviewRenderer.php index 73e98640..12a5cd88 100644 --- a/Classes/Backend/Preview/ContainerPreviewRenderer.php +++ b/Classes/Backend/Preview/ContainerPreviewRenderer.php @@ -12,122 +12,37 @@ * of the License, or any later version. */ -use B13\Container\Backend\Grid\ContainerGridColumn; -use B13\Container\Backend\Grid\ContainerGridColumnItem; -use B13\Container\Backend\Service\NewContentUrlBuilder; -use B13\Container\Domain\Factory\Exception; -use B13\Container\Domain\Factory\PageView\Backend\ContainerFactory; -use B13\Container\Events\BeforeContainerPreviewIsRenderedEvent; -use B13\Container\Tca\Registry; -use Psr\EventDispatcher\EventDispatcherInterface; + use TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer; -use TYPO3\CMS\Backend\Utility\BackendUtility; -use TYPO3\CMS\Backend\View\BackendLayout\Grid\Grid; use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem; -use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridRow; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Fluid\View\StandaloneView; class ContainerPreviewRenderer extends StandardContentPreviewRenderer { - /** - * @var Registry - */ - protected $tcaRegistry; - - /** - * @var ContainerFactory - */ - protected $containerFactory; - - protected NewContentUrlBuilder $newContentUrlBuilder; - - /** - * @var EventDispatcherInterface - */ - protected $eventDispatcher; + protected GridRenderer $gridRenderer; + protected FrontendInterface $runtimeCache; - public function __construct( - Registry $tcaRegistry, - ContainerFactory $containerFactory, - NewContentUrlBuilder $newContentUrlBuilder, - EventDispatcherInterface $eventDispatcher - ) { - $this->eventDispatcher = $eventDispatcher; - $this->tcaRegistry = $tcaRegistry; - $this->containerFactory = $containerFactory; - $this->newContentUrlBuilder = $newContentUrlBuilder; + public function __construct(GridRenderer $gridRenderer, FrontendInterface $runtimeCache) { + $this->gridRenderer = $gridRenderer; + $this->runtimeCache = $runtimeCache; } - public function renderPageModulePreviewContent(GridColumnItem $item): string + public function renderPageModulePreviewHeader(GridColumnItem $item): string { - $content = parent::renderPageModulePreviewContent($item); - $context = $item->getContext(); - $record = $item->getRecord(); - $grid = GeneralUtility::makeInstance(Grid::class, $context); - try { - $container = $this->containerFactory->buildContainer((int)$record['uid']); - } catch (Exception $e) { - // not a container - return $content; - } - $containerGrid = $this->tcaRegistry->getGrid($record['CType']); - foreach ($containerGrid as $cols) { - $rowObject = GeneralUtility::makeInstance(GridRow::class, $context); - foreach ($cols as $col) { - $defVals = $this->getDefValsForContentDefenderAllowsOnlyOneSpecificContentType($record['CType'], (int)$col['colPos']); - $url = $this->newContentUrlBuilder->getNewContentUrlAtTopOfColumn($context, $container, (int)$col['colPos'], $defVals); - $columnObject = GeneralUtility::makeInstance(ContainerGridColumn::class, $context, $col, $container, $url, $defVals !== null); - $rowObject->addColumn($columnObject); - if (isset($col['colPos'])) { - $records = $container->getChildrenByColPos($col['colPos']); - foreach ($records as $contentRecord) { - $url = $this->newContentUrlBuilder->getNewContentUrlAfterChild($context, $container, (int)$col['colPos'], (int)$contentRecord['uid'], $defVals); - $columnItem = GeneralUtility::makeInstance(ContainerGridColumnItem::class, $context, $columnObject, $contentRecord, $container, $url); - $columnObject->addItem($columnItem); - } - } - } - $grid->addRow($rowObject); - } - - $gridTemplate = $this->tcaRegistry->getGridTemplate($record['CType']); - $partialRootPaths = $this->tcaRegistry->getGridPartialPaths($record['CType']); - $layoutRootPaths = $this->tcaRegistry->getGridLayoutPaths($record['CType']); - $view = GeneralUtility::makeInstance(StandaloneView::class); - $view->setPartialRootPaths($partialRootPaths); - $view->setLayoutRootPaths($layoutRootPaths); - $view->setTemplatePathAndFilename($gridTemplate); - - $view->assign('hideRestrictedColumns', (bool)(BackendUtility::getPagesTSconfig($context->getPageId())['mod.']['web_layout.']['hideRestrictedCols'] ?? false)); - $view->assign('newContentTitle', $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:newContentElement')); - $view->assign('newContentTitleShort', $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:content')); - $view->assign('allowEditContent', $this->getBackendUser()->check('tables_modify', 'tt_content')); - // keep compatibility - $view->assign('containerGrid', $grid); - $view->assign('grid', $grid); - $view->assign('containerRecord', $record); - $view->assign('context', $context); - $beforeContainerPreviewIsRendered = new BeforeContainerPreviewIsRenderedEvent($container, $view, $grid, $item); - $this->eventDispatcher->dispatch($beforeContainerPreviewIsRendered); - $rendered = $view->render(); - - return $content . $rendered; + $this->runtimeCache->set('tx_container_current_gridColumItem', $item); + return parent::renderPageModulePreviewHeader($item); } - protected function getDefValsForContentDefenderAllowsOnlyOneSpecificContentType(string $cType, int $colPos): ?array + public function renderPageModulePreviewContent(GridColumnItem $item): string { - $contentDefefenderConfiguration = $this->tcaRegistry->getContentDefenderConfiguration($cType, $colPos); - $allowedCTypes = GeneralUtility::trimExplode(',', $contentDefefenderConfiguration['allowed.']['CType'] ?? '', true); - $allowedListTypes = GeneralUtility::trimExplode(',', $contentDefefenderConfiguration['allowed.']['list_type'] ?? '', true); - if (count($allowedCTypes) === 1) { - if ($allowedCTypes[0] !== 'list') { - return ['CType' => $allowedCTypes[0]]; - } - if (count($allowedListTypes) === 1) { - return ['CType' => 'list', 'list_type' => $allowedListTypes[0]]; - } + if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() > 11) { + return parent::renderPageModulePreviewContent($item); } - return null; + $record = $item->getRecord(); + $record['tx_container_grid'] = $this->gridRenderer->renderGrid($record, $item->getContext()); + $item->setRecord($record); + return parent::renderPageModulePreviewContent($item); } } diff --git a/Classes/Backend/Preview/GridRenderer.php b/Classes/Backend/Preview/GridRenderer.php new file mode 100644 index 00000000..101ee4ea --- /dev/null +++ b/Classes/Backend/Preview/GridRenderer.php @@ -0,0 +1,135 @@ +eventDispatcher = $eventDispatcher; + $this->tcaRegistry = $tcaRegistry; + $this->containerFactory = $containerFactory; + $this->newContentUrlBuilder = $newContentUrlBuilder; + $this->runtimeCache = $runtimeCache; + } + + public function renderGrid(array $record, PageLayoutContext $context): string + { + $grid = GeneralUtility::makeInstance(Grid::class, $context); + try { + $container = $this->containerFactory->buildContainer((int)$record['uid']); + } catch (Exception $e) { + // not a container + return ''; + } + $containerGrid = $this->tcaRegistry->getGrid($record['CType']); + foreach ($containerGrid as $cols) { + $rowObject = GeneralUtility::makeInstance(GridRow::class, $context); + foreach ($cols as $col) { + $defVals = $this->getDefValsForContentDefenderAllowsOnlyOneSpecificContentType($record['CType'], (int)$col['colPos']); + $url = $this->newContentUrlBuilder->getNewContentUrlAtTopOfColumn($context, $container, (int)$col['colPos'], $defVals); + $columnObject = GeneralUtility::makeInstance(ContainerGridColumn::class, $context, $col, $container, $url, $defVals !== null); + $rowObject->addColumn($columnObject); + if (isset($col['colPos'])) { + $records = $container->getChildrenByColPos($col['colPos']); + foreach ($records as $contentRecord) { + $url = $this->newContentUrlBuilder->getNewContentUrlAfterChild($context, $container, (int)$col['colPos'], (int)$contentRecord['uid'], $defVals); + $columnItem = GeneralUtility::makeInstance(ContainerGridColumnItem::class, $context, $columnObject, $contentRecord, $container, $url); + $columnObject->addItem($columnItem); + } + } + } + $grid->addRow($rowObject); + } + + $gridTemplate = $this->tcaRegistry->getGridTemplate($record['CType']); + $partialRootPaths = $this->tcaRegistry->getGridPartialPaths($record['CType']); + $layoutRootPaths = $this->tcaRegistry->getGridLayoutPaths($record['CType']); + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setPartialRootPaths($partialRootPaths); + $view->setLayoutRootPaths($layoutRootPaths); + $view->setTemplatePathAndFilename($gridTemplate); + + $view->assign('hideRestrictedColumns', (bool)(BackendUtility::getPagesTSconfig($context->getPageId())['mod.']['web_layout.']['hideRestrictedCols'] ?? false)); + $view->assign('newContentTitle', $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:newContentElement')); + $view->assign('newContentTitleShort', $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:content')); + $view->assign('allowEditContent', $this->getBackendUser()->check('tables_modify', 'tt_content')); + // keep compatibility + $view->assign('containerGrid', $grid); + $view->assign('grid', $grid); + $view->assign('containerRecord', $record); + $view->assign('context', $context); + $parentGridColumnItem = $this->runtimeCache->get('tx_container_current_gridColumItem'); + $beforeContainerPreviewIsRendered = new BeforeContainerPreviewIsRenderedEvent($container, $view, $grid, $parentGridColumnItem); + $this->eventDispatcher->dispatch($beforeContainerPreviewIsRendered); + $rendered = $view->render(); + return $rendered; + } + + protected function getDefValsForContentDefenderAllowsOnlyOneSpecificContentType(string $cType, int $colPos): ?array + { + $contentDefefenderConfiguration = $this->tcaRegistry->getContentDefenderConfiguration($cType, $colPos); + $allowedCTypes = GeneralUtility::trimExplode(',', $contentDefefenderConfiguration['allowed.']['CType'] ?? '', true); + $allowedListTypes = GeneralUtility::trimExplode(',', $contentDefefenderConfiguration['allowed.']['list_type'] ?? '', true); + if (count($allowedCTypes) === 1) { + if ($allowedCTypes[0] !== 'list') { + return ['CType' => $allowedCTypes[0]]; + } + if (count($allowedListTypes) === 1) { + return ['CType' => 'list', 'list_type' => $allowedListTypes[0]]; + } + } + return null; + } + + protected function getBackendUser(): BackendUserAuthentication + { + return $GLOBALS['BE_USER']; + } + + protected function getLanguageService(): LanguageService + { + return $GLOBALS['LANG']; + } +} diff --git a/Classes/Events/BeforeContainerPreviewIsRenderedEvent.php b/Classes/Events/BeforeContainerPreviewIsRenderedEvent.php index 15e4fb01..665827d6 100644 --- a/Classes/Events/BeforeContainerPreviewIsRenderedEvent.php +++ b/Classes/Events/BeforeContainerPreviewIsRenderedEvent.php @@ -52,6 +52,7 @@ public function getGrid(): Grid public function getItem(): GridColumnItem { + trigger_error('gridColumItem property will be removed on next major release', E_USER_DEPRECATED); return $this->item; } } diff --git a/Classes/Listener/PageContentPreviewRendering.php b/Classes/Listener/PageContentPreviewRendering.php new file mode 100644 index 00000000..e986e24a --- /dev/null +++ b/Classes/Listener/PageContentPreviewRendering.php @@ -0,0 +1,39 @@ +gridRenderer = $gridRenderer; + $this->tcaRegistry = $tcaRegistry; + } + + public function __invoke(PageContentPreviewRenderingEvent $event): void + { + $record = $event->getRecord(); + if (!$this->tcaRegistry->isContainerElement($record['CType'])) { + return; + } + $record['tx_container_grid'] = $this->gridRenderer->renderGrid($record, $event->getPageLayoutContext()); + $event->setRecord($record); + } +} diff --git a/Classes/Tca/ContainerConfiguration.php b/Classes/Tca/ContainerConfiguration.php index a0a52709..0c9a1c42 100644 --- a/Classes/Tca/ContainerConfiguration.php +++ b/Classes/Tca/ContainerConfiguration.php @@ -42,7 +42,7 @@ class ContainerConfiguration */ protected $icon = 'EXT:container/Resources/Public/Icons/Extension.svg'; - protected ?string $backendTemplate = null; + protected string $backendTemplate = 'EXT:container/Resources/Private/Templates/Container.html'; /** * @var string @@ -228,7 +228,7 @@ public function getIcon(): string return $this->icon; } - public function getBackendTemplate(): ?string + public function getBackendTemplate(): string { return $this->backendTemplate; } diff --git a/Classes/Tca/Registry.php b/Classes/Tca/Registry.php index 1f41df6e..8e22be93 100644 --- a/Classes/Tca/Registry.php +++ b/Classes/Tca/Registry.php @@ -65,9 +65,9 @@ public function configureContainer(ContainerConfiguration $containerConfiguratio ] ); } - $GLOBALS['TCA']['tt_content']['types'][$containerConfiguration->getCType()]['previewRenderer'] = \B13\Container\Backend\Preview\ContainerPreviewRenderer::class; + if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() >= 13) { if (!isset($GLOBALS['TCA']['tt_content']['types'][$containerConfiguration->getCType()]['creationOptions'])) { $GLOBALS['TCA']['tt_content']['types'][$containerConfiguration->getCType()]['creationOptions'] = []; @@ -266,12 +266,10 @@ public function getPageTsString(): string } $groupedByGroup[$group][$cType] = $containerConfiguration; } - if ($containerConfiguration['backendTemplate'] !== null) { - $pageTs .= LF . 'mod.web_layout.tt_content.preview { + $pageTs .= LF . 'mod.web_layout.tt_content.preview { ' . $cType . ' = ' . $containerConfiguration['backendTemplate'] . ' } '; - } } $typo3Version = GeneralUtility::makeInstance(Typo3Version::class); if ($typo3Version->getMajorVersion() > 12) { diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index ee4ba369..d3324614 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -15,8 +15,13 @@ services: B13\Container\Tca\Registry: public: true + B13\Container\Backend\Preview\GridRenderer: + arguments: + $runtimeCache: '@cache.runtime' B13\Container\Backend\Preview\ContainerPreviewRenderer: public: true + arguments: + $runtimeCache: '@cache.runtime' B13\Container\Hooks\UsedRecords: public: true B13\Container\Hooks\Datahandler\CommandMapAfterFinishHook: @@ -68,6 +73,11 @@ services: tags: - name: event.listener identifier: 'tx-container-boot-completed' + B13\Container\Listener\PageContentPreviewRendering: + tags: + - name: event.listener + identifier: 'tx-container-page-content-preview-rendering' + before: 'typo3-backend/fluid-preview/content' B13\Container\Command\FixLanguageModeCommand: tags: - name: 'console.command' diff --git a/README.md b/README.md index 59bc09c8..8b298e7e 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ This is an example to create a 2 column container. The code snippet goes into a | Method name | Description | Parameters | Default | | ----------- | ----------- | ---------- | ---------- | | `setIcon` | icon file, or existing icon identifier | `string $icon` | `'EXT:container/Resources/Public/Icons/Extension.svg'` | -| `setBackendTemplate` | Template for backend view| `string $backendTemplate` | `null'` | +| `setBackendTemplate` | Template for backend view| `string $backendTemplate` | `EXT:container/Resources/Private/Templates/Container.html'` | | `setGridTemplate` | Template for grid | `string $gridTemplate` | `'EXT:container/Resources/Private/Templates/Container.html'` | | `setGridPartialPaths` / `addGridPartialPath` | Partial root paths for grid | `array $gridPartialPaths` / `string $gridPartialPath` | `['EXT:backend/Resources/Private/Partials/', 'EXT:container/Resources/Private/Partials/']` | | `setGridLayoutPaths` | Layout root paths for grid | `array $gridLayoutPaths` | `[]` | diff --git a/Resources/Private/Templates/Container.html b/Resources/Private/Templates/Container.html new file mode 100644 index 00000000..a91e8da2 --- /dev/null +++ b/Resources/Private/Templates/Container.html @@ -0,0 +1,6 @@ + +{tx_container_grid} + \ No newline at end of file diff --git a/Tests/Acceptance/Backend/LayoutCest.php b/Tests/Acceptance/Backend/LayoutCest.php index 85e53fd5..e34de03b 100644 --- a/Tests/Acceptance/Backend/LayoutCest.php +++ b/Tests/Acceptance/Backend/LayoutCest.php @@ -198,10 +198,14 @@ public function canCreateContainerContentElementSaveAndClose(BackendTester $I, P if ($typo3Version->getMajorVersion() < 12) { $I->click('Container'); // b13-2cols - $I->click('2 Column Some Description of the Container'); + // this also tests container-example eventListener + // https://github.com/b13/container-example/commit/df2560e75966a73754b5d4ea091d14727c16f024 + $I->click('2 Column mod -- Some Description of the Container'); } else { $I->executeJS("document.querySelector('" . $I->getNewRecordWizardSelector() . "').filter('container')"); $I->wait(0.5); + // test event listener + $I->see('mod -- Some Description of the Container'); $I->executeJS("document.querySelector('" . $I->getNewRecordWizardSelector() . "').shadowRoot.querySelector('button[data-identifier=\"container_b13-2cols\"]').click()"); } $I->switchToContentFrame(); @@ -484,6 +488,21 @@ public function canSeeContainerColumnTitleForDifferentContainers(BackendTester $ $I->see('2-cols-right'); } + public function canSeeCustomBackendTemplate(BackendTester $I, PageTree $pageTree, PageTreeV13 $pageTreeV13): void + { + $I->click('Page'); + if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + $I->waitForElement('#typo3-pagetree-tree .nodes .node'); + $pageTree->openPath(['home', 'pageWithDifferentContainers']); + } else { + $pageTreeV13->openPath(['home', 'pageWithDifferentContainers']); + } + $I->wait(0.2); + $I->switchToContentFrame(); + $I->waitForElement('#tx-container-example-custom-backend-template'); + $I->see('custom backend template'); + } + /** * @param BackendTester $I * @param PageTree $pageTree