[TASK] Ensure ?id= is allowed to be called in site handling 78/58078/13
authorBenni Mack <benni@typo3.org>
Thu, 30 Aug 2018 06:13:17 +0000 (08:13 +0200)
committerSusanne Moog <susanne.moog@typo3.org>
Thu, 30 Aug 2018 21:43:51 +0000 (23:43 +0200)
When using site configurations, it should still be possible to call a
page with "index.php?id=13" IF the site configuration's base is valid.

Currently, this was only possible when a language parameter was also
given, which vanished now.

Releases: master
Resolves: #86032
Change-Id: Iec160367be33786781f58cbb69f6d750d03e1496
Reviewed-on: https://review.typo3.org/58078
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
composer.json
composer.lock
typo3/sysext/core/Classes/Routing/SiteMatcher.php
typo3/sysext/core/Classes/Site/PseudoSiteFinder.php
typo3/sysext/core/composer.json
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/LinkGeneratorTest.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/SiteRequestTest.php

index 04b5904..af78c0d 100644 (file)
@@ -69,7 +69,7 @@
                "fiunchinho/phpunit-randomizer": "^4.0",
                "friendsofphp/php-cs-fixer": "^2.12.2",
                "typo3/cms-styleguide": "~9.2.0",
-               "typo3/testing-framework": "~4.8.0"
+               "typo3/testing-framework": "~4.8.1"
        },
        "suggest": {
                "ext-gd": "GDlib/Freetype is required for building images with text (GIFBUILDER) and can also be used to scale images",
index 6de4c36..5d582a9 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "59f10e6d8dc3b2a4b6241f91e633603f",
+    "content-hash": "2b9e27d9ea6bb521fab6bfd77c588843",
     "packages": [
         {
             "name": "cogpowered/finediff",
         },
         {
             "name": "typo3/testing-framework",
-            "version": "4.8.0",
+            "version": "4.8.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/TYPO3/testing-framework.git",
-                "reference": "bdc0c1a4cd1b8d4739175f118f0812675327b2c5"
+                "reference": "66a73520e6b68b6bf96d5540b19c10abfde42732"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/bdc0c1a4cd1b8d4739175f118f0812675327b2c5",
-                "reference": "bdc0c1a4cd1b8d4739175f118f0812675327b2c5",
+                "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/66a73520e6b68b6bf96d5540b19c10abfde42732",
+                "reference": "66a73520e6b68b6bf96d5540b19c10abfde42732",
                 "shasum": ""
             },
             "require": {
                 "tests",
                 "typo3"
             ],
-            "time": "2018-08-30T11:31:41+00:00"
+            "time": "2018-08-30T19:45:19+00:00"
         },
         {
             "name": "webmozart/assert",
index 8ba196d..9142249 100644 (file)
@@ -88,25 +88,25 @@ class SiteMatcher implements SingletonInterface
         $language = null;
 
         $pageId = $request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? 0;
-        $languageId = $request->getQueryParams()['L'] ?? $request->getParsedBody()['L'] ?? null;
 
         if (!empty($pageId) && !MathUtility::canBeInterpretedAsInteger($pageId)) {
             $pageId = (int)GeneralUtility::makeInstance(PageRepository::class)->getPageIdFromAlias($pageId);
         }
         // First, check if we have a _GET/_POST parameter for "id", then a site information can be resolved based.
-        if ($pageId > 0 && $languageId !== null) {
+        if ($pageId > 0) {
             // Loop over the whole rootline without permissions to get the actual site information
             try {
                 $site = $this->finder->getSiteByPageId((int)$pageId);
                 // If a "L" parameter is given, we take that one into account.
+                $languageId = $request->getQueryParams()['L'] ?? $request->getParsedBody()['L'] ?? null;
                 if ($languageId !== null) {
                     $language = $site->getLanguageById((int)$languageId);
-                } else {
-                    $allLanguages = $site->getLanguages();
-                    $language = reset($allLanguages);
                 }
             } catch (SiteNotFoundException $e) {
-                // No site found by ID
+                // No site found by the given page
+            } catch (\InvalidArgumentException $e) {
+                // The language fetched by getLanguageById() was not available, now the PSR-15 middleware
+                // redirects to the default page.
             }
         }
 
@@ -130,8 +130,13 @@ class SiteMatcher implements SingletonInterface
                 $result = $matcher->match($request->getUri()->getPath());
                 return new RouteResult($request->getUri(), $result['site'], $result['language'], $result['tail']);
             } catch (NoConfigurationException | ResourceNotFoundException $e) {
-                // No site found
+                // No site+language combination found so far
             }
+            // At this point we discard a possible found site via ?id=123
+            // Because ?id=123 _can_ only work if the actual domain/site base works
+            // so www.domain-without-site-configuration/index.php?id=123 (where 123 is a page referring
+            // to a page within a site configuration will never be resolved here) properly
+            $site = null;
         }
 
         // Check against any sys_domain records
@@ -157,8 +162,10 @@ class SiteMatcher implements SingletonInterface
                 // No domain record found
             }
         }
+        // No domain record found, use the first "pseudo-site" found
         if ($site == null) {
-            $site = $this->pseudoSiteFinder->getSiteByPageId(0);
+            $allPseudoSites = $this->pseudoSiteFinder->findAll();
+            $site = reset($allPseudoSites);
         }
         return new RouteResult($request->getUri(), $site, $language);
     }
index 63fd61f..fc2f11e 100644 (file)
@@ -20,6 +20,7 @@ use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 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\Database\Query\Restriction\HiddenRestriction;
 use TYPO3\CMS\Core\Exception\Page\PageNotFoundException;
 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
@@ -201,7 +202,9 @@ class PseudoSiteFinder
     {
         $usedPageIds = $this->getExistingSiteConfigurationRootPageIds();
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
-        $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+        $queryBuilder->getRestrictions()->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(FrontendWorkspaceRestriction::class, 0, false));
         $queryBuilder
             ->select('uid')
             ->from('pages')
index 17539ed..0d5fcbf 100644 (file)
@@ -51,7 +51,7 @@
                "fiunchinho/phpunit-randomizer": "^4.0",
                "friendsofphp/php-cs-fixer": "^2.12.2",
                "typo3/cms-styleguide": "~9.2.0",
-               "typo3/testing-framework": "~4.8.0"
+               "typo3/testing-framework": "~4.8.1"
        },
        "suggest": {
                "ext-fileinfo": "Used for proper file type detection in the file abstraction layer",
index 88c1515..0f2da15 100644 (file)
@@ -50,6 +50,7 @@ use TYPO3\CMS\Core\Log\LogManager;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Service\DependencyOrderingService;
+use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
@@ -3155,7 +3156,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         return GeneralUtility::makeInstance(ContentObjectRenderer::class, $this)->typoLink_URL([
             'parameter' => $parameter,
             'addQueryString' => true,
-            'addQueryString.' => ['exclude' => 'id']
+            'addQueryString.' => ['exclude' => 'id'],
+            // ensure absolute URL is generated when having a valid Site
+            'forceAbsoluteUrl' => $GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface
+                && $GLOBALS['TYPO3_REQUEST']->getAttribute('site') instanceof Site
         ]);
     }
 
index 2e34660..1d43d74 100644 (file)
@@ -140,39 +140,40 @@ class LinkGeneratorTest extends AbstractTestCase
     {
         $instructions = [
             // acme.com -> acme.com (same site)
-            [1100, 1000, '/?id=acme-root'],
-            [1100, 1100, '/?id=acme-first'],
-            [1100, 1200, '/?id=1200'],
-            [1100, 1210, '/?id=1210'],
-            [1100, 404, '/?id=404'],
+            ['https://acme.us/', 1100, 1000, '/?id=acme-root'],
+            ['https://acme.us/', 1100, 1100, '/?id=acme-first'],
+            ['https://acme.us/', 1100, 1200, '/?id=1200'],
+            ['https://acme.us/', 1100, 1210, '/?id=1210'],
+            ['https://acme.us/', 1100, 404, '/?id=404'],
             // acme.com -> products.acme.com (nested sub-site)
-            [1100, 1300, '/?id=1300'],
-            [1100, 1310, '/?id=1310'],
+            ['https://acme.us/', 1100, 1300, '/?id=1300'],
+            ['https://acme.us/', 1100, 1310, '/?id=1310'],
             // acme.com -> blog.acme.com (different site)
             // @todo https://blog.acme.com/ not prefixed
-            [1100, 2000, '/?id=blog-root'],
-            [1100, 2100, '/?id=2100'],
-            [1100, 2110, '/john/?id=2110'],
-            [1100, 2111, '/john/?id=2111'],
+            ['https://acme.us/', 1100, 2000, '/?id=blog-root'],
+            ['https://acme.us/', 1100, 2100, '/?id=2100'],
+            ['https://acme.us/', 1100, 2110, '/john/?id=2110'],
+            ['https://acme.us/', 1100, 2111, '/john/?id=2111'],
             // blog.acme.com -> acme.com (different site)
             // @todo https://acme.com/ not prefixed
-            [2100, 1000, '/?id=acme-root'],
-            [2100, 1100, '/?id=acme-first'],
-            [2100, 1200, '/?id=1200'],
-            [2100, 1210, '/?id=1210'],
-            [2100, 404, '/?id=404'],
+            ['https://blog.acme.com/', 2100, 1000, '/?id=acme-root'],
+            ['https://blog.acme.com/', 2100, 1100, '/?id=acme-first'],
+            ['https://blog.acme.com/', 2100, 1200, '/?id=1200'],
+            ['https://blog.acme.com/', 2100, 1210, '/?id=1210'],
+            ['https://blog.acme.com/', 2100, 404, '/?id=404'],
             // blog.acme.com -> products.acme.com (different sub-site)
-            [2100, 1300, '/?id=1300'],
-            [2100, 1310, '/?id=1310'],
+            ['https://blog.acme.com/', 2100, 1300, '/?id=1300'],
+            ['https://blog.acme.com/', 2100, 1310, '/?id=1310'],
         ];
 
         return $this->keysFromTemplate(
             $instructions,
-            '%1$d->%2$d'
+            '%2$d->%3$d'
         );
     }
 
     /**
+     * @param string $hostPrefix
      * @param int $sourcePageId
      * @param int $targetPageId
      * @param string $expectation
@@ -180,10 +181,10 @@ class LinkGeneratorTest extends AbstractTestCase
      * @test
      * @dataProvider linkIsGeneratedDataProvider
      */
-    public function linkIsGenerated(int $sourcePageId, int $targetPageId, string $expectation)
+    public function linkIsGenerated(string $hostPrefix, int $sourcePageId, int $targetPageId, string $expectation)
     {
         $response = $this->executeFrontendRequest(
-            (new InternalRequest())
+            (new InternalRequest($hostPrefix))
                 ->withPageId($sourcePageId)
                 ->withInstructions([
                     $this->createTypoLinkUrlInstruction([
@@ -203,41 +204,41 @@ class LinkGeneratorTest extends AbstractTestCase
     {
         $instructions = [
             // acme.com -> acme.com (same site)
-            [[7100, 1700], 7110, 1000, '/?id=acme-root'],
-            [[7100, 1700], 7110, 1100, '/?id=acme-first'],
-            [[7100, 1700], 7110, 1200, '/?id=1200'],
-            [[7100, 1700], 7110, 1210, '/?id=1210'],
-            [[7100, 1700], 7110, 404, '/?id=404'],
+            ['https://acme.us/', [7100, 1700], 7110, 1000, '/?id=acme-root'],
+            ['https://acme.us/', [7100, 1700], 7110, 1100, '/?id=acme-first'],
+            ['https://acme.us/', [7100, 1700], 7110, 1200, '/?id=1200'],
+            ['https://acme.us/', [7100, 1700], 7110, 1210, '/?id=1210'],
+            ['https://acme.us/', [7100, 1700], 7110, 404, '/?id=404'],
             // acme.com -> products.acme.com (nested sub-site)
-            [[7100, 1700], 7110, 1300, '/?id=1300'],
-            [[7100, 1700], 7110, 1310, '/?id=1310'],
+            ['https://acme.us/', [7100, 1700], 7110, 1300, '/?id=1300'],
+            ['https://acme.us/', [7100, 1700], 7110, 1310, '/?id=1310'],
             // acme.com -> blog.acme.com (different site)
             // @todo https://blog.acme.com/ not prefixed
-            [[7100, 1700], 7110, 2000, '/?id=blog-root'],
-            [[7100, 1700], 7110, 2100, '/?id=2100'],
-            [[7100, 1700], 7110, 2110, '/john/?id=2110'],
-            [[7100, 1700], 7110, 2111, '/john/?id=2111'],
+            ['https://acme.us/', [7100, 1700], 7110, 2000, '/?id=blog-root'],
+            ['https://acme.us/', [7100, 1700], 7110, 2100, '/?id=2100'],
+            ['https://acme.us/', [7100, 1700], 7110, 2110, '/john/?id=2110'],
+            ['https://acme.us/', [7100, 1700], 7110, 2111, '/john/?id=2111'],
             // blog.acme.com -> acme.com (different site)
             // @todo https://acme.com/ not prefixed
-            [[7100, 2700], 7110, 1000, '/?id=acme-root'],
-            [[7100, 2700], 7110, 1100, '/?id=acme-first'],
-            [[7100, 2700], 7110, 1200, '/?id=1200'],
-            [[7100, 2700], 7110, 1210, '/?id=1210'],
-            [[7100, 2700], 7110, 404, '/?id=404'],
+            ['https://blog.acme.com/', [7100, 2700], 7110, 1000, '/?id=acme-root'],
+            ['https://blog.acme.com/', [7100, 2700], 7110, 1100, '/?id=acme-first'],
+            ['https://blog.acme.com/', [7100, 2700], 7110, 1200, '/?id=1200'],
+            ['https://blog.acme.com/', [7100, 2700], 7110, 1210, '/?id=1210'],
+            ['https://blog.acme.com/', [7100, 2700], 7110, 404, '/?id=404'],
             // blog.acme.com -> products.acme.com (different sub-site)
-            [[7100, 2700], 7110, 1300, '/?id=1300'],
-            [[7100, 2700], 7110, 1310, '/?id=1310'],
+            ['https://blog.acme.com/', [7100, 2700], 7110, 1300, '/?id=1300'],
+            ['https://blog.acme.com/', [7100, 2700], 7110, 1310, '/?id=1310'],
         ];
 
         return $this->keysFromTemplate(
             $instructions,
-            '%2$d->%3$d (mount:%1$s)',
+            '%3$d->%4$d (mount:%2$s)',
             function (array $items) {
                 array_splice(
                     $items,
-                    0,
                     1,
-                    [implode('->', $items[0])]
+                    1,
+                    [implode('->', $items[1])]
                 );
                 return $items;
             }
@@ -245,6 +246,7 @@ class LinkGeneratorTest extends AbstractTestCase
     }
 
     /**
+     * @param string $hostPrefix
      * @param array $pageMount
      * @param int $sourcePageId
      * @param int $targetPageId
@@ -253,10 +255,10 @@ class LinkGeneratorTest extends AbstractTestCase
      * @test
      * @dataProvider linkIsGeneratedFromMountPointDataProvider
      */
-    public function linkIsGeneratedFromMountPoint(array $pageMount, int $sourcePageId, int $targetPageId, string $expectation)
+    public function linkIsGeneratedFromMountPoint(string $hostPrefix, array $pageMount, int $sourcePageId, int $targetPageId, string $expectation)
     {
         $response = $this->executeFrontendRequest(
-            (new InternalRequest())
+            (new InternalRequest($hostPrefix))
                 ->withMountPoint(...$pageMount)
                 ->withPageId($sourcePageId)
                 ->withInstructions([
@@ -278,47 +280,48 @@ class LinkGeneratorTest extends AbstractTestCase
         // @todo L-parameter is not applied
         $instructions = [
             // acme.com -> acme.com (same site)
-            [1100, 1100, 0, '/?id=acme-first'],
-            [1100, 1100, 1, '/?id=acme-first'],
-            [1100, 1100, 2, '/?id=acme-first'],
+            ['https://acme.us/', 1100, 1100, 0, '/?id=acme-first'],
+            ['https://acme.us/', 1100, 1100, 1, '/?id=acme-first'],
+            ['https://acme.us/', 1100, 1100, 2, '/?id=acme-first'],
             // @todo Configuration bug on duplicating alias names and uniqueness
-            [1100, 1101, 0, '/?id=acme-first0'],
-            [1100, 1102, 0, '/?id=acme-first1'],
+            ['https://acme.us/', 1100, 1101, 0, '/?id=acme-first0'],
+            ['https://acme.us/', 1100, 1102, 0, '/?id=acme-first1'],
             // acme.com -> products.acme.com (nested sub-site)
-            [1100, 1300, 0, '/?id=1300'],
-            [1100, 1310, 0, '/?id=1310'],
+            ['https://acme.us/', 1100, 1300, 0, '/?id=1300'],
+            ['https://acme.us/', 1100, 1310, 0, '/?id=1310'],
             // acme.com -> archive (outside site)
-            [1100, 3100, 0, 'index.php?id=3100&L=0'],
-            [1100, 3100, 1, 'index.php?id=3100&L=1'],
-            [1100, 3100, 2, 'index.php?id=3100&L=2'],
-            [1100, 3101, 0, 'index.php?id=3101&L=0'],
-            [1100, 3102, 0, 'index.php?id=3102&L=0'],
+            ['https://acme.us/', 1100, 3100, 0, '/index.php?id=3100&L=0'],
+            ['https://acme.us/', 1100, 3100, 1, '/index.php?id=3100&L=1'],
+            ['https://acme.us/', 1100, 3100, 2, '/index.php?id=3100&L=2'],
+            ['https://acme.us/', 1100, 3101, 0, '/index.php?id=3101&L=0'],
+            ['https://acme.us/', 1100, 3102, 0, '/index.php?id=3102&L=0'],
             // blog.acme.com -> acme.com (different site)
             // @todo https://acme.com/ not prefixed
-            [2100, 1100, 0, '/?id=acme-first'],
-            [2100, 1100, 1, '/?id=acme-first'],
-            [2100, 1100, 2, '/?id=acme-first'],
+            ['https://blog.acme.com/', 2100, 1100, 0, '/?id=acme-first'],
+            ['https://blog.acme.com/', 2100, 1100, 1, '/?id=acme-first'],
+            ['https://blog.acme.com/', 2100, 1100, 2, '/?id=acme-first'],
             // @todo Configuration bug on duplicating alias names and uniqueness
-            [2100, 1101, 0, '/?id=acme-first0'],
-            [2100, 1102, 0, '/?id=acme-first1'],
+            ['https://blog.acme.com/', 2100, 1101, 0, '/?id=acme-first0'],
+            ['https://blog.acme.com/', 2100, 1102, 0, '/?id=acme-first1'],
             // blog.acme.com -> archive (outside site)
-            [2100, 3100, 0, 'index.php?id=3100&L=0'],
-            [2100, 3100, 1, 'index.php?id=3100&L=1'],
-            [2100, 3100, 2, 'index.php?id=3100&L=2'],
-            [2100, 3101, 0, 'index.php?id=3101&L=0'],
-            [2100, 3102, 0, 'index.php?id=3102&L=0'],
+            ['https://blog.acme.com/', 2100, 3100, 0, '/index.php?id=3100&L=0'],
+            ['https://blog.acme.com/', 2100, 3100, 1, '/index.php?id=3100&L=1'],
+            ['https://blog.acme.com/', 2100, 3100, 2, '/index.php?id=3100&L=2'],
+            ['https://blog.acme.com/', 2100, 3101, 0, '/index.php?id=3101&L=0'],
+            ['https://blog.acme.com/', 2100, 3102, 0, '/index.php?id=3102&L=0'],
             // blog.acme.com -> products.acme.com (different sub-site)
-            [2100, 1300, 0, '/?id=1300'],
-            [2100, 1310, 0, '/?id=1310'],
+            ['https://blog.acme.com/', 2100, 1300, 0, '/?id=1300'],
+            ['https://blog.acme.com/', 2100, 1310, 0, '/?id=1310'],
         ];
 
         return $this->keysFromTemplate(
             $instructions,
-            '%1$d->%2$d (lang:%3$d)'
+            '%2$d->%3$d (lang:%4$d)'
         );
     }
 
     /**
+     * @param string $hostPrefix
      * @param int $sourcePageId
      * @param int $targetPageId
      * @param int $targetLanguageId
@@ -327,10 +330,10 @@ class LinkGeneratorTest extends AbstractTestCase
      * @test
      * @dataProvider linkIsGeneratedForLanguageDataProvider
      */
-    public function linkIsGeneratedForLanguage(int $sourcePageId, int $targetPageId, int $targetLanguageId, string $expectation)
+    public function linkIsGeneratedForLanguage(string $hostPrefix, int $sourcePageId, int $targetPageId, int $targetLanguageId, string $expectation)
     {
         $response = $this->executeFrontendRequest(
-            (new InternalRequest())
+            (new InternalRequest($hostPrefix))
                 ->withPageId($sourcePageId)
                 ->withInstructions([
                     $this->createTypoLinkUrlInstruction([
@@ -351,39 +354,40 @@ class LinkGeneratorTest extends AbstractTestCase
     {
         $instructions = [
             // acme.com -> acme.com (same site)
-            [1100, 1000, '/?id=acme-root&testing%5Bvalue%5D=1&cHash=7d1f13fa91159dac7feb3c824936b39d'],
-            [1100, 1100, '/?id=acme-first&testing%5Bvalue%5D=1&cHash=f42b850e435f0cedd366f5db749fc1af'],
-            [1100, 1200, '/?id=1200&testing%5Bvalue%5D=1&cHash=784e11c50ea1a13fd7d969df4ec53ea3'],
-            [1100, 1210, '/?id=1210&testing%5Bvalue%5D=1&cHash=ccb7067022b9835ebfd8f720722bc708'],
-            [1100, 404, '/?id=404&testing%5Bvalue%5D=1&cHash=864e96f586a78a53452f3bf0f4d24591'],
+            ['https://acme.us/', 1100, 1000, '/?id=acme-root&testing%5Bvalue%5D=1&cHash=7d1f13fa91159dac7feb3c824936b39d'],
+            ['https://acme.us/', 1100, 1100, '/?id=acme-first&testing%5Bvalue%5D=1&cHash=f42b850e435f0cedd366f5db749fc1af'],
+            ['https://acme.us/', 1100, 1200, '/?id=1200&testing%5Bvalue%5D=1&cHash=784e11c50ea1a13fd7d969df4ec53ea3'],
+            ['https://acme.us/', 1100, 1210, '/?id=1210&testing%5Bvalue%5D=1&cHash=ccb7067022b9835ebfd8f720722bc708'],
+            ['https://acme.us/', 1100, 404, '/?id=404&testing%5Bvalue%5D=1&cHash=864e96f586a78a53452f3bf0f4d24591'],
             // acme.com -> products.acme.com (nested sub-site)
-            [1100, 1300, '/?id=1300&testing%5Bvalue%5D=1&cHash=dbd6597d72ed5098cce3d03eac1eeefe'],
-            [1100, 1310, '/?id=1310&testing%5Bvalue%5D=1&cHash=e64bfc7ab7dd6b70d161e4d556be9726'],
+            ['https://acme.us/', 1100, 1300, '/?id=1300&testing%5Bvalue%5D=1&cHash=dbd6597d72ed5098cce3d03eac1eeefe'],
+            ['https://acme.us/', 1100, 1310, '/?id=1310&testing%5Bvalue%5D=1&cHash=e64bfc7ab7dd6b70d161e4d556be9726'],
             // acme.com -> blog.acme.com (different site)
             // @todo https://blog.acme.com/ not prefixed
-            [1100, 2000, '/?id=blog-root&testing%5Bvalue%5D=1&cHash=a14da633e46dba71640cb85226cd12c5'],
-            [1100, 2100, '/?id=2100&testing%5Bvalue%5D=1&cHash=d23d74cb50383f8788a9930ec8ba679f'],
-            [1100, 2110, '/john/?id=2110&testing%5Bvalue%5D=1&cHash=bf25eea89f44a9a79dabdca98f38a432'],
-            [1100, 2111, '/john/?id=2111&testing%5Bvalue%5D=1&cHash=42dbaeb9172b6b1ca23b49941e194db2'],
+            ['https://acme.us/', 1100, 2000, '/?id=blog-root&testing%5Bvalue%5D=1&cHash=a14da633e46dba71640cb85226cd12c5'],
+            ['https://acme.us/', 1100, 2100, '/?id=2100&testing%5Bvalue%5D=1&cHash=d23d74cb50383f8788a9930ec8ba679f'],
+            ['https://acme.us/', 1100, 2110, '/john/?id=2110&testing%5Bvalue%5D=1&cHash=bf25eea89f44a9a79dabdca98f38a432'],
+            ['https://acme.us/', 1100, 2111, '/john/?id=2111&testing%5Bvalue%5D=1&cHash=42dbaeb9172b6b1ca23b49941e194db2'],
             // blog.acme.com -> acme.com (different site)
             // @todo https://acme.com/ not prefixed
-            [2100, 1000, '/?id=acme-root&testing%5Bvalue%5D=1&cHash=7d1f13fa91159dac7feb3c824936b39d'],
-            [2100, 1100, '/?id=acme-first&testing%5Bvalue%5D=1&cHash=f42b850e435f0cedd366f5db749fc1af'],
-            [2100, 1200, '/?id=1200&testing%5Bvalue%5D=1&cHash=784e11c50ea1a13fd7d969df4ec53ea3'],
-            [2100, 1210, '/?id=1210&testing%5Bvalue%5D=1&cHash=ccb7067022b9835ebfd8f720722bc708'],
-            [2100, 404, '/?id=404&testing%5Bvalue%5D=1&cHash=864e96f586a78a53452f3bf0f4d24591'],
+            ['https://blog.acme.com/', 2100, 1000, '/?id=acme-root&testing%5Bvalue%5D=1&cHash=7d1f13fa91159dac7feb3c824936b39d'],
+            ['https://blog.acme.com/', 2100, 1100, '/?id=acme-first&testing%5Bvalue%5D=1&cHash=f42b850e435f0cedd366f5db749fc1af'],
+            ['https://blog.acme.com/', 2100, 1200, '/?id=1200&testing%5Bvalue%5D=1&cHash=784e11c50ea1a13fd7d969df4ec53ea3'],
+            ['https://blog.acme.com/', 2100, 1210, '/?id=1210&testing%5Bvalue%5D=1&cHash=ccb7067022b9835ebfd8f720722bc708'],
+            ['https://blog.acme.com/', 2100, 404, '/?id=404&testing%5Bvalue%5D=1&cHash=864e96f586a78a53452f3bf0f4d24591'],
             // blog.acme.com -> products.acme.com (different sub-site)
-            [2100, 1300, '/?id=1300&testing%5Bvalue%5D=1&cHash=dbd6597d72ed5098cce3d03eac1eeefe'],
-            [2100, 1310, '/?id=1310&testing%5Bvalue%5D=1&cHash=e64bfc7ab7dd6b70d161e4d556be9726'],
+            ['https://blog.acme.com/', 2100, 1300, '/?id=1300&testing%5Bvalue%5D=1&cHash=dbd6597d72ed5098cce3d03eac1eeefe'],
+            ['https://blog.acme.com/', 2100, 1310, '/?id=1310&testing%5Bvalue%5D=1&cHash=e64bfc7ab7dd6b70d161e4d556be9726'],
         ];
 
         return $this->keysFromTemplate(
             $instructions,
-            '%1$d->%2$d'
+            '%2$d->%3$d'
         );
     }
 
     /**
+     * @param string $hostPrefix
      * @param int $sourcePageId
      * @param int $targetPageId
      * @param string $expectation
@@ -391,10 +395,10 @@ class LinkGeneratorTest extends AbstractTestCase
      * @test
      * @dataProvider linkIsGeneratedWithQueryParametersDataProvider
      */
-    public function linkIsGeneratedWithQueryParameters(int $sourcePageId, int $targetPageId, string $expectation)
+    public function linkIsGeneratedWithQueryParameters(string $hostPrefix, int $sourcePageId, int $targetPageId, string $expectation)
     {
         $response = $this->executeFrontendRequest(
-            (new InternalRequest())
+            (new InternalRequest($hostPrefix))
                 ->withPageId($sourcePageId)
                 ->withInstructions([
                     $this->createTypoLinkUrlInstruction([
@@ -415,42 +419,43 @@ class LinkGeneratorTest extends AbstractTestCase
     public function linkIsGeneratedForRestrictedPageDataProvider(): array
     {
         $instructions = [
-            [1100, 1510, 0, ''],
-            // [1100, 1511, 0, ''], // @todo Fails, not expanded to sub-pages
-            [1100, 1512, 0, ''],
-            [1100, 1515, 0, ''],
-            [1100, 1520, 0, ''],
-            // [1100, 1521, 0, ''], // @todo Fails, not expanded to sub-pages
+            ['https://acme.us/', 1100, 1510, 0, ''],
+            // ['https://acme.us/', 1100, 1511, 0, ''], // @todo Fails, not expanded to sub-pages
+            ['https://acme.us/', 1100, 1512, 0, ''],
+            ['https://acme.us/', 1100, 1515, 0, ''],
+            ['https://acme.us/', 1100, 1520, 0, ''],
+            // ['https://acme.us/', 1100, 1521, 0, ''], // @todo Fails, not expanded to sub-pages
             //
-            [1100, 1510, 1, '/?id=1510'],
-            [1100, 1511, 1, '/?id=1511'],
-            [1100, 1512, 1, '/?id=1512'],
-            [1100, 1515, 1, ''],
-            [1100, 1520, 1, ''],
-            // [1100, 1521, 1, ''], // @todo Fails, not expanded to sub-pages
+            ['https://acme.us/', 1100, 1510, 1, '/?id=1510'],
+            ['https://acme.us/', 1100, 1511, 1, '/?id=1511'],
+            ['https://acme.us/', 1100, 1512, 1, '/?id=1512'],
+            ['https://acme.us/', 1100, 1515, 1, ''],
+            ['https://acme.us/', 1100, 1520, 1, ''],
+            // ['https://acme.us/', 1100, 1521, 1, ''], // @todo Fails, not expanded to sub-pages
             //
-            [1100, 1510, 2, '/?id=1510'],
-            [1100, 1511, 2, '/?id=1511'],
-            [1100, 1512, 2, ''],
-            [1100, 1515, 2, '/?id=1515'],
-            [1100, 1520, 2, '/?id=1520'],
-            [1100, 1521, 2, '/?id=1521'],
+            ['https://acme.us/', 1100, 1510, 2, '/?id=1510'],
+            ['https://acme.us/', 1100, 1511, 2, '/?id=1511'],
+            ['https://acme.us/', 1100, 1512, 2, ''],
+            ['https://acme.us/', 1100, 1515, 2, '/?id=1515'],
+            ['https://acme.us/', 1100, 1520, 2, '/?id=1520'],
+            ['https://acme.us/', 1100, 1521, 2, '/?id=1521'],
             //
-            [1100, 1510, 3, '/?id=1510'],
-            [1100, 1511, 3, '/?id=1511'],
-            [1100, 1512, 3, '/?id=1512'],
-            [1100, 1515, 3, '/?id=1515'],
-            [1100, 1520, 3, '/?id=1520'],
-            [1100, 1521, 3, '/?id=1521'],
+            ['https://acme.us/', 1100, 1510, 3, '/?id=1510'],
+            ['https://acme.us/', 1100, 1511, 3, '/?id=1511'],
+            ['https://acme.us/', 1100, 1512, 3, '/?id=1512'],
+            ['https://acme.us/', 1100, 1515, 3, '/?id=1515'],
+            ['https://acme.us/', 1100, 1520, 3, '/?id=1520'],
+            ['https://acme.us/', 1100, 1521, 3, '/?id=1521'],
         ];
 
         return $this->keysFromTemplate(
             $instructions,
-            '%1$d->%2$d (user:%3$d)'
+            '%2$d->%3$d (user:%4$d)'
         );
     }
 
     /**
+     * @param string $hostPrefix
      * @param int $sourcePageId
      * @param int $targetPageId
      * @param int $frontendUserId
@@ -459,10 +464,10 @@ class LinkGeneratorTest extends AbstractTestCase
      * @test
      * @dataProvider linkIsGeneratedForRestrictedPageDataProvider
      */
-    public function linkIsGeneratedForRestrictedPage(int $sourcePageId, int $targetPageId, int $frontendUserId, string $expectation)
+    public function linkIsGeneratedForRestrictedPage(string $hostPrefix, int $sourcePageId, int $targetPageId, int $frontendUserId, string $expectation)
     {
         $response = $this->executeFrontendRequest(
-            (new InternalRequest())
+            (new InternalRequest($hostPrefix))
                 ->withPageId($sourcePageId)
                 ->withInstructions([
                     $this->createTypoLinkUrlInstruction([
@@ -483,42 +488,43 @@ class LinkGeneratorTest extends AbstractTestCase
     {
         $instructions = [
             // no frontend user given
-            [1100, 1510, 1500, 0, '/?id=1500&pageId=1510'],
-            // [1100, 1511, 1500, 0, '/?id=1500&pageId=1511'], // @todo Fails, not expanded to sub-pages
-            [1100, 1512, 1500, 0, '/?id=1500&pageId=1512'],
-            [1100, 1515, 1500, 0, '/?id=1500&pageId=1515'],
-            [1100, 1520, 1500, 0, '/?id=1500&pageId=1520'],
-            // [1100, 1521, 1500, 0, '/?id=1500&pageId=1521'], // @todo Fails, not expanded to sub-pages
+            ['https://acme.us/', 1100, 1510, 1500, 0, '/?id=1500&pageId=1510'],
+            // ['https://acme.us/', 1100, 1511, 1500, 0, '/?id=1500&pageId=1511'], // @todo Fails, not expanded to sub-pages
+            ['https://acme.us/', 1100, 1512, 1500, 0, '/?id=1500&pageId=1512'],
+            ['https://acme.us/', 1100, 1515, 1500, 0, '/?id=1500&pageId=1515'],
+            ['https://acme.us/', 1100, 1520, 1500, 0, '/?id=1500&pageId=1520'],
+            // ['https://acme.us/', 1100, 1521, 1500, 0, '/?id=1500&pageId=1521'], // @todo Fails, not expanded to sub-pages
             // frontend user 1
-            [1100, 1510, 1500, 1, '/?id=1510'],
-            [1100, 1511, 1500, 1, '/?id=1511'],
-            [1100, 1512, 1500, 1, '/?id=1512'],
-            [1100, 1515, 1500, 1, '/?id=1500&pageId=1515'],
-            [1100, 1520, 1500, 1, '/?id=1500&pageId=1520'],
-            // [1100, 1521, 1500, 1, '/?id=1500&pageId=1521'], // @todo Fails, not expanded to sub-pages
+            ['https://acme.us/', 1100, 1510, 1500, 1, '/?id=1510'],
+            ['https://acme.us/', 1100, 1511, 1500, 1, '/?id=1511'],
+            ['https://acme.us/', 1100, 1512, 1500, 1, '/?id=1512'],
+            ['https://acme.us/', 1100, 1515, 1500, 1, '/?id=1500&pageId=1515'],
+            ['https://acme.us/', 1100, 1520, 1500, 1, '/?id=1500&pageId=1520'],
+            // ['https://acme.us/', 1100, 1521, 1500, 1, '/?id=1500&pageId=1521'], // @todo Fails, not expanded to sub-pages
             // frontend user 2
-            [1100, 1510, 1500, 2, '/?id=1510'],
-            [1100, 1511, 1500, 2, '/?id=1511'],
-            [1100, 1512, 1500, 2, '/?id=1500&pageId=1512'],
-            [1100, 1515, 1500, 2, '/?id=1515'],
-            [1100, 1520, 1500, 2, '/?id=1520'],
-            [1100, 1521, 1500, 2, '/?id=1521'],
+            ['https://acme.us/', 1100, 1510, 1500, 2, '/?id=1510'],
+            ['https://acme.us/', 1100, 1511, 1500, 2, '/?id=1511'],
+            ['https://acme.us/', 1100, 1512, 1500, 2, '/?id=1500&pageId=1512'],
+            ['https://acme.us/', 1100, 1515, 1500, 2, '/?id=1515'],
+            ['https://acme.us/', 1100, 1520, 1500, 2, '/?id=1520'],
+            ['https://acme.us/', 1100, 1521, 1500, 2, '/?id=1521'],
             // frontend user 3
-            [1100, 1510, 1500, 3, '/?id=1510'],
-            [1100, 1511, 1500, 3, '/?id=1511'],
-            [1100, 1512, 1500, 3, '/?id=1512'],
-            [1100, 1515, 1500, 3, '/?id=1515'],
-            [1100, 1520, 1500, 3, '/?id=1520'],
-            [1100, 1521, 1500, 3, '/?id=1521'],
+            ['https://acme.us/', 1100, 1510, 1500, 3, '/?id=1510'],
+            ['https://acme.us/', 1100, 1511, 1500, 3, '/?id=1511'],
+            ['https://acme.us/', 1100, 1512, 1500, 3, '/?id=1512'],
+            ['https://acme.us/', 1100, 1515, 1500, 3, '/?id=1515'],
+            ['https://acme.us/', 1100, 1520, 1500, 3, '/?id=1520'],
+            ['https://acme.us/', 1100, 1521, 1500, 3, '/?id=1521'],
         ];
 
         return $this->keysFromTemplate(
             $instructions,
-            '%1$d->%2$d (via: %3$d, user:%4$d)'
+            '%2$d->%3$d (via: %4$d, user:%5$d)'
         );
     }
 
     /**
+     * @param string $hostPrefix
      * @param int $sourcePageId
      * @param int $targetPageId
      * @param int $loginPageId
@@ -528,10 +534,10 @@ class LinkGeneratorTest extends AbstractTestCase
      * @test
      * @dataProvider linkIsGeneratedForRestrictedPageUsingLoginPageDataProvider
      */
-    public function linkIsGeneratedForRestrictedPageUsingLoginPage(int $sourcePageId, int $targetPageId, int $loginPageId, int $frontendUserId, string $expectation)
+    public function linkIsGeneratedForRestrictedPageUsingLoginPage(string $hostPrefix, int $sourcePageId, int $targetPageId, int $loginPageId, int $frontendUserId, string $expectation)
     {
         $response = $this->executeFrontendRequest(
-            (new InternalRequest())
+            (new InternalRequest($hostPrefix))
                 ->withPageId($sourcePageId)
                 ->withInstructions([
                     (new TypoScriptInstruction(TemplateService::class))
@@ -558,25 +564,26 @@ class LinkGeneratorTest extends AbstractTestCase
         // -> most probably since pid=-1 is not correctly resolved
         $instructions = [
             // acme.com -> acme.com (same site)
-            [1100, 1100, false, '/?id=acme-first'],
-            [1100, 1100, true, 'index.php?id=acme-first'],
-            // [1100, 1950, false, '/?id=1950'], // @todo Not generated for new-placeholder
-            [1100, 1950, true, 'index.php?id={targetPageId}'],
+            ['https://acme.us/', 1100, 1100, false, '/?id=acme-first'],
+            ['https://acme.us/', 1100, 1100, true, '/index.php?id=acme-first&L=0'],
+            // ['https://acme.us/', 1100, 1950, false, '/?id=1950'], // @todo Not generated for new-placeholder
+            ['https://acme.us/', 1100, 1950, true, '/index.php?id={targetPageId}&L=0'],
             // blog.acme.com -> acme.com (different site)
             // @todo https://acme.com/ not prefixed
-            [2100, 1100, false, '/?id=acme-first'],
-            [2100, 1100, true, 'index.php?id=acme-first'],
-            // [2100, 1950, false, '/?id=1950'], // @todo Not generated for new-placeholder
-            [2100, 1950, true, 'index.php?id={targetPageId}'],
+            ['https://blog.acme.com/', 2100, 1100, false, '/?id=acme-first'],
+            ['https://blog.acme.com/', 2100, 1100, true, '/index.php?id=acme-first&L=0'],
+            // ['https://blog.acme.com/', 2100, 1950, false, '/?id=1950'], // @todo Not generated for new-placeholder
+            ['https://blog.acme.com/', 2100, 1950, true, '/index.php?id={targetPageId}&L=0'],
         ];
 
         return $this->keysFromTemplate(
             $instructions,
-            '%1$d->%2$d (resolve:%3$d)'
+            '%2$d->%3$d (resolve:%4$d)'
         );
     }
 
     /**
+     * @param string $hostPrefix
      * @param int $sourcePageId
      * @param int $targetPageId
      * @param bool $resolveVersion
@@ -585,7 +592,7 @@ class LinkGeneratorTest extends AbstractTestCase
      * @test
      * @dataProvider linkIsGeneratedForPageVersionDataProvider
      */
-    public function linkIsGeneratedForPageVersion(int $sourcePageId, int $targetPageId, bool $resolveVersion, string $expectation)
+    public function linkIsGeneratedForPageVersion(string $hostPrefix, int $sourcePageId, int $targetPageId, bool $resolveVersion, string $expectation)
     {
         $workspaceId = 1;
         if ($resolveVersion) {
@@ -598,7 +605,7 @@ class LinkGeneratorTest extends AbstractTestCase
         }
 
         $response = $this->executeFrontendRequest(
-            (new InternalRequest())
+            (new InternalRequest($hostPrefix))
                 ->withPageId($sourcePageId)
                 ->withInstructions([
                     $this->createTypoLinkUrlInstruction([
@@ -622,6 +629,7 @@ class LinkGeneratorTest extends AbstractTestCase
     {
         return [
             'ACME Inc' => [
+                'https://acme.us/',
                 1100,
                 [
                     ['title' => 'EN: Welcome', 'link' => '/?id=acme-first'],
@@ -661,15 +669,15 @@ class LinkGeneratorTest extends AbstractTestCase
                         'children' => [
                             [
                                 'title' => 'Markets',
-                                'link' => 'index.php?id=7110&MP=7100-1700',
+                                'link' => '/index.php?id=7110&MP=7100-1700&L=0',
                             ],
                             [
                                 'title' => 'Products',
-                                'link' => 'index.php?id=7120&MP=7100-1700',
+                                'link' => '/index.php?id=7120&MP=7100-1700&L=0',
                             ],
                             [
                                 'title' => 'Partners',
-                                'link' => 'index.php?id=7130&MP=7100-1700',
+                                'link' => '/index.php?id=7130&MP=7100-1700&L=0',
                             ],
                         ],
                     ],
@@ -679,6 +687,7 @@ class LinkGeneratorTest extends AbstractTestCase
                 ]
             ],
             'ACME Blog' => [
+                'https://blog.acme.com/',
                 2100,
                 [
                     [
@@ -702,15 +711,15 @@ class LinkGeneratorTest extends AbstractTestCase
                             'children' => [
                                 [
                                     'title' => 'Markets',
-                                    'link' => 'index.php?id=7110&MP=7100-2700',
+                                    'link' => '/index.php?id=7110&MP=7100-2700&L=0',
                                 ],
                                 [
                                     'title' => 'Products',
-                                    'link' => 'index.php?id=7120&MP=7100-2700',
+                                    'link' => '/index.php?id=7120&MP=7100-2700&L=0',
                                 ],
                                 [
                                     'title' => 'Partners',
-                                    'link' => 'index.php?id=7130&MP=7100-2700',
+                                    'link' => '/index.php?id=7130&MP=7100-2700&L=0',
                                 ],
                             ],
                         ],
@@ -722,16 +731,17 @@ class LinkGeneratorTest extends AbstractTestCase
     }
 
     /**
+     * @param string $hostPrefix
      * @param int $sourcePageId
      * @param array $expectation
      *
      * @test
      * @dataProvider menuIsGeneratedDataProvider
      */
-    public function menuIsGenerated(int $sourcePageId, array $expectation)
+    public function menuIsGenerated(string $hostPrefix, int $sourcePageId, array $expectation)
     {
         $response = $this->executeFrontendRequest(
-            (new InternalRequest())
+            (new InternalRequest($hostPrefix))
                 ->withPageId($sourcePageId)
                 ->withInstructions([
                     $this->createMenuProcessorInstruction([
index cd08761..a545caf 100644 (file)
@@ -101,8 +101,9 @@ class SiteRequestTest extends AbstractTestCase
     public function shortcutsAreRedirectedDataProvider(): array
     {
         $domainPaths = [
-            '/',
-            'https://localhost/',
+            // @todo Implicit strict mode handling when calling non-existent site
+            // '/',
+            // 'https://localhost/',
             'https://website.local/',
         ];
 
@@ -129,11 +130,14 @@ class SiteRequestTest extends AbstractTestCase
     {
         $this->writeSiteConfiguration(
             'website-local',
-            $this->buildSiteConfiguration(1000, 'https://website.local/')
+            $this->buildSiteConfiguration(1000, 'https://website.local/'),
+            [
+                $this->buildDefaultLanguageConfiguration('EN', '/en-en/'),
+            ]
         );
 
         $expectedStatusCode = 307;
-        $expectedHeaders = ['location' => ['/?id=acme-first']];
+        $expectedHeaders = ['location' => ['https://website.local/en-en/']];
 
         $response = $this->executeFrontendRequest(
             new InternalRequest($uri),
@@ -153,7 +157,10 @@ class SiteRequestTest extends AbstractTestCase
     {
         $this->writeSiteConfiguration(
             'website-local',
-            $this->buildSiteConfiguration(1000, 'https://website.local/')
+            $this->buildSiteConfiguration(1000, 'https://website.local/'),
+            [
+                $this->buildDefaultLanguageConfiguration('EN', '/en-en/'),
+            ]
         );
 
         $expectedStatusCode = 200;
@@ -273,7 +280,8 @@ class SiteRequestTest extends AbstractTestCase
             'https://website.us/',
             'https://website.fr/',
             'https://website.ca/',
-            'https://website.other/',
+            // @todo Implicit strict mode handling when calling non-existent site
+            // 'https://website.other/',
         ];
 
         $queries = [
@@ -754,8 +762,9 @@ class SiteRequestTest extends AbstractTestCase
     public function pageIsRenderedWithValidCacheHashDataProvider(): array
     {
         $domainPaths = [
-            '/',
-            'https://localhost/',
+            // @todo Implicit strict mode handling when calling non-existent site
+            // '/',
+            // 'https://localhost/',
             'https://website.local/',
         ];