diff --git a/composer.json b/composer.json index 4720aed90e37..80455d91ac21 100644 --- a/composer.json +++ b/composer.json @@ -302,6 +302,7 @@ "TYPO3Tests\\ActionControllerArgumentTest\\": "typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Extension/action_controller_argument_test/Classes/", "TYPO3Tests\\ActionControllerTest\\": "typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Extension/action_controller_test/Classes/", "TYPO3Tests\\BlogExample\\": "typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Classes/", + "TYPO3Tests\\TestBolt\\": "typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/Classes/", "TYPO3Tests\\ParentChildTranslation\\": "typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/parent_child_translation/Classes/", "TYPO3Tests\\TestEid\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_eid/Classes/", "TYPO3Tests\\FluidTest\\": "typo3/sysext/fluid/Tests/Functional/Fixtures/Extensions/fluid_test/Classes/", diff --git a/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/Classes/EventListener/AddTypoScriptFromSiteExtensionEventListener.php b/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/Classes/EventListener/AddTypoScriptFromSiteExtensionEventListener.php new file mode 100644 index 000000000000..a24222da3dba --- /dev/null +++ b/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/Classes/EventListener/AddTypoScriptFromSiteExtensionEventListener.php @@ -0,0 +1,151 @@ +getSite(); + if (!$site instanceof Site) { + return; + } + + $emulateBolt = (bool)($site->getConfiguration()['test_bolt_enabled'] ?? false); + if (!$emulateBolt) { + return; + } + $constants = (string)($site->getConfiguration()['test_bolt_constants'] ?? ''); + $setup = (string)($site->getConfiguration()['test_bolt_setup'] ?? ''); + + $siteRootPageId = $site->getRootPageId(); + $rootline = $event->getRootline(); + $sysTemplateRows = $event->getTemplateRows(); + + $highestUid = 1; + foreach ($sysTemplateRows as $sysTemplateRow) { + if ((int)($sysTemplateRow['uid'] ?? 0) > $highestUid) { + $highestUid = (int)$sysTemplateRow['uid']; + } + } + + $fakeRow = [ + 'uid' => $highestUid + 1, + 'pid' => $siteRootPageId, + 'title' => 'Site extension include by test_bolt', + 'root' => 1, + 'clear' => 3, + 'include_static_file' => null, + 'constants' => $constants, + 'config' => $setup, + ]; + // Set various "db" fields conditionally to be as robust as possible in case + // core or some other loaded extension fiddles with them. + $deleteField = $GLOBALS['TCA']['sys_template']['ctrl']['delete'] ?? null; + if ($deleteField) { + $fakeRow[$deleteField] = 0; + } + $disableField = $GLOBALS['TCA']['sys_template']['ctrl']['enablecolumns']['disabled'] ?? null; + if ($disableField) { + $fakeRow[$disableField] = 0; + } + $endtimeField = $GLOBALS['TCA']['sys_template']['ctrl']['enablecolumns']['endtime'] ?? null; + if ($endtimeField) { + $fakeRow[$endtimeField] = 0; + } + $starttimeField = $GLOBALS['TCA']['sys_template']['ctrl']['enablecolumns']['starttime'] ?? null; + if ($starttimeField) { + $fakeRow[$starttimeField] = 0; + } + $sortbyField = $GLOBALS['TCA']['sys_template']['ctrl']['sortby'] ?? null; + if ($sortbyField) { + $fakeRow[$sortbyField] = 0; + } + $tstampField = $GLOBALS['TCA']['sys_template']['ctrl']['tstamp'] ?? null; + if ($tstampField) { + $fakeRow[$tstampField] = self::SIMULATED_TIME; + } + if ($GLOBALS['TCA']['sys_template']['columns']['basedOn'] ?? false) { + $fakeRow['basedOn'] = null; + } + if ($GLOBALS['TCA']['sys_template']['columns']['includeStaticAfterBasedOn'] ?? false) { + $fakeRow['includeStaticAfterBasedOn'] = 0; + } + if ($GLOBALS['TCA']['sys_template']['columns']['static_file_mode'] ?? false) { + $fakeRow['static_file_mode'] = 0; + } + + if (empty($sysTemplateRows)) { + // Simple things first: If there are no sys_template records yet, add our fake row and done. + $sysTemplateRows[] = $fakeRow; + $event->setTemplateRows($sysTemplateRows); + return; + } + + // When there are existing sys_template rows, we try to add our fake row at the most useful position. + $newSysTemplateRows = []; + $pidsBeforeSite = [0]; + $reversedRootline = array_reverse($rootline); + foreach ($reversedRootline as $page) { + if (($page['uid'] ?? 0) !== $siteRootPageId) { + $pidsBeforeSite[] = (int)($page['uid'] ?? 0); + } else { + break; + } + } + $pidsBeforeSite = array_unique($pidsBeforeSite); + + $fakeRowAdded = false; + foreach ($sysTemplateRows as $sysTemplateRow) { + if ($fakeRowAdded) { + // We added the fake row already. Just add all other templates below this. + $newSysTemplateRows[] = $sysTemplateRow; + continue; + } + if (in_array((int)($sysTemplateRow['pid'] ?? 0), $pidsBeforeSite)) { + $newSysTemplateRows[] = $sysTemplateRow; + // If there is a sys_template row *before* our site, we assume settings from above + // templates should "fall through", so we unset the clear flags. If this is not + // wanted, an instance may need to register another event listener after this one + // to set the clear flag again. + $fakeRow['clear'] = 0; + } elseif ((int)($sysTemplateRow['pid'] ?? 0) === $siteRootPageId) { + // There is a sys_template on the site root page already. We add our fake row before + // this one, then force the root and the clear flag of the sys_template row to 0. + $newSysTemplateRows[] = $fakeRow; + $fakeRowAdded = true; + $sysTemplateRow['root'] = 0; + $sysTemplateRow['clear'] = 0; + $newSysTemplateRows[] = $sysTemplateRow; + } else { + // Not a sys_template row before, not an sys_template record on same page. Add our + // fake row and mark we added it. + $newSysTemplateRows[] = $fakeRow; + $newSysTemplateRows[] = $sysTemplateRow; + $fakeRowAdded = true; + } + } + $event->setTemplateRows($newSysTemplateRows); + } +} diff --git a/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/Configuration/Services.yaml b/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/Configuration/Services.yaml new file mode 100644 index 000000000000..8f591253be5b --- /dev/null +++ b/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/Configuration/Services.yaml @@ -0,0 +1,14 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + TYPO3Tests\TestBolt\: + resource: '../Classes/*' + + TYPO3Tests\TestBolt\EventListener\AddTypoScriptFromSiteExtensionEventListener: + public: true + tags: + - name: event.listener + identifier: 'test-bolt/add-typoscript-from-site-extension' diff --git a/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/composer.json b/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/composer.json new file mode 100644 index 000000000000..10c92cccd1b8 --- /dev/null +++ b/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/composer.json @@ -0,0 +1,20 @@ +{ + "name": "typo3tests/test-bolt", + "type": "typo3-cms-extension", + "description": "This extension simulates b13/bolt.", + "license": "GPL-2.0-or-later", + "require": { + "typo3/cms-core": "13.1.*@dev", + "typo3/cms-frontend": "13.1.*@dev" + }, + "extra": { + "typo3/cms": { + "extension-key": "test_bolt" + } + }, + "autoload": { + "psr-4": { + "TYPO3Tests\\TestBolt\\": "Classes/" + } + } +} diff --git a/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/ext_emconf.php b/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/ext_emconf.php new file mode 100644 index 000000000000..d0f26436753a --- /dev/null +++ b/typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt/ext_emconf.php @@ -0,0 +1,21 @@ + 'This extension simulates b13/bolt.', + 'description' => 'This extension simulates b13/bolt.', + 'category' => 'example', + 'version' => '13.1.0', + 'state' => 'beta', + 'author' => 'Stefan Bürk', + 'author_email' => 'stefan@buerk.tech', + 'author_company' => '', + 'constraints' => [ + 'depends' => [ + 'typo3' => '13.1.0', + ], + 'conflicts' => [], + 'suggests' => [], + ], +]; diff --git a/typo3/sysext/redirects/Tests/Functional/Service/Fixtures/SourceHostWithoutSourceConfigRedirect.csv b/typo3/sysext/redirects/Tests/Functional/Service/Fixtures/SourceHostWithoutSourceConfigRedirect.csv new file mode 100644 index 000000000000..c3ecc7aba455 --- /dev/null +++ b/typo3/sysext/redirects/Tests/Functional/Service/Fixtures/SourceHostWithoutSourceConfigRedirect.csv @@ -0,0 +1,8 @@ +"pages",,,,,,,, +,"uid","pid","title","slug","hidden","deleted","fe_group", +,1,0,"Root","/",0,0,0, +,2,1,"Page2","/page2",0,0,0, +"sys_redirect",,,,,,,, +,"uid","pid","target_statuscode","disabled","deleted","source_host","source_path","target" +,1,0,301,0,0,"non-configured.domain.tld","/redirect-to-pid1","t3://page?uid=1" +,2,0,301,0,0,"non-configured.domain.tld","/redirect-to-pid2","t3://page?uid=2" diff --git a/typo3/sysext/redirects/Tests/Functional/Service/RedirectServiceTest.php b/typo3/sysext/redirects/Tests/Functional/Service/RedirectServiceTest.php index 6c2b96de7ebf..3e1c7dd8d2b6 100644 --- a/typo3/sysext/redirects/Tests/Functional/Service/RedirectServiceTest.php +++ b/typo3/sysext/redirects/Tests/Functional/Service/RedirectServiceTest.php @@ -50,6 +50,10 @@ final class RedirectServiceTest extends FunctionalTestCase protected array $coreExtensionsToLoad = ['redirects']; + protected array $testExtensionsToLoad = [ + 'typo3/sysext/redirects/Tests/Functional/Fixtures/Extensions/test_bolt', + ]; + protected array $testFilesToDelete = []; protected array $configurationToUseInTestInstance = [ @@ -972,4 +976,94 @@ public function regExpRedirectsWithArgumentMatchesWithSimilarRegExpWithoutQueryP self::assertEquals('TYPO3 Redirect ' . $redirectUid, $response->getHeader('X-Redirect-By')[0]); self::assertEquals($targetUrl, $response->getHeader('location')[0]); } + + public static function sourceHostNotNotContainedInAnySiteConfigRedirectIsRedirectedDataProvider(): \Generator + { + yield 'non-configured source_host with site rootpage target using T3 LinkHandler syntax' => [ + 'request' => new InternalRequest('https://non-configured.domain.tld/redirect-to-pid1'), + 'rootPageTypoScriptFiles' => ['setup' => ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']], + 'useTestBolt' => false, + 'expectedRedirectStatusCode' => 301, + 'expectedRedirectUid' => 1, + 'expectedRedirectLocationUri' => 'https://acme.com/', + ]; + yield 'non-configured source_host with site sub-page target using T3 LinkHandler syntax' => [ + 'request' => new InternalRequest('https://non-configured.domain.tld/redirect-to-pid2'), + 'rootPageTypoScriptFiles' => ['setup' => ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']], + 'useTestBolt' => false, + 'expectedRedirectStatusCode' => 301, + 'expectedRedirectUid' => 2, + 'expectedRedirectLocationUri' => 'https://acme.com/page2', + ]; + // Regression test for https://forge.typo3.org/issues/103395 + // @todo Enable again in bugfix change. + // yield 'non-configured source_host with site root target without typoscript using T3 LinkHandler syntax' => [ + // 'request' => new InternalRequest('https://non-configured.domain.tld/redirect-to-pid1'), + // 'rootPageTypoScriptFiles' => ['setup' => ['EXT:redirects/Tests/Functional/Service/Fixtures/Redirects.typoscript']], + // 'useTestBolt' => true, + // 'expectedRedirectStatusCode' => 301, + // 'expectedRedirectUid' => 1, + // 'expectedRedirectLocationUri' => 'https://acme.com/', + // ]; + } + + /** + * @param array{constants?: string[], setup?: string[]} $rootPageTypoScriptFiles + */ + #[DataProvider('sourceHostNotNotContainedInAnySiteConfigRedirectIsRedirectedDataProvider')] + #[Test] + public function sourceHostNotNotContainedInAnySiteConfigRedirectIsRedirected( + InternalRequest $request, + array $rootPageTypoScriptFiles, + bool $useTestBolt, + int $expectedRedirectStatusCode, + int $expectedRedirectUid, + string $expectedRedirectLocationUri, + ): void { + $this->importCSVDataSet(__DIR__ . '/Fixtures/SourceHostWithoutSourceConfigRedirect.csv'); + $this->writeSiteConfiguration( + 'acme-com', + $this->buildSiteConfiguration(1, 'https://acme.com/') + ); + if ($useTestBolt === true) { + $constants = ''; + foreach ($rootPageTypoScriptFiles['constants'] ?? [] as $typoScriptFile) { + if (!str_starts_with($typoScriptFile, 'EXT:')) { + // @deprecated will be removed in version 8, use "EXT:" syntax instead + $constants .= '' . LF; + } else { + $constants .= '@import \'' . $typoScriptFile . '\'' . LF; + } + } + $setup = ''; + foreach ($rootPageTypoScriptFiles['setup'] ?? [] as $typoScriptFile) { + if (!str_starts_with($typoScriptFile, 'EXT:')) { + // @deprecated will be removed in version 8, use "EXT:" syntax instead + $setup .= '' . LF; + } else { + $setup .= '@import \'' . $typoScriptFile . '\'' . LF; + } + } + $this->mergeSiteConfiguration('acme-com', [ + 'test_bolt_enabled' => true, + 'test_bolt_constants' => $constants, + 'test_bolt_setup' => $setup, + ]); + $connection = $this->getConnectionPool()->getConnectionForTable('pages'); + $connection->update( + 'pages', + ['is_siteroot' => 1], + ['uid' => 1] + ); + } else { + $this->setUpFrontendRootPage(1, $rootPageTypoScriptFiles); + } + + $response = $this->executeFrontendSubRequest($request); + self::assertEquals($expectedRedirectStatusCode, $response->getStatusCode()); + self::assertIsArray($response->getHeader('X-Redirect-By')); + self::assertIsArray($response->getHeader('location')); + self::assertEquals('TYPO3 Redirect ' . $expectedRedirectUid, $response->getHeader('X-Redirect-By')[0]); + self::assertEquals($expectedRedirectLocationUri, $response->getHeader('location')[0]); + } }