‪TYPO3CMS  ‪main
SiteResolverTest.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;
23 use Psr\Http\Message\ResponseInterface;
24 use Psr\Http\Message\ServerRequestInterface;
25 use Psr\Http\Server\RequestHandlerInterface;
39 use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
40 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
41 
42 final class ‪SiteResolverTest extends UnitTestCase
43 {
44  protected bool ‪$resetSingletonInstances = true;
45  protected ‪SiteFinder&AccessibleObjectInterface ‪$siteFinder;
46 
47  protected RequestHandlerInterface ‪$siteFoundRequestHandler;
48  protected string ‪$originalLocale;
49 
50  protected function ‪setUp(): void
51  {
52  parent::setUp();
53  $this->originalLocale = setlocale(LC_COLLATE, '0');
54  $this->siteFinder = $this->getAccessibleMock(SiteFinder::class, null, [], '', false);
55 
56  // A request handler which expects a site to be found.
57  $this->siteFoundRequestHandler = new class () implements RequestHandlerInterface {
58  public function handle(ServerRequestInterface $request): ResponseInterface
59  {
60  $site = $request->getAttribute('site', false);
61  $language = $request->getAttribute('language', false);
62  if ($site && $language) {
63  return new ‪JsonResponse(
64  [
65  'site' => $site->getIdentifier(),
66  'language-id' => $language->getLanguageId(),
67  'language-base' => (string)$language->getBase(),
68  'rootpage' => $site->getRootPageId(),
69  ]
70  );
71  }
72  return new ‪NullResponse();
73  }
74  };
75 
76  $cacheManagerMock = $this->getMockBuilder(CacheManager::class)->disableOriginalConstructor()->getMock();
77  GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerMock);
78  }
79 
80  protected function ‪tearDown(): void
81  {
82  // restore locale to original setting
83  setlocale(LC_COLLATE, $this->originalLocale);
84  setlocale(LC_MONETARY, $this->originalLocale);
85  setlocale(LC_TIME, $this->originalLocale);
86  parent::tearDown();
87  }
88 
95  #[Test]
97  {
98  $incomingUrl = 'https://a-random-domain.com/mysite/';
99  ‪$siteIdentifier = 'full-site';
101  'base' => '/mysite/',
102  'languages' => [
103  0 => [
104  'languageId' => 0,
105  'locale' => 'fr_FR.UTF-8',
106  'base' => '/',
107  ],
108  ],
109  ]));
110  $featuresMock = $this->‪createFeaturesMock();
111  $requestContextFactory = new ‪RequestContextFactory(new ‪BackendEntryPointResolver());
112  $subject = new ‪SiteResolver(new ‪SiteMatcher($featuresMock, ‪$siteFinder, $requestContextFactory));
113 
114  $request = new ‪ServerRequest($incomingUrl, 'GET');
115  $response = $subject->process($request, $this->siteFoundRequestHandler);
116 
117  if ($response instanceof ‪NullResponse) {
118  self::fail('No site configuration found in URL ' . $incomingUrl . '.');
119  } else {
120  $result = $response->getBody()->getContents();
121  $result = json_decode($result, true);
122  self::assertEquals(‪$siteIdentifier, $result['site']);
123  self::assertEquals(0, $result['language-id']);
124  self::assertEquals('/mysite/', $result['language-base']);
125  }
126  }
127 
137  #[Test]
139  {
140  $incomingUrl = 'https://www.random-result.com/mysubsite/you-know-why/';
142  new ‪Site('outside-site', 13, [
143  'base' => '/',
144  'languages' => [
145  0 => [
146  'languageId' => 0,
147  'locale' => 'fr_FR.UTF-8',
148  'base' => '/',
149  ],
150  ],
151  ]),
152  new ‪Site('sub-site', 15, [
153  'base' => '/mysubsite/',
154  'languages' => [
155  0 => [
156  'languageId' => 0,
157  'locale' => 'fr_FR.UTF-8',
158  'base' => '/',
159  ],
160  ],
161  ]),
162  );
163 
164  $featuresMock = $this->‪createFeaturesMock();
165  $requestContextFactory = new ‪RequestContextFactory(new ‪BackendEntryPointResolver());
166  $subject = new ‪SiteResolver(new ‪SiteMatcher($featuresMock, ‪$siteFinder, $requestContextFactory));
167 
168  $request = new ‪ServerRequest($incomingUrl, 'GET');
169  $response = $subject->process($request, $this->siteFoundRequestHandler);
170  if ($response instanceof ‪NullResponse) {
171  self::fail('No site configuration found in URL ' . $incomingUrl . '.');
172  } else {
173  $result = $response->getBody()->getContents();
174  $result = json_decode($result, true);
175  self::assertEquals('sub-site', $result['site']);
176  self::assertEquals(0, $result['language-id']);
177  self::assertEquals('/mysubsite/', $result['language-base']);
178  }
179  }
180 
182  {
183  return [
184  'matches second site' => [
185  'https://www.random-result.com/mysubsite/you-know-why/',
186  'sub-site',
187  14,
188  '/mysubsite/',
189  ],
190  'matches third site' => [
191  'https://www.random-result.com/mysubsite/micro-site/oh-yes-you-do/',
192  'subsub-site',
193  15,
194  '/mysubsite/micro-site/',
195  ],
196  'matches a subsite in first site' => [
197  'https://www.random-result.com/products/pampers/',
198  'outside-site',
199  13,
200  '/',
201  ],
202  ];
203  }
204 
218  #[DataProvider('detectSubSubsiteInsideNestedUrlStructureDataProvider')]
219  #[Test]
220  public function ‪detectSubSubsiteInsideNestedUrlStructure($incomingUrl, $expectedSiteIdentifier, $expectedRootPageId, $expectedBase): void
221  {
223  new ‪Site('outside-site', 13, [
224  'base' => '/',
225  'languages' => [
226  0 => [
227  'languageId' => 0,
228  'locale' => 'fr_FR.UTF-8',
229  'base' => '/',
230  ],
231  ],
232  ]),
233  new ‪Site('sub-site', 14, [
234  'base' => '/mysubsite/',
235  'languages' => [
236  0 => [
237  'languageId' => 0,
238  'locale' => 'fr_FR.UTF-8',
239  'base' => '/',
240  ],
241  ],
242  ]),
243  new ‪Site('subsub-site', 15, [
244  'base' => '/mysubsite/micro-site/',
245  'languages' => [
246  0 => [
247  'languageId' => 0,
248  'locale' => 'fr_FR.UTF-8',
249  'base' => '/',
250  ],
251  ],
252  ]),
253  );
254 
255  $featuresMock = $this->‪createFeaturesMock();
256  $requestContextFactory = new ‪RequestContextFactory(new ‪BackendEntryPointResolver());
257  $subject = new ‪SiteResolver(new ‪SiteMatcher($featuresMock, ‪$siteFinder, $requestContextFactory));
258 
259  $request = new ‪ServerRequest($incomingUrl, 'GET');
260  $response = $subject->process($request, $this->siteFoundRequestHandler);
261 
262  if ($response instanceof ‪NullResponse) {
263  self::fail('No site configuration found in URL ' . $incomingUrl . '.');
264  } else {
265  $result = $response->getBody()->getContents();
266  $result = json_decode($result, true);
267  self::assertEquals($expectedSiteIdentifier, $result['site']);
268  self::assertEquals($expectedRootPageId, $result['rootpage']);
269  self::assertEquals($expectedBase, $result['language-base']);
270  }
271  }
272 
273  public static function ‪detectProperLanguageByIncomingUrlDataProvider(): array
274  {
275  return [
276  'matches second site' => [
277  'https://www.random-result.com/mysubsite/you-know-why/',
278  'sub-site',
279  14,
280  2,
281  '/mysubsite/',
282  ],
283  'matches second site in other language' => [
284  'https://www.random-result.com/mysubsite/it/you-know-why/',
285  'sub-site',
286  14,
287  2,
288  '/mysubsite/',
289  ],
290  'matches third site' => [
291  'https://www.random-result.com/mysubsite/micro-site/ru/oh-yes-you-do/',
292  'subsub-site',
293  15,
294  13,
295  '/mysubsite/micro-site/ru/',
296  ],
297  'matches a subpage in first site' => [
298  'https://www.random-result.com/en/products/pampers/',
299  'outside-site',
300  13,
301  0,
302  '/en/',
303  ],
304  'matches a subpage with translation in first site' => [
305  'https://www.random-result.com/fr/products/pampers/',
306  'outside-site',
307  13,
308  1,
309  '/fr/',
310  ],
311  ];
312  }
313 
324  #[DataProvider('detectProperLanguageByIncomingUrlDataProvider')]
325  #[Test]
326  public function ‪detectProperLanguageByIncomingUrl($incomingUrl, $expectedSiteIdentifier, $expectedRootPageId, $expectedLanguageId, $expectedBase): void
327  {
329  new ‪Site('outside-site', 13, [
330  'base' => '/',
331  'languages' => [
332  0 => [
333  'languageId' => 0,
334  'locale' => 'en_US.UTF-8',
335  'base' => '/en/',
336  ],
337  1 => [
338  'languageId' => 1,
339  'locale' => 'fr_CA.UTF-8',
340  'base' => '/fr/',
341  ],
342  ],
343  ]),
344  new ‪Site('sub-site', 14, [
345  'base' => '/mysubsite/',
346  'languages' => [
347  2 => [
348  'languageId' => 2,
349  'locale' => 'it_IT.UTF-8',
350  'base' => '/',
351  ],
352  ],
353  ]),
354  new ‪Site('subsub-site', 15, [
355  'base' => '/mysubsite/micro-site/',
356  'languages' => [
357  13 => [
358  'languageId' => 13,
359  'locale' => 'ru_RU.UTF-8',
360  'base' => '/ru/',
361  ],
362  ],
363  ]),
364  );
365 
366  $featuresMock = $this->‪createFeaturesMock();
367  $requestContextFactory = new ‪RequestContextFactory(new ‪BackendEntryPointResolver());
368  $subject = new ‪SiteResolver(new ‪SiteMatcher($featuresMock, ‪$siteFinder, $requestContextFactory));
369 
370  $request = new ‪ServerRequest($incomingUrl, 'GET');
371  $response = $subject->process($request, $this->siteFoundRequestHandler);
372 
373  if ($response instanceof ‪NullResponse) {
374  self::fail('No site configuration found in URL ' . $incomingUrl . '.');
375  } else {
376  $result = $response->getBody()->getContents();
377  $result = json_decode($result, true);
378  self::assertEquals($expectedSiteIdentifier, $result['site']);
379  self::assertEquals($expectedRootPageId, $result['rootpage']);
380  self::assertEquals($expectedLanguageId, $result['language-id']);
381  self::assertEquals($expectedBase, $result['language-base']);
382  }
383  }
384 
385  private function ‪createFeaturesMock(): MockObject&‪Features
386  {
387  $mock = $this->getMockBuilder(Features::class)
388  ->onlyMethods(['isFeatureEnabled'])
389  ->getMock();
390  $mock->expects(self::any())
391  ->method('isFeatureEnabled')
392  ->with('security.frontend.allowInsecureSiteResolutionByQueryParameters')
393  ->willReturn(false);
394  return $mock;
395  }
396 
397  private function ‪createSiteFinder(‪Site ...$sites): ‪SiteFinder
398  {
399  $siteConfiguration = new class ($sites) extends ‪SiteConfiguration {
400  public function __construct(
401  protected array $sites
402  ) {
403  // empty by default
404  }
405 
406  public function getAllExistingSites(bool $useCache = true): array
407  {
408  return array_combine(
409  array_map(static function (‪Site $site) { return $site->‪getIdentifier(); }, $this->sites),
410  $this->sites
411  );
412  }
413  };
414  return new ‪SiteFinder(new $siteConfiguration($sites));
415  }
416 }
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\detectSubSubsiteInsideNestedUrlStructureDataProvider
‪static detectSubSubsiteInsideNestedUrlStructureDataProvider()
Definition: SiteResolverTest.php:181
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\$siteFinder
‪SiteFinder &AccessibleObjectInterface $siteFinder
Definition: SiteResolverTest.php:45
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\detectProperLanguageByIncomingUrlDataProvider
‪static detectProperLanguageByIncomingUrlDataProvider()
Definition: SiteResolverTest.php:273
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\$siteFoundRequestHandler
‪RequestHandlerInterface $siteFoundRequestHandler
Definition: SiteResolverTest.php:47
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\$resetSingletonInstances
‪bool $resetSingletonInstances
Definition: SiteResolverTest.php:44
‪TYPO3\CMS\Core\Http\NullResponse
Definition: NullResponse.php:26
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest
Definition: SiteResolverTest.php:43
‪TYPO3\CMS\Frontend\Middleware\SiteResolver
Definition: SiteResolver.php:37
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\detectASingleSiteWhenProperRequestIsGiven
‪detectASingleSiteWhenProperRequestIsGiven()
Definition: SiteResolverTest.php:96
‪TYPO3\CMS\Core\Configuration\SiteConfiguration
Definition: SiteConfiguration.php:47
‪TYPO3\CMS\Core\Site\Entity\Site
Definition: Site.php:42
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\createSiteFinder
‪createSiteFinder(Site ... $sites)
Definition: SiteResolverTest.php:397
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware
Definition: PageArgumentValidatorTest.php:18
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\tearDown
‪tearDown()
Definition: SiteResolverTest.php:80
‪TYPO3\CMS\Core\Configuration\Features
Definition: Features.php:56
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Core\Http\ServerRequest
Definition: ServerRequest.php:39
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\detectProperLanguageByIncomingUrl
‪detectProperLanguageByIncomingUrl($incomingUrl, $expectedSiteIdentifier, $expectedRootPageId, $expectedLanguageId, $expectedBase)
Definition: SiteResolverTest.php:326
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\createFeaturesMock
‪createFeaturesMock()
Definition: SiteResolverTest.php:385
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\$originalLocale
‪string $originalLocale
Definition: SiteResolverTest.php:48
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\detectSubSubsiteInsideNestedUrlStructure
‪detectSubSubsiteInsideNestedUrlStructure($incomingUrl, $expectedSiteIdentifier, $expectedRootPageId, $expectedBase)
Definition: SiteResolverTest.php:220
‪TYPO3\CMS\Frontend\Tests\Unit\Middleware\SiteResolverTest\setUp
‪setUp()
Definition: SiteResolverTest.php:50
‪TYPO3\CMS\Core\Http\JsonResponse
Definition: JsonResponse.php:28
‪TYPO3\CMS\Webhooks\Message\$siteIdentifier
‪identifier readonly int readonly array readonly string readonly string $siteIdentifier
Definition: PageModificationMessage.php:38
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪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\Frontend\Tests\Unit\Middleware\SiteResolverTest\detectSubsiteInsideNestedUrlStructure
‪detectSubsiteInsideNestedUrlStructure()
Definition: SiteResolverTest.php:138
‪TYPO3\CMS\Core\Routing\RequestContextFactory
Definition: RequestContextFactory.php:30