Test Failed
Branch master (7b1793)
by Tymoteusz
36:50 queued 18:38
created

PageLinkBuilder::build()   F

Complexity

Conditions 77
Paths 876

Size

Total Lines 232
Code Lines 135

Duplication

Lines 6
Ratio 2.59 %

Importance

Changes 0
Metric Value
cc 77
eloc 135
nc 876
nop 4
dl 6
loc 232
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 TYPO3\CMS\Core\Utility\GeneralUtility;
19
use TYPO3\CMS\Core\Utility\MathUtility;
20
use TYPO3\CMS\Frontend\ContentObject\TypolinkModifyLinkConfigForPageLinksHookInterface;
21
use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
22
use TYPO3\CMS\Frontend\Page\PageRepository;
23
24
/**
25
 * Builds a TypoLink to a certain page
26
 */
27
class PageLinkBuilder extends AbstractTypolinkBuilder
28
{
29
    /**
30
     * @inheritdoc
31
     */
32
    public function build(array &$linkDetails, string $linkText, string $target, array $conf): array
33
    {
34
        $tsfe = $this->getTypoScriptFrontendController();
35
        // Checking if the id-parameter is an alias.
36
        if (!empty($linkDetails['pagealias'])) {
37
            $linkDetails['pageuid'] = $tsfe->sys_page->getPageIdFromAlias($linkDetails['pagealias']);
38
        } elseif (empty($linkDetails['pageuid']) || $linkDetails['pageuid'] === 'current') {
39
            // If no id or alias is given
40
            $linkDetails['pageuid'] = $tsfe->id;
41
        }
42
43
        // Link to page even if access is missing?
44
        if (isset($conf['linkAccessRestrictedPages'])) {
45
            $disableGroupAccessCheck = (bool)$conf['linkAccessRestrictedPages'];
46
        } else {
47
            $disableGroupAccessCheck = (bool)$tsfe->config['config']['typolinkLinkAccessRestrictedPages'];
48
        }
49
50
        // Looking up the page record to verify its existence:
51
        $page = $tsfe->sys_page->getPage($linkDetails['pageuid'], $disableGroupAccessCheck);
52
53 View Code Duplication
        if (empty($page)) {
54
            throw new UnableToLinkException('Page id "' . $linkDetails['typoLinkParameter'] . '" was not found, so "' . $linkText . '" was not linked.', 1490987336, null, $linkText);
55
        }
56
57
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typolinkProcessing']['typolinkModifyParameterForPageLinks'] ?? [] as $classData) {
58
            $hookObject = GeneralUtility::makeInstance($classData);
59
            if (!$hookObject instanceof TypolinkModifyLinkConfigForPageLinksHookInterface) {
60
                throw new \UnexpectedValueException('$hookObject must implement interface ' . TypolinkModifyLinkConfigForPageLinksHookInterface::class, 1483114905);
61
            }
62
            /** @var $hookObject TypolinkModifyLinkConfigForPageLinksHookInterface */
63
            $conf = $hookObject->modifyPageLinkConfiguration($conf, $linkDetails, $page);
64
        }
65
        $enableLinksAcrossDomains = $tsfe->config['config']['typolinkEnableLinksAcrossDomains'];
66
        if ($conf['no_cache.']) {
67
            $conf['no_cache'] = (string)$this->contentObjectRenderer->stdWrap($conf['no_cache'], $conf['no_cache.']);
68
        }
69
70
        $sectionMark = trim(isset($conf['section.']) ? (string)$this->contentObjectRenderer->stdWrap($conf['section'], $conf['section.']) : (string)$conf['section']);
71
        if ($sectionMark === '' && isset($linkDetails['fragment'])) {
72
            $sectionMark = $linkDetails['fragment'];
73
        }
74
        if ($sectionMark !== '') {
75
            $sectionMark = '#' . (MathUtility::canBeInterpretedAsInteger($sectionMark) ? 'c' : '') . $sectionMark;
76
        }
77
        // Overruling 'type'
78
        $pageType = $linkDetails['pagetype'] ?? 0;
79
80
        if (isset($linkDetails['parameters'])) {
81
            $conf['additionalParams'] .= '&' . ltrim($linkDetails['parameters'], '&');
82
        }
83
        // MointPoints, look for closest MPvar:
84
        $MPvarAcc = [];
85
        if (!$tsfe->config['config']['MP_disableTypolinkClosestMPvalue']) {
86
            $temp_MP = $this->getClosestMountPointValueForPage($page['uid']);
87
            if ($temp_MP) {
88
                $MPvarAcc['closest'] = $temp_MP;
89
            }
90
        }
91
        // Look for overlay Mount Point:
92
        $mount_info = $tsfe->sys_page->getMountPointInfo($page['uid'], $page);
93
        if (is_array($mount_info) && $mount_info['overlay']) {
94
            $page = $tsfe->sys_page->getPage($mount_info['mount_pid'], $disableGroupAccessCheck);
95 View Code Duplication
            if (empty($page)) {
96
                throw new UnableToLinkException('Mount point "' . $mount_info['mount_pid'] . '" was not available, so "' . $linkText . '" was not linked.', 1490987337, null, $linkText);
97
            }
98
            $MPvarAcc['re-map'] = $mount_info['MPvar'];
99
        }
100
        // Setting title if blank value to link
101
        $linkText = $this->parseFallbackLinkTextIfLinkTextIsEmpty($linkText, $page['title']);
102
        // Query Params:
103
        $addQueryParams = $conf['addQueryString'] ? $this->contentObjectRenderer->getQueryArguments($conf['addQueryString.']) : '';
104
        $addQueryParams .= isset($conf['additionalParams.']) ? trim((string)$this->contentObjectRenderer->stdWrap($conf['additionalParams'], $conf['additionalParams.'])) : trim((string)$conf['additionalParams']);
105
        if ($addQueryParams === '&' || $addQueryParams[0] !== '&') {
106
            $addQueryParams = '';
107
        }
108
        $targetDomain = '';
109
        $currentDomain = (string)GeneralUtility::getIndpEnv('HTTP_HOST');
110
        // Mount pages are always local and never link to another domain
111
        if (!empty($MPvarAcc)) {
112
            // Add "&MP" var:
113
            $addQueryParams .= '&MP=' . rawurlencode(implode(',', $MPvarAcc));
114
        } elseif (strpos($addQueryParams, '&MP=') === false) {
115
            // We do not come here if additionalParams had '&MP='. This happens when typoLink is called from
116
            // menu. Mount points always work in the content of the current domain and we must not change
117
            // domain if MP variables exist.
118
            // If we link across domains and page is free type shortcut, we must resolve the shortcut first!
119
            // If we do not do it, TYPO3 will fail to (1) link proper page in RealURL/CoolURI because
120
            // they return relative links and (2) show proper page if no RealURL/CoolURI exists when link is clicked
121
            if ($enableLinksAcrossDomains
122
                && (int)$page['doktype'] === PageRepository::DOKTYPE_SHORTCUT
123
                && (int)$page['shortcut_mode'] === PageRepository::SHORTCUT_MODE_NONE
124
            ) {
125
                // Save in case of broken destination or endless loop
126
                $page2 = $page;
127
                // Same as in RealURL, seems enough
128
                $maxLoopCount = 20;
129
                while ($maxLoopCount
130
                    && is_array($page)
131
                    && (int)$page['doktype'] === PageRepository::DOKTYPE_SHORTCUT
132
                    && (int)$page['shortcut_mode'] === PageRepository::SHORTCUT_MODE_NONE
133
                ) {
134
                    $page = $tsfe->sys_page->getPage($page['shortcut'], $disableGroupAccessCheck);
135
                    $maxLoopCount--;
136
                }
137
                if (empty($page) || $maxLoopCount === 0) {
138
                    // We revert if shortcut is broken or maximum number of loops is exceeded (indicates endless loop)
139
                    $page = $page2;
140
                }
141
            }
142
143
            $targetDomainRecord = $tsfe->getDomainDataForPid($page['uid']);
144
            $targetDomain = $targetDomainRecord ? $targetDomainRecord['domainName'] : null;
145
            // Do not prepend the domain if it is the current hostname
146
            if (!$targetDomain || $tsfe->domainNameMatchesCurrentRequest($targetDomain)) {
147
                $targetDomain = '';
148
            }
149
        }
150
        if ($conf['useCacheHash']) {
151
            $params = $tsfe->linkVars . $addQueryParams . '&id=' . $page['uid'];
152
            if (trim($params, '& ') !== '') {
153
                $cacheHash = GeneralUtility::makeInstance(CacheHashCalculator::class);
154
                $cHash = $cacheHash->generateForParameters($params);
155
                $addQueryParams .= $cHash ? '&cHash=' . $cHash : '';
156
            }
157
            unset($params);
158
        }
159
        $absoluteUrlScheme = 'http';
160
        // URL shall be absolute:
161
        if (isset($conf['forceAbsoluteUrl']) && $conf['forceAbsoluteUrl']) {
162
            // Override scheme:
163
            if (isset($conf['forceAbsoluteUrl.']['scheme']) && $conf['forceAbsoluteUrl.']['scheme']) {
164
                $absoluteUrlScheme = $conf['forceAbsoluteUrl.']['scheme'];
165
            } elseif (GeneralUtility::getIndpEnv('TYPO3_SSL')) {
166
                $absoluteUrlScheme = 'https';
167
            }
168
            // If no domain records are defined, use current domain:
169
            $currentUrlScheme = parse_url(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'), PHP_URL_SCHEME);
170
            if ($targetDomain === '' && ($conf['forceAbsoluteUrl'] || $absoluteUrlScheme !== $currentUrlScheme)) {
171
                $targetDomain = $currentDomain;
172
            }
173
            // If go for an absolute link, add site path if it's not taken care about by absRefPrefix
174
            if (!$tsfe->config['config']['absRefPrefix'] && $targetDomain === $currentDomain) {
175
                $targetDomain = $currentDomain . rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'), '/');
176
            }
177
        }
178
        // If target page has a different domain and the current domain's linking scheme (e.g. RealURL/...) should not be used
179
        if ($targetDomain !== '' && $targetDomain !== $currentDomain && !$enableLinksAcrossDomains) {
180
            $target = $target ?: $this->resolveTargetAttribute($conf, 'extTarget', false, $tsfe->extTarget);
181
            $LD['target'] = $target;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$LD was never initialized. Although not strictly required by PHP, it is generally a good practice to add $LD = array(); before regardless.
Loading history...
182
            // Convert IDNA-like domain (if any)
183
            if (!preg_match('/^[a-z0-9.\\-]*$/i', $targetDomain)) {
184
                $targetDomain =  GeneralUtility::idnaEncode($targetDomain);
185
            }
186
            $url = $absoluteUrlScheme . '://' . $targetDomain . '/index.php?id=' . $page['uid'] . $addQueryParams . $sectionMark;
187
        } else {
188
            // Internal link or current domain's linking scheme should be used
189
            // Internal target:
190
            if (empty($target)) {
191
                $target = $this->resolveTargetAttribute($conf, 'target', true, $tsfe->intTarget);
192
            }
193
            $LD = $tsfe->tmpl->linkData($page, $target, $conf['no_cache'], '', '', $addQueryParams, $pageType, $targetDomain);
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type array expected by parameter $overrideArray of TYPO3\CMS\Core\TypoScrip...lateService::linkData(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
            $LD = $tsfe->tmpl->linkData($page, $target, $conf['no_cache'], '', /** @scrutinizer ignore-type */ '', $addQueryParams, $pageType, $targetDomain);
Loading history...
194
            if ($targetDomain !== '') {
195
                // We will add domain only if URL does not have it already.
196
                if ($enableLinksAcrossDomains && $targetDomain !== $currentDomain) {
197
                    // Get rid of the absRefPrefix if necessary. absRefPrefix is applicable only
198
                    // to the current web site. If we have domain here it means we link across
199
                    // domains. absRefPrefix can contain domain name, which will screw up
200
                    // the link to the external domain.
201
                    $prefixLength = strlen($tsfe->config['config']['absRefPrefix']);
202
                    if (substr($LD['totalURL'], 0, $prefixLength) === $tsfe->config['config']['absRefPrefix']) {
203
                        $LD['totalURL'] = substr($LD['totalURL'], $prefixLength);
204
                    }
205
                }
206
                $urlParts = parse_url($LD['totalURL']);
207
                if (empty($urlParts['host'])) {
208
                    $LD['totalURL'] = $absoluteUrlScheme . '://' . $targetDomain . ($LD['totalURL'][0] === '/' ? '' : '/') . $LD['totalURL'];
209
                }
210
            }
211
            $url = $LD['totalURL'] . $sectionMark;
212
        }
213
        $target = $LD['target'];
214
        // 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.
215
        if ($sectionMark
216
            && !$tsfe->config['config']['baseURL']
217
            && (int)$page['uid'] === (int)$tsfe->id
218
            && !trim($addQueryParams)
219
            && (empty($conf['addQueryString']) || !isset($conf['addQueryString.']))
220
        ) {
221
            $currentQueryArray = GeneralUtility::explodeUrl2Array(GeneralUtility::getIndpEnv('QUERY_STRING'), true);
222
            $currentQueryParams = GeneralUtility::implodeArrayForUrl('', $currentQueryArray, '', false, true);
223
224
            if (!trim($currentQueryParams)) {
225
                list(, $URLparams) = explode('?', $url);
226
                list($URLparams) = explode('#', (string)$URLparams);
227
                parse_str($URLparams . $LD['orig_type'], $URLparamsArray);
228
                // Type nums must match as well as page ids
229
                if ((int)$URLparamsArray['type'] === (int)$tsfe->type) {
230
                    unset($URLparamsArray['id']);
231
                    unset($URLparamsArray['type']);
232
                    // If there are no parameters left.... set the new url.
233
                    if (empty($URLparamsArray)) {
234
                        $url = $sectionMark;
235
                    }
236
                }
237
            }
238
        }
239
240
        // If link is to an access restricted page which should be redirected, then find new URL:
241
        if (empty($conf['linkAccessRestrictedPages'])
242
            && $tsfe->config['config']['typolinkLinkAccessRestrictedPages']
243
            && $tsfe->config['config']['typolinkLinkAccessRestrictedPages'] !== 'NONE'
244
            && !$tsfe->checkPageGroupAccess($page)
245
        ) {
246
            $thePage = $tsfe->sys_page->getPage($tsfe->config['config']['typolinkLinkAccessRestrictedPages']);
247
            $addParams = str_replace(
248
                [
249
                    '###RETURN_URL###',
250
                    '###PAGE_ID###'
251
                ],
252
                [
253
                    rawurlencode($url),
254
                    $page['uid']
255
                ],
256
                $tsfe->config['config']['typolinkLinkAccessRestrictedPages_addParams']
257
            );
258
            $url = $this->contentObjectRenderer->getTypoLink_URL($thePage['uid'] . ($pageType ? ',' . $pageType : ''), $addParams, $target);
259
            $url = $this->forceAbsoluteUrl($url, $conf);
260
            $this->contentObjectRenderer->lastTypoLinkLD['totalUrl'] = $url;
261
        }
262
263
        return [$url, $linkText, $target];
264
    }
265
266
    /**
267
     * Returns the &MP variable value for a page id.
268
     * 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.
269
     *
270
     * @param int $pageId page id
271
     * @return string MP value, prefixed with &MP= (depending on $raw)
272
     */
273
    protected function getClosestMountPointValueForPage($pageId)
274
    {
275
        $tsfe = $this->getTypoScriptFrontendController();
276
        if (empty($GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) || !$tsfe->MP) {
277
            return '';
278
        }
279
        // Same page as current.
280
        if ((int)$tsfe->id === (int)$pageId) {
281
            return $tsfe->MP;
282
        }
283
284
        // Find closest mount point
285
        // Gets rootline of linked-to page
286
        $tCR_rootline = $tsfe->sys_page->getRootLine($pageId, '', true);
287
        $inverseTmplRootline = array_reverse($tsfe->tmpl->rootLine);
288
        $rl_mpArray = [];
289
        $startMPaccu = false;
290
        // Traverse root line of link uid and inside of that the REAL root line of current position.
291
        foreach ($tCR_rootline as $tCR_data) {
292
            foreach ($inverseTmplRootline as $rlKey => $invTmplRLRec) {
293
                // Force accumulating when in overlay mode: Links to this page have to stay within the current branch
294
                if ($invTmplRLRec['_MOUNT_OL'] && (int)$tCR_data['uid'] === (int)$invTmplRLRec['uid']) {
295
                    $startMPaccu = true;
296
                }
297
                // Accumulate MP data:
298
                if ($startMPaccu && $invTmplRLRec['_MP_PARAM']) {
299
                    $rl_mpArray[] = $invTmplRLRec['_MP_PARAM'];
300
                }
301
                // If two PIDs matches and this is NOT the site root, start accumulation of MP data (on the next level):
302
                // (The check for site root is done so links to branches outsite the site but sharing the site roots PID
303
                // is NOT detected as within the branch!)
304
                if ((int)$tCR_data['pid'] === (int)$invTmplRLRec['pid'] && count($inverseTmplRootline) !== $rlKey + 1) {
305
                    $startMPaccu = true;
306
                }
307
            }
308
            if ($startMPaccu) {
309
                // Good enough...
310
                break;
311
            }
312
        }
313
        return !empty($rl_mpArray) ? implode(',', array_reverse($rl_mpArray)) : '';
314
    }
315
}
316