[TASK] Centralize sys_domain resolving
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Typolink / PageLinkBuilder.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Frontend\Typolink;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Core\Cache\CacheManager;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
22 use TYPO3\CMS\Core\Exception\Page\RootLineException;
23 use TYPO3\CMS\Core\Routing\PageUriBuilder;
24 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\CMS\Core\Utility\MathUtility;
27 use TYPO3\CMS\Core\Utility\RootlineUtility;
28 use TYPO3\CMS\Frontend\Compatibility\LegacyDomainResolver;
29 use TYPO3\CMS\Frontend\ContentObject\TypolinkModifyLinkConfigForPageLinksHookInterface;
30 use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
31 use TYPO3\CMS\Frontend\Page\PageRepository;
32
33 /**
34 * Builds a TypoLink to a certain page
35 */
36 class PageLinkBuilder extends AbstractTypolinkBuilder
37 {
38 /**
39 * @inheritdoc
40 */
41 public function build(array &$linkDetails, string $linkText, string $target, array $conf): array
42 {
43 $tsfe = $this->getTypoScriptFrontendController();
44 // Checking if the id-parameter is an alias.
45 if (!empty($linkDetails['pagealias'])) {
46 $linkDetails['pageuid'] = $tsfe->sys_page->getPageIdFromAlias($linkDetails['pagealias']);
47 } elseif (empty($linkDetails['pageuid']) || $linkDetails['pageuid'] === 'current') {
48 // If no id or alias is given
49 $linkDetails['pageuid'] = $tsfe->id;
50 }
51
52 // Link to page even if access is missing?
53 if (isset($conf['linkAccessRestrictedPages'])) {
54 $disableGroupAccessCheck = (bool)$conf['linkAccessRestrictedPages'];
55 } else {
56 $disableGroupAccessCheck = (bool)$tsfe->config['config']['typolinkLinkAccessRestrictedPages'];
57 }
58
59 // Looking up the page record to verify its existence:
60 $page = $tsfe->sys_page->getPage($linkDetails['pageuid'], $disableGroupAccessCheck);
61
62 if (empty($page)) {
63 throw new UnableToLinkException('Page id "' . $linkDetails['typoLinkParameter'] . '" was not found, so "' . $linkText . '" was not linked.', 1490987336, null, $linkText);
64 }
65 $language = $page[$GLOBALS['TCA']['pages']['ctrl']['languageField']];
66 if ($language === 0 && GeneralUtility::hideIfDefaultLanguage($page['l18n_cfg'])) {
67 throw new UnableToLinkException('Default language of page "' . $linkDetails['typoLinkParameter'] . '" is hidden, so "' . $linkText . '" was not linked.', 1529527301, null, $linkText);
68 }
69 if ($language > 0 && !isset($page['_PAGES_OVERLAY']) && GeneralUtility::hideIfNotTranslated($page['l18n_cfg'])) {
70 throw new UnableToLinkException('Fallback to default language of page "' . $linkDetails['typoLinkParameter'] . '" is disabled, so "' . $linkText . '" was not linked.', 1529527488, null, $linkText);
71 }
72
73 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typolinkProcessing']['typolinkModifyParameterForPageLinks'] ?? [] as $classData) {
74 $hookObject = GeneralUtility::makeInstance($classData);
75 if (!$hookObject instanceof TypolinkModifyLinkConfigForPageLinksHookInterface) {
76 throw new \UnexpectedValueException('$hookObject must implement interface ' . TypolinkModifyLinkConfigForPageLinksHookInterface::class, 1483114905);
77 }
78 /** @var $hookObject TypolinkModifyLinkConfigForPageLinksHookInterface */
79 $conf = $hookObject->modifyPageLinkConfiguration($conf, $linkDetails, $page);
80 }
81 $enableLinksAcrossDomains = $tsfe->config['config']['typolinkEnableLinksAcrossDomains'];
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 // MointPoints, look for closest MPvar:
100 $MPvarAcc = [];
101 if (!$tsfe->config['config']['MP_disableTypolinkClosestMPvalue']) {
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 // Setting title if blank value to link
117 $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $page['title']);
118 // Query Params:
119 $addQueryParams = $conf['addQueryString'] ? $this->contentObjectRenderer->getQueryArguments($conf['addQueryString.']) : '';
120 $addQueryParams .= isset($conf['additionalParams.']) ? trim((string)$this->contentObjectRenderer->stdWrap($conf['additionalParams'], $conf['additionalParams.'])) : trim((string)$conf['additionalParams']);
121 if ($addQueryParams === '&' || $addQueryParams[0] !== '&') {
122 $addQueryParams = '';
123 }
124 $targetDomain = '';
125 $currentDomain = (string)GeneralUtility::getIndpEnv('HTTP_HOST');
126 // Mount pages are always local and never link to another domain
127 if (!empty($MPvarAcc)) {
128 // Add "&MP" var:
129 $addQueryParams .= '&MP=' . rawurlencode(implode(',', $MPvarAcc));
130 } elseif (strpos($addQueryParams, '&MP=') === false) {
131 // We do not come here if additionalParams had '&MP='. This happens when typoLink is called from
132 // menu. Mount points always work in the content of the current domain and we must not change
133 // domain if MP variables exist.
134 // If we link across domains and page is free type shortcut, we must resolve the shortcut first!
135 // If we do not do it, TYPO3 will fail to (1) link proper page in RealURL/CoolURI because
136 // they return relative links and (2) show proper page if no RealURL/CoolURI exists when link is clicked
137 if ($enableLinksAcrossDomains
138 && (int)$page['doktype'] === PageRepository::DOKTYPE_SHORTCUT
139 && (int)$page['shortcut_mode'] === PageRepository::SHORTCUT_MODE_NONE
140 ) {
141 // Save in case of broken destination or endless loop
142 $page2 = $page;
143 // Same as in RealURL, seems enough
144 $maxLoopCount = 20;
145 while ($maxLoopCount
146 && is_array($page)
147 && (int)$page['doktype'] === PageRepository::DOKTYPE_SHORTCUT
148 && (int)$page['shortcut_mode'] === PageRepository::SHORTCUT_MODE_NONE
149 ) {
150 $page = $tsfe->sys_page->getPage($page['shortcut'], $disableGroupAccessCheck);
151 $maxLoopCount--;
152 }
153 if (empty($page) || $maxLoopCount === 0) {
154 // We revert if shortcut is broken or maximum number of loops is exceeded (indicates endless loop)
155 $page = $page2;
156 }
157 }
158 // @todo: This obviously needs more detection with Site Handling, to detect the site language ID
159 $domainResolver = GeneralUtility::makeInstance(LegacyDomainResolver::class);
160 $targetDomainRecord = $domainResolver->matchPageId((int)$page['uid'], $GLOBALS['TYPO3_REQUEST']);
161 $targetDomain = '';
162 // Do not prepend the domain if it is the current hostname
163 if (!empty($targetDomainRecord) && !$targetDomainRecord['isCurrentDomain']) {
164 $targetDomain = $targetDomainRecord['domainName'];
165 }
166 }
167 if ($conf['useCacheHash']) {
168 $params = $tsfe->linkVars . $addQueryParams . '&id=' . $page['uid'];
169 if (trim($params, '& ') !== '') {
170 $cacheHash = GeneralUtility::makeInstance(CacheHashCalculator::class);
171 $cHash = $cacheHash->generateForParameters($params);
172 $addQueryParams .= $cHash ? '&cHash=' . $cHash : '';
173 }
174 unset($params);
175 }
176 $absoluteUrlScheme = GeneralUtility::getIndpEnv('TYPO3_SSL') ? 'https' : 'http';
177 // URL shall be absolute:
178 if (isset($conf['forceAbsoluteUrl']) && $conf['forceAbsoluteUrl']) {
179 // Override scheme:
180 if (isset($conf['forceAbsoluteUrl.']['scheme']) && $conf['forceAbsoluteUrl.']['scheme']) {
181 $absoluteUrlScheme = $conf['forceAbsoluteUrl.']['scheme'];
182 }
183 // If no domain records are defined, use current domain:
184 $currentUrlScheme = parse_url(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'), PHP_URL_SCHEME);
185 if ($targetDomain === '' && ($conf['forceAbsoluteUrl'] || $absoluteUrlScheme !== $currentUrlScheme)) {
186 $targetDomain = $currentDomain;
187 }
188 // If go for an absolute link, add site path if it's not taken care about by absRefPrefix
189 if (!$tsfe->config['config']['absRefPrefix'] && $targetDomain === $currentDomain) {
190 $targetDomain = $currentDomain . rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'), '/');
191 }
192 }
193 // If target page has a different domain and the current domain's linking scheme (e.g. RealURL/...) should not be used
194 if ($targetDomain !== '' && $targetDomain !== $currentDomain && !$enableLinksAcrossDomains) {
195 $target = $target ?: $this->resolveTargetAttribute($conf, 'extTarget', false, $tsfe->extTarget);
196 // Convert IDNA-like domain (if any)
197 if (!preg_match('/^[a-z0-9.\\-]*$/i', $targetDomain)) {
198 $targetDomain = GeneralUtility::idnaEncode($targetDomain);
199 }
200 $url = $absoluteUrlScheme . '://' . $targetDomain . '/index.php?id=' . $page['uid'] . $addQueryParams . $sectionMark;
201 } else {
202 // Internal link or current domain's linking scheme should be used
203 // Internal target:
204 $target = (isset($page['target']) && trim($page['target'])) ? $page['target'] : $target;
205 if (empty($target)) {
206 $target = $this->resolveTargetAttribute($conf, 'target', true, $tsfe->intTarget);
207 }
208 $LD = $this->createTotalUrlAndLinkData($page, $target, $conf['no_cache'], $addQueryParams, $pageType, $targetDomain);
209 if ($targetDomain !== '') {
210 // We will add domain only if URL does not have it already.
211 if ($enableLinksAcrossDomains && $targetDomain !== $currentDomain && isset($tsfe->config['config']['absRefPrefix'])) {
212 // Get rid of the absRefPrefix if necessary. absRefPrefix is applicable only
213 // to the current web site. If we have domain here it means we link across
214 // domains. absRefPrefix can contain domain name, which will screw up
215 // the link to the external domain.
216 $prefixLength = strlen($tsfe->config['config']['absRefPrefix']);
217 if (substr($LD['totalURL'], 0, $prefixLength) === $tsfe->config['config']['absRefPrefix']) {
218 $LD['totalURL'] = substr($LD['totalURL'], $prefixLength);
219 }
220 }
221 $urlParts = parse_url($LD['totalURL']);
222 if (empty($urlParts['host'])) {
223 $LD['totalURL'] = $absoluteUrlScheme . '://' . $targetDomain . ($LD['totalURL'][0] === '/' ? '' : '/') . $LD['totalURL'];
224 }
225 }
226 $url = $LD['totalURL'] . $sectionMark;
227 }
228 // If sectionMark is set, there is no baseURL AND the current page is the page the link is to, check if there are any additional parameters or addQueryString parameters and if not, drop the url.
229 if ($sectionMark
230 && !$tsfe->config['config']['baseURL']
231 && (int)$page['uid'] === (int)$tsfe->id
232 && !trim($addQueryParams)
233 && (empty($conf['addQueryString']) || !isset($conf['addQueryString.']))
234 ) {
235 $currentQueryArray = [];
236 parse_str(GeneralUtility::getIndpEnv('QUERY_STRING'), $currentQueryArray);
237 $currentQueryParams = GeneralUtility::implodeArrayForUrl('', $currentQueryArray, '', false, true);
238
239 if (!trim($currentQueryParams)) {
240 list(, $URLparams) = explode('?', $url);
241 list($URLparams) = explode('#', (string)$URLparams);
242 parse_str($URLparams . $LD['orig_type'], $URLparamsArray);
243 // Type nums must match as well as page ids
244 if ((int)$URLparamsArray['type'] === (int)$tsfe->type) {
245 unset($URLparamsArray['id']);
246 unset($URLparamsArray['type']);
247 // If there are no parameters left.... set the new url.
248 if (empty($URLparamsArray)) {
249 $url = $sectionMark;
250 }
251 }
252 }
253 }
254
255 // If link is to an access restricted page which should be redirected, then find new URL:
256 if (empty($conf['linkAccessRestrictedPages'])
257 && $tsfe->config['config']['typolinkLinkAccessRestrictedPages']
258 && $tsfe->config['config']['typolinkLinkAccessRestrictedPages'] !== 'NONE'
259 && !$tsfe->checkPageGroupAccess($page)
260 ) {
261 $thePage = $tsfe->sys_page->getPage($tsfe->config['config']['typolinkLinkAccessRestrictedPages']);
262 $addParams = str_replace(
263 [
264 '###RETURN_URL###',
265 '###PAGE_ID###'
266 ],
267 [
268 rawurlencode($url),
269 $page['uid']
270 ],
271 $tsfe->config['config']['typolinkLinkAccessRestrictedPages_addParams']
272 );
273 $url = $this->contentObjectRenderer->getTypoLink_URL($thePage['uid'] . ($pageType ? ',' . $pageType : ''), $addParams, $target);
274 $url = $this->forceAbsoluteUrl($url, $conf);
275 $this->contentObjectRenderer->lastTypoLinkLD['totalUrl'] = $url;
276 }
277
278 return [$url, $linkText, $target];
279 }
280
281 /**
282 * Returns the &MP variable value for a page id.
283 * 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.
284 *
285 * @param int $pageId page id
286 * @return string MP value, prefixed with &MP= (depending on $raw)
287 */
288 protected function getClosestMountPointValueForPage($pageId)
289 {
290 $tsfe = $this->getTypoScriptFrontendController();
291 if (empty($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) || !$tsfe->MP) {
292 return '';
293 }
294 // Same page as current.
295 if ((int)$tsfe->id === (int)$pageId) {
296 return $tsfe->MP;
297 }
298
299 // Find closest mount point
300 // Gets rootline of linked-to page
301 try {
302 $tCR_rootline = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get();
303 } catch (RootLineException $e) {
304 $tCR_rootline = [];
305 }
306 $inverseTmplRootline = array_reverse($tsfe->tmpl->rootLine);
307 $rl_mpArray = [];
308 $startMPaccu = false;
309 // Traverse root line of link uid and inside of that the REAL root line of current position.
310 foreach ($tCR_rootline as $tCR_data) {
311 foreach ($inverseTmplRootline as $rlKey => $invTmplRLRec) {
312 // Force accumulating when in overlay mode: Links to this page have to stay within the current branch
313 if ($invTmplRLRec['_MOUNT_OL'] && (int)$tCR_data['uid'] === (int)$invTmplRLRec['uid']) {
314 $startMPaccu = true;
315 }
316 // Accumulate MP data:
317 if ($startMPaccu && $invTmplRLRec['_MP_PARAM']) {
318 $rl_mpArray[] = $invTmplRLRec['_MP_PARAM'];
319 }
320 // If two PIDs matches and this is NOT the site root, start accumulation of MP data (on the next level):
321 // (The check for site root is done so links to branches outsite the site but sharing the site roots PID
322 // is NOT detected as within the branch!)
323 if ((int)$tCR_data['pid'] === (int)$invTmplRLRec['pid'] && count($inverseTmplRootline) !== $rlKey + 1) {
324 $startMPaccu = true;
325 }
326 }
327 if ($startMPaccu) {
328 // Good enough...
329 break;
330 }
331 }
332 return !empty($rl_mpArray) ? implode(',', array_reverse($rl_mpArray)) : '';
333 }
334
335 /**
336 * Initializes the automatically created mountPointMap coming from the "config.MP_mapRootPoints" setting
337 * Can be called many times with overhead only the first time since then the map is generated and cached in memory.
338 *
339 * Previously located within TemplateService::getFromMPmap()
340 *
341 * @param int $pageId Page id to return MPvar value for.
342 * @return string
343 */
344 public function getMountPointParameterFromRootPointMaps(int $pageId)
345 {
346 // Create map if not found already
347 $config = $this->getTypoScriptFrontendController()->config;
348 $mountPointMap = $this->initializeMountPointMap(
349 !empty($config['config']['MP_defaults']) ? $config['config']['MP_defaults'] : null,
350 !empty($config['config']['MP_mapRootPoints']) ? $config['config']['MP_mapRootPoints'] : null
351 );
352
353 // Finding MP var for Page ID:
354 if (!empty($mountPointMap[$pageId])) {
355 return implode(',', $mountPointMap[$pageId]);
356 }
357 return '';
358 }
359
360 /**
361 * Create mount point map, based on TypoScript config.MP_mapRootPoints and config.MP_defaults.
362 *
363 * @param string $defaultMountPoints a string as defined in config.MP_defaults
364 * @param string|null $mapRootPointList a string as defined in config.MP_mapRootPoints
365 * @return array
366 */
367 protected function initializeMountPointMap(string $defaultMountPoints = null, string $mapRootPointList = null): array
368 {
369 $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
370 $mountPointMap = $runtimeCache->get('pageLinkBuilderMountPointMap') ?: [];
371 if (!empty($mountPointMap) || (empty($mapRootPointList) && empty($defaultMountPoints))) {
372 return $mountPointMap;
373 }
374 if ($defaultMountPoints) {
375 $defaultMountPoints = GeneralUtility::trimExplode('|', $defaultMountPoints, true);
376 foreach ($defaultMountPoints as $temp_p) {
377 list($temp_idP, $temp_MPp) = explode(':', $temp_p, 2);
378 $temp_ids = GeneralUtility::intExplode(',', $temp_idP);
379 foreach ($temp_ids as $temp_id) {
380 $mountPointMap[$temp_id] = trim($temp_MPp);
381 }
382 }
383 }
384
385 $rootPoints = GeneralUtility::trimExplode(',', strtolower($mapRootPointList), true);
386 // Traverse rootpoints
387 foreach ($rootPoints as $p) {
388 $initMParray = [];
389 if ($p === 'root') {
390 $rootPage = $this->getTypoScriptFrontendController()->tmpl->rootLine[0];
391 $p = $rootPage['uid'];
392 if ($p['_MOUNT_OL'] && $p['_MP_PARAM']) {
393 $initMParray[] = $p['_MP_PARAM'];
394 }
395 }
396 $this->populateMountPointMapForPageRecursively($mountPointMap, $p, $initMParray);
397 }
398 $runtimeCache->set('pageLinkBuilderMountPointMap', $mountPointMap);
399 return $mountPointMap;
400 }
401
402 /**
403 * Creating mountPointMap for a certain ID root point.
404 * Previously called TemplateService->initMPmap_create()
405 *
406 * @param array $mountPointMap the exiting mount point map
407 * @param int $id Root id from which to start map creation.
408 * @param array $MP_array MP_array passed from root page.
409 * @param int $level Recursion brake. Incremented for each recursive call. 20 is the limit.
410 * @see getMountPointParameterFromRootPointMaps()
411 */
412 protected function populateMountPointMapForPageRecursively(array &$mountPointMap, int $id, $MP_array = [], $level = 0)
413 {
414 if ($id <= 0) {
415 return;
416 }
417 // First level, check id
418 if (!$level) {
419 // Find mount point if any:
420 $mount_info = $this->getTypoScriptFrontendController()->sys_page->getMountPointInfo($id);
421 // Overlay mode:
422 if (is_array($mount_info) && $mount_info['overlay']) {
423 $MP_array[] = $mount_info['MPvar'];
424 $id = $mount_info['mount_pid'];
425 }
426 // Set mapping information for this level:
427 $mountPointMap[$id] = $MP_array;
428 // Normal mode:
429 if (is_array($mount_info) && !$mount_info['overlay']) {
430 $MP_array[] = $mount_info['MPvar'];
431 $id = $mount_info['mount_pid'];
432 }
433 }
434 if ($id && $level < 20) {
435 $nextLevelAcc = [];
436 // Select and traverse current level pages:
437 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
438 $queryBuilder->getRestrictions()
439 ->removeAll()
440 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
441 $queryResult = $queryBuilder
442 ->select('uid', 'pid', 'doktype', 'mount_pid', 'mount_pid_ol')
443 ->from('pages')
444 ->where(
445 $queryBuilder->expr()->eq(
446 'pid',
447 $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
448 ),
449 $queryBuilder->expr()->neq(
450 'doktype',
451 $queryBuilder->createNamedParameter(PageRepository::DOKTYPE_RECYCLER, \PDO::PARAM_INT)
452 ),
453 $queryBuilder->expr()->neq(
454 'doktype',
455 $queryBuilder->createNamedParameter(PageRepository::DOKTYPE_BE_USER_SECTION, \PDO::PARAM_INT)
456 )
457 )->execute();
458 while ($row = $queryResult->fetch()) {
459 // Find mount point if any:
460 $next_id = $row['uid'];
461 $next_MP_array = $MP_array;
462 $mount_info = $this->getTypoScriptFrontendController()->sys_page->getMountPointInfo($next_id, $row);
463 // Overlay mode:
464 if (is_array($mount_info) && $mount_info['overlay']) {
465 $next_MP_array[] = $mount_info['MPvar'];
466 $next_id = $mount_info['mount_pid'];
467 }
468 if (!isset($mountPointMap[$next_id])) {
469 // Set mapping information for this level:
470 $mountPointMap[$next_id] = $next_MP_array;
471 // Normal mode:
472 if (is_array($mount_info) && !$mount_info['overlay']) {
473 $next_MP_array[] = $mount_info['MPvar'];
474 $next_id = $mount_info['mount_pid'];
475 }
476 // Register recursive call
477 // (have to do it this way since ALL of the current level should be registered BEFORE the sublevel at any time)
478 $nextLevelAcc[] = [$next_id, $next_MP_array];
479 }
480 }
481 // Call recursively, if any:
482 foreach ($nextLevelAcc as $pSet) {
483 $this->populateMountPointMapForPageRecursively($mountPointMap, $pSet[0], $pSet[1], $level + 1);
484 }
485 }
486 }
487
488 /**
489 * The mother of all functions creating links/URLs etc in a TypoScript environment.
490 * See the references below.
491 * Basically this function takes care of issues such as type,id,alias and Mount Points, URL rewriting (through hooks), M5/B6 encoded parameters etc.
492 * It is important to pass all links created through this function since this is the guarantee that globally configured settings for link creating are observed and that your applications will conform to the various/many configuration options in TypoScript Templates regarding this.
493 *
494 * @param array $page The page record of the page to which we are creating a link. Needed due to fields like uid, alias, target, title and sectionIndex_uid.
495 * @param string $target Target string
496 * @param bool $no_cache If set, then the "&no_cache=1" parameter is included in the URL.
497 * @param string $addParams Additional URL parameters to set in the URL. Syntax is "&foo=bar&foo2=bar2" etc. Also used internally to add parameters if needed.
498 * @param string $typeOverride If you set this value to something else than a blank string, then the typeNumber used in the link will be forced to this value. Normally the typeNum is based on the target set OR on $this->getTypoScriptFrontendController()->config['config']['forceTypeValue'] if found.
499 * @param string $targetDomain The target Doamin, if any was detected in typolink
500 * @return array Contains keys like "totalURL", "url", "sectionIndex", "linkVars", "no_cache", "type" of which "totalURL" is normally the value you would use while the other keys contains various parts that was used to construct "totalURL
501 */
502 protected function createTotalUrlAndLinkData($page, $target, $no_cache, $addParams = '', $typeOverride = '', $targetDomain = '')
503 {
504 $allQueryParameters = [];
505 $LD = [];
506 // Adding Mount Points, "&MP=", parameter for the current page if any is set
507 // but non other set explicitly
508 if (strpos($addParams, '&MP=') === false) {
509 $mountPointParameter = $this->getMountPointParameterFromRootPointMaps((int)$page['uid']);
510 if ($mountPointParameter) {
511 $addParams .= '&MP=' . rawurlencode($mountPointParameter);
512 }
513 }
514 // Setting ID/alias:
515 $script = 'index.php';
516 if ($page['alias']) {
517 $LD['url'] = $script . '?id=' . rawurlencode($page['alias']);
518 } else {
519 $LD['url'] = $script . '?id=' . $page['uid'];
520 }
521 // typeNum
522 $typeNum = $this->getTypoScriptFrontendController()->tmpl->setup[$target . '.']['typeNum'];
523 $config = $this->getTypoScriptFrontendController()->config;
524 if (!MathUtility::canBeInterpretedAsInteger($typeOverride) && !empty($config['config']['forceTypeValue']) && (int)$config['config']['forceTypeValue']) {
525 $typeOverride = (int)$config['config']['forceTypeValue'];
526 }
527 if ((string)$typeOverride !== '') {
528 $typeNum = $typeOverride;
529 }
530 // Override...
531 if ($typeNum) {
532 $LD['type'] = '&type=' . (int)$typeNum;
533 $allQueryParameters['type'] = (int)$typeNum;
534 } else {
535 $LD['type'] = '';
536 }
537 // Preserving the type number.
538 $LD['orig_type'] = $LD['type'];
539 // noCache
540 if ($no_cache) {
541 $LD['no_cache'] = '&no_cache=1';
542 $allQueryParameters['no_cache'] = 1;
543 } else {
544 $LD['no_cache'] = '';
545 }
546 // linkVars
547 $queryParameters = GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars . $addParams);
548 if (!empty($queryParameters)) {
549 $allQueryParameters = array_replace_recursive($queryParameters, $allQueryParameters);
550 $LD['linkVars'] = GeneralUtility::implodeArrayForUrl('', $queryParameters, '', false, true);
551 }
552 // Add absRefPrefix if exists.
553 $LD['url'] = $this->getTypoScriptFrontendController()->absRefPrefix . $LD['url'];
554 // If the special key 'sectionIndex_uid' (added 'manually' in tslib/menu.php to the page-record) is set, then the link jumps directly to a section on the page.
555 $LD['sectionIndex'] = $page['sectionIndex_uid'] ? '#c' . $page['sectionIndex_uid'] : '';
556
557 // Compile the total url
558 $urlParts = parse_url($LD['url']);
559
560 // Now see if the URL can be replaced by a URL generated by the Site-based Page Builder,
561 // but first find out if a language has been set explicitly
562 if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) {
563 $currentSiteLanguage = $GLOBALS['TYPO3_REQUEST']->getAttribute('language');
564 if ($currentSiteLanguage instanceof SiteLanguage) {
565 $languageId = $currentSiteLanguage->getLanguageId();
566 }
567 }
568 $absRefPrefix = $this->getTypoScriptFrontendController()->absRefPrefix;
569 $languageId = $queryParameters['L'] ?? $languageId ?? null;
570 $totalUrl = (string)GeneralUtility::makeInstance(PageUriBuilder::class)->buildUri(
571 (int)$page['uid'],
572 $allQueryParameters,
573 $LD['sectionIndex'],
574 ['language' => $languageId, 'alternativePageId' => $page['alias'] ?: $page['uid'], 'legacyUrlPrefix' => $absRefPrefix],
575 (!$urlParts['scheme'] && !$urlParts['host']) ? PageUriBuilder::ABSOLUTE_PATH : PageUriBuilder::ABSOLUTE_URL
576 );
577
578 // $totalUri contains /index.php for legacy URLs, as previously "it was index.php"
579 // In case an URI has is prefixed with "/" which is not the absRefPrefix, remove it.
580 // this might change in the future
581 if (strpos($totalUrl, '/index.php') === 0 && $absRefPrefix !== '/') {
582 $totalUrl = substr($totalUrl, 1);
583 }
584
585 // Add the method url id token later-on
586 if ($this->getTypoScriptFrontendController()->getMethodUrlIdToken) {
587 if (strpos($totalUrl, '#') !== false) {
588 $totalUrl = str_replace('#', $this->getTypoScriptFrontendController()->getMethodUrlIdToken . '#', $totalUrl);
589 } else {
590 $totalUrl .= $this->getTypoScriptFrontendController()->getMethodUrlIdToken;
591 }
592 }
593 $LD['totalURL'] = $totalUrl;
594 // Call post processing function for link rendering:
595 $_params = [
596 'LD' => &$LD,
597 'args' => ['page' => $page, 'oTarget' => $target, 'no_cache' => $no_cache, 'script' => $script, 'addParams' => $addParams, 'typeOverride' => $typeOverride, 'targetDomain' => $targetDomain],
598 'typeNum' => $typeNum
599 ];
600 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tstemplate.php']['linkData-PostProc'] ?? [] as $_funcRef) {
601 GeneralUtility::callUserFunction($_funcRef, $_params, $this->getTypoScriptFrontendController()->tmpl);
602 }
603 return $LD;
604 }
605 }