[!!!][TASK] Remove dependencies of TSFE 55/61155/20
authorBenni Mack <benni@typo3.org>
Wed, 26 Jun 2019 21:04:29 +0000 (23:04 +0200)
committerAndreas Fernandez <a.fernandez@scripting-base.de>
Sat, 13 Jul 2019 13:43:39 +0000 (15:43 +0200)
This patch re-arranges the TYPO3 Core internally used
middlewares for lifting off the weight of $GLOBALS['TSFE']
as Site Handling already introduced a lot of functionality
which can now be utilized further.

For this reason, the Frontend Rendering chain has
been adapted.

* If there is a "Site" + "Language" resolved, this information can
be used directly, as there are no dependencies currently.

* Frontend + Backend User Authentication works regardless
of TSFE, Frontend User is added to the Request object as
attribute to be added to TSFE later-on.

* Resolving the Page ("slug") and mapping them to Page
Arguments (URL parts + GET parameters) as well as validation
against cHash is fully decoupled from TSFE.

After that, TSFE is instantiated, which now gets all resolved
objects injected.

TSFE now only resolves the rootline against the proper permissions
(auth) and validates the final page. Once done, TypoScript is
compiled / cached.

TSFE still contains the rootline, TypoScript, and the information
about which non-cacheables are there.

RequestHandler creates or fetches cached content, but currently piped
through TSFE. This should be simplified further later-on.

Resolves: #88717
Releases: master
Change-Id: I12807455fd8b01493b2da45cf73a5c532b108cbe
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/61155
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
27 files changed:
composer.json
composer.lock
typo3/sysext/adminpanel/Configuration/RequestMiddlewares.php
typo3/sysext/core/Classes/Http/Uri.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-88540-ChangedRequestWorkflowForFrontendRequests.rst
typo3/sysext/core/composer.json
typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php
typo3/sysext/fluid/Tests/Functional/ViewHelpers/TypolinkViewHelperTest.php
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php
typo3/sysext/frontend/Classes/Middleware/FrontendUserAuthenticator.php
typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php
typo3/sysext/frontend/Classes/Middleware/PageResolver.php
typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php
typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php
typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
typo3/sysext/frontend/Classes/Typolink/AbstractTypolinkBuilder.php
typo3/sysext/frontend/Configuration/RequestMiddlewares.php
typo3/sysext/frontend/Tests/Functional/Configuration/TypoScript/ConditionMatching/ConditionMatcherTest.php
typo3/sysext/frontend/Tests/Functional/ContentObject/ContentObjectRendererTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php
typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php
typo3/sysext/redirects/Classes/Service/RedirectService.php
typo3/sysext/redirects/Configuration/RequestMiddlewares.php
typo3/sysext/seo/Tests/Functional/Canonical/CanonicalGeneratorTest.php
typo3/sysext/workspaces/Classes/Middleware/WorkspacePreview.php
typo3/sysext/workspaces/Configuration/RequestMiddlewares.php

index 7790276..bcb6758 100644 (file)
@@ -75,7 +75,7 @@
                "friendsofphp/php-cs-fixer": "^2.12.2",
                "phpspec/prophecy": "^1.7.5",
                "typo3/cms-styleguide": "~10.0.2",
-               "typo3/testing-framework": "~5.0.10"
+               "typo3/testing-framework": "~5.0.11"
        },
        "suggest": {
                "ext-gd": "GDlib/Freetype is required for building images with text (GIFBUILDER) and can also be used to scale images",
index 151f892..cec5b8a 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "a46e7f1e82efeff6223502b47a7cf998",
+    "content-hash": "112098a7382ac5f79a2b4dfe5fa616a9",
     "packages": [
         {
             "name": "cogpowered/finediff",
         },
         {
             "name": "typo3/testing-framework",
-            "version": "5.0.10",
+            "version": "5.0.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/TYPO3/testing-framework.git",
-                "reference": "63a0f97ee27607b93301dc6b322927fbe98185c5"
+                "reference": "ac55b0e2d6e2d4369ece187cc31b58457d5b7122"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/63a0f97ee27607b93301dc6b322927fbe98185c5",
-                "reference": "63a0f97ee27607b93301dc6b322927fbe98185c5",
+                "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/ac55b0e2d6e2d4369ece187cc31b58457d5b7122",
+                "reference": "ac55b0e2d6e2d4369ece187cc31b58457d5b7122",
                 "shasum": ""
             },
             "require": {
                 "tests",
                 "typo3"
             ],
-            "time": "2019-06-18T07:52:50+00:00"
+            "time": "2019-07-13T12:16:05+00:00"
         }
     ],
     "aliases": [],
index 0b08aa5..4ca5dcd 100644 (file)
@@ -14,10 +14,10 @@ return [
             'target' => \TYPO3\CMS\Adminpanel\Middleware\AdminPanelInitiator::class,
             'before' => [
                 'typo3/cms-frontend/prepare-tsfe-rendering',
-                'typo3/cms-frontend/page-resolver'
+                'typo3/cms-frontend/tsfe',
+                'typo3/cms-frontend/page-resolver',
             ],
             'after' => [
-                'typo3/cms-frontend/tsfe',
                 'typo3/cms-frontend/authentication',
                 'typo3/cms-frontend/backend-user-authentication',
             ]
@@ -29,7 +29,7 @@ return [
                 'typo3/cms-frontend/backend-user-authentication',
             ],
             'before' => [
-                'typo3/cms-frontend/site'
+                'typo3/cms-frontend/tsfe'
             ]
         ],
         'typo3/cms-adminpanel/data-persister' => [
index 7f624e2..d7c31b2 100644 (file)
@@ -120,7 +120,7 @@ class Uri implements UriInterface
         $uriParts = parse_url($uri);
 
         if ($uriParts === false) {
-            throw new \InvalidArgumentException('The parsedUri string appears to be malformed', 1436717322);
+            throw new \InvalidArgumentException('The parsedUri "' . $uri . '" appears to be malformed', 1436717322);
         }
 
         if (isset($uriParts['scheme'])) {
index d655d45..94abba3 100644 (file)
@@ -10,10 +10,11 @@ Description
 ===========
 
 The "Frontend Request Workflow" is the PHP code responsible for
-setting up various functionality. This includes Login/Permission Check, resolving
+setting up various functionality when the TYPO3 Frontend (= rendering of the website)
+is booted and the content is built. This includes Login/Permission Check, resolving
 the current site + language, and checking the page + rootline, then
 parsing TypoScript, which will then lead to building content (or taken from
-cache), until the actual output.
+cache), until the actual output is returned.
 
 Since TYPO3 v9, this is all built via PSR-15 middlewares, the PSR-15 Request Handler,
 and the global TypoScriptFrontendController (TSFE).
@@ -24,11 +25,51 @@ other / extended functionality.
 
 The following changes have been made:
 
-- Storing session data from a Frontend User Session / Anonymous session is now
-triggered within the Frontend User ("frontend-user-authenticator") Middleware,
-at a later point. Before this was part of the RequestHandler logic after content
-was put together. This was due to legacy reasons of the previous hook execution order.
-
+- Storing session data from a Frontend User Session / Anonymous session is now triggered within the Frontend User
+("frontend-user-authenticator") Middleware, at a later point - once the page was generated. Up until TYPO3 v9, this
+was part of the RequestHandler logic right after content was put together. This was due to legacy reasons of the
+previous hook execution order.
+
+- Resolving the actual site - that is the site configuration plus the language - now happens before Frontend
+and Backend User Authentication. This is important to understand to be able to define further settings within
+Site Handling configuration in the future. Site and Site Language Resolving is now 100% independent of any permission
+settings. Evaluating if a language is active is evaluated separately.
+
+- Backend User Authentication ("$BE_USER") is now started before Frontend User Authentication ("fe_user"), previously
+this was the other way around. Frontend Users are now stored in the request object via the "frontend.user" attribute,
+instead of `$TSFE->fe_user`, until `$TSFE` is instantiated.
+
+- Once all site + permission/authentication functionality has been set up, Routing now tries to detect
+the target page ID and the URL parameters ("PageResolver" middleware) and evaluates the result, so-called
+"Page Arguments" directly afterwards ("PageArgumentValidator" middleware). This effectively validates the cHash
+logic.
+
+- All of the mentioned parts above do not depend on TSFE anymore. In fact, they are 100% independent of
+any TSFE-related code. TSFE is instantiated after all site resolving, authentication, page resolving and argument
+validation is done.
+
+The new request workflow looks like this (simplified):
+
+1. Evaluation of Normalized Parameters (a.k.a. getIndpEnv) & Evaluation of "Maintenance Mode" functionality
+2. Handling registered eID scripts depending on GET parameter `eID`
+3. Resolving Site configuration and Language from URL if possible
+4. Resolving logged-in Backend User Authentication for previewing hidden pages or languages
+5. Authentication of Website Users ("Frontend Users")
+6. Executing various static routes and redirct functionality
+7. Resolving Target Page ID and URL parameters based on Routing, Validation of Page Arguments based on "cHash"
+8. Setting up global $TSFE object, injecting previously resolved settings into TSFE.
+9. Resolving the Rootline for the page
+10. Parsing and Evaluation of TypoScript Instructions to render the page content
+11. Build the content (cached / uncached)
+12. Return the Response (PSR-7) to the base application and output headers + content.
+
+In addition, TypoScriptFrontendController now expects the following constructor arguments:
+
+1. Context API object (previously a copy of $TYPO3_CONF_VARS, until TYPO3 v8, then, unused)
+2. SiteInterface object (previously the Page ID)
+3. SiteLanguage object (previously the Page Type)
+4. PageArguments object (previously the no_cache GET parameter)
+5. FrontendUserAuthentication object (previously the cHash parameter)
 
 Impact
 ======
@@ -50,11 +91,18 @@ Any hooks from third party extensions that run
 
 and depend on the frontend session data being written.
 
+Any TYPO3 extensions using middlewares in the frontend.
 
 Migration
 =========
 
-Consider using a PSR-15 middleware instead of using a hook, or explicitly
-call "storeSessionData" within the PHP hook if necessary.
+Consider using a PSR-15 middleware instead of using a hook, or explicitly call "storeSessionData" within
+the PHP hook if necessary.
+
+If an existing middleware was used, ensure that it's loaded in TYPO3 v10 at the proper location, as the
+`typo3-cms/frontend/tsfe` middleware is loaded at a very late point.
+
+Ensure to use proper objects for the constructor arguments on `TypoScriptFrontendController` when instantiating
+the object on your own.
 
 .. index:: Frontend, PHP-API, NotScanned
index b38736a..e34079e 100644 (file)
@@ -54,7 +54,7 @@
                "friendsofphp/php-cs-fixer": "^2.12.2",
                "phpspec/prophecy": "^1.7.5",
                "typo3/cms-styleguide": "~10.0.2",
-               "typo3/testing-framework": "~5.0.10"
+               "typo3/testing-framework": "~5.0.11"
        },
        "suggest": {
                "ext-fileinfo": "Used for proper file type detection in the file abstraction layer",
index b7e0f62..eae14dc 100644 (file)
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Fluid\ViewHelpers;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
 use TYPO3\CMS\Extbase\Object\ObjectManager;
@@ -184,7 +185,7 @@ class CObjectViewHelper extends AbstractViewHelper
     {
         return GeneralUtility::makeInstance(
             ContentObjectRenderer::class,
-            $GLOBALS['TSFE'] ?? GeneralUtility::makeInstance(TypoScriptFrontendController::class, null, 0, 0)
+            $GLOBALS['TSFE'] ?? GeneralUtility::makeInstance(TypoScriptFrontendController::class, GeneralUtility::makeInstance(Context::class))
         );
     }
 
index 482ae66..981094b 100644 (file)
@@ -16,9 +16,11 @@ namespace TYPO3\CMS\Fluid\Tests\Functional\ViewHelpers;
  */
 
 use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
 
-class TypolinkViewHelperTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
+class TypolinkViewHelperTest extends FunctionalTestCase
 {
     use SiteBasedTestTrait;
 
@@ -64,6 +66,9 @@ class TypolinkViewHelperTest extends \TYPO3\TestingFramework\Core\Functional\Fun
             'foo' => 'bar',
             'temp' => 'test',
         ];
+        $_SERVER['HTTP_HOST'] = 'example.com';
+        $_SERVER['REQUEST_URI'] = '/en/';
+        GeneralUtility::flushInternalRuntimeCaches();
     }
 
     /**
index e949877..c264045 100644 (file)
@@ -40,6 +40,7 @@ use TYPO3\CMS\Core\Error\Http\ServiceUnavailableException;
 use TYPO3\CMS\Core\Error\Http\ShortcutTargetPageNotFoundException;
 use TYPO3\CMS\Core\Exception\Page\RootLineException;
 use TYPO3\CMS\Core\Http\ImmediateResponseException;
+use TYPO3\CMS\Core\Http\ServerRequestFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Localization\Locales;
 use TYPO3\CMS\Core\Locking\Exception\LockAcquireWouldBlockException;
@@ -50,6 +51,7 @@ use TYPO3\CMS\Core\PageTitle\PageTitleProviderManager;
 use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Routing\PageArguments;
 use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\Entity\SiteInterface;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
@@ -106,6 +108,16 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     public $type = '';
 
     /**
+     * @var Site
+     */
+    protected $site;
+
+    /**
+     * @var SiteLanguage
+     */
+    protected $language;
+
+    /**
      * The submitted cHash
      * @var string
      * @internal
@@ -653,26 +665,77 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      * The processing of these variables goes on later in this class.
      * Also sets a unique string (->uniqueString) for this script instance; A md5 hash of the microtime()
      *
-     * @param array $_ unused, previously defined to set TYPO3_CONF_VARS
-     * @param mixed $id The value of GeneralUtility::_GP('id')
-     * @param int $type The value of GeneralUtility::_GP('type')
-     * @param bool|string $_1 unused, previously the value of GeneralUtility::_GP('no_cache')
-     * @param string $cHash The value of GeneralUtility::_GP('cHash')
-     * @param string $_2 previously was used to define the jumpURL
-     * @param string $MP The value of GeneralUtility::_GP('MP')
-     */
-    public function __construct($_ = null, $id, $type, $_1 = null, $cHash = '', $_2 = null, $MP = '')
-    {
-        // Setting some variables:
-        $this->id = $id;
-        $this->type = $type;
-        $this->cHash = $cHash;
-        $this->MP = $GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids'] ? (string)$MP : '';
+     * @param Context|array $context the Context object to work on, previously defined to set TYPO3_CONF_VARS
+     * @param mixed|SiteInterface $siteOrId The resolved site to work on, previously this was the value of GeneralUtility::_GP('id')
+     * @param int|SiteLanguage $siteLanguageOrType The resolved language to work on, previously the value of GeneralUtility::_GP('type')
+     * @param bool|string|PageArguments $pageArguments The PageArguments object containing ID, type and GET parameters, previously unused or the value of GeneralUtility::_GP('no_cache')
+     * @param string|FrontendUserAuthentication $cHashOrFrontendUser FrontendUserAuthentication object, previously the value of GeneralUtility::_GP('cHash'), use the PageArguments object instead, will be removed in TYPO3 v11.0
+     * @param string $_2 previously was used to define the jumpURL, use the PageArguments object instead, will be removed in TYPO3 v11.0
+     * @param string $MP The value of GeneralUtility::_GP('MP'), use the PageArguments object instead, will be removed in TYPO3 v11.0
+     */
+    public function __construct($context = null, $siteOrId = null, $siteLanguageOrType = null, $pageArguments = null, $cHashOrFrontendUser = null, $_2 = null, $MP = null)
+    {
+        if ($context instanceof Context) {
+            $this->context = $context;
+        } else {
+            // Use the global context for now
+            trigger_error('TypoScriptFrontendController requires a context object as first constructor argument in TYPO3 v11.0, now falling back to the global Context. This fallback layer will be removed in TYPO3 v11.0', E_USER_DEPRECATED);
+            $this->context = GeneralUtility::makeInstance(Context::class);
+        }
+        $request = $GLOBALS['TYPO3_REQUEST'] ?? ServerRequestFactory::fromGlobals();
+        if ($siteOrId instanceof SiteInterface) {
+            $this->site = $siteOrId;
+        } else {
+            trigger_error('TypoScriptFrontendController should evaluate the parameter "id" by the PageArguments object, not by a separate constructor argument. This functionality will be removed in TYPO3 v11.0', E_USER_DEPRECATED);
+            $this->id = $siteOrId;
+            if ($request->getAttribute('site') instanceof SiteInterface) {
+                $this->site = $request->getAttribute('site');
+            } else {
+                throw new \InvalidArgumentException('TypoScriptFrontendController must be constructed with a valid Site object or a resolved site in the current request as fallback. None given.', 1561583122);
+            }
+        }
+        if ($siteLanguageOrType instanceof SiteLanguage) {
+            $this->language = $siteLanguageOrType;
+        } else {
+            $this->type = $siteLanguageOrType;
+            if ($request->getAttribute('language') instanceof SiteLanguage) {
+                $this->language = $request->getAttribute('language');
+            } else {
+                throw new \InvalidArgumentException('TypoScriptFrontendController must be constructed with a valid SiteLanguage object or a resolved site in the current request as fallback. None given.', 1561583127);
+            }
+        }
+
+        if (!$pageArguments instanceof PageArguments) {
+            $pageArguments = $request->getAttribute('routing');
+            if (!$pageArguments instanceof PageArguments) {
+                trigger_error('TypoScriptFrontendController must be constructed with a valid PageArguments object or a resolved page argument in the current request as fallback. None given.', E_USER_DEPRECATED);
+                $queryParams = $request->getQueryParams();
+                $pageId = $queryParams['id'] ?? $request->getParsedBody()['id'] ?? 0;
+                $pageType = $queryParams['type'] ?? $request->getParsedBody()['type'] ?? 0;
+                $pageArguments = new PageArguments((int)$pageId, (string)$pageType, [], $queryParams);
+            }
+        }
+        $this->setPageArguments($pageArguments);
+
+        if ($cHashOrFrontendUser !== null) {
+            if ($cHashOrFrontendUser instanceof FrontendUserAuthentication) {
+                $this->fe_user = $cHashOrFrontendUser;
+            } else {
+                trigger_error('TypoScriptFrontendController should evaluate the parameter "cHash" by the PageArguments object, not by a separate constructor argument. This functionality will be removed in TYPO3 v11.0', E_USER_DEPRECATED);
+                $this->cHash = $cHashOrFrontendUser;
+            }
+        }
+        if ($MP !== null) {
+            trigger_error('TypoScriptFrontendController should evaluate the MountPoint Parameter "MP" by the PageArguments object, not by a separate constructor argument. This functionality will be removed in TYPO3 v11.0', E_USER_DEPRECATED);
+            if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
+                $this->MP = (string)$MP;
+            }
+        }
+
+        $this->domainStartPage = $this->site->getRootPageId();
         $this->uniqueString = md5(microtime());
         $this->initPageRenderer();
         $this->initCaches();
-        // Use the global context for now
-        $this->context = GeneralUtility::makeInstance(Context::class);
     }
 
     /**
@@ -916,11 +979,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             ->setMaxResults(1);
 
         // $this->id always points to the ID of the default language page, so we check
-        // currentSiteLanguage to determine if we need to fetch a translation
-        if ($this->getCurrentSiteLanguage() instanceof SiteLanguage && $this->getCurrentSiteLanguage()->getLanguageId() > 0) {
+        // the current site language to determine if we need to fetch a translation
+        if ($this->language->getLanguageId() > 0) {
             $queryBuilder->andWhere(
                 $queryBuilder->expr()->eq('l10n_parent', $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)),
-                $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter($this->getCurrentSiteLanguage()->getLanguageId(), \PDO::PARAM_INT))
+                $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter($this->language->getLanguageId(), \PDO::PARAM_INT))
             );
         } else {
             $queryBuilder->andWhere(
@@ -1563,11 +1626,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         $this->getTimeTracker()->setTSlogMessage('TSFE->reqCHash(): No &cHash parameter was sent for GET vars though required so caching is disabled', 2);
     }
 
-    /**
-     * @param PageArguments $pageArguments
-     * @internal
-     */
-    public function setPageArguments(PageArguments $pageArguments)
+    protected function setPageArguments(PageArguments $pageArguments): void
     {
         $this->pageArguments = $pageArguments;
         $queryParams = $pageArguments->getDynamicArguments();
@@ -1580,6 +1639,12 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         } else {
             $this->cHash_array = [];
         }
+        $this->id = $pageArguments->getPageId();
+        $this->type = $pageArguments->getPageType() ?: 0;
+        $this->cHash = $pageArguments->getArguments()['cHash'] ?? '';
+        if ($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
+            $this->MP = (string)($pageArguments->getArguments()['MP'] ?? '');
+        }
     }
 
     /**
@@ -1779,7 +1844,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     {
         // Ensure the language base is used for the hash base calculation as well, otherwise TypoScript and page-related rendering
         // is not cached properly as we don't have any language-specific conditions anymore
-        $siteBase = (string)$this->getCurrentSiteLanguage()->getBase();
+        $siteBase = (string)$this->language->getBase();
 
         // Fetch the list of user groups
         /** @var UserAspect $userAspect */
@@ -1947,14 +2012,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             GeneralUtility::callUserFunction($_funcRef, $_params, $this);
         }
 
-        $siteLanguage = $this->getCurrentSiteLanguage();
-        if (!$siteLanguage) {
-            throw new PageNotFoundException('Frontend cannot be displayed, as there is no language available', 1557924417);
-        }
-
         // Initialize charset settings etc.
-        $languageKey = $siteLanguage->getTypo3Language();
-        $this->setOutputLanguage($languageKey);
+        $this->setOutputLanguage($this->language->getTypo3Language());
 
         // Rendering charset of HTML page.
         if (isset($this->config['config']['metaCharset']) && $this->config['config']['metaCharset'] !== 'utf-8') {
@@ -1962,7 +2021,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         }
 
         // Get values from site language
-        $languageAspect = LanguageAspectFactory::createFromSiteLanguage($siteLanguage);
+        $languageAspect = LanguageAspectFactory::createFromSiteLanguage($this->language);
 
         $languageId = $languageAspect->getId();
         $languageContentId = $languageAspect->getContentId();
@@ -2070,10 +2129,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             $this->updateRootLinesWithTranslations();
         }
 
-        // Finding the ISO code for the currently selected language
-        // fetched by the sys_language record when not fetching content from the default language
         // @deprecated - can be removed in TYPO3 v11.0
-        $this->sys_language_isocode = $siteLanguage->getTwoLetterIsoCode();
+        $this->sys_language_isocode = $this->language->getTwoLetterIsoCode();
 
         $_params = [];
         foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['settingLanguage_postProcess'] ?? [] as $_funcRef) {
@@ -2101,9 +2158,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     public function settingLocale()
     {
         trigger_error('TSFE->settingLocale() will be removed in TYPO3 v11.0. Use Locales::setSystemLocaleFromSiteLanguage() instead, as this functionality is independent of TSFE.', E_USER_DEPRECATED);
-        $siteLanguage = $this->getCurrentSiteLanguage();
-        if ($siteLanguage->getLocale() && !Locales::setSystemLocaleFromSiteLanguage($siteLanguage)) {
-            $this->getTimeTracker()->setTSlogMessage('Locale "' . htmlspecialchars($siteLanguage->getLocale()) . '" not found.', 3);
+        if ($this->language->getLocale() && !Locales::setSystemLocaleFromSiteLanguage($this->language)) {
+            $this->getTimeTracker()->setTSlogMessage('Locale "' . htmlspecialchars($this->language->getLocale()) . '" not found.', 3);
         }
     }
 
@@ -2937,7 +2993,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             $response = $response->withHeader('Content-Type', $this->contentType . '; charset=' . trim($this->metaCharset));
         }
         // Set header for content language unless disabled
-        $contentLanguage = $this->getCurrentSiteLanguage() instanceof SiteLanguage ? $this->getCurrentSiteLanguage()->getTwoLetterIsoCode() : '';
+        $contentLanguage = $this->language->getTwoLetterIsoCode();
         if (empty($this->config['config']['disableLanguageHeader']) && !empty($contentLanguage)) {
             $response = $response->withHeader('Content-Language', trim($contentLanguage));
         }
@@ -3703,21 +3759,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     }
 
     /**
-     * Returns the currently configured "site language" if a site is configured (= resolved) in the current request.
-     *
-     * @internal
-     */
-    protected function getCurrentSiteLanguage(): ?SiteLanguage
-    {
-        if (isset($GLOBALS['TYPO3_REQUEST'])
-            && $GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface
-            && $GLOBALS['TYPO3_REQUEST']->getAttribute('language') instanceof SiteLanguage) {
-            return $GLOBALS['TYPO3_REQUEST']->getAttribute('language');
-        }
-        return null;
-    }
-
-    /**
      * Return the global instance of this class.
      *
      * Intended to be used as prototype factory for this class, see Services.yaml.
@@ -3742,4 +3783,24 @@ class TypoScriptFrontendController implements LoggerAwareInterface
 
         throw new \LogicException('TypoScriptFrontendController was tried to be injected before initial creation', 1538370377);
     }
+
+    public function getLanguage(): SiteLanguage
+    {
+        return $this->language;
+    }
+
+    public function getSite(): Site
+    {
+        return $this->site;
+    }
+
+    public function getContext(): Context
+    {
+        return $this->context;
+    }
+
+    public function getPageArguments(): PageArguments
+    {
+        return $this->pageArguments;
+    }
 }
index cebd558..b9b1fdf 100644 (file)
@@ -36,15 +36,11 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  * a different middleware later-on might unset the BE_USER as he/she is not allowed to preview a certain
  * page due to rights management. As this can only happen once the page ID is resolved, this will happen
  * after the routing middleware.
- *
- * Currently, this middleware depends on the availability of $GLOBALS['TSFE'], however, this is solely
- * due to backwards-compatibility and will be disabled in the future.
  */
 class BackendUserAuthenticator implements MiddlewareInterface
 {
     /**
-     * Creates a frontend user authentication object, tries to authenticate a user
-     * and stores the object in $GLOBALS['TSFE']->fe_user.
+     * Creates a backend user authentication object, tries to authenticate a user
      *
      * @param ServerRequestInterface $request
      * @param RequestHandlerInterface $handler
index a8e332c..09a7ee4 100644 (file)
@@ -27,13 +27,12 @@ use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
 
 /**
  * This middleware authenticates a Frontend User (fe_users).
- * A valid $GLOBALS['TSFE'] object is needed for the time being, being fully backwards-compatible.
  */
 class FrontendUserAuthenticator implements MiddlewareInterface
 {
     /**
-     * Creates a frontend user authentication object, tries to authenticate a user
-     * and stores the object in $GLOBALS['TSFE']->fe_user.
+     * Creates a frontend user authentication object, tries to authenticate a user and stores
+     * it in the current request as attribute.
      *
      * @param ServerRequestInterface $request
      * @param RequestHandlerInterface $handler
@@ -59,11 +58,9 @@ class FrontendUserAuthenticator implements MiddlewareInterface
         $frontendUser->start();
         $frontendUser->unpack_uc();
 
-        // Keep the backwards-compatibility for TYPO3 v9, to have the fe_user within the global TSFE object
-        $GLOBALS['TSFE']->fe_user = $frontendUser;
-
-        // Register the frontend user as aspect
+        // Register the frontend user as aspect and within the session
         $this->setFrontendUserAspect(GeneralUtility::makeInstance(Context::class), $frontendUser);
+        $request = $request->withAttribute('frontend.user', $frontendUser);
 
         $response = $handler->handle($request);
 
index 7d6ff74..6bf951d 100644 (file)
@@ -47,16 +47,12 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
     protected $cacheHashCalculator;
 
     /**
-     * @var TypoScriptFrontendController
+     * @var bool will be used to set $TSFE->no_cache later-on
      */
-    protected $controller;
+    protected $disableCache = false;
 
-    /**
-     * @param TypoScriptFrontendController|null $controller
-     */
-    public function __construct(TypoScriptFrontendController $controller = null)
+    public function __construct()
     {
-        $this->controller = $controller ?? $GLOBALS['TSFE'];
         $this->cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class);
     }
 
@@ -69,6 +65,7 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
      */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
+        $this->disableCache = (bool)$request->getAttribute('noCache', false);
         $pageNotFoundOnValidationError = (bool)($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError'] ?? true);
         /** @var PageArguments $pageArguments */
         $pageArguments = $request->getAttribute('routing', null);
@@ -81,7 +78,12 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
                 ['code' => PageAccessFailureReasons::INVALID_PAGE_ARGUMENTS]
             );
         }
-        if ($this->controller->no_cache && !$pageNotFoundOnValidationError) {
+        if ($GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ?? true) {
+            $cachingDisabledByRequest = false;
+        } else {
+            $cachingDisabledByRequest = $pageArguments->getArguments()['no_cache'] ?? $request->getParsedBody()['no_cache'] ?? false;
+        }
+        if (($cachingDisabledByRequest || $this->disableCache) && !$pageNotFoundOnValidationError) {
             // No need to test anything if caching was already disabled.
             return $handler->handle($request);
         }
@@ -116,6 +118,8 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
                 );
             }
         }
+
+        $request = $request->withAttribute('noCache', $this->disableCache);
         return $handler->handle($request);
     }
 
@@ -153,7 +157,7 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
             return false;
         }
         // Caching is disabled now (but no 404)
-        $this->controller->no_cache = true;
+        $this->disableCache = true;
         $this->getTimeTracker()->setTSlogMessage('The incoming cHash "' . $cHash . '" and calculated cHash "' . $calculatedCacheHash . '" did not match, so caching was disabled. The fieldlist used was "' . implode(',', array_keys($relevantParameters)) . '"', 2);
         return true;
     }
@@ -177,7 +181,7 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
             return false;
         }
         // Caching is disabled now (but no 404)
-        $this->controller->no_cache = true;
+        $this->disableCache = true;
         $this->getTimeTracker()->setTSlogMessage('TSFE->reqCHash(): No &cHash parameter was sent for GET vars though required so caching is disabled', 2);
         return true;
     }
index 6315a9a..42d9136 100644 (file)
@@ -19,18 +19,12 @@ use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
-use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Context\Context;
-use TYPO3\CMS\Core\Context\UserAspect;
-use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Routing\PageArguments;
 use TYPO3\CMS\Core\Routing\RouteNotFoundException;
 use TYPO3\CMS\Core\Routing\SiteRouteResult;
 use TYPO3\CMS\Core\Site\Entity\Site;
-use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Controller\ErrorController;
-use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
 
 /**
@@ -45,16 +39,6 @@ use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
 class PageResolver implements MiddlewareInterface
 {
     /**
-     * @var TypoScriptFrontendController
-     */
-    protected $controller;
-
-    public function __construct(TypoScriptFrontendController $controller = null)
-    {
-        $this->controller = $controller ?? $GLOBALS['TSFE'];
-    }
-
-    /**
      * Resolve the page ID
      *
      * @param ServerRequestInterface $request
@@ -73,9 +57,6 @@ class PageResolver implements MiddlewareInterface
             );
         }
 
-        // First, resolve the root page of the site, the Page ID of the current domain
-        $this->controller->domainStartPage = $site->getRootPageId();
-
         /** @var SiteRouteResult $previousResult */
         $previousResult = $request->getAttribute('routing', null);
         if (!$previousResult) {
@@ -90,6 +71,7 @@ class PageResolver implements MiddlewareInterface
         try {
             /** @var PageArguments $pageArguments */
             $pageArguments = $site->getRouter()->matchRequest($request, $previousResult);
+            $request = $request->withAttribute('routing', $pageArguments);
         } catch (RouteNotFoundException $e) {
             return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
                 $request,
@@ -106,9 +88,6 @@ class PageResolver implements MiddlewareInterface
             );
         }
 
-        $this->controller->id = $pageArguments->getPageId();
-        $this->controller->type = $pageArguments->getPageType() ?? $this->controller->type;
-        $request = $request->withAttribute('routing', $pageArguments);
         // stop in case arguments are dirty (=defined twice in route and GET query parameters)
         if ($pageArguments->areDirty()) {
             return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
@@ -121,33 +100,7 @@ class PageResolver implements MiddlewareInterface
         // merge the PageArguments with the request query parameters
         $queryParams = array_replace_recursive($request->getQueryParams(), $pageArguments->getArguments());
         $request = $request->withQueryParams($queryParams);
-        $this->controller->setPageArguments($pageArguments);
-
-        // as long as TSFE throws errors with the global object, this needs to be set,
-        // but should be removed later-on
-        $GLOBALS['TYPO3_REQUEST'] = $request;
-        $this->controller->determineId();
-
-        // No access? Then remove user and re-evaluate the page id
-        if ($this->controller->isBackendUserLoggedIn() && !$GLOBALS['BE_USER']->doesUserHaveAccess($this->controller->page, Permission::PAGE_SHOW)) {
-            unset($GLOBALS['BE_USER']);
-            // Register an empty backend user as aspect
-            $this->setBackendUserAspect(GeneralUtility::makeInstance(Context::class), null);
-            $this->controller->determineId();
-        }
 
         return $handler->handle($request);
     }
-
-    /**
-     * Register the backend user as aspect
-     *
-     * @param Context $context
-     * @param BackendUserAuthentication $user
-     */
-    protected function setBackendUserAspect(Context $context, BackendUserAuthentication $user = null)
-    {
-        $context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
-        $context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $user ? $user->workspace : 0));
-    }
 }
index e0f9039..915efa5 100644 (file)
@@ -19,7 +19,7 @@ namespace TYPO3\CMS\Frontend\Middleware;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\MiddlewareInterface;
-use Psr\Http\Server\RequestHandlerInterface as PsrRequestHandlerInterface;
+use Psr\Http\Server\RequestHandlerInterface;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
@@ -29,7 +29,7 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
  *
  * Do all necessary preparation steps for rendering
  *
- * @internal this middleware might get removed in TYPO3 v10.0.
+ * @internal this middleware might get removed in TYPO3 v10.x.
  */
 class PrepareTypoScriptFrontendRendering implements MiddlewareInterface
 {
@@ -53,10 +53,10 @@ class PrepareTypoScriptFrontendRendering implements MiddlewareInterface
      * Initialize TypoScriptFrontendController to the point right before rendering of the page is triggered
      *
      * @param ServerRequestInterface $request
-     * @param PsrRequestHandlerInterface $handler
+     * @param RequestHandlerInterface $handler
      * @return ResponseInterface
      */
-    public function process(ServerRequestInterface $request, PsrRequestHandlerInterface $handler): ResponseInterface
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
         // as long as TSFE throws errors with the global object, this needs to be set, but
         // should be removed later-on once TypoScript Condition Matcher is built with the current request object.
index 184acf5..1e9b277 100644 (file)
@@ -28,7 +28,7 @@ use TYPO3\CMS\Frontend\Page\PageRepository;
  * Checks mount points, shortcuts and redirects to the target.
  * Alternatively, checks if the current page is an redirect to an external page
  *
- * @internal this middleware might get removed in TYPO3 v10.0.
+ * @internal this middleware might get removed in TYPO3 v10.x.
  */
 class ShortcutAndMountPointRedirect implements MiddlewareInterface
 {
index 55fba5f..7af1b35 100644 (file)
@@ -19,14 +19,26 @@ use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
+use TYPO3\CMS\Core\Routing\PageArguments;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Controller\ErrorController;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
 
 /**
  * Creates an instance of TypoScriptFrontendController and makes this globally available
  * via $GLOBALS['TSFE'].
  *
- * @internal this middleware might get removed in TYPO3 v10.0.
+ * In addition, determineId builds up the rootline based on a valid frontend-user authentication and
+ * Backend permissions if previewing.
+ *
+ * @internal this middleware might get removed in TYPO3 v11.0.
  */
 class TypoScriptFrontendInitialization implements MiddlewareInterface
 {
@@ -39,19 +51,61 @@ class TypoScriptFrontendInitialization implements MiddlewareInterface
      */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
-        $GLOBALS['TSFE'] = GeneralUtility::makeInstance(
+        $GLOBALS['TYPO3_REQUEST'] = $request;
+        /** @var Site $site */
+        $site = $request->getAttribute('site', null);
+        $pageArguments = $request->getAttribute('routing', null);
+        if (!$pageArguments instanceof PageArguments) {
+            // Page Arguments must be set in order to validate. This middleware only works if PageArguments
+            // is available, and is usually combined with the Page Resolver middleware
+            return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
+                $request,
+                'Page Arguments could not be resolved',
+                ['code' => PageAccessFailureReasons::INVALID_PAGE_ARGUMENTS]
+            );
+        }
+        $context = GeneralUtility::makeInstance(Context::class);
+
+        $controller = GeneralUtility::makeInstance(
             TypoScriptFrontendController::class,
-            null,
-            $request->getParsedBody()['id'] ?? $request->getQueryParams()['id'] ?? 0,
-            $request->getParsedBody()['type'] ?? $request->getQueryParams()['type'] ?? 0,
-            null,
-            $request->getParsedBody()['cHash'] ?? $request->getQueryParams()['cHash'] ?? '',
-            null,
-            $request->getParsedBody()['MP'] ?? $request->getQueryParams()['MP'] ?? ''
+            $context,
+            $site,
+            $request->getAttribute('language', $site->getDefaultLanguage()),
+            $pageArguments,
+            $request->getAttribute('frontend.user', null)
         );
-        if ($request->getParsedBody()['no_cache'] ?? $request->getQueryParams()['no_cache'] ?? false) {
-            $GLOBALS['TSFE']->set_no_cache('&no_cache=1 has been supplied, so caching is disabled! URL: "' . (string)$request->getUri() . '"');
+        if ($pageArguments->getArguments()['no_cache'] ?? $request->getParsedBody()['no_cache'] ?? false) {
+            $controller->set_no_cache('&no_cache=1 has been supplied, so caching is disabled! URL: "' . (string)$request->getUri() . '"');
+        }
+        // Usually only set by the PageArgumentValidator
+        if ($request->getAttribute('noCache', false)) {
+            $controller->no_cache = 1;
+        }
+
+        $controller->determineId();
+
+        // No access? Then remove user and re-evaluate the page id
+        if ($controller->isBackendUserLoggedIn() && !$GLOBALS['BE_USER']->doesUserHaveAccess($controller->page, Permission::PAGE_SHOW)) {
+            unset($GLOBALS['BE_USER']);
+            // Register an empty backend user as aspect
+            $this->setBackendUserAspect($context, null);
+            $controller->determineId();
         }
+
+        // Make TSFE globally available
+        $GLOBALS['TSFE'] = $controller;
         return $handler->handle($request);
     }
+
+    /**
+     * Register the backend user as aspect
+     *
+     * @param Context $context
+     * @param BackendUserAuthentication|null $user
+     */
+    protected function setBackendUserAspect(Context $context, ?BackendUserAuthentication $user): void
+    {
+        $context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
+        $context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $user ? $user->workspace : 0));
+    }
 }
index 1b83a47..e184dc3 100644 (file)
@@ -15,7 +15,14 @@ namespace TYPO3\CMS\Frontend\Typolink;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Http\ServerRequestFactory;
+use TYPO3\CMS\Core\Routing\PageArguments;
 use TYPO3\CMS\Core\Service\DependencyOrderingService;
+use TYPO3\CMS\Core\Site\Entity\NullSite;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
+use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\TypoScript\TemplateService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
@@ -204,12 +211,32 @@ abstract class AbstractTypolinkBuilder
         // This usually happens when typolink is created by the TYPO3 Backend, where no TSFE object
         // is there. This functionality is currently completely internal, as these links cannot be
         // created properly from the Backend.
-        // However, this is added to avoid any exceptions when trying to create a link
+        // However, this is added to avoid any exceptions when trying to create a link.
+        // Detecting the "first" site usually comes from the fact that TSFE needs to be instantiated
+        // during tests
+        $request = $GLOBALS['TYPO3_REQUEST'] ?? ServerRequestFactory::fromGlobals();
+        $site = $request->getAttribute('site');
+        if (!$site instanceof Site) {
+            $sites = GeneralUtility::makeInstance(SiteFinder::class)->getAllSites();
+            $site = reset($sites);
+            if (!$site instanceof Site) {
+                $site = new NullSite();
+            }
+        }
+        $language = $request->getAttribute('language');
+        if (!$language instanceof SiteLanguage) {
+            $language = $site->getDefaultLanguage();
+        }
+
+        $id = $request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? $site->getRootPageId();
+        $type = $request->getQueryParams()['type'] ?? $request->getParsedBody()['type'] ?? '0';
+
         $this->typoScriptFrontendController = GeneralUtility::makeInstance(
             TypoScriptFrontendController::class,
-            null,
-            GeneralUtility::_GP('id'),
-            (int)GeneralUtility::_GP('type')
+            GeneralUtility::makeInstance(Context::class),
+            $site,
+            $language,
+            $request->getAttribute('routing', new PageArguments((int)$id, (string)$type, []))
         );
         $this->typoScriptFrontendController->sys_page = GeneralUtility::makeInstance(PageRepository::class);
         $this->typoScriptFrontendController->tmpl = GeneralUtility::makeInstance(TemplateService::class);
index bf043ef..41791e6 100644 (file)
@@ -43,41 +43,14 @@ return [
                 'typo3/cms-frontend/maintenance-mode'
             ]
         ],
-        /** internal: do not use or reference this middleware in your own code, as this will be possibly be removed */
-        'typo3/cms-frontend/tsfe' => [
-            'target' => \TYPO3\CMS\Frontend\Middleware\TypoScriptFrontendInitialization::class,
-            'after' => [
-                'typo3/cms-frontend/eid',
-            ]
-        ],
-        /** internal: do not use or reference this middleware in your own code, as this will be possibly be removed */
-        'typo3/cms-frontend/output-compression' => [
-            'target' => \TYPO3\CMS\Frontend\Middleware\OutputCompression::class,
-            'after' => [
-                'typo3/cms-frontend/tsfe',
-            ]
-        ],
-        'typo3/cms-frontend/authentication' => [
-            'target' => \TYPO3\CMS\Frontend\Middleware\FrontendUserAuthenticator::class,
-            'after' => [
-                'typo3/cms-frontend/tsfe',
-            ]
-        ],
-        'typo3/cms-frontend/backend-user-authentication' => [
-            'target' => \TYPO3\CMS\Frontend\Middleware\BackendUserAuthenticator::class,
-            'after' => [
-                'typo3/cms-frontend/tsfe',
-            ]
-        ],
         'typo3/cms-frontend/site' => [
             'target' => \TYPO3\CMS\Frontend\Middleware\SiteResolver::class,
             'after' => [
                 'typo3/cms-core/normalized-params-attribute',
-                'typo3/cms-frontend/tsfe',
-                'typo3/cms-frontend/authentication',
-                'typo3/cms-frontend/backend-user-authentication',
             ],
             'before' => [
+                'typo3/cms-frontend/authentication',
+                'typo3/cms-frontend/backend-user-authentication',
                 'typo3/cms-frontend/page-resolver'
             ]
         ],
@@ -99,13 +72,31 @@ return [
                 'typo3/cms-frontend/page-resolver'
             ]
         ],
+        'typo3/cms-frontend/backend-user-authentication' => [
+            'target' => \TYPO3\CMS\Frontend\Middleware\BackendUserAuthenticator::class,
+            'before' => [
+                'typo3/cms-frontend/authentication',
+            ]
+        ],
+        'typo3/cms-frontend/authentication' => [
+            'target' => \TYPO3\CMS\Frontend\Middleware\FrontendUserAuthenticator::class,
+            'before' => [
+                'typo3/cms-frontend/tsfe',
+            ],
+            'after' => [
+                'typo3/cms-frontend/maintenance-mode',
+                'typo3/cms-frontend/site'
+            ]
+        ],
         'typo3/cms-frontend/page-resolver' => [
             'target' => \TYPO3\CMS\Frontend\Middleware\PageResolver::class,
             'after' => [
-                'typo3/cms-frontend/tsfe',
+                'typo3/cms-frontend/site',
                 'typo3/cms-frontend/authentication',
                 'typo3/cms-frontend/backend-user-authentication',
-                'typo3/cms-frontend/site',
+            ],
+            'before' => [
+                'typo3/cms-frontend/tsfe',
             ]
         ],
         'typo3/cms-frontend/page-argument-validator' => [
@@ -114,14 +105,29 @@ return [
                 'typo3/cms-frontend/page-resolver',
             ],
             'before' => [
-                'typo3/cms-frontend/prepare-tsfe-rendering',
+                'typo3/cms-frontend/tsfe',
+            ]
+        ],
+        /** internal: do not use or reference this middleware in your own code, as this will be possibly be removed */
+        'typo3/cms-frontend/tsfe' => [
+            'target' => \TYPO3\CMS\Frontend\Middleware\TypoScriptFrontendInitialization::class,
+            'after' => [
+                'typo3/cms-frontend/eid',
+                'typo3/cms-frontend/page-argument-validator',
+            ]
+        ],
+        /** internal: do not use or reference this middleware in your own code, as this will be possibly be removed */
+        'typo3/cms-frontend/output-compression' => [
+            'target' => \TYPO3\CMS\Frontend\Middleware\OutputCompression::class,
+            'after' => [
+                'typo3/cms-frontend/tsfe',
             ]
         ],
         /** internal: do not use or reference this middleware in your own code, as this will be possibly be removed */
         'typo3/cms-frontend/prepare-tsfe-rendering' => [
             'target' => \TYPO3\CMS\Frontend\Middleware\PrepareTypoScriptFrontendRendering::class,
             'after' => [
-                'typo3/cms-frontend/page-argument-validator',
+                'typo3/cms-frontend/tsfe',
             ]
         ],
         /** internal: do not use or reference this middleware in your own code, as this will be possibly be removed */
index b535eb6..35e6341 100644 (file)
@@ -20,6 +20,7 @@ use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Http\ServerRequest;
 use TYPO3\CMS\Core\Log\Logger;
+use TYPO3\CMS\Core\Routing\PageArguments;
 use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\TypoScript\TemplateService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -445,11 +446,26 @@ class ConditionMatcherTest extends FunctionalTestCase
      */
     protected function setupFrontendController(int $pageId): void
     {
+        $site = new Site('angelo', 13, [
+            'languages' => [
+                [
+                    'languageId' => 0,
+                    'title' => 'United States',
+                    'locale' => 'en_US.UTF-8',
+                ],
+                [
+                    'languageId' => 2,
+                    'title' => 'UK',
+                    'locale' => 'en_UK.UTF-8',
+                ]
+            ]
+        ]);
         $GLOBALS['TSFE'] = GeneralUtility::makeInstance(
             TypoScriptFrontendController::class,
-            null,
-            $pageId,
-            0
+            GeneralUtility::makeInstance(Context::class),
+            $site,
+            $site->getLanguageById(0),
+            new PageArguments($pageId, '0', [])
         );
         $GLOBALS['TSFE']->sys_page = GeneralUtility::makeInstance(PageRepository::class);
         $GLOBALS['TSFE']->tmpl = GeneralUtility::makeInstance(TemplateService::class);
index 1138016..a31f79c 100644 (file)
@@ -14,39 +14,72 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\ContentObject;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Routing\PageArguments;
+use TYPO3\CMS\Core\Site\SiteFinder;
+use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
 use TYPO3\CMS\Core\TypoScript\TemplateService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3\CMS\Frontend\Page\PageRepository;
 use TYPO3\CMS\Frontend\Typolink\PageLinkBuilder;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
 
 /**
  * Testcase for TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
  */
-class ContentObjectRendererTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
+class ContentObjectRendererTest extends FunctionalTestCase
 {
+    use SiteBasedTestTrait;
+
+    /**
+     * @var array[]
+     */
+    protected const LANGUAGE_PRESETS = [
+        'EN' => ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8'],
+    ];
+
     /**
      * @var ContentObjectRenderer
      */
     protected $subject;
 
+    /**
+     * @var TypoScriptFrontendController
+     */
+    protected $typoScriptFrontendController;
+
     protected function setUp(): void
     {
         parent::setUp();
+        $this->writeSiteConfiguration(
+            'test',
+            $this->buildSiteConfiguration(1, '/'),
+            [
+                $this->buildDefaultLanguageConfiguration('EN', '/en/'),
+            ],
+            [
+                $this->buildErrorHandlingConfiguration('Fluid', [404])
+            ]
+        );
+        $_SERVER['HTTP_HOST'] = 'example.com';
+        $_SERVER['REQUEST_URI'] = '/en/';
+        $_GET['id'] = 1;
+        GeneralUtility::flushInternalRuntimeCaches();
+        $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByIdentifier('test');
 
-        $typoScriptFrontendController = GeneralUtility::makeInstance(
+        $this->typoScriptFrontendController = GeneralUtility::makeInstance(
             TypoScriptFrontendController::class,
-            null,
-            1,
-            0
+            GeneralUtility::makeInstance(Context::class),
+            $site,
+            $site->getDefaultLanguage(),
+            new PageArguments(1, '0', [])
         );
-        $typoScriptFrontendController->sys_page = GeneralUtility::makeInstance(PageRepository::class);
-        $typoScriptFrontendController->tmpl = GeneralUtility::makeInstance(TemplateService::class);
-        $GLOBALS['TSFE'] = $typoScriptFrontendController;
-
-        $this->subject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
+        $this->typoScriptFrontendController->sys_page = GeneralUtility::makeInstance(PageRepository::class);
+        $this->typoScriptFrontendController->tmpl = GeneralUtility::makeInstance(TemplateService::class);
+        $this->subject = GeneralUtility::makeInstance(ContentObjectRenderer::class, $this->typoScriptFrontendController);
     }
 
     /**
@@ -201,7 +234,7 @@ class ContentObjectRendererTest extends \TYPO3\TestingFramework\Core\Functional\
      */
     public function getQueryCallsGetTreeListWithNegativeValuesIfRecursiveIsSet()
     {
-        $this->subject = $this->getAccessibleMock(ContentObjectRenderer::class, ['getTreeList']);
+        $this->subject = $this->getAccessibleMock(ContentObjectRenderer::class, ['getTreeList'], [$this->typoScriptFrontendController]);
         $this->subject->start([], 'tt_content');
 
         $conf = [
@@ -226,9 +259,9 @@ class ContentObjectRendererTest extends \TYPO3\TestingFramework\Core\Functional\
      */
     public function getQueryCallsGetTreeListWithCurrentPageIfThisIsSet()
     {
-        $GLOBALS['TSFE']->id = 27;
+        $this->typoScriptFrontendController->id = 27;
 
-        $this->subject = $this->getAccessibleMock(ContentObjectRenderer::class, ['getTreeList']);
+        $this->subject = $this->getAccessibleMock(ContentObjectRenderer::class, ['getTreeList'], [$this->typoScriptFrontendController]);
         $this->subject->start([], 'tt_content');
 
         $conf = [
index 0269b62..76abf1a 100644 (file)
@@ -22,6 +22,9 @@ use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Http\ServerRequest;
+use TYPO3\CMS\Core\Routing\PageArguments;
+use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
 use TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject;
@@ -44,10 +47,22 @@ class AbstractMenuContentObjectTest extends UnitTestCase
     protected function setUp(): void
     {
         parent::setUp();
+        $GLOBALS['TYPO3_REQUEST'] = new ServerRequest('https://www.example.com', 'GET');
         $proxyClassName = $this->buildAccessibleProxy(AbstractMenuContentObject::class);
         $this->subject = $this->getMockForAbstractClass($proxyClassName);
+        $site = new Site('test', 1, [
+            'base' => 'https://www.example.com',
+            'languages' => [
+                [
+                    'languageId' => 0,
+                    'title' => 'English',
+                    'locale' => 'en_UK',
+                    'base' => '/'
+                ]
+            ]
+        ]);
         $GLOBALS['TSFE'] = $this->getMockBuilder(\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class)
-            ->setConstructorArgs([$GLOBALS['TYPO3_CONF_VARS'], 1, 1])
+            ->setConstructorArgs([new Context(), $site, $site->getDefaultLanguage(), new PageArguments(1, '1', [])])
             ->setMethods(['initCaches'])
             ->getMock();
         $GLOBALS['TSFE']->cObj = new ContentObjectRenderer();
index b94278a..2dff952 100644 (file)
@@ -493,8 +493,7 @@ class TypoScriptFrontendControllerTest extends UnitTestCase
         $cacheManager->getCache('pages')->willReturn($nullCacheBackend);
         GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManager->reveal());
 
-        $subject = new TypoScriptFrontendController(null, 1, 0);
-        $subject->generatePageTitle();
-        $this->assertSame($pageTitle, $subject->indexedDocTitle);
+        $this->subject->generatePageTitle();
+        $this->assertSame($pageTitle, $this->subject->indexedDocTitle);
     }
 }
index 12e4f3d..6aad128 100644 (file)
@@ -18,11 +18,13 @@ namespace TYPO3\CMS\Redirects\Service;
 use Psr\Http\Message\UriInterface;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Http\Uri;
 use TYPO3\CMS\Core\LinkHandling\LinkService;
 use TYPO3\CMS\Core\Resource\Exception\InvalidPathException;
 use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\Folder;
+use TYPO3\CMS\Core\Routing\PageArguments;
 use TYPO3\CMS\Core\Site\Entity\SiteInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\HttpUtility;
@@ -285,11 +287,13 @@ class RedirectService implements LoggerAwareInterface
      */
     protected function bootFrontendController(?SiteInterface $site, array $queryParams): TypoScriptFrontendController
     {
+        $pageId = $site ? $site->getRootPageId() : ($GLOBALS['TSFE'] ? $GLOBALS['TSFE']->id : 0);
         $controller = GeneralUtility::makeInstance(
             TypoScriptFrontendController::class,
-            null,
-            $site ? $site->getRootPageId() : $GLOBALS['TSFE']->id,
-            0
+            GeneralUtility::makeInstance(Context::class),
+            $site,
+            $site->getDefaultLanguage(),
+            new PageArguments((int)$pageId, '0', [])
         );
         $controller->fe_user = $GLOBALS['TSFE']->fe_user ?? null;
         $controller->fetch_the_id();
index b7e195c..288afab 100644 (file)
@@ -11,7 +11,6 @@ return [
                 'typo3/cms-frontend/page-resolver',
             ],
             'after' => [
-                'typo3/cms-frontend/tsfe',
                 'typo3/cms-frontend/authentication',
                 'typo3/cms-frontend/static-route-resolver',
             ],
index 490f435..94f780d 100644 (file)
@@ -17,6 +17,9 @@ namespace TYPO3\CMS\Seo\Tests\Functional\Canonical;
  */
 
 use Psr\Log\NullLogger;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Routing\PageArguments;
+use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\TypoScript\TemplateService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
@@ -45,17 +48,25 @@ class CanonicalGeneratorTest extends AbstractTestCase
             'website-local',
             $this->buildSiteConfiguration(1, 'http://localhost/')
         );
+        $_SERVER['HTTP_HOST'] = 'localhost';
+        $_SERVER['REQUEST_URI'] = '/';
+        GeneralUtility::flushInternalRuntimeCaches();
     }
 
     protected function initTypoScriptFrontendController(int $uid): TypoScriptFrontendController
     {
-        $typoScriptFrontendController = new TypoScriptFrontendController(null, $uid, 0);
+        $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByIdentifier('website-local');
+        $typoScriptFrontendController = new TypoScriptFrontendController(
+            GeneralUtility::makeInstance(Context::class),
+            $site,
+            $site->getDefaultLanguage(),
+            new PageArguments($uid, '0', [])
+        );
         $typoScriptFrontendController->cObj = new ContentObjectRenderer();
         $typoScriptFrontendController->cObj->setLogger(new NullLogger());
         $typoScriptFrontendController->sys_page = GeneralUtility::makeInstance(PageRepository::class);
         $typoScriptFrontendController->tmpl = GeneralUtility::makeInstance(TemplateService::class);
         $typoScriptFrontendController->getPageAndRootlineWithDomain(1);
-        $GLOBALS['TSFE'] = $typoScriptFrontendController;
         return $typoScriptFrontendController;
     }
 
index 2fa9a64..c410ad9 100644 (file)
@@ -28,6 +28,7 @@ use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Http\NormalizedParams;
 use TYPO3\CMS\Core\Http\Stream;
 use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Routing\PageArguments;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3\CMS\Workspaces\Authentication\PreviewUserAuthentication;
@@ -64,6 +65,7 @@ class WorkspacePreview implements MiddlewareInterface
      */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
+        $addInformationAboutDisabledCache = false;
         $keyword = $this->getPreviewInputCode($request);
         if ($keyword) {
             switch ($keyword) {
@@ -75,13 +77,14 @@ class WorkspacePreview implements MiddlewareInterface
                     $message = $this->getLogoutTemplateMessage($request->getQueryParams()['returnUrl'] ?? '');
                     return new HtmlResponse($message);
                 default:
+                    $pageArguments = $request->getAttribute('routing', null);
                     // A keyword was found in a query parameter or in a cookie
                     // If the keyword is valid, activate a BE User and override any existing BE Users
                     $configuration = $this->getPreviewConfigurationFromRequest($request, $keyword);
-                    if (is_array($configuration) && $configuration['fullWorkspace'] > 0) {
+                    if (is_array($configuration) && $configuration['fullWorkspace'] > 0 && $pageArguments instanceof PageArguments) {
                         $previewUser = $this->initializePreviewUser(
                             (int)$configuration['fullWorkspace'],
-                            $GLOBALS['TSFE']->id
+                            $pageArguments->getPageId()
                         );
                         if ($previewUser) {
                             $GLOBALS['BE_USER'] = $previewUser;
@@ -100,11 +103,17 @@ class WorkspacePreview implements MiddlewareInterface
             // Register the backend user as aspect
             $this->setBackendUserAspect(GeneralUtility::makeInstance(Context::class), null);
             // Caching is disabled, because otherwise generated URLs could include the ADMCMD_noBeUser parameter
-            $GLOBALS['TSFE']->set_no_cache('GET Parameter ADMCMD_noBeUser was given', true);
+            $request = $request->withAttribute('noCache', true);
+            $addInformationAboutDisabledCache = true;
         }
 
         $response = $handler->handle($request);
 
+        // Caching is disabled, because otherwise generated URLs could include the ADMCMD_noBeUser parameter
+        if ($addInformationAboutDisabledCache) {
+            $GLOBALS['TSFE']->set_no_cache('GET Parameter ADMCMD_noBeUser was given', true);
+        }
+
         // Add a info box to the frontend content
         if ($GLOBALS['TSFE']->doWorkspacePreview() && $GLOBALS['TSFE']->isOutputting()) {
             $previewInfo = $this->renderPreviewInfo($GLOBALS['TSFE'], $request->getAttribute('normalizedParams'));
index eeb3a84..98d2517 100644 (file)
@@ -7,13 +7,14 @@ return [
         'typo3/cms-workspaces/preview' => [
             'target' => \TYPO3\CMS\Workspaces\Middleware\WorkspacePreview::class,
             'after' => [
-                // TSFE is needed to store information about the preview
-                'typo3/cms-frontend/tsfe',
+                // PageArguments are needed to store information about the preview
+                'typo3/cms-frontend/page-argument-validator',
                 // A preview user will override an existing logged-in backend user
                 'typo3/cms-frontend/backend-user-authentication',
             ],
             'before' => [
-                'typo3/cms-frontend/page-resolver',
+                // TSFE is needed to store information about the preview
+                'typo3/cms-frontend/tsfe',
             ]
         ],
     ]