From e50b1c1acdd5da514a35f837d9b853692bcfa16d Mon Sep 17 00:00:00 2001 From: Benni Mack <benni@typo3.org> Date: Wed, 26 Jun 2019 23:04:29 +0200 Subject: [PATCH] [!!!][TASK] Remove dependencies of TSFE 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> --- composer.json | 2 +- composer.lock | 12 +- .../Configuration/RequestMiddlewares.php | 6 +- typo3/sysext/core/Classes/Http/Uri.php | 2 +- ...ngedRequestWorkflowForFrontendRequests.rst | 66 ++++++- typo3/sysext/core/composer.json | 2 +- .../Classes/ViewHelpers/CObjectViewHelper.php | 3 +- .../ViewHelpers/TypolinkViewHelperTest.php | 7 +- .../TypoScriptFrontendController.php | 173 ++++++++++++------ .../Middleware/BackendUserAuthenticator.php | 6 +- .../Middleware/FrontendUserAuthenticator.php | 11 +- .../Middleware/PageArgumentValidator.php | 24 ++- .../Classes/Middleware/PageResolver.php | 49 +---- .../PrepareTypoScriptFrontendRendering.php | 8 +- .../ShortcutAndMountPointRedirect.php | 2 +- .../TypoScriptFrontendInitialization.php | 76 ++++++-- .../Typolink/AbstractTypolinkBuilder.php | 35 +++- .../Configuration/RequestMiddlewares.php | 72 ++++---- .../ConditionMatcherTest.php | 22 ++- .../ContentObjectRendererTest.php | 59 ++++-- .../Menu/AbstractMenuContentObjectTest.php | 17 +- .../TypoScriptFrontendControllerTest.php | 5 +- .../Classes/Service/RedirectService.php | 10 +- .../Configuration/RequestMiddlewares.php | 1 - .../Canonical/CanonicalGeneratorTest.php | 15 +- .../Classes/Middleware/WorkspacePreview.php | 15 +- .../Configuration/RequestMiddlewares.php | 7 +- 27 files changed, 473 insertions(+), 234 deletions(-) diff --git a/composer.json b/composer.json index 779027662297..bcb67589840d 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/composer.lock b/composer.lock index 151f8925669a..cec5b8a2733e 100644 --- a/composer.lock +++ b/composer.lock @@ -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", @@ -6316,16 +6316,16 @@ }, { "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": { @@ -6372,7 +6372,7 @@ "tests", "typo3" ], - "time": "2019-06-18T07:52:50+00:00" + "time": "2019-07-13T12:16:05+00:00" } ], "aliases": [], diff --git a/typo3/sysext/adminpanel/Configuration/RequestMiddlewares.php b/typo3/sysext/adminpanel/Configuration/RequestMiddlewares.php index 0b08aa5ad172..4ca5dcd3cc25 100644 --- a/typo3/sysext/adminpanel/Configuration/RequestMiddlewares.php +++ b/typo3/sysext/adminpanel/Configuration/RequestMiddlewares.php @@ -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' => [ diff --git a/typo3/sysext/core/Classes/Http/Uri.php b/typo3/sysext/core/Classes/Http/Uri.php index 7f624e2c1f8c..d7c31b213b0c 100644 --- a/typo3/sysext/core/Classes/Http/Uri.php +++ b/typo3/sysext/core/Classes/Http/Uri.php @@ -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'])) { diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-88540-ChangedRequestWorkflowForFrontendRequests.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-88540-ChangedRequestWorkflowForFrontendRequests.rst index d655d4581140..94abba36d190 100644 --- a/typo3/sysext/core/Documentation/Changelog/master/Breaking-88540-ChangedRequestWorkflowForFrontendRequests.rst +++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-88540-ChangedRequestWorkflowForFrontendRequests.rst @@ -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 diff --git a/typo3/sysext/core/composer.json b/typo3/sysext/core/composer.json index b38736a5a5ca..e34079e3af7a 100644 --- a/typo3/sysext/core/composer.json +++ b/typo3/sysext/core/composer.json @@ -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", diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php index b7e0f625595e..eae14dc02e5a 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php @@ -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)) ); } diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TypolinkViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TypolinkViewHelperTest.php index 482ae66dc1e2..981094bf7728 100644 --- a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TypolinkViewHelperTest.php +++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TypolinkViewHelperTest.php @@ -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(); } /** diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php index e94987739a6b..c264045f72d2 100644 --- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php +++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php @@ -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; @@ -105,6 +107,16 @@ class TypoScriptFrontendController implements LoggerAwareInterface */ public $type = ''; + /** + * @var Site + */ + protected $site; + + /** + * @var SiteLanguage + */ + protected $language; + /** * The submitted cHash * @var string @@ -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)); } @@ -3702,21 +3758,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface return GeneralUtility::makeInstance(TimeTracker::class); } - /** - * 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. * @@ -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; + } } diff --git a/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php b/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php index cebd55804c8c..b9b1fdf2082d 100644 --- a/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php +++ b/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php @@ -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 diff --git a/typo3/sysext/frontend/Classes/Middleware/FrontendUserAuthenticator.php b/typo3/sysext/frontend/Classes/Middleware/FrontendUserAuthenticator.php index a8e332c5b1cb..09a7ee440b30 100644 --- a/typo3/sysext/frontend/Classes/Middleware/FrontendUserAuthenticator.php +++ b/typo3/sysext/frontend/Classes/Middleware/FrontendUserAuthenticator.php @@ -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); diff --git a/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php b/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php index 7d6ff74ac032..6bf951d4e886 100644 --- a/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php +++ b/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php @@ -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; } diff --git a/typo3/sysext/frontend/Classes/Middleware/PageResolver.php b/typo3/sysext/frontend/Classes/Middleware/PageResolver.php index 6315a9a1fb9d..42d9136990df 100644 --- a/typo3/sysext/frontend/Classes/Middleware/PageResolver.php +++ b/typo3/sysext/frontend/Classes/Middleware/PageResolver.php @@ -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; /** @@ -44,16 +38,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 * @@ -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)); - } } diff --git a/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php b/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php index e0f903950f99..915efa5a2a43 100644 --- a/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php +++ b/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php @@ -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. diff --git a/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php b/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php index 184acf5f4480..1e9b2775fac7 100644 --- a/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php +++ b/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php @@ -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 { diff --git a/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php b/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php index 55fba5f71d0a..7af1b35a48c8 100644 --- a/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php +++ b/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php @@ -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)); + } } diff --git a/typo3/sysext/frontend/Classes/Typolink/AbstractTypolinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/AbstractTypolinkBuilder.php index 1b83a471c23f..e184dc30c259 100644 --- a/typo3/sysext/frontend/Classes/Typolink/AbstractTypolinkBuilder.php +++ b/typo3/sysext/frontend/Classes/Typolink/AbstractTypolinkBuilder.php @@ -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); diff --git a/typo3/sysext/frontend/Configuration/RequestMiddlewares.php b/typo3/sysext/frontend/Configuration/RequestMiddlewares.php index bf043ef2cd1a..41791e69d37f 100644 --- a/typo3/sysext/frontend/Configuration/RequestMiddlewares.php +++ b/typo3/sysext/frontend/Configuration/RequestMiddlewares.php @@ -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 */ diff --git a/typo3/sysext/frontend/Tests/Functional/Configuration/TypoScript/ConditionMatching/ConditionMatcherTest.php b/typo3/sysext/frontend/Tests/Functional/Configuration/TypoScript/ConditionMatching/ConditionMatcherTest.php index b535eb6dfe86..35e63418b399 100644 --- a/typo3/sysext/frontend/Tests/Functional/Configuration/TypoScript/ConditionMatching/ConditionMatcherTest.php +++ b/typo3/sysext/frontend/Tests/Functional/Configuration/TypoScript/ConditionMatching/ConditionMatcherTest.php @@ -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); diff --git a/typo3/sysext/frontend/Tests/Functional/ContentObject/ContentObjectRendererTest.php b/typo3/sysext/frontend/Tests/Functional/ContentObject/ContentObjectRendererTest.php index 113801659c2d..a31f79c4d86f 100644 --- a/typo3/sysext/frontend/Tests/Functional/ContentObject/ContentObjectRendererTest.php +++ b/typo3/sysext/frontend/Tests/Functional/ContentObject/ContentObjectRendererTest.php @@ -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 = [ diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php index 0269b62e667f..76abf1a73269 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php @@ -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(); diff --git a/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php b/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php index b94278afbc6e..2dff9524ef40 100644 --- a/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php +++ b/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php @@ -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); } } diff --git a/typo3/sysext/redirects/Classes/Service/RedirectService.php b/typo3/sysext/redirects/Classes/Service/RedirectService.php index 12e4f3d7ef46..6aad12893f4a 100644 --- a/typo3/sysext/redirects/Classes/Service/RedirectService.php +++ b/typo3/sysext/redirects/Classes/Service/RedirectService.php @@ -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(); diff --git a/typo3/sysext/redirects/Configuration/RequestMiddlewares.php b/typo3/sysext/redirects/Configuration/RequestMiddlewares.php index b7e195cd3dbf..288afab3e2f8 100644 --- a/typo3/sysext/redirects/Configuration/RequestMiddlewares.php +++ b/typo3/sysext/redirects/Configuration/RequestMiddlewares.php @@ -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', ], diff --git a/typo3/sysext/seo/Tests/Functional/Canonical/CanonicalGeneratorTest.php b/typo3/sysext/seo/Tests/Functional/Canonical/CanonicalGeneratorTest.php index 490f435ff128..94f780d57e78 100644 --- a/typo3/sysext/seo/Tests/Functional/Canonical/CanonicalGeneratorTest.php +++ b/typo3/sysext/seo/Tests/Functional/Canonical/CanonicalGeneratorTest.php @@ -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; } diff --git a/typo3/sysext/workspaces/Classes/Middleware/WorkspacePreview.php b/typo3/sysext/workspaces/Classes/Middleware/WorkspacePreview.php index 2fa9a64c0f58..c410ad95bb09 100644 --- a/typo3/sysext/workspaces/Classes/Middleware/WorkspacePreview.php +++ b/typo3/sysext/workspaces/Classes/Middleware/WorkspacePreview.php @@ -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')); diff --git a/typo3/sysext/workspaces/Configuration/RequestMiddlewares.php b/typo3/sysext/workspaces/Configuration/RequestMiddlewares.php index eeb3a84f3eaa..98d2517b6218 100644 --- a/typo3/sysext/workspaces/Configuration/RequestMiddlewares.php +++ b/typo3/sysext/workspaces/Configuration/RequestMiddlewares.php @@ -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', ] ], ] -- GitLab