From eb4936469039f513420c751f11f9ac50f01a60af Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Thu, 7 Dec 2017 21:06:25 +0100
Subject: [PATCH] [TASK] Move Page Title generation to TSFE

The static method "PageGenerator::generatePageTitle()" only
operated on TSFE and is moved within the main controller,
which can be retriggered multiple times as before.

The same goes for TemplateService->printTitle().

Additionally, the method isAllowedLinkVarValue() is moved to TSFE
as well.

Resolves: #83254
Releases: master
Change-Id: If519963e33a57c21ac5cc575e4395444ab50450d
Reviewed-on: https://review.typo3.org/54973
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
---
 .../Classes/TypoScript/TemplateService.php    |   2 +
 ...254-MovedPageGenerationMethodsIntoTSFE.rst |  39 ++++++
 .../TypoScriptFrontendController.php          | 126 +++++++++++++++++-
 .../frontend/Classes/Page/PageGenerator.php   |  46 ++-----
 .../Tests/Unit/Page/PageGeneratorTest.php     |  11 ++
 .../Php/MethodCallMatcher.php                 |   7 +
 .../Php/MethodCallStaticMatcher.php           |  14 ++
 7 files changed, 202 insertions(+), 43 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Deprecation-83254-MovedPageGenerationMethodsIntoTSFE.rst

diff --git a/typo3/sysext/core/Classes/TypoScript/TemplateService.php b/typo3/sysext/core/Classes/TypoScript/TemplateService.php
index b36bb97ee9d7..47b2de335c01 100644
--- a/typo3/sysext/core/Classes/TypoScript/TemplateService.php
+++ b/typo3/sysext/core/Classes/TypoScript/TemplateService.php
@@ -1407,9 +1407,11 @@ class TemplateService
      * @param string $pageTitleSeparator an alternative to the ": " as the separator between site title and page title
      * @return string The page title on the form "[sitetitle]: [input-title]". Not htmlspecialchar()'ed.
      * @see \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::tempPageCacheContent(), \TYPO3\CMS\Frontend\Page\PageGenerator::renderContentWithHeader()
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10, use $TSFE->generatePageTitle() instead.
      */
     public function printTitle($pageTitle, $noTitle = false, $showTitleFirst = false, $pageTitleSeparator = '')
     {
+        trigger_error('This method will be removed in TYPO3 v10. Title tag generation has been moved into TSFE itself, re-implement this method if you need to, otherwise use TSFE->generatePageTitle() for full usage.', E_USER_DEPRECATED);
         $siteTitle = trim($this->setup['sitetitle']);
         $pageTitle = $noTitle ? '' : $pageTitle;
         if ($showTitleFirst) {
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83254-MovedPageGenerationMethodsIntoTSFE.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83254-MovedPageGenerationMethodsIntoTSFE.rst
new file mode 100644
index 000000000000..31ca386a203b
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83254-MovedPageGenerationMethodsIntoTSFE.rst
@@ -0,0 +1,39 @@
+.. include:: ../../Includes.txt
+
+=============================================================
+Deprecation: #83254 - Moved page generation methods into TSFE
+=============================================================
+
+See :issue:`83254`
+
+Description
+===========
+
+The following methods have been marked as deprecated
+
+* :php:`TYPO3\CMS\Frontend\Page\PageGenerator::isAllowedLinkVarValue()`
+* :php:`TYPO3\CMS\Frontend\Page\PageGenerator::generatePageTitle()`
+* :php:`TYPO3\CMS\Core\TypoScript\TemplateService->printTitle()`
+
+As their functionality has been moved into TypoScriptFrontendController.
+
+
+Impact
+======
+
+Calling any of the PHP methods above will trigger a deprecation warning.
+
+
+Affected Installations
+======================
+
+Any installation with a third-party extension directly accessing these methods.
+
+
+Migration
+=========
+
+For the generation of the page title tag, the method
+:php:`TypoScriptFrontendController->generatePageTitle()` should be used instead.
+
+.. index:: Frontend, PHP-API, FullyScanned
\ No newline at end of file
diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
index 8c33e933d0e1..fb83d984dd90 100644
--- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
+++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
@@ -2816,7 +2816,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 }
                 if (!is_array($value)) {
                     $temp = rawurlencode($value);
-                    if ($test !== '' && !PageGenerator::isAllowedLinkVarValue($temp, $test)) {
+                    if ($test !== '' && !$this->isAllowedLinkVarValue($temp, $test)) {
                         // Error: This value was not allowed for this key
                         continue;
                     }
@@ -2855,6 +2855,49 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         return str_replace($tempCommaReplacementString, ',', $string);
     }
 
+    /**
+     * Checks if the value defined in "config.linkVars" contains an allowed value.
+     * Otherwise, return FALSE which means the value will not be added to any links.
+     *
+     * @param string $haystack The string in which to find $needle
+     * @param string $needle The string to find in $haystack
+     * @return bool Returns TRUE if $needle matches or is found in $haystack
+     */
+    protected function isAllowedLinkVarValue(string $haystack, string $needle): bool
+    {
+        $isAllowed = false;
+        // Integer
+        if ($needle === 'int' || $needle === 'integer') {
+            if (MathUtility::canBeInterpretedAsInteger($haystack)) {
+                $isAllowed = true;
+            }
+        } elseif (preg_match('/^\\/.+\\/[imsxeADSUXu]*$/', $needle)) {
+            // Regular expression, only "//" is allowed as delimiter
+            if (@preg_match($needle, $haystack)) {
+                $isAllowed = true;
+            }
+        } elseif (strstr($needle, '-')) {
+            // Range
+            if (MathUtility::canBeInterpretedAsInteger($haystack)) {
+                $range = explode('-', $needle);
+                if ($range[0] <= $haystack && $range[1] >= $haystack) {
+                    $isAllowed = true;
+                }
+            }
+        } elseif (strstr($needle, '|')) {
+            // List
+            // Trim the input
+            $haystack = str_replace(' ', '', $haystack);
+            if (strstr('|' . $needle . '|', '|' . $haystack . '|')) {
+                $isAllowed = true;
+            }
+        } elseif ((string)$needle === (string)$haystack) {
+            // String comparison
+            $isAllowed = true;
+        }
+        return $isAllowed;
+    }
+
     /**
      * Redirect to target page if the current page is an overlaid mountpoint.
      *
@@ -2931,7 +2974,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         $this->tempContent = false;
         if (!$this->no_cache) {
             $seconds = 30;
-            $title = htmlspecialchars($this->tmpl->printTitle($this->page['title']));
+            $title = htmlspecialchars($this->printTitle($this->page['title']));
             $request_uri = htmlspecialchars(GeneralUtility::getIndpEnv('REQUEST_URI'));
             $stdMsg = '
 		<strong>Page is being generated.</strong><br />
@@ -3298,11 +3341,82 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     }
 
     /**
-     * Generate the page title again as TSFE->altPageTitle might have been modified by an inc script
+     * Generate the page title, can be called multiple times,
+     * as $this->altPageTitle might have been modified by an uncached plugin etc.
+     *
+     * @return string the generated page title
+     */
+    public function generatePageTitle(): string
+    {
+        $pageTitleSeparator = '';
+
+        // Check for a custom pageTitleSeparator, and perform stdWrap on it
+        if (isset($this->config['config']['pageTitleSeparator']) && $this->config['config']['pageTitleSeparator'] !== '') {
+            $pageTitleSeparator = $this->config['config']['pageTitleSeparator'];
+
+            if (isset($this->config['config']['pageTitleSeparator.']) && is_array($this->config['config']['pageTitleSeparator.'])) {
+                $pageTitleSeparator = $this->cObj->stdWrap($pageTitleSeparator, $this->config['config']['pageTitleSeparator.']);
+            } else {
+                $pageTitleSeparator .= ' ';
+            }
+        }
+
+        $pageTitle = $this->altPageTitle ?: $this->page['title'] ?? '';
+        $titleTagContent = $this->printTitle(
+            $pageTitle,
+            (bool)$this->config['config']['noPageTitle'],
+            (bool)$this->config['config']['pageTitleFirst'],
+            $pageTitleSeparator
+        );
+        if ($this->config['config']['titleTagFunction']) {
+            $titleTagContent = $this->cObj->callUserFunction(
+                $this->config['config']['titleTagFunction'],
+                [],
+                $titleTagContent
+            );
+        }
+        // stdWrap around the title tag
+        if (isset($this->config['config']['pageTitle.']) && is_array($this->config['config']['pageTitle.'])) {
+            $titleTagContent = $this->cObj->stdWrap($titleTagContent, $this->config['config']['pageTitle.']);
+        }
+
+        // config.noPageTitle = 2 - means do not render the page title
+        if ($this->config['config']['noPageTitle'] === 2) {
+            $titleTagContent = '';
+        }
+        if ($titleTagContent !== '') {
+            $this->pageRenderer->setTitle($titleTagContent);
+        }
+        return (string)$titleTagContent;
+    }
+
+    /**
+     * Compiles the content for the page <title> tag.
+     *
+     * @param string $pageTitle The input title string, typically the "title" field of a page's record.
+     * @param bool $noTitle If set, then only the site title is outputted (from $this->setup['sitetitle'])
+     * @param bool $showTitleFirst If set, then "sitetitle" and $title is swapped
+     * @param string $pageTitleSeparator an alternative to the ": " as the separator between site title and page title
+     * @return string The page title on the form "[sitetitle]: [input-title]". Not htmlspecialchar()'ed.
+     * @see tempPageCacheContent(), generatePageTitle()
      */
-    protected function regeneratePageTitle()
+    protected function printTitle(string $pageTitle, bool $noTitle = false, bool $showTitleFirst = false, string $pageTitleSeparator = ''): string
     {
-        PageGenerator::generatePageTitle();
+        $siteTitle = trim($this->tmpl->setup['sitetitle'] ?? '');
+        $pageTitle = $noTitle ? '' : $pageTitle;
+        if ($showTitleFirst) {
+            $temp = $siteTitle;
+            $siteTitle = $pageTitle;
+            $pageTitle = $temp;
+        }
+        // only show a separator if there are both site title and page title
+        if ($pageTitle === '' || $siteTitle === '') {
+            $pageTitleSeparator = '';
+        } elseif (empty($pageTitleSeparator)) {
+            // use the default separator if non given
+            $pageTitleSeparator = ': ';
+        }
+        return $siteTitle . $pageTitleSeparator . $pageTitle;
     }
 
     /**
@@ -3329,7 +3443,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         $this->recursivelyReplaceIntPlaceholdersInContent();
         $this->getTimeTracker()->push('Substitute header section');
         $this->INTincScript_loadJSCode();
-        $this->regeneratePageTitle();
+        $this->generatePageTitle();
 
         $this->content = str_replace(
             [
diff --git a/typo3/sysext/frontend/Classes/Page/PageGenerator.php b/typo3/sysext/frontend/Classes/Page/PageGenerator.php
index 2bb76ab1e791..9c9fd538b653 100644
--- a/typo3/sysext/frontend/Classes/Page/PageGenerator.php
+++ b/typo3/sysext/frontend/Classes/Page/PageGenerator.php
@@ -34,6 +34,7 @@ class PageGenerator
     /**
      * Do not render title tag
      * Typoscript setting: [config][noPageTitle]
+     * @deprecated will not be used anymore, and will be removed in TYPO3 v10.
      */
     const NO_PAGE_TITLE = 2;
 
@@ -517,7 +518,7 @@ class PageGenerator
         if (is_array($tsfe->pSetup['footerData.'])) {
             $pageRenderer->addFooterData($tsfe->cObj->cObjGet($tsfe->pSetup['footerData.'], 'footerData.'));
         }
-        static::generatePageTitle();
+        $tsfe->generatePageTitle();
 
         static::generateMetaTagHtml(
             isset($tsfe->pSetup['meta.']) ? $tsfe->pSetup['meta.'] : [],
@@ -796,9 +797,12 @@ class PageGenerator
      * @param string $haystack The string in which to find $needle
      * @param string $needle The string to find in $haystack
      * @return bool Returns TRUE if $needle matches or is found in $haystack
+     *
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10, is now called within TSFE itself, if needed outside the regular calculations, reimplement the method on your own.
      */
     public static function isAllowedLinkVarValue($haystack, $needle)
     {
+        trigger_error('The method will be removed in TYPO3 v10.0, if needed outside of linkVar calculation, re-implement the method in your own extension', E_USER_DEPRECATED);
         $OK = false;
         // Integer
         if ($needle === 'int' || $needle === 'integer') {
@@ -837,45 +841,13 @@ class PageGenerator
      * Takes the settings [config][noPageTitle], [config][pageTitleFirst], [config][titleTagFunction]
      * [config][pageTitleSeparator] and [config][noPageTitle] into account.
      * Furthermore $GLOBALS[TSFE]->altPageTitle is observed.
+     *
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, as TSFE->generatePageTitle() should be used instead.
      */
     public static function generatePageTitle()
     {
-        /** @var TypoScriptFrontendController $tsfe */
-        $tsfe = $GLOBALS['TSFE'];
-
-        $pageTitleSeparator = '';
-
-        // check for a custom pageTitleSeparator, and perform stdWrap on it
-        if (isset($tsfe->config['config']['pageTitleSeparator']) && $tsfe->config['config']['pageTitleSeparator'] !== '') {
-            $pageTitleSeparator = $tsfe->config['config']['pageTitleSeparator'];
-
-            if (isset($tsfe->config['config']['pageTitleSeparator.']) && is_array($tsfe->config['config']['pageTitleSeparator.'])) {
-                $pageTitleSeparator = $tsfe->cObj->stdWrap($pageTitleSeparator, $tsfe->config['config']['pageTitleSeparator.']);
-            } else {
-                $pageTitleSeparator .= ' ';
-            }
-        }
-
-        $titleTagContent = $tsfe->tmpl->printTitle(
-            $tsfe->altPageTitle ?: $tsfe->page['title'],
-            $tsfe->config['config']['noPageTitle'],
-            $tsfe->config['config']['pageTitleFirst'],
-            $pageTitleSeparator
-        );
-        if ($tsfe->config['config']['titleTagFunction']) {
-            $titleTagContent = $tsfe->cObj->callUserFunction(
-                $tsfe->config['config']['titleTagFunction'],
-                [],
-                $titleTagContent
-            );
-        }
-        // stdWrap around the title tag
-        if (isset($tsfe->config['config']['pageTitle.']) && is_array($tsfe->config['config']['pageTitle.'])) {
-            $titleTagContent = $tsfe->cObj->stdWrap($titleTagContent, $tsfe->config['config']['pageTitle.']);
-        }
-        if ($titleTagContent !== '' && (int)$tsfe->config['config']['noPageTitle'] !== self::NO_PAGE_TITLE) {
-            static::getPageRenderer()->setTitle($titleTagContent);
-        }
+        trigger_error('This method will be removed in TYPO3 v10.0. Use $TSFE->generatePageTitle() instead.', E_USER_DEPRECATED);
+        $GLOBALS['TSFE']->generatePageTitle();
     }
 
     /**
diff --git a/typo3/sysext/frontend/Tests/Unit/Page/PageGeneratorTest.php b/typo3/sysext/frontend/Tests/Unit/Page/PageGeneratorTest.php
index 378b11d87657..9d3605d69112 100644
--- a/typo3/sysext/frontend/Tests/Unit/Page/PageGeneratorTest.php
+++ b/typo3/sysext/frontend/Tests/Unit/Page/PageGeneratorTest.php
@@ -179,6 +179,8 @@ class PageGeneratorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $cObj->stdWrap(Argument::cetera())->willReturn($stdWrapResult);
         $tmpl = $this->prophesize(TemplateService::class);
         $tsfe = $this->prophesize(TypoScriptFrontendController::class);
+        $tsfe->generatePageTitle()->willReturn('');
+        $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
         $tsfe->cObj = $cObj->reveal();
         $tsfe->tmpl = $tmpl->reveal();
         $tsfe->page = [
@@ -212,8 +214,11 @@ class PageGeneratorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $cObj->stdWrap(Argument::cetera())->willReturn($stdWrapResult);
         $tmpl = $this->prophesize(TemplateService::class);
         $tsfe = $this->prophesize(TypoScriptFrontendController::class);
+        $tsfe->generatePageTitle()->willReturn('');
+        $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
         $tsfe->cObj = $cObj->reveal();
         $tsfe->tmpl = $tmpl->reveal();
+        $tsfe->config['config'] = [];
         $tsfe->page = [
             'title' => ''
         ];
@@ -246,8 +251,11 @@ class PageGeneratorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $cObj->stdWrap(Argument::cetera())->willReturn($stdWrapResult);
         $tmpl = $this->prophesize(TemplateService::class);
         $tsfe = $this->prophesize(TypoScriptFrontendController::class);
+        $tsfe->generatePageTitle()->willReturn('');
+        $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
         $tsfe->cObj = $cObj->reveal();
         $tsfe->tmpl = $tmpl->reveal();
+        $tsfe->config['config'] = [];
         $tsfe->page = [
             'title' => ''
         ];
@@ -334,8 +342,11 @@ class PageGeneratorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $cObj->stdWrap(Argument::cetera())->willReturn($stdWrapResult);
         $tmpl = $this->prophesize(TemplateService::class);
         $tsfe = $this->prophesize(TypoScriptFrontendController::class);
+        $tsfe->generatePageTitle()->willReturn('');
+        $tsfe->INTincScript_loadJSCode()->shouldBeCalled();
         $tsfe->cObj = $cObj->reveal();
         $tsfe->tmpl = $tmpl->reveal();
+        $tsfe->config['config'] = [];
         $tsfe->page = [
             'title' => ''
         ];
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
index 8e5988fc5df5..16eee6442340 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
@@ -1528,4 +1528,11 @@ return [
             'Breaking-83256-RemovedLockFilePathFunctionality.rst',
         ],
     ],
+    'TYPO3\CMS\Core\TypoScript\TemplateService->printTitle' => [
+        'numberOfMandatoryArguments' => 0,
+        'maximumNumberOfArguments' => 0,
+        'restFiles' => [
+            'Deprecation-83254-MovedPageGenerationMethodsIntoTSFE.rst',
+        ],
+    ],
 ];
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
index 49b793bf0972..ed9ddba20425 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
@@ -533,4 +533,18 @@ return [
             'Deprecation-83118-DeleteClauseMethods.rst',
         ],
     ],
+    'TYPO3\CMS\Frontend\Page\PageGenerator::generatePageTitle' => [
+        'numberOfMandatoryArguments' => 0,
+        'maximumNumberOfArguments' => 0,
+        'restFiles' => [
+            'Deprecation-83254-MovedPageGenerationMethodsIntoTSFE.rst',
+        ],
+    ],
+    'TYPO3\CMS\Frontend\Page\PageGenerator::isAllowedLinkVarValue' => [
+        'numberOfMandatoryArguments' => 0,
+        'maximumNumberOfArguments' => 0,
+        'restFiles' => [
+            'Deprecation-83254-MovedPageGenerationMethodsIntoTSFE.rst',
+        ],
+    ],
 ];
-- 
GitLab