‪TYPO3CMS  11.5
PageLinkBuilder.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 Psr\Http\Message\ServerRequestInterface;
21 use Psr\Http\Message\UriInterface;
47 
52 {
57  public function ‪build(array &$linkDetails, string $linkText, string $target, array $conf): ‪LinkResultInterface
58  {
59  $linkResultType = ‪LinkService::TYPE_PAGE;
60  $tsfe = $this->‪getTypoScriptFrontendController();
61  if (empty($linkDetails['pageuid']) || $linkDetails['pageuid'] === 'current') {
62  // If no id is given
63  $linkDetails['pageuid'] = $tsfe->id;
64  }
65 
66  // Link to page even if access is missing?
67  if (isset($conf['linkAccessRestrictedPages'])) {
68  $disableGroupAccessCheck = (bool)($conf['linkAccessRestrictedPages'] ?? false);
69  } else {
70  $disableGroupAccessCheck = (bool)($tsfe->config['config']['typolinkLinkAccessRestrictedPages'] ?? false);
71  }
72 
73  // Looking up the page record to verify its existence:
74  $page = $this->‪resolvePage($linkDetails, $conf, $disableGroupAccessCheck);
75 
76  if (empty($page)) {
77  throw new ‪UnableToLinkException('Page id "' . $linkDetails['pageuid'] . '" was not found, so "' . $linkText . '" was not linked.', 1490987336, null, $linkText);
78  }
79 
80  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typolinkProcessing']['typolinkModifyParameterForPageLinks'] ?? [] as $classData) {
81  $hookObject = GeneralUtility::makeInstance($classData);
82  if (!$hookObject instanceof ‪TypolinkModifyLinkConfigForPageLinksHookInterface) {
83  throw new \UnexpectedValueException('$hookObject must implement interface ' . TypolinkModifyLinkConfigForPageLinksHookInterface::class, 1483114905);
84  }
86  $conf = $hookObject->modifyPageLinkConfiguration($conf, $linkDetails, $page);
87  }
88  $conf['additionalParams'] ??= '';
89 
90  $fragment = $this->‪calculateUrlFragment($conf, $linkDetails);
91  $queryParameters = $this->‪calculateQueryParameters($conf, $linkDetails);
92  // Add MP parameter
93  $mountPointParameter = $this->‪calculateMountPointParameters($page, $disableGroupAccessCheck, $linkText);
94  if ($mountPointParameter !== null) {
95  $queryParameters['MP'] = $mountPointParameter;
96  }
97 
98  // Check if the target page has a site configuration
99  try {
100  $siteOfTargetPage = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId((int)$page['uid'], null, $queryParameters['MP'] ?? '');
101  $currentSite = $this->‪getCurrentSite();
102  } catch (‪SiteNotFoundException $e) {
103  // Usually happens in tests, as sites with configuration should be available everywhere.
104  $siteOfTargetPage = null;
105  $currentSite = null;
106  }
107  if ($siteOfTargetPage == null) {
108  throw new ‪UnableToLinkException('Could not link to page with ID: ' . $page['uid'], 1546887172, null, $linkText);
109  }
110 
111  try {
112  $siteLanguageOfTargetPage = $this->‪getSiteLanguageOfTargetPage($siteOfTargetPage, (string)($conf['language'] ?? 'current'));
113  } catch (‪UnableToLinkException $e) {
114  throw new ‪UnableToLinkException($e->getMessage(), $e->getCode(), $e, $linkText);
115  }
116  $languageAspect = ‪LanguageAspectFactory::createFromSiteLanguage($siteLanguageOfTargetPage);
117  $pageRepository = $this->‪buildPageRepository($languageAspect);
118 
119  // Now overlay the page in the target language, in order to have valid title attributes etc.
120  if ($siteLanguageOfTargetPage->getLanguageId() > 0) {
121  $page = $pageRepository->getPageOverlay($page);
122  // Check if the translated page is a shortcut, but the default page wasn't a shortcut, so this is
123  // resolved as well, see ScenarioDTest in functional tests.
124  // Currently not supported: When this is the case (only a translated page is a shortcut),
125  // but the page links to a different site.
126  $shortcutPage = $this->‪resolveShortcutPage($page, $pageRepository, $disableGroupAccessCheck);
127  if (!empty($shortcutPage)) {
128  $page = $shortcutPage;
129  }
130  }
131  // Check if the target page can be access depending on l18n_cfg
132  if (!$pageRepository->isPageSuitableForLanguage($page, $languageAspect)) {
133  $pageTranslationVisibility = new ‪PageTranslationVisibility((int)($page['l18n_cfg'] ?? 0));
134  if ($siteLanguageOfTargetPage->getLanguageId() === 0 && $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage()) {
135  throw new ‪UnableToLinkException('Default language of page "' . ($linkDetails['typoLinkParameter'] ?? 'unknown') . '" is hidden, so "' . $linkText . '" was not linked.', 1551621985, null, $linkText);
136  }
137  // If the requested language is not the default language and the page has no overlay for this language
138  // generating a link would cause a 404 error when using this like if one of those conditions apply:
139  // - The page is set to be hidden if it is not translated (evaluated in TSFE)
140  // - The site configuration has a "strict" fallback set (evaluated in the Router - very early)
141  if ($siteLanguageOfTargetPage->getLanguageId() > 0 && !isset($page['_PAGES_OVERLAY']) && ($pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists() || $siteLanguageOfTargetPage->getFallbackType() === 'strict')) {
142  throw new ‪UnableToLinkException('Fallback to default language of page "' . ($linkDetails['typoLinkParameter'] ?? 'unknown') . '" is disabled, so "' . $linkText . '" was not linked.', 1551621996, null, $linkText);
143  }
144  }
145 
146  $treatAsExternalLink = true;
147  // External links are resolved via calling Typolink again (could be anything, really)
148  if ((int)$page['doktype'] === ‪PageRepository::DOKTYPE_LINK) {
149  $conf['parameter'] = $page['url'];
150  unset($conf['parameter.']);
151  // Use "pages.target" as this is the requested field for external links as well
152  if (!isset($conf['extTarget'])) {
153  $conf['extTarget'] = (isset($page['target']) && trim($page['target'])) ? $page['target'] : $target;
154  }
155  $this->contentObjectRenderer->typoLink($linkText, $conf);
156  $target = $this->contentObjectRenderer->lastTypoLinkTarget;
157  $url = $this->contentObjectRenderer->lastTypoLinkUrl;
158  // If the page external URL is resolved into a URL or email, this should be taken into account when compiling the final link result object
159  if ($this->contentObjectRenderer->lastTypoLinkResult) {
160  $linkResultType = $this->contentObjectRenderer->lastTypoLinkResult->getType();
161  }
162  if (empty($url)) {
163  throw new ‪UnableToLinkException('Link to external page "' . $page['uid'] . '" does not have a proper target URL, so "' . $linkText . '" was not linked.', 1551621999, null, $linkText);
164  }
165  } else {
166  // Generate the URL
167  $url = $this->‪generateUrlForPageWithSiteConfiguration($page, $siteOfTargetPage, $queryParameters, $fragment, $conf);
168  // no scheme => always not external
169  if (!$url->getScheme() || !$url->getHost()) {
170  $treatAsExternalLink = false;
171  } else {
172  // URL has a scheme, possibly because someone requested a full URL. So now lets check if the URL
173  // is on the same site pagetree. If this is the case, we'll treat it as internal
174  // @todo: currently this does not check if the target page is a mounted page in a different site,
175  // so it is treating this as an absolute URL, which is wrong
176  if ($currentSite && $currentSite->getRootPageId() === $siteOfTargetPage->getRootPageId()) {
177  $treatAsExternalLink = false;
178  }
179  }
180  $url = (string)$url;
181  }
182 
183  $target = $this->‪calculateTargetAttribute($page, $conf, $treatAsExternalLink, $target);
184 
185  // If link is to an access-restricted page which should be redirected, then find new URL
186  $url = $this->‪modifyUrlForAccessRestrictedPage($url, $page, $linkDetails['pagetype'] ?? '', $conf);
187 
188  // Setting title if blank value to link
189  $linkText = $this->‪parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $page['title'] ?? '');
190  $result = new ‪LinkResult($linkResultType, $url);
191  return $result
192  ->withLinkConfiguration($conf)
193  ->withTarget($target)
194  ->withLinkText($linkText);
195  }
196 
203  protected function ‪calculateUrlFragment(array $conf, array $linkDetails): string
204  {
205  $fragment = trim((string)$this->contentObjectRenderer->stdWrapValue('section', $conf, $linkDetails['fragment'] ?? ''));
206  return ($fragment && ‪MathUtility::canBeInterpretedAsInteger($fragment) ? 'c' : '') . $fragment;
207  }
208 
223  protected function ‪calculateQueryParameters(array &$conf, array $linkDetails): array
224  {
225  if (isset($linkDetails['parameters'])) {
226  $conf['additionalParams'] .= '&' . ltrim($linkDetails['parameters'], '&');
227  }
228 
229  $queryParameters = [];
230  $addQueryParams = ($conf['addQueryString'] ?? false) ? $this->contentObjectRenderer->getQueryArguments($conf['addQueryString.'] ?? []) : '';
231  $addQueryParams .= trim((string)$this->contentObjectRenderer->stdWrapValue('additionalParams', $conf));
232  if ($addQueryParams === '&' || ($addQueryParams[0] ?? '') !== '&') {
233  $addQueryParams = '';
234  }
235  parse_str($addQueryParams, $queryParameters);
236  // get config.linkVars and prepend them before the actual GET parameters
237  $tsfe = $this->‪getTypoScriptFrontendController();
238  if ($tsfe->linkVars) {
239  $globalQueryParameters = [];
240  parse_str($tsfe->linkVars, $globalQueryParameters);
241  $queryParameters = array_replace_recursive($globalQueryParameters, $queryParameters);
242  }
243  // Disable "?id=", for pages with no site configuration, this is added later-on anyway
244  unset($queryParameters['id']);
245  if ($linkDetails['pagetype'] ?? '') {
246  $queryParameters['type'] = $linkDetails['pagetype'];
247  }
248  $conf['no_cache'] = (string)$this->contentObjectRenderer->stdWrapValue('no_cache', $conf);
249  if ($conf['no_cache'] ?? false) {
250  $queryParameters['no_cache'] = 1;
251  }
252  // Override language property if not being set already, supporting historically 'L' and
253  // modern '_language' arguments, giving '_language' the precedence.
254  if (isset($queryParameters['_language'])) {
255  if (!isset($conf['language'])) {
256  $conf['language'] = $queryParameters['_language'];
257  }
258  unset($queryParameters['_language']);
259  }
260  if (isset($queryParameters['L'])) {
261  if (!isset($conf['language'])) {
262  $conf['language'] = $queryParameters['L'];
263  }
264  unset($queryParameters['L']);
265  }
266  return $queryParameters;
267  }
268 
272  protected function ‪calculateMountPointParameters(array &$page, bool $disableGroupAccessCheck, string $linkText): ?string
273  {
274  // MountPoints, look for closest MPvar:
275  $mountPointPairs = [];
276  $tsfe = $this->‪getTypoScriptFrontendController();
277  if (!($tsfe->config['config']['MP_disableTypolinkClosestMPvalue'] ?? false)) {
278  $temp_MP = $this->‪getClosestMountPointValueForPage((int)$page['uid']);
279  if ($temp_MP) {
280  $mountPointPairs['closest'] = $temp_MP;
281  }
282  }
283  // Look for overlay Mount Point:
284  $mount_info = $tsfe->sys_page->getMountPointInfo($page['uid'], $page);
285  if (is_array($mount_info) && $mount_info['overlay']) {
286  $page = $tsfe->sys_page->getPage($mount_info['mount_pid'], $disableGroupAccessCheck);
287  if (empty($page)) {
288  throw new ‪UnableToLinkException('Mount point "' . $mount_info['mount_pid'] . '" was not available, so "' . $linkText . '" was not linked.', 1490987337, null, $linkText);
289  }
290  $mountPointPairs['re-map'] = $mount_info['MPvar'];
291  }
292  // Mount pages are always local and never link to another domain,
293  $addMountPointParameters = !empty($mountPointPairs);
294  // Add "&MP" var, only if the original page was NOT a shortcut to another domain
295  if ($addMountPointParameters && !empty($page['_SHORTCUT_ORIGINAL_PAGE_UID'])) {
296  $siteOfTargetPage = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId((int)$page['_SHORTCUT_ORIGINAL_PAGE_UID']);
297  $currentSite = $this->‪getCurrentSite();
298  if ($siteOfTargetPage !== $currentSite) {
299  $addMountPointParameters = false;
300  }
301  }
302  if ($addMountPointParameters) {
303  return rawurlencode(implode(',', $mountPointPairs));
304  }
305  return null;
306  }
307 
311  protected function ‪calculateTargetAttribute(array $page, array $conf, bool $treatAsExternalLink, string $target): string
312  {
313  $tsfe = $this->‪getTypoScriptFrontendController();
314  if ($treatAsExternalLink) {
315  $target = $target ?: $this->‪resolveTargetAttribute($conf, 'extTarget', false, $tsfe->extTarget);
316  } else {
317  $target = (isset($page['target']) && trim($page['target'])) ? $page['target'] : $target;
318  if (empty($target)) {
319  $target = $this->‪resolveTargetAttribute($conf, 'target', true, $tsfe->intTarget);
320  }
321  }
322  return $target;
323  }
324 
329  protected function ‪modifyUrlForAccessRestrictedPage(string $url, array $page, string $overridePageType, array $conf): string
330  {
331  $tsfe = $this->‪getTypoScriptFrontendController();
332  if (empty($conf['linkAccessRestrictedPages'])
333  && ($tsfe->config['config']['typolinkLinkAccessRestrictedPages'] ?? false)
334  && $tsfe->config['config']['typolinkLinkAccessRestrictedPages'] !== 'NONE'
335  && !$tsfe->checkPageGroupAccess($page)
336  ) {
337  $thePage = $tsfe->sys_page->getPage($tsfe->config['config']['typolinkLinkAccessRestrictedPages']);
338  $addParams = str_replace(
339  [
340  '###RETURN_URL###',
341  '###PAGE_ID###',
342  ],
343  [
344  rawurlencode($url),
345  $page['uid'],
346  ],
347  $tsfe->config['config']['typolinkLinkAccessRestrictedPages_addParams'] ?? ''
348  );
349  $url = $this->contentObjectRenderer->getTypoLink_URL($thePage['uid'] . ($overridePageType ? ',' . $overridePageType : ''), $addParams);
350  }
351  return $url;
352  }
353 
364  protected function ‪resolvePage(array &$linkDetails, array &$configuration, bool $disableGroupAccessCheck): array
365  {
366  $pageRepository = $this->‪buildPageRepository();
367  // Looking up the page record to verify its existence
368  // This is used when a page to a translated page is executed directly.
369  $page = $pageRepository->getPage($linkDetails['pageuid'], $disableGroupAccessCheck);
370 
371  if (empty($page) || !is_array($page)) {
372  return [];
373  }
374  $page = $this->‪resolveShortcutPage($page, $pageRepository, $disableGroupAccessCheck);
375 
376  $languageField = ‪$GLOBALS['TCA']['pages']['ctrl']['languageField'] ?? null;
377  $languageParentField = ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] ?? null;
378  $language = (int)($page[$languageField] ?? 0);
379 
380  // The page that should be linked is actually a default-language page, nothing to do here.
381  if ($language === 0 || empty($page[$languageParentField])) {
382  return $page;
383  }
384 
385  // Let's fetch the default-language page now
386  $languageParentPage = $pageRepository->getPage(
387  $page[$languageParentField],
388  $disableGroupAccessCheck
389  );
390  if (empty($languageParentPage)) {
391  return $page;
392  }
393  // Check for the shortcut of the default-language page
394  $languageParentPage = $this->‪resolveShortcutPage($languageParentPage, $pageRepository, $disableGroupAccessCheck);
395 
396  // Set the "pageuid" to the default-language page ID.
397  $linkDetails['pageuid'] = (int)$languageParentPage['uid'];
398  $configuration['language'] = $language;
399  return $languageParentPage;
400  }
401 
405  protected function ‪resolveShortcutPage(array $page, ‪PageRepository $pageRepository, bool $disableGroupAccessCheck): array
406  {
407  try {
408  $page = $pageRepository->‪resolveShortcutPage($page, false, $disableGroupAccessCheck);
409  } catch (\‪Exception $e) {
410  // Keep the existing page record if shortcut could not be resolved
411  }
412  return $page;
413  }
414 
423  protected function ‪getSiteLanguageOfTargetPage(‪Site $siteOfTargetPage, string $targetLanguageId): ‪SiteLanguage
424  {
425  $currentSite = $this->‪getCurrentSite();
426  $currentSiteLanguage = $this->‪getCurrentSiteLanguage();
427  // Happens when currently on a pseudo-site configuration
428  // We assume to use the default language then
429  if ($currentSite && !($currentSiteLanguage instanceof ‪SiteLanguage)) {
430  $currentSiteLanguage = $currentSite->getDefaultLanguage();
431  }
432 
433  if ($targetLanguageId === 'current') {
434  $targetLanguageId = $currentSiteLanguage ? $currentSiteLanguage->getLanguageId() : 0;
435  } else {
436  $targetLanguageId = (int)$targetLanguageId;
437  }
438  try {
439  $siteLanguageOfTargetPage = $siteOfTargetPage->‪getLanguageById($targetLanguageId);
440  } catch (\InvalidArgumentException $e) {
441  throw new ‪UnableToLinkException('The target page does not have a language with ID ' . $targetLanguageId . ' configured in its site configuration.', 1535477406);
442  }
443  return $siteLanguageOfTargetPage;
444  }
445 
457  protected function ‪generateUrlForPageWithSiteConfiguration(array $page, ‪Site $siteOfTargetPage, array $queryParameters, string $fragment, array $conf): UriInterface
458  {
459  $tsfe = $this->‪getTypoScriptFrontendController();
460  $currentSite = $this->‪getCurrentSite();
461  $currentSiteLanguage = $this->‪getCurrentSiteLanguage();
462  // Happens when currently on a pseudo-site configuration
463  // We assume to use the default language then
464  if ($currentSite && !($currentSiteLanguage instanceof ‪SiteLanguage)) {
465  $currentSiteLanguage = $currentSite->getDefaultLanguage();
466  }
467 
468  $siteLanguageOfTargetPage = $this->‪getSiteLanguageOfTargetPage($siteOfTargetPage, (string)($conf['language'] ?? 'current'));
469 
470  // By default, it is assumed to ab an internal link or current domain's linking scheme should be used
471  // Use the config option to override this.
472  $useAbsoluteUrl = $conf['forceAbsoluteUrl'] ?? false;
473  // Check if the current page equal to the site of the target page, now only set the absolute URL
474  // Always generate absolute URLs if no current site is set
475  if (
476  !$currentSite
477  || $currentSite->getRootPageId() !== $siteOfTargetPage->‪getRootPageId()
478  || $siteLanguageOfTargetPage->getBase()->getHost() !== $currentSiteLanguage->getBase()->getHost()) {
479  $useAbsoluteUrl = true;
480  }
481 
482  $targetPageId = (int)($page['l10n_parent'] > 0 ? $page['l10n_parent'] : $page['uid']);
483  $queryParameters['_language'] = $siteLanguageOfTargetPage;
484  $pageObject = new ‪Page($page);
485 
486  if ($fragment
487  && $useAbsoluteUrl === false
488  && $currentSiteLanguage === $siteLanguageOfTargetPage
489  && $targetPageId === (int)$tsfe->id
490  && (empty($conf['addQueryString']) || !isset($conf['addQueryString.']))
491  && !($tsfe->config['config']['baseURL'] ?? false)
492  && count($queryParameters) === 1 // _language is always set
493  ) {
494  $uri = (new ‪Uri())->withFragment($fragment);
495  } else {
496  try {
497  $uri = $siteOfTargetPage->‪getRouter()->‪generateUri(
498  $pageObject,
499  $queryParameters,
500  $fragment,
502  );
503  } catch (‪InvalidRouteArgumentsException $e) {
504  throw new ‪UnableToLinkException('The target page could not be linked. Error: ' . $e->getMessage(), 1535472406);
505  }
506  // Override scheme if absoluteUrl is set, but only if the site defines a domain/host. Fall back to site scheme and else https.
507  if ($useAbsoluteUrl && $uri->getHost()) {
508  $scheme = $conf['forceAbsoluteUrl.']['scheme'] ?? false;
509  if (!$scheme) {
510  $scheme = $uri->getScheme() ?: 'https';
511  }
512  $uri = $uri->withScheme($scheme);
513  }
514  }
515 
516  return $uri;
517  }
518 
525  protected function ‪getClosestMountPointValueForPage(int $pageId): string
526  {
527  $tsfe = $this->‪getTypoScriptFrontendController();
528  if (empty(‪$GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) || !$tsfe->MP) {
529  return '';
530  }
531  // Same page as current.
532  if ((int)$tsfe->id === (int)$pageId) {
533  return $tsfe->MP;
534  }
535 
536  // Find closest mount point
537  // Gets rootline of linked-to page
538  try {
539  $tCR_rootline = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get();
540  } catch (‪RootLineException $e) {
541  $tCR_rootline = [];
542  }
543  $inverseTmplRootline = array_reverse($tsfe->tmpl->rootLine);
544  $rl_mpArray = [];
545  $startMPaccu = false;
546  // Traverse root line of link uid and inside of that the REAL root line of current position.
547  foreach ($tCR_rootline as $tCR_data) {
548  foreach ($inverseTmplRootline as $rlKey => $invTmplRLRec) {
549  // Force accumulating when in overlay mode: Links to this page have to stay within the current branch
550  if (($invTmplRLRec['_MOUNT_OL'] ?? false) && (int)$tCR_data['uid'] === (int)$invTmplRLRec['uid']) {
551  $startMPaccu = true;
552  }
553  // Accumulate MP data:
554  if ($startMPaccu && ($invTmplRLRec['_MP_PARAM'] ?? false)) {
555  $rl_mpArray[] = $invTmplRLRec['_MP_PARAM'];
556  }
557  // If two PIDs matches and this is NOT the site root, start accumulation of MP data (on the next level):
558  // (The check for site root is done so links to branches outside the site but sharing the site roots PID
559  // is NOT detected as within the branch!)
560  if ((int)$tCR_data['pid'] === (int)$invTmplRLRec['pid'] && count($inverseTmplRootline) !== $rlKey + 1) {
561  $startMPaccu = true;
562  }
563  }
564  if ($startMPaccu) {
565  // Good enough...
566  break;
567  }
568  }
569  return !empty($rl_mpArray) ? implode(',', array_reverse($rl_mpArray)) : '';
570  }
571 
581  public function ‪getMountPointParameterFromRootPointMaps(int $pageId): string
582  {
583  // Create map if not found already
584  $config = $this->‪getTypoScriptFrontendController()->config;
585  $mountPointMap = $this->‪initializeMountPointMap(
586  !empty($config['config']['MP_defaults']) ? $config['config']['MP_defaults'] : '',
587  !empty($config['config']['MP_mapRootPoints']) ? $config['config']['MP_mapRootPoints'] : ''
588  );
589 
590  // Finding MP var for Page ID:
591  if (!empty($mountPointMap[$pageId])) {
592  return implode(',', $mountPointMap[$pageId]);
593  }
594  return '';
595  }
596 
604  protected function ‪initializeMountPointMap(string $defaultMountPoints = '', string $mapRootPointList = ''): array
605  {
606  $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
607  $mountPointMap = $runtimeCache->get('pageLinkBuilderMountPointMap') ?: [];
608  if (!empty($mountPointMap) || (empty($mapRootPointList) && empty($defaultMountPoints))) {
609  return $mountPointMap;
610  }
611  if ($defaultMountPoints) {
612  $defaultMountPoints = ‪GeneralUtility::trimExplode('|', $defaultMountPoints, true);
613  foreach ($defaultMountPoints as $temp_p) {
614  [$temp_idP, $temp_MPp] = explode(':', $temp_p, 2);
615  $temp_ids = ‪GeneralUtility::intExplode(',', $temp_idP);
616  foreach ($temp_ids as $temp_id) {
617  $mountPointMap[$temp_id] = trim($temp_MPp);
618  }
619  }
620  }
621 
622  $rootPoints = ‪GeneralUtility::trimExplode(',', strtolower($mapRootPointList), true);
623  // Traverse rootpoints
624  foreach ($rootPoints as $p) {
625  $initMParray = [];
626  if ($p === 'root') {
627  $rootPage = $this->‪getTypoScriptFrontendController()->tmpl->rootLine[0];
628  $p = $rootPage['uid'];
629  if (($rootPage['_MOUNT_OL'] ?? false) && ($rootPage['_MP_PARAM'] ?? false)) {
630  $initMParray[] = $rootPage['_MP_PARAM'];
631  }
632  }
633  $this->‪populateMountPointMapForPageRecursively($mountPointMap, (int)$p, $initMParray);
634  }
635  $runtimeCache->set('pageLinkBuilderMountPointMap', $mountPointMap);
636  return $mountPointMap;
637  }
638 
649  protected function ‪populateMountPointMapForPageRecursively(array &$mountPointMap, int $id, array $MP_array = [], int $level = 0): void
650  {
651  if ($id <= 0) {
652  return;
653  }
654  // First level, check id
655  if (!$level) {
656  // Find mount point if any:
657  $mount_info = $this->‪getTypoScriptFrontendController()->sys_page->getMountPointInfo($id);
658  // Overlay mode:
659  if (is_array($mount_info) && $mount_info['overlay']) {
660  $MP_array[] = $mount_info['MPvar'];
661  $id = $mount_info['mount_pid'];
662  }
663  // Set mapping information for this level:
664  $mountPointMap[$id] = $MP_array;
665  // Normal mode:
666  if (is_array($mount_info) && !$mount_info['overlay']) {
667  $MP_array[] = $mount_info['MPvar'];
668  $id = $mount_info['mount_pid'];
669  }
670  }
671  if ($id && $level < 20) {
672  $nextLevelAcc = [];
673  // Select and traverse current level pages:
674  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
675  $queryBuilder->getRestrictions()
676  ->removeAll()
677  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
678  $queryResult = $queryBuilder
679  ->select('uid', 'pid', 'doktype', 'mount_pid', 'mount_pid_ol', 't3ver_state', 'l10n_parent')
680  ->from('pages')
681  ->where(
682  $queryBuilder->expr()->eq(
683  'pid',
684  $queryBuilder->createNamedParameter($id, ‪Connection::PARAM_INT)
685  ),
686  $queryBuilder->expr()->neq(
687  'doktype',
688  $queryBuilder->createNamedParameter(‪PageRepository::DOKTYPE_RECYCLER, ‪Connection::PARAM_INT)
689  ),
690  $queryBuilder->expr()->neq(
691  'doktype',
692  $queryBuilder->createNamedParameter(‪PageRepository::DOKTYPE_BE_USER_SECTION, ‪Connection::PARAM_INT)
693  )
694  )->executeQuery();
695  while ($row = $queryResult->fetchAssociative()) {
696  // Find mount point if any:
697  $next_id = (int)$row['uid'];
698  $next_MP_array = $MP_array;
699  $mount_info = $this->‪getTypoScriptFrontendController()->sys_page->getMountPointInfo($next_id, $row);
700  // Overlay mode:
701  if (is_array($mount_info) && $mount_info['overlay']) {
702  $next_MP_array[] = $mount_info['MPvar'];
703  $next_id = (int)$mount_info['mount_pid'];
704  }
705  if (!isset($mountPointMap[$next_id])) {
706  // Set mapping information for this level:
707  $mountPointMap[$next_id] = $next_MP_array;
708  // Normal mode:
709  if (is_array($mount_info) && !$mount_info['overlay']) {
710  $next_MP_array[] = $mount_info['MPvar'];
711  $next_id = (int)$mount_info['mount_pid'];
712  }
713  // Register recursive call
714  // (have to do it this way since ALL of the current level should be registered BEFORE the sublevel at any time)
715  $nextLevelAcc[] = [$next_id, $next_MP_array];
716  }
717  }
718  // Call recursively, if any:
719  foreach ($nextLevelAcc as $pSet) {
720  $this->‪populateMountPointMapForPageRecursively($mountPointMap, $pSet[0], $pSet[1], $level + 1);
721  }
722  }
723  }
724 
731  protected function ‪getCurrentSite(): ?‪SiteInterface
732  {
733  if ($this->typoScriptFrontendController instanceof ‪TypoScriptFrontendController) {
734  return $this->typoScriptFrontendController->getSite();
735  }
736  if (isset(‪$GLOBALS['TYPO3_REQUEST']) && ‪$GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) {
737  return ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('site', null);
738  }
739  if (‪MathUtility::canBeInterpretedAsInteger(‪$GLOBALS['TSFE']->id) && ‪$GLOBALS['TSFE']->id > 0) {
740  ‪$finder = GeneralUtility::makeInstance(SiteFinder::class);
741  try {
742  $site = ‪$finder->getSiteByPageId((int)‪$GLOBALS['TSFE']->id);
743  } catch (‪SiteNotFoundException $e) {
744  $site = null;
745  }
746  return $site;
747  }
748  return null;
749  }
750 
758  {
759  if ($this->typoScriptFrontendController instanceof ‪TypoScriptFrontendController) {
760  return $this->typoScriptFrontendController->getLanguage();
761  }
762  if (isset(‪$GLOBALS['TYPO3_REQUEST']) && ‪$GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) {
763  return ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('language', null);
764  }
765  return null;
766  }
767 
772  protected function ‪buildPageRepository(‪LanguageAspect $languageAspect = null): ‪PageRepository
773  {
774  // clone global context object (singleton)
775  $context = clone GeneralUtility::makeInstance(Context::class);
776  $context->setAspect(
777  'language',
778  $languageAspect ?? GeneralUtility::makeInstance(LanguageAspect::class)
779  );
780  return GeneralUtility::makeInstance(
781  PageRepository::class,
782  $context
783  );
784  }
785 }
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:999
‪TYPO3\CMS\Core\Site\Entity\SiteInterface
Definition: SiteInterface.php:26
‪TYPO3\CMS\Core\Domain\Page
Definition: Page.php:24
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:49
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Core\Context\LanguageAspectFactory
Definition: LanguageAspectFactory.php:27
‪TYPO3\CMS\Core\Routing\RouterInterface
Definition: RouterInterface.php:28
‪$finder
‪if(PHP_SAPI !=='cli') $finder
Definition: header-comment.php:22
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\resolveShortcutPage
‪resolveShortcutPage(array $page, bool $resolveRandomSubpages=false, bool $disableGroupAccessCheck=false)
Definition: PageRepository.php:1101
‪TYPO3\CMS\Core\Site\Entity\Site\getRouter
‪RouterInterface getRouter(Context $context=null)
Definition: Site.php:368
‪TYPO3\CMS\Core\Utility\RootlineUtility
Definition: RootlineUtility.php:38
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_LINK
‪const DOKTYPE_LINK
Definition: PageRepository.php:111
‪TYPO3\CMS\Core\Exception\SiteNotFoundException
Definition: SiteNotFoundException.php:25
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Core\Context\LanguageAspectFactory\createFromSiteLanguage
‪static LanguageAspect createFromSiteLanguage(SiteLanguage $language)
Definition: LanguageAspectFactory.php:34
‪TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility
Definition: PageTranslationVisibility.php:30
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:53
‪TYPO3\CMS\Core\Routing\RouterInterface\generateUri
‪UriInterface generateUri($route, array $parameters=[], string $fragment='', string $type=self::ABSOLUTE_URL)
‪TYPO3\CMS\Core\Http\Uri
Definition: Uri.php:29
‪TYPO3\CMS\Core\Site\Entity\Site
Definition: Site.php:42
‪TYPO3\CMS\Core\Site\Entity\SiteLanguage
Definition: SiteLanguage.php:26
‪TYPO3\CMS\Frontend\Exception
Definition: Exception.php:23
‪TYPO3\CMS\Core\Site\Entity\SiteInterface\getLanguageById
‪SiteLanguage getLanguageById(int $languageId)
‪TYPO3\CMS\Core\Routing\RouterInterface\ABSOLUTE_PATH
‪const ABSOLUTE_PATH
Definition: RouterInterface.php:37
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_BE_USER_SECTION
‪const DOKTYPE_BE_USER_SECTION
Definition: PageRepository.php:113
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException
Definition: InvalidRouteArgumentsException.php:25
‪TYPO3\CMS\Core\Context\LanguageAspect
Definition: LanguageAspect.php:57
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:38
‪TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
Definition: TypoScriptFrontendController.php:104
‪TYPO3\CMS\Core\Site\Entity\Site\getRootPageId
‪int getRootPageId()
Definition: Site.php:199
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:927
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Core\Exception\Page\RootLineException
Definition: RootLineException.php:24
‪TYPO3\CMS\Core\Domain\Repository\PageRepository
Definition: PageRepository.php:53
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Routing\RouterInterface\ABSOLUTE_URL
‪const ABSOLUTE_URL
Definition: RouterInterface.php:32
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_RECYCLER
‪const DOKTYPE_RECYCLER
Definition: PageRepository.php:117