From 4b7b0e38497003ad9fb74b500bb86297618229fe Mon Sep 17 00:00:00 2001 From: David Steeb Date: Thu, 11 Jun 2026 16:46:58 +0200 Subject: [PATCH 1/8] [!!!][TASK] Raise minimum to TYPO3 v13.4/v14, drop legacy layer BREAKING: drops support for TYPO3 v10, v11 and v12 (and PHP < 8.2). - Remove legacy PageLayoutView hook (BackendPreviewRenderer) and its registration; v12+ always uses the previewRenderer. - Remove ext_tables.php (only registered the < v12 backend skin). - Reduce ext_localconf.php to the (now unconditional) stylesheet entry. - Remove BootCompleted listener (registered unused Fontawesome icons). - ContentPreview: render previews via ViewFactoryInterface on both the RecordInterface (v14) and the array (v13) path, dropping StandaloneView. - DatabaseRowService: drop the pre-v12 manual file-field fallback. - GetDatabaseRecordViewHelper: drop the v12 QueryBuilder::add() branch. - CI/PHPStan: drop the v12 matrix entry, config and baseline; regenerate the v14 baseline (12 -> 3 entries). - Bump constraints: PHP ^8.2, TYPO3 ^13.4 || ^14.3, version 2.0.0. Relates: BEXT-596 --- .github/workflows/ci.yml | 11 +-- Build/phpstan-baseline.neon | 49 ---------- Build/phpstan-baseline12.neon | 87 ----------------- Build/phpstan.neon | 4 +- Build/phpstan12.neon | 8 -- Build/phpstan13.neon | 4 +- Classes/Backend/Preview/ContentPreview.php | 93 ++++++++++--------- .../StandardContentPreviewRenderer.php | 2 +- Classes/Hooks/BackendPreviewRenderer.php | 62 ------------- Classes/Listener/BootCompleted.php | 45 --------- Classes/Service/DatabaseRowService.php | 38 ++------ .../GetDatabaseRecordViewHelper.php | 7 +- Configuration/Services.yaml | 6 -- composer.json | 6 +- ext_emconf.php | 4 +- ext_localconf.php | 30 +----- ext_tables.php | 12 --- 17 files changed, 70 insertions(+), 398 deletions(-) delete mode 100644 Build/phpstan-baseline12.neon delete mode 100644 Build/phpstan12.neon delete mode 100644 Classes/Hooks/BackendPreviewRenderer.php delete mode 100644 Classes/Listener/BootCompleted.php delete mode 100644 ext_tables.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e0c807..8c51e40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - TYPO3: [ '12', '13', '14' ] + TYPO3: [ '13', '14' ] steps: - name: Checkout @@ -37,10 +37,6 @@ jobs: if: matrix.TYPO3 == '13' run: | composer require typo3/cms-core:^13.4 typo3/cms-fluid:^13.4 --no-progress --no-interaction --dev -W - - name: Install composer dependencies TYPO3 12 - if: matrix.TYPO3 == '12' - run: | - composer require typo3/cms-core:^12.4 typo3/cms-extbase:^12.4 typo3/cms-fluid:^12.4 --no-progress --no-interaction --dev -W - name: Phpcsfix run: .Build/bin/php-cs-fixer fix --config=Build/php-cs-fixer.php --dry-run --stop-on-violation --using-cache=no - name: Phpstan 13 @@ -48,7 +44,4 @@ jobs: run: .Build/bin/phpstan analyze -c Build/phpstan13.neon - name: Phpstan 14 if: matrix.TYPO3 == '14' - run: .Build/bin/phpstan analyze -c Build/phpstan.neon - - name: Phpstan 12 - if: matrix.TYPO3 == '12' - run: .Build/bin/phpstan analyze -c Build/phpstan12.neon \ No newline at end of file + run: .Build/bin/phpstan analyze -c Build/phpstan.neon \ No newline at end of file diff --git a/Build/phpstan-baseline.neon b/Build/phpstan-baseline.neon index b4fa326..967d5ac 100644 --- a/Build/phpstan-baseline.neon +++ b/Build/phpstan-baseline.neon @@ -1,45 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Call to method assignMultiple\\(\\) on an unknown class TYPO3\\\\CMS\\\\Fluid\\\\View\\\\StandaloneView\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Call to method hasTemplate\\(\\) on an unknown class TYPO3\\\\CMS\\\\Fluid\\\\View\\\\StandaloneView\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Call to method render\\(\\) on an unknown class TYPO3\\\\CMS\\\\Fluid\\\\View\\\\StandaloneView\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Call to method setLayoutRootPaths\\(\\) on an unknown class TYPO3\\\\CMS\\\\Fluid\\\\View\\\\StandaloneView\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Call to method setPartialRootPaths\\(\\) on an unknown class TYPO3\\\\CMS\\\\Fluid\\\\View\\\\StandaloneView\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Call to method setTemplate\\(\\) on an unknown class TYPO3\\\\CMS\\\\Fluid\\\\View\\\\StandaloneView\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Call to method setTemplateRootPaths\\(\\) on an unknown class TYPO3\\\\CMS\\\\Fluid\\\\View\\\\StandaloneView\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Class TYPO3\\\\CMS\\\\Fluid\\\\View\\\\StandaloneView not found\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - message: "#^Else branch is unreachable because previous condition is always true\\.$#" count: 1 @@ -54,12 +14,3 @@ parameters: message: "#^Parameter \\#1 \\$row of method B13\\\\Backendpreviews\\\\Service\\\\DatabaseRowService\\:\\:extendRow\\(\\) expects array, TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface given\\.$#" count: 1 path: ../Classes/Listener/PageContentPreviewRendering.php - - - - message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Core\\\\Database\\\\Query\\\\QueryBuilder\\:\\:add\\(\\)\\.$#" - count: 1 - path: ../Classes/ViewHelpers/GetDatabaseRecordViewHelper.php - - - message: "#^Class TYPO3\\\\CMS\\\\Core\\\\Imaging\\\\IconProvider\\\\FontawesomeIconProvider not found\\.$#" - count: 2 - path: ../Classes/Listener/BootCompleted.php diff --git a/Build/phpstan-baseline12.neon b/Build/phpstan-baseline12.neon deleted file mode 100644 index c3a7f0a..0000000 --- a/Build/phpstan-baseline12.neon +++ /dev/null @@ -1,87 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Backend\\\\View\\\\PageLayoutContext\\:\\:getCurrentRequest\\(\\)\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Call to method create\\(\\) on an unknown class TYPO3\\\\CMS\\\\Core\\\\View\\\\ViewFactoryInterface\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Call to method getPid\\(\\) on an unknown class TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Call to method getRecordType\\(\\) on an unknown class TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Class TYPO3\\\\CMS\\\\Core\\\\View\\\\ViewFactoryInterface not found\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Instantiated class TYPO3\\\\CMS\\\\Core\\\\View\\\\ViewFactoryData not found\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Parameter \\$record of method B13\\\\Backendpreviews\\\\Backend\\\\Preview\\\\ContentPreview\\:\\:render\\(\\) has invalid type TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface\\.$#" - count: 1 - path: ../Classes/Backend/Preview/ContentPreview.php - - - - message: "#^Class TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface not found\\.$#" - count: 1 - path: ../Classes/Backend/Preview/StandardContentPreviewRenderer.php - - - - message: "#^Instanceof between array and TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface will always evaluate to false\\.$#" - count: 1 - path: ../Classes/Backend/Preview/StandardContentPreviewRenderer.php - - - message: "#^Call to method get\\(\\) on an unknown class TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface\\.$#" - count: 1 - path: ../Classes/Service/DatabaseRowService.php - - - - message: "#^Call to method getMainType\\(\\) on an unknown class TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface\\.$#" - count: 2 - path: ../Classes/Service/DatabaseRowService.php - - - - message: "#^Call to method getPid\\(\\) on an unknown class TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface\\.$#" - count: 1 - path: ../Classes/Service/DatabaseRowService.php - - - - message: "#^Call to method getRawRecord\\(\\) on an unknown class TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface\\.$#" - count: 1 - path: ../Classes/Service/DatabaseRowService.php - - - - message: "#^Call to method getUid\\(\\) on an unknown class TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface\\.$#" - count: 4 - path: ../Classes/Service/DatabaseRowService.php - - - message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Core\\\\Authentication\\\\BackendUserAuthentication\\:\\:checkRecordEditAccess\\(\\)\\.$#" - count: 1 - path: ../Classes/Service/DatabaseRowService.php - - - - message: "#^Parameter \\$record of method B13\\\\Backendpreviews\\\\Service\\\\DatabaseRowService\\:\\:getAdditionalDataForView\\(\\) has invalid type TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface\\.$#" - count: 1 - path: ../Classes/Service/DatabaseRowService.php - - - message: "#^Class TYPO3\\\\CMS\\\\Core\\\\Imaging\\\\IconProvider\\\\FontawesomeIconProvider not found\\.$#" - count: 2 - path: ../Classes/Listener/BootCompleted.php - - - message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Backend\\\\View\\\\PageLayoutContext\\:\\:getReturnUrl\\(\\)\\.$#" - count: 1 - path: ../Classes/Service/DatabaseRowService.php \ No newline at end of file diff --git a/Build/phpstan.neon b/Build/phpstan.neon index d27b0d2..34cb13a 100644 --- a/Build/phpstan.neon +++ b/Build/phpstan.neon @@ -3,6 +3,4 @@ includes: parameters: level: 5 paths: - - %currentWorkingDirectory%/Classes - excludePaths: - - %currentWorkingDirectory%/Classes/Hooks/BackendPreviewRenderer.php \ No newline at end of file + - %currentWorkingDirectory%/Classes \ No newline at end of file diff --git a/Build/phpstan12.neon b/Build/phpstan12.neon deleted file mode 100644 index 2ec6711..0000000 --- a/Build/phpstan12.neon +++ /dev/null @@ -1,8 +0,0 @@ -includes: - - phpstan-baseline12.neon -parameters: - level: 5 - paths: - - %currentWorkingDirectory%/Classes - excludePaths: - - %currentWorkingDirectory%/Classes/Hooks/BackendPreviewRenderer.php \ No newline at end of file diff --git a/Build/phpstan13.neon b/Build/phpstan13.neon index 0ebc213..27c0da6 100644 --- a/Build/phpstan13.neon +++ b/Build/phpstan13.neon @@ -3,6 +3,4 @@ includes: parameters: level: 5 paths: - - %currentWorkingDirectory%/Classes - excludePaths: - - %currentWorkingDirectory%/Classes/Hooks/BackendPreviewRenderer.php \ No newline at end of file + - %currentWorkingDirectory%/Classes \ No newline at end of file diff --git a/Classes/Backend/Preview/ContentPreview.php b/Classes/Backend/Preview/ContentPreview.php index 2468ab1..7723444 100644 --- a/Classes/Backend/Preview/ContentPreview.php +++ b/Classes/Backend/Preview/ContentPreview.php @@ -19,43 +19,23 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\View\ViewFactoryData; use TYPO3\CMS\Core\View\ViewFactoryInterface; -use TYPO3\CMS\Fluid\View\StandaloneView; +use TYPO3\CMS\Core\View\ViewInterface; use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException; class ContentPreview { public function render(RecordInterface $record, PageLayoutContext $context): ?string { - $previewConfiguration = BackendUtility::getPagesTSconfig($record->getPid())['mod.']['web_layout.']['tt_content.']['preview.'] ?? []; - if (!$previewConfiguration) { - // Early return in case no preview configuration can be found + $previewConfiguration = $this->getPreviewConfiguration((int)$record->getPid()); + if ($previewConfiguration === null) { return null; } - $fluidConfiguration = $previewConfiguration['view.'] ?? []; - if (!$fluidConfiguration) { - // Early return in case no fluid template configuration can be found - return null; - } - - $templateConfiguration = $previewConfiguration['template.'] ?? []; $cType = $record->getRecordType(); - if (!empty($templateConfiguration[$cType])) { - $fluidTemplateName = $templateConfiguration[$cType]; - } else { - $fluidTemplateName = $cType; - } - $viewFactory = GeneralUtility::makeInstance(ViewFactoryInterface::class); - $view = $viewFactory->create( - new ViewFactoryData( - $fluidConfiguration['templateRootPaths.'] ?? null, - $fluidConfiguration['partialRootPaths.'] ?? null, - $fluidConfiguration['layoutRootPaths.'] ?? null, - null, - $context->getCurrentRequest() - ) - ); + $templateConfiguration = $previewConfiguration['template.'] ?? []; + $fluidTemplateName = !empty($templateConfiguration[$cType]) ? $templateConfiguration[$cType] : $cType; + $view = $this->createView($previewConfiguration['view.'], $context); $data = GeneralUtility::makeInstance(DatabaseRowService::class)->getAdditionalDataForView($record, $context); $view->assignMultiple($data); $view->assign('record', $record); @@ -66,17 +46,16 @@ public function render(RecordInterface $record, PageLayoutContext $context): ?st return null; } - public function renderLegacy(array $row): ?string + /** + * Render path for TYPO3 v13, where the page module still passes the content element as an array + * instead of a RecordInterface (see Breaking-92434). + * + * @param array $row + */ + public function renderLegacy(array $row, PageLayoutContext $context): ?string { - $previewConfiguration = BackendUtility::getPagesTSconfig($row['pid'])['mod.']['web_layout.']['tt_content.']['preview.'] ?? []; - if (!$previewConfiguration) { - // Early return in case no preview configuration can be found - return null; - } - - $fluidConfiguration = $previewConfiguration['view.'] ?? []; - if (!$fluidConfiguration) { - // Early return in case no fluid template configuration can be found + $previewConfiguration = $this->getPreviewConfiguration((int)$row['pid']); + if ($previewConfiguration === null) { return null; } @@ -89,16 +68,42 @@ public function renderLegacy(array $row): ?string $fluidTemplateName = $row['CType']; } - $view = GeneralUtility::makeInstance(StandaloneView::class); - $view->setLayoutRootPaths($fluidConfiguration['layoutRootPaths.'] ?? []); - $view->setPartialRootPaths($fluidConfiguration['partialRootPaths.'] ?? []); - $view->setTemplateRootPaths($fluidConfiguration['templateRootPaths.'] ?? []); - $view->setTemplate($fluidTemplateName); + $view = $this->createView($previewConfiguration['view.'], $context); $view->assignMultiple($row); - - if ($view->hasTemplate()) { - return $view->render(); + try { + return $view->render($fluidTemplateName); + } catch (InvalidTemplateResourceException) { } return null; } + + /** + * @return array|null + */ + protected function getPreviewConfiguration(int $pid): ?array + { + $previewConfiguration = BackendUtility::getPagesTSconfig($pid)['mod.']['web_layout.']['tt_content.']['preview.'] ?? []; + // Early return in case no preview (or fluid template) configuration can be found + if (!$previewConfiguration || empty($previewConfiguration['view.'])) { + return null; + } + return $previewConfiguration; + } + + /** + * @param array $fluidConfiguration + */ + protected function createView(array $fluidConfiguration, PageLayoutContext $context): ViewInterface + { + $viewFactory = GeneralUtility::makeInstance(ViewFactoryInterface::class); + return $viewFactory->create( + new ViewFactoryData( + $fluidConfiguration['templateRootPaths.'] ?? null, + $fluidConfiguration['partialRootPaths.'] ?? null, + $fluidConfiguration['layoutRootPaths.'] ?? null, + null, + $context->getCurrentRequest() + ) + ); + } } diff --git a/Classes/Backend/Preview/StandardContentPreviewRenderer.php b/Classes/Backend/Preview/StandardContentPreviewRenderer.php index 0d37168..09ec9d2 100644 --- a/Classes/Backend/Preview/StandardContentPreviewRenderer.php +++ b/Classes/Backend/Preview/StandardContentPreviewRenderer.php @@ -36,7 +36,7 @@ public function renderPageModulePreviewContent(GridColumnItem $item): string if ($record instanceof RecordInterface) { $content = $contentPreview->render($record, $context); } else { - $content = $contentPreview->renderLegacy($record); + $content = $contentPreview->renderLegacy($record, $context); } if ($content !== null) { return $content; diff --git a/Classes/Hooks/BackendPreviewRenderer.php b/Classes/Hooks/BackendPreviewRenderer.php deleted file mode 100644 index c13e9e2..0000000 --- a/Classes/Hooks/BackendPreviewRenderer.php +++ /dev/null @@ -1,62 +0,0 @@ -databaseRowService = $databaseRowService; - } - - /** - * Preprocesses the preview rendering of a content element of any type - * - * @param \TYPO3\CMS\Backend\View\PageLayoutView $parentObject Calling parent object - * @param bool $drawItem Whether to draw the item using the default functionality - * @param string $headerContent Header content - * @param string $itemContent Item content - * @param array $row Record row of tt_content - */ - public function preProcess( - PageLayoutView &$parentObject, - &$drawItem, - &$headerContent, - &$itemContent, - array &$row - ) { - $row = $this->databaseRowService->extendRow($row); - if ((GeneralUtility::makeInstance(Features::class))->isFeatureEnabled('fluidBasedPageModule') === false) { - $contentPreview = GeneralUtility::makeInstance(ContentPreview::class); - $content = $contentPreview->renderLegacy($row); - if ($content !== null) { - $itemContent .= $content; - $drawItem = false; - } - } - } -} diff --git a/Classes/Listener/BootCompleted.php b/Classes/Listener/BootCompleted.php deleted file mode 100644 index 4d797c9..0000000 --- a/Classes/Listener/BootCompleted.php +++ /dev/null @@ -1,45 +0,0 @@ -packageManager->isPackageActive('fontawesome_provider')) { - return; - } - $this->iconRegistry->registerIcon( - 'exclamation-triangle', - \TYPO3\CMS\Core\Imaging\IconProvider\FontawesomeIconProvider::class, - [ - 'name' => 'exclamation-triangle', - ] - ); - $this->iconRegistry->registerIcon( - 'exclamation-circle', - \TYPO3\CMS\Core\Imaging\IconProvider\FontawesomeIconProvider::class, - [ - 'name' => 'exclamation-circle', - ] - ); - } -} diff --git a/Classes/Service/DatabaseRowService.php b/Classes/Service/DatabaseRowService.php index 1e5c0bf..046a3de 100644 --- a/Classes/Service/DatabaseRowService.php +++ b/Classes/Service/DatabaseRowService.php @@ -120,38 +120,18 @@ public function extendRow(array $row): array $row['pi_flexform_transformed'] = $flexFormService->convertFlexFormContentToArray($row['pi_flexform']); } - // return all sys_file_reference rows - // automatic resolving of all type=file, only works with TYPO3 v12+ - $foundOne = false; + // return all sys_file_reference rows by automatically resolving all type=file fields foreach ($GLOBALS['TCA']['tt_content']['columns'] as $fieldName => $fieldConfig) { - if ($fieldConfig['config']['type'] === 'file') { - $variableName = GeneralUtility::underscoredToUpperCamelCase($fieldName); - if ($fieldName === 'image') { - $variableName = 'Images'; - } - if ($row[$fieldName] ?? false) { - $row['all' . $variableName] = $this->fileRepository->findByRelation('tt_content', $fieldName, $row['uid']); - $row['all' . $variableName . '-visible'] = $this->countVisibleFileReferences($row['all' . $variableName]); - } - $foundOne = true; + if (($fieldConfig['config']['type'] ?? '') !== 'file') { + continue; } - } - if (!$foundOne) { - if ($row['assets'] ?? false) { - $row['allAssets'] = $this->fileRepository->findByRelation('tt_content', 'assets', $row['uid']); - $row['allAssets-visible'] = $this->countVisibleFileReferences($row['allAssets']); - } - if ($row['assets2'] ?? false) { - $row['allAssets2'] = $this->fileRepository->findByRelation('tt_content', 'assets2', $row['uid']); - $row['allAssets2-visible'] = $this->countVisibleFileReferences($row['allAssets2']); - } - if ($row['media'] ?? false) { - $row['allMedia'] = $this->fileRepository->findByRelation('tt_content', 'media', $row['uid']); - $row['allMedia-visible'] = $this->countVisibleFileReferences($row['allMedia']); + $variableName = GeneralUtility::underscoredToUpperCamelCase($fieldName); + if ($fieldName === 'image') { + $variableName = 'Images'; } - if ($row['image'] ?? false) { - $row['allImages'] = $this->fileRepository->findByRelation('tt_content', 'image', $row['uid']); - $row['allImages-visible'] = $this->countVisibleFileReferences($row['allImages']); + if ($row[$fieldName] ?? false) { + $row['all' . $variableName] = $this->fileRepository->findByRelation('tt_content', $fieldName, $row['uid']); + $row['all' . $variableName . '-visible'] = $this->countVisibleFileReferences($row['all' . $variableName]); } } return $row; diff --git a/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php b/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php index ad80011..01f7d1e 100644 --- a/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php +++ b/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php @@ -17,7 +17,6 @@ use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction; -use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; @@ -73,11 +72,7 @@ public function render(): array ->where( $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY)) ); - if ((new Typo3Version())->getMajorVersion() > 12) { - $queryBuilder->getConcreteQueryBuilder()->addOrderBy('FIELD(uid,' . implode(',', $uids) . ')'); - } else { - $queryBuilder->add('orderBy', 'FIELD(uid,' . implode(',', $uids) . ')'); - } + $queryBuilder->getConcreteQueryBuilder()->addOrderBy('FIELD(uid,' . implode(',', $uids) . ')'); return $queryBuilder ->executeQuery() ->fetchAllAssociative(); diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 6d456a7..2ed931b 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -6,8 +6,6 @@ services: B13\Backendpreviews\: resource: '../Classes/*' - B13\Backendpreviews\Hooks\BackendPreviewRenderer: - public: true B13\Backendpreviews\Service\DatabaseRowService: public: true B13\Backendpreviews\Listener\PageContentPreviewRendering: @@ -15,7 +13,3 @@ services: - name: event.listener event: TYPO3\CMS\Backend\View\Event\PageContentPreviewRenderingEvent identifier: 'b13-backendpreview-page-content-preview-rendering' - B13\Backendpreviews\Listener\BootCompleted: - tags: - - name: event.listener - identifier: 'b13-backendpreview-boot-completed' diff --git a/composer.json b/composer.json index f837402..64a10ed 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "typo3/cms": { "web-dir": ".Build/Web", "extension-key": "backendpreviews", - "version": "1.5.2", + "version": "2.0.0", "Package": { "providesPackages": {} } @@ -16,8 +16,8 @@ "GPL-2.0-or-later" ], "require": { - "php": "^7.4 || ~8.0", - "typo3/cms-backend": "^10.4 || ^11.5 || ^12.4 || ^13.4 || ^14.3" + "php": "^8.2", + "typo3/cms-backend": "^13.4 || ^14.3" }, "require-dev": { "phpstan/phpstan": "^1.10", diff --git a/ext_emconf.php b/ext_emconf.php index a7c34c1..15ac31a 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -9,10 +9,10 @@ 'state' => 'stable', 'clearCacheOnLoad' => true, 'author_company' => 'b13 GmbH, Stuttgart', - 'version' => '1.5.2', + 'version' => '2.0.0', 'constraints' => [ 'depends' => [ - 'typo3' => '10.4.0-14.99.99', + 'typo3' => '13.4.0-14.99.99', ], ], ]; diff --git a/ext_localconf.php b/ext_localconf.php index e25159a..8aa9adc 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -2,32 +2,4 @@ defined('TYPO3') or die(); -if ((\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Information\Typo3Version::class))->getMajorVersion() < 12) { - // extends database row and render Backend-Preview if fluidBasedPageModule is not used - // (if fluidBasedPageModule is used "$GLOBALS['TCA']['tt_content']['ctrl']['previewRenderer']" takes effect for Backend-Preview-Rendering) - $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem']['tx_backendpreviews'] = - \B13\Backendpreviews\Hooks\BackendPreviewRenderer::class; -} -// if TYPO3 > 11 "$GLOBALS['TCA']['tt_content']['ctrl']['previewRenderer']" is always used for Backend-Preview-Rendering -// extending the database row is handled by B13\Backendpreviews\Listener\PageContentPreviewRendering - -if ((new \TYPO3\CMS\Core\Information\Typo3Version())->getMajorVersion() < 12) { - $iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class); - $iconRegistry->registerIcon( - 'exclamation-triangle', - \TYPO3\CMS\Core\Imaging\IconProvider\FontawesomeIconProvider::class, - [ - 'name' => 'exclamation-triangle', - ] - ); - $iconRegistry->registerIcon( - 'exclamation-circle', - \TYPO3\CMS\Core\Imaging\IconProvider\FontawesomeIconProvider::class, - [ - 'name' => 'exclamation-circle', - ] - ); -} -if ((\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Information\Typo3Version::class))->getMajorVersion() >= 12) { - $GLOBALS['TYPO3_CONF_VARS']['BE']['stylesheets']['b13/backendpreviews'] = 'EXT:backendpreviews/Resources/Public/Backend/Css/Skin/'; -} +$GLOBALS['TYPO3_CONF_VARS']['BE']['stylesheets']['b13/backendpreviews'] = 'EXT:backendpreviews/Resources/Public/Backend/Css/Skin/'; diff --git a/ext_tables.php b/ext_tables.php deleted file mode 100644 index 0983654..0000000 --- a/ext_tables.php +++ /dev/null @@ -1,12 +0,0 @@ -getMajorVersion() < 12) { - $GLOBALS['TBE_STYLES']['skins']['backendpreviews']['name'] = 'backendpreviews'; - $GLOBALS['TBE_STYLES']['skins']['backendpreviews']['stylesheetDirectories'] = [ - 'EXT:backendpreviews/Resources/Public/Backend/Css/Skin/', - ]; - } -})(); From 4b152f38cbadecff67da921c7f804c90c46141f8 Mon Sep 17 00:00:00 2001 From: David Steeb Date: Thu, 11 Jun 2026 16:48:58 +0200 Subject: [PATCH 2/8] [TASK] Regenerate v13 PHPStan baseline for 2.0 cleanup Relates: BEXT-596 --- Build/phpstan-baseline13.neon | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Build/phpstan-baseline13.neon b/Build/phpstan-baseline13.neon index cd5a79f..967d5ac 100644 --- a/Build/phpstan-baseline13.neon +++ b/Build/phpstan-baseline13.neon @@ -1,18 +1,16 @@ parameters: ignoreErrors: - - message: "#^Instanceof between array and TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface will always evaluate to false\\.$#" + message: "#^Else branch is unreachable because previous condition is always true\\.$#" count: 1 path: ../Classes/Backend/Preview/StandardContentPreviewRenderer.php + - - message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Core\\\\Authentication\\\\BackendUserAuthentication\\:\\:checkRecordEditAccess\\(\\)\\.$#" + message: "#^Parameter \\#1 \\$record of method TYPO3\\\\CMS\\\\Backend\\\\View\\\\Event\\\\PageContentPreviewRenderingEvent\\:\\:setRecord\\(\\) expects TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface, array given\\.$#" count: 1 - path: ../Classes/Service/DatabaseRowService.php + path: ../Classes/Listener/PageContentPreviewRendering.php + - - message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Core\\\\Database\\\\Query\\\\QueryBuilder\\:\\:add\\(\\)\\.$#" + message: "#^Parameter \\#1 \\$row of method B13\\\\Backendpreviews\\\\Service\\\\DatabaseRowService\\:\\:extendRow\\(\\) expects array, TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface given\\.$#" count: 1 - path: ../Classes/ViewHelpers/GetDatabaseRecordViewHelper.php - - - message: "#^Class TYPO3\\\\CMS\\\\Core\\\\Imaging\\\\IconProvider\\\\FontawesomeIconProvider not found\\.$#" - count: 2 - path: ../Classes/Listener/BootCompleted.php + path: ../Classes/Listener/PageContentPreviewRendering.php From 89ea23a36536e524c9b0d8a39b14c2e32160ba09 Mon Sep 17 00:00:00 2001 From: David Steeb Date: Thu, 11 Jun 2026 17:11:10 +0200 Subject: [PATCH 3/8] [TASK] Fix v13 PHPStan baseline to match CI The previous regeneration ran against a broken local v13 vendor state (mixed v13/v14), producing v14-style entries. Restore the actual v13 errors reported by CI (instanceof-always-false + checkRecordEditAccess), which differ from the v14 baseline. Relates: BEXT-596 --- Build/phpstan-baseline13.neon | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Build/phpstan-baseline13.neon b/Build/phpstan-baseline13.neon index 967d5ac..e4987ad 100644 --- a/Build/phpstan-baseline13.neon +++ b/Build/phpstan-baseline13.neon @@ -1,16 +1,10 @@ parameters: ignoreErrors: - - message: "#^Else branch is unreachable because previous condition is always true\\.$#" + message: "#^Instanceof between array and TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface will always evaluate to false\\.$#" count: 1 path: ../Classes/Backend/Preview/StandardContentPreviewRenderer.php - - - message: "#^Parameter \\#1 \\$record of method TYPO3\\\\CMS\\\\Backend\\\\View\\\\Event\\\\PageContentPreviewRenderingEvent\\:\\:setRecord\\(\\) expects TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface, array given\\.$#" + message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Core\\\\Authentication\\\\BackendUserAuthentication\\:\\:checkRecordEditAccess\\(\\)\\.$#" count: 1 - path: ../Classes/Listener/PageContentPreviewRendering.php - - - - message: "#^Parameter \\#1 \\$row of method B13\\\\Backendpreviews\\\\Service\\\\DatabaseRowService\\:\\:extendRow\\(\\) expects array, TYPO3\\\\CMS\\\\Core\\\\Domain\\\\RecordInterface given\\.$#" - count: 1 - path: ../Classes/Listener/PageContentPreviewRendering.php + path: ../Classes/Service/DatabaseRowService.php From 1a1ff007a77a2be3769ac20e2e773851316e016f Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Wed, 9 Sep 2026 15:26:13 +0200 Subject: [PATCH 4/8] [TASK] Address review of the 2.0 cleanup extendRow() runs on v13 only, so the "-visible" counts it added are dropped: v14 has no equivalent, and neither the extension's own partials nor a consumer template reads them. With the minimum at v13.4 the getMajorVersion() < 13 branch in getAdditionalDataForView() cannot be reached either, so the condition is just the missing context now. ORDER BY FIELD() is MySQL and MariaDB only. The record order is built as a CASE expression instead, which holds on PostgreSQL and SQLite as well. The uids come from intExplode() and are integers, so inlining them is safe. Pre-existing, this cleanup only moved the line. Relates: BEXT-596 --- Classes/Service/DatabaseRowService.php | 16 +--------------- .../ViewHelpers/GetDatabaseRecordViewHelper.php | 7 ++++++- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/Classes/Service/DatabaseRowService.php b/Classes/Service/DatabaseRowService.php index 046a3de..4da1f90 100644 --- a/Classes/Service/DatabaseRowService.php +++ b/Classes/Service/DatabaseRowService.php @@ -19,7 +19,6 @@ use TYPO3\CMS\Core\Domain\RecordInterface; use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Localization\LanguageService; -use TYPO3\CMS\Core\Resource\FileReference; use TYPO3\CMS\Core\Resource\FileRepository; use TYPO3\CMS\Core\Service\FlexFormService; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -45,7 +44,7 @@ public function getAdditionalDataForView(RecordInterface $record, ?PageLayoutCon if ($context === null) { trigger_error('not passing context is deprecated', E_USER_DEPRECATED); } - if ($context === null || (new Typo3Version())->getMajorVersion() < 13) { + if ($context === null) { $returnUrl = GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $record->getUid(); } else { $returnUrl = $context->getReturnUrl() . '#element-tt_content-' . $record->getUid(); @@ -131,7 +130,6 @@ public function extendRow(array $row): array } if ($row[$fieldName] ?? false) { $row['all' . $variableName] = $this->fileRepository->findByRelation('tt_content', $fieldName, $row['uid']); - $row['all' . $variableName . '-visible'] = $this->countVisibleFileReferences($row['all' . $variableName]); } } return $row; @@ -146,16 +144,4 @@ protected function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } - - protected function countVisibleFileReferences(array $references): int - { - $cnt = 0; - /** @var FileReference $reference */ - foreach ($references as $reference) { - if ((int)$reference->getProperty('hidden') === 0) { - $cnt++; - } - } - return $cnt; - } } diff --git a/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php b/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php index 01f7d1e..ef115e4 100644 --- a/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php +++ b/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php @@ -72,7 +72,12 @@ public function render(): array ->where( $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY)) ); - $queryBuilder->getConcreteQueryBuilder()->addOrderBy('FIELD(uid,' . implode(',', $uids) . ')'); + $orderByCase = 'CASE ' . $queryBuilder->quoteIdentifier('uid'); + foreach (array_values($uids) as $position => $uid) { + $orderByCase .= ' WHEN ' . $uid . ' THEN ' . $position; + } + $orderByCase .= ' END'; + $queryBuilder->getConcreteQueryBuilder()->addOrderBy($orderByCase); return $queryBuilder ->executeQuery() ->fetchAllAssociative(); From b31a771ea933a177f2829388fbde948ed0f3b293 Mon Sep 17 00:00:00 2001 From: David Steeb Date: Wed, 9 Sep 2026 16:16:52 +0200 Subject: [PATCH 5/8] [DOCS] Update README for 2.0 and add UTM link Fix the stale hook description, the requirements, the site set file name and the textpic previewRenderer example, and document which template variables v13 and v14 each provide. Credits block switched to the portfolio wording with utm_source/utm_medium on the b13.com link. Related: BEXT-596 --- README.md | 98 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index c70039b..f88246d 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,36 @@ # Nice Backend Previews for Content Elements in TYPO3 -## About this extension +## About This Extension -This extension adds a hook for rendering content element previews for TYPO3's backend view in the page module, -adding the ability to use Fluid Partials and Layouts to enable consistent preview markup. +This extension takes over the preview rendering for content elements in TYPO3's page module, +adding the ability to use Fluid templates, layouts, and partials to enable consistent preview markup. ## Requirements -* TYPO3 v10.4, v11.5, v12.4, v13.4 or v14 -* PHP 7.4 or higher +* TYPO3 v13.4 or v14 +* PHP 8.2 or higher + +Version 2.0 dropped support for TYPO3 v10.4, v11.5, and v12.4 together with the compatibility layer +for those versions. If you are still on one of them, stay on the 1.5 releases. ## Installation -Use composer to add this content element to your project +Use composer to add this extension to your project `composer require b13/backendpreviews` -and install the extension using the Extension Manager in your TYPO3 backend. +and set it up with `vendor/bin/typo3 extension:setup`. -## Add configuration +## Add Configuration -Add this to your PageTsConfig to include the default Fluid Templates provided with this extension: +Add this to your PageTsConfig to include the default Fluid templates provided with this extension: ``` @import 'EXT:backendpreviews/Configuration/PageTs/PageTs.tsconfig' ``` -On TYPO3 v13 and v14 you can alternatively include the shipped site set `b13/backendpreviews` -as a dependency of your own site set (`Configuration/Sets//settings.yaml`) instead of -importing the PageTsConfig manually: +Alternatively, include the shipped site set `b13/backendpreviews` as a dependency of your own site +set (`Configuration/Sets//config.yaml`) instead of importing the PageTsConfig manually: ```yaml dependencies: @@ -55,25 +57,57 @@ You can set a different templateName explicitly like this: mod.web_layout.tt_content.preview.template.mytype = Myowntemplate ``` -For plugins a template name for a specific plugin can be specified like this: +## What Your Template Gets -``` -mod.web_layout.tt_content.preview.template.list.mylist_type = Listtypetemplate -``` +TYPO3 v14 hands the content element to a preview renderer as a record object, while v13 still passes +a plain array (see [Breaking-92434](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/14.0/Breaking-92434-UseRecordAPIInPageModulePreviewRendering.html)). +The variables in your preview template therefore differ between the two versions. -All flexform data of the plugin are available in `{pi_flexform_transformed}` to create meaningful previews: +On **both** versions: -``` -Page: {pi_flexform_transformed.settings.page} -``` +* `{editLink.url}` and `{editLink.title}` – the edit link for the element, if the user may edit it. + On v14 there is also `{editLink.contextual}`, used by the shipped layouts to render a + `typo3-backend-contextual-record-edit-trigger`. +* `{CType-label}` – the resolved label of the element's CType. + +On **v14** only: + +* `{record}` – the element itself, so its fields are read as `{record.header}`, `{record.bodytext}`, + and so on. + +On **v13** only: + +* All fields of the `tt_content` row directly, so `{header}`, `{bodytext}`, and so on. +* `{list_type-label}` – the resolved plugin label for elements with CType `list`. +* `{pi_flexform_transformed}` – all flexform data of the plugin as an array, to create meaningful + previews: + + ``` + Page: {pi_flexform_transformed.settings.page} + ``` + +* `{allImages}`, and `{all}` for every other TCA field of type `file` – the file + references of that field, ready to be passed into the `Images` partial. +* A template name for a specific plugin can be specified like this: + + ``` + mod.web_layout.tt_content.preview.template.list.mylist_type = Listtypetemplate + ``` + +The shipped layouts and partials cover both cases, so a template built on top of them keeps working +on either version. -## Use custom backend previews for default CTypes +## Use Custom Backend Previews for Default CTypes -Default CTypes for `fluid_styled_content` define dedicated `previewRenderer` classes. If you want to use `EXT:backendpreviews` instead, -remove the configuration for each of these CTypes in your extension's `ext_localconf.php`: +This extension registers its preview renderer for the whole `tt_content` table. A renderer that is +registered for a single type wins over that, so an element whose type brings its own +`previewRenderer` is not rendered by this extension. The CTypes of `fluid_styled_content` no longer +register one, but other extensions still do—`EXT:form`, for instance, for `form_formframework`. +To use `EXT:backendpreviews` for such a type, remove that configuration in your own extension's +`Configuration/TCA/Overrides/tt_content.php`: ``` -unset($GLOBALS['TCA']['tt_content']['types']['textpic']['previewRenderer']); +unset($GLOBALS['TCA']['tt_content']['types']['form_formframework']['previewRenderer']); ``` ## ViewHelpers @@ -105,13 +139,15 @@ namespace `B13\Backendpreviews\ViewHelpers`. Register them in your template like ## License -As TYPO3 Core, _backendpreviews_ is licensed under GPL2 or later. See the LICENSE file for more details. +As TYPO3 Core, _backendpreviews_ is licensed under GPL-2.0-or-later. See the [LICENSE](LICENSE) file +for more details. -## Background, Authors & Further Maintenance +## Credits -`EXT:backendpreviews` was initially created by David Steeb in 2021 for [b13, Stuttgart](https://b13.com). We use this as -a basis to add consistent previews for our custom content element types. +`EXT:backendpreviews` was created by David Steeb and is maintained by [b13 GmbH](https://b13.com), +Stuttgart, Germany. We use it as a basis to add consistent previews for our custom content element +types. -[Find more TYPO3 extensions we have developed](https://b13.com/useful-typo3-extensions-from-b13-to-you) that help us -deliver value in client projects. As part of the way we work, we focus on testing and best practices to ensure long-term -performance, reliability, and results in all our code. +[Find more TYPO3 extensions we have developed](https://b13.com/useful-typo3-extensions-from-b13-to-you?utm_source=backendpreviews&utm_medium=readme) +that help us deliver value in client projects. As part of our work, we focus on testing and best +practices to ensure long-term performance, reliability, and results in all our code. From eccdbe3ac05e1f92db5fcc799dd94e305bf8106f Mon Sep 17 00:00:00 2001 From: David Steeb Date: Wed, 9 Sep 2026 16:28:49 +0200 Subject: [PATCH 6/8] [TASK] Add .gitattributes for the package boundary Build/ and .github/ were shipped in the release archive. Verified with git archive: 66 entries now, none of them developer tooling. .ddev/, .claude/ and .internal/ are listed up front so the first internal file placed there does not go out with the package. Related: BEXT-729 --- .gitattributes | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fb62b9f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,16 @@ +# Tests and developer tools +/Build/ export-ignore +/Tests/ export-ignore +/.ddev/ export-ignore + +# Git and CI metadata +/.gitattributes export-ignore +/.gitignore export-ignore +/.github/ export-ignore + +# Config, versioned but not part of the package +/.claude/ export-ignore +/CLAUDE.md export-ignore + +# Internal documents, versioned but not part of the package +/.internal/ export-ignore From 97dbd0ec2e69c90d4c0839e4b6db43cae968c5cc Mon Sep 17 00:00:00 2001 From: David Steeb Date: Wed, 9 Sep 2026 16:28:49 +0200 Subject: [PATCH 7/8] [TASK] Name b13 GmbH as package author The authors block was missing entirely and ext_emconf named a person. Machine-readable metadata points at the company that answers; the extension's creator is credited in the README. Category corrected from fe to be, which is what this extension is. Related: BEXT-729 --- composer.json | 12 ++++++++++++ ext_emconf.php | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 64a10ed..9783560 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,18 @@ "license": [ "GPL-2.0-or-later" ], + "authors": [ + { + "name": "b13 GmbH", + "email": "typo3@b13.com", + "homepage": "https://b13.com" + } + ], + "homepage": "https://b13.com", + "support": { + "issues": "https://github.com/b13/backendpreviews/issues", + "source": "https://github.com/b13/backendpreviews" + }, "require": { "php": "^8.2", "typo3/cms-backend": "^13.4 || ^14.3" diff --git a/ext_emconf.php b/ext_emconf.php index 15ac31a..997b637 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -3,8 +3,8 @@ $EM_CONF[$_EXTKEY] = [ 'title' => 'Enhanced Fluid based backend element previews', 'description' => 'Adds full Fluid Templates/Layouts/Partials to backend element previews to enable consistent use of partials.', - 'category' => 'fe', - 'author' => 'David Steeb', + 'category' => 'be', + 'author' => 'b13 GmbH', 'author_email' => 'typo3@b13.com', 'state' => 'stable', 'clearCacheOnLoad' => true, From e8564438cfe44f7287d2a6340e647f9144098c43 Mon Sep 17 00:00:00 2001 From: David Steeb Date: Wed, 9 Sep 2026 16:28:49 +0200 Subject: [PATCH 8/8] [DOCS] Add changelog, security policy, and v14 notes CHANGELOG.md follows Keep a Changelog with the 2.0 entries under Unreleased, to be renamed when the release is tagged. SECURITY.md names the reporting route without promising a response time, and records that b13:getDatabaseRecord reads any table without a permission check. The README section on template variables was wrong about v14: the record object resolves relations and flexform itself, so pi_flexform_transformed and all have no v14 counterpart by design, not by omission. Related: BEXT-729 --- CHANGELOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 57 ++++++++++++++++---------- SECURITY.md | 64 +++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 20 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 SECURITY.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8964747 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,111 @@ +# Changelog + +All notable changes to `b13/backendpreviews` are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +This section becomes 2.0.0 when the release is tagged. + +### Removed + +- **Support for TYPO3 v10.4, v11.5, and v12.4 is dropped**, together with PHP + below 8.2. The extension now requires TYPO3 v13.4 or v14 and PHP 8.2. If you + are on an older TYPO3, stay on the 1.5 releases—they keep working, they just + do not get new features. +- **The `tt_content_drawItem` hook is gone.** `PageLayoutViewDrawItemHookInterface` + no longer exists in the TYPO3 versions this release supports, so + `B13\Backendpreviews\Hooks\BackendPreviewRenderer` and its registration were + removed. Previews are rendered through the `previewRenderer` registered on + `tt_content` plus the `PageContentPreviewRenderingEvent` listener, which is + how it already worked on v12 and above. Only relevant if your own code + referenced that class. +- **`ext_tables.php` is gone.** It only registered the pre-v12 backend skin; + the stylesheet is registered unconditionally in `ext_localconf.php` now. +- **The Fontawesome icon registration is gone.** `exclamation-triangle` and + `exclamation-circle` were registered but never used—the shipped warning + partials use the core icons `actions-exclamation-*-alt`. + +### Changed + +- **Both render paths use `ViewFactoryInterface`.** `StandaloneView`, which is + removed in TYPO3 v14, is no longer referenced anywhere. Preview templates are + unaffected; the same templates, layouts, and partials keep rendering. +- **Metadata corrected:** the `authors` block in `composer.json` and the + `author` in `ext_emconf.php` now name b13 GmbH, with the extension's creator + credited in the README instead. The extension category changed from `fe` to + `be`, which is what it actually is. + +### Documentation + +- **README brought in line with the code.** It described a hook that no longer + exists, still listed TYPO3 v10.4 and PHP 7.4 as supported, pointed at + `settings.yaml` instead of `config.yaml` for the site set dependency, and used + a `previewRenderer` example (`textpic`) that has not applied for several major + versions—`fluid_styled_content` registers none at all on v13 and v14. +- **A new "What Your Template Gets" section** spells out that v14 assigns a + single `{record}` object while v13 assigns the row's fields individually, and + which variables this extension adds on each version. Templates that read + element fields directly need both spellings to work on v13 and v14; see + [Breaking-92434](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/14.0/Breaking-92434-UseRecordAPIInPageModulePreviewRendering.html) + for the core migration path. +- **`SECURITY.md` added** with the reporting route and a note on what a preview + template can expose—in particular that `b13:getDatabaseRecord` reads whatever + table it is pointed at without applying backend user permissions. +- **`CHANGELOG.md` added**, which is this file. + +## [1.5.3] — 2026-08-26 + +### Added + +- **Non-image media files get a fallback icon** in the preview instead of a + broken thumbnail: video, audio, and everything else render the matching + mimetype icon. + +### Documentation + +- **README completed** with a requirements section, the site set as an + alternative to importing the PageTsConfig, and documentation for the three + shipped ViewHelpers (`b13:renderBodytext`, `b13:getDatabaseRecord`, + `b13:explodeList`). + +## [1.5.2] — 2026-04-24 + +### Added + +- **Contextual edit URL on TYPO3 v14.** Previews link through + `record_edit_contextual`, so clicking one opens the element in the page + module's own editing context instead of navigating away. + +### Fixed + +- Compatibility with TYPO3 14.3 and a v14 deprecation. + +## [1.5.1] — 2026-02-26 + +### Fixed + +- **The icon registration no longer runs in `ext_localconf.php`.** Using + `IconRegistry` that early broke on some installations. + +## [1.5.0] — 2026-02-13 + +### Added + +- **TYPO3 v14 and Fluid 5 support**, alongside the existing versions. + +### Changed + +- **List group items are separated more clearly** in the preview markup. + +### Fixed + +- Several fixes to the shipped partials, the `Link` partial on v14, and + dependency injection for `DatabaseRowService`. + +## [1.4.3] and earlier + +Released between 2021 and 2024, covering the TYPO3 v10 to v13 era. See the +[README](README.md) for what the extension does and how it is configured. diff --git a/README.md b/README.md index f88246d..90e1b87 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ adding the ability to use Fluid templates, layouts, and partials to enable consi Version 2.0 dropped support for TYPO3 v10.4, v11.5, and v12.4 together with the compatibility layer for those versions. If you are still on one of them, stay on the 1.5 releases. +Release notes live in [CHANGELOG.md](CHANGELOG.md). + ## Installation Use composer to add this extension to your project @@ -60,42 +62,52 @@ mod.web_layout.tt_content.preview.template.mytype = Myowntemplate ## What Your Template Gets TYPO3 v14 hands the content element to a preview renderer as a record object, while v13 still passes -a plain array (see [Breaking-92434](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/14.0/Breaking-92434-UseRecordAPIInPageModulePreviewRendering.html)). -The variables in your preview template therefore differ between the two versions. - -On **both** versions: +a plain array (see +[Breaking-92434](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/14.0/Breaking-92434-UseRecordAPIInPageModulePreviewRendering.html), +which is also the migration guide for your own templates). The variables in a preview template +therefore differ between the two versions. -* `{editLink.url}` and `{editLink.title}` – the edit link for the element, if the user may edit it. - On v14 there is also `{editLink.contextual}`, used by the shipped layouts to render a - `typo3-backend-contextual-record-edit-trigger`. -* `{CType-label}` – the resolved label of the element's CType. +On **v14**, everything from the element itself hangs off a single `{record}`: -On **v14** only: +```html +

{record.header}

+

{record.bodytext}

+Image UID: {record.image.uid} +{record.pi_flexform.sheets.s_messages.settings.welcome_header} +``` -* `{record}` – the element itself, so its fields are read as `{record.header}`, `{record.bodytext}`, - and so on. +Relations are already resolved on that object, and flexform values come as a `FlexFormFieldValues` +object grouped by sheet. -On **v13** only: +On **v13**, the fields of the `tt_content` row are assigned individually, so the same template reads +`{header}`, `{bodytext}`, and `{image}`. This extension adds three things that only exist on that +version, because v14 covers them through the record: -* All fields of the `tt_content` row directly, so `{header}`, `{bodytext}`, and so on. -* `{list_type-label}` – the resolved plugin label for elements with CType `list`. -* `{pi_flexform_transformed}` – all flexform data of the plugin as an array, to create meaningful - previews: +* `{pi_flexform_transformed}` – all flexform data of the plugin as a flat array: ``` Page: {pi_flexform_transformed.settings.page} ``` * `{allImages}`, and `{all}` for every other TCA field of type `file` – the file - references of that field, ready to be passed into the `Images` partial. -* A template name for a specific plugin can be specified like this: + references of that field, which is what the shipped `Images` partial expects. +* `{list_type-label}`, plus a template name per plugin for elements with CType `list`: ``` mod.web_layout.tt_content.preview.template.list.mylist_type = Listtypetemplate ``` -The shipped layouts and partials cover both cases, so a template built on top of them keeps working -on either version. + There is no v14 counterpart—`list_type` is gone from the core. + +On **both** versions this extension assigns: + +* `{editLink.url}` and `{editLink.title}` – the edit link for the element, if the user may edit it. + On v14 there is also `{editLink.contextual}`, which the shipped layouts use to render a + `typo3-backend-contextual-record-edit-trigger`. +* `{CType-label}` – the resolved label of the element's CType. + +If your templates build on the shipped layouts and partials, the version switch is handled there. +A template that reads element fields directly needs both spellings to work on v13 and v14. ## Use Custom Backend Previews for Default CTypes @@ -137,6 +149,11 @@ namespace `B13\Backendpreviews\ViewHelpers`. Register them in your template like * `b13:explodeList` – splits a list value into an array you can iterate over with `f:for`, either by a character (`splitChar`, default `,`) or by newlines (`splitNL`). +## Security + +Please report security issues to [security@b13.com](mailto:security@b13.com). See +[SECURITY.md](SECURITY.md) for the reporting process and what to expect. + ## License As TYPO3 Core, _backendpreviews_ is licensed under GPL-2.0-or-later. See the [LICENSE](LICENSE) file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f3f53a4 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,64 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report security issues to **[security@b13.com](mailto:security@b13.com)**, +not through the public issue tracker. + +Helpful in a report: + +- The extension version and the TYPO3 version +- What an attacker can do, and what access they need to do it +- Steps to reproduce, or a proof of concept +- Whether the issue is already public anywhere + +If you would like to encrypt your report, ask us for a key first. + +## What to Expect + +We read every report and reply to it. You will hear back from us with an +assessment of the finding, and we agree a disclosure date with you before +anything is published. + +We do not publish fixed response times. We would rather answer you quickly than +name a deadline we might miss—if a date matters for your own disclosure +process, say so in your report and we will agree one with you. + +We publish a fix before we describe the issue. Advisories appear next to the +[changelog](CHANGELOG.md) once a fixed version is available, and we credit +reporters who want to be credited. + +## Supported Versions + +Security fixes go into the current minor release of each supported major +version. There are no backports to majors that have reached their end of +support. + +The extension's own support window follows the TYPO3 versions it declares in +`composer.json`. See the [changelog](CHANGELOG.md) for the version history. + +## Data This Extension Handles + +Worth knowing when you assess the impact of a finding: + +- **The extension holds no data of its own.** It has no database tables, no + configuration of its own, and writes nothing. It renders content that is + already in `tt_content`. +- **It renders in the backend only**, inside the page module. Nothing it + produces is reachable from the frontend. +- **Preview templates decide what is shown.** The templates shipped here render + fields of the content element that the editor is already allowed to see in + that module. A template added in a project can widen that—the + `b13:getDatabaseRecord` ViewHelper reads any table it is pointed at, without + applying backend user permissions to the result. Treat a preview template as + code, and do not use it to surface records an editor may not see otherwise. +- **The edit link is permission-checked.** It is only assigned when the backend + user may edit the record. + +## Scope + +In scope: the code in this repository. + +Out of scope: vulnerabilities in TYPO3 core (report those to the +[TYPO3 Security Team](https://typo3.org/help/security-advisories)) or in a +third-party extension.