[TASK] Centralize sys_domain resolving 49/57949/12
authorBenni Mack <benni@typo3.org>
Fri, 17 Aug 2018 12:40:56 +0000 (14:40 +0200)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Sat, 18 Aug 2018 21:40:35 +0000 (23:40 +0200)
Core provides various places to either retrieve the pageId
of a sys_domain record, or to find out the root page ID
given a request (previously domain + path).

However, due to the inclusion of symfony/routing, the
detection can now be handled by this new component.

For this purpose, a new class LegacyDomainResolver is
introduced (internal) which utilizes symfony/routing
under the hood and does proper caching.

This way, the following methods can be deprecated:
- TypoScriptFrontendController->domainNameMatchesCurrentRequest()
- TypoScriptFrontendController->getDomainDataForPid()
- BackendUtility::getDomainStartPage()
- BackendUtility::firstDomainRecord()

This is a precursor for fetching all sys_domain data,
to minimize the efforts later-on to wrap the LegacyDomainResolver
into a proper Router, and also to build pseudo-sites
on top.

Resolves: #85892
Releases: master
Change-Id: I831d33ac06f090cbe4d2a16e592549739489f99a
Reviewed-on: https://review.typo3.org/57949
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
12 files changed:
typo3/sysext/backend/Classes/Utility/BackendUtility.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-85892-VariousMethodsRegardingSysDomainResolving.rst [new file with mode: 0644]
typo3/sysext/frontend/Classes/Compatibility/LegacyDomainResolver.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/frontend/Classes/Middleware/SiteResolver.php
typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
typo3/sysext/frontend/Tests/Functional/Controller/TypoScriptFrontendControllerTest.php
typo3/sysext/frontend/Tests/Unit/Compatibility/LegacyDomainResolverTest.php [new file with mode: 0644]
typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php
typo3/sysext/frontend/Tests/UnitDeprecated/Controller/TypoScriptFrontendControllerTest.php [new file with mode: 0644]
typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php

index d45bba0..c9cc3fb 100644 (file)
@@ -30,6 +30,7 @@ use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
 use TYPO3\CMS\Core\Database\RelationHandler;
 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
+use TYPO3\CMS\Core\Http\ServerRequest;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
@@ -50,6 +51,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
 use TYPO3\CMS\Core\Versioning\VersionState;
+use TYPO3\CMS\Frontend\Compatibility\LegacyDomainResolver;
 use TYPO3\CMS\Frontend\Page\PageRepository;
 
 /**
@@ -2825,13 +2827,24 @@ class BackendUtility
                     $domainName = $previewDomainConfig;
                 }
             } else {
-                $domainName = self::firstDomainRecord($rootLine);
+                $domainResolver = GeneralUtility::makeInstance(LegacyDomainResolver::class);
+                foreach ($rootLine as $row) {
+                    $domainRecord = $domainResolver->matchRootPageId($row['uid']);
+                    if (is_array($domainRecord)) {
+                        $domainName = rtrim($domainRecord['domainName'], '/');
+                        break;
+                    }
+                }
             }
             if ($domainName) {
                 $domain = $domainName;
             } else {
-                $domainRecord = self::getDomainStartPage($urlParts['host'], $urlParts['path']);
-                $domain = $domainRecord['domainName'];
+                $domainResolver = GeneralUtility::makeInstance(LegacyDomainResolver::class);
+                $rootPageId = $domainResolver->matchRequest(new ServerRequest($domain));
+                if ($rootPageId > 0) {
+                    $domainRecord = $domainResolver->matchRootPageId($rootPageId);
+                    $domain = $domainRecord['domainName'];
+                }
             }
             if ($domain) {
                 $domain = $protocol . '://' . $domain;
@@ -3552,37 +3565,16 @@ class BackendUtility
      *
      * @param array $rootLine Root line array
      * @return string|null Domain name or NULL
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0. Use Link Generation / Router instead.
      */
     public static function firstDomainRecord($rootLine)
     {
-        $queryBuilder = static::getQueryBuilderForTable('sys_domain');
-        $queryBuilder->getRestrictions()
-            ->removeAll()
-            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
-            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
-
-        $queryBuilder->select('domainName')
-            ->from('sys_domain')
-            ->where(
-                $queryBuilder->expr()->eq(
-                    'pid',
-                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT, ':pid')
-                ),
-                $queryBuilder->expr()->eq(
-                    'hidden',
-                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
-                )
-            )
-            ->setMaxResults(1)
-            ->orderBy('sorting');
-
+        trigger_error('BackendUtility::firstDomainRecord() will be removed in TYPO3 v10.0. Use the new LigetDomainStartPagenk Generation functionality instead.', E_USER_DEPRECATED);
+        $domainResolver = GeneralUtility::makeInstance(LegacyDomainResolver::class);
         foreach ($rootLine as $row) {
-            $domainName = $queryBuilder->setParameter('pid', $row['uid'], \PDO::PARAM_INT)
-                ->execute()
-                ->fetchColumn(0);
-
-            if ($domainName) {
-                return rtrim($domainName, '/');
+            $domain = $domainResolver->matchRootPageId($row['uid']);
+            if (is_array($domain)) {
+                return rtrim($domain['domainName'], '/');
             }
         }
         return null;
@@ -3594,9 +3586,11 @@ class BackendUtility
      * @param string $domain Domain name
      * @param string $path Appended path
      * @return array|bool Domain record, if found, false otherwise
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0. Use Link Generation / Router instead.
      */
     public static function getDomainStartPage($domain, $path = '')
     {
+        trigger_error('BackendUtility::getDomainStartPage() will be removed in TYPO3 v10.0. Use the new Link Generation functionality instead.', E_USER_DEPRECATED);
         $domain = explode(':', $domain);
         $domain = strtolower(preg_replace('/\\.$/', '', $domain[0]));
         // Path is calculated.
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85892-VariousMethodsRegardingSysDomainResolving.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85892-VariousMethodsRegardingSysDomainResolving.rst
new file mode 100644 (file)
index 0000000..30f539e
--- /dev/null
@@ -0,0 +1,43 @@
+.. include:: ../../Includes.txt
+
+====================================================================
+Deprecation: #85892 - Various methods regarding sys_domain-resolving
+====================================================================
+
+See :issue:`85892`
+
+Description
+===========
+
+Various methods specific for handling `sys_domain` records have been deprecated. As the new site handling is in place in favor of using `sys_domain`
+records, these methods have been centralized in a :php:`LegacyDomainResolver` class, which is however marked as internal.
+
+Instead, generating URLs should be done via the new PageUriBuilder and Routing API (still in progress), which covers both the new
+site handling and the specific sys_domain record.
+
+The following methods have been deprecated:
+* :php:`TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->domainNameMatchesCurrentRequest()`
+* :php:`TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getDomainDataForPid()`
+* :php:`TYPO3\CMS\Backend\Utility\BackendUtility::getDomainStartPage()`
+* :php:`TYPO3\CMS\Backend\Utility\BackendUtility::firstDomainRecord()`
+
+
+Impact
+======
+
+Calling any of the methods will trigger a deprecation message.
+
+
+Affected Installations
+======================
+
+Any installation with custom functionality regarding sys_domain handling where any of the methods above are used.
+
+
+Migration
+=========
+
+Migrate to either the new Routing API (finalized for 9 LTS) or implement the functionality in your own, or use the :php:`LegacyDomainResolver` class,
+but since the concept of sys_domain handling will be removed in TYPO3 v10.0, consider use the Site handling functionality instead.
+
+.. index:: FullyScanned
\ No newline at end of file
diff --git a/typo3/sysext/frontend/Classes/Compatibility/LegacyDomainResolver.php b/typo3/sysext/frontend/Classes/Compatibility/LegacyDomainResolver.php
new file mode 100644 (file)
index 0000000..1fe891a
--- /dev/null
@@ -0,0 +1,282 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Frontend\Compatibility;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\ServerRequestInterface;
+use Symfony\Component\Routing\Exception\NoConfigurationException;
+use Symfony\Component\Routing\Matcher\UrlMatcher;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException;
+use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Exception\Page\RootLineException;
+use TYPO3\CMS\Core\Http\NormalizedParams;
+use TYPO3\CMS\Core\SingletonInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\RootlineUtility;
+
+/**
+ * Resolves sys_domain entries when a Request object is given,
+ * or a pageId is given or a rootpage Id is given (= if there is a sys_domain record on that specific page).
+ * Always keeps the sorting in line.
+ *
+ * @todo: would be nice to flush caches if sys_domain has been touched in DataHandler
+ * @internal as this should ideally be wrapped inside the "main" site router in the future.
+ */
+class LegacyDomainResolver implements SingletonInterface
+{
+    /**
+     * Runtime cache of domains per processed page ids.
+     *
+     * @var array
+     */
+    protected $domainDataCache = [];
+
+    /**
+     * @var FrontendInterface
+     */
+    protected $cache;
+
+    /**
+     * Whether a sys_domain like example.com should also match for my.blog.example.com
+     *
+     * @var bool
+     */
+    protected $recursiveDomainSearch;
+
+    /**
+     * @var RouteCollection
+     */
+    protected $routeCollection;
+
+    /**
+     * all entries in sys_domain
+     * @var array
+     */
+    protected $allDomainRecords;
+
+    /**
+     * all entries in sys_domain grouped by page (pid)
+     * @var array
+     */
+    protected $groupedDomainsPerPage;
+
+    public function __construct()
+    {
+        $this->cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_core');
+        $this->recursiveDomainSearch = (bool)$GLOBALS['TYPO3_CONF_VARS']['SYS']['recursiveDomainSearch'];
+        $this->routeCollection = new RouteCollection();
+        $this->populate();
+    }
+
+    /**
+     * Builds up all domain records from DB and all routes
+     */
+    protected function populate()
+    {
+        if ($data = $this->cache->get('legacy-domains')) {
+            // Due to the nature of PhpFrontend, the `<?php` and `#` wraps have to be removed
+            $data = preg_replace('/^<\?php\s*|\s*#$/', '', $data);
+            $data = unserialize($data, ['allowed_classes' => [Route::class, RouteCollection::class]]);
+            $this->routeCollection = $data['routeCollection'];
+            $this->allDomainRecords = $data['allDomainRecords'];
+            $this->groupedDomainsPerPage = $data['groupedDomainsPerPage'];
+        } else {
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_domain');
+            $queryBuilder->getRestrictions()->removeAll();
+            $statement = $queryBuilder
+                ->select('*')
+                ->from('sys_domain')
+                ->orderBy('sorting', 'ASC')
+                ->execute();
+
+            while ($row = $statement->fetch()) {
+                $row['domainName'] = rtrim($row['domainName'], '/');
+                $this->allDomainRecords[(int)$row['uid']] = $row;
+                $this->groupedDomainsPerPage[(int)$row['pid']][] = $row;
+                if (!$row['hidden']) {
+                    if (strpos($row['domainName'], '/') === false) {
+                        $path = '';
+                        list($host, $port) = explode(':', $row['domainName']);
+                    } else {
+                        $urlParts = parse_url($row['domainName']);
+                        $path = trim($urlParts['path'], '/');
+                        $host = $urlParts['host'];
+                        $port = (string)$urlParts['port'];
+                    }
+                    $route = new Route(
+                        $path . '/{next}',
+                        ['pageId' => $row['pid']],
+                        array_filter(['next' => '.*', 'port' => $port]),
+                        ['utf8' => true],
+                        $host ?? ''
+                    );
+                    $this->routeCollection->add('domain_' . $row['uid'], $route);
+                }
+            }
+
+            $data = [
+                'routeCollection' => $this->routeCollection,
+                'allDomainRecords' => $this->allDomainRecords,
+                'groupedDomainsPerPage' => $this->groupedDomainsPerPage
+            ];
+            $this->cache->set('legacy-domains', serialize($data), ['sys_domain'], 0);
+        }
+    }
+
+    /**
+     * Return the page ID (pid) of a sys_domain record, based on a request object, does the infamous
+     * "recursive domain search", to also detect if the domain is like "abc.def.example.com" even if the
+     * sys_domain entry is "example.com".
+     *
+     * @param ServerRequestInterface $request
+     * @return int page ID
+     */
+    public function matchRequest(ServerRequestInterface $request): int
+    {
+        if (empty($this->allDomainRecords) || count($this->routeCollection) === 0) {
+            return 0;
+        }
+        $context = new RequestContext('/', $request->getMethod(), $request->getUri()->getHost());
+        $matcher = new UrlMatcher($this->routeCollection, $context);
+        if ($this->recursiveDomainSearch) {
+            $pageUid = 0;
+            $host = explode('.', $request->getUri()->getHost());
+            while (count($host)) {
+                $context->setHost(implode('.', $host));
+                try {
+                    $result = $matcher->match($request->getUri()->getPath());
+                    return (int)$result['pageId'];
+                } catch (NoConfigurationException | ResourceNotFoundException $e) {
+                    array_shift($host);
+                }
+            }
+            return $pageUid;
+        }
+        try {
+            $result = $matcher->match($request->getUri()->getPath());
+            return (int)$result['pageId'];
+        } catch (NoConfigurationException | ResourceNotFoundException $e) {
+            // No domain record found
+        }
+        return 0;
+    }
+
+    /**
+     * Obtains a sys_domain record that fits for a given page ID by traversing the rootline up and finding
+     * a suitable page with sys_domain records.
+     * As all sys_domains have been fetched already, the internal grouped list of sys_domains can be used directly.
+     *
+     * Usually used in the Frontend to find out the domain of a page to link to.
+     *
+     * Includes a runtime cache if a frontend request links to the same page multiple times.
+     *
+     * @param int $pageId Target page id
+     * @param ServerRequestInterface|null $currentRequest if given, the domain record is marked with "isCurrentDomain"
+     * @return array|null the sys_domain record if found
+     */
+    public function matchPageId(int $pageId, ServerRequestInterface $currentRequest = null): ?array
+    {
+        // Using array_key_exists() here, nice $result can be NULL
+        // (happens, if there's no domain records defined)
+        if (array_key_exists($pageId, $this->domainDataCache)) {
+            return $this->domainDataCache[$pageId];
+        }
+        try {
+            $this->domainDataCache[$pageId] = $this->resolveDomainEntry(
+                $pageId,
+                $currentRequest
+            );
+        } catch (RootLineException $e) {
+            $this->domainDataCache[$pageId] = null;
+        }
+        return $this->domainDataCache[$pageId];
+    }
+
+    /**
+     * Returns the full sys_domain record, based on a page record, which is assumed the "pid" of the sys_domain record.
+     * Since ordering is taken into account, this is the first sys_domain record on that page Id.
+     *
+     * @param int $pageId
+     * @return array|null
+     */
+    public function matchRootPageId(int $pageId): ?array
+    {
+        return !empty($this->groupedDomainsPerPage[$pageId]) ? reset($this->groupedDomainsPerPage[$pageId]) : null;
+    }
+
+    /**
+     * @param int $pageId
+     * @param ServerRequestInterface|null $currentRequest
+     * @return array|null
+     */
+    protected function resolveDomainEntry(int $pageId, ?ServerRequestInterface $currentRequest): ?array
+    {
+        $rootLine = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get();
+        // walk the rootline downwards from the target page
+        // to the root page, until a domain record is found
+        foreach ($rootLine as $pageInRootline) {
+            $pidInRootline = $pageInRootline['uid'];
+            if (empty($this->groupedDomainsPerPage[$pidInRootline])) {
+                continue;
+            }
+
+            $domainEntriesOfPage = $this->groupedDomainsPerPage[$pidInRootline];
+            foreach ($domainEntriesOfPage as $domainEntry) {
+                if ($domainEntry['hidden']) {
+                    continue;
+                }
+                // When no currentRequest is given, let's take the first non-hidden sys_domain page
+                if ($currentRequest === null) {
+                    return $domainEntry;
+                }
+                // Otherwise the check should match against the current domain (and set "isCurrentDomain")
+                // Current domain is "forced", however, otherwise the first one is fine
+                if ($this->domainNameMatchesCurrentRequest($domainEntry['domainName'], $currentRequest)) {
+                    $result = $domainEntry;
+                    $result['isCurrentDomain'] = true;
+                    return $result;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Whether the given domain name (potentially including a path segment) matches currently requested host or
+     * the host including the path segment
+     *
+     * @param string $domainName
+     * @param ServerRequestInterface|null $request
+     * @return bool
+     */
+    protected function domainNameMatchesCurrentRequest($domainName, ServerRequestInterface $request): bool
+    {
+        /** @var NormalizedParams $normalizedParams */
+        $normalizedParams = $request->getAttribute('normalizedParams');
+        if (!($normalizedParams instanceof NormalizedParams)) {
+            return false;
+        }
+        $currentDomain = $normalizedParams->getHttpHost();
+        // remove the script filename from the path segment.
+        $currentPathSegment = trim(preg_replace('|/[^/]*$|', '', $normalizedParams->getScriptName()));
+        return $currentDomain === $domainName || $currentDomain . $currentPathSegment === $domainName;
+    }
+}
index b674128..ced4e5a 100644 (file)
@@ -34,7 +34,6 @@ use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Controller\ErrorPageController;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
@@ -64,6 +63,7 @@ use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
 use TYPO3\CMS\Core\Utility\RootlineUtility;
 use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
+use TYPO3\CMS\Frontend\Compatibility\LegacyDomainResolver;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
 use TYPO3\CMS\Frontend\Http\UrlHandlerInterface;
 use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
@@ -760,13 +760,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     protected $cacheHash;
 
     /**
-     * Runtime cache of domains per processed page ids.
-     *
-     * @var array
-     */
-    protected $domainDataCache = [];
-
-    /**
      * Content type HTTP header being sent in the request.
      * @todo Ticket: #63642 Should be refactored to a request/response model later
      * @internal Should only be used by TYPO3 core for now
@@ -4711,56 +4704,16 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     }
 
     /**
-     * Fetches/returns the cached contents of the sys_domain database table.
-     *
-     * @return array Domain data
-     */
-    protected function getSysDomainCache()
-    {
-        $entryIdentifier = 'core-database-sys_domain-complete';
-        /** @var $runtimeCache \TYPO3\CMS\Core\Cache\Frontend\AbstractFrontend */
-        $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
-
-        $sysDomainData = [];
-        if ($runtimeCache->has($entryIdentifier)) {
-            $sysDomainData = $runtimeCache->get($entryIdentifier);
-        } else {
-            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_domain');
-            $queryBuilder->setRestrictions(GeneralUtility::makeInstance(DefaultRestrictionContainer::class));
-            $result = $queryBuilder
-                ->select('uid', 'pid', 'domainName')
-                ->from('sys_domain')
-                ->orderBy('sorting', 'ASC')
-                ->execute();
-
-            while ($row = $result->fetch()) {
-                // If there is already an entry for this pid, we should not override it
-                // Except if it is the current domain
-                if (isset($sysDomainData[$row['pid']]) && !$this->domainNameMatchesCurrentRequest($row['domainName'])) {
-                    continue;
-                }
-
-                // as we passed all previous checks, we save this domain for the current pid
-                $sysDomainData[$row['pid']] = [
-                    'uid' => $row['uid'],
-                    'pid' => $row['pid'],
-                    'domainName' => rtrim($row['domainName'], '/'),
-                ];
-            }
-            $runtimeCache->set($entryIdentifier, $sysDomainData);
-        }
-        return $sysDomainData;
-    }
-
-    /**
      * Whether the given domain name (potentially including a path segment) matches currently requested host or
      * the host including the path segment
      *
      * @param string $domainName
      * @return bool
+     * @deprecated will be removed in TYPO3 v10.
      */
     public function domainNameMatchesCurrentRequest($domainName)
     {
+        trigger_error('This method will be removed in TYPO3 v10, use LegacyDomainResolver instead.', E_USER_DEPRECATED);
         $currentDomain = GeneralUtility::getIndpEnv('HTTP_HOST');
         $currentPathSegment = trim(preg_replace('|/[^/]*$|', '', GeneralUtility::getIndpEnv('SCRIPT_NAME')));
         return $currentDomain === $domainName || $currentDomain . $currentPathSegment === $domainName;
@@ -4772,32 +4725,12 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      *
      * @param int $targetPid Target page id
      * @return mixed Return domain data or NULL
+     * @deprecated will be removed in TYPO3 v10.
      */
     public function getDomainDataForPid($targetPid)
     {
-        // Using array_key_exists() here, nice $result can be NULL
-        // (happens, if there's no domain records defined)
-        if (!array_key_exists($targetPid, $this->domainDataCache)) {
-            $result = null;
-            $sysDomainData = $this->getSysDomainCache();
-            try {
-                $rootLine = GeneralUtility::makeInstance(RootlineUtility::class, $targetPid, null, $this->context)->get();
-                // walk the rootline downwards from the target page
-                // to the root page, until a domain record is found
-                foreach ($rootLine as $pageInRootline) {
-                    $pidInRootline = $pageInRootline['uid'];
-                    if (isset($sysDomainData[$pidInRootline])) {
-                        $result = $sysDomainData[$pidInRootline];
-                        break;
-                    }
-                }
-            } catch (RootLineException $e) {
-                // Do nothing
-            }
-            $this->domainDataCache[$targetPid] = $result;
-        }
-
-        return $this->domainDataCache[$targetPid];
+        trigger_error('This method will be removed in TYPO3 v10, use LegacyDomainResolver instead.', E_USER_DEPRECATED);
+        return GeneralUtility::makeInstance(LegacyDomainResolver::class)->matchPageId((int)$targetPid, $GLOBALS['TYPO3_REQUEST']);
     }
 
     /**
@@ -4806,12 +4739,12 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      *
      * @param int $targetPid Target page id
      * @return mixed Return domain name or NULL if not found
-     * @deprecated will be removed in TYPO3 v10, as getDomainDataForPid could work
+     * @deprecated will be removed in TYPO3 v10.
      */
     public function getDomainNameForPid($targetPid)
     {
-        trigger_error('This method will be removed in TYPO3 v10, use $TSFE->getDomainDataForPid() instead.', E_USER_DEPRECATED);
-        $domainData = $this->getDomainDataForPid($targetPid);
+        trigger_error('This method will be removed in TYPO3 v10, use LegacyDomainResolver instead.', E_USER_DEPRECATED);
+        $domainData = GeneralUtility::makeInstance(LegacyDomainResolver::class)->matchPageId((int)$targetPid, $GLOBALS['TYPO3_REQUEST']);
         return $domainData ? $domainData['domainName'] : null;
     }
 
index 9002c0c..19f5bff 100644 (file)
@@ -23,13 +23,12 @@ use Symfony\Component\Routing\Exception\ResourceNotFoundException;
 use Symfony\Component\Routing\Matcher\UrlMatcher;
 use Symfony\Component\Routing\RequestContext;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
-use TYPO3\CMS\Core\Http\NormalizedParams;
 use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Compatibility\LegacyDomainResolver;
 use TYPO3\CMS\Frontend\Controller\ErrorController;
 use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
 
@@ -145,84 +144,14 @@ class SiteResolver implements MiddlewareInterface
         if ($site instanceof Site) {
             $GLOBALS['TSFE']->domainStartPage = $site->getRootPageId();
         } else {
-            $GLOBALS['TSFE']->domainStartPage = $this->findDomainRecord($request->getAttribute('normalizedParams'), (bool)$GLOBALS['TYPO3_CONF_VARS']['SYS']['recursiveDomainSearch']);
+            $GLOBALS['TSFE']->domainStartPage = GeneralUtility::makeInstance(LegacyDomainResolver::class)
+                ->matchRequest($request);
         }
 
         return $handler->handle($request);
     }
 
     /**
-     * Looking up a domain record based on server parameters HTTP_HOST
-     *
-     * @param NormalizedParams $requestParams used to get sanitized information of the current request
-     * @param bool $recursive If set, it looks "recursively" meaning that a domain like "123.456.typo3.com" would find a domain record like "typo3.com" if "123.456.typo3.com" or "456.typo3.com" did not exist.
-     * @return int|null Returns the page id of the page where the domain record was found or null if no sys_domain record found.
-     * previously found at \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::findDomainRecord()
-     */
-    protected function findDomainRecord(NormalizedParams $requestParams, $recursive = false): ?int
-    {
-        if ($recursive) {
-            $pageUid = 0;
-            $host = explode('.', $requestParams->getHttpHost());
-            while (count($host)) {
-                $pageUid = $this->getRootPageIdFromDomainRecord(implode('.', $host), $requestParams->getScriptName());
-                if ($pageUid) {
-                    return $pageUid;
-                }
-                array_shift($host);
-            }
-            return $pageUid;
-        }
-        return $this->getRootPageIdFromDomainRecord($requestParams->getHttpHost(), $requestParams->getScriptName());
-    }
-
-    /**
-     * Will find the page ID carrying the domain record matching the input domain.
-     *
-     * @param string $domain Domain name to search for. Eg. "www.typo3.com". Typical the HTTP_HOST value.
-     * @param string $path Path for the current script in domain. Eg. "/somedir/subdir". Typ. supplied by \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('SCRIPT_NAME')
-     * @return int|null If found, returns integer with page UID where found. Otherwise null.
-     * previously found at PageRepository::getDomainStartPage
-     */
-    protected function getRootPageIdFromDomainRecord(string $domain, string $path = ''): ?int
-    {
-        list($domain) = explode(':', $domain);
-        $domain = strtolower(preg_replace('/\\.$/', '', $domain));
-        // Removing extra trailing slashes
-        $path = trim(preg_replace('/\\/[^\\/]*$/', '', $path));
-        // Appending to domain string
-        $domain .= $path;
-        $domain = preg_replace('/\\/*$/', '', $domain);
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_domain');
-        $queryBuilder->getRestrictions()->removeAll();
-        $row = $queryBuilder
-            ->select(
-                'pid'
-            )
-            ->from('sys_domain')
-            ->where(
-                $queryBuilder->expr()->eq(
-                    'hidden',
-                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
-                ),
-                $queryBuilder->expr()->orX(
-                    $queryBuilder->expr()->eq(
-                        'domainName',
-                        $queryBuilder->createNamedParameter($domain, \PDO::PARAM_STR)
-                    ),
-                    $queryBuilder->expr()->eq(
-                        'domainName',
-                        $queryBuilder->createNamedParameter($domain . '/', \PDO::PARAM_STR)
-                    )
-                )
-            )
-            ->setMaxResults(1)
-            ->execute()
-            ->fetch();
-        return $row ? (int)$row['pid'] : null;
-    }
-
-    /**
      * Checks if the language is allowed in Frontend, if not, check if there is valid BE user
      *
      * @param SiteLanguage|null $language
index ae12ca0..30a2275 100644 (file)
@@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Utility\RootlineUtility;
+use TYPO3\CMS\Frontend\Compatibility\LegacyDomainResolver;
 use TYPO3\CMS\Frontend\ContentObject\TypolinkModifyLinkConfigForPageLinksHookInterface;
 use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
 use TYPO3\CMS\Frontend\Page\PageRepository;
@@ -154,12 +155,13 @@ class PageLinkBuilder extends AbstractTypolinkBuilder
                     $page = $page2;
                 }
             }
-
-            $targetDomainRecord = $tsfe->getDomainDataForPid($page['uid']);
-            $targetDomain = $targetDomainRecord ? $targetDomainRecord['domainName'] : null;
+            // @todo: This obviously needs more detection with Site Handling, to detect the site language ID
+            $domainResolver = GeneralUtility::makeInstance(LegacyDomainResolver::class);
+            $targetDomainRecord = $domainResolver->matchPageId((int)$page['uid'], $GLOBALS['TYPO3_REQUEST']);
+            $targetDomain = '';
             // Do not prepend the domain if it is the current hostname
-            if (!$targetDomain || $tsfe->domainNameMatchesCurrentRequest($targetDomain)) {
-                $targetDomain = '';
+            if (!empty($targetDomainRecord) && !$targetDomainRecord['isCurrentDomain']) {
+                $targetDomain = $targetDomainRecord['domainName'];
             }
         }
         if ($conf['useCacheHash']) {
index b6aec4e..2570129 100644 (file)
@@ -14,10 +14,6 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
-use Doctrine\DBAL\Platforms\SQLServerPlatform;
-use TYPO3\CMS\Core\Cache\CacheManager;
-use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
 
@@ -86,62 +82,6 @@ class TypoScriptFrontendControllerTest extends FunctionalTestCase
     }
 
     /**
-     * @param string $currentDomain
-     * @test
-     * @dataProvider getSysDomainCacheDataProvider
-     */
-    public function getSysDomainCacheReturnsCurrentDomainRecord($currentDomain)
-    {
-        GeneralUtility::flushInternalRuntimeCaches();
-
-        $_SERVER['HTTP_HOST'] = $currentDomain;
-        $domainRecords = [
-            'typo3.org' => [
-                'uid' => '1',
-                'pid' => '1',
-                'domainName' => 'typo3.org',
-            ],
-            'foo.bar' => [
-                'uid' => '2',
-                'pid' => '1',
-                'domainName' => 'foo.bar',
-            ],
-            'example.com' => [
-                'uid' => '3',
-                'pid' => '1',
-                'domainName' => 'example.com',
-            ],
-        ];
-
-        $connection = (new ConnectionPool())->getConnectionForTable('sys_domain');
-
-        $sqlServerIdentityDisabled = false;
-        if ($connection->getDatabasePlatform() instanceof SQLServerPlatform) {
-            $connection->exec('SET IDENTITY_INSERT sys_domain ON');
-            $sqlServerIdentityDisabled = true;
-        }
-
-        foreach ($domainRecords as $domainRecord) {
-            $connection->insert(
-                'sys_domain',
-                $domainRecord
-            );
-        }
-
-        if ($sqlServerIdentityDisabled) {
-            $connection->exec('SET IDENTITY_INSERT sys_domain OFF');
-        }
-
-        GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime')->flush();
-        $expectedResult = [
-            $domainRecords[$currentDomain]['pid'] => $domainRecords[$currentDomain],
-        ];
-
-        $actualResult = $this->tsFrontendController->_call('getSysDomainCache');
-        $this->assertEquals($expectedResult, $actualResult);
-    }
-
-    /**
      * @param string $tablePid
      * @param int $now
      * @return int
@@ -150,22 +90,4 @@ class TypoScriptFrontendControllerTest extends FunctionalTestCase
     {
         return $this->tsFrontendController->_call('getFirstTimeValueForRecord', $tablePid, $now);
     }
-
-    /**
-     * @return array
-     */
-    public function getSysDomainCacheDataProvider()
-    {
-        return [
-            'typo3.org' => [
-                'typo3.org',
-            ],
-            'foo.bar' => [
-                'foo.bar',
-            ],
-            'example.com' => [
-                'example.com',
-            ],
-        ];
-    }
 }
diff --git a/typo3/sysext/frontend/Tests/Unit/Compatibility/LegacyDomainResolverTest.php b/typo3/sysext/frontend/Tests/Unit/Compatibility/LegacyDomainResolverTest.php
new file mode 100644 (file)
index 0000000..11eaf0f
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Frontend\Tests\Unit\Compatibility;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Http\NormalizedParams;
+use TYPO3\CMS\Core\Http\ServerRequestFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Compatibility\LegacyDomainResolver;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class LegacyDomainResolverTest extends UnitTestCase
+{
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface|LegacyDomainResolver
+     */
+    protected $subject;
+
+    protected $resetSingletonInstances = true;
+
+    protected $backupEnvironment = true;
+
+    protected function setUp()
+    {
+        parent::setUp();
+        $this->subject = $this->getAccessibleMock(LegacyDomainResolver::class, ['dummy'], [], '', false);
+        GeneralUtility::flushInternalRuntimeCaches();
+    }
+
+    /**
+     * Tests concerning domainNameMatchesCurrentRequest
+     */
+
+    /**
+     * @return array
+     */
+    public function domainNameMatchesCurrentRequestDataProvider()
+    {
+        return [
+            'same domains' => [
+                'typo3.org',
+                'typo3.org',
+                '/index.php',
+                true,
+            ],
+            'same domains with subdomain' => [
+                'www.typo3.org',
+                'www.typo3.org',
+                '/index.php',
+                true,
+            ],
+            'different domains' => [
+                'foo.bar',
+                'typo3.org',
+                '/index.php',
+                false,
+            ],
+            'domain record with script name' => [
+                'typo3.org',
+                'typo3.org/foo/bar',
+                '/foo/bar/index.php',
+                true,
+            ],
+            'domain record with wrong script name' => [
+                'typo3.org',
+                'typo3.org/foo/bar',
+                '/bar/foo/index.php',
+                false,
+            ],
+        ];
+    }
+
+    /**
+     * @param string $currentDomain
+     * @param string $domainRecord
+     * @param string $scriptName
+     * @param bool $expectedResult
+     * @test
+     * @dataProvider domainNameMatchesCurrentRequestDataProvider
+     */
+    public function domainNameMatchesCurrentRequest(string $currentDomain, string $domainRecord, string $scriptName, bool $expectedResult)
+    {
+        $_SERVER['HTTP_HOST'] = $currentDomain;
+        $_SERVER['SCRIPT_NAME'] = $scriptName;
+        $request = ServerRequestFactory::fromGlobals();
+        $normalizedParams = new NormalizedParams($request, [], Environment::getCurrentScript(), Environment::getPublicPath());
+        $request = $request->withAttribute('normalizedParams', $normalizedParams);
+        $this->assertEquals($expectedResult, $this->subject->_call('domainNameMatchesCurrentRequest', $domainRecord, $request));
+    }
+}
index ef34c1c..cace168 100644 (file)
@@ -145,64 +145,6 @@ class TypoScriptFrontendControllerTest extends UnitTestCase
     }
 
     /**
-     * Tests concerning domainNameMatchesCurrentRequest
-     */
-
-    /**
-     * @return array
-     */
-    public function domainNameMatchesCurrentRequestDataProvider()
-    {
-        return [
-            'same domains' => [
-                'typo3.org',
-                'typo3.org',
-                '/index.php',
-                true,
-            ],
-            'same domains with subdomain' => [
-                'www.typo3.org',
-                'www.typo3.org',
-                '/index.php',
-                true,
-            ],
-            'different domains' => [
-                'foo.bar',
-                'typo3.org',
-                '/index.php',
-                false,
-            ],
-            'domain record with script name' => [
-                'typo3.org',
-                'typo3.org/foo/bar',
-                '/foo/bar/index.php',
-                true,
-            ],
-            'domain record with wrong script name' => [
-                'typo3.org',
-                'typo3.org/foo/bar',
-                '/bar/foo/index.php',
-                false,
-            ],
-        ];
-    }
-
-    /**
-     * @param string $currentDomain
-     * @param string $domainRecord
-     * @param string $scriptName
-     * @param bool $expectedResult
-     * @test
-     * @dataProvider domainNameMatchesCurrentRequestDataProvider
-     */
-    public function domainNameMatchesCurrentRequest($currentDomain, $domainRecord, $scriptName, $expectedResult)
-    {
-        $_SERVER['HTTP_HOST'] = $currentDomain;
-        $_SERVER['SCRIPT_NAME'] = $scriptName;
-        $this->assertEquals($expectedResult, $this->subject->domainNameMatchesCurrentRequest($domainRecord));
-    }
-
-    /**
      * @return array
      */
     public function baseUrlWrapHandlesDifferentUrlsDataProvider()
diff --git a/typo3/sysext/frontend/Tests/UnitDeprecated/Controller/TypoScriptFrontendControllerTest.php b/typo3/sysext/frontend/Tests/UnitDeprecated/Controller/TypoScriptFrontendControllerTest.php
new file mode 100644 (file)
index 0000000..758e0ea
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Frontend\Tests\UnitDeprecated\Controller;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class TypoScriptFrontendControllerTest extends UnitTestCase
+{
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface|TypoScriptFrontendController
+     */
+    protected $subject;
+
+    protected function setUp()
+    {
+        parent::setUp();
+        GeneralUtility::flushInternalRuntimeCaches();
+        $this->subject = $this->getAccessibleMock(TypoScriptFrontendController::class, ['dummy'], [], '', false);
+    }
+
+    /**
+     * Tests concerning domainNameMatchesCurrentRequest
+     */
+
+    /**
+     * @return array
+     */
+    public function domainNameMatchesCurrentRequestDataProvider()
+    {
+        return [
+            'same domains' => [
+                'typo3.org',
+                'typo3.org',
+                '/index.php',
+                true,
+            ],
+            'same domains with subdomain' => [
+                'www.typo3.org',
+                'www.typo3.org',
+                '/index.php',
+                true,
+            ],
+            'different domains' => [
+                'foo.bar',
+                'typo3.org',
+                '/index.php',
+                false,
+            ],
+            'domain record with script name' => [
+                'typo3.org',
+                'typo3.org/foo/bar',
+                '/foo/bar/index.php',
+                true,
+            ],
+            'domain record with wrong script name' => [
+                'typo3.org',
+                'typo3.org/foo/bar',
+                '/bar/foo/index.php',
+                false,
+            ],
+        ];
+    }
+
+    /**
+     * @param string $currentDomain
+     * @param string $domainRecord
+     * @param string $scriptName
+     * @param bool $expectedResult
+     * @test
+     * @dataProvider domainNameMatchesCurrentRequestDataProvider
+     */
+    public function domainNameMatchesCurrentRequest($currentDomain, $domainRecord, $scriptName, $expectedResult)
+    {
+        $_SERVER['HTTP_HOST'] = $currentDomain;
+        $_SERVER['SCRIPT_NAME'] = $scriptName;
+        $this->assertEquals($expectedResult, $this->subject->domainNameMatchesCurrentRequest($domainRecord));
+    }
+}
index 325a225..52e6366 100644 (file)
@@ -2928,4 +2928,18 @@ return [
             'Deprecation-85878-EidUtilityAndVariousTSFEMethods.rst',
         ],
     ],
+    'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->domainNameMatchesCurrentRequest' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 1,
+        'restFiles' => [
+            'Deprecation-85892-VariousMethodsRegardingSysDomainResolving.rst',
+        ],
+    ],
+    'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getDomainDataForPid' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 1,
+        'restFiles' => [
+            'Deprecation-85892-VariousMethodsRegardingSysDomainResolving.rst',
+        ],
+    ],
 ];
index 6bb0b0a..b06338b 100644 (file)
@@ -771,4 +771,18 @@ return [
             'Deprecation-85878-EidUtilityAndVariousTSFEMethods.rst',
         ],
     ],
+    'TYPO3\CMS\Backend\Utility\BackendUtility::getDomainStartPage' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 2,
+        'restFiles' => [
+            'Deprecation-85892-VariousMethodsRegardingSysDomainResolving.rst',
+        ],
+    ],
+    'TYPO3\CMS\Backend\Utility\BackendUtility::firstDomainRecord' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 1,
+        'restFiles' => [
+            'Deprecation-85892-VariousMethodsRegardingSysDomainResolving.rst',
+        ],
+    ],
 ];