Total Complexity | 91 |
Total Lines | 287 |
Duplicated Lines | 0 % |
Changes | 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 |
||
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 | 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 | 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; |
||
|
|||
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); |
||
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) |
||
314 | } |
||
315 | } |
||
316 |