‪TYPO3CMS  10.4
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;
42 
47 {
52  public function ‪build(array &$linkDetails, string $linkText, string $target, array $conf): array
53  {
54  $tsfe = $this->‪getTypoScriptFrontendController();
55  if (empty($linkDetails['pageuid']) || $linkDetails['pageuid'] === 'current') {
56  // If no id is given
57  $linkDetails['pageuid'] = $tsfe->id;
58  }
59 
60  // Link to page even if access is missing?
61  if (isset($conf['linkAccessRestrictedPages'])) {
62  $disableGroupAccessCheck = (bool)$conf['linkAccessRestrictedPages'];
63  } else {
64  $disableGroupAccessCheck = (bool)($tsfe->config['config']['typolinkLinkAccessRestrictedPages'] ?? false);
65  }
66 
67  // Looking up the page record to verify its existence:
68  $page = $this->‪resolvePage($linkDetails, $conf, $disableGroupAccessCheck);
69 
70  if (empty($page)) {
71  throw new ‪UnableToLinkException('Page id "' . $linkDetails['typoLinkParameter'] . '" was not found, so "' . $linkText . '" was not linked.', 1490987336, null, $linkText);
72  }
73 
74  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typolinkProcessing']['typolinkModifyParameterForPageLinks'] ?? [] as $classData) {
75  $hookObject = GeneralUtility::makeInstance($classData);
76  if (!$hookObject instanceof ‪TypolinkModifyLinkConfigForPageLinksHookInterface) {
77  throw new \UnexpectedValueException('$hookObject must implement interface ' . TypolinkModifyLinkConfigForPageLinksHookInterface::class, 1483114905);
78  }
80  $conf = $hookObject->modifyPageLinkConfiguration($conf, $linkDetails, $page);
81  }
82  if ($conf['no_cache.']) {
83  $conf['no_cache'] = (string)$this->contentObjectRenderer->stdWrap($conf['no_cache'], $conf['no_cache.']);
84  }
85 
86  $sectionMark = trim(isset($conf['section.']) ? (string)$this->contentObjectRenderer->stdWrap($conf['section'], $conf['section.']) : (string)$conf['section']);
87  if ($sectionMark === '' && isset($linkDetails['fragment'])) {
88  $sectionMark = $linkDetails['fragment'];
89  }
90  if ($sectionMark !== '') {
91  $sectionMark = '#' . (‪MathUtility::canBeInterpretedAsInteger($sectionMark) ? 'c' : '') . $sectionMark;
92  }
93  // Overruling 'type'
94  $pageType = $linkDetails['pagetype'] ?? '';
95 
96  if (isset($linkDetails['parameters'])) {
97  $conf['additionalParams'] .= '&' . ltrim($linkDetails['parameters'], '&');
98  }
99  // MountPoints, look for closest MPvar:
100  $MPvarAcc = [];
101  if (!($tsfe->config['config']['MP_disableTypolinkClosestMPvalue'] ?? false)) {
102  $temp_MP = $this->‪getClosestMountPointValueForPage($page['uid']);
103  if ($temp_MP) {
104  $MPvarAcc['closest'] = $temp_MP;
105  }
106  }
107  // Look for overlay Mount Point:
108  $mount_info = $tsfe->sys_page->getMountPointInfo($page['uid'], $page);
109  if (is_array($mount_info) && $mount_info['overlay']) {
110  $page = $tsfe->sys_page->getPage($mount_info['mount_pid'], $disableGroupAccessCheck);
111  if (empty($page)) {
112  throw new ‪UnableToLinkException('Mount point "' . $mount_info['mount_pid'] . '" was not available, so "' . $linkText . '" was not linked.', 1490987337, null, $linkText);
113  }
114  $MPvarAcc['re-map'] = $mount_info['MPvar'];
115  }
116  // Query Params:
117  $addQueryParams = $conf['addQueryString'] ? $this->contentObjectRenderer->getQueryArguments($conf['addQueryString.'] ?? []) : '';
118  $addQueryParams .= isset($conf['additionalParams.']) ? trim((string)$this->contentObjectRenderer->stdWrap($conf['additionalParams'] ?? '', $conf['additionalParams.'])) : trim((string)($conf['additionalParams'] ?? ''));
119  if ($addQueryParams === '&' || $addQueryParams[0] !== '&') {
120  $addQueryParams = '';
121  }
122  // Mount pages are always local and never link to another domain
123  if (!empty($MPvarAcc)) {
124  // Add "&MP" var:
125  $addQueryParams .= '&MP=' . rawurlencode(implode(',', $MPvarAcc));
126  } elseif (strpos($addQueryParams, '&MP=') === false) {
127  // We do not come here if additionalParams had '&MP='. This happens when typoLink is called from
128  // menu. Mount points always work in the content of the current domain and we must not change
129  // domain if MP variables exist.
130  // If we link across domains and page is free type shortcut, we must resolve the shortcut first!
131  if ((int)$page['doktype'] === ‪PageRepository::DOKTYPE_SHORTCUT
132  && (int)$page['shortcut_mode'] === ‪PageRepository::SHORTCUT_MODE_NONE
133  ) {
134  // Save in case of broken destination or endless loop
135  $page2 = $page;
136  // Same as in RealURL, seems enough
137  $maxLoopCount = 20;
138  while ($maxLoopCount
139  && is_array($page)
140  && (int)$page['doktype'] === ‪PageRepository::DOKTYPE_SHORTCUT
141  && (int)$page['shortcut_mode'] === ‪PageRepository::SHORTCUT_MODE_NONE
142  ) {
143  $page = $tsfe->sys_page->getPage($page['shortcut'], $disableGroupAccessCheck);
144  $maxLoopCount--;
145  }
146  if (empty($page) || $maxLoopCount === 0) {
147  // We revert if shortcut is broken or maximum number of loops is exceeded (indicates endless loop)
148  $page = $page2;
149  }
150  }
151  }
152 
153  if (isset($conf['useCacheHash'])) {
154  // This option will be removed in TYPO3 v11.0.
155  trigger_error('Setting typolink.useCacheHash has no effect anymore. Remove the option in all your TypoScript code and Fluid templates.', E_USER_DEPRECATED);
156  }
157 
158  // get config.linkVars and prepend them before the actual GET parameters
159  $queryParameters = [];
160  parse_str($addQueryParams, $queryParameters);
161  if ($tsfe->linkVars) {
162  $globalQueryParameters = [];
163  parse_str($tsfe->linkVars, $globalQueryParameters);
164  $queryParameters = array_replace_recursive($globalQueryParameters, $queryParameters);
165  }
166  // Disable "?id=", for pages with no site configuration, this is added later-on anyway
167  unset($queryParameters['id']);
168 
169  // Override language property if not being set already, supporting historically 'L' and
170  // modern '_language' arguments, giving '_language' the precedence.
171  if (isset($queryParameters['_language'])) {
172  if (!isset($conf['language'])) {
173  $conf['language'] = (int)$queryParameters['_language'];
174  }
175  unset($queryParameters['_language']);
176  }
177  if (isset($queryParameters['L'])) {
178  if (!isset($conf['language'])) {
179  $conf['language'] = (int)$queryParameters['L'];
180  }
181  unset($queryParameters['L']);
182  }
183 
184  // Check if the target page has a site configuration
185  try {
186  $siteOfTargetPage = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId((int)$page['uid'], null, $queryParameters['MP'] ?? '');
187  $currentSite = $this->‪getCurrentSite();
188  } catch (‪SiteNotFoundException $e) {
189  // Usually happens in tests, as sites with configuration should be available everywhere.
190  $siteOfTargetPage = null;
191  $currentSite = null;
192  }
193 
194  // Link to a page that has a site configuration
195  if ($siteOfTargetPage !== null) {
196  try {
197  $siteLanguageOfTargetPage = $this->‪getSiteLanguageOfTargetPage($siteOfTargetPage, (string)($conf['language'] ?? 'current'));
198  } catch (‪UnableToLinkException $e) {
199  throw new ‪UnableToLinkException($e->getMessage(), $e->getCode(), $e, $linkText);
200  }
201  $languageAspect = ‪LanguageAspectFactory::createFromSiteLanguage($siteLanguageOfTargetPage);
202 
203  // Now overlay the page in the target language, in order to have valid title attributes etc.
204  if ($siteLanguageOfTargetPage->getLanguageId() > 0) {
205  $context = clone GeneralUtility::makeInstance(Context::class);
206  $context->setAspect('language', $languageAspect);
207  $pageRepository = GeneralUtility::makeInstance(PageRepository::class, $context);
208  $page = $pageRepository->getPageOverlay($page);
209  }
210  // Check if the target page can be access depending on l18n_cfg
211  if (!$tsfe->sys_page->isPageSuitableForLanguage($page, $languageAspect)) {
212  if ($siteLanguageOfTargetPage->getLanguageId() === 0 && GeneralUtility::hideIfDefaultLanguage($page['l18n_cfg'])) {
213  throw new ‪UnableToLinkException('Default language of page "' . $linkDetails['typoLinkParameter'] . '" is hidden, so "' . $linkText . '" was not linked.', 1551621985, null, $linkText);
214  }
215  // If the requested language is not the default language and the page has no overlay for this language
216  // generating a link would cause a 404 error when using this like if one of those conditions apply:
217  // - The page is set to be hidden if it is not translated (evaluated in TSFE)
218  // - The site configuration has a "strict" fallback set (evaluated in the Router - very early)
219  if ($siteLanguageOfTargetPage->getLanguageId() > 0 && !isset($page['_PAGES_OVERLAY']) && (GeneralUtility::hideIfNotTranslated($page['l18n_cfg']) || $siteLanguageOfTargetPage->getFallbackType() === 'strict')) {
220  throw new ‪UnableToLinkException('Fallback to default language of page "' . $linkDetails['typoLinkParameter'] . '" is disabled, so "' . $linkText . '" was not linked.', 1551621996, null, $linkText);
221  }
222  }
223 
224  if ($pageType) {
225  $queryParameters['type'] = (int)$pageType;
226  }
227 
228  $treatAsExternalLink = true;
229  // External links are resolved via calling Typolink again (could be anything, really)
230  if ((int)$page['doktype'] === ‪PageRepository::DOKTYPE_LINK) {
231  $conf['parameter'] = $page['url'];
232  unset($conf['parameter.']);
233  $this->contentObjectRenderer->typoLink($linkText, $conf);
234  $target = $this->contentObjectRenderer->lastTypoLinkTarget;
235  $url = $this->contentObjectRenderer->lastTypoLinkUrl;
236  if (empty($url)) {
237  throw new ‪UnableToLinkException('Link to external page "' . $page['uid'] . '" does not have a proper target URL, so "' . $linkText . '" was not linked.', 1551621999, null, $linkText);
238  }
239  } else {
240  // Generate the URL
241  $url = $this->‪generateUrlForPageWithSiteConfiguration($page, $siteOfTargetPage, $queryParameters, $sectionMark, $conf);
242  // no scheme => always not external
243  if (!$url->getScheme() || !$url->getHost()) {
244  $treatAsExternalLink = false;
245  } else {
246  // URL has a scheme, possibly because someone requested a full URL. So now lets check if the URL
247  // is on the same site pagetree. If this is the case, we'll treat it as internal
248  // @todo: currently this does not check if the target page is a mounted page in a different site,
249  // so it is treating this as an absolute URL, which is wrong
250  if ($currentSite instanceof ‪Site && $currentSite->‪getRootPageId() === $siteOfTargetPage->getRootPageId()) {
251  $treatAsExternalLink = false;
252  }
253  }
254  $url = (string)$url;
255  }
256  if ($treatAsExternalLink) {
257  $target = $target ?: $this->‪resolveTargetAttribute($conf, 'extTarget', false, $tsfe->extTarget);
258  } else {
259  $target = (isset($page['target']) && trim($page['target'])) ? $page['target'] : $target;
260  if (empty($target)) {
261  $target = $this->‪resolveTargetAttribute($conf, 'target', true, $tsfe->intTarget);
262  }
263  }
264  } else {
265  throw new ‪UnableToLinkException('Could not link to page with ID: ' . $page['uid'], 1546887172, null, $linkText);
266  }
267 
268  // If link is to an access restricted page which should be redirected, then find new URL:
269  if (empty($conf['linkAccessRestrictedPages'])
270  && ($tsfe->config['config']['typolinkLinkAccessRestrictedPages'] ?? false)
271  && $tsfe->config['config']['typolinkLinkAccessRestrictedPages'] !== 'NONE'
272  && !$tsfe->checkPageGroupAccess($page)
273  ) {
274  $thePage = $tsfe->sys_page->getPage($tsfe->config['config']['typolinkLinkAccessRestrictedPages']);
275  $addParams = str_replace(
276  [
277  '###RETURN_URL###',
278  '###PAGE_ID###'
279  ],
280  [
281  rawurlencode($url),
282  $page['uid']
283  ],
284  $tsfe->config['config']['typolinkLinkAccessRestrictedPages_addParams']
285  );
286  $url = $this->contentObjectRenderer->getTypoLink_URL($thePage['uid'] . ($pageType ? ',' . $pageType : ''), $addParams, $target);
287  $url = $this->‪forceAbsoluteUrl($url, $conf);
288  }
289 
290  // Setting title if blank value to link
291  $linkText = $this->‪parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $page['title']);
292  return [$url, $linkText, $target];
293  }
294 
305  protected function ‪resolvePage(array &$linkDetails, array &$configuration, bool $disableGroupAccessCheck): array
306  {
307  $pageRepository = $this->‪buildPageRepository();
308  // Looking up the page record to verify its existence
309  // This is used when a page to a translated page is executed directly.
310  $page = $pageRepository->getPage($linkDetails['pageuid'], $disableGroupAccessCheck);
311 
312  if (empty($page) || !is_array($page)) {
313  return [];
314  }
315 
316  $languageField = ‪$GLOBALS['TCA']['pages']['ctrl']['languageField'] ?? null;
317  $languageParentField = ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] ?? null;
318  $language = (int)($page[$languageField] ?? 0);
319 
320  // The page that should be linked is actually a default-language page, nothing to do here.
321  if ($language === 0 || empty($page[$languageParentField])) {
322  return $page;
323  }
324 
325  // Let's fetch the default-language page now
326  $languageParentPage = $pageRepository->getPage(
327  $page[$languageParentField],
328  $disableGroupAccessCheck
329  );
330  if (empty($languageParentPage)) {
331  return $page;
332  }
333 
334  // Set the "pageuid" to the default-language page ID.
335  $linkDetails['pageuid'] = (int)$languageParentPage['uid'];
336  $configuration['language'] = $language;
337  return $languageParentPage;
338  }
339 
348  protected function ‪getSiteLanguageOfTargetPage(‪Site $siteOfTargetPage, string $targetLanguageId): ‪SiteLanguage
349  {
350  $currentSite = $this->‪getCurrentSite();
351  $currentSiteLanguage = $this->‪getCurrentSiteLanguage();
352  // Happens when currently on a pseudo-site configuration
353  // We assume to use the default language then
354  if ($currentSite && !($currentSiteLanguage instanceof ‪SiteLanguage)) {
355  $currentSiteLanguage = $currentSite->getDefaultLanguage();
356  }
357 
358  if ($targetLanguageId === 'current') {
359  $targetLanguageId = $currentSiteLanguage ? $currentSiteLanguage->getLanguageId() : 0;
360  } else {
361  $targetLanguageId = (int)$targetLanguageId;
362  }
363  try {
364  $siteLanguageOfTargetPage = $siteOfTargetPage->‪getLanguageById($targetLanguageId);
365  } catch (\InvalidArgumentException $e) {
366  throw new ‪UnableToLinkException('The target page does not have a language with ID ' . $targetLanguageId . ' configured in its site configuration.', 1535477406);
367  }
368  return $siteLanguageOfTargetPage;
369  }
370 
382  protected function ‪generateUrlForPageWithSiteConfiguration(array $page, ‪Site $siteOfTargetPage, array $queryParameters, string $fragment, array $conf): UriInterface
383  {
384  $currentSite = $this->‪getCurrentSite();
385  $currentSiteLanguage = $this->‪getCurrentSiteLanguage();
386  // Happens when currently on a pseudo-site configuration
387  // We assume to use the default language then
388  if ($currentSite && !($currentSiteLanguage instanceof ‪SiteLanguage)) {
389  $currentSiteLanguage = $currentSite->getDefaultLanguage();
390  }
391 
392  $siteLanguageOfTargetPage = $this->‪getSiteLanguageOfTargetPage($siteOfTargetPage, (string)($conf['language'] ?? 'current'));
393 
394  // By default, it is assumed to ab an internal link or current domain's linking scheme should be used
395  // Use the config option to override this.
396  $useAbsoluteUrl = $conf['forceAbsoluteUrl'] ?? false;
397  // Check if the current page equal to the site of the target page, now only set the absolute URL
398  // Always generate absolute URLs if no current site is set
399  if (
400  !$currentSite
401  || $currentSite->getRootPageId() !== $siteOfTargetPage->‪getRootPageId()
402  || $siteLanguageOfTargetPage->getBase()->getHost() !== $currentSiteLanguage->getBase()->getHost()) {
403  $useAbsoluteUrl = true;
404  }
405 
406  $targetPageId = (int)($page['l10n_parent'] > 0 ? $page['l10n_parent'] : $page['uid']);
407  $queryParameters['_language'] = $siteLanguageOfTargetPage;
408 
409  if ($conf['no_cache'] ?? false) {
410  $queryParameters['no_cache'] = 1;
411  }
412 
413  if ($fragment
414  && $useAbsoluteUrl === false
415  && $currentSiteLanguage === $siteLanguageOfTargetPage
416  && $targetPageId === (int)‪$GLOBALS['TSFE']->id
417  && (empty($conf['addQueryString']) || !isset($conf['addQueryString.']))
418  && !‪$GLOBALS['TSFE']->config['config']['baseURL']
419  && count($queryParameters) === 1 // _language is always set
420  ) {
421  $uri = (new ‪Uri())->withFragment($fragment);
422  } else {
423  try {
424  $uri = $siteOfTargetPage->‪getRouter()->‪generateUri(
425  $targetPageId,
426  $queryParameters,
427  $fragment,
429  );
430  } catch (‪InvalidRouteArgumentsException $e) {
431  throw new ‪UnableToLinkException('The target page could not be linked. Error: ' . $e->getMessage(), 1535472406);
432  }
433  // Override scheme if absoluteUrl is set, but only if the site defines a domain/host. Fall back to site scheme and else https.
434  if ($useAbsoluteUrl && $uri->getHost()) {
435  $scheme = $conf['forceAbsoluteUrl.']['scheme'] ?? false;
436  if (!$scheme) {
437  $scheme = $uri->getScheme() ?: 'https';
438  }
439  $uri = $uri->withScheme($scheme);
440  }
441  }
442 
443  return $uri;
444  }
445 
452  protected function ‪getClosestMountPointValueForPage($pageId)
453  {
454  $tsfe = $this->‪getTypoScriptFrontendController();
455  if (empty(‪$GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) || !$tsfe->MP) {
456  return '';
457  }
458  // Same page as current.
459  if ((int)$tsfe->id === (int)$pageId) {
460  return $tsfe->MP;
461  }
462 
463  // Find closest mount point
464  // Gets rootline of linked-to page
465  try {
466  $tCR_rootline = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get();
467  } catch (‪RootLineException $e) {
468  $tCR_rootline = [];
469  }
470  $inverseTmplRootline = array_reverse($tsfe->tmpl->rootLine);
471  $rl_mpArray = [];
472  $startMPaccu = false;
473  // Traverse root line of link uid and inside of that the REAL root line of current position.
474  foreach ($tCR_rootline as $tCR_data) {
475  foreach ($inverseTmplRootline as $rlKey => $invTmplRLRec) {
476  // Force accumulating when in overlay mode: Links to this page have to stay within the current branch
477  if ($invTmplRLRec['_MOUNT_OL'] && (int)$tCR_data['uid'] === (int)$invTmplRLRec['uid']) {
478  $startMPaccu = true;
479  }
480  // Accumulate MP data:
481  if ($startMPaccu && $invTmplRLRec['_MP_PARAM']) {
482  $rl_mpArray[] = $invTmplRLRec['_MP_PARAM'];
483  }
484  // If two PIDs matches and this is NOT the site root, start accumulation of MP data (on the next level):
485  // (The check for site root is done so links to branches outside the site but sharing the site roots PID
486  // is NOT detected as within the branch!)
487  if ((int)$tCR_data['pid'] === (int)$invTmplRLRec['pid'] && count($inverseTmplRootline) !== $rlKey + 1) {
488  $startMPaccu = true;
489  }
490  }
491  if ($startMPaccu) {
492  // Good enough...
493  break;
494  }
495  }
496  return !empty($rl_mpArray) ? implode(',', array_reverse($rl_mpArray)) : '';
497  }
498 
508  public function ‪getMountPointParameterFromRootPointMaps(int $pageId)
509  {
510  // Create map if not found already
511  $config = $this->‪getTypoScriptFrontendController()->config;
512  $mountPointMap = $this->‪initializeMountPointMap(
513  !empty($config['config']['MP_defaults']) ? $config['config']['MP_defaults'] : '',
514  !empty($config['config']['MP_mapRootPoints']) ? $config['config']['MP_mapRootPoints'] : ''
515  );
516 
517  // Finding MP var for Page ID:
518  if (!empty($mountPointMap[$pageId])) {
519  return implode(',', $mountPointMap[$pageId]);
520  }
521  return '';
522  }
523 
531  protected function ‪initializeMountPointMap(string $defaultMountPoints = '', string $mapRootPointList = ''): array
532  {
533  $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
534  $mountPointMap = $runtimeCache->get('pageLinkBuilderMountPointMap') ?: [];
535  if (!empty($mountPointMap) || (empty($mapRootPointList) && empty($defaultMountPoints))) {
536  return $mountPointMap;
537  }
538  if ($defaultMountPoints) {
539  $defaultMountPoints = ‪GeneralUtility::trimExplode('|', $defaultMountPoints, true);
540  foreach ($defaultMountPoints as $temp_p) {
541  [$temp_idP, $temp_MPp] = explode(':', $temp_p, 2);
542  $temp_ids = ‪GeneralUtility::intExplode(',', $temp_idP);
543  foreach ($temp_ids as $temp_id) {
544  $mountPointMap[$temp_id] = trim($temp_MPp);
545  }
546  }
547  }
548 
549  $rootPoints = ‪GeneralUtility::trimExplode(',', strtolower($mapRootPointList), true);
550  // Traverse rootpoints
551  foreach ($rootPoints as $p) {
552  $initMParray = [];
553  if ($p === 'root') {
554  $rootPage = $this->‪getTypoScriptFrontendController()->tmpl->rootLine[0];
555  $p = $rootPage['uid'];
556  if ($rootPage['_MOUNT_OL'] && $rootPage['_MP_PARAM']) {
557  $initMParray[] = $rootPage['_MP_PARAM'];
558  }
559  }
560  $this->‪populateMountPointMapForPageRecursively($mountPointMap, (int)$p, $initMParray);
561  }
562  $runtimeCache->set('pageLinkBuilderMountPointMap', $mountPointMap);
563  return $mountPointMap;
564  }
565 
576  protected function ‪populateMountPointMapForPageRecursively(array &$mountPointMap, int $id, $MP_array = [], $level = 0)
577  {
578  if ($id <= 0) {
579  return;
580  }
581  // First level, check id
582  if (!$level) {
583  // Find mount point if any:
584  $mount_info = $this->‪getTypoScriptFrontendController()->sys_page->getMountPointInfo($id);
585  // Overlay mode:
586  if (is_array($mount_info) && $mount_info['overlay']) {
587  $MP_array[] = $mount_info['MPvar'];
588  $id = $mount_info['mount_pid'];
589  }
590  // Set mapping information for this level:
591  $mountPointMap[$id] = $MP_array;
592  // Normal mode:
593  if (is_array($mount_info) && !$mount_info['overlay']) {
594  $MP_array[] = $mount_info['MPvar'];
595  $id = $mount_info['mount_pid'];
596  }
597  }
598  if ($id && $level < 20) {
599  $nextLevelAcc = [];
600  // Select and traverse current level pages:
601  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
602  $queryBuilder->getRestrictions()
603  ->removeAll()
604  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
605  $queryResult = $queryBuilder
606  ->select('uid', 'pid', 'doktype', 'mount_pid', 'mount_pid_ol', 't3ver_state', 'l10n_parent')
607  ->from('pages')
608  ->where(
609  $queryBuilder->expr()->eq(
610  'pid',
611  $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
612  ),
613  $queryBuilder->expr()->neq(
614  'doktype',
615  $queryBuilder->createNamedParameter(‪PageRepository::DOKTYPE_RECYCLER, \PDO::PARAM_INT)
616  ),
617  $queryBuilder->expr()->neq(
618  'doktype',
619  $queryBuilder->createNamedParameter(‪PageRepository::DOKTYPE_BE_USER_SECTION, \PDO::PARAM_INT)
620  )
621  )->execute();
622  while ($row = $queryResult->fetch()) {
623  // Find mount point if any:
624  $next_id = (int)$row['uid'];
625  $next_MP_array = $MP_array;
626  $mount_info = $this->‪getTypoScriptFrontendController()->sys_page->getMountPointInfo($next_id, $row);
627  // Overlay mode:
628  if (is_array($mount_info) && $mount_info['overlay']) {
629  $next_MP_array[] = $mount_info['MPvar'];
630  $next_id = (int)$mount_info['mount_pid'];
631  }
632  if (!isset($mountPointMap[$next_id])) {
633  // Set mapping information for this level:
634  $mountPointMap[$next_id] = $next_MP_array;
635  // Normal mode:
636  if (is_array($mount_info) && !$mount_info['overlay']) {
637  $next_MP_array[] = $mount_info['MPvar'];
638  $next_id = (int)$mount_info['mount_pid'];
639  }
640  // Register recursive call
641  // (have to do it this way since ALL of the current level should be registered BEFORE the sublevel at any time)
642  $nextLevelAcc[] = [$next_id, $next_MP_array];
643  }
644  }
645  // Call recursively, if any:
646  foreach ($nextLevelAcc as $pSet) {
647  $this->‪populateMountPointMapForPageRecursively($mountPointMap, $pSet[0], $pSet[1], $level + 1);
648  }
649  }
650  }
651 
658  protected function ‪getCurrentSite(): ?‪SiteInterface
659  {
660  if (‪$GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) {
661  return ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('site', null);
662  }
663  if (‪MathUtility::canBeInterpretedAsInteger(‪$GLOBALS['TSFE']->id) && ‪$GLOBALS['TSFE']->id > 0) {
664  ‪$finder = GeneralUtility::makeInstance(SiteFinder::class);
665  try {
666  $site = ‪$finder->getSiteByPageId((int)‪$GLOBALS['TSFE']->id);
667  } catch (‪SiteNotFoundException $e) {
668  $site = null;
669  }
670  return $site;
671  }
672  return null;
673  }
674 
682  {
683  if (‪$GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) {
684  return ‪$GLOBALS['TYPO3_REQUEST']->getAttribute('language', null);
685  }
686  return null;
687  }
688 
696  {
697  // clone global context object (singleton)
698  $context = clone GeneralUtility::makeInstance(Context::class);
699  $context->setAspect(
700  'language',
701  GeneralUtility::makeInstance(LanguageAspect::class)
702  );
703  $pageRepository = GeneralUtility::makeInstance(
704  PageRepository::class,
705  $context
706  );
707  return $pageRepository;
708  }
709 }
‪TYPO3\CMS\Core\Site\Entity\SiteInterface
Definition: SiteInterface.php:26
‪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\Site\Entity\Site\getLanguageById
‪SiteLanguage getLanguageById(int $languageId)
Definition: Site.php:235
‪TYPO3\CMS\Core\Site\Entity\Site\getRouter
‪RouterInterface getRouter(Context $context=null)
Definition: Site.php:365
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_SHORTCUT
‪const DOKTYPE_SHORTCUT
Definition: PageRepository.php:105
‪TYPO3\CMS\Core\Utility\RootlineUtility
Definition: RootlineUtility.php:39
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_LINK
‪const DOKTYPE_LINK
Definition: PageRepository.php:104
‪TYPO3\CMS\Core\Exception\SiteNotFoundException
Definition: SiteNotFoundException.php:26
‪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\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:40
‪TYPO3\CMS\Core\Site\Entity\SiteLanguage
Definition: SiteLanguage.php:26
‪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:106
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:35
‪TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException
Definition: InvalidRouteArgumentsException.php:26
‪TYPO3\CMS\Core\Context\LanguageAspect
Definition: LanguageAspect.php:57
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪TYPO3\CMS\Core\Site\Entity\Site\getRootPageId
‪int getRootPageId()
Definition: Site.php:197
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\SHORTCUT_MODE_NONE
‪const SHORTCUT_MODE_NONE
Definition: PageRepository.php:115
‪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:988
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Core\Exception\Page\RootLineException
Definition: RootLineException.php:25
‪TYPO3\CMS\Core\Domain\Repository\PageRepository
Definition: PageRepository.php:52
‪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:46
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_RECYCLER
‪const DOKTYPE_RECYCLER
Definition: PageRepository.php:110