[TASK] Move domainStartPage setting to PageResolver
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Unit / Middleware / SiteResolverTest.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Frontend\Tests\Unit\Middleware;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use Psr\Http\Message\ResponseInterface;
20 use Psr\Http\Message\ServerRequestInterface;
21 use Psr\Http\Server\RequestHandlerInterface;
22 use TYPO3\CMS\Core\Cache\CacheManager;
23 use TYPO3\CMS\Core\Http\JsonResponse;
24 use TYPO3\CMS\Core\Http\NullResponse;
25 use TYPO3\CMS\Core\Http\ServerRequest;
26 use TYPO3\CMS\Core\Routing\SiteMatcher;
27 use TYPO3\CMS\Core\Site\Entity\Site;
28 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
29 use TYPO3\CMS\Core\Site\SiteFinder;
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31 use TYPO3\CMS\Frontend\Middleware\SiteResolver;
32 use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Fixtures\PhpError;
33 use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
34 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
35
36 class SiteResolverTest extends UnitTestCase
37 {
38 /**
39 * @var bool Reset singletons created by subject
40 */
41 protected $resetSingletonInstances = true;
42
43 /**
44 * @var SiteFinder|AccessibleObjectInterface
45 */
46 protected $siteFinder;
47
48 protected $siteFoundRequestHandler;
49
50 /**
51 * Set up
52 */
53 protected function setUp(): void
54 {
55 $this->siteFinder = $this->getAccessibleMock(SiteFinder::class, ['dummy'], [], '', false);
56
57 // A request handler which expects a site to be found.
58 $this->siteFoundRequestHandler = new class implements RequestHandlerInterface {
59 public function handle(ServerRequestInterface $request): ResponseInterface
60 {
61 /** @var Site $site */
62 /** @var SiteLanguage $language */
63 $site = $request->getAttribute('site', false);
64 $language = $request->getAttribute('language', false);
65 if ($site && $language) {
66 return new JsonResponse(
67 [
68 'site' => $site->getIdentifier(),
69 'language-id' => $language->getLanguageId(),
70 'language-base' => $language->getBase(),
71 'rootpage' => $site->getRootPageId()
72 ]
73 );
74 }
75 return new NullResponse();
76 }
77 };
78
79 $cacheManagerProphecy = $this->prophesize(CacheManager::class);
80 GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal());
81 }
82
83 /**
84 * Expect a URL handed in, as a request. This URL does not have a GET parameter "id"
85 * Then the site handling gets triggered, and the URL is taken to resolve a site.
86 *
87 * This case tests against a site with no domain or scheme, and successfully finds it.
88 *
89 * @test
90 */
91 public function detectASingleSiteWhenProperRequestIsGiven()
92 {
93 $incomingUrl = 'https://a-random-domain.com/mysite/';
94 $siteIdentifier = 'full-site';
95 $this->siteFinder->_set('sites', [
96 $siteIdentifier => new Site($siteIdentifier, 13, [
97 'base' => '/mysite/',
98 'languages' => [
99 0 => [
100 'languageId' => 0,
101 'locale' => 'fr_FR.UTF-8',
102 'base' => '/'
103 ]
104 ]
105 ])
106 ]);
107
108 $request = new ServerRequest($incomingUrl, 'GET');
109 $subject = new SiteResolver(new SiteMatcher($this->siteFinder));
110 $response = $subject->process($request, $this->siteFoundRequestHandler);
111 if ($response instanceof NullResponse) {
112 $this->fail('No site configuration found in URL ' . $incomingUrl . '.');
113 } else {
114 $result = $response->getBody()->getContents();
115 $result = json_decode($result, true);
116 $this->assertEquals($siteIdentifier, $result['site']);
117 $this->assertEquals(0, $result['language-id']);
118 $this->assertEquals('/mysite/', $result['language-base']);
119 }
120 }
121
122 /**
123 * Scenario with two sites
124 * Site 1: /
125 * Site 2: /mysubsite/
126 *
127 * The result should be that site 2 is resolved by the router when calling
128 *
129 * www.random-result.com/mysubsite/you-know-why/
130 *
131 * @test
132 */
133 public function detectSubsiteInsideNestedUrlStructure()
134 {
135 $incomingUrl = 'https://www.random-result.com/mysubsite/you-know-why/';
136 $this->siteFinder->_set('sites', [
137 'outside-site' => new Site('outside-site', 13, [
138 'base' => '/',
139 'languages' => [
140 0 => [
141 'languageId' => 0,
142 'locale' => 'fr_FR.UTF-8',
143 'base' => '/'
144 ]
145 ]
146 ]),
147 'sub-site' => new Site('sub-site', 15, [
148 'base' => '/mysubsite/',
149 'languages' => [
150 0 => [
151 'languageId' => 0,
152 'locale' => 'fr_FR.UTF-8',
153 'base' => '/'
154 ]
155 ]
156 ]),
157 ]);
158
159 $request = new ServerRequest($incomingUrl, 'GET');
160 $subject = new SiteResolver(new SiteMatcher($this->siteFinder));
161 $response = $subject->process($request, $this->siteFoundRequestHandler);
162 if ($response instanceof NullResponse) {
163 $this->fail('No site configuration found in URL ' . $incomingUrl . '.');
164 } else {
165 $result = $response->getBody()->getContents();
166 $result = json_decode($result, true);
167 $this->assertEquals('sub-site', $result['site']);
168 $this->assertEquals(0, $result['language-id']);
169 $this->assertEquals('/mysubsite/', $result['language-base']);
170 }
171 }
172
173 public function detectSubSubsiteInsideNestedUrlStructureDataProvider()
174 {
175 return [
176 'matches second site' => [
177 'https://www.random-result.com/mysubsite/you-know-why/',
178 'sub-site',
179 14,
180 '/mysubsite/'
181 ],
182 'matches third site' => [
183 'https://www.random-result.com/mysubsite/micro-site/oh-yes-you-do/',
184 'subsub-site',
185 15,
186 '/mysubsite/micro-site/'
187 ],
188 'matches a subsite in first site' => [
189 'https://www.random-result.com/products/pampers/',
190 'outside-site',
191 13,
192 '/'
193 ],
194 ];
195 }
196
197 /**
198 * Scenario with three sites
199 * Site 1: /
200 * Site 2: /mysubsite/
201 * Site 3: /mysubsite/micro-site/
202 *
203 * The result should be that site 2 is resolved by the router when calling
204 *
205 * www.random-result.com/mysubsite/you-know-why/
206 *
207 * and site 3 when calling
208 * www.random-result.com/mysubsite/micro-site/oh-yes-you-do/
209 *
210 * @test
211 * @dataProvider detectSubSubsiteInsideNestedUrlStructureDataProvider
212 */
213 public function detectSubSubsiteInsideNestedUrlStructure($incomingUrl, $expectedSiteIdentifier, $expectedRootPageId, $expectedBase)
214 {
215 $this->siteFinder->_set('sites', [
216 'outside-site' => new Site('outside-site', 13, [
217 'base' => '/',
218 'languages' => [
219 0 => [
220 'languageId' => 0,
221 'locale' => 'fr_FR.UTF-8',
222 'base' => '/'
223 ]
224 ]
225 ]),
226 'sub-site' => new Site('sub-site', 14, [
227 'base' => '/mysubsite/',
228 'languages' => [
229 0 => [
230 'languageId' => 0,
231 'locale' => 'fr_FR.UTF-8',
232 'base' => '/'
233 ]
234 ]
235 ]),
236 'subsub-site' => new Site('subsub-site', 15, [
237 'base' => '/mysubsite/micro-site/',
238 'languages' => [
239 0 => [
240 'languageId' => 0,
241 'locale' => 'fr_FR.UTF-8',
242 'base' => '/'
243 ]
244 ]
245 ]),
246 ]);
247
248 $request = new ServerRequest($incomingUrl, 'GET');
249 $subject = new SiteResolver(new SiteMatcher($this->siteFinder));
250 $response = $subject->process($request, $this->siteFoundRequestHandler);
251 if ($response instanceof NullResponse) {
252 $this->fail('No site configuration found in URL ' . $incomingUrl . '.');
253 } else {
254 $result = $response->getBody()->getContents();
255 $result = json_decode($result, true);
256 $this->assertEquals($expectedSiteIdentifier, $result['site']);
257 $this->assertEquals($expectedRootPageId, $result['rootpage']);
258 $this->assertEquals($expectedBase, $result['language-base']);
259 }
260 }
261
262 public function detectProperLanguageByIncomingUrlDataProvider()
263 {
264 return [
265 'matches second site' => [
266 'https://www.random-result.com/mysubsite/you-know-why/',
267 'sub-site',
268 14,
269 2,
270 '/mysubsite/'
271 ],
272 'matches second site in other language' => [
273 'https://www.random-result.com/mysubsite/it/you-know-why/',
274 'sub-site',
275 14,
276 2,
277 '/mysubsite/'
278 ],
279 'matches second site because third site language prefix did not match' => [
280 'https://www.random-result.com/mysubsite/micro-site/oh-yes-you-do/',
281 'sub-site',
282 14,
283 2,
284 '/mysubsite/'
285 ],
286 'matches third site' => [
287 'https://www.random-result.com/mysubsite/micro-site/ru/oh-yes-you-do/',
288 'subsub-site',
289 15,
290 13,
291 '/mysubsite/micro-site/ru/'
292 ],
293 /**
294 * This case does not work, as no language prefix is defined.
295 'matches a subsite in first site' => [
296 'https://www.random-result.com/products/pampers/',
297 'outside-site',
298 13,
299 0,
300 '/'
301 ],
302 */
303 'matches a subsite with translation in first site' => [
304 'https://www.random-result.com/fr/products/pampers/',
305 'outside-site',
306 13,
307 1,
308 '/fr/'
309 ],
310 ];
311 }
312
313 /**
314 * Scenario with three one site and three languages
315 * Site 1: /
316 * Language 0: /en/
317 * Language 1: /fr/
318 * Site 2: /mysubsite/
319 * Language: 2: /
320 * Site 3: /mysubsite/micro-site/
321 * Language: 13: /ru/
322 *
323 * @test
324 * @dataProvider detectProperLanguageByIncomingUrlDataProvider
325 */
326 public function detectProperLanguageByIncomingUrl($incomingUrl, $expectedSiteIdentifier, $expectedRootPageId, $expectedLanguageId, $expectedBase)
327 {
328 $this->siteFinder->_set('sites', [
329 'outside-site' => 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 'sub-site' => 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 'subsub-site' => 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 $request = new ServerRequest($incomingUrl, 'GET');
367 $subject = new SiteResolver(new SiteMatcher($this->siteFinder));
368 $response = $subject->process($request, $this->siteFoundRequestHandler);
369 if ($response instanceof NullResponse) {
370 $this->fail('No site configuration found in URL ' . $incomingUrl . '.');
371 } else {
372 $result = $response->getBody()->getContents();
373 $result = json_decode($result, true);
374 $this->assertEquals($expectedSiteIdentifier, $result['site']);
375 $this->assertEquals($expectedRootPageId, $result['rootpage']);
376 $this->assertEquals($expectedLanguageId, $result['language-id']);
377 $this->assertEquals($expectedBase, $result['language-base']);
378 }
379 }
380
381 /**
382 * @test
383 */
384 public function checkIf404IsSiteLanguageIsDisabledInFrontend()
385 {
386 $this->siteFinder->_set('sites', [
387 'mixed-site' => new Site('mixed-site', 13, [
388 'base' => '/',
389 'errorHandling' => [
390 [
391 'errorCode' => 404,
392 'errorHandler' => 'PHP',
393 'errorPhpClassFQCN' => PhpError::class
394 ]
395 ],
396 'languages' => [
397 0 => [
398 'languageId' => 0,
399 'locale' => 'en_US.UTF-8',
400 'base' => '/en/',
401 'enabled' => false
402 ],
403 1 => [
404 'languageId' => 1,
405 'locale' => 'fr_CA.UTF-8',
406 'base' => '/fr/',
407 'enabled' => true
408 ]
409 ]
410 ]),
411 ]);
412
413 // Reqest to default page
414 $request = new ServerRequest('https://twenty.one/en/pilots/', 'GET');
415 $subject = new SiteResolver(new SiteMatcher($this->siteFinder));
416 $response = $subject->process($request, $this->siteFoundRequestHandler);
417 $this->assertEquals(404, $response->getStatusCode());
418
419 $request = new ServerRequest('https://twenty.one/fr/pilots/', 'GET');
420 $subject = new SiteResolver(new SiteMatcher($this->siteFinder));
421 $response = $subject->process($request, $this->siteFoundRequestHandler);
422 $this->assertEquals(200, $response->getStatusCode());
423 }
424 }