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