[BUGFIX] Ensure most site related exceptions are handled 37/58537/6
authorMarkus Klein <markus.klein@typo3.org>
Mon, 1 Oct 2018 16:47:53 +0000 (18:47 +0200)
committerBenni Mack <benni@typo3.org>
Mon, 1 Oct 2018 18:32:57 +0000 (20:32 +0200)
Make sure that in most places any site related exception is handled
in a graceful way to avoid negative UX.

Resolves: #86522
Releases: master
Change-Id: I3b0d7f9ce63351f8dd7bb6b4988704fc8a3d0a9f
Reviewed-on: https://review.typo3.org/58537
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Richard Haeser <richard@maxserv.com>
Reviewed-by: Jurian Janssen <jurian.janssen@gmail.com>
Reviewed-by: Andreas Wolf <andreas.wolf@typo3.org>
Reviewed-by: Daniel Goerz <daniel.goerz@posteo.de>
Tested-by: Daniel Goerz <daniel.goerz@posteo.de>
Reviewed-by: Jörg Bösche <typo3@joergboesche.de>
Tested-by: Richard Haeser <richard@maxserv.com>
Tested-by: TYPO3com <no-reply@typo3.com>
Tested-by: Benni Mack <benni@typo3.org>
16 files changed:
typo3/sysext/backend/Classes/Form/FormDataProvider/SiteTcaInline.php
typo3/sysext/backend/Classes/Utility/BackendUtility.php
typo3/sysext/backend/Classes/View/PageLayoutView.php
typo3/sysext/core/Classes/DataHandling/SlugHelper.php
typo3/sysext/core/Classes/Error/PageErrorHandler/PageContentErrorHandler.php
typo3/sysext/core/Classes/Page/PageRenderer.php
typo3/sysext/core/Classes/Routing/PageRouter.php
typo3/sysext/core/Classes/Routing/SiteMatcher.php
typo3/sysext/core/Classes/Site/Entity/NullSite.php
typo3/sysext/core/Classes/Site/Entity/PseudoSite.php
typo3/sysext/core/Classes/Site/SiteFinder.php
typo3/sysext/frontend/Classes/Middleware/StaticRouteResolver.php
typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
typo3/sysext/viewpage/Classes/Controller/ViewModuleController.php
typo3/sysext/workspaces/Classes/Controller/PreviewController.php
typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php

index 37a6291..3f7e7b7 100644 (file)
@@ -23,6 +23,7 @@ use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -115,7 +116,11 @@ class SiteTcaInline extends AbstractDatabaseRecordProvider implements FormDataPr
         if ($result['command'] === 'edit') {
             $siteConfigurationForPageUid = (int)$result['databaseRow']['rootPageId'][0];
             $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
-            $site = $siteFinder->getSiteByRootPageId($siteConfigurationForPageUid);
+            try {
+                $site = $siteFinder->getSiteByRootPageId($siteConfigurationForPageUid);
+            } catch (SiteNotFoundException $e) {
+                $site = null;
+            }
             $siteConfiguration = $site ? $site->getConfiguration() : [];
             if (is_array($siteConfiguration[$fieldName])) {
                 $connectedUids = array_keys($siteConfiguration[$fieldName]);
index f63382f..93436a7 100644 (file)
@@ -2675,7 +2675,6 @@ class BackendUtility
             // Check if the page (= its rootline) has a site attached, otherwise just keep the URL as is
             $rootLine = $rootLine ?? BackendUtility::BEgetRootLine($pageUid);
             try {
-                /** @var Site $site */
                 $site = $siteFinder->getSiteByPageId((int)$pageUid, $rootLine);
                 // Create a multi-dimensional array out of the additional get vars
                 $additionalQueryParams = [];
index 9ae82ee..b664bfd 100644 (file)
@@ -2979,6 +2979,7 @@ class PageLayoutView implements LoggerAwareInterface
      * @param string $search Search word, if any
      * @param int $levels Number of levels to search down the page tree
      * @param int $showLimit Limit of records to be listed.
+     * @throws SiteNotFoundException
      */
     public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
     {
index 4c4fa37..30b8bd0 100644 (file)
@@ -239,6 +239,7 @@ class SlugHelper
      * @param int $pageId
      * @param int $languageId
      * @return bool
+     * @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
      */
     public function isUniqueInSite(string $slug, $recordId, int $pageId, int $languageId): bool
     {
@@ -283,6 +284,7 @@ class SlugHelper
      * @param int $realPid pageID (already workspace-resolved)
      * @param int $languageId the language ID realm to be searched for
      * @return string
+     * @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
      */
     public function buildSlugForUniqueInSite(string $slug, $recordId, int $realPid, int $languageId): string
     {
index 0c50c56..6d2b275 100644 (file)
@@ -18,8 +18,10 @@ namespace TYPO3\CMS\Core\Error\PageErrorHandler;
 
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\LinkHandling\LinkService;
+use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
 use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -64,8 +66,12 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
      */
     public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
     {
-        $resolvedUrl = $this->resolveUrl($request, $this->errorHandlerConfiguration['errorContentSource']);
-        $content = GeneralUtility::getUrl($resolvedUrl);
+        try {
+            $resolvedUrl = $this->resolveUrl($request, $this->errorHandlerConfiguration['errorContentSource']);
+            $content = GeneralUtility::getUrl($resolvedUrl);
+        } catch (InvalidRouteArgumentsException | SiteNotFoundException $e) {
+            $content = 'Invalid error handler configuration: ' . $this->errorHandlerConfiguration['errorContentSource'];
+        }
         return new HtmlResponse($content, $this->statusCode);
     }
 
@@ -75,6 +81,8 @@ class PageContentErrorHandler implements PageErrorHandlerInterface
      * @param ServerRequestInterface $request
      * @param string $typoLinkUrl
      * @return string
+     * @throws SiteNotFoundException
+     * @throws InvalidRouteArgumentsException
      */
     protected function resolveUrl(ServerRequestInterface $request, string $typoLinkUrl): string
     {
index ac99cca..5e9414b 100644 (file)
@@ -1593,7 +1593,7 @@ class PageRenderer implements \TYPO3\CMS\Core\SingletonInterface
      *
      * @param string $namespace
      * @param string $key
-     * @param string $value
+     * @param mixed $value
      */
     public function addInlineSetting($namespace, $key, $value)
     {
index 2801912..023b0b3 100644 (file)
@@ -28,6 +28,7 @@ use TYPO3\CMS\Core\Context\LanguageAspect;
 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\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Http\Uri;
 use TYPO3\CMS\Core\Routing\Aspect\AspectFactory;
 use TYPO3\CMS\Core\Routing\Aspect\MappableProcessor;
@@ -169,6 +170,7 @@ class PageRouter implements RouterInterface
      * @param string $fragment additional #my-fragment part
      * @param string $type see the RouterInterface for possible types
      * @return UriInterface
+     * @throws InvalidRouteArgumentsException
      */
     public function generateUri($route, array $parameters = [], string $fragment = '', string $type = ''): UriInterface
     {
@@ -330,8 +332,11 @@ class PageRouter implements RouterInterface
         $siteMatcher = GeneralUtility::makeInstance(SiteMatcher::class);
         while ($row = $statement->fetch()) {
             $pageIdInDefaultLanguage = (int)($languageId > 0 ? $row['l10n_parent'] : $row['uid']);
-            if ($siteMatcher->matchByPageId($pageIdInDefaultLanguage)->getRootPageId() === $this->site->getRootPageId()) {
-                $pages[] = $row;
+            try {
+                if ($siteMatcher->matchByPageId($pageIdInDefaultLanguage)->getRootPageId() === $this->site->getRootPageId()) {
+                    $pages[] = $row;
+                }
+            } catch (SiteNotFoundException $e) {
             }
         }
         return $pages;
index a5b5baf..130a869 100644 (file)
@@ -182,6 +182,7 @@ class SiteMatcher implements SingletonInterface
      * @param int $pageId uid of a page in default language
      * @param array|null $rootLine an alternative root line, if already at and.
      * @return SiteInterface
+     * @throws SiteNotFoundException
      */
     public function matchByPageId(int $pageId, array $rootLine = null): SiteInterface
     {
index 774ec6c..6d90944 100644 (file)
@@ -43,8 +43,9 @@ class NullSite implements SiteInterface
      * Sets up a null site object
      *
      * @param array $languages (sys_language objects)
+     * @param Uri|null $baseEntryPoint
      */
-    public function __construct(array $languages = null)
+    public function __construct(array $languages = null, Uri $baseEntryPoint = null)
     {
         foreach ($languages ?? [] as $languageConfiguration) {
             $languageUid = (int)$languageConfiguration['languageId'];
@@ -53,7 +54,7 @@ class NullSite implements SiteInterface
             $this->languages[$languageUid] = new SiteLanguage(
                 $languageUid,
                 $languageConfiguration['locale'] ?? '',
-                new Uri('/'),
+                $baseEntryPoint ?: new Uri('/'),
                 $languageConfiguration
             );
         }
index 454f881..a275175 100644 (file)
@@ -57,17 +57,8 @@ class PseudoSite extends NullSite implements SiteInterface
             $this->entryPoints = [new Uri('/')];
         }
         $baseEntryPoint = reset($this->entryPoints);
-        foreach ($configuration['languages'] as $languageConfiguration) {
-            $languageUid = (int)$languageConfiguration['languageId'];
-            // Language configuration does not have a base defined
-            // So the main site base is used (usually done for default languages)
-            $this->languages[$languageUid] = new SiteLanguage(
-                $languageUid,
-                $languageConfiguration['locale'] ?? '',
-                $baseEntryPoint,
-                $languageConfiguration
-            );
-        }
+
+        parent::__construct($configuration['languages'], $baseEntryPoint);
     }
 
     /**
index 8e5b151..6924899 100644 (file)
@@ -21,7 +21,6 @@ use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Exception\Page\PageNotFoundException;
 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Site\Entity\Site;
-use TYPO3\CMS\Core\Site\Entity\SiteInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\RootlineUtility;
 
@@ -69,11 +68,11 @@ class SiteFinder
      * Find a site by given root page id
      *
      * @param int $rootPageId the page ID (default language)
-     * @return SiteInterface
+     * @return Site
      * @throws SiteNotFoundException
      * @internal only for usage in some places for managing Site Configuration, might be removed without further notice
      */
-    public function getSiteByRootPageId(int $rootPageId): SiteInterface
+    public function getSiteByRootPageId(int $rootPageId): Site
     {
         if (isset($this->mappingRootPageIdToIdentifier[$rootPageId])) {
             return $this->sites[$this->mappingRootPageIdToIdentifier[$rootPageId]];
@@ -101,10 +100,10 @@ class SiteFinder
      *
      * @param int $pageId
      * @param array $rootLine
-     * @return SiteInterface
+     * @return Site
      * @throws SiteNotFoundException
      */
-    public function getSiteByPageId(int $pageId, array $rootLine = null): SiteInterface
+    public function getSiteByPageId(int $pageId, array $rootLine = null): Site
     {
         if ($pageId === 0) {
             // page uid 0 has no root line. We don't need to ask the root line resolver to know that.
index 4f6ee32..be1048c 100644 (file)
@@ -24,6 +24,7 @@ use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Http\RequestFactory;
 use TYPO3\CMS\Core\LinkHandling\LinkService;
 use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
 use TYPO3\CMS\Core\Routing\RouterInterface;
 use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -33,6 +34,13 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  */
 class StaticRouteResolver implements MiddlewareInterface
 {
+    /**
+     * Checks if there is a valid site with route configuration.
+     *
+     * @param ServerRequestInterface $request
+     * @param RequestHandlerInterface $handler
+     * @return ResponseInterface
+     */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
         if (($site = $request->getAttribute('site', null)) instanceof Site &&
@@ -45,21 +53,35 @@ class StaticRouteResolver implements MiddlewareInterface
             if (in_array($path, $routeNames, true)) {
                 $key = array_search($path, $routeNames, true);
                 $routeConfig = $configuration[$key];
-                [$content, $contentType] = $this->resolveByType($request, $site, $routeConfig['type'], $routeConfig);
+                try {
+                    [$content, $contentType] = $this->resolveByType($request, $site, $routeConfig['type'], $routeConfig);
+                } catch (InvalidRouteArgumentsException $e) {
+                    $content = 'Invalid route';
+                    $contentType = 'text/plain';
+                }
+
                 return new HtmlResponse($content, 200, ['Content-Type' => $contentType]);
             }
         }
         return $handler->handle($request);
     }
 
-    private function getFromFile(File $file): array
+    /**
+     * @param File $file
+     * @return array
+     */
+    protected function getFromFile(File $file): array
     {
         $content = $file->getContents();
         $contentType = $file->getMimeType();
         return [$content, $contentType];
     }
 
-    private function getFromUri(string $uri): array
+    /**
+     * @param string $uri
+     * @return array
+     */
+    protected function getFromUri(string $uri): array
     {
         $requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
         $response = $requestFactory->request($uri);
@@ -73,7 +95,14 @@ class StaticRouteResolver implements MiddlewareInterface
         return [$content, $contentType];
     }
 
-    private function getPageUri(ServerRequestInterface $request, Site $site, array $urlParams): string
+    /**
+     * @param ServerRequestInterface $request
+     * @param Site $site
+     * @param array $urlParams
+     * @return string
+     * @throws InvalidRouteArgumentsException
+     */
+    protected function getPageUri(ServerRequestInterface $request, Site $site, array $urlParams): string
     {
         $uri = $site->getRouter()->generateUri(
             (int)$urlParams['pageuid'],
@@ -84,7 +113,15 @@ class StaticRouteResolver implements MiddlewareInterface
         return (string)$uri;
     }
 
-    private function resolveByType(ServerRequestInterface $request, Site $site, string $type, array $routeConfig): array
+    /**
+     * @param ServerRequestInterface $request
+     * @param Site $site
+     * @param string $type
+     * @param array $routeConfig
+     * @return array
+     * @throws InvalidRouteArgumentsException
+     */
+    protected function resolveByType(ServerRequestInterface $request, Site $site, string $type, array $routeConfig): array
     {
         switch ($type) {
             case 'staticText':
index b380587..a8d7caf 100644 (file)
@@ -45,6 +45,7 @@ class PageLinkBuilder extends AbstractTypolinkBuilder
 {
     /**
      * @inheritdoc
+     * @throws UnableToLinkException
      */
     public function build(array &$linkDetails, string $linkText, string $target, array $conf): array
     {
@@ -787,7 +788,12 @@ class PageLinkBuilder extends AbstractTypolinkBuilder
         }
         if (MathUtility::canBeInterpretedAsInteger($GLOBALS['TSFE']->id) && $GLOBALS['TSFE']->id > 0) {
             $matcher = GeneralUtility::makeInstance(SiteMatcher::class);
-            return $matcher->matchByPageId((int)$GLOBALS['TSFE']->id);
+            try {
+                $site = $matcher->matchByPageId((int)$GLOBALS['TSFE']->id);
+            } catch (SiteNotFoundException $e) {
+                $site = null;
+            }
+            return $site;
         }
         return null;
     }
index 6cf7229..20eec02 100644 (file)
@@ -24,11 +24,13 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
 use TYPO3\CMS\Core\Routing\SiteMatcher;
 use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
@@ -210,7 +212,11 @@ class ViewModuleController
             $pageRepository = GeneralUtility::makeInstance(PageRepository::class);
             $additionalGetVars = $this->getAdminCommand($pageId);
             $siteMatcher = GeneralUtility::makeInstance(SiteMatcher::class);
-            $site = $siteMatcher->matchByPageId($pageId, $rootLine);
+            try {
+                $site = $siteMatcher->matchByPageId($pageId, $rootLine);
+            } catch (SiteNotFoundException $e) {
+                $site = null;
+            }
             $finalPageIdToShow = $pageId;
             $mountPointInformation = $pageRepository->getMountPointInfo($pageId);
             if ($mountPointInformation && $mountPointInformation['overlay']) {
@@ -223,7 +229,11 @@ class ViewModuleController
                 $additionalQueryParams = [];
                 parse_str($additionalGetVars, $additionalQueryParams);
                 $additionalQueryParams['_language'] = $site->getLanguageById($languageId);
-                $uri = (string)$site->getRouter()->generateUri($finalPageIdToShow, $additionalQueryParams);
+                try {
+                    $uri = (string)$site->getRouter()->generateUri($finalPageIdToShow, $additionalQueryParams);
+                } catch (InvalidRouteArgumentsException $e) {
+                    return '#';
+                }
             } else {
                 $uri = BackendUtility::getPreviewUrl($finalPageIdToShow, '', $rootLine, '', '', $additionalGetVars);
             }
index 77e8ce6..fb75f42 100644 (file)
@@ -23,7 +23,7 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Http\HtmlResponse;
-use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
@@ -105,6 +105,7 @@ class PreviewController
      *
      * @param ServerRequestInterface $request
      * @return ResponseInterface
+     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
      */
     public function handleRequest(ServerRequestInterface $request): ResponseInterface
     {
@@ -143,7 +144,6 @@ class PreviewController
 
         $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
         try {
-            /** @var Site $site */
             $site = $siteFinder->getSiteByPageId($this->pageId);
             if (isset($queryParameters['L'])) {
                 $queryParameters['_language'] = $site->getLanguageById((int)$queryParameters['L']);
@@ -161,7 +161,7 @@ class PreviewController
             $parameters['ADMCMD_editIcons'] = 1;
             $parameters['ADMCMD_prev'] = 'IGNORE';
             $wsUrl = (string)$site->getRouter()->generateUri($this->pageId, $parameters);
-        } catch (SiteNotFoundException $e) {
+        } catch (SiteNotFoundException | InvalidRouteArgumentsException $e) {
             // Base URL for frontend preview links
             $previewBaseUrl = BackendUtility::getViewDomain($this->pageId) . '/index.php?' . $queryString;
             if (!WorkspaceService::isNewPage($this->pageId)) {
index 138f114..38a1242 100644 (file)
@@ -23,7 +23,7 @@ use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
-use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Versioning\VersionState;
@@ -68,7 +68,6 @@ class PreviewUriBuilder
 
         $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
         try {
-            /** @var Site $site */
             $site = $siteFinder->getSiteByPageId($uid);
             try {
                 $language = $site->getLanguageById($languageId);
@@ -77,7 +76,7 @@ class PreviewUriBuilder
             }
             $uri = $site->getRouter()->generateUri($uid, ['ADMCMD_prev' => $previewKeyword, '_language' => $language], '');
             return (string)$uri;
-        } catch (SiteNotFoundException $e) {
+        } catch (SiteNotFoundException | InvalidRouteArgumentsException $e) {
             $linkParams = [
                 'ADMCMD_prev' => $previewKeyword,
                 'id' => $uid,
@@ -111,6 +110,7 @@ class PreviewUriBuilder
      * @param int $uid The ID of the record to be linked
      * @param bool $addDomain Parameter to decide if domain should be added to the generated link, FALSE per default
      * @return string the preview link without the trailing '/'
+     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
      */
     public function buildUriForWorkspaceSplitPreview(int $uid, bool $addDomain = false): string
     {