‪TYPO3CMS  ‪main
PersistedPatternMapperTest.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 Psr\EventDispatcher\EventDispatcherInterface;
30 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
31 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
32 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
33 
34 final class ‪PersistedPatternMapperTest extends FunctionalTestCase
35 {
36  private const ‪ASPECT_CONFIGURATION = [
37  'tableName' => 'tt_content',
38  'routeFieldName' => 'header',
39  // `layout` does not really much sense here since it's always `0`
40  // just to have an additional field for the pattern mapper
41  'routeFieldPattern' => '^(?P<header>.+)-(?P<layout>\d+)$',
42  'routeFieldResult' => '{header}-{layout}',
43  ];
44 
45  private const ‪SLUG_CONFIGURATION = [
46  'type' => 'slug',
47  'generatorOptions' => [
48  'prefixParentPageSlug' => false,
49  ],
50  'fallbackCharacter' => '-',
51  'required' => true,
52  'eval' => 'uniqueInSite',
53  'default' => '',
54  ];
55 
56  private const ‪LANGUAGE_MAP = [
57  'es-es' => 3,
58  'fr-ca' => 2,
59  'fr-fr' => 1,
60  'default' => 0,
61  ];
62 
63  private const ‪SITE_ADDITION = [
64  'acme' => 0,
65  'other' => 4000,
66  ];
67 
71  private ‪$subject;
72 
76  private ‪$sites;
77 
78  protected function ‪setUp(): void
79  {
80  parent::setUp();
81  $this->withDatabaseSnapshot(function () {
82  $this->importCSVDataSet(__DIR__ . '/../../Fixtures/be_users.csv');
83  $backendUser = $this->setUpBackendUser(1);
85  $scenarioFile = __DIR__ . '/Fixtures/AspectScenario.yaml';
86  $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
87  $writer = DataHandlerWriter::withBackendUser($backendUser);
88  $writer->invokeFactory($factory);
89  if (!empty($writer->getErrors())) {
90  self::fail(var_export($writer->getErrors(), true));
91  }
92  });
93 
94  // declare tt_content.header as `slug` field having `uniqueInSite` set
95  $tableName = self::ASPECT_CONFIGURATION['tableName'];
96  $fieldName = self::ASPECT_CONFIGURATION['routeFieldName'];
97  ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'] = ‪self::SLUG_CONFIGURATION;
98 
99  ‪$languages = [
100  [
101  'languageId' => 3,
102  'base' => '/es-es/',
103  'locale' => 'es_ES.UTF-8',
104  'fallbackType' => 'fallback',
105  'fallbacks' => [0],
106  'title' => 'Spanish',
107  ],
108  [
109  'languageId' => 2,
110  'base' => '/fr-ca/',
111  'locale' => 'fr_CA.UTF-8',
112  'fallbackType' => 'fallback',
113  'fallbacks' => [1, 0],
114  'title' => 'Franco-Canadian',
115  ],
116  [
117  'languageId' => 1,
118  'base' => '/fr-fr/',
119  'locale' => 'fr_FR.UTF-8',
120  'fallbackType' => 'fallback',
121  'fallbacks' => [0],
122  'French',
123  ],
124  [
125  'languageId' => 0,
126  'base' => 'en_US.UTF-8',
127  'locale' => '/en-us/',
128  ],
129  ];
130  $this->sites = [
131  'acme' => new ‪Site('acme-inc', 1000, [
132  'identifier' => 'acme-inc',
133  'rootPageId' => 1000,
134  'base' => 'https://acme.com/',
135  'languages' => ‪$languages,
136  ]),
137  'other' => new ‪Site('other-inc', 5000, [
138  'identifier' => 'other-inc',
139  'rootPageId' => 5000,
140  'base' => 'https://other.com/',
141  'languages' => ‪$languages,
142  ]),
143  ];
144  $this->‪writeSiteConfiguration($this->sites['acme']);
145  $this->‪writeSiteConfiguration($this->sites['other']);
146  $this->subject = new ‪PersistedPatternMapper(self::ASPECT_CONFIGURATION);
147  $this->subject->setSiteLanguage($this->sites['acme']->getLanguageById(0));
148  $this->subject->setSite($this->sites['acme']);
149  }
150 
151  protected function ‪tearDown(): void
152  {
153  unset($this->subject, $this->sites);
154  parent::tearDown();
155  }
156 
157  public static function ‪languageAwareRecordsAreResolvedDataProvider(): array
158  {
159  $baseDataSet = [
160  'non-existing, default language' => ['this-value-does-not-exist', 'default', null],
161 
162  '30xx-slug, default language' => ['30xx-slug-0', 'default', '3010'],
163  '30xx-slug, fr-fr language' => ['30xx-slug-0', 'fr-fr', '3010'],
164  '30xx-slug, fr-ca language' => ['30xx-slug-0', 'fr-ca', '3010'],
165 
166  '30xx-slug-fr-ca, fr-ca language' => ['30xx-slug-fr-ca-0', 'fr-ca', '3010'],
167  // '30xx-slug-fr-ca-0' available in default language as well, fallbacks to that one
168  '30xx-slug-fr-ca, fr-fr language' => ['30xx-slug-fr-ca-0', 'fr-fr', '3030'],
169  // '30xx-slug-fr-ca-0' available in default language, use it directly
170  '30xx-slug-fr-ca, default language' => ['30xx-slug-fr-ca-0', 'default', '3030'],
171 
172  '30xx-slug-fr, fr-ca language' => ['30xx-slug-fr-0', 'fr-ca', '3010'],
173  '30xx-slug-fr, fr-fr language' => ['30xx-slug-fr-0', 'fr-fr', '3010'],
174  // '30xx-slug-fr-0' available in default language, use it directly
175  '30xx-slug-fr, default language' => ['30xx-slug-fr-0', 'default', '3020'],
176 
177  // basically the same, but being stored in reverse order in database
178  '40xx-slug, default language' => ['40xx-slug-0', 'default', '4040'],
179  '40xx-slug, fr-fr language' => ['40xx-slug-0', 'fr-fr', '4040'],
180  '40xx-slug, fr-ca language' => ['40xx-slug-0', 'fr-ca', '4040'],
181 
182  '40xx-slug-fr-ca, fr-ca language' => ['40xx-slug-fr-ca-0', 'fr-ca', '4040'],
183  // '40xx-slug-fr-ca-0' available in default language as well, fallbacks to that one
184  '40xx-slug-fr-ca, fr-fr language' => ['40xx-slug-fr-ca-0', 'fr-fr', '4030'],
185  // '40xx-slug-fr-ca-0' available in default language, use it directly
186  '40xx-slug-fr-ca, default language' => ['40xx-slug-fr-ca-0', 'default', '4030'],
187 
188  '40xx-slug-fr, fr-ca language' => ['40xx-slug-fr-0', 'fr-ca', '4040'],
189  '40xx-slug-fr, fr-fr language' => ['40xx-slug-fr-0', 'fr-fr', '4040'],
190  // '40xx-slug-fr-ca-0' available in default language, use it directly
191  '40xx-slug-fr, default language' => ['40xx-slug-fr-0', 'default', '4020'],
192  ];
193  // permute $baseDataSet to be either prepended
194  // with site identifier argument 'acme' or 'other'
195  $dataSet = [];
196  foreach (['acme', 'other'] as $site) {
197  foreach ($baseDataSet as $key => $arguments) {
198  array_unshift($arguments, $site);
199  $dataSet[$site . ':' . $key] = $arguments;
200  }
201  }
202  return $dataSet;
203  }
204 
209  public function ‪languageAwareRecordsAreResolved(string ‪$identifier, string $requestValue, string $language, ?string $expectation): void
210  {
211  $this->subject->setSiteLanguage(
212  $this->sites[‪$identifier]->getLanguageById(self::LANGUAGE_MAP[$language])
213  );
214  $this->subject->setSite(
215  $this->sites[‪$identifier]
216  );
217  if ($expectation !== null) {
218  $expectation = (string)((int)$expectation + self::SITE_ADDITION[‪$identifier]);
219  }
220  self::assertSame($expectation, $this->subject->resolve($requestValue));
221  }
222 
223  public static function ‪recordVisibilityDataProvider(): array
224  {
225  $rawContext = new ‪Context();
226  $visibleContext = new ‪Context();
227  $visibleContext->setAspect(
228  'visibility',
229  new ‪VisibilityAspect(false, true, false)
230  );
231  $frontendGroupsContext = new ‪Context();
232  $frontendGroupsContext->setAspect(
233  'frontend.user',
234  new ‪UserAspect(null, [13])
235  );
236  $scheduledContext = new ‪Context();
237  $scheduledContext->setAspect(
238  'date',
239  new ‪DateTimeAspect(new \DateTimeImmutable('@20000'))
240  );
241 
242  return [
243  'hidden-visibility-slug, raw context' => [
244  $rawContext,
245  ['slug' => 'hidden-visibility-slug-0', 'uid' => '4051'],
246  false,
247  ],
248  // fe_group slugs are always considered
249  'restricted-visibility-slug, raw context' => [
250  $rawContext,
251  ['slug' => 'restricted-visibility-slug-0', 'uid' => '4052'],
252  true,
253  ],
254  'scheduled-visibility-slug, raw context' => [
255  $rawContext,
256  ['slug' => 'scheduled-visibility-slug-0', 'uid' => '4053'],
257  false,
258  ],
259  'hidden-visibility-slug, visibility context (include hidden content)' => [
260  $visibleContext,
261  ['slug' => 'hidden-visibility-slug-0', 'uid' => '4051'],
262  true,
263  ],
264  // fe_group slugs are always considered
265  'restricted-visibility-slug, frontend-groups context (13)' => [
266  $frontendGroupsContext,
267  ['slug' => 'restricted-visibility-slug-0', 'uid' => '4052'],
268  true,
269  ],
270  'scheduled-visibility-slug, scheduled context (timestamp 20000)' => [
271  $scheduledContext,
272  ['slug' => 'scheduled-visibility-slug-0', 'uid' => '4053'],
273  false, // @todo actually `true`, Start-/EndTimeRestriction do not support Context, yet
274  ],
275  ];
276  }
277 
282  public function ‪recordVisibilityIsConsideredForResolving(Context $context, array $parameters, bool $expectation): void
283  {
284  $this->subject->setContext($context);
285  $expectedResult = $expectation ? $parameters['uid'] : null;
286  self::assertSame($expectedResult, $this->subject->resolve($parameters['slug']));
287  }
288 
293  public function ‪recordVisibilityIsConsideredForGeneration(Context $context, array $parameters, bool $expectation): void
294  {
295  $this->subject->setContext($context);
296  $expectedResult = $expectation ? $parameters['slug'] : null;
297  self::assertSame($expectedResult, $this->subject->generate($parameters['uid']));
298  }
299 
304  {
305  $result = $this->subject->generate('3010');
306 
307  self::assertSame('30xx-slug-0', $result);
308  }
309 
314  {
315  $result = $this->subject->generate('3010-i-am-garbage');
316 
317  self::assertNull($result);
318  }
319 
320  private function ‪writeSiteConfiguration(‪Site $site): void
321  {
322  try {
323  // ensure no previous site configuration influences the test
324  $path = $this->instancePath . '/typo3conf/sites';
325  $cache = $this->get('cache.core');
326  $eventDispatcher = $this->get(EventDispatcherInterface::class);
327  ‪GeneralUtility::rmdir($path . '/' . $site->‪getIdentifier(), true);
328  GeneralUtility::makeInstance(SiteConfiguration::class, $path, $eventDispatcher, $cache)->write($site->‪getIdentifier(), $site->‪getConfiguration());
329  } catch (\‪Exception $exception) {
330  self::markTestSkipped($exception->getMessage());
331  }
332  }
333 }
‪TYPO3\CMS\Core\Context\VisibilityAspect
Definition: VisibilityAspect.php:31
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest
Definition: PersistedPatternMapperTest.php:35
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\recordVisibilityIsConsideredForGeneration
‪recordVisibilityIsConsideredForGeneration(Context $context, array $parameters, bool $expectation)
Definition: PersistedPatternMapperTest.php:291
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\$subject
‪PersistedPatternMapper $subject
Definition: PersistedPatternMapperTest.php:70
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\writeSiteConfiguration
‪writeSiteConfiguration(Site $site)
Definition: PersistedPatternMapperTest.php:318
‪TYPO3\CMS\Core\Exception
Definition: Exception.php:21
‪$languages
‪$languages
Definition: updateIsoDatabase.php:104
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper
Definition: PersistedPatternMapper.php:58
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\generateWithUidOfExistingPageSuffixedWithGarbageStringReturnsNull
‪generateWithUidOfExistingPageSuffixedWithGarbageStringReturnsNull()
Definition: PersistedPatternMapperTest.php:311
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect
Definition: PersistedAliasMapperTest.php:18
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:54
‪TYPO3\CMS\Core\Configuration\SiteConfiguration
Definition: SiteConfiguration.php:47
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\$sites
‪Site[] $sites
Definition: PersistedPatternMapperTest.php:74
‪TYPO3\CMS\Core\Site\Entity\Site
Definition: Site.php:42
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\languageAwareRecordsAreResolvedDataProvider
‪static languageAwareRecordsAreResolvedDataProvider()
Definition: PersistedPatternMapperTest.php:155
‪TYPO3\CMS\Core\Core\Bootstrap\initializeLanguageObject
‪static initializeLanguageObject()
Definition: Bootstrap.php:539
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\ASPECT_CONFIGURATION
‪const ASPECT_CONFIGURATION
Definition: PersistedPatternMapperTest.php:36
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\recordVisibilityDataProvider
‪static recordVisibilityDataProvider()
Definition: PersistedPatternMapperTest.php:221
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\recordVisibilityIsConsideredForResolving
‪recordVisibilityIsConsideredForResolving(Context $context, array $parameters, bool $expectation)
Definition: PersistedPatternMapperTest.php:280
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\setUp
‪setUp()
Definition: PersistedPatternMapperTest.php:76
‪TYPO3\CMS\Core\Utility\GeneralUtility\rmdir
‪static bool rmdir(string $path, bool $removeNonEmpty=false)
Definition: GeneralUtility.php:1679
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\LANGUAGE_MAP
‪const LANGUAGE_MAP
Definition: PersistedPatternMapperTest.php:56
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\languageAwareRecordsAreResolved
‪languageAwareRecordsAreResolved(string $identifier, string $requestValue, string $language, ?string $expectation)
Definition: PersistedPatternMapperTest.php:207
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\SITE_ADDITION
‪const SITE_ADDITION
Definition: PersistedPatternMapperTest.php:63
‪TYPO3\CMS\Core\Site\Entity\Site\getConfiguration
‪getConfiguration()
Definition: Site.php:294
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\generateWithUidOfExistingPageReturnsPageSlug
‪generateWithUidOfExistingPageReturnsPageSlug()
Definition: PersistedPatternMapperTest.php:301
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\SLUG_CONFIGURATION
‪const SLUG_CONFIGURATION
Definition: PersistedPatternMapperTest.php:45
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Core\Bootstrap
Definition: Bootstrap.php:64
‪TYPO3\CMS\Core\Tests\Functional\Routing\Aspect\PersistedPatternMapperTest\tearDown
‪tearDown()
Definition: PersistedPatternMapperTest.php:149
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Core\Context\DateTimeAspect
Definition: DateTimeAspect.php:35
‪TYPO3\CMS\Core\Site\Entity\Site\getIdentifier
‪getIdentifier()
Definition: Site.php:173
‪TYPO3\CMS\Core\Context\UserAspect
Definition: UserAspect.php:37
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37