From bf6bf9b0fbfcb4ac3c95887d80923a344402ca06 Mon Sep 17 00:00:00 2001 From: Benni Mack <benni@typo3.org> Date: Fri, 22 Mar 2024 10:08:05 +0100 Subject: [PATCH] [TASK] Centralize Page Layout resolving MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change centralizes Frontend's "PageLayoutResolver", used in TypoScript, and BackendLayoutView logic to find the used page layout, while also modelling more towards an object within PageLayout which can be used at a later stage in FE to retrieve more information. At the same time, some BackendLayoutView code is reduced now. Resolves: #103466 Releases: main Change-Id: I716fe7313894aac92e5519a6b725feefff908270 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/83567 Tested-by: Stefan Bürk <stefan@buerk.tech> Reviewed-by: Nikita Hovratov <nikita.h@live.de> Reviewed-by: Benni Mack <benni@typo3.org> Tested-by: Nikita Hovratov <nikita.h@live.de> Tested-by: Benni Mack <benni@typo3.org> Tested-by: core-ci <typo3@b13.com> Reviewed-by: Stefan Bürk <stefan@buerk.tech> --- .../Classes/View/BackendLayoutView.php | 68 +++------ .../Functional/View/BackendLayoutViewTest.php | 18 +-- typo3/sysext/core/Classes/Page/PageLayout.php | 49 +++++++ .../core/Classes/Page/PageLayoutResolver.php | 134 ++++++++++++++++++ .../IncludeTreeConditionMatcherVisitor.php | 4 +- typo3/sysext/core/Configuration/Services.yaml | 3 + .../Page/PageLayoutResolverTest.php | 87 ++++++++++++ .../ContentObject/ContentObjectRenderer.php | 4 +- .../Classes/Page/PageLayoutResolver.php | 73 ---------- .../Unit/Page/PageLayoutResolverTest.php | 87 ------------ .../Tests/Functional/WebhookExecutionTest.php | 5 +- 11 files changed, 299 insertions(+), 233 deletions(-) create mode 100644 typo3/sysext/core/Classes/Page/PageLayout.php create mode 100644 typo3/sysext/core/Classes/Page/PageLayoutResolver.php create mode 100644 typo3/sysext/core/Tests/Functional/Page/PageLayoutResolverTest.php delete mode 100644 typo3/sysext/frontend/Classes/Page/PageLayoutResolver.php delete mode 100644 typo3/sysext/frontend/Tests/Unit/Page/PageLayoutResolverTest.php diff --git a/typo3/sysext/backend/Classes/View/BackendLayoutView.php b/typo3/sysext/backend/Classes/View/BackendLayoutView.php index c65a8d470572..3ede3dfb0f30 100644 --- a/typo3/sysext/backend/Classes/View/BackendLayoutView.php +++ b/typo3/sysext/backend/Classes/View/BackendLayoutView.php @@ -24,6 +24,7 @@ use TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext; use TYPO3\CMS\Backend\View\BackendLayout\DefaultDataProvider; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Page\PageLayoutResolver; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -43,6 +44,7 @@ class BackendLayoutView implements SingletonInterface public function __construct( private readonly DataProviderCollection $dataProviderCollection, private readonly TypoScriptStringFactory $typoScriptStringFactory, + private readonly PageLayoutResolver $pageLayoutResolver, ) { $this->dataProviderCollection->add('default', DefaultDataProvider::class); foreach ((array)($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider'] ?? []) as $identifier => $className) { @@ -146,35 +148,26 @@ class BackendLayoutView implements SingletonInterface * Returns the backend layout which should be used for this page. * * @return false|string Identifier of the backend layout to be used, or FALSE if none + * @internal only public for testing purposes */ - protected function getSelectedCombinedIdentifier(int $pageId): string|false + public function getSelectedCombinedIdentifier(int $pageId): string|false { if (!isset($this->selectedCombinedIdentifier[$pageId])) { - $page = $this->getPage($pageId); - $this->selectedCombinedIdentifier[$pageId] = (string)($page['backend_layout'] ?? null); - if ($this->selectedCombinedIdentifier[$pageId] === '-1') { + // If it not set check the root-line for a layout on next level and use this + // (root-line starts with current page and has page "0" at the end) + $rootLine = BackendUtility::BEgetRootLine($pageId, '', true); + // Use first element as current page, + $page = reset($rootLine); + // and remove last element (root page / pid=0) + array_pop($rootLine); + $selectedLayout = $this->pageLayoutResolver->getLayoutIdentifierForPage($page, $rootLine); + if ($selectedLayout === 'none') { // If it is set to "none" - don't use any - $this->selectedCombinedIdentifier[$pageId] = false; - } elseif ($this->selectedCombinedIdentifier[$pageId] === '' || $this->selectedCombinedIdentifier[$pageId] === '0') { - // If it not set check the root-line for a layout on next level and use this - // (root-line starts with current page and has page "0" at the end) - $rootLine = BackendUtility::BEgetRootLine($pageId, '', true); - // Remove first and last element (current and root page) - array_shift($rootLine); - array_pop($rootLine); - foreach ($rootLine as $rootLinePage) { - $this->selectedCombinedIdentifier[$pageId] = (string)$rootLinePage['backend_layout_next_level']; - if ($this->selectedCombinedIdentifier[$pageId] === '-1') { - // If layout for "next level" is set to "none" - don't use any and stop searching - $this->selectedCombinedIdentifier[$pageId] = false; - break; - } - if ($this->selectedCombinedIdentifier[$pageId] !== '' && $this->selectedCombinedIdentifier[$pageId] !== '0') { - // Stop searching if a layout for "next level" is set - break; - } - } + $selectedLayout = false; + } elseif ($selectedLayout === 'default') { + $selectedLayout = '0'; } + $this->selectedCombinedIdentifier[$pageId] = $selectedLayout; } // If it is set to a positive value use this return $this->selectedCombinedIdentifier[$pageId]; @@ -316,31 +309,4 @@ class BackendLayoutView implements SingletonInterface } '; } - - /** - * Gets a page record. - */ - protected function getPage(int $pageId): ?array - { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable('pages'); - $queryBuilder->getRestrictions() - ->removeAll(); - $page = $queryBuilder - ->select('uid', 'pid', 'backend_layout') - ->from('pages') - ->where( - $queryBuilder->expr()->eq( - 'uid', - $queryBuilder->createNamedParameter($pageId, Connection::PARAM_INT) - ) - ) - ->executeQuery() - ->fetchAssociative(); - if (is_array($page)) { - BackendUtility::workspaceOL('pages', $page); - } - - return is_array($page) ? $page : null; - } } diff --git a/typo3/sysext/backend/Tests/Functional/View/BackendLayoutViewTest.php b/typo3/sysext/backend/Tests/Functional/View/BackendLayoutViewTest.php index a5904c937724..55b00a3b02a6 100644 --- a/typo3/sysext/backend/Tests/Functional/View/BackendLayoutViewTest.php +++ b/typo3/sysext/backend/Tests/Functional/View/BackendLayoutViewTest.php @@ -19,11 +19,9 @@ namespace TYPO3\CMS\Backend\Tests\Functional\View; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; -use PHPUnit\Framework\MockObject\MockObject; use TYPO3\CMS\Backend\View\BackendLayoutView; use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; -use TYPO3\TestingFramework\Core\AccessibleObjectInterface; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; final class BackendLayoutViewTest extends FunctionalTestCase @@ -31,19 +29,13 @@ final class BackendLayoutViewTest extends FunctionalTestCase private const RUNTIME_CACHE_ENTRY = 'backendUtilityBeGetRootLine'; private FrontendInterface $runtimeCache; - private BackendLayoutView&MockObject&AccessibleObjectInterface $backendLayoutView; + private BackendLayoutView $subject; protected function setUp(): void { parent::setUp(); $this->runtimeCache = $this->get(CacheManager::class)->getCache('runtime'); - $this->backendLayoutView = $this->getAccessibleMock( - BackendLayoutView::class, - ['getPage'], - [], - '', - false - ); + $this->subject = $this->get(BackendLayoutView::class); } protected function tearDown(): void @@ -61,11 +53,7 @@ final class BackendLayoutViewTest extends FunctionalTestCase $this->mockRootLine((int)$pageId, $rootLine); } - $this->backendLayoutView->expects(self::once()) - ->method('getPage')->with(self::equalTo($pageId)) - ->willReturn($page); - - $selectedCombinedIdentifier = $this->backendLayoutView->_call('getSelectedCombinedIdentifier', $pageId); + $selectedCombinedIdentifier = $this->subject->getSelectedCombinedIdentifier($pageId); self::assertEquals($expected, $selectedCombinedIdentifier); } diff --git a/typo3/sysext/core/Classes/Page/PageLayout.php b/typo3/sysext/core/Classes/Page/PageLayout.php new file mode 100644 index 000000000000..61b49254a034 --- /dev/null +++ b/typo3/sysext/core/Classes/Page/PageLayout.php @@ -0,0 +1,49 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * 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. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Page; + +/** + * Contains information about the layout of a page, + * mainly which content areas (colPos=0, colPos=1, ...) are used and filled. + * + * @internal This is not part of TYPO3 Core API. + */ +class PageLayout +{ + public function __construct( + protected string $identifier, + protected string $title, + protected array $contentAreas, + protected array $fullConfiguration + ) {} + + public function getIdentifier(): string + { + return $this->identifier; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getContentAreas(): array + { + return $this->contentAreas; + } +} diff --git a/typo3/sysext/core/Classes/Page/PageLayoutResolver.php b/typo3/sysext/core/Classes/Page/PageLayoutResolver.php new file mode 100644 index 000000000000..df7c330eb4a3 --- /dev/null +++ b/typo3/sysext/core/Classes/Page/PageLayoutResolver.php @@ -0,0 +1,134 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * 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. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Page; + +use TYPO3\CMS\Backend\View\BackendLayout\DataProviderCollection; +use TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext; +use TYPO3\CMS\Backend\View\BackendLayout\DefaultDataProvider; +use TYPO3\CMS\Core\Site\SiteFinder; +use TYPO3\CMS\Core\TypoScript\PageTsConfigFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Finds the proper layout for a page, using the database fields "backend_layout" + * and "backend_layout_next_level". + * + * The most crucial part is that "backend_layout" is only applied for the CURRENT level, + * whereas backend_layout_next_level. + * + * Used in TypoScript as "getData:pagelayout". + * + * Currently, there is a hard dependency on EXT:backend however, all DataProvider logic should be migrated + * towards EXT:core. + * + * @internal This is not part of TYPO3 Core API. + */ +class PageLayoutResolver +{ + public function __construct( + protected readonly DataProviderCollection $dataProviderCollection, + protected readonly SiteFinder $siteFinder, + protected readonly PageTsConfigFactory $pageTsConfigFactory + ) { + $this->dataProviderCollection->add('default', DefaultDataProvider::class); + foreach ((array)($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider'] ?? []) as $identifier => $className) { + $this->dataProviderCollection->add($identifier, $className); + } + } + + public function getLayoutForPage(array $pageRecord, array $rootLine): ?PageLayout + { + $pageId = (int)$pageRecord['uid']; + $site = $this->siteFinder->getSiteByPageId($pageId, $rootLine); + $pageTsConfig = $this->pageTsConfigFactory->create($rootLine, $site); + + $dataProviderContext = GeneralUtility::makeInstance(DataProviderContext::class); + $dataProviderContext + ->setPageId($pageId) + ->setData($pageRecord) + ->setTableName('pages') + ->setFieldName('backend_layout') + ->setPageTsConfig($pageTsConfig->getPageTsConfigArray()); + + $selectedPageLayout = $this->getLayoutIdentifierForPage($pageRecord, $rootLine); + $layout = $this->dataProviderCollection->getBackendLayout($selectedPageLayout, $pageId); + + if ($layout === null) { + return null; + } + + $fullStructure = $layout->getStructure()['__config']; + $contentAreas = []; + // find all arrays recursively from , where one of the columns within the array is called "colPos" + $findColPos = function (array $structure) use (&$findColPos, &$contentAreas) { + if (isset($structure['colPos'])) { + unset($structure['colspan'], $structure['rowspan']); + $contentAreas[] = $structure; + } + foreach ($structure as $value) { + if (is_array($value)) { + $findColPos($value); + } + } + }; + $findColPos($fullStructure); + + return new PageLayout($layout->getIdentifier(), $layout->getTitle(), $contentAreas, $layout->getStructure()); + } + + /** + * Check if the current page has a value in the DB field "backend_layout" + * if empty, check the root line for "backend_layout_next_level" + * Same as TypoScript: + * field = backend_layout + * ifEmpty.data = levelfield:-2, backend_layout_next_level, slide + * ifEmpty.ifEmpty = default + */ + public function getLayoutIdentifierForPage(array $page, array $rootLine): string + { + $selectedLayout = $page['backend_layout'] ?? ''; + + // If it is set to "none" - don't use any + if ($selectedLayout === '-1') { + return 'none'; + } + + if ($selectedLayout === '' || $selectedLayout === '0') { + // If it not set check the root-line for a layout on next level and use this + // Remove first element, which is the current page + // See also \TYPO3\CMS\Backend\View\BackendLayoutView::getSelectedCombinedIdentifier() + array_shift($rootLine); + foreach ($rootLine as $rootLinePage) { + $selectedLayout = (string)($rootLinePage['backend_layout_next_level'] ?? ''); + // If layout for "next level" is set to "none" - don't use any and stop searching + if ($selectedLayout === '-1') { + $selectedLayout = 'none'; + break; + } + if ($selectedLayout !== '' && $selectedLayout !== '0') { + // Stop searching if a layout for "next level" is set + break; + } + } + } + if ($selectedLayout === '0' || $selectedLayout === '') { + $selectedLayout = 'default'; + } + return $selectedLayout; + } +} diff --git a/typo3/sysext/core/Classes/TypoScript/IncludeTree/Visitor/IncludeTreeConditionMatcherVisitor.php b/typo3/sysext/core/Classes/TypoScript/IncludeTree/Visitor/IncludeTreeConditionMatcherVisitor.php index ebf959c667b4..f41520121c7a 100644 --- a/typo3/sysext/core/Classes/TypoScript/IncludeTree/Visitor/IncludeTreeConditionMatcherVisitor.php +++ b/typo3/sysext/core/Classes/TypoScript/IncludeTree/Visitor/IncludeTreeConditionMatcherVisitor.php @@ -26,9 +26,9 @@ use TYPO3\CMS\Core\Context\UserAspect; use TYPO3\CMS\Core\Context\WorkspaceAspect; use TYPO3\CMS\Core\ExpressionLanguage\RequestWrapper; use TYPO3\CMS\Core\ExpressionLanguage\Resolver; +use TYPO3\CMS\Core\Page\PageLayoutResolver; use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\IncludeConditionInterface; use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\IncludeInterface; -use TYPO3\CMS\Frontend\Page\PageLayoutResolver; /** * A visitor that looks at IncludeConditionInterface nodes and @@ -122,7 +122,7 @@ final class IncludeTreeConditionMatcherVisitor implements IncludeTreeVisitorInte // the 'nearest' parent. However, here it is always passed sorted, so it is a top-down rootLine. Hence, this needs to be once // again reversed at this point. $bottomUpFullRootLine = array_reverse($fullRootLine); - $tree->pagelayout = $this->pageLayoutResolver->getLayoutForPage($variables['page'], $bottomUpFullRootLine); + $tree->pagelayout = $this->pageLayoutResolver->getLayoutIdentifierForPage($variables['page'], $bottomUpFullRootLine); $enrichedVariables['tree'] = $tree; } diff --git a/typo3/sysext/core/Configuration/Services.yaml b/typo3/sysext/core/Configuration/Services.yaml index 7d38fbae5174..138be5a33462 100644 --- a/typo3/sysext/core/Configuration/Services.yaml +++ b/typo3/sysext/core/Configuration/Services.yaml @@ -213,6 +213,9 @@ services: TYPO3\CMS\Core\Locking\ResourceMutex: public: true + TYPO3\CMS\Core\Page\PageLayoutResolver: + public: true + TYPO3\CMS\Core\Page\PageRenderer: arguments: $assetsCache: '@cache.assets' diff --git a/typo3/sysext/core/Tests/Functional/Page/PageLayoutResolverTest.php b/typo3/sysext/core/Tests/Functional/Page/PageLayoutResolverTest.php new file mode 100644 index 000000000000..11021c298aa9 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Page/PageLayoutResolverTest.php @@ -0,0 +1,87 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * 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. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Tests\Functional\Page; + +use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Page\PageLayoutResolver; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +final class PageLayoutResolverTest extends FunctionalTestCase +{ + #[Test] + public function getLayoutIdentifierForPageFetchesSelectedPageDirectly(): void + { + $subject = $this->get(PageLayoutResolver::class); + $result = $subject->getLayoutIdentifierForPage(['backend_layout' => '1'], ['does-not-matter']); + self::assertEquals('1', $result); + } + + #[Test] + public function getLayoutIdentifierForPageTreatsSpecialMinusOneValueAsNone(): void + { + $subject = $this->get(PageLayoutResolver::class); + $result = $subject->getLayoutIdentifierForPage(['backend_layout' => '-1'], ['does-not-matter']); + self::assertEquals('none', $result); + } + + #[Test] + public function getLayoutIdentifierForPageTreatsSpecialValueZeroOrEmptyAsDefaultWithEmptyRootLine(): void + { + $subject = $this->get(PageLayoutResolver::class); + $parentPages = [['backend_layout' => '']]; + $page = ['backend_layout' => '0', 'uid' => 123]; + $result = $subject->getLayoutIdentifierForPage($page, array_merge([$page], $parentPages)); + self::assertEquals('default', $result); + $page = ['backend_layout' => '', 'uid' => 123]; + $result = $subject->getLayoutIdentifierForPage($page, array_merge([$page], $parentPages)); + self::assertEquals('default', $result); + } + + #[Test] + public function getLayoutIdentifierForPageTreatsSpecialValueZeroOrEmptyAsDefaultWhenNothingGivenInRootLine(): void + { + $subject = $this->get(PageLayoutResolver::class); + // No layout specified for current page + $page = ['backend_layout' => '', 'uid' => 123]; + $parentPages = [['uid' => 13, 'backend_layout' => 'does-not-matter'], ['uid' => 1, 'backend_layout_next_level' => '0']]; + $result = $subject->getLayoutIdentifierForPage($page, array_merge([$page], $parentPages)); + self::assertEquals('default', $result); + } + + #[Test] + public function getLayoutIdentifierForPageFetchesRootLinePagesUpUntilSomethingWasFound(): void + { + $subject = $this->get(PageLayoutResolver::class); + // No layout specified for current page + $page = ['backend_layout' => '', 'uid' => 123]; + $parentPages = [['uid' => 13, 'backend_layout' => 'does-not-matter', 'backend_layout_next_level' => ''], ['uid' => 1, 'backend_layout_next_level' => 'regular']]; + $result = $subject->getLayoutIdentifierForPage($page, array_merge([$page], $parentPages)); + self::assertEquals('regular', $result); + } + + #[Test] + public function getLayoutIdentifierForPageFetchesRootLinePagesUpWhenNoneWasSelectedExplicitly(): void + { + $subject = $this->get(PageLayoutResolver::class); + // No layout specified for current page + $page = ['backend_layout' => '', 'uid' => 123]; + $parentPages = [['uid' => 13, 'backend_layout' => 'does-not-matter'], ['uid' => 15, 'backend_layout_next_level' => '-1'], ['uid' => 1, 'backend_layout_next_level' => 'regular']]; + $result = $subject->getLayoutIdentifierForPage($page, array_merge([$page], $parentPages)); + self::assertEquals('none', $result); + } +} diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php index 19b4dd829a1a..a854c8de238b 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php +++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php @@ -47,6 +47,7 @@ use TYPO3\CMS\Core\Localization\LanguageServiceFactory; use TYPO3\CMS\Core\Localization\Locales; use TYPO3\CMS\Core\Log\LogManager; use TYPO3\CMS\Core\Page\DefaultJavaScriptAssetTrait; +use TYPO3\CMS\Core\Page\PageLayoutResolver; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Resource\Exception; use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException; @@ -82,7 +83,6 @@ use TYPO3\CMS\Frontend\ContentObject\Exception\ExceptionHandlerInterface; use TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler; use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; use TYPO3\CMS\Frontend\Imaging\GifBuilder; -use TYPO3\CMS\Frontend\Page\PageLayoutResolver; use TYPO3\CMS\Frontend\Resource\FilePathSanitizer; use TYPO3\CMS\Frontend\Typolink\LinkFactory; use TYPO3\CMS\Frontend\Typolink\LinkResult; @@ -3901,7 +3901,7 @@ class ContentObjectRenderer implements LoggerAwareInterface case 'pagelayout': $pageInformation = $this->getRequest()->getAttribute('frontend.page.information'); $pageLayoutResolver = GeneralUtility::makeInstance(PageLayoutResolver::class); - $retVal = $pageLayoutResolver->getLayoutForPage($pageInformation->getPageRecord(), $pageInformation->getRootLine()); + $retVal = $pageLayoutResolver->getLayoutIdentifierForPage($pageInformation->getPageRecord(), $pageInformation->getRootLine()); break; case 'current': $retVal = $this->data[$this->currentValKey] ?? null; diff --git a/typo3/sysext/frontend/Classes/Page/PageLayoutResolver.php b/typo3/sysext/frontend/Classes/Page/PageLayoutResolver.php deleted file mode 100644 index 2d1165300165..000000000000 --- a/typo3/sysext/frontend/Classes/Page/PageLayoutResolver.php +++ /dev/null @@ -1,73 +0,0 @@ -<?php - -declare(strict_types=1); - -/* - * This file is part of the TYPO3 CMS project. - * - * 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. - * - * For the full copyright and license information, please read the - * LICENSE.txt file that was distributed with this source code. - * - * The TYPO3 project - inspiring people to share! - */ - -namespace TYPO3\CMS\Frontend\Page; - -/** - * Finds the proper layout for a page, using the database fields "backend_layout" - * and "backend_layout_next_level". - * - * The most crucial part is that "backend_layout" is only applied for the CURRENT level, - * whereas backend_layout_next_level. - * - * Used in TypoScript as "getData: pagelayout". - * - * @internal as this might get moved to EXT:core if usages in TYPO3 Backend are helpful as well. - */ -class PageLayoutResolver -{ - /** - * Check if the current page has a value in the DB field "backend_layout" - * if empty, check the root line for "backend_layout_next_level" - * Same as TypoScript: - * field = backend_layout - * ifEmpty.data = levelfield:-2, backend_layout_next_level, slide - * ifEmpty.ifEmpty = default - */ - public function getLayoutForPage(array $page, array $rootLine): string - { - $selectedLayout = $page['backend_layout'] ?? ''; - - // If it is set to "none" - don't use any - if ($selectedLayout === '-1') { - return 'none'; - } - - if ($selectedLayout === '' || $selectedLayout === '0') { - // If it not set check the root-line for a layout on next level and use this - // Remove first element, which is the current page - // See also \TYPO3\CMS\Backend\View\BackendLayoutView::getSelectedCombinedIdentifier() - array_shift($rootLine); - foreach ($rootLine as $rootLinePage) { - $selectedLayout = (string)($rootLinePage['backend_layout_next_level'] ?? ''); - // If layout for "next level" is set to "none" - don't use any and stop searching - if ($selectedLayout === '-1') { - $selectedLayout = 'none'; - break; - } - if ($selectedLayout !== '' && $selectedLayout !== '0') { - // Stop searching if a layout for "next level" is set - break; - } - } - } - if ($selectedLayout === '0' || $selectedLayout === '') { - $selectedLayout = 'default'; - } - return $selectedLayout; - } -} diff --git a/typo3/sysext/frontend/Tests/Unit/Page/PageLayoutResolverTest.php b/typo3/sysext/frontend/Tests/Unit/Page/PageLayoutResolverTest.php deleted file mode 100644 index b34d24079d85..000000000000 --- a/typo3/sysext/frontend/Tests/Unit/Page/PageLayoutResolverTest.php +++ /dev/null @@ -1,87 +0,0 @@ -<?php - -declare(strict_types=1); - -/* - * This file is part of the TYPO3 CMS project. - * - * 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. - * - * For the full copyright and license information, please read the - * LICENSE.txt file that was distributed with this source code. - * - * The TYPO3 project - inspiring people to share! - */ - -namespace TYPO3\CMS\Frontend\Tests\Unit\Page; - -use PHPUnit\Framework\Attributes\Test; -use TYPO3\CMS\Frontend\Page\PageLayoutResolver; -use TYPO3\TestingFramework\Core\Unit\UnitTestCase; - -final class PageLayoutResolverTest extends UnitTestCase -{ - #[Test] - public function getLayoutForPageFetchesSelectedPageDirectly(): void - { - $subject = new PageLayoutResolver(); - $result = $subject->getLayoutForPage(['backend_layout' => '1'], ['does-not-matter']); - self::assertEquals('1', $result); - } - - #[Test] - public function getLayoutForPageTreatsSpecialMinusOneValueAsNone(): void - { - $subject = new PageLayoutResolver(); - $result = $subject->getLayoutForPage(['backend_layout' => '-1'], ['does-not-matter']); - self::assertEquals('none', $result); - } - - #[Test] - public function getLayoutForPageTreatsSpecialValueZeroOrEmptyAsDefaultWithEmptyRootLine(): void - { - $subject = new PageLayoutResolver(); - $parentPages = [['backend_layout' => '']]; - $page = ['backend_layout' => '0']; - $result = $subject->getLayoutForPage($page, array_merge([$page], $parentPages)); - self::assertEquals('default', $result); - $page = ['backend_layout' => '']; - $result = $subject->getLayoutForPage($page, array_merge([$page], $parentPages)); - self::assertEquals('default', $result); - } - - #[Test] - public function getLayoutForPageTreatsSpecialValueZeroOrEmptyAsDefaultWhenNothingGivenInRootLine(): void - { - $subject = new PageLayoutResolver(); - // No layout specified for current page - $page = ['backend_layout' => '']; - $parentPages = [['uid' => 13, 'backend_layout' => 'does-not-matter'], ['uid' => 1, 'backend_layout_next_level' => '0']]; - $result = $subject->getLayoutForPage($page, array_merge([$page], $parentPages)); - self::assertEquals('default', $result); - } - - #[Test] - public function getLayoutForPageFetchesRootLinePagesUpUntilSomethingWasFound(): void - { - $subject = new PageLayoutResolver(); - // No layout specified for current page - $page = ['backend_layout' => '']; - $parentPages = [['uid' => 13, 'backend_layout' => 'does-not-matter', 'backend_layout_next_level' => ''], ['uid' => 1, 'backend_layout_next_level' => 'regular']]; - $result = $subject->getLayoutForPage($page, array_merge([$page], $parentPages)); - self::assertEquals('regular', $result); - } - - #[Test] - public function getLayoutForPageFetchesRootLinePagesUpWhenNoneWasSelectedExplicitly(): void - { - $subject = new PageLayoutResolver(); - // No layout specified for current page - $page = ['backend_layout' => '']; - $parentPages = [['uid' => 13, 'backend_layout' => 'does-not-matter'], ['uid' => 15, 'backend_layout_next_level' => '-1'], ['uid' => 1, 'backend_layout_next_level' => 'regular']]; - $result = $subject->getLayoutForPage($page, array_merge([$page], $parentPages)); - self::assertEquals('none', $result); - } -} diff --git a/typo3/sysext/webhooks/Tests/Functional/WebhookExecutionTest.php b/typo3/sysext/webhooks/Tests/Functional/WebhookExecutionTest.php index 15adafdcad98..4ec5841cd36f 100644 --- a/typo3/sysext/webhooks/Tests/Functional/WebhookExecutionTest.php +++ b/typo3/sysext/webhooks/Tests/Functional/WebhookExecutionTest.php @@ -57,9 +57,6 @@ final class WebhookExecutionTest extends FunctionalTestCase $this->importCSVDataSet(__DIR__ . '/Fixtures/pages.csv'); $this->importCSVDataSet(__DIR__ . '/Fixtures/sys_webhooks.csv'); - $backendUser = $this->setUpBackendUser(1); - $GLOBALS['LANG'] = $this->get(LanguageServiceFactory::class)->createFromUserPreferences($backendUser); - $this->writeSiteConfiguration( 'testing', $this->buildSiteConfiguration(1, '/'), @@ -67,6 +64,8 @@ final class WebhookExecutionTest extends FunctionalTestCase $this->buildDefaultLanguageConfiguration('EN', '/'), ] ); + $backendUser = $this->setUpBackendUser(1); + $GLOBALS['LANG'] = $this->get(LanguageServiceFactory::class)->createFromUserPreferences($backendUser); } private function registerRequestInspector(callable $inspector): void -- GitLab