‪TYPO3CMS  ‪main
PersistedPatternMapper.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 
24 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
32 
56 {
57  use ‪AspectTrait;
60  use UnresolvedValueTrait;
61 
62  protected const ‪PATTERN_RESULT = '#\{(?P<fieldName>[^}]+)\}#';
63 
67  protected ‪$settings;
68 
72  protected ‪$tableName;
73 
77  protected ‪$routeFieldPattern;
78 
83 
87  protected ‪$routeFieldResultNames;
88 
92  protected ‪$languageFieldName;
93 
98 
102  protected ‪$slugUniqueInSite;
103 
107  public function ‪__construct(array ‪$settings)
108  {
109  ‪$tableName = ‪$settings['tableName'] ?? null;
110  ‪$routeFieldPattern = ‪$settings['routeFieldPattern'] ?? null;
111  ‪$routeFieldResult = ‪$settings['routeFieldResult'] ?? null;
112 
113  if (!is_string(‪$tableName)) {
114  throw new \InvalidArgumentException('tableName must be string', 1537277173);
115  }
116  if (!is_string(‪$routeFieldPattern)) {
117  throw new \InvalidArgumentException('routeFieldPattern must be string', 1537277174);
118  }
119  if (!is_string(‪$routeFieldResult)) {
120  throw new \InvalidArgumentException('routeFieldResult must be string', 1537277175);
121  }
122  if (!preg_match_all(static::PATTERN_RESULT, ‪$routeFieldResult, ‪$routeFieldResultNames)) {
123  throw new \InvalidArgumentException(
124  'routeFieldResult must contain substitutable field names',
125  1537962752
126  );
127  }
128 
129  $this->settings = ‪$settings;
130  $this->tableName = ‪$tableName;
131  $this->routeFieldPattern = ‪$routeFieldPattern;
132  $this->routeFieldResult = ‪$routeFieldResult;
133  $this->routeFieldResultNames = ‪$routeFieldResultNames['fieldName'] ?? [];
134  $this->languageFieldName = ‪$GLOBALS['TCA'][‪$this->tableName]['ctrl']['languageField'] ?? null;
135  $this->languageParentFieldName = ‪$GLOBALS['TCA'][‪$this->tableName]['ctrl']['transOrigPointerField'] ?? null;
136  $this->slugUniqueInSite = $this->hasSlugUniqueInSite($this->tableName, ...$this->routeFieldResultNames);
137  }
138 
142  public function ‪generate(string $value): ?string
143  {
144  $result = $this->‪findByIdentifier($value);
145  $result = $this->‪resolveOverlay($result);
146  return $this->‪createRouteResult($result);
147  }
148 
152  public function ‪resolve(string $value): ?string
153  {
154  if (!preg_match('#' . $this->routeFieldPattern . '#', $value, $matches)) {
155  return null;
156  }
157  $values = $this->‪filterNamesKeys($matches);
158  $result = $this->‪findByRouteFieldValues($values);
159  if (($result[$this->languageParentFieldName] ?? null) > 0) {
160  return (string)$result[‪$this->languageParentFieldName];
161  }
162  if (isset($result['uid'])) {
163  return (string)$result['uid'];
164  }
165  return null;
166  }
167 
171  protected function ‪createRouteResult(?array $result): ?string
172  {
173  if ($result === null) {
174  return $result;
175  }
176  $substitutes = [];
177  foreach ($this->routeFieldResultNames as $fieldName) {
178  if (!isset($result[$fieldName])) {
179  return null;
180  }
181  $routeFieldName = '{' . $fieldName . '}';
182  $substitutes[$routeFieldName] = $result[$fieldName];
183  }
184  return str_replace(
185  array_keys($substitutes),
186  array_values($substitutes),
187  $this->routeFieldResult
188  );
189  }
190 
191  protected function ‪filterNamesKeys(array $array): array
192  {
193  return array_filter(
194  $array,
195  static function ($key) {
196  return !is_numeric($key);
197  },
198  ARRAY_FILTER_USE_KEY
199  );
200  }
201 
202  protected function ‪findByIdentifier(string $value): ?array
203  {
205  return null;
206  }
207 
208  $queryBuilder = $this->‪createQueryBuilder();
209  $result = $queryBuilder
210  ->select('*')
211  ->where($queryBuilder->expr()->eq(
212  'uid',
213  $queryBuilder->createNamedParameter($value, ‪Connection::PARAM_INT)
214  ))
215  ->executeQuery()
216  ->fetchAssociative();
217  return $result !== false ? $result : null;
218  }
219 
220  protected function ‪findByRouteFieldValues(array $values): ?array
221  {
222  $languageAware = $this->languageFieldName !== null && $this->languageParentFieldName !== null;
223 
224  $queryBuilder = $this->‪createQueryBuilder();
225  $results = $queryBuilder
226  ->select('*')
227  ->where(...$this->‪createRouteFieldConstraints($queryBuilder, $values))
228  ->executeQuery()
229  ->fetchAllAssociative();
230  // limit results to be contained in rootPageId of current Site
231  // (which is defining the route configuration currently being processed)
232  if ($this->slugUniqueInSite) {
233  $results = array_values($this->filterContainedInSite($results));
234  }
235  // return first result record in case table is not language aware
236  if (!$languageAware) {
237  return $results[0] ?? null;
238  }
239  // post-process language fallbacks
240  $languageIds = $this->resolveAllRelevantLanguageIds();
241  return $this->resolveLanguageFallback($results, $this->languageFieldName, $languageIds);
242  }
243 
244  protected function ‪createQueryBuilder(): QueryBuilder
245  {
246  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
247  ->getQueryBuilderForTable($this->tableName)
248  ->from($this->tableName);
249  $queryBuilder->setRestrictions(
250  GeneralUtility::makeInstance(FrontendRestrictionContainer::class, GeneralUtility::makeInstance(Context::class))
251  );
252  // Frontend Groups are not available at this time
253  // So this must be excluded to allow access restricted records
254  $queryBuilder->getRestrictions()->removeByType(FrontendGroupRestriction::class);
255  return $queryBuilder;
256  }
257 
258  protected function ‪createRouteFieldConstraints(QueryBuilder $queryBuilder, array $values): array
259  {
260  $languageAware = $this->languageFieldName !== null && $this->languageParentFieldName !== null;
261  $languageExpansion = $languageAware && isset($values['uid']);
262 
263  $constraints = [];
264  foreach ($values as $fieldName => $fieldValue) {
265  if ($languageExpansion && $fieldName === 'uid') {
266  continue;
267  }
268  $constraints[] = $queryBuilder->expr()->eq(
269  $fieldName,
270  $queryBuilder->createNamedParameter(
271  $fieldValue
272  )
273  );
274  }
275  // either match uid or language parent field value (for any language)
276  if ($languageExpansion) {
277  $idParameter = $queryBuilder->createNamedParameter(
278  $values['uid'],
280  );
281  $constraints[] = $queryBuilder->expr()->or(
282  $queryBuilder->expr()->eq('uid', $idParameter),
283  $queryBuilder->expr()->eq($this->languageParentFieldName, $idParameter)
284  );
285  // otherwise - basically uid is not in pattern - restrict to languages and apply fallbacks
286  } elseif ($languageAware) {
287  $languageIds = $this->resolveAllRelevantLanguageIds();
288  $constraints[] = $queryBuilder->expr()->in(
289  $this->languageFieldName,
290  $queryBuilder->createNamedParameter($languageIds, ‪Connection::PARAM_INT_ARRAY)
291  );
292  }
293 
294  return $constraints;
295  }
296 
297  protected function ‪resolveOverlay(?array ‪$record): ?array
298  {
299  $languageId = $this->siteLanguage->getLanguageId();
300  if (‪$record === null || $languageId === 0) {
301  return ‪$record;
302  }
303 
304  $pageRepository = $this->‪createPageRepository();
305  return $pageRepository->getLanguageOverlay($this->tableName, ‪$record) ?: null;
306  }
307 
308  protected function ‪createPageRepository(): ‪PageRepository
309  {
310  $context = clone GeneralUtility::makeInstance(Context::class);
311  $context->setAspect(
312  'language',
314  );
315  return GeneralUtility::makeInstance(
316  PageRepository::class,
317  $context
318  );
319  }
320 }
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\resolve
‪resolve(string $value)
Definition: PersistedPatternMapper.php:144
‪TYPO3\CMS\Core\Routing\Aspect\UnresolvedValueInterface
Definition: UnresolvedValueInterface.php:24
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$languageFieldName
‪string null $languageFieldName
Definition: PersistedPatternMapper.php:86
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Core\Context\LanguageAspectFactory
Definition: LanguageAspectFactory.php:27
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper
Definition: PersistedPatternMapper.php:56
‪TYPO3\CMS\Core\Routing\Aspect\SiteAccessorTrait
Definition: SiteAccessorTrait.php:31
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\PATTERN_RESULT
‪const PATTERN_RESULT
Definition: PersistedPatternMapper.php:62
‪TYPO3\CMS\Core\Site\SiteLanguageAwareInterface
Definition: SiteLanguageAwareInterface.php:26
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$routeFieldPattern
‪string $routeFieldPattern
Definition: PersistedPatternMapper.php:74
‪TYPO3\CMS\Core\Context\LanguageAspectFactory\createFromSiteLanguage
‪static createFromSiteLanguage(SiteLanguage $language)
Definition: LanguageAspectFactory.php:31
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\__construct
‪__construct(array $settings)
Definition: PersistedPatternMapper.php:99
‪TYPO3\CMS\Core\Database\Query\Restriction\FrontendGroupRestriction
Definition: FrontendGroupRestriction.php:30
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:54
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$routeFieldResult
‪string $routeFieldResult
Definition: PersistedPatternMapper.php:78
‪TYPO3\CMS\Core\Site\SiteAwareInterface
Definition: SiteAwareInterface.php:26
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\createRouteFieldConstraints
‪createRouteFieldConstraints(QueryBuilder $queryBuilder, array $values)
Definition: PersistedPatternMapper.php:250
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Core\Routing\Aspect\SiteLanguageAccessorTrait
Definition: SiteLanguageAccessorTrait.php:26
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$tableName
‪string $tableName
Definition: PersistedPatternMapper.php:70
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\filterNamesKeys
‪filterNamesKeys(array $array)
Definition: PersistedPatternMapper.php:183
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\findByRouteFieldValues
‪findByRouteFieldValues(array $values)
Definition: PersistedPatternMapper.php:212
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\createQueryBuilder
‪createQueryBuilder()
Definition: PersistedPatternMapper.php:236
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$languageParentFieldName
‪string null $languageParentFieldName
Definition: PersistedPatternMapper.php:90
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\resolveOverlay
‪resolveOverlay(?array $record)
Definition: PersistedPatternMapper.php:289
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$settings
‪array $settings
Definition: PersistedPatternMapper.php:66
‪TYPO3\CMS\Core\Routing\Aspect
Definition: AspectFactory.php:18
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Routing\Aspect\AspectTrait
Definition: AspectTrait.php:23
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\findByIdentifier
‪findByIdentifier(string $value)
Definition: PersistedPatternMapper.php:194
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$slugUniqueInSite
‪bool $slugUniqueInSite
Definition: PersistedPatternMapper.php:94
‪TYPO3\CMS\Core\Routing\Aspect\StaticMappableAspectInterface
Definition: StaticMappableAspectInterface.php:23
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Core\Domain\Repository\PageRepository
Definition: PageRepository.php:69
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$routeFieldResultNames
‪string[] $routeFieldResultNames
Definition: PersistedPatternMapper.php:82
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\createRouteResult
‪createRouteResult(?array $result)
Definition: PersistedPatternMapper.php:163
‪TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer
Definition: FrontendRestrictionContainer.php:30
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\generate
‪generate(string $value)
Definition: PersistedPatternMapper.php:134
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT_ARRAY
‪const PARAM_INT_ARRAY
Definition: Connection.php:72
‪TYPO3\CMS\Core\Routing\Aspect\PersistedMappableAspectInterface
Definition: PersistedMappableAspectInterface.php:24
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\createPageRepository
‪createPageRepository()
Definition: PersistedPatternMapper.php:300