‪TYPO3CMS  10.4
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 
20 use Doctrine\DBAL\Connection;
34 
58 {
59  use ‪AspectTrait;
64 
65  protected const ‪PATTERN_RESULT = '#\{(?P<fieldName>[^}]+)\}#';
66 
70  protected ‪$settings;
71 
75  protected ‪$tableName;
76 
80  protected ‪$routeFieldPattern;
81 
86 
90  protected ‪$routeFieldResultNames;
91 
95  protected ‪$languageFieldName;
96 
100  protected ‪$languageParentFieldName;
101 
105  protected ‪$slugUniqueInSite;
106 
111  public function ‪__construct(array ‪$settings)
112  {
113  ‪$tableName = ‪$settings['tableName'] ?? null;
114  ‪$routeFieldPattern = ‪$settings['routeFieldPattern'] ?? null;
115  ‪$routeFieldResult = ‪$settings['routeFieldResult'] ?? null;
116 
117  if (!is_string(‪$tableName)) {
118  throw new \InvalidArgumentException('tableName must be string', 1537277173);
119  }
120  if (!is_string(‪$routeFieldPattern)) {
121  throw new \InvalidArgumentException('routeFieldPattern must be string', 1537277174);
122  }
123  if (!is_string(‪$routeFieldResult)) {
124  throw new \InvalidArgumentException('routeFieldResult must be string', 1537277175);
125  }
126  if (!preg_match_all(static::PATTERN_RESULT, ‪$routeFieldResult, ‪$routeFieldResultNames)) {
127  throw new \InvalidArgumentException(
128  'routeFieldResult must contain substitutable field names',
129  1537962752
130  );
131  }
132 
133  $this->settings = ‪$settings;
134  $this->tableName = ‪$tableName;
135  $this->routeFieldPattern = ‪$routeFieldPattern;
136  $this->routeFieldResult = ‪$routeFieldResult;
137  $this->routeFieldResultNames = ‪$routeFieldResultNames['fieldName'] ?? [];
138  $this->languageFieldName = ‪$GLOBALS['TCA'][‪$this->tableName]['ctrl']['languageField'] ?? null;
139  $this->languageParentFieldName = ‪$GLOBALS['TCA'][‪$this->tableName]['ctrl']['transOrigPointerField'] ?? null;
140  $this->slugUniqueInSite = $this->hasSlugUniqueInSite($this->tableName, ...$this->routeFieldResultNames);
141  }
142 
146  public function ‪generate(string $value): ?string
147  {
148  $result = $this->‪findByIdentifier($value);
149  $result = $this->‪resolveOverlay($result);
150  return $this->‪createRouteResult($result);
151  }
152 
156  public function ‪resolve(string $value): ?string
157  {
158  if (!preg_match('#' . $this->routeFieldPattern . '#', $value, $matches)) {
159  return null;
160  }
161  $values = $this->‪filterNamesKeys($matches);
162  $result = $this->‪findByRouteFieldValues($values);
163  if ($result[$this->languageParentFieldName] ?? null > 0) {
164  return (string)$result[‪$this->languageParentFieldName];
165  }
166  if (isset($result['uid'])) {
167  return (string)$result['uid'];
168  }
169  return null;
170  }
171 
177  protected function ‪createRouteResult(?array $result): ?string
178  {
179  if ($result === null) {
180  return $result;
181  }
182  $substitutes = [];
183  foreach ($this->routeFieldResultNames as $fieldName) {
184  if (!isset($result[$fieldName])) {
185  return null;
186  }
187  $routeFieldName = '{' . $fieldName . '}';
188  $substitutes[$routeFieldName] = $result[$fieldName];
189  }
190  return str_replace(
191  array_keys($substitutes),
192  array_values($substitutes),
193  $this->routeFieldResult
194  );
195  }
196 
201  protected function ‪filterNamesKeys(array $array): array
202  {
203  return array_filter(
204  $array,
205  function ($key) {
206  return !is_numeric($key);
207  },
208  ARRAY_FILTER_USE_KEY
209  );
210  }
211 
212  protected function ‪findByIdentifier(string $value): ?array
213  {
214  $queryBuilder = $this->‪createQueryBuilder();
215  $result = $queryBuilder
216  ->select('*')
217  ->where($queryBuilder->expr()->eq(
218  'uid',
219  $queryBuilder->createNamedParameter($value, \PDO::PARAM_INT)
220  ))
221  ->execute()
222  ->fetch();
223  return $result !== false ? $result : null;
224  }
225 
226  protected function ‪findByRouteFieldValues(array $values): ?array
227  {
228  $languageAware = $this->languageFieldName !== null && $this->languageParentFieldName !== null;
229 
230  $queryBuilder = $this->‪createQueryBuilder();
231  $results = $queryBuilder
232  ->select('*')
233  ->where(...$this->‪createRouteFieldConstraints($queryBuilder, $values))
234  ->execute()
235  ->fetchAll();
236  // limit results to be contained in rootPageId of current Site
237  // (which is defining the route configuration currently being processed)
238  if ($this->slugUniqueInSite) {
239  $results = array_values($this->filterContainedInSite($results));
240  }
241  // return first result record in case table is not language aware
242  if (!$languageAware) {
243  return $results[0] ?? null;
244  }
245  // post-process language fallbacks
246  $languageIds = $this->resolveAllRelevantLanguageIds();
247  return $this->resolveLanguageFallback($results, $this->languageFieldName, $languageIds);
248  }
249 
250  protected function ‪createQueryBuilder(): QueryBuilder
251  {
252  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
253  ->getQueryBuilderForTable($this->tableName)
254  ->from($this->tableName);
255  $queryBuilder->setRestrictions(
256  GeneralUtility::makeInstance(FrontendRestrictionContainer::class, $this->context)
257  );
258  // Frontend Groups are not available at this time (initialized via TSFE->determineId)
259  // So this must be excluded to allow access restricted records
260  $queryBuilder->getRestrictions()->removeByType(FrontendGroupRestriction::class);
261  return $queryBuilder;
262  }
263 
269  protected function ‪createRouteFieldConstraints(‪QueryBuilder $queryBuilder, array $values): array
270  {
271  $languageAware = $this->languageFieldName !== null && $this->languageParentFieldName !== null;
272  $languageExpansion = $languageAware && isset($values['uid']);
273 
274  $constraints = [];
275  foreach ($values as $fieldName => $fieldValue) {
276  if ($languageExpansion && $fieldName === 'uid') {
277  continue;
278  }
279  $constraints[] = $queryBuilder->‪expr()->‪eq(
280  $fieldName,
281  $queryBuilder->‪createNamedParameter(
282  $fieldValue,
283  \PDO::PARAM_STR
284  )
285  );
286  }
287  // either match uid or language parent field value (for any language)
288  if ($languageExpansion) {
289  $idParameter = $queryBuilder->‪createNamedParameter(
290  $values['uid'],
291  \PDO::PARAM_INT
292  );
293  $constraints[] = $queryBuilder->‪expr()->‪orX(
294  $queryBuilder->‪expr()->‪eq('uid', $idParameter),
295  $queryBuilder->‪expr()->‪eq($this->languageParentFieldName, $idParameter)
296  );
297  // otherwise - basically uid is not in pattern - restrict to languages and apply fallbacks
298  } elseif ($languageAware) {
299  $languageIds = $this->resolveAllRelevantLanguageIds();
300  $constraints[] = $queryBuilder->‪expr()->‪in(
301  $this->languageFieldName,
302  $queryBuilder->‪createNamedParameter($languageIds, Connection::PARAM_INT_ARRAY)
303  );
304  }
305 
306  return $constraints;
307  }
308 
313  protected function ‪resolveOverlay(?array $record): ?array
314  {
315  $languageId = $this->siteLanguage->getLanguageId();
316  if ($record === null || $languageId === 0) {
317  return $record;
318  }
319 
320  $pageRepository = $this->‪createPageRepository();
321  if ($this->tableName === 'pages') {
322  return $pageRepository->getPageOverlay($record, $languageId);
323  }
324  return $pageRepository
325  ->getRecordOverlay($this->tableName, $record, $languageId) ?: null;
326  }
327 
331  protected function ‪createPageRepository(): ‪PageRepository
332  {
333  $context = clone GeneralUtility::makeInstance(Context::class);
334  $context->setAspect(
335  'language',
337  );
338  return GeneralUtility::makeInstance(
339  PageRepository::class,
340  $context
341  );
342  }
343 }
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\resolve
‪resolve(string $value)
Definition: PersistedPatternMapper.php:148
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$languageFieldName
‪string null $languageFieldName
Definition: PersistedPatternMapper.php:89
‪TYPO3\CMS\Core\Context\LanguageAspectFactory
Definition: LanguageAspectFactory.php:27
‪TYPO3\CMS\Core\Context\ContextAwareTrait
Definition: ContextAwareTrait.php:21
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\eq
‪string eq(string $fieldName, $value)
Definition: ExpressionBuilder.php:109
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\filterNamesKeys
‪array filterNamesKeys(array $array)
Definition: PersistedPatternMapper.php:193
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper
Definition: PersistedPatternMapper.php:58
‪TYPO3\CMS\Core\Routing\Aspect\SiteAccessorTrait
Definition: SiteAccessorTrait.php:31
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\createNamedParameter
‪string createNamedParameter($value, int $type=\PDO::PARAM_STR, string $placeHolder=null)
Definition: QueryBuilder.php:941
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\PATTERN_RESULT
‪const PATTERN_RESULT
Definition: PersistedPatternMapper.php:65
‪TYPO3\CMS\Core\Site\SiteLanguageAwareInterface
Definition: SiteLanguageAwareInterface.php:26
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$routeFieldPattern
‪string $routeFieldPattern
Definition: PersistedPatternMapper.php:77
‪TYPO3\CMS\Core\Context\LanguageAspectFactory\createFromSiteLanguage
‪static LanguageAspect createFromSiteLanguage(SiteLanguage $language)
Definition: LanguageAspectFactory.php:34
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\createPageRepository
‪PageRepository createPageRepository()
Definition: PersistedPatternMapper.php:323
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\__construct
‪__construct(array $settings)
Definition: PersistedPatternMapper.php:103
‪TYPO3\CMS\Core\Database\Query\Restriction\FrontendGroupRestriction
Definition: FrontendGroupRestriction.php:30
‪TYPO3\CMS\Core\Routing\Legacy\PersistedPatternMapperLegacyTrait
Definition: PersistedPatternMapperLegacyTrait.php:26
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:53
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$routeFieldResult
‪string $routeFieldResult
Definition: PersistedPatternMapper.php:81
‪TYPO3\CMS\Core\Site\SiteAwareInterface
Definition: SiteAwareInterface.php:26
‪TYPO3\CMS\Core\Database\Query\QueryBuilder
Definition: QueryBuilder.php:52
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\from
‪QueryBuilder from(string $from, string $alias=null)
Definition: QueryBuilder.php:531
‪TYPO3\CMS\Core\Routing\Aspect\SiteLanguageAccessorTrait
Definition: SiteLanguageAccessorTrait.php:26
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\in
‪string in(string $fieldName, $value)
Definition: ExpressionBuilder.php:244
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\resolveOverlay
‪array null resolveOverlay(?array $record)
Definition: PersistedPatternMapper.php:305
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$tableName
‪string $tableName
Definition: PersistedPatternMapper.php:73
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\findByRouteFieldValues
‪findByRouteFieldValues(array $values)
Definition: PersistedPatternMapper.php:218
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\createRouteFieldConstraints
‪array createRouteFieldConstraints(QueryBuilder $queryBuilder, array $values)
Definition: PersistedPatternMapper.php:261
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\createQueryBuilder
‪createQueryBuilder()
Definition: PersistedPatternMapper.php:242
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\createRouteResult
‪string null createRouteResult(?array $result)
Definition: PersistedPatternMapper.php:169
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\orX
‪CompositeExpression orX(... $expressions)
Definition: ExpressionBuilder.php:82
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$languageParentFieldName
‪string null $languageParentFieldName
Definition: PersistedPatternMapper.php:93
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$settings
‪array $settings
Definition: PersistedPatternMapper.php:69
‪TYPO3\CMS\Core\Routing\Aspect
Definition: AspectFactory.php:18
‪TYPO3\CMS\Core\Context\ContextAwareInterface
Definition: ContextAwareInterface.php:21
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Routing\Aspect\AspectTrait
Definition: AspectTrait.php:23
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\findByIdentifier
‪findByIdentifier(string $value)
Definition: PersistedPatternMapper.php:204
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$slugUniqueInSite
‪bool $slugUniqueInSite
Definition: PersistedPatternMapper.php:97
‪TYPO3\CMS\Core\Routing\Aspect\StaticMappableAspectInterface
Definition: StaticMappableAspectInterface.php:24
‪TYPO3\CMS\Core\Domain\Repository\PageRepository
Definition: PageRepository.php:52
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\$routeFieldResultNames
‪string[] $routeFieldResultNames
Definition: PersistedPatternMapper.php:85
‪TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer
Definition: FrontendRestrictionContainer.php:31
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\setRestrictions
‪setRestrictions(QueryRestrictionContainerInterface $restrictionContainer)
Definition: QueryBuilder.php:112
‪TYPO3\CMS\Core\Routing\Aspect\PersistedPatternMapper\generate
‪generate(string $value)
Definition: PersistedPatternMapper.php:138
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\expr
‪ExpressionBuilder expr()
Definition: QueryBuilder.php:151
‪TYPO3\CMS\Core\Routing\Aspect\PersistedMappableAspectInterface
Definition: PersistedMappableAspectInterface.php:25