| Total Complexity | 151 |
| Total Lines | 644 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like PageLinkBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use PageLinkBuilder, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 44 | class PageLinkBuilder extends AbstractTypolinkBuilder |
||
| 45 | { |
||
| 46 | /** |
||
| 47 | * @inheritdoc |
||
| 48 | * @throws UnableToLinkException |
||
| 49 | */ |
||
| 50 | public function build(array &$linkDetails, string $linkText, string $target, array $conf): array |
||
| 51 | { |
||
| 52 | $tsfe = $this->getTypoScriptFrontendController(); |
||
| 53 | if (empty($linkDetails['pageuid']) || $linkDetails['pageuid'] === 'current') { |
||
| 54 | // If no id is given |
||
| 55 | $linkDetails['pageuid'] = $tsfe->id; |
||
| 56 | } |
||
| 57 | |||
| 58 | // Link to page even if access is missing? |
||
| 59 | if (isset($conf['linkAccessRestrictedPages'])) { |
||
| 60 | $disableGroupAccessCheck = (bool)$conf['linkAccessRestrictedPages']; |
||
| 61 | } else { |
||
| 62 | $disableGroupAccessCheck = (bool)$tsfe->config['config']['typolinkLinkAccessRestrictedPages']; |
||
| 63 | } |
||
| 64 | |||
| 65 | // Looking up the page record to verify its existence: |
||
| 66 | $page = $this->resolvePage($linkDetails, $conf, $disableGroupAccessCheck); |
||
| 67 | |||
| 68 | if (empty($page)) { |
||
| 69 | throw new UnableToLinkException('Page id "' . $linkDetails['typoLinkParameter'] . '" was not found, so "' . $linkText . '" was not linked.', 1490987336, null, $linkText); |
||
| 70 | } |
||
| 71 | |||
| 72 | foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typolinkProcessing']['typolinkModifyParameterForPageLinks'] ?? [] as $classData) { |
||
| 73 | $hookObject = GeneralUtility::makeInstance($classData); |
||
| 74 | if (!$hookObject instanceof TypolinkModifyLinkConfigForPageLinksHookInterface) { |
||
| 75 | throw new \UnexpectedValueException('$hookObject must implement interface ' . TypolinkModifyLinkConfigForPageLinksHookInterface::class, 1483114905); |
||
| 76 | } |
||
| 77 | /** @var TypolinkModifyLinkConfigForPageLinksHookInterface $hookObject */ |
||
| 78 | $conf = $hookObject->modifyPageLinkConfiguration($conf, $linkDetails, $page); |
||
| 79 | } |
||
| 80 | if ($conf['no_cache.']) { |
||
| 81 | $conf['no_cache'] = (string)$this->contentObjectRenderer->stdWrap($conf['no_cache'], $conf['no_cache.']); |
||
| 82 | } |
||
| 83 | |||
| 84 | $sectionMark = trim(isset($conf['section.']) ? (string)$this->contentObjectRenderer->stdWrap($conf['section'], $conf['section.']) : (string)$conf['section']); |
||
| 85 | if ($sectionMark === '' && isset($linkDetails['fragment'])) { |
||
| 86 | $sectionMark = $linkDetails['fragment']; |
||
| 87 | } |
||
| 88 | if ($sectionMark !== '') { |
||
| 89 | $sectionMark = '#' . (MathUtility::canBeInterpretedAsInteger($sectionMark) ? 'c' : '') . $sectionMark; |
||
| 90 | } |
||
| 91 | // Overruling 'type' |
||
| 92 | $pageType = $linkDetails['pagetype'] ?? ''; |
||
| 93 | |||
| 94 | if (isset($linkDetails['parameters'])) { |
||
| 95 | $conf['additionalParams'] .= '&' . ltrim($linkDetails['parameters'], '&'); |
||
| 96 | } |
||
| 97 | // MountPoints, look for closest MPvar: |
||
| 98 | $MPvarAcc = []; |
||
| 99 | if (!$tsfe->config['config']['MP_disableTypolinkClosestMPvalue']) { |
||
| 100 | $temp_MP = $this->getClosestMountPointValueForPage($page['uid']); |
||
| 101 | if ($temp_MP) { |
||
| 102 | $MPvarAcc['closest'] = $temp_MP; |
||
| 103 | } |
||
| 104 | } |
||
| 105 | // Look for overlay Mount Point: |
||
| 106 | $mount_info = $tsfe->sys_page->getMountPointInfo($page['uid'], $page); |
||
| 107 | if (is_array($mount_info) && $mount_info['overlay']) { |
||
| 108 | $page = $tsfe->sys_page->getPage($mount_info['mount_pid'], $disableGroupAccessCheck); |
||
| 109 | if (empty($page)) { |
||
| 110 | throw new UnableToLinkException('Mount point "' . $mount_info['mount_pid'] . '" was not available, so "' . $linkText . '" was not linked.', 1490987337, null, $linkText); |
||
| 111 | } |
||
| 112 | $MPvarAcc['re-map'] = $mount_info['MPvar']; |
||
| 113 | } |
||
| 114 | // Query Params: |
||
| 115 | $addQueryParams = $conf['addQueryString'] ? $this->contentObjectRenderer->getQueryArguments($conf['addQueryString.']) : ''; |
||
| 116 | $addQueryParams .= isset($conf['additionalParams.']) ? trim((string)$this->contentObjectRenderer->stdWrap($conf['additionalParams'], $conf['additionalParams.'])) : trim((string)$conf['additionalParams']); |
||
| 117 | if ($addQueryParams === '&' || $addQueryParams[0] !== '&') { |
||
| 118 | $addQueryParams = ''; |
||
| 119 | } |
||
| 120 | // Mount pages are always local and never link to another domain |
||
| 121 | if (!empty($MPvarAcc)) { |
||
| 122 | // Add "&MP" var: |
||
| 123 | $addQueryParams .= '&MP=' . rawurlencode(implode(',', $MPvarAcc)); |
||
| 124 | } elseif (strpos($addQueryParams, '&MP=') === false) { |
||
| 125 | // We do not come here if additionalParams had '&MP='. This happens when typoLink is called from |
||
| 126 | // menu. Mount points always work in the content of the current domain and we must not change |
||
| 127 | // domain if MP variables exist. |
||
| 128 | // If we link across domains and page is free type shortcut, we must resolve the shortcut first! |
||
| 129 | if ((int)$page['doktype'] === PageRepository::DOKTYPE_SHORTCUT |
||
| 130 | && (int)$page['shortcut_mode'] === PageRepository::SHORTCUT_MODE_NONE |
||
| 131 | ) { |
||
| 132 | // Save in case of broken destination or endless loop |
||
| 133 | $page2 = $page; |
||
| 134 | // Same as in RealURL, seems enough |
||
| 135 | $maxLoopCount = 20; |
||
| 136 | while ($maxLoopCount |
||
| 137 | && is_array($page) |
||
| 138 | && (int)$page['doktype'] === PageRepository::DOKTYPE_SHORTCUT |
||
| 139 | && (int)$page['shortcut_mode'] === PageRepository::SHORTCUT_MODE_NONE |
||
| 140 | ) { |
||
| 141 | $page = $tsfe->sys_page->getPage($page['shortcut'], $disableGroupAccessCheck); |
||
| 142 | $maxLoopCount--; |
||
| 143 | } |
||
| 144 | if (empty($page) || $maxLoopCount === 0) { |
||
| 145 | // We revert if shortcut is broken or maximum number of loops is exceeded (indicates endless loop) |
||
| 146 | $page = $page2; |
||
| 147 | } |
||
| 148 | } |
||
| 149 | } |
||
| 150 | |||
| 151 | if (isset($conf['useCacheHash'])) { |
||
| 152 | // This option will be removed in TYPO3 v11.0. |
||
| 153 | trigger_error('Setting typolink.useCacheHash has no effect anymore. Remove the option in all your TypoScript code and Fluid templates.', E_USER_DEPRECATED); |
||
| 154 | } |
||
| 155 | |||
| 156 | // get config.linkVars and prepend them before the actual GET parameters |
||
| 157 | $queryParameters = []; |
||
| 158 | parse_str($addQueryParams, $queryParameters); |
||
| 159 | if ($tsfe->linkVars) { |
||
| 160 | $globalQueryParameters = []; |
||
| 161 | parse_str($tsfe->linkVars, $globalQueryParameters); |
||
| 162 | $queryParameters = array_replace_recursive($globalQueryParameters, $queryParameters); |
||
| 163 | } |
||
| 164 | // Disable "?id=", for pages with no site configuration, this is added later-on anyway |
||
| 165 | unset($queryParameters['id']); |
||
| 166 | |||
| 167 | // Override language property if not being set already |
||
| 168 | if (isset($queryParameters['L']) && !isset($conf['language'])) { |
||
| 169 | $conf['language'] = (int)$queryParameters['L']; |
||
| 170 | unset($queryParameters['L']); |
||
| 171 | } |
||
| 172 | |||
| 173 | // Check if the target page has a site configuration |
||
| 174 | try { |
||
| 175 | $siteOfTargetPage = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId((int)$page['uid'], null, $queryParameters['MP'] ?? ''); |
||
| 176 | $currentSite = $this->getCurrentSite(); |
||
| 177 | } catch (SiteNotFoundException $e) { |
||
| 178 | // Usually happens in tests, as sites with configuration should be available everywhere. |
||
| 179 | $siteOfTargetPage = null; |
||
| 180 | $currentSite = null; |
||
| 181 | } |
||
| 182 | |||
| 183 | // Link to a page that has a site configuration |
||
| 184 | if ($siteOfTargetPage !== null) { |
||
| 185 | $siteLanguageOfTargetPage = $this->getSiteLanguageOfTargetPage($siteOfTargetPage, (string)($conf['language'] ?? 'current')); |
||
| 186 | $languageAspect = LanguageAspectFactory::createFromSiteLanguage($siteLanguageOfTargetPage); |
||
| 187 | |||
| 188 | // Now overlay the page in the target language, in order to have valid title attributes etc. |
||
| 189 | if ($siteLanguageOfTargetPage->getLanguageId() > 0) { |
||
| 190 | $context = clone GeneralUtility::makeInstance(Context::class); |
||
| 191 | $context->setAspect('language', $languageAspect); |
||
| 192 | $pageRepository = GeneralUtility::makeInstance(PageRepository::class, $context); |
||
| 193 | $page = $pageRepository->getPageOverlay($page); |
||
| 194 | } |
||
| 195 | // Check if the target page can be access depending on l18n_cfg |
||
| 196 | if (!$tsfe->sys_page->isPageSuitableForLanguage($page, $languageAspect)) { |
||
| 197 | $languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField'] ?? null; |
||
| 198 | $languageOfPageRecord = (int)($page[$languageField] ?? 0); |
||
| 199 | if ($languageOfPageRecord === 0 && GeneralUtility::hideIfDefaultLanguage($page['l18n_cfg'])) { |
||
| 200 | throw new UnableToLinkException('Default language of page "' . $linkDetails['typoLinkParameter'] . '" is hidden, so "' . $linkText . '" was not linked.', 1551621985, null, $linkText); |
||
| 201 | } |
||
| 202 | if ($languageOfPageRecord > 0 && !isset($page['_PAGES_OVERLAY']) && GeneralUtility::hideIfNotTranslated($page['l18n_cfg'])) { |
||
| 203 | throw new UnableToLinkException('Fallback to default language of page "' . $linkDetails['typoLinkParameter'] . '" is disabled, so "' . $linkText . '" was not linked.', 1551621996, null, $linkText); |
||
| 204 | } |
||
| 205 | } |
||
| 206 | |||
| 207 | if ($pageType) { |
||
| 208 | $queryParameters['type'] = (int)$pageType; |
||
| 209 | } |
||
| 210 | |||
| 211 | $treatAsExternalLink = true; |
||
| 212 | // External links are resolved via calling Typolink again (could be anything, really) |
||
| 213 | if ((int)$page['doktype'] === PageRepository::DOKTYPE_LINK) { |
||
| 214 | $conf['parameter'] = $page['url']; |
||
| 215 | unset($conf['parameter.']); |
||
| 216 | $this->contentObjectRenderer->typoLink($linkText, $conf); |
||
| 217 | $target = $this->contentObjectRenderer->lastTypoLinkTarget; |
||
| 218 | $url = $this->contentObjectRenderer->lastTypoLinkUrl; |
||
| 219 | if (empty($url)) { |
||
| 220 | throw new UnableToLinkException('Link to external page "' . $page['uid'] . '" does not have a proper target URL, so "' . $linkText . '" was not linked.', 1551621999, null, $linkText); |
||
| 221 | } |
||
| 222 | } else { |
||
| 223 | // Generate the URL |
||
| 224 | $url = $this->generateUrlForPageWithSiteConfiguration($page, $siteOfTargetPage, $queryParameters, $sectionMark, $conf); |
||
| 225 | // no scheme => always not external |
||
| 226 | if (!$url->getScheme() || !$url->getHost()) { |
||
| 227 | $treatAsExternalLink = false; |
||
| 228 | } else { |
||
| 229 | // URL has a scheme, possibly because someone requested a full URL. So now lets check if the URL |
||
| 230 | // is on the same site pagetree. If this is the case, we'll treat it as internal |
||
| 231 | // @todo: currently this does not check if the target page is a mounted page in a different site, |
||
| 232 | // so it is treating this as an absolute URL, which is wrong |
||
| 233 | if ($currentSite instanceof Site && $currentSite->getRootPageId() === $siteOfTargetPage->getRootPageId()) { |
||
| 234 | $treatAsExternalLink = false; |
||
| 235 | } |
||
| 236 | } |
||
| 237 | $url = (string)$url; |
||
| 238 | } |
||
| 239 | if ($treatAsExternalLink) { |
||
| 240 | $target = $target ?: $this->resolveTargetAttribute($conf, 'extTarget', false, $tsfe->extTarget); |
||
| 241 | } else { |
||
| 242 | $target = (isset($page['target']) && trim($page['target'])) ? $page['target'] : $target; |
||
| 243 | if (empty($target)) { |
||
| 244 | $target = $this->resolveTargetAttribute($conf, 'target', true, $tsfe->intTarget); |
||
| 245 | } |
||
| 246 | } |
||
| 247 | } else { |
||
| 248 | throw new UnableToLinkException('Could not link to page with ID: ' . $page['uid'], 1546887172, null, $linkText); |
||
| 249 | } |
||
| 250 | |||
| 251 | // If link is to an access restricted page which should be redirected, then find new URL: |
||
| 252 | if (empty($conf['linkAccessRestrictedPages']) |
||
| 253 | && $tsfe->config['config']['typolinkLinkAccessRestrictedPages'] |
||
| 254 | && $tsfe->config['config']['typolinkLinkAccessRestrictedPages'] !== 'NONE' |
||
| 255 | && !$tsfe->checkPageGroupAccess($page) |
||
| 256 | ) { |
||
| 257 | $thePage = $tsfe->sys_page->getPage($tsfe->config['config']['typolinkLinkAccessRestrictedPages']); |
||
| 258 | $addParams = str_replace( |
||
| 259 | [ |
||
| 260 | '###RETURN_URL###', |
||
| 261 | '###PAGE_ID###' |
||
| 262 | ], |
||
| 263 | [ |
||
| 264 | rawurlencode($url), |
||
| 265 | $page['uid'] |
||
| 266 | ], |
||
| 267 | $tsfe->config['config']['typolinkLinkAccessRestrictedPages_addParams'] |
||
| 268 | ); |
||
| 269 | $url = $this->contentObjectRenderer->getTypoLink_URL($thePage['uid'] . ($pageType ? ',' . $pageType : ''), $addParams, $target); |
||
| 270 | $url = $this->forceAbsoluteUrl($url, $conf); |
||
| 271 | } |
||
| 272 | |||
| 273 | // Setting title if blank value to link |
||
| 274 | $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $page['title']); |
||
| 275 | return [$url, $linkText, $target]; |
||
| 276 | } |
||
| 277 | |||
| 278 | /** |
||
| 279 | * Resolves page and if a translated page was found, resolves that to it |
||
| 280 | * language parent, adjusts `$linkDetails['pageuid']` (for hook processing) |
||
| 281 | * and modifies `$configuration['language']` (for language URL generation). |
||
| 282 | * |
||
| 283 | * @param array $linkDetails |
||
| 284 | * @param array $configuration |
||
| 285 | * @param bool $disableGroupAccessCheck |
||
| 286 | * @return array |
||
| 287 | */ |
||
| 288 | protected function resolvePage(array &$linkDetails, array &$configuration, bool $disableGroupAccessCheck): array |
||
| 321 | } |
||
| 322 | |||
| 323 | /** |
||
| 324 | * Fetches the requested language of a site that the link should be built for |
||
| 325 | * |
||
| 326 | * @param Site $siteOfTargetPage |
||
| 327 | * @param string $targetLanguageId "current" or the languageId |
||
| 328 | * @return SiteLanguage |
||
| 329 | * @throws UnableToLinkException |
||
| 330 | */ |
||
| 331 | protected function getSiteLanguageOfTargetPage(Site $siteOfTargetPage, string $targetLanguageId): SiteLanguage |
||
| 332 | { |
||
| 333 | $currentSite = $this->getCurrentSite(); |
||
| 334 | $currentSiteLanguage = $this->getCurrentSiteLanguage(); |
||
| 335 | // Happens when currently on a pseudo-site configuration |
||
| 336 | // We assume to use the default language then |
||
| 337 | if ($currentSite && !($currentSiteLanguage instanceof SiteLanguage)) { |
||
| 338 | $currentSiteLanguage = $currentSite->getDefaultLanguage(); |
||
| 339 | } |
||
| 340 | |||
| 341 | if ($targetLanguageId === 'current') { |
||
| 342 | $targetLanguageId = $currentSiteLanguage ? $currentSiteLanguage->getLanguageId() : 0; |
||
| 343 | } else { |
||
| 344 | $targetLanguageId = (int)$targetLanguageId; |
||
| 345 | } |
||
| 346 | try { |
||
| 347 | $siteLanguageOfTargetPage = $siteOfTargetPage->getLanguageById($targetLanguageId); |
||
| 348 | } catch (\InvalidArgumentException $e) { |
||
| 349 | throw new UnableToLinkException('The target page does not have a language with ID ' . $targetLanguageId . ' configured in its site configuration.', 1535477406); |
||
| 350 | } |
||
| 351 | return $siteLanguageOfTargetPage; |
||
| 352 | } |
||
| 353 | |||
| 354 | /** |
||
| 355 | * Create a UriInterface object when linking to a page with a site configuration |
||
| 356 | * |
||
| 357 | * @param array $page |
||
| 358 | * @param Site $siteOfTargetPage |
||
| 359 | * @param array $queryParameters |
||
| 360 | * @param string $fragment |
||
| 361 | * @param array $conf |
||
| 362 | * @return UriInterface |
||
| 363 | * @throws UnableToLinkException |
||
| 364 | */ |
||
| 365 | protected function generateUrlForPageWithSiteConfiguration(array $page, Site $siteOfTargetPage, array $queryParameters, string $fragment, array $conf): UriInterface |
||
| 424 | } |
||
| 425 | |||
| 426 | /** |
||
| 427 | * The function will do its best to find a MP value that will keep the page id inside the current Mount Point rootline if any. |
||
| 428 | * |
||
| 429 | * @param int $pageId page id |
||
| 430 | * @return string MP value, prefixed with &MP= (depending on $raw) |
||
| 431 | */ |
||
| 432 | protected function getClosestMountPointValueForPage($pageId) |
||
| 433 | { |
||
| 434 | $tsfe = $this->getTypoScriptFrontendController(); |
||
| 435 | if (empty($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) || !$tsfe->MP) { |
||
| 436 | return ''; |
||
| 437 | } |
||
| 438 | // Same page as current. |
||
| 439 | if ((int)$tsfe->id === (int)$pageId) { |
||
| 440 | return $tsfe->MP; |
||
| 441 | } |
||
| 442 | |||
| 443 | // Find closest mount point |
||
| 444 | // Gets rootline of linked-to page |
||
| 445 | try { |
||
| 446 | $tCR_rootline = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get(); |
||
| 447 | } catch (RootLineException $e) { |
||
| 448 | $tCR_rootline = []; |
||
| 449 | } |
||
| 450 | $inverseTmplRootline = array_reverse($tsfe->tmpl->rootLine); |
||
| 451 | $rl_mpArray = []; |
||
| 452 | $startMPaccu = false; |
||
| 453 | // Traverse root line of link uid and inside of that the REAL root line of current position. |
||
| 454 | foreach ($tCR_rootline as $tCR_data) { |
||
| 455 | foreach ($inverseTmplRootline as $rlKey => $invTmplRLRec) { |
||
| 456 | // Force accumulating when in overlay mode: Links to this page have to stay within the current branch |
||
| 457 | if ($invTmplRLRec['_MOUNT_OL'] && (int)$tCR_data['uid'] === (int)$invTmplRLRec['uid']) { |
||
| 458 | $startMPaccu = true; |
||
| 459 | } |
||
| 460 | // Accumulate MP data: |
||
| 461 | if ($startMPaccu && $invTmplRLRec['_MP_PARAM']) { |
||
| 462 | $rl_mpArray[] = $invTmplRLRec['_MP_PARAM']; |
||
| 463 | } |
||
| 464 | // If two PIDs matches and this is NOT the site root, start accumulation of MP data (on the next level): |
||
| 465 | // (The check for site root is done so links to branches outside the site but sharing the site roots PID |
||
| 466 | // is NOT detected as within the branch!) |
||
| 467 | if ((int)$tCR_data['pid'] === (int)$invTmplRLRec['pid'] && count($inverseTmplRootline) !== $rlKey + 1) { |
||
| 468 | $startMPaccu = true; |
||
| 469 | } |
||
| 470 | } |
||
| 471 | if ($startMPaccu) { |
||
| 472 | // Good enough... |
||
| 473 | break; |
||
| 474 | } |
||
| 475 | } |
||
| 476 | return !empty($rl_mpArray) ? implode(',', array_reverse($rl_mpArray)) : ''; |
||
| 477 | } |
||
| 478 | |||
| 479 | /** |
||
| 480 | * Initializes the automatically created mountPointMap coming from the "config.MP_mapRootPoints" setting |
||
| 481 | * Can be called many times with overhead only the first time since then the map is generated and cached in memory. |
||
| 482 | * |
||
| 483 | * Previously located within TemplateService::getFromMPmap() |
||
| 484 | * |
||
| 485 | * @param int $pageId Page id to return MPvar value for. |
||
| 486 | * @return string |
||
| 487 | */ |
||
| 488 | public function getMountPointParameterFromRootPointMaps(int $pageId) |
||
| 489 | { |
||
| 490 | // Create map if not found already |
||
| 491 | $config = $this->getTypoScriptFrontendController()->config; |
||
| 492 | $mountPointMap = $this->initializeMountPointMap( |
||
| 493 | !empty($config['config']['MP_defaults']) ? $config['config']['MP_defaults'] : null, |
||
| 494 | !empty($config['config']['MP_mapRootPoints']) ? $config['config']['MP_mapRootPoints'] : null |
||
| 495 | ); |
||
| 496 | |||
| 497 | // Finding MP var for Page ID: |
||
| 498 | if (!empty($mountPointMap[$pageId])) { |
||
| 499 | return implode(',', $mountPointMap[$pageId]); |
||
| 500 | } |
||
| 501 | return ''; |
||
| 502 | } |
||
| 503 | |||
| 504 | /** |
||
| 505 | * Create mount point map, based on TypoScript config.MP_mapRootPoints and config.MP_defaults. |
||
| 506 | * |
||
| 507 | * @param string $defaultMountPoints a string as defined in config.MP_defaults |
||
| 508 | * @param string|null $mapRootPointList a string as defined in config.MP_mapRootPoints |
||
| 509 | * @return array |
||
| 510 | */ |
||
| 511 | protected function initializeMountPointMap(string $defaultMountPoints = null, string $mapRootPointList = null): array |
||
| 512 | { |
||
| 513 | $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime'); |
||
| 514 | $mountPointMap = $runtimeCache->get('pageLinkBuilderMountPointMap') ?: []; |
||
| 515 | if (!empty($mountPointMap) || (empty($mapRootPointList) && empty($defaultMountPoints))) { |
||
| 516 | return $mountPointMap; |
||
| 517 | } |
||
| 518 | if ($defaultMountPoints) { |
||
| 519 | $defaultMountPoints = GeneralUtility::trimExplode('|', $defaultMountPoints, true); |
||
| 520 | foreach ($defaultMountPoints as $temp_p) { |
||
| 521 | [$temp_idP, $temp_MPp] = explode(':', $temp_p, 2); |
||
| 522 | $temp_ids = GeneralUtility::intExplode(',', $temp_idP); |
||
| 523 | foreach ($temp_ids as $temp_id) { |
||
| 524 | $mountPointMap[$temp_id] = trim($temp_MPp); |
||
| 525 | } |
||
| 526 | } |
||
| 527 | } |
||
| 528 | |||
| 529 | $rootPoints = GeneralUtility::trimExplode(',', strtolower($mapRootPointList), true); |
||
| 530 | // Traverse rootpoints |
||
| 531 | foreach ($rootPoints as $p) { |
||
| 532 | $initMParray = []; |
||
| 533 | if ($p === 'root') { |
||
| 534 | $rootPage = $this->getTypoScriptFrontendController()->tmpl->rootLine[0]; |
||
| 535 | $p = $rootPage['uid']; |
||
| 536 | if ($rootPage['_MOUNT_OL'] && $rootPage['_MP_PARAM']) { |
||
| 537 | $initMParray[] = $rootPage['_MP_PARAM']; |
||
| 538 | } |
||
| 539 | } |
||
| 540 | $this->populateMountPointMapForPageRecursively($mountPointMap, (int)$p, $initMParray); |
||
| 541 | } |
||
| 542 | $runtimeCache->set('pageLinkBuilderMountPointMap', $mountPointMap); |
||
| 543 | return $mountPointMap; |
||
| 544 | } |
||
| 545 | |||
| 546 | /** |
||
| 547 | * Creating mountPointMap for a certain ID root point. |
||
| 548 | * Previously called TemplateService->initMPmap_create() |
||
| 549 | * |
||
| 550 | * @param array $mountPointMap the exiting mount point map |
||
| 551 | * @param int $id Root id from which to start map creation. |
||
| 552 | * @param array $MP_array MP_array passed from root page. |
||
| 553 | * @param int $level Recursion brake. Incremented for each recursive call. 20 is the limit. |
||
| 554 | * @see getMountPointParameterFromRootPointMaps() |
||
| 555 | */ |
||
| 556 | protected function populateMountPointMapForPageRecursively(array &$mountPointMap, int $id, $MP_array = [], $level = 0) |
||
| 557 | { |
||
| 558 | if ($id <= 0) { |
||
| 559 | return; |
||
| 560 | } |
||
| 561 | // First level, check id |
||
| 562 | if (!$level) { |
||
| 563 | // Find mount point if any: |
||
| 564 | $mount_info = $this->getTypoScriptFrontendController()->sys_page->getMountPointInfo($id); |
||
| 565 | // Overlay mode: |
||
| 566 | if (is_array($mount_info) && $mount_info['overlay']) { |
||
| 567 | $MP_array[] = $mount_info['MPvar']; |
||
| 568 | $id = $mount_info['mount_pid']; |
||
| 569 | } |
||
| 570 | // Set mapping information for this level: |
||
| 571 | $mountPointMap[$id] = $MP_array; |
||
| 572 | // Normal mode: |
||
| 573 | if (is_array($mount_info) && !$mount_info['overlay']) { |
||
| 574 | $MP_array[] = $mount_info['MPvar']; |
||
| 575 | $id = $mount_info['mount_pid']; |
||
| 576 | } |
||
| 577 | } |
||
| 578 | if ($id && $level < 20) { |
||
| 579 | $nextLevelAcc = []; |
||
| 580 | // Select and traverse current level pages: |
||
| 581 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); |
||
| 582 | $queryBuilder->getRestrictions() |
||
| 583 | ->removeAll() |
||
| 584 | ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); |
||
| 585 | $queryResult = $queryBuilder |
||
| 586 | ->select('uid', 'pid', 'doktype', 'mount_pid', 'mount_pid_ol', 't3ver_state') |
||
| 587 | ->from('pages') |
||
| 588 | ->where( |
||
| 589 | $queryBuilder->expr()->eq( |
||
| 590 | 'pid', |
||
| 591 | $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT) |
||
| 592 | ), |
||
| 593 | $queryBuilder->expr()->neq( |
||
| 594 | 'doktype', |
||
| 595 | $queryBuilder->createNamedParameter(PageRepository::DOKTYPE_RECYCLER, \PDO::PARAM_INT) |
||
| 596 | ), |
||
| 597 | $queryBuilder->expr()->neq( |
||
| 598 | 'doktype', |
||
| 599 | $queryBuilder->createNamedParameter(PageRepository::DOKTYPE_BE_USER_SECTION, \PDO::PARAM_INT) |
||
| 600 | ) |
||
| 601 | )->execute(); |
||
| 602 | while ($row = $queryResult->fetch()) { |
||
| 603 | // Find mount point if any: |
||
| 604 | $next_id = (int)$row['uid']; |
||
| 605 | $next_MP_array = $MP_array; |
||
| 606 | $mount_info = $this->getTypoScriptFrontendController()->sys_page->getMountPointInfo($next_id, $row); |
||
| 607 | // Overlay mode: |
||
| 608 | if (is_array($mount_info) && $mount_info['overlay']) { |
||
| 609 | $next_MP_array[] = $mount_info['MPvar']; |
||
| 610 | $next_id = (int)$mount_info['mount_pid']; |
||
| 611 | } |
||
| 612 | if (!isset($mountPointMap[$next_id])) { |
||
| 613 | // Set mapping information for this level: |
||
| 614 | $mountPointMap[$next_id] = $next_MP_array; |
||
| 615 | // Normal mode: |
||
| 616 | if (is_array($mount_info) && !$mount_info['overlay']) { |
||
| 617 | $next_MP_array[] = $mount_info['MPvar']; |
||
| 618 | $next_id = (int)$mount_info['mount_pid']; |
||
| 619 | } |
||
| 620 | // Register recursive call |
||
| 621 | // (have to do it this way since ALL of the current level should be registered BEFORE the sublevel at any time) |
||
| 622 | $nextLevelAcc[] = [$next_id, $next_MP_array]; |
||
| 623 | } |
||
| 624 | } |
||
| 625 | // Call recursively, if any: |
||
| 626 | foreach ($nextLevelAcc as $pSet) { |
||
| 627 | $this->populateMountPointMapForPageRecursively($mountPointMap, $pSet[0], $pSet[1], $level + 1); |
||
| 628 | } |
||
| 629 | } |
||
| 630 | } |
||
| 631 | |||
| 632 | /** |
||
| 633 | * Check if we have a site object in the current request. if null, this usually means that |
||
| 634 | * this class was called from CLI context. |
||
| 635 | * |
||
| 636 | * @return SiteInterface|null |
||
| 637 | */ |
||
| 638 | protected function getCurrentSite(): ?SiteInterface |
||
| 639 | { |
||
| 640 | if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) { |
||
| 641 | return $GLOBALS['TYPO3_REQUEST']->getAttribute('site', null); |
||
| 642 | } |
||
| 643 | if (MathUtility::canBeInterpretedAsInteger($GLOBALS['TSFE']->id) && $GLOBALS['TSFE']->id > 0) { |
||
| 644 | $finder = GeneralUtility::makeInstance(SiteFinder::class); |
||
| 645 | try { |
||
| 646 | $site = $finder->getSiteByPageId((int)$GLOBALS['TSFE']->id); |
||
| 647 | } catch (SiteNotFoundException $e) { |
||
| 648 | $site = null; |
||
| 649 | } |
||
| 650 | return $site; |
||
| 651 | } |
||
| 652 | return null; |
||
| 653 | } |
||
| 654 | |||
| 655 | /** |
||
| 656 | * If the current request has a site language, this means that the SiteResolver has detected a |
||
| 657 | * page with a site configuration and a selected language, so let's choose that one. |
||
| 658 | * |
||
| 659 | * @return SiteLanguage|null |
||
| 660 | */ |
||
| 661 | protected function getCurrentSiteLanguage(): ?SiteLanguage |
||
| 662 | { |
||
| 663 | if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) { |
||
| 664 | return $GLOBALS['TYPO3_REQUEST']->getAttribute('language', null); |
||
| 665 | } |
||
| 666 | return null; |
||
| 667 | } |
||
| 668 | |||
| 669 | /** |
||
| 670 | * Builds PageRepository instance without depending on global context, e.g. |
||
| 671 | * not automatically overlaying records based on current request language. |
||
| 672 | * |
||
| 673 | * @return PageRepository |
||
| 674 | */ |
||
| 675 | protected function buildPageRepository(): PageRepository |
||
| 688 | } |
||
| 689 | } |
||
| 690 |