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