[TASK] Move page layout resolving into a separate class 06/62306/4
authorBenni Mack <benni@typo3.org>
Tue, 12 Nov 2019 15:50:03 +0000 (16:50 +0100)
committerGeorg Ringer <georg.ringer@gmail.com>
Wed, 13 Nov 2019 08:37:13 +0000 (09:37 +0100)
The functionality "getData = pagelayout" resolves the
selected backend layout for the current page, which is
used in TypoScript quite frequently.

In order to use this in e.g. TypoScript conditions, this
functionality is now extracted into a separate class,
and multiple unit tests are added.

Resolves: #89655
Releases: master
Change-Id: I5f2af5ec692c0dd98712c8916a7dc2fed8836a95
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62306
Tested-by: Susanne Moog <look@susi.dev>
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Susanne Moog <look@susi.dev>
Reviewed-by: Guido Schmechel <guido.schmechel@brandung.de>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
typo3/sysext/frontend/Classes/Page/PageLayoutResolver.php [new file with mode: 0644]
typo3/sysext/frontend/Tests/Unit/Page/PageLayoutResolverTest.php [new file with mode: 0644]

index e6af250..05a0256 100644 (file)
@@ -70,6 +70,7 @@ use TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3\CMS\Frontend\Http\UrlProcessorInterface;
 use TYPO3\CMS\Frontend\Imaging\GifBuilder;
+use TYPO3\CMS\Frontend\Page\PageLayoutResolver;
 use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
 use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
 use TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder;
@@ -4599,39 +4600,8 @@ class ContentObjectRenderer implements LoggerAwareInterface
                         $retVal = $tsfe->page[$key];
                         break;
                     case 'pagelayout':
-                        // 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
-                        //   field = backend_layout
-                        //   ifEmpty.data = levelfield:-2, backend_layout_next_level, slide
-                        //   ifEmpty.ifEmpty = default
-                        $retVal = $tsfe->page['backend_layout'];
-
-                        // If it is set to "none" - don't use any
-                        if ($retVal === '-1') {
-                            $retVal = 'none';
-                        } elseif ($retVal === '' || $retVal === '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()
-                            $rootLine = $tsfe->rootLine;
-                            array_shift($rootLine);
-                            foreach ($rootLine as $rootLinePage) {
-                                $retVal = (string)$rootLinePage['backend_layout_next_level'];
-                                // If layout for "next level" is set to "none" - don't use any and stop searching
-                                if ($retVal === '-1') {
-                                    $retVal = 'none';
-                                    break;
-                                }
-                                if ($retVal !== '' && $retVal !== '0') {
-                                    // Stop searching if a layout for "next level" is set
-                                    break;
-                                }
-                            }
-                        }
-                        if ($retVal === '0' || $retVal === '') {
-                            $retVal = 'default';
-                        }
+                        $retVal = GeneralUtility::makeInstance(PageLayoutResolver::class)
+                            ->getLayoutForPage($tsfe->page, $tsfe->rootLine);
                         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
new file mode 100644 (file)
index 0000000..0fd3b04
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Frontend\Page;
+
+/*
+ * 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!
+ */
+
+/**
+ * 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
+     *
+     * @param array $page
+     * @param array $rootLine
+     * @return string
+     */
+    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
new file mode 100644 (file)
index 0000000..0b25b9c
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Frontend\Tests\Unit\Page;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Frontend\Page\PageLayoutResolver;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class PageLayoutResolverTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function getLayoutForPageFetchesSelectedPageDirectly(): void
+    {
+        $subject = new PageLayoutResolver();
+        $result = $subject->getLayoutForPage(['backend_layout' => '1'], ['does-not-matter']);
+        self::assertEquals($result, '1');
+    }
+
+    /**
+     * @test
+     */
+    public function getLayoutForPageTreatsSpecialMinusOneValueAsNone(): void
+    {
+        $subject = new PageLayoutResolver();
+        $result = $subject->getLayoutForPage(['backend_layout' => '-1'], ['does-not-matter']);
+        self::assertEquals($result, 'none');
+    }
+
+    /**
+     * @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($result, 'default');
+        $page = ['backend_layout' => ''];
+        $result = $subject->getLayoutForPage($page, array_merge([$page], $parentPages));
+        self::assertEquals($result, 'default');
+    }
+
+    /**
+     * @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($result, 'default');
+    }
+
+    /**
+     * @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($result, 'regular');
+    }
+
+    /**
+     * @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($result, 'none');
+    }
+}