‪TYPO3CMS  9.5
RedirectService.php
Go to the documentation of this file.
1 <?php
2 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 
18 use Psr\Http\Message\UriInterface;
19 use Psr\Log\LoggerAwareInterface;
20 use Psr\Log\LoggerAwareTrait;
34 
40 class ‪RedirectService implements LoggerAwareInterface
41 {
42  use LoggerAwareTrait;
43 
52  public function ‪matchRedirect(string $domain, string $path, string $query = '')
53  {
54  $allRedirects = $this->‪fetchRedirects();
55  $path = rawurldecode($path);
56  // Check if the domain matches, or if there is a
57  // redirect fitting for any domain
58  foreach ([$domain, '*'] as $domainName) {
59  if (empty($allRedirects[$domainName])) {
60  continue;
61  }
62 
63  $possibleRedirects = [];
64  // check if a flat redirect matches
65  if (!empty($allRedirects[$domainName]['flat'][rtrim($path, '/') . '/'])) {
66  $possibleRedirects = $allRedirects[$domainName]['flat'][rtrim($path, '/') . '/'];
67  }
68  // check if a flat redirect matches with the Query applied
69  if (!empty($query)) {
70  $pathWithQuery = rtrim($path, '/') . '?' . ltrim($query, '?');
71  if (!empty($allRedirects[$domainName]['respect_query_parameters'][$pathWithQuery])) {
72  $possibleRedirects = $allRedirects[$domainName]['respect_query_parameters'][$pathWithQuery];
73  } else {
74  $pathWithQueryAndSlash = rtrim($path, '/') . '/?' . ltrim($query, '?');
75  if (!empty($allRedirects[$domainName]['respect_query_parameters'][$pathWithQueryAndSlash])) {
76  $possibleRedirects = $allRedirects[$domainName]['respect_query_parameters'][$pathWithQueryAndSlash];
77  }
78  }
79  }
80  // check all redirects that are registered as regex
81  if (!empty($allRedirects[$domainName]['regexp'])) {
82  $allRegexps = array_keys($allRedirects[$domainName]['regexp']);
83  foreach ($allRegexps as $regexp) {
84  $matchResult = @preg_match($regexp, $path);
85  if ($matchResult) {
86  $possibleRedirects += $allRedirects[$domainName]['regexp'][$regexp];
87  } elseif ($matchResult === false) {
88  $this->logger->warning('Invalid regex in redirect', ['regex' => $regexp]);
89  }
90  }
91  }
92 
93  foreach ($possibleRedirects as $possibleRedirect) {
94  // check starttime and endtime for all existing records
95  if ($this->‪isRedirectActive($possibleRedirect)) {
96  return $possibleRedirect;
97  }
98  }
99  }
100  }
101 
109  protected function ‪isRedirectActive(array $redirectRecord): bool
110  {
111  return !$redirectRecord['disabled'] && $redirectRecord['starttime'] <= ‪$GLOBALS['SIM_ACCESS_TIME'] &&
112  (!$redirectRecord['endtime'] || $redirectRecord['endtime'] >= ‪$GLOBALS['SIM_ACCESS_TIME']);
113  }
114 
121  protected function ‪fetchRedirects(): array
122  {
123  return GeneralUtility::makeInstance(RedirectCacheService::class)->getRedirects();
124  }
125 
133  protected function ‪resolveLinkDetailsFromLinkTarget(string $redirectTarget): array
134  {
135  // build the target URL, take force SSL into account etc.
136  $linkService = GeneralUtility::makeInstance(LinkService::class);
137  try {
138  $linkDetails = $linkService->resolve($redirectTarget);
139  switch ($linkDetails['type']) {
141  // all set up, nothing to do
142  break;
145  $file = $linkDetails['file'];
146  if ($file instanceof ‪File) {
147  $linkDetails['url'] = $file->getPublicUrl();
148  }
149  break;
152  $folder = $linkDetails['folder'];
153  if ($folder instanceof ‪Folder) {
154  $linkDetails['url'] = $folder->getPublicUrl();
155  }
156  break;
157  default:
158  // we have to return the link details without having a "URL" parameter
159 
160  }
161  } catch (‪InvalidPathException $e) {
162  return [];
163  }
164  return $linkDetails;
165  }
166 
174  public function ‪getTargetUrl(array $matchedRedirect, array $queryParams, UriInterface $uri, ?‪SiteInterface $site = null): ?UriInterface
175  {
176  $this->logger->debug('Found a redirect to process', $matchedRedirect);
177  $linkParameterParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode((string)$matchedRedirect['target']);
178  $redirectTarget = $linkParameterParts['url'];
179  $linkDetails = $this->‪resolveLinkDetailsFromLinkTarget($redirectTarget);
180  $this->logger->debug('Resolved link details for redirect', $linkDetails);
181  if (!empty($linkParameterParts['additionalParams']) && $matchedRedirect['keep_query_parameters']) {
182  $params = GeneralUtility::explodeUrl2Array($linkParameterParts['additionalParams']);
183  foreach ($params as $key => $value) {
184  $queryParams[$key] = $value;
185  }
186  }
187  // Do this for files, folders, external URLs
188  if (!empty($linkDetails['url'])) {
189  if ($matchedRedirect['is_regexp'] ?? false) {
190  $linkDetails = $this->‪replaceRegExpCaptureGroup($matchedRedirect, $uri, $linkDetails);
191  }
192 
193  $url = new ‪Uri($linkDetails['url']);
194  if ($matchedRedirect['force_https']) {
195  $url = $url->withScheme('https');
196  }
197  if ($matchedRedirect['keep_query_parameters']) {
198  $url = $this->‪addQueryParams($queryParams, $url);
199  }
200  return $url;
201  }
202  // If it's a record or page, then boot up TSFE and use typolink
203  return $this->‪getUriFromCustomLinkDetails($matchedRedirect, $site, $linkDetails, $queryParams);
204  }
205 
213  protected function ‪addQueryParams(array $queryParams, ‪Uri $url): ‪Uri
214  {
215  // New query parameters overrule the ones that should be kept
216  $newQueryParamString = $url->‪getQuery();
217  if (!empty($newQueryParamString)) {
218  $newQueryParams = [];
219  parse_str($newQueryParamString, $newQueryParams);
220  $queryParams = array_replace_recursive($queryParams, $newQueryParams);
221  }
222  $query = http_build_query($queryParams, '', '&', PHP_QUERY_RFC3986);
223  if ($query) {
224  $url = $url->‪withQuery($query);
225  }
226  return $url;
227  }
228 
238  protected function ‪getUriFromCustomLinkDetails(array $redirectRecord, ?‪SiteInterface $site, array $linkDetails, array $queryParams): ?UriInterface
239  {
240  if (!isset($linkDetails['type'], ‪$GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']])) {
241  return null;
242  }
243  $controller = $this->‪bootFrontendController($site, $queryParams);
245  $linkBuilder = GeneralUtility::makeInstance(
246  ‪$GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']],
247  $controller->cObj,
248  $controller
249  );
250  try {
251  $configuration = [
252  'parameter' => (string)$redirectRecord['target'],
253  'forceAbsoluteUrl' => true,
254  ];
255  if ($redirectRecord['force_https']) {
256  $configuration['forceAbsoluteUrl.']['scheme'] = 'https';
257  }
258  if ($redirectRecord['keep_query_parameters']) {
259  $configuration['additionalParams'] = ‪HttpUtility::buildQueryString($queryParams, '&');
260  }
261  [$url] = $linkBuilder->build($linkDetails, '', '', $configuration);
262  return new ‪Uri($url);
263  } catch (‪UnableToLinkException $e) {
264  // This exception is also thrown by the DatabaseRecordTypolinkBuilder
265  $url = $controller->cObj->lastTypoLinkUrl;
266  if (!empty($url)) {
267  return new ‪Uri($url);
268  }
269  return null;
270  }
271  }
272 
290  protected function ‪bootFrontendController(?‪SiteInterface $site, array $queryParams): ‪TypoScriptFrontendController
291  {
292  // disable page errors
293  ‪$GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'] = false;
294  $controller = GeneralUtility::makeInstance(
295  TypoScriptFrontendController::class,
296  null,
297  $site ? $site->‪getRootPageId() : ‪$GLOBALS['TSFE']->id,
298  0
299  );
300  $controller->fe_user = ‪$GLOBALS['TSFE']->fe_user ?? null;
301  $controller->fetch_the_id();
302  $controller->calculateLinkVars($queryParams);
303  $controller->getConfigArray();
304  $controller->settingLanguage();
305  $controller->settingLocale();
306  $controller->newCObj();
307  if (!‪$GLOBALS['TSFE'] instanceof ‪TypoScriptFrontendController) {
308  ‪$GLOBALS['TSFE'] = $controller;
309  }
310  if (!‪$GLOBALS['TSFE']->sys_page instanceof ‪PageRepository) {
311  ‪$GLOBALS['TSFE']->sys_page = GeneralUtility::makeInstance(PageRepository::class);
312  }
313  return $controller;
314  }
315 
322  protected function ‪replaceRegExpCaptureGroup(array $matchedRedirect, UriInterface $uri, array $linkDetails): array
323  {
324  $matchResult = @preg_match($matchedRedirect['source_path'], $uri->getPath(), $matches);
325  if ($matchResult > 0) {
326  foreach ($matches as $key => $val) {
327  $linkDetails['url'] = str_replace('$' . $key, $val, $linkDetails['url']);
328  }
329  }
330  return $linkDetails;
331  }
332 }
‪TYPO3\CMS\Core\Site\Entity\SiteInterface
Definition: SiteInterface.php:25
‪TYPO3\CMS\Core\Site\Entity\SiteInterface\getRootPageId
‪int getRootPageId()
‪TYPO3\CMS\Redirects\Service\RedirectService\bootFrontendController
‪TypoScriptFrontendController bootFrontendController(?SiteInterface $site, array $queryParams)
Definition: RedirectService.php:290
‪TYPO3\CMS\Redirects\Service\RedirectService\isRedirectActive
‪bool isRedirectActive(array $redirectRecord)
Definition: RedirectService.php:109
‪TYPO3\CMS\Redirects\Service\RedirectService
Definition: RedirectService.php:41
‪TYPO3\CMS\Core\Http\Uri\getQuery
‪string getQuery()
Definition: Uri.php:310
‪TYPO3\CMS\Redirects\Service\RedirectService\replaceRegExpCaptureGroup
‪array replaceRegExpCaptureGroup(array $matchedRedirect, UriInterface $uri, array $linkDetails)
Definition: RedirectService.php:322
‪TYPO3\CMS\Core\Http\Uri
Definition: Uri.php:27
‪TYPO3\CMS\Redirects\Service\RedirectService\getTargetUrl
‪UriInterface null getTargetUrl(array $matchedRedirect, array $queryParams, UriInterface $uri, ?SiteInterface $site=null)
Definition: RedirectService.php:174
‪TYPO3\CMS\Redirects\Service\RedirectService\addQueryParams
‪Uri addQueryParams(array $queryParams, Uri $url)
Definition: RedirectService.php:213
‪TYPO3\CMS\Redirects\Service\RedirectService\fetchRedirects
‪array fetchRedirects()
Definition: RedirectService.php:121
‪TYPO3\CMS\Frontend\Page\PageRepository
Definition: PageRepository.php:53
‪TYPO3\CMS\Redirects\Service\RedirectService\resolveLinkDetailsFromLinkTarget
‪array resolveLinkDetailsFromLinkTarget(string $redirectTarget)
Definition: RedirectService.php:133
‪TYPO3\CMS\Redirects\Service\RedirectService\getUriFromCustomLinkDetails
‪UriInterface null getUriFromCustomLinkDetails(array $redirectRecord, ?SiteInterface $site, array $linkDetails, array $queryParams)
Definition: RedirectService.php:238
‪TYPO3\CMS\Core\Resource\Folder
Definition: Folder.php:34
‪TYPO3\CMS\Core\Utility\HttpUtility\buildQueryString
‪static string buildQueryString(array $parameters, string $prependCharacter='', bool $skipEmptyParameters=false)
Definition: HttpUtility.php:160
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:23
‪TYPO3\CMS\Redirects\Service
Definition: RedirectCacheService.php:3
‪TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
Definition: TypoScriptFrontendController.php:97
‪TYPO3\CMS\Core\Http\Uri\withQuery
‪static withQuery($query)
Definition: Uri.php:504
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Utility\HttpUtility
Definition: HttpUtility.php:21
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Resource\Exception\InvalidPathException
Definition: InvalidPathException.php:21
‪TYPO3\CMS\Redirects\Service\RedirectService\matchRedirect
‪array null matchRedirect(string $domain, string $path, string $query='')
Definition: RedirectService.php:52