[!!!][TASK] Remove deprecated frontend-related hooks and include scripts
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Middleware / PageResolver.php
index f2ec184..f99baf7 100644 (file)
@@ -23,13 +23,18 @@ 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\Http\RedirectResponse;
-use TYPO3\CMS\Core\Routing\RouteResult;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\FrontendWorkspaceRestriction;
+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\Site\Entity\SiteInterface;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Frontend\Controller\ErrorController;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
@@ -72,59 +77,69 @@ class PageResolver implements MiddlewareInterface
 
         // Resolve the page ID based on TYPO3's native routing functionality
         if ($hasSiteConfiguration) {
-            /** @var RouteResult $previousResult */
+            /** @var SiteRouteResult $previousResult */
             $previousResult = $request->getAttribute('routing', null);
-            if ($previousResult && $previousResult->getTail()) {
+            if (!$previousResult) {
+                return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
+                    $request,
+                    'The requested page does not exist',
+                    ['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
+                );
+            }
+
+            $requestId = (string)($request->getQueryParams()['id'] ?? '');
+            if (!empty($requestId) && !empty($page = $this->resolvePageId($requestId))) {
+                // Legacy URIs (?id=12345) takes precedence, not matter if a route is given
+                $pageArguments = new PageArguments(
+                    (int)($page['l10n_parent'] ?: $page['uid']),
+                    (string)($request->getQueryParams()['type'] ?? '0'),
+                    [],
+                    [],
+                    $request->getQueryParams()
+                );
+            } else {
                 // Check for the route
-                $routeResult = $site->getRouter()->matchRequest($request, $previousResult);
-                if ($routeResult === null) {
-                    return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
-                        $request,
-                        'The requested page does not exist',
-                        ['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
-                    );
-                }
-                $request = $request->withAttribute('routing', $routeResult);
-                if (is_array($routeResult['page'])) {
-                    $page = $routeResult['page'];
-                    $this->controller->id = (int)($page['l10n_parent'] > 0 ? $page['l10n_parent'] : $page['uid']);
-                    $tail = $routeResult->getTail();
-                    $requestedUri = $request->getUri();
-                    // the request was called with "/my-page" but it's actually called "/my-page/", let's do a redirect
-                    if ($tail === '' && substr($requestedUri->getPath(), -1) !== substr($page['slug'], -1)) {
-                        $uri = $requestedUri->withPath($requestedUri->getPath() . '/');
-                        return new RedirectResponse($uri, 307);
-                    }
-                    if ($tail === '/') {
-                        $uri = $requestedUri->withPath(rtrim($requestedUri->getPath(), '/'));
-                        return new RedirectResponse($uri, 307);
-                    }
-                    if (!empty($tail)) {
-                        // @todo: kick in the resolvers for the RouteEnhancers at this point
-                        return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
-                            $request,
-                            'The requested page does not exist',
-                            ['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
-                        );
-                    }
-                } else {
+                try {
+                    $pageArguments = $site->getRouter()->matchRequest($request, $previousResult);
+                } catch (RouteNotFoundException $e) {
                     return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
                         $request,
                         'The requested page does not exist',
                         ['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
                     );
                 }
-                // At this point, we later get further route modifiers
-                // for bw-compat we update $GLOBALS[TYPO3_REQUEST] to be used later in TSFE.
-                $GLOBALS['TYPO3_REQUEST'] = $request;
             }
+            if (!$pageArguments->getPageId()) {
+                return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
+                    $request,
+                    'The requested page does not exist',
+                    ['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
+                );
+            }
+
+            $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(
+                    $request,
+                    'The requested URL is not distinct',
+                    ['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
+                );
+            }
+
+            // merge the PageArguments with the request query parameters
+            $queryParams = array_replace_recursive($request->getQueryParams(), $pageArguments->getArguments());
+            $request = $request->withQueryParams($queryParams);
+            $this->controller->setPageArguments($pageArguments);
+
+            // At this point, we later get further route modifiers
+            // for bw-compat we update $GLOBALS[TYPO3_REQUEST] to be used later in TSFE.
+            $GLOBALS['TYPO3_REQUEST'] = $request;
         } else {
             // old-school page resolving for realurl, cooluri etc.
             $this->controller->siteScript = $request->getAttribute('normalizedParams')->getSiteScript();
-            if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'])) {
-                trigger_error('The "checkAlternativeIdMethods-PostProc" hook will be removed in TYPO3 v10.0 in favor of PSR-15. Use a middleware instead.', E_USER_DEPRECATED);
-                $this->checkAlternativeIdMethods($this->controller);
-            }
         }
 
         $this->controller->determineId();
@@ -134,34 +149,49 @@ class PageResolver implements MiddlewareInterface
             unset($GLOBALS['BE_USER']);
             // Register an empty backend user as aspect
             $this->setBackendUserAspect(GeneralUtility::makeInstance(Context::class), null);
-            if (!$hasSiteConfiguration) {
-                $this->checkAlternativeIdMethods($this->controller);
-            }
             $this->controller->determineId();
         }
 
-        // Evaluate the cache hash parameter
-        $this->controller->makeCacheHash($request);
-
         return $handler->handle($request);
     }
 
     /**
-     * Provides ways to bypass the '?id=[xxx]&type=[xx]' format, using either PATH_INFO or Server Rewrites
-     *
-     * Two options:
-     * 1) Use PATH_INFO (also Apache) to extract id and type from that var. Does not require any special modules compiled with apache. (less typical)
-     * 2) Using hook which enables features like those provided from "realurl" extension (AKA "Speaking URLs")
-     *
-     * @param TypoScriptFrontendController $tsfe
+     * @param string $pageId
+     * @return array|null
      */
-    protected function checkAlternativeIdMethods(TypoScriptFrontendController $tsfe)
+    protected function resolvePageId(string $pageId): ?array
     {
-        // Call post processing function for custom URL methods.
-        $_params = ['pObj' => &$tsfe];
-        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'] ?? [] as $_funcRef) {
-            GeneralUtility::callUserFunction($_funcRef, $_params, $tsfe);
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('pages');
+        $queryBuilder
+            ->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(FrontendWorkspaceRestriction::class));
+
+        if (MathUtility::canBeInterpretedAsInteger($pageId)) {
+            $constraint = $queryBuilder->expr()->eq(
+                'uid',
+                $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
+            );
+        } else {
+            $constraint = $queryBuilder->expr()->eq(
+                'alias',
+                $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_STR)
+            );
+        }
+
+        $statement = $queryBuilder
+            ->select('uid', 'l10n_parent', 'pid')
+            ->from('pages')
+            ->where($constraint)
+            ->execute();
+
+        $page = $statement->fetch();
+        if (empty($page)) {
+            return null;
         }
+        return $page;
     }
 
     /**