diff --git a/composer.json b/composer.json index 779027662297c4ad00af653a9a8bb33331db812a..bcb67589840dfc99e96954f3b1e954597baefe4e 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 151f8925669a058512ea851f45cedf3fb312686d..cec5b8a2733e22c95c3e0b75f0b15b7ba24f9015 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 0b08aa5ad172c740399f03521c79f0c56bc8f05a..4ca5dcd3cc25802a02d3e62b8dcbd761c11aeb50 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 7f624e2c1f8cfbcf0682a5a6f735b929105f29b5..d7c31b213b0cf56db97c79aaad53f95121872c59 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 d655d45811408efc5163d5a07d0785e75e325f40..94abba36d190a2b86e2847c70df84bd94919155a 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 b38736a5a5ca6ef76312567138d54120c4860d59..e34079e3af7a3d3faca1d7b728ec65b4fcb70e4f 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 b7e0f625595e1da1b9300743eb5587b0e1170f70..eae14dc02e5ac35879895cd7b429839667e0f1a6 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 482ae66dc1e2a76c5fc4ea376b2e4bc0bfcb40b5..981094bf772882dad82528f3b96f5e7db178b9a2 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 e94987739a6b37014a5a085560e4f8094d2d766c..c264045f72d23a5192ea8d3f8607c805d974da07 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 cebd55804c8c654520e2551e8c657187b33c1258..b9b1fdf2082d4c9f7bf7ee922b68a56482d0aa9d 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 a8e332c5b1cbd1286d651edf59a9ae695a3d002c..09a7ee440b3037057365c95e002dc5144beb477f 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 7d6ff74ac03211777cb69c02e19cde20dd895e1a..6bf951d4e886045f124bb274309e58626df02ed9 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 6315a9a1fb9d73dd57f6aa9027d3f38579844e32..42d9136990df5e2d3f282dfa6135935f99415500 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 e0f903950f9907982b2de10947809d5492503ff8..915efa5a2a43bb6ab72e7d63728e484b8d2aa8c1 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 184acf5f4480acb54c752999e6b44e1054308c19..1e9b2775fac7be7651d1e1b8490b991948c38602 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 55fba5f71d0a7bedb9839f5d54f73ba3133b99fc..7af1b35a48c8d0a26e70c43dfe3f9d2436098164 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 1b83a471c23f7a53c32d7ea7b84e543d98536a72..e184dc30c259550e92ce02d66f11fdf3e60bac8b 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 bf043ef2cd1a85bf2f1fd1a0e5e7b923e6f255a8..41791e69d37f29b2cf778638755b616dbc2da4f2 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 b535eb6dfe86ec68614640fe20cac8e77425dd97..35e63418b3998fb332c4951e8c63b9808653acfa 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 113801659c2d8afaaee30a136e9fd315d2e90736..a31f79c4d86fb6a0953bb4d2a7bff849365b7140 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 0269b62e667f0c8a1d1c1a1b676d6910e9da4fc9..76abf1a732693268a9c7e24b130dbb76992bf763 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 b94278afbc6e7cb11df4733136eefc87e1b1abf0..2dff9524ef4015b7d85de7f7a3610f352b1f9c14 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 12e4f3d7ef46fffaf4bba0378e1dc20b595962c3..6aad12893f4aaaaa0451acf06db87b48ab58f5f0 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 b7e195cd3dbf771ab33487384440c97f32f677c1..288afab3e2f81ffd2a462432ce7218df88f26d4a 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 490f435ff1283553f76aaf1e8439df9a206065cc..94f780d57e7829d6065edfcadd45ee8085ca046e 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 2fa9a64c0f58654d90a77271bcbf1de253651ed8..c410ad95bb095c0d29b6ed1e2063f2920d540444 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 eeb3a84f3eaa32b7d06e786528cabca8240abea3..98d2517b62181896231644426b55fd2eeed4dcf7 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', ] ], ]