‪TYPO3CMS  ‪main
SiteMatcherTest.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use PHPUnit\Framework\Attributes\DataProvider;
21 use PHPUnit\Framework\Attributes\Test;
22 use PHPUnit\Framework\MockObject\MockObject;
32 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
33 
34 final class ‪SiteMatcherTest extends UnitTestCase
35 {
36  #[Test]
38  {
39  $site = new ‪Site('main', 1, [
40  'base' => '/',
41  'languages' => [
42  0 => [
43  'title' => 'English',
44  'languageId' => 0,
45  'base' => 'http://9-5.typo3.test/',
46  'locale' => 'en_US-UTF-8',
47  ],
48  2 => [
49  'title' => 'Deutsch',
50  'languageId' => 2,
51  'base' => 'http://de.9-5.typo3.test/',
52  'locale' => 'en_US-UTF-8',
53  ],
54  1 => [
55  'title' => 'Dansk',
56  'languageId' => 1,
57  'base' => 'http://9-5.typo3.test/da/',
58  'locale' => 'da_DK.UTF-8',
59  ],
60  ],
61  ]);
62  $secondSite = new ‪Site('second', 13, [
63  'base' => '/',
64  'languages' => [
65  0 => [
66  'title' => 'English',
67  'languageId' => 0,
68  'base' => '/en/',
69  'locale' => 'en_US-UTF-8',
70  ],
71  1 => [
72  'title' => 'Dansk',
73  'languageId' => 1,
74  'base' => '/da/',
75  'locale' => 'da_DK.UTF-8',
76  ],
77  ],
78  ]);
79  $featuresMock = $this->‪createFeaturesMock();
80  $finderMock = $this->‪createSiteFinder($site, $secondSite);
81  $requestContextFactory = new ‪RequestContextFactory(new ‪BackendEntryPointResolver());
82  $subject = new ‪SiteMatcher($featuresMock, $finderMock, $requestContextFactory);
83 
84  $request = new ‪ServerRequest('http://9-5.typo3.test/da/my-page/');
86  $result = $subject->matchRequest($request);
87  self::assertEquals(1, $result->getLanguage()->getLanguageId());
88 
89  $request = new ‪ServerRequest('http://9-5.typo3.test/da');
91  $result = $subject->matchRequest($request);
92  // Matches danish, as path fits
93  self::assertEquals(1, $result->getLanguage()->getLanguageId());
94 
95  $request = new ‪ServerRequest('https://9-5.typo3.test/da');
97  $result = $subject->matchRequest($request);
98  // Matches the second site, as this is HTTPS and HTTP
99  self::assertEquals('second', $result->getSite()->getIdentifier());
100  self::assertEquals(1, $result->getLanguage()->getLanguageId());
101 
102  $request = new ‪ServerRequest('http://de.9-5.typo3.test/da');
104  $result = $subject->matchRequest($request);
105  // Matches german, as the domain fits!
106  self::assertEquals(2, $result->getLanguage()->getLanguageId());
107 
108  $request = new ‪ServerRequest('http://9-5.typo3.test/');
110  $result = $subject->matchRequest($request);
111  // Matches english
112  self::assertEquals(0, $result->getLanguage()->getLanguageId());
113 
114  // @todo this is a random result and needs to be refined
115  // www.example.com is not defined at all, but it actually "matches"...
116  $request = new ‪ServerRequest('http://www.example.com/');
118  $result = $subject->matchRequest($request);
119  // finds the second site, since that configuration does not have a host part, thus only `/` matches
120  // @todo for future versions (TYPO3 v12+) this should be adjusted and be more explicit by enforcing host names
121  self::assertNull($result->getLanguage());
122  self::assertEquals('second', $result->getSite()->getIdentifier());
123  }
124 
128  #[Test]
130  {
131  $site = new ‪Site('main', 1, [
132  'base' => 'https://www.example.com/',
133  'languages' => [
134  0 => [
135  'title' => 'English',
136  'languageId' => 0,
137  'base' => 'http://example.us/',
138  'locale' => 'en_US-UTF-8',
139  ],
140  2 => [
141  'title' => 'Deutsch',
142  'languageId' => 2,
143  'base' => 'http://www.example.de/',
144  'locale' => 'en_US-UTF-8',
145  ],
146  1 => [
147  'title' => 'Dansk',
148  'languageId' => 1,
149  'base' => 'http://www.example.com/da/',
150  'locale' => 'da_DK.UTF-8',
151  ],
152  3 => [
153  'title' => 'French',
154  'languageId' => 3,
155  'base' => '/fr/',
156  'locale' => 'fr_FR.UTF-8',
157  ],
158  ],
159  ]);
160  $secondSite = new ‪Site('second', 13, [
161  'base' => '/',
162  'languages' => [
163  0 => [
164  'title' => 'English',
165  'languageId' => 0,
166  'base' => '/en/',
167  'locale' => 'en_US-UTF-8',
168  ],
169  1 => [
170  'title' => 'Dansk',
171  'languageId' => 1,
172  'base' => '/da/',
173  'locale' => 'da_DK.UTF-8',
174  ],
175  ],
176  ]);
177  $featuresMock = $this->‪createFeaturesMock();
178  $finderMock = $this->‪createSiteFinder($site, $secondSite);
179  $requestContextFactory = new ‪RequestContextFactory(new ‪BackendEntryPointResolver());
180  $subject = new ‪SiteMatcher($featuresMock, $finderMock, $requestContextFactory);
181 
182  $request = new ‪ServerRequest('https://www.example.com/de');
184  $result = $subject->matchRequest($request);
185  // Site found, but no language
186  self::assertEquals($site, $result->getSite());
187  self::assertNull($result->getLanguage());
188 
189  $request = new ‪ServerRequest('http://www.other-domain.com/da');
191  $result = $subject->matchRequest($request);
192  self::assertEquals($secondSite, $result->getSite());
193  self::assertEquals(1, $result->getLanguage()->getLanguageId());
194 
195  $request = new ‪ServerRequest('http://www.other-domain.com/de');
197  $result = $subject->matchRequest($request);
198  // No language for this solution
199  self::assertEquals($secondSite, $result->getSite());
200  self::assertNull($result->getLanguage());
201  }
202 
203  public static function ‪bestMatchingUrlIsUsedDataProvider(): \Generator
204  {
205  yield ['https://example.org/page', '1-main', 'en-US'];
206  yield ['https://example.org/', '1-main', 'en-US'];
207  yield ['https://example.org/f', '1-main', 'en-US'];
208  yield ['https://example.org/fr', '3-fr', 'fr-FR'];
209  yield ['https://example.org/friendly-page', '1-main', 'en-US'];
210  yield ['https://example.org/fr/', '3-fr', 'fr-FR'];
211  yield ['https://example.org/fr/page', '3-fr', 'fr-FR'];
212  yield ['https://example.org/de', '1-main', 'de-DE'];
213  yield ['https://example.org/deterministic', '1-main', 'en-US'];
214  yield ['https://example.org/da', '2-da', 'da-DK'];
215  yield ['https://example.org/daother', '1-main', 'en-US'];
216  }
217 
218  #[DataProvider('bestMatchingUrlIsUsedDataProvider')]
219  #[Test]
220  public function ‪bestMatchingUrlIsUsed(string $requestUri, string $expectedSite, string $expectedLocale): void
221  {
222  $mainSite = new ‪Site('1-main', 31, [
223  'base' => 'https://example.org/',
224  'languages' => [
225  [
226  'languageId' => 0,
227  'base' => 'https://example.org/',
228  'locale' => 'en-US',
229  ],
230  [
231  'languageId' => 2,
232  'base' => 'https://example.org/de/',
233  'locale' => 'de-DE',
234  ],
235  ],
236  ]);
237  $dkSite = new ‪Site('2-da', 21, [
238  'base' => 'https://example.org/',
239  'languages' => [
240  [
241  'languageId' => 0,
242  'base' => 'https://example.org/da/',
243  'locale' => 'da-DK',
244  ],
245  ],
246  ]);
247  $frSite = new ‪Site('3-fr', 11, [
248  'base' => 'https://example.org/',
249  'languages' => [
250  [
251  'languageId' => 0,
252  'base' => 'https://example.org/fr/',
253  'locale' => 'fr-FR',
254  ],
255  ],
256  ]);
257 
258  $featuresMock = $this->‪createFeaturesMock();
259  $finderMock = $this->‪createSiteFinder($mainSite, $dkSite, $frSite);
260  $requestContextFactory = new ‪RequestContextFactory(new ‪BackendEntryPointResolver());
261  $subject = new ‪SiteMatcher($featuresMock, $finderMock, $requestContextFactory);
262 
263  $request = new ‪ServerRequest($requestUri);
265  $result = $subject->matchRequest($request);
266 
267  self::assertSame($expectedSite, $result->getSite()->getIdentifier());
268  self::assertSame($expectedLocale, (string)$result->getLanguage()->getLocale());
269  }
270 
271  private function ‪createFeaturesMock(): MockObject&‪Features
272  {
273  $mock = $this->getMockBuilder(Features::class)
274  ->onlyMethods(['isFeatureEnabled'])
275  ->getMock();
276  $mock->expects(self::any())
277  ->method('isFeatureEnabled')
278  ->with('security.frontend.allowInsecureSiteResolutionByQueryParameters')
279  ->willReturn(false);
280  return $mock;
281  }
282 
283  private function ‪createSiteFinder(‪Site ...$sites): ‪SiteFinder
284  {
285  $siteConfiguration = new class ($sites) extends ‪SiteConfiguration {
286  public function __construct(
287  protected array $sites
288  ) {
289  // empty by default
290  }
291 
292  public function getAllExistingSites(bool $useCache = true): array
293  {
294  return array_combine(
295  array_map(static function (‪Site $site) { return $site->‪getIdentifier(); }, $this->sites),
296  $this->sites
297  );
298  }
299  };
300  return new ‪SiteFinder(new $siteConfiguration($sites));
301  }
302 }
‪TYPO3\CMS\Core\Tests\Unit\Routing\SiteMatcherTest\bestMatchingUrlIsUsed
‪bestMatchingUrlIsUsed(string $requestUri, string $expectedSite, string $expectedLocale)
Definition: SiteMatcherTest.php:220
‪TYPO3\CMS\Core\Tests\Unit\Routing\SiteMatcherTest\bestMatchingUrlIsUsedDataProvider
‪static bestMatchingUrlIsUsedDataProvider()
Definition: SiteMatcherTest.php:203
‪TYPO3\CMS\Core\Tests\Unit\Routing
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Core\Tests\Unit\Routing\SiteMatcherTest\createSiteFinder
‪createSiteFinder(Site ... $sites)
Definition: SiteMatcherTest.php:283
‪TYPO3\CMS\Core\Configuration\SiteConfiguration
Definition: SiteConfiguration.php:47
‪TYPO3\CMS\Core\Tests\Unit\Routing\SiteMatcherTest\fullUrlMatchesSpecificLanguageWithSubdomainsAndPathSuffixes
‪fullUrlMatchesSpecificLanguageWithSubdomainsAndPathSuffixes()
Definition: SiteMatcherTest.php:129
‪TYPO3\CMS\Core\Site\Entity\Site
Definition: Site.php:42
‪TYPO3\CMS\Core\Tests\Unit\Routing\SiteMatcherTest\createFeaturesMock
‪createFeaturesMock()
Definition: SiteMatcherTest.php:271
‪TYPO3\CMS\Core\Routing\SiteRouteResult
Definition: SiteRouteResult.php:30
‪TYPO3\CMS\Core\Configuration\Features
Definition: Features.php:56
‪TYPO3\CMS\Core\Http\ServerRequest
Definition: ServerRequest.php:39
‪TYPO3\CMS\Core\Tests\Unit\Routing\SiteMatcherTest\fullUrlMatchesSpecificLanguageWithSubdomainsAndDomainSuffixes
‪fullUrlMatchesSpecificLanguageWithSubdomainsAndDomainSuffixes()
Definition: SiteMatcherTest.php:37
‪TYPO3\CMS\Core\Tests\Unit\Routing\SiteMatcherTest
Definition: SiteMatcherTest.php:35
‪TYPO3\CMS\Core\Routing\BackendEntryPointResolver
Definition: BackendEntryPointResolver.php:29
‪TYPO3\CMS\Core\Site\Entity\Site\getIdentifier
‪getIdentifier()
Definition: Site.php:185
‪TYPO3\CMS\Core\Routing\SiteMatcher
Definition: SiteMatcher.php:52
‪TYPO3\CMS\Core\Routing\RequestContextFactory
Definition: RequestContextFactory.php:30