2 declare(strict_types = 1);
18 use Psr\Http\Message\ServerRequestInterface;
19 use Psr\Http\Message\UriInterface;
54 public function build(array &$linkDetails,
string $linkText,
string $target, array $conf): array
58 if (!empty($linkDetails[
'pagealias'])) {
59 $linkDetails[
'pageuid'] = $tsfe->sys_page->getPageIdFromAlias($linkDetails[
'pagealias']);
60 } elseif (empty($linkDetails[
'pageuid']) || $linkDetails[
'pageuid'] ===
'current') {
62 $linkDetails[
'pageuid'] = $tsfe->id;
66 if (isset($conf[
'linkAccessRestrictedPages'])) {
67 $disableGroupAccessCheck = (bool)$conf[
'linkAccessRestrictedPages'];
69 $disableGroupAccessCheck = (bool)$tsfe->config[
'config'][
'typolinkLinkAccessRestrictedPages'];
73 $page = $this->
resolvePage($linkDetails, $conf, $disableGroupAccessCheck);
76 throw new UnableToLinkException(
'Page id "' . $linkDetails[
'typoLinkParameter'] .
'" was not found, so "' . $linkText .
'" was not linked.', 1490987336,
null, $linkText);
79 foreach (
$GLOBALS[
'TYPO3_CONF_VARS'][
'SC_OPTIONS'][
'typolinkProcessing'][
'typolinkModifyParameterForPageLinks'] ?? [] as $classData) {
80 $hookObject = GeneralUtility::makeInstance($classData);
82 throw new \UnexpectedValueException(
'$hookObject must implement interface ' . TypolinkModifyLinkConfigForPageLinksHookInterface::class, 1483114905);
85 $conf = $hookObject->modifyPageLinkConfiguration($conf, $linkDetails, $page);
87 $enableLinksAcrossDomains = $tsfe->config[
'config'][
'typolinkEnableLinksAcrossDomains'];
88 if ($conf[
'no_cache.']) {
89 $conf[
'no_cache'] = (string)$this->contentObjectRenderer->stdWrap($conf[
'no_cache'], $conf[
'no_cache.']);
92 $sectionMark = trim(isset($conf[
'section.']) ? (
string)$this->contentObjectRenderer->stdWrap($conf[
'section'], $conf[
'section.']) : (
string)$conf[
'section']);
93 if ($sectionMark ===
'' && isset($linkDetails[
'fragment'])) {
94 $sectionMark = $linkDetails[
'fragment'];
96 if ($sectionMark !==
'') {
100 $pageType = $linkDetails[
'pagetype'] ??
'';
102 if (isset($linkDetails[
'parameters'])) {
103 $conf[
'additionalParams'] .=
'&' . ltrim($linkDetails[
'parameters'],
'&');
107 if (!$tsfe->config[
'config'][
'MP_disableTypolinkClosestMPvalue']) {
110 $MPvarAcc[
'closest'] = $temp_MP;
114 $mount_info = $tsfe->sys_page->getMountPointInfo($page[
'uid'], $page);
115 if (is_array($mount_info) && $mount_info[
'overlay']) {
116 $page = $tsfe->sys_page->getPage($mount_info[
'mount_pid'], $disableGroupAccessCheck);
118 throw new UnableToLinkException(
'Mount point "' . $mount_info[
'mount_pid'] .
'" was not available, so "' . $linkText .
'" was not linked.', 1490987337,
null, $linkText);
120 $MPvarAcc[
're-map'] = $mount_info[
'MPvar'];
123 $addQueryParams = $conf[
'addQueryString'] ? $this->contentObjectRenderer->getQueryArguments($conf[
'addQueryString.']) :
'';
124 $addQueryParams .= isset($conf[
'additionalParams.']) ? trim((
string)$this->contentObjectRenderer->stdWrap($conf[
'additionalParams'], $conf[
'additionalParams.'])) : trim((
string)$conf[
'additionalParams']);
125 if ($addQueryParams ===
'&' || $addQueryParams[0] !==
'&') {
126 $addQueryParams =
'';
129 if (!empty($MPvarAcc)) {
131 $addQueryParams .=
'&MP=' . rawurlencode(implode(
',', $MPvarAcc));
132 } elseif (strpos($addQueryParams,
'&MP=') ===
false) {
139 if ($enableLinksAcrossDomains
152 $page = $tsfe->sys_page->getPage($page[
'shortcut'], $disableGroupAccessCheck);
155 if (empty($page) || $maxLoopCount === 0) {
161 if ($conf[
'useCacheHash']) {
162 $params = $tsfe->linkVars . $addQueryParams .
'&id=' . $page[
'uid'];
163 if (trim($params,
'& ') !==
'') {
164 $cHash = GeneralUtility::makeInstance(CacheHashCalculator::class)->generateForParameters($params);
165 $addQueryParams .= $cHash ?
'&cHash=' . $cHash :
'';
171 $queryParameters = [];
172 parse_str($addQueryParams, $queryParameters);
173 if ($tsfe->linkVars) {
174 $globalQueryParameters = [];
175 parse_str($tsfe->linkVars, $globalQueryParameters);
176 $queryParameters = array_replace_recursive($globalQueryParameters, $queryParameters);
179 unset($queryParameters[
'id']);
182 if (isset($queryParameters[
'L']) && !isset($conf[
'language'])) {
183 $conf[
'language'] = (int)$queryParameters[
'L'];
188 $siteOfTargetPage = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId((
int)$page[
'uid'],
null, $queryParameters[
'MP'] ??
'');
192 $siteOfTargetPage =
null;
197 if ($siteOfTargetPage !==
null) {
202 if ($siteLanguageOfTargetPage->getLanguageId() > 0) {
203 $context = clone GeneralUtility::makeInstance(Context::class);
204 $context->setAspect(
'language', $languageAspect);
205 $pageRepository = GeneralUtility::makeInstance(PageRepository::class, $context);
206 $page = $pageRepository->getPageOverlay($page);
209 if (!$tsfe->sys_page->isPageSuitableForLanguage($page, $languageAspect)) {
210 if ($siteLanguageOfTargetPage->getLanguageId() === 0 && GeneralUtility::hideIfDefaultLanguage($page[
'l18n_cfg'])) {
211 throw new UnableToLinkException(
'Default language of page "' . $linkDetails[
'typoLinkParameter'] .
'" is hidden, so "' . $linkText .
'" was not linked.', 1551621985,
null, $linkText);
217 if ($siteLanguageOfTargetPage->getLanguageId() > 0 && !isset($page[
'_PAGES_OVERLAY']) && (GeneralUtility::hideIfNotTranslated($page[
'l18n_cfg']) || $siteLanguageOfTargetPage->getFallbackType() ===
'strict')) {
218 throw new UnableToLinkException(
'Fallback to default language of page "' . $linkDetails[
'typoLinkParameter'] .
'" is disabled, so "' . $linkText .
'" was not linked.', 1551621996,
null, $linkText);
223 unset($queryParameters[
'L']);
225 $queryParameters[
'type'] = (int)$pageType;
230 $treatAsExternalLink =
true;
232 if (!$url->getScheme() || !$url->getHost()) {
233 $treatAsExternalLink =
false;
239 if ($currentSite instanceof
Site && $currentSite->
getRootPageId() === $siteOfTargetPage->getRootPageId()) {
240 $treatAsExternalLink =
false;
245 if ($treatAsExternalLink) {
248 $target = (isset($page[
'target']) && trim($page[
'target'])) ? $page[
'target'] : $target;
249 if (empty($target)) {
255 if (isset($conf[
'language']) && $conf[
'language'] > 0 && $conf[
'language'] !==
'current') {
256 $page = $tsfe->sys_page->getPageOverlay($page, (
int)$conf[
'language']);
258 if ($conf[
'language'] === 0 && GeneralUtility::hideIfDefaultLanguage($page[
'l18n_cfg'])) {
259 throw new UnableToLinkException(
'Default language of page "' . $linkDetails[
'typoLinkParameter'] .
'" is hidden, so "' . $linkText .
'" was not linked.', 1529527301,
null, $linkText);
264 if ($conf[
'language'] > 0 && !isset($page[
'_PAGES_OVERLAY']) && GeneralUtility::hideIfNotTranslated($page[
'l18n_cfg'])) {
265 throw new UnableToLinkException(
'Fallback to default language of page "' . $linkDetails[
'typoLinkParameter'] .
'" is disabled, so "' . $linkText .
'" was not linked.', 1529527488,
null, $linkText);
270 $queryParameters[
'L'] = $conf[
'language'];
276 if (empty($conf[
'linkAccessRestrictedPages'])
277 && $tsfe->config[
'config'][
'typolinkLinkAccessRestrictedPages']
278 && $tsfe->config[
'config'][
'typolinkLinkAccessRestrictedPages'] !==
'NONE'
279 && !$tsfe->checkPageGroupAccess($page)
281 $thePage = $tsfe->sys_page->getPage($tsfe->config[
'config'][
'typolinkLinkAccessRestrictedPages']);
282 $addParams = str_replace(
291 $tsfe->config[
'config'][
'typolinkLinkAccessRestrictedPages_addParams']
293 $url = $this->contentObjectRenderer->getTypoLink_URL($thePage[
'uid'] . ($pageType ?
',' . $pageType :
''), $addParams, $target);
299 return [$url, $linkText, $target];
312 protected function resolvePage(array &$linkDetails, array &$configuration,
bool $disableGroupAccessCheck): array
317 $page = $pageRepository->getPage($linkDetails[
'pageuid'], $disableGroupAccessCheck);
319 if (empty($page) || !is_array($page)) {
323 $languageField =
$GLOBALS[
'TCA'][
'pages'][
'ctrl'][
'languageField'] ??
null;
324 $languageParentField =
$GLOBALS[
'TCA'][
'pages'][
'ctrl'][
'transOrigPointerField'] ??
null;
325 $language = (int)($page[$languageField] ?? 0);
328 if ($language === 0 || empty($page[$languageParentField])) {
333 $languageParentPage = $pageRepository->getPage(
334 $page[$languageParentField],
335 $disableGroupAccessCheck
337 if (empty($languageParentPage)) {
342 $linkDetails[
'pageuid'] = (int)$languageParentPage[
'uid'];
343 $configuration[
'language'] = $language;
344 $linkDetails[
'parameters'] .=
'&L=' . $language;
345 return $languageParentPage;
362 if ($currentSite && !($currentSiteLanguage instanceof
SiteLanguage)) {
363 $currentSiteLanguage = $currentSite->getDefaultLanguage();
366 if ($targetLanguageId ===
'current') {
367 $targetLanguageId = $currentSiteLanguage ? $currentSiteLanguage->getLanguageId() : 0;
369 $targetLanguageId = (int)$targetLanguageId;
372 $siteLanguageOfTargetPage = $siteOfTargetPage->
getLanguageById($targetLanguageId);
373 }
catch (\InvalidArgumentException $e) {
374 throw new UnableToLinkException(
'The target page does not have a language with ID ' . $targetLanguageId .
' configured in its site configuration.', 1535477406);
376 return $siteLanguageOfTargetPage;
396 if ($currentSite && !($currentSiteLanguage instanceof
SiteLanguage)) {
397 $currentSiteLanguage = $currentSite->getDefaultLanguage();
404 $useAbsoluteUrl = $conf[
'forceAbsoluteUrl'] ??
false;
409 || $currentSite->getRootPageId() !== $siteOfTargetPage->
getRootPageId()
410 || $siteLanguageOfTargetPage->getBase()->getHost() !== $currentSiteLanguage->getBase()->getHost()) {
411 $useAbsoluteUrl =
true;
414 $targetPageId = (int)($page[
'l10n_parent'] > 0 ? $page[
'l10n_parent'] : $page[
'uid']);
415 $queryParameters[
'_language'] = $siteLanguageOfTargetPage;
417 if ($conf[
'no_cache']) {
418 $queryParameters[
'no_cache'] = 1;
422 && $useAbsoluteUrl ===
false
423 && $currentSiteLanguage === $siteLanguageOfTargetPage
424 && $targetPageId === (
int)
$GLOBALS[
'TSFE']->id
425 && (empty($conf[
'addQueryString']) || !isset($conf[
'addQueryString.']))
426 && !
$GLOBALS[
'TSFE']->config[
'config'][
'baseURL']
427 && count($queryParameters) === 1
429 $uri = (
new Uri())->withFragment($fragment);
439 throw new UnableToLinkException(
'The target page could not be linked. Error: ' . $e->getMessage(), 1535472406);
442 if ($useAbsoluteUrl && !$uri->getScheme() && $uri->getHost()) {
443 $scheme = $conf[
'forceAbsoluteUrl.'][
'scheme'] ??
'https';
444 $uri = $uri->withScheme($scheme);
466 $additionalQueryParams = http_build_query($additionalQueryParams,
'',
'&', PHP_QUERY_RFC3986);
467 if (!empty($additionalQueryParams)) {
468 $additionalQueryParams =
'&' . $additionalQueryParams;
472 $enableLinksAcrossDomains = $tsfe->config[
'config'][
'typolinkEnableLinksAcrossDomains'];
474 $currentDomain = (string)GeneralUtility::getIndpEnv(
'HTTP_HOST');
475 if (!empty($MPvarAcc)) {
476 $domainResolver = GeneralUtility::makeInstance(LegacyDomainResolver::class);
477 $targetDomainRecord = $domainResolver->matchPageId((
int)$page[
'uid'],
$GLOBALS[
'TYPO3_REQUEST']);
479 if (!empty($targetDomainRecord) && !$targetDomainRecord[
'isCurrentDomain']) {
480 $targetDomain = $targetDomainRecord[
'domainName'];
483 $absoluteUrlScheme = GeneralUtility::getIndpEnv(
'TYPO3_SSL') ?
'https' :
'http';
485 if (isset($conf[
'forceAbsoluteUrl']) && $conf[
'forceAbsoluteUrl']) {
487 if (isset($conf[
'forceAbsoluteUrl.'][
'scheme']) && $conf[
'forceAbsoluteUrl.'][
'scheme']) {
488 $absoluteUrlScheme = $conf[
'forceAbsoluteUrl.'][
'scheme'];
491 $targetDomain = $targetDomain ?: $currentDomain;
493 if (!$tsfe->absRefPrefix && $targetDomain === $currentDomain) {
494 $targetDomain = $currentDomain . rtrim(GeneralUtility::getIndpEnv(
'TYPO3_SITE_PATH'),
'/');
498 if ($targetDomain !==
'' && $targetDomain !== $currentDomain && !$enableLinksAcrossDomains) {
501 if (!preg_match(
'/^[a-z0-9.\\-]*$/i', $targetDomain)) {
502 $targetDomain = GeneralUtility::idnaEncode($targetDomain);
504 $url = $absoluteUrlScheme .
'://' . $targetDomain .
'/index.php?id=' . $page[
'uid'] . $additionalQueryParams;
508 $target = (isset($page[
'target']) && trim($page[
'target'])) ? $page[
'target'] : $target;
509 if (empty($target)) {
513 if ($targetDomain !==
'') {
515 if ($enableLinksAcrossDomains && $targetDomain !== $currentDomain && !empty($tsfe->absRefPrefix)) {
520 $prefixLength = strlen($tsfe->absRefPrefix);
521 if (strpos($LD[
'totalURL'], $tsfe->absRefPrefix) === 0) {
522 $LD[
'totalURL'] = substr($LD[
'totalURL'], $prefixLength);
525 $urlParts = parse_url($LD[
'totalURL']);
526 if (empty($urlParts[
'host'])) {
527 $LD[
'totalURL'] = $absoluteUrlScheme .
'://' . $targetDomain . ($LD[
'totalURL'][0] ===
'/' ?
'' :
'/') . $LD[
'totalURL'];
530 $url = $LD[
'totalURL'];
532 $url .= $sectionMark;
536 && !$tsfe->config[
'config'][
'baseURL']
537 && (
int)$page[
'uid'] === (int)$tsfe->id
538 && !trim($additionalQueryParams)
539 && (empty($conf[
'addQueryString']) || !isset($conf[
'addQueryString.']))
541 $currentQueryArray = [];
542 parse_str(GeneralUtility::getIndpEnv(
'QUERY_STRING'), $currentQueryArray);
544 if (empty($currentQueryArray)) {
545 list(, $URLparams) = explode(
'?', $url);
546 list($URLparams) = explode(
'#', (
string)$URLparams);
547 parse_str($URLparams . $LD[
'orig_type'], $URLparamsArray);
549 if ((
int)$URLparamsArray[
'type'] === (
int)$tsfe->type) {
550 unset($URLparamsArray[
'id']);
551 unset($URLparamsArray[
'type']);
553 if (empty($URLparamsArray)) {
559 return [$url, $target];
572 if (empty(
$GLOBALS[
'TYPO3_CONF_VARS'][
'FE'][
'enable_mount_pids']) || !$tsfe->MP) {
576 if ((
int)$tsfe->id === (
int)$pageId) {
583 $tCR_rootline = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get();
587 $inverseTmplRootline = array_reverse($tsfe->tmpl->rootLine);
589 $startMPaccu =
false;
591 foreach ($tCR_rootline as $tCR_data) {
592 foreach ($inverseTmplRootline as $rlKey => $invTmplRLRec) {
594 if ($invTmplRLRec[
'_MOUNT_OL'] && (
int)$tCR_data[
'uid'] === (
int)$invTmplRLRec[
'uid']) {
598 if ($startMPaccu && $invTmplRLRec[
'_MP_PARAM']) {
599 $rl_mpArray[] = $invTmplRLRec[
'_MP_PARAM'];
604 if ((
int)$tCR_data[
'pid'] === (
int)$invTmplRLRec[
'pid'] && count($inverseTmplRootline) !== $rlKey + 1) {
613 return !empty($rl_mpArray) ? implode(
',', array_reverse($rl_mpArray)) :
'';
630 !empty($config[
'config'][
'MP_defaults']) ? $config[
'config'][
'MP_defaults'] :
null,
631 !empty($config[
'config'][
'MP_mapRootPoints']) ? $config[
'config'][
'MP_mapRootPoints'] :
null
635 if (!empty($mountPointMap[$pageId])) {
636 return implode(
',', $mountPointMap[$pageId]);
650 $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache(
'cache_runtime');
651 $mountPointMap = $runtimeCache->get(
'pageLinkBuilderMountPointMap') ?: [];
652 if (!empty($mountPointMap) || (empty($mapRootPointList) && empty($defaultMountPoints))) {
653 return $mountPointMap;
655 if ($defaultMountPoints) {
656 $defaultMountPoints = GeneralUtility::trimExplode(
'|', $defaultMountPoints,
true);
657 foreach ($defaultMountPoints as $temp_p) {
658 list($temp_idP, $temp_MPp) = explode(
':', $temp_p, 2);
659 $temp_ids = GeneralUtility::intExplode(
',', $temp_idP);
660 foreach ($temp_ids as $temp_id) {
661 $mountPointMap[$temp_id] = trim($temp_MPp);
666 $rootPoints = GeneralUtility::trimExplode(
',', strtolower($mapRootPointList),
true);
668 foreach ($rootPoints as $p) {
672 $p = $rootPage[
'uid'];
673 if ($rootPage[
'_MOUNT_OL'] && $rootPage[
'_MP_PARAM']) {
674 $initMParray[] = $rootPage[
'_MP_PARAM'];
679 $runtimeCache->set(
'pageLinkBuilderMountPointMap', $mountPointMap);
680 return $mountPointMap;
703 if (is_array($mount_info) && $mount_info[
'overlay']) {
704 $MP_array[] = $mount_info[
'MPvar'];
705 $id = $mount_info[
'mount_pid'];
708 $mountPointMap[$id] = $MP_array;
710 if (is_array($mount_info) && !$mount_info[
'overlay']) {
711 $MP_array[] = $mount_info[
'MPvar'];
712 $id = $mount_info[
'mount_pid'];
715 if ($id && $level < 20) {
718 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(
'pages');
719 $queryBuilder->getRestrictions()
721 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
722 $queryResult = $queryBuilder
723 ->select(
'uid',
'pid',
'doktype',
'mount_pid',
'mount_pid_ol',
't3ver_state')
726 $queryBuilder->expr()->eq(
728 $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
730 $queryBuilder->expr()->neq(
734 $queryBuilder->expr()->neq(
739 while ($row = $queryResult->fetch()) {
741 $next_id = (int)$row[
'uid'];
742 $next_MP_array = $MP_array;
745 if (is_array($mount_info) && $mount_info[
'overlay']) {
746 $next_MP_array[] = $mount_info[
'MPvar'];
747 $next_id = (int)$mount_info[
'mount_pid'];
749 if (!isset($mountPointMap[$next_id])) {
751 $mountPointMap[$next_id] = $next_MP_array;
753 if (is_array($mount_info) && !$mount_info[
'overlay']) {
754 $next_MP_array[] = $mount_info[
'MPvar'];
755 $next_id = (int)$mount_info[
'mount_pid'];
759 $nextLevelAcc[] = [$next_id, $next_MP_array];
763 foreach ($nextLevelAcc as $pSet) {
788 if (strpos($addParams,
'&MP=') ===
false) {
790 if ($mountPointParameter) {
791 $addParams .=
'&MP=' . rawurlencode($mountPointParameter);
795 $script =
'index.php';
796 if ($page[
'alias']) {
797 $LD[
'url'] = $script .
'?id=' . rawurlencode($page[
'alias']);
799 $LD[
'url'] = $script .
'?id=' . $page[
'uid'];
806 if ((
string)$typeOverride !==
'') {
807 $typeNum = $typeOverride;
811 $LD[
'type'] =
'&type=' . (int)$typeNum;
816 $LD[
'orig_type'] = $LD[
'type'];
818 $LD[
'no_cache'] = $no_cache ?
'&no_cache=1' :
'';
828 $LD[
'sectionIndex'] = $page[
'sectionIndex_uid'] ?
'#c' . $page[
'sectionIndex_uid'] :
'';
830 $LD[
'totalURL'] = rtrim($LD[
'url'] . $LD[
'type'] . $LD[
'no_cache'] . $LD[
'linkVars'] . $this->
getTypoScriptFrontendController()->getMethodUrlIdToken,
'?') . $LD[
'sectionIndex'];
834 'args' => [
'page' => $page,
'oTarget' => $target,
'no_cache' => $no_cache,
'script' => $script,
'addParams' => $addParams,
'typeOverride' => $typeOverride,
'targetDomain' => $targetDomain],
835 'typeNum' => $typeNum
837 foreach (
$GLOBALS[
'TYPO3_CONF_VARS'][
'SC_OPTIONS'][
't3lib/class.t3lib_tstemplate.php'][
'linkData-PostProc'] ?? [] as $_funcRef) {
851 if (
$GLOBALS[
'TYPO3_REQUEST'] instanceof ServerRequestInterface) {
852 return $GLOBALS[
'TYPO3_REQUEST']->getAttribute(
'site',
null);
855 $matcher = GeneralUtility::makeInstance(SiteMatcher::class);
857 $site = $matcher->matchByPageId((
int)
$GLOBALS[
'TSFE']->
id);
874 if (
$GLOBALS[
'TYPO3_REQUEST'] instanceof ServerRequestInterface) {
875 return $GLOBALS[
'TYPO3_REQUEST']->getAttribute(
'language',
null);
889 $context = clone GeneralUtility::makeInstance(Context::class);
892 GeneralUtility::makeInstance(LanguageAspect::class)
894 $pageRepository = GeneralUtility::makeInstance(
895 PageRepository::class,
898 return $pageRepository;