diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e0d8288 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: CI + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + TYPO3: [ '12', '13', '14' ] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up PHP Version + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + tools: composer:v2 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.composer/cache + key: dependencies-composer-${{ hashFiles('composer.json') }} + + - name: Install composer dependencies TYPO3 14 + if: matrix.TYPO3 == '14' + run: | + composer install --no-progress --no-interaction + - name: Install composer dependencies TYPO3 13 + 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 + if: matrix.TYPO3 == '13' + 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c483ff8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.Build/ +Build/phpunit/.phpunit.cache +var/ +composer.lock +.php-cs-fixer.cache +.env +.idea diff --git a/Build/php-cs-fixer.php b/Build/php-cs-fixer.php new file mode 100644 index 0000000..16494b6 --- /dev/null +++ b/Build/php-cs-fixer.php @@ -0,0 +1,11 @@ +getFinder()->exclude(['var'])->in(__DIR__ . '/..'); +$config->addRules([ + 'nullable_type_declaration' => [ + 'syntax' => 'question_mark', + ], + 'nullable_type_declaration_for_default_null_value' => true, +]); +return $config; diff --git a/Build/phpstan-baseline.neon b/Build/phpstan-baseline.neon new file mode 100644 index 0000000..2faaf69 --- /dev/null +++ b/Build/phpstan-baseline.neon @@ -0,0 +1,61 @@ +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 + 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\\.$#" + 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 + + - + message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Core\\\\Database\\\\Query\\\\QueryBuilder\\:\\:add\\(\\)\\.$#" + count: 1 + path: ../Classes/ViewHelpers/GetDatabaseRecordViewHelper.php diff --git a/Build/phpstan-baseline12.neon b/Build/phpstan-baseline12.neon new file mode 100644 index 0000000..6aee1e5 --- /dev/null +++ b/Build/phpstan-baseline12.neon @@ -0,0 +1,46 @@ +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 diff --git a/Build/phpstan-baseline13.neon b/Build/phpstan-baseline13.neon new file mode 100644 index 0000000..40382ae --- /dev/null +++ b/Build/phpstan-baseline13.neon @@ -0,0 +1,11 @@ +parameters: + ignoreErrors: + - + 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 an undefined method TYPO3\\\\CMS\\\\Core\\\\Database\\\\Query\\\\QueryBuilder\\:\\:add\\(\\)\\.$#" + count: 1 + path: ../Classes/ViewHelpers/GetDatabaseRecordViewHelper.php diff --git a/Build/phpstan.neon b/Build/phpstan.neon new file mode 100644 index 0000000..d27b0d2 --- /dev/null +++ b/Build/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon +parameters: + level: 5 + paths: + - %currentWorkingDirectory%/Classes + excludePaths: + - %currentWorkingDirectory%/Classes/Hooks/BackendPreviewRenderer.php \ No newline at end of file diff --git a/Build/phpstan12.neon b/Build/phpstan12.neon new file mode 100644 index 0000000..2ec6711 --- /dev/null +++ b/Build/phpstan12.neon @@ -0,0 +1,8 @@ +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 new file mode 100644 index 0000000..0ebc213 --- /dev/null +++ b/Build/phpstan13.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline13.neon +parameters: + level: 5 + paths: + - %currentWorkingDirectory%/Classes + excludePaths: + - %currentWorkingDirectory%/Classes/Hooks/BackendPreviewRenderer.php \ No newline at end of file diff --git a/Classes/Backend/Preview/ContentPreview.php b/Classes/Backend/Preview/ContentPreview.php index 0863563..524c577 100644 --- a/Classes/Backend/Preview/ContentPreview.php +++ b/Classes/Backend/Preview/ContentPreview.php @@ -13,12 +13,56 @@ */ use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Backend\View\PageLayoutContext; +use TYPO3\CMS\Core\Domain\RecordInterface; 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 TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException; class ContentPreview { - public function render(array $row): ?string + 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 + 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() + ) + ); + $view->assign('record', $record); + try { + return $view->render($fluidTemplateName); + } catch (InvalidTemplateResourceException) { + } + return null; + } + + public function renderLegacy(array $row): ?string { $previewConfiguration = BackendUtility::getPagesTSconfig($row['pid'])['mod.']['web_layout.']['tt_content.']['preview.'] ?? []; if (!$previewConfiguration) { diff --git a/Classes/Backend/Preview/StandardContentPreviewRenderer.php b/Classes/Backend/Preview/StandardContentPreviewRenderer.php index 00d2185..e1a579a 100644 --- a/Classes/Backend/Preview/StandardContentPreviewRenderer.php +++ b/Classes/Backend/Preview/StandardContentPreviewRenderer.php @@ -13,6 +13,7 @@ */ use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem; +use TYPO3\CMS\Core\Domain\RecordInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; class StandardContentPreviewRenderer extends \TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer @@ -29,8 +30,13 @@ public function renderPageModulePreviewHeader(GridColumnItem $item): string public function renderPageModulePreviewContent(GridColumnItem $item): string { $record = $item->getRecord(); + $context = $item->getContext(); $contentPreview = GeneralUtility::makeInstance(ContentPreview::class); - $content = $contentPreview->render($record); + if ($record instanceof RecordInterface) { + $content = $contentPreview->render($record, $context); + } else { + $content = $contentPreview->renderLegacy($record); + } if ($content !== null) { return $content; } diff --git a/Classes/Hooks/BackendPreviewRenderer.php b/Classes/Hooks/BackendPreviewRenderer.php index 9f5fdaf..c13e9e2 100644 --- a/Classes/Hooks/BackendPreviewRenderer.php +++ b/Classes/Hooks/BackendPreviewRenderer.php @@ -52,7 +52,7 @@ public function preProcess( $row = $this->databaseRowService->extendRow($row); if ((GeneralUtility::makeInstance(Features::class))->isFeatureEnabled('fluidBasedPageModule') === false) { $contentPreview = GeneralUtility::makeInstance(ContentPreview::class); - $content = $contentPreview->render($row); + $content = $contentPreview->renderLegacy($row); if ($content !== null) { $itemContent .= $content; $drawItem = false; diff --git a/Classes/Listener/PageContentPreviewRendering.php b/Classes/Listener/PageContentPreviewRendering.php index c33e68c..15e2ac5 100644 --- a/Classes/Listener/PageContentPreviewRendering.php +++ b/Classes/Listener/PageContentPreviewRendering.php @@ -14,6 +14,7 @@ use B13\Backendpreviews\Service\DatabaseRowService; use TYPO3\CMS\Backend\View\Event\PageContentPreviewRenderingEvent; +use TYPO3\CMS\Core\Information\Typo3Version; class PageContentPreviewRendering { @@ -26,6 +27,9 @@ public function __construct(DatabaseRowService $databaseRowService) public function __invoke(PageContentPreviewRenderingEvent $event): void { + if ((new Typo3Version())->getMajorVersion() > 13) { + return; + } $record = $event->getRecord(); $record = $this->databaseRowService->extendRow($record); $event->setRecord($record); diff --git a/Classes/Service/DatabaseRowService.php b/Classes/Service/DatabaseRowService.php index ebd42b9..2a7a7ac 100644 --- a/Classes/Service/DatabaseRowService.php +++ b/Classes/Service/DatabaseRowService.php @@ -16,6 +16,7 @@ use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; 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\SingletonInterface; diff --git a/Classes/ViewHelpers/ExplodeListViewHelper.php b/Classes/ViewHelpers/ExplodeListViewHelper.php index 30aa797..d944e41 100644 --- a/Classes/ViewHelpers/ExplodeListViewHelper.php +++ b/Classes/ViewHelpers/ExplodeListViewHelper.php @@ -12,9 +12,7 @@ * of the License, or any later version. */ -use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; -use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic; /** * Class ExplodeListViewHelper @@ -47,9 +45,7 @@ public function initializeArguments(): void $this->registerArgument( 'splitNL', 'boolean', - 'Split newlines. If this is true, splitChar is ignored.', - '', - false + 'Split newlines. If this is true, splitChar is ignored.' ); } @@ -60,7 +56,7 @@ public function render() } else { $splitChar = $this->arguments['splitChar'] ?? self::DEFAULT_SPLIT_CHAR; } - $value = $this->arguments['value'] ?? $renderChildrenClosure(); + $value = $this->arguments['value'] ?? $this->renderChildren(); return explode($splitChar, (string)$value); } } diff --git a/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php b/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php index acca642..ad80011 100644 --- a/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php +++ b/Classes/ViewHelpers/GetDatabaseRecordViewHelper.php @@ -19,9 +19,7 @@ use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction; use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; -use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic; /** * Class GetDatabaseRecordViewHelper @@ -76,9 +74,9 @@ public function render(): array $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY)) ); if ((new Typo3Version())->getMajorVersion() > 12) { - $queryBuilder->getConcreteQueryBuilder()->addOrderBy('FIELD(uid,' . implode(',', $uids ) . ')'); + $queryBuilder->getConcreteQueryBuilder()->addOrderBy('FIELD(uid,' . implode(',', $uids) . ')'); } else { - $queryBuilder->add('orderBy', 'FIELD(uid,' . implode(',', $uids ) . ')'); + $queryBuilder->add('orderBy', 'FIELD(uid,' . implode(',', $uids) . ')'); } return $queryBuilder ->executeQuery() diff --git a/Classes/ViewHelpers/RenderBodytextViewHelper.php b/Classes/ViewHelpers/RenderBodytextViewHelper.php index 25301e6..09922f5 100644 --- a/Classes/ViewHelpers/RenderBodytextViewHelper.php +++ b/Classes/ViewHelpers/RenderBodytextViewHelper.php @@ -13,10 +13,7 @@ */ use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; -use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic; -use function HighlightUtilities\splitCodeIntoArray; /** * Class RenderBodytextViewHelper @@ -38,8 +35,6 @@ public function initializeArguments(): void 'value', 'string', 'The input value. If not given, the evaluated child nodes will be used.', - false, - null ); $this->registerArgument( 'crop', @@ -60,21 +55,13 @@ public function render() } else { $crop = $this->arguments['crop'] ?: self::DEFAULT_CROP_VALUE; } - $value = $this->arguments['value']; - $keepTags = $this->arguments['keepTags'] ? explode(',', str_replace(' ', '', $this->arguments['keepTags'])): self::DEFAULT_KEEP_TAGS_LIST; - if ($value === null) { - $value = $renderChildrenClosure(); - } - - if ($value) { - $value = strip_tags($value, $keepTags); - $value = GeneralUtility::fixed_lgd_cs($value, $crop); - if ($keepTags !== null) { - return nl2br(trim($value)); - } - return nl2br(htmlspecialchars(trim($value), ENT_QUOTES, 'UTF-8', false)); + $value = (string)($this->arguments['value'] ?? $this->renderChildren()); + if ($value === '') { + return ''; } - - return ''; + $keepTags = $this->arguments['keepTags'] ? explode(',', str_replace(' ', '', $this->arguments['keepTags'])): self::DEFAULT_KEEP_TAGS_LIST; + $value = strip_tags($value, $keepTags); + $value = GeneralUtility::fixed_lgd_cs($value, $crop); + return nl2br(trim($value)); } } diff --git a/composer.json b/composer.json index a3b0a42..c761c5d 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "description": "Enhanced Fluid based backend element previews", "extra": { "typo3/cms": { + "web-dir": ".Build/Web", "extension-key": "backendpreviews" } }, @@ -12,7 +13,11 @@ ], "require": { "php": "^7.4 || ~8.0", - "typo3/cms-core": "^10.4 || ^11.5 || ^12.1 || ^13.0 || ^14.0" + "typo3/cms-backend": "^10.4 || ^11.5 || ^12.4 || ^13.4 || ^14.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "typo3/coding-standards": "^0.5.5" }, "suggest": { "b13/listelements": "*" @@ -21,5 +26,13 @@ "psr-4": { "B13\\Backendpreviews\\": "Classes/" } + }, + "config": { + "vendor-dir": ".Build/vendor", + "bin-dir": ".Build/bin", + "allow-plugins": { + "typo3/cms-composer-installers": true, + "typo3/class-alias-loader": true + } } }