Skip to content

Commit

Permalink
[BUGFIX] custom backend templates
Browse files Browse the repository at this point in the history
the container grid is not longer rendered after the
content of the StandardContentRenderer but is assign
as a variable "tx_container_grid" to the record.

this variable is used in a backend template:
<f:format.raw>{tx_container_grid}</f:format.raw>

container shipped the default backend template (again, we has
dropped this in EXT:container 3.0)

in v12 and v13 the ContainerPreviewRenderer is no longer used
we use PageContentPreviewRendering Event Listener instead of
the ContainerPreviewRenderer
the Listener assigns the container grid to tx_container_grid

in v11 the PreviewRenderer is still used but assigns the
grid as a variable instead of rendering the grid after the content

background: in v13 the Event Listener "FluidBasedContentPreviewRenderer"
was introduced for rendering backend templates and "disables" StandardContentRenderer
if a backend template is defined.

in addition with this change you can also render custom stuff
in you backend template after the grid, which was not possible before

Fixes: #563
  • Loading branch information
achimfritz committed Jan 30, 2025
1 parent 9d29e35 commit dfc61c4
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 117 deletions.
1 change: 1 addition & 0 deletions Build/phpstan11-7.4.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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

1 change: 1 addition & 0 deletions Build/phpstan11.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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

113 changes: 7 additions & 106 deletions Classes/Backend/Preview/ContainerPreviewRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,122 +12,23 @@
* 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\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;

class ContainerPreviewRenderer extends StandardContentPreviewRenderer
{
/**
* @var Registry
*/
protected $tcaRegistry;

/**
* @var ContainerFactory
*/
protected $containerFactory;

protected NewContentUrlBuilder $newContentUrlBuilder;
protected GridRenderer $gridRenderer;

/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;

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) {
$this->gridRenderer = $gridRenderer;
}

public function renderPageModulePreviewContent(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;
}

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;
$record['tx_container_grid'] = $this->gridRenderer->renderGrid($record, $item->getContext(), $item);
$item->setRecord($record);
return parent::renderPageModulePreviewContent($item);
}
}
130 changes: 130 additions & 0 deletions Classes/Backend/Preview/GridRenderer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

declare(strict_types=1);

namespace B13\Container\Backend\Preview;

/*
* This file is part of TYPO3 CMS-based extension "container" by b13.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* 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\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\Backend\View\PageLayoutContext;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;

class GridRenderer
{

protected Registry $tcaRegistry;
protected ContainerFactory $containerFactory;
protected NewContentUrlBuilder $newContentUrlBuilder;
protected EventDispatcherInterface $eventDispatcher;

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 renderGrid(array $record, PageLayoutContext $context, ?GridColumnItem $parentGridColumnItem = null): 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);
$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'];
}
}
6 changes: 3 additions & 3 deletions Classes/Events/BeforeContainerPreviewIsRenderedEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ final class BeforeContainerPreviewIsRenderedEvent

protected Grid $grid;

protected GridColumnItem $item;
protected ?GridColumnItem $item;

public function __construct(Container $container, StandaloneView $view, Grid $grid, GridColumnItem $item)
public function __construct(Container $container, StandaloneView $view, Grid $grid, ?GridColumnItem $item)
{
$this->container = $container;
$this->view = $view;
Expand All @@ -50,7 +50,7 @@ public function getGrid(): Grid
return $this->grid;
}

public function getItem(): GridColumnItem
public function getItem(): ?GridColumnItem
{
return $this->item;
}
Expand Down
39 changes: 39 additions & 0 deletions Classes/Listener/PageContentPreviewRendering.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace B13\Container\Listener;

/*
* This file is part of TYPO3 CMS-based extension "container" by b13.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/

use B13\Container\Backend\Preview\GridRenderer;
use B13\Container\Tca\Registry;
use TYPO3\CMS\Backend\View\Event\PageContentPreviewRenderingEvent;

class PageContentPreviewRendering
{
protected GridRenderer $gridRenderer;
protected Registry $tcaRegistry;

public function __construct(GridRenderer $gridRenderer, Registry $tcaRegistry)
{
$this->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);
}
}
4 changes: 2 additions & 2 deletions Classes/Tca/ContainerConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -228,7 +228,7 @@ public function getIcon(): string
return $this->icon;
}

public function getBackendTemplate(): ?string
public function getBackendTemplate(): string
{
return $this->backendTemplate;
}
Expand Down
7 changes: 2 additions & 5 deletions Classes/Tca/Registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,9 @@ public function configureContainer(ContainerConfiguration $containerConfiguratio
$containerConfiguration->getGroup(),
]
);
$GLOBALS['TCA']['tt_content']['types'][$containerConfiguration->getCType()]['previewRenderer'] = \B13\Container\Backend\Preview\ContainerPreviewRenderer::class;
}

$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'] = [];
Expand Down Expand Up @@ -266,12 +265,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) {
Expand Down
5 changes: 5 additions & 0 deletions Configuration/Services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,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'
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` | `[]` |
Expand Down
Loading

0 comments on commit dfc61c4

Please sign in to comment.