Skip to content

Commit

Permalink
[TASK] Add RedirectService tests as preparation for a bugfix
Browse files Browse the repository at this point in the history
Issue with the `RedirectService` has been reported
wiht #103395 related to non-matching SiteConfig in
cases no TypoScript template records exists, like
when `b13/bolt` is used in instances.

Changes #103437, #103439 and #103554 fixed the
mentioned issue #103395 for TYPO3 v13, leaving
the issue open for TYPO3 v12.

Note: For v12 the regression test dataset needs
to be disabled for now, and will be enabled with
the upcoming fix.

This change adds now some tests for non matching
SiteConfiguration for `source_hosts`.

Resolves: #103555
Related: #103395
Related: #103554
Related: #103439
Related: #103437
Releases: main, 12.4
Change-Id: I2c72de3fa5f8d8aae5e6d2ca702ea4685cd367b3
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/83695
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: Stefan Bürk <stefan@buerk.tech>
  • Loading branch information
sbuerk committed Apr 7, 2024
1 parent 7279f63 commit ad2f507
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 0 deletions.
1 change: 1 addition & 0 deletions composer.json
Expand Up @@ -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/",
Expand Down
@@ -0,0 +1,151 @@
<?php

declare(strict_types=1);

/*
* 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!
*/

namespace TYPO3Tests\TestBolt\EventListener;

use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\TypoScript\IncludeTree\Event\AfterTemplatesHaveBeenDeterminedEvent;

final class AddTypoScriptFromSiteExtensionEventListener
{
// 2024-04-07T13:58:03+00:00 (UTC/GMT)
private const SIMULATED_TIME = 1712498283;

public function __invoke(AfterTemplatesHaveBeenDeterminedEvent $event): void
{
$site = $event->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);
}
}
@@ -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'
@@ -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/"
}
}
}
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

$EM_CONF[$_EXTKEY] = [
'title' => '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' => [],
],
];
@@ -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"
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 .= '<INCLUDE_TYPOSCRIPT: source="FILE:' . $typoScriptFile . '">' . 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 .= '<INCLUDE_TYPOSCRIPT: source="FILE:' . $typoScriptFile . '">' . 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]);
}
}

0 comments on commit ad2f507

Please sign in to comment.