From df49c38b5c113a6aeedbfe8d035802c219e10387 Mon Sep 17 00:00:00 2001
From: Christian Kuhn <lolli@schwarzbu.ch>
Date: Sat, 23 Dec 2023 14:48:33 +0100
Subject: [PATCH] [TASK] Avoid TSFE->MP
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Based on the new PageInformation object attached to the
Request from #102715, we can get rid of usages of
TSFE->MP property.

The patch does this and streamlines an admin panel class
and the seo CanonicalGenerator class along the way.

Change-Id: I0597fdfe5392128a6ae1932305e21a7d25076e29
Resolves: #102797
Related: #102715
Releases: main
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/82277
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Tested-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: Stefan Bürk <stefan@buerk.tech>
---
 .../Modules/Info/GeneralInformation.php       | 30 +++-----
 .../adminpanel/Configuration/Services.yaml    |  2 +
 .../TypoScriptFrontendController.php          | 38 +++++------
 .../TypoScriptFrontendInitialization.php      |  2 -
 .../Classes/Typolink/PageLinkBuilder.php      | 14 ++--
 .../PageExposingTsfeMpParameter.typoscript    |  3 -
 .../Fixtures/PageHelloWorld.typoscript        |  3 +
 ...oScriptFrontendControllerTestUserFuncs.php | 11 ---
 .../TypoScriptFrontendControllerTest.php      | 46 +------------
 .../FrontendGenerationPageIndexingTrigger.php | 16 +++--
 .../Classes/Service/RedirectService.php       |  4 +-
 .../Classes/Canonical/CanonicalGenerator.php  | 68 ++++++++++---------
 .../Event/ModifyUrlForCanonicalTagEvent.php   |  3 +-
 .../Classes/XmlSitemap/XmlSitemapRenderer.php |  2 +-
 typo3/sysext/seo/Configuration/Services.yaml  |  3 +
 .../Canonical/CanonicalGeneratorTest.php      |  2 +-
 .../ModifyUrlForCanonicalTagEventTest.php     |  9 ++-
 17 files changed, 102 insertions(+), 154 deletions(-)
 delete mode 100644 typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/PageExposingTsfeMpParameter.typoscript
 create mode 100644 typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/PageHelloWorld.typoscript

diff --git a/typo3/sysext/adminpanel/Classes/Modules/Info/GeneralInformation.php b/typo3/sysext/adminpanel/Classes/Modules/Info/GeneralInformation.php
index c3f0c0f51c34..7f1f5700c982 100644
--- a/typo3/sysext/adminpanel/Classes/Modules/Info/GeneralInformation.php
+++ b/typo3/sysext/adminpanel/Classes/Modules/Info/GeneralInformation.php
@@ -22,27 +22,28 @@ use TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule;
 use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
 use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
 use TYPO3\CMS\Core\Context\Context;
-use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Page\AssetCollector;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
-use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
 /**
- * General information module displaying info about the current
- * request
+ * General information module displaying info about the current request
  *
  * @internal
  */
 class GeneralInformation extends AbstractSubModule implements DataProviderInterface
 {
+    public function __construct(
+        private readonly TimeTracker $timeTracker,
+        private readonly Context $context,
+    ) {}
+
     public function getDataToStore(ServerRequestInterface $request): ModuleData
     {
-        /** @var UserAspect $frontendUserAspect */
-        $frontendUserAspect = GeneralUtility::makeInstance(Context::class)->getAspect('frontend.user');
-        $tsfe = $this->getTypoScriptFrontendController();
+        $frontendUserAspect = $this->context->getAspect('frontend.user');
+        $tsfe = $request->getAttribute('frontend.controller');
         return new ModuleData(
             [
                 'post' => $request->getParsedBody(),
@@ -56,7 +57,7 @@ class GeneralInformation extends AbstractSubModule implements DataProviderInterf
                     'noCache' => !$this->isCachingAllowed($request),
                     'noCacheReasons' => $request->getAttribute('frontend.cache.instruction')->getDisabledCacheReasons(),
                     'countUserInt' => count($tsfe->config['INTincScript'] ?? []),
-                    'totalParsetime' => $this->getTimeTracker()->getParseTime(),
+                    'totalParsetime' => $this->timeTracker->getParseTime(),
                     'feUser' => [
                         'uid' => $frontendUserAspect->get('id') ?: 0,
                         'username' => $frontendUserAspect->get('username') ?: '',
@@ -145,7 +146,8 @@ class GeneralInformation extends AbstractSubModule implements DataProviderInterf
     {
         $documentSize = 0;
         if (!$this->isCachingAllowed($request)) {
-            $documentSize = mb_strlen($this->getTypoScriptFrontendController()->content, 'UTF-8');
+            $tsfe = $request->getAttribute('frontend.controller');
+            $documentSize = mb_strlen($tsfe->content, 'UTF-8');
         }
         return GeneralUtility::formatSize($documentSize);
     }
@@ -154,14 +156,4 @@ class GeneralInformation extends AbstractSubModule implements DataProviderInterf
     {
         return $request->getAttribute('frontend.cache.instruction')->isCachingAllowed();
     }
-
-    protected function getTypoScriptFrontendController(): TypoScriptFrontendController
-    {
-        return $GLOBALS['TSFE'];
-    }
-
-    protected function getTimeTracker(): TimeTracker
-    {
-        return GeneralUtility::makeInstance(TimeTracker::class);
-    }
 }
diff --git a/typo3/sysext/adminpanel/Configuration/Services.yaml b/typo3/sysext/adminpanel/Configuration/Services.yaml
index 23980b6a0b9d..cca02b9dd6c1 100644
--- a/typo3/sysext/adminpanel/Configuration/Services.yaml
+++ b/typo3/sysext/adminpanel/Configuration/Services.yaml
@@ -31,6 +31,8 @@ services:
     public: true
   TYPO3\CMS\Adminpanel\Modules\Debug\Events:
     public: true
+  TYPO3\CMS\Adminpanel\Modules\Info\GeneralInformation:
+    public: true
   TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall:
     public: true
 
diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
index cf561829dabc..ae52e4319faa 100644
--- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
+++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
@@ -26,7 +26,6 @@ use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
 use TYPO3\CMS\Core\Context\Context;
-use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Domain\Repository\PageRepository;
@@ -142,11 +141,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      */
     public ?PageRepository $sys_page = null;
 
-    /**
-     * @internal
-     */
-    public string $MP = '';
-
     /**
      * A central data array consisting of various keys, initialized and
      * processed at various places in the class.
@@ -336,6 +330,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      */
     public function __construct(Context $context, Site $site, SiteLanguage $siteLanguage, PageArguments $pageArguments)
     {
+        $this->sys_page = GeneralUtility::makeInstance(PageRepository::class);
         $this->context = $context;
         $this->site = $site;
         $this->language = $siteLanguage;
@@ -614,7 +609,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         // constant condition verdicts, the setup condition verdicts, plus various not TypoScript related details like
         // obviously the page id.
         $this->lock = GeneralUtility::makeInstance(ResourceMutex::class);
-        $this->newHash = $this->createHashBase($sysTemplateRows, $constantConditionList, $setupConditionList);
+        $this->newHash = $this->createHashBase($request, $sysTemplateRows, $constantConditionList, $setupConditionList);
         if ($isCachingAllowed) {
             if ($this->shouldAcquireCacheData($request)) {
                 // Try to get a page cache row.
@@ -731,7 +726,9 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 }
             }
 
-            $type = (int)($this->pageArguments->getPageType() ?: 0);
+            /** @var PageArguments $pageArguments */
+            $pageArguments = $request->getAttribute('routing');
+            $type = (int)($pageArguments->getPageType() ?: 0);
             $typoScriptPageTypeName = $setupArray['types.'][$type] ?? '';
             $typoScriptPageTypeSetup = $setupArray[$typoScriptPageTypeName . '.'] ?? null;
             if (!is_array($typoScriptPageTypeSetup)) {
@@ -849,28 +846,31 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      * the other requests wait until this finished and re-use the result.
      *
      * This hash is unique to the TS template and constant and setup condition verdict,
-     * the variables ->id, ->type, list of frontend user groups, ->MP (Mount Points) and cHash array.
+     * the variables ->id, ->type, list of frontend user groups, mount points and cHash array.
      *
      * @return string Page cache entry identifier also used as page generation lock
      */
-    protected function createHashBase(array $sysTemplateRows, array $constantConditionList, array $setupConditionList): string
+    protected function createHashBase(ServerRequestInterface $request, array $sysTemplateRows, array $constantConditionList, array $setupConditionList): string
     {
         // Fetch the list of user groups
-        /** @var UserAspect $userAspect */
         $userAspect = $this->context->getAspect('frontend.user');
+        $pageInformation = $request->getAttribute('frontend.page.information');
+        $site = $request->getAttribute('site');
+        $language = $request->getAttribute('language', $site->getDefaultLanguage());
+        $pageArguments = $request->getAttribute('routing');
         $hashParameters = [
-            'id' => $this->id,
-            'type' => (int)($this->pageArguments->getPageType() ?: 0),
-            'groupIds' => (string)implode(',', $userAspect->getGroupIds()),
-            'MP' => (string)$this->MP,
-            'site' => $this->site->getIdentifier(),
+            'id' => $pageInformation->getId(),
+            'type' => (int)($pageArguments->getPageType() ?: 0),
+            'groupIds' => implode(',', $userAspect->getGroupIds()),
+            'MP' => $pageInformation->getMountPoint(),
+            'site' => $site->getIdentifier(),
             // 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->language->getBase(),
+            'siteBase' => (string)$language->getBase(),
             // additional variation trigger for static routes
-            'staticRouteArguments' => $this->pageArguments->getStaticArguments(),
+            'staticRouteArguments' => $pageArguments->getStaticArguments(),
             // dynamic route arguments (if route was resolved)
-            'dynamicArguments' => $this->getRelevantParametersForCachingFromPageArguments($this->pageArguments),
+            'dynamicArguments' => $this->getRelevantParametersForCachingFromPageArguments($pageArguments),
             'sysTemplateRows' => $sysTemplateRows,
             'constantConditionList' => $constantConditionList,
             'setupConditionList' => $setupConditionList,
diff --git a/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php b/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
index 58bbe180c1f2..6d48184548eb 100644
--- a/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
+++ b/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
@@ -143,9 +143,7 @@ final class TypoScriptFrontendInitialization implements MiddlewareInterface
         );
         // b/w compat layer
         $controller->id = $pageInformation->getId();
-        $controller->sys_page = GeneralUtility::makeInstance(PageRepository::class);
         $controller->page = $pageInformation->getPageRecord();
-        $controller->MP = $pageInformation->getMountPoint();
         $controller->contentPid = $pageInformation->getContentFromPid();
         $controller->rootLine = $pageInformation->getRootLine();
 
diff --git a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
index 7bb855057bef..765d964c1055 100644
--- a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
+++ b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
@@ -576,22 +576,24 @@ class PageLinkBuilder extends AbstractTypolinkBuilder
      */
     protected function getClosestMountPointValueForPage(int $pageId): string
     {
-        $tsfe = $this->getTypoScriptFrontendController();
-        if (empty($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) || !$tsfe->MP) {
+        $request = $this->contentObjectRenderer->getRequest();
+        $mountPoint = $request->getAttribute('frontend.page.information')?->getMountPoint() ?? '';
+        if (empty($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) || !$mountPoint) {
             return '';
         }
         // Same page as current.
-        if ($tsfe->id === $pageId) {
-            return $tsfe->MP;
+        if (($request->getAttribute('frontend.page.information')?->getId() ?? 0) === $pageId) {
+            return $mountPoint;
         }
 
-        // Find closest mount point
+        // Find the closest mount point
         // Gets rootline of linked-to page
         try {
             $tCR_rootline = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get();
-        } catch (RootLineException $e) {
+        } catch (RootLineException) {
             $tCR_rootline = [];
         }
+        $tsfe = $this->getTypoScriptFrontendController();
         $inverseLocalRootLine = array_reverse($tsfe->config['rootLine'] ?? []);
         $rl_mpArray = [];
         $startMPaccu = false;
diff --git a/typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/PageExposingTsfeMpParameter.typoscript b/typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/PageExposingTsfeMpParameter.typoscript
deleted file mode 100644
index e4095aabffaf..000000000000
--- a/typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/PageExposingTsfeMpParameter.typoscript
+++ /dev/null
@@ -1,3 +0,0 @@
-page = PAGE
-page.10 = USER_INT
-page.10.userFunc = TYPO3\CMS\Frontend\Tests\Functional\Controller\Fixtures\TypoScriptFrontendControllerTestUserFuncs->pageExposingMpParameterUserInt
diff --git a/typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/PageHelloWorld.typoscript b/typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/PageHelloWorld.typoscript
new file mode 100644
index 000000000000..87ad609dfc1e
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/PageHelloWorld.typoscript
@@ -0,0 +1,3 @@
+page = PAGE
+page.10 = TEXT
+page.10.value = Hello world
diff --git a/typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/TypoScriptFrontendControllerTestUserFuncs.php b/typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/TypoScriptFrontendControllerTestUserFuncs.php
index a9d832238593..b895637e53ae 100644
--- a/typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/TypoScriptFrontendControllerTestUserFuncs.php
+++ b/typo3/sysext/frontend/Tests/Functional/Controller/Fixtures/TypoScriptFrontendControllerTestUserFuncs.php
@@ -44,15 +44,4 @@ final class TypoScriptFrontendControllerTestUserFuncs
     {
         return $GLOBALS['TSFE']->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:mod_tx_cms_webinfo_page');
     }
-
-    /**
-     * A USER_INT method referenced in PageExposingTsfeMpParameter.typoscript
-     */
-    public function pageExposingMpParameterUserInt(): string
-    {
-        if ($GLOBALS['TSFE']->MP === '') {
-            return 'empty';
-        }
-        return 'foo' . $GLOBALS['TSFE']->MP . 'bar';
-    }
 }
diff --git a/typo3/sysext/frontend/Tests/Functional/Controller/TypoScriptFrontendControllerTest.php b/typo3/sysext/frontend/Tests/Functional/Controller/TypoScriptFrontendControllerTest.php
index fa0ba641d611..e024b56afb96 100644
--- a/typo3/sysext/frontend/Tests/Functional/Controller/TypoScriptFrontendControllerTest.php
+++ b/typo3/sysext/frontend/Tests/Functional/Controller/TypoScriptFrontendControllerTest.php
@@ -160,50 +160,6 @@ alert(yes);', $body);
         self::assertStringContainsString('Pagetree Overview', $body);
     }
 
-    public static function mountPointParameterContainsOnlyValidMPValuesDataProvider(): array
-    {
-        return [
-            'no MP Parameter given' => [
-                '',
-                'empty',
-            ],
-            'single MP parameter given' => [
-                '592-182',
-                'foo592-182bar',
-            ],
-            'invalid characters included' => [
-                '12-13,a34-45/',
-                'foo12-13,34-45bar',
-            ],
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider mountPointParameterContainsOnlyValidMPValuesDataProvider
-     */
-    public function mountPointParameterContainsOnlyValidMPValues(string $inputMp, string $expected): void
-    {
-        $this->importCSVDataSet(__DIR__ . '/DataSet/LiveDefaultPages.csv');
-        $this->setUpFrontendRootPage(
-            2,
-            ['EXT:frontend/Tests/Functional/Controller/Fixtures/PageExposingTsfeMpParameter.typoscript']
-        );
-        $this->writeSiteConfiguration(
-            'test',
-            $this->buildSiteConfiguration(2, 'https://website.local/'),
-            [$this->buildDefaultLanguageConfiguration('EN', '/en/')]
-        );
-
-        $response = $this->executeFrontendSubRequest(
-            (new InternalRequest('https://website.local/en/'))
-                ->withPageId(88)
-                ->withQueryParameter('MP', $inputMp)
-        );
-        $body = (string)$response->getBody();
-        self::assertStringContainsString($expected, $body);
-    }
-
     public static function getFromCacheSetsConfigRootlineToLocalRootlineDataProvider(): array
     {
         $page1 = [
@@ -381,7 +337,7 @@ alert(yes);', $body);
         $this->importCSVDataSet(__DIR__ . '/DataSet/LiveDefaultPages.csv');
         $this->setUpFrontendRootPage(
             2,
-            ['EXT:frontend/Tests/Functional/Controller/Fixtures/PageExposingTsfeMpParameter.typoscript']
+            ['EXT:frontend/Tests/Functional/Controller/Fixtures/PageHelloWorld.typoscript']
         );
         $this->writeSiteConfiguration(
             'test',
diff --git a/typo3/sysext/indexed_search/Classes/EventListener/FrontendGenerationPageIndexingTrigger.php b/typo3/sysext/indexed_search/Classes/EventListener/FrontendGenerationPageIndexingTrigger.php
index f4f2b5c14ca8..75b97c92bf72 100644
--- a/typo3/sysext/indexed_search/Classes/EventListener/FrontendGenerationPageIndexingTrigger.php
+++ b/typo3/sysext/indexed_search/Classes/EventListener/FrontendGenerationPageIndexingTrigger.php
@@ -18,11 +18,13 @@ declare(strict_types=1);
 namespace TYPO3\CMS\IndexedSearch\EventListener;
 
 use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Attribute\AsEventListener;
 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\PageTitle\PageTitleProviderManager;
+use TYPO3\CMS\Core\Routing\PageArguments;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3\CMS\Frontend\Event\AfterCacheableContentIsGeneratedEvent;
@@ -83,7 +85,7 @@ class FrontendGenerationPageIndexingTrigger
         }
         // Init and start indexing
         $this->indexer->forceIndexing = $forceIndexing;
-        $this->indexer->init($this->initializeIndexerConfiguration($tsfe, $languageAspect));
+        $this->indexer->init($this->initializeIndexerConfiguration($event->getRequest(), $tsfe, $languageAspect));
         $this->indexer->indexTypo3PageContent();
         $this->timeTracker->pull();
     }
@@ -92,24 +94,26 @@ class FrontendGenerationPageIndexingTrigger
      * Setting up internal configuration from config array based on TypoScriptFrontendController
      * Information about page for which the indexing takes place
      */
-    protected function initializeIndexerConfiguration(TypoScriptFrontendController $tsfe, LanguageAspect $languageAspect): array
+    protected function initializeIndexerConfiguration(ServerRequestInterface $request, TypoScriptFrontendController $tsfe, LanguageAspect $languageAspect): array
     {
-        $pageArguments = $tsfe->getPageArguments();
+        /** @var PageArguments $pageArguments */
+        $pageArguments = $request->getAttribute('routing');
+        $pageInformation = $request->getAttribute('frontend.page.information');
         $configuration = [
             // Page id
-            'id' => $tsfe->id,
+            'id' => $pageInformation->getId(),
             // Page type
             'type' => $pageArguments->getPageType(),
             // site language id of the language of the indexing.
             'sys_language_uid' => $languageAspect->getId(),
             // MP variable, if any (Mount Points)
-            'MP' => $tsfe->MP,
+            'MP' => $pageInformation->getMountPoint(),
             // Group list
             'gr_list' => implode(',', $this->context->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1])),
             // page arguments array
             'staticPageArguments' => $pageArguments->getStaticArguments(),
             // The creation date of the TYPO3 page
-            'crdate' => $tsfe->page['crdate'],
+            'crdate' => $pageInformation->getPageRecord()['crdate'],
             'rootline_uids' => [],
         ];
 
diff --git a/typo3/sysext/redirects/Classes/Service/RedirectService.php b/typo3/sysext/redirects/Classes/Service/RedirectService.php
index 368a3b5007fa..48a15e8d2aa8 100644
--- a/typo3/sysext/redirects/Classes/Service/RedirectService.php
+++ b/typo3/sysext/redirects/Classes/Service/RedirectService.php
@@ -23,7 +23,6 @@ use Psr\Http\Message\UriInterface;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Core\Context\Context;
-use TYPO3\CMS\Core\Domain\Repository\PageRepository;
 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Http\Uri;
 use TYPO3\CMS\Core\LinkHandling\LinkService;
@@ -395,6 +394,7 @@ class RedirectService implements LoggerAwareInterface
         $mergedQueryParams = array_merge($queryParams, $queryParamsFromRequest);
         $originalRequest = $originalRequest->withQueryParams($mergedQueryParams);
         $pageArguments = new PageArguments($site->getRootPageId(), '0', []);
+        $originalRequest = $originalRequest->withAttribute('routing', $pageArguments);
         $pageInformation = new PageInformation();
         $pageInformation->setId($site->getRootPageId());
         $pageInformation->setMountPoint('');
@@ -411,9 +411,7 @@ class RedirectService implements LoggerAwareInterface
         );
         // b/w compat layer
         $controller->id = $pageInformation->getId();
-        $controller->sys_page = GeneralUtility::makeInstance(PageRepository::class);
         $controller->page = $pageInformation->getPageRecord();
-        $controller->MP = $pageInformation->getMountPoint();
         $controller->contentPid = $pageInformation->getContentFromPid();
         $controller->rootLine = $pageInformation->getRootLine();
         $newRequest = $controller->getFromCache($originalRequest);
diff --git a/typo3/sysext/seo/Classes/Canonical/CanonicalGenerator.php b/typo3/sysext/seo/Classes/Canonical/CanonicalGenerator.php
index 6de1412d3c6d..a291d207a09f 100644
--- a/typo3/sysext/seo/Classes/Canonical/CanonicalGenerator.php
+++ b/typo3/sysext/seo/Classes/Canonical/CanonicalGenerator.php
@@ -18,10 +18,12 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Seo\Canonical;
 
 use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Domain\Page;
 use TYPO3\CMS\Core\Domain\Repository\PageRepository;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\RootlineUtility;
+use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3\CMS\Frontend\Utility\CanonicalizationUtility;
 use TYPO3\CMS\Seo\Event\ModifyUrlForCanonicalTagEvent;
@@ -31,30 +33,26 @@ use TYPO3\CMS\Seo\Event\ModifyUrlForCanonicalTagEvent;
  *
  * @internal this class is not part of TYPO3's Core API.
  */
-class CanonicalGenerator
+readonly class CanonicalGenerator
 {
-    protected TypoScriptFrontendController $typoScriptFrontendController;
-    protected PageRepository $pageRepository;
-    protected EventDispatcherInterface $eventDispatcher;
-
-    public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null, EventDispatcherInterface $eventDispatcher = null)
-    {
-        $this->eventDispatcher = $eventDispatcher ?? GeneralUtility::makeInstance(EventDispatcherInterface::class);
-        $this->typoScriptFrontendController = $typoScriptFrontendController ?? $this->getTypoScriptFrontendController();
-        $this->pageRepository = GeneralUtility::makeInstance(PageRepository::class);
-    }
+    public function __construct(
+        private EventDispatcherInterface $eventDispatcher,
+    ) {}
 
     public function generate(array $params): string
     {
-        if ($this->typoScriptFrontendController->config['config']['disableCanonical'] ?? false) {
+        $typoScriptFrontendController = $this->getTypoScriptFrontendController();
+        if ($typoScriptFrontendController->config['config']['disableCanonical'] ?? false) {
             return '';
         }
 
-        $event = new ModifyUrlForCanonicalTagEvent('', $params['request'], new Page($params['page']));
+        /** @var ServerRequestInterface $request */
+        $request = $params['request'];
+        $event = new ModifyUrlForCanonicalTagEvent($request, new Page($params['page']));
         $event = $this->eventDispatcher->dispatch($event);
         $href = $event->getUrl();
 
-        if (empty($href) && (int)$this->typoScriptFrontendController->page['no_index'] === 1) {
+        if (empty($href) && (int)$typoScriptFrontendController->page['no_index'] === 1) {
             return '';
         }
 
@@ -68,7 +66,7 @@ class CanonicalGenerator
         }
         if (empty($href)) {
             // 3) Fallback, create canonical URL
-            $href = $this->checkDefaultCanonical();
+            $href = $this->checkDefaultCanonical($request);
         }
 
         if (!empty($href)) {
@@ -76,7 +74,7 @@ class CanonicalGenerator
                 'rel' => 'canonical',
                 'href' => $href,
             ], true) . '/>' . LF;
-            $this->typoScriptFrontendController->additionalHeaderData[] = $canonical;
+            $typoScriptFrontendController->additionalHeaderData[] = $canonical;
             return $canonical;
         }
         return '';
@@ -84,9 +82,10 @@ class CanonicalGenerator
 
     protected function checkForCanonicalLink(): string
     {
-        if (!empty($this->typoScriptFrontendController->page['canonical_link'])) {
-            return $this->typoScriptFrontendController->cObj->createUrl([
-                'parameter' => $this->typoScriptFrontendController->page['canonical_link'],
+        $typoScriptFrontendController = $this->getTypoScriptFrontendController();
+        if (!empty($typoScriptFrontendController->page['canonical_link'])) {
+            return $typoScriptFrontendController->cObj->createUrl([
+                'parameter' => $typoScriptFrontendController->page['canonical_link'],
                 'forceAbsoluteUrl' => true,
             ]);
         }
@@ -95,14 +94,16 @@ class CanonicalGenerator
 
     protected function checkContentFromPid(): string
     {
-        if ($this->typoScriptFrontendController->contentPid !== $this->typoScriptFrontendController->id) {
-            $parameter = $this->typoScriptFrontendController->contentPid;
+        $typoScriptFrontendController = $this->getTypoScriptFrontendController();
+        if ($typoScriptFrontendController->contentPid !== $typoScriptFrontendController->id) {
+            $parameter = $typoScriptFrontendController->contentPid;
             if ($parameter > 0) {
-                $targetPage = $this->pageRepository->getPage($parameter, true);
+                $pageRepository = GeneralUtility::makeInstance(PageRepository::class);
+                $targetPage = $pageRepository->getPage($parameter, true);
                 if (!empty($targetPage['canonical_link'])) {
                     $parameter = $targetPage['canonical_link'];
                 }
-                return $this->typoScriptFrontendController->cObj->createUrl([
+                return $typoScriptFrontendController->cObj->createUrl([
                     'parameter' => $parameter,
                     'forceAbsoluteUrl' => true,
                 ]);
@@ -111,10 +112,11 @@ class CanonicalGenerator
         return '';
     }
 
-    protected function checkDefaultCanonical(): string
+    protected function checkDefaultCanonical(ServerRequestInterface $request): string
     {
+        $typoScriptFrontendController = $this->getTypoScriptFrontendController();
         // We should only create a canonical link to the target, if the target is within a valid site root
-        $inSiteRoot = $this->isPageWithinSiteRoot($this->typoScriptFrontendController->id);
+        $inSiteRoot = $this->isPageWithinSiteRoot($typoScriptFrontendController->id);
         if (!$inSiteRoot) {
             return '';
         }
@@ -122,24 +124,26 @@ class CanonicalGenerator
         // Temporarily remove current mountpoint information as we want to have the
         // URL of the target page and not of the page within the mountpoint if the
         // current page is a mountpoint.
-        $previousMp = $this->typoScriptFrontendController->MP;
-        $this->typoScriptFrontendController->MP = '';
-
-        $link = $this->typoScriptFrontendController->cObj->createUrl([
-            'parameter' => $this->typoScriptFrontendController->id . ',' . $this->typoScriptFrontendController->getPageArguments()->getPageType(),
+        $pageInformation = clone $request->getAttribute('frontend.page.information');
+        $pageInformation->setMountPoint('');
+        $request = $request->withAttribute('frontend.page.information', $pageInformation);
+        $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class, $typoScriptFrontendController);
+        $cObj->setRequest($request);
+        $cObj->start($pageInformation->getPageRecord(), 'pages');
+        $link = $cObj->createUrl([
+            'parameter' => $typoScriptFrontendController->id . ',' . $request->getAttribute('routing')->getPageType(),
             'forceAbsoluteUrl' => true,
             'addQueryString' => true,
             'addQueryString.' => [
                 'exclude' => implode(
                     ',',
                     CanonicalizationUtility::getParamsToExcludeForCanonicalizedUrl(
-                        $this->typoScriptFrontendController->id,
+                        $typoScriptFrontendController->id,
                         (array)$GLOBALS['TYPO3_CONF_VARS']['FE']['additionalCanonicalizedUrlParameters']
                     )
                 ),
             ],
         ]);
-        $this->typoScriptFrontendController->MP = $previousMp;
         return $link;
     }
 
diff --git a/typo3/sysext/seo/Classes/Event/ModifyUrlForCanonicalTagEvent.php b/typo3/sysext/seo/Classes/Event/ModifyUrlForCanonicalTagEvent.php
index 69b20ff60280..0227192cadd7 100644
--- a/typo3/sysext/seo/Classes/Event/ModifyUrlForCanonicalTagEvent.php
+++ b/typo3/sysext/seo/Classes/Event/ModifyUrlForCanonicalTagEvent.php
@@ -25,8 +25,9 @@ use TYPO3\CMS\Core\Domain\Page;
  */
 final class ModifyUrlForCanonicalTagEvent
 {
+    private string $url = '';
+
     public function __construct(
-        private string $url,
         private readonly ServerRequestInterface $request,
         private readonly Page $page
     ) {}
diff --git a/typo3/sysext/seo/Classes/XmlSitemap/XmlSitemapRenderer.php b/typo3/sysext/seo/Classes/XmlSitemap/XmlSitemapRenderer.php
index 9957496dc01e..d005b7b10117 100644
--- a/typo3/sysext/seo/Classes/XmlSitemap/XmlSitemapRenderer.php
+++ b/typo3/sysext/seo/Classes/XmlSitemap/XmlSitemapRenderer.php
@@ -57,7 +57,7 @@ final class XmlSitemapRenderer
         $templatePaths->setFormat('xml');
         $sitemapType = $typoScriptConfiguration['sitemapType'] ?? 'xmlSitemap';
         $view = GeneralUtility::makeInstance(TemplateView::class, $renderingContext);
-        $view->assign('type', $request->getAttribute('frontend.controller')->getPageArguments()->getPageType());
+        $view->assign('type', $request->getAttribute('routing')->getPageType());
         $view->assign('sitemapType', $sitemapType);
         $configConfiguration = $configurationArrayWithoutDots['config'] ?? [];
         if (!empty($sitemapName = ($request->getQueryParams()['sitemap'] ?? null))) {
diff --git a/typo3/sysext/seo/Configuration/Services.yaml b/typo3/sysext/seo/Configuration/Services.yaml
index 04e09af63e75..38f95453bf81 100644
--- a/typo3/sysext/seo/Configuration/Services.yaml
+++ b/typo3/sysext/seo/Configuration/Services.yaml
@@ -10,6 +10,9 @@ services:
   TYPO3\CMS\Seo\MetaTag\MetaTagGenerator:
     public: true
 
+  TYPO3\CMS\Seo\Canonical\CanonicalGenerator:
+    public: true
+
   TYPO3\CMS\Seo\XmlSitemap\XmlSitemapRenderer:
     public: true
 
diff --git a/typo3/sysext/seo/Tests/Functional/Canonical/CanonicalGeneratorTest.php b/typo3/sysext/seo/Tests/Functional/Canonical/CanonicalGeneratorTest.php
index ff4d35508441..ef84aa387663 100644
--- a/typo3/sysext/seo/Tests/Functional/Canonical/CanonicalGeneratorTest.php
+++ b/typo3/sysext/seo/Tests/Functional/Canonical/CanonicalGeneratorTest.php
@@ -175,7 +175,7 @@ final class CanonicalGeneratorTest extends FunctionalTestCase
         $GLOBALS['TSFE']->id = 123;
         $GLOBALS['TSFE']->page['no_index'] = 1;
 
-        (new CanonicalGenerator())->generate(['request' => new ServerRequest('https://example.com'), 'page' => ['uid' => 123]]);
+        $this->get(CanonicalGenerator::class)->generate(['request' => new ServerRequest('https://example.com'), 'page' => ['uid' => 123]]);
 
         self::assertInstanceOf(ModifyUrlForCanonicalTagEvent::class, $modifyUrlForCanonicalTagEvent);
         self::assertEmpty('', $modifyUrlForCanonicalTagEvent->getUrl());
diff --git a/typo3/sysext/seo/Tests/Unit/Event/ModifyUrlForCanonicalTagEventTest.php b/typo3/sysext/seo/Tests/Unit/Event/ModifyUrlForCanonicalTagEventTest.php
index 0607b423feaa..8abe06c2e375 100644
--- a/typo3/sysext/seo/Tests/Unit/Event/ModifyUrlForCanonicalTagEventTest.php
+++ b/typo3/sysext/seo/Tests/Unit/Event/ModifyUrlForCanonicalTagEventTest.php
@@ -19,7 +19,6 @@ namespace TYPO3\CMS\Seo\Tests\Unit\Event;
 
 use TYPO3\CMS\Core\Domain\Page;
 use TYPO3\CMS\Core\Http\ServerRequest;
-use TYPO3\CMS\Core\Http\Uri;
 use TYPO3\CMS\Seo\Event\ModifyUrlForCanonicalTagEvent;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
@@ -30,12 +29,12 @@ final class ModifyUrlForCanonicalTagEventTest extends UnitTestCase
      */
     public function gettersReturnInitializedObjects(): void
     {
-        $url = (string)new Uri('https://example.com');
-        $request = (new ServerRequest($url));
+        $request = (new ServerRequest(''));
         $page = new Page(['uid' => 123]);
-        $event = new ModifyUrlForCanonicalTagEvent($url, $request, $page);
+        $event = new ModifyUrlForCanonicalTagEvent($request, $page);
+        $event->setUrl('https://example.com');
 
-        self::assertEquals($url, $event->getUrl());
+        self::assertEquals('https://example.com', $event->getUrl());
         self::assertEquals($request, $event->getRequest());
         self::assertEquals($page, $event->getPage());
     }
-- 
GitLab