1<?php 2declare(strict_types = 1); 3namespace 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 18use TYPO3\CMS\Core\Service\DependencyOrderingService; 19use TYPO3\CMS\Core\TypoScript\TemplateService; 20use TYPO3\CMS\Core\Utility\GeneralUtility; 21use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; 22use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; 23use TYPO3\CMS\Frontend\Http\UrlProcessorInterface; 24use TYPO3\CMS\Frontend\Page\PageRepository; 25 26/** 27 * Abstract class to provide proper helper for most types necessary 28 * Hands in the ContentObject and TSFE which are needed here for all the stdWrap magic. 29 */ 30abstract class AbstractTypolinkBuilder 31{ 32 /** 33 * @var ContentObjectRenderer 34 */ 35 protected $contentObjectRenderer; 36 37 /** 38 * @var TypoScriptFrontendController 39 */ 40 protected $typoScriptFrontendController; 41 42 /** 43 * AbstractTypolinkBuilder constructor. 44 * 45 * @param ContentObjectRenderer $contentObjectRenderer 46 * @param TypoScriptFrontendController $typoScriptFrontendController 47 */ 48 public function __construct(ContentObjectRenderer $contentObjectRenderer, TypoScriptFrontendController $typoScriptFrontendController = null) 49 { 50 $this->contentObjectRenderer = $contentObjectRenderer; 51 $this->typoScriptFrontendController = $typoScriptFrontendController ?? $GLOBALS['TSFE']; 52 } 53 54 /** 55 * Should be implemented by all subclasses to return an array with three parts: 56 * - URL 57 * - Link Text (can be modified) 58 * - Target (can be modified) 59 * 60 * @param array $linkDetails parsed link details by the LinkService 61 * @param string $linkText the link text 62 * @param string $target the target to point to 63 * @param array $conf the TypoLink configuration array 64 * @return array an array with three parts (URL, Link Text, Target) 65 */ 66 abstract public function build(array &$linkDetails, string $linkText, string $target, array $conf): array; 67 68 /** 69 * Forces a given URL to be absolute. 70 * 71 * @param string $url The URL to be forced to be absolute 72 * @param array $configuration TypoScript configuration of typolink 73 * @return string The absolute URL 74 */ 75 protected function forceAbsoluteUrl(string $url, array $configuration): string 76 { 77 if (!empty($url) && !empty($configuration['forceAbsoluteUrl']) && preg_match('#^(?:([a-z]+)(://)([^/]*)/?)?(.*)$#', $url, $matches)) { 78 $urlParts = [ 79 'scheme' => $matches[1], 80 'delimiter' => '://', 81 'host' => $matches[3], 82 'path' => $matches[4] 83 ]; 84 $isUrlModified = false; 85 // Set scheme and host if not yet part of the URL: 86 if (empty($urlParts['host'])) { 87 $urlParts['scheme'] = GeneralUtility::getIndpEnv('TYPO3_SSL') ? 'https' : 'http'; 88 $urlParts['host'] = GeneralUtility::getIndpEnv('HTTP_HOST'); 89 $urlParts['path'] = '/' . ltrim($urlParts['path'], '/'); 90 // absRefPrefix has been prepended to $url beforehand 91 // so we only modify the path if no absRefPrefix has been set 92 // otherwise we would destroy the path 93 if ($this->getTypoScriptFrontendController()->absRefPrefix === '') { 94 $urlParts['path'] = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . ltrim($urlParts['path'], '/'); 95 } 96 $isUrlModified = true; 97 } 98 // Override scheme: 99 $forceAbsoluteUrl = &$configuration['forceAbsoluteUrl.']['scheme']; 100 if (!empty($forceAbsoluteUrl) && $urlParts['scheme'] !== $forceAbsoluteUrl) { 101 $urlParts['scheme'] = $forceAbsoluteUrl; 102 $isUrlModified = true; 103 } 104 // Recreate the absolute URL: 105 if ($isUrlModified) { 106 $url = implode('', $urlParts); 107 } 108 } 109 return $url; 110 } 111 112 /** 113 * Determines whether lib.parseFunc is defined. 114 * 115 * @return bool 116 */ 117 protected function isLibParseFuncDefined(): bool 118 { 119 $configuration = $this->contentObjectRenderer->mergeTSRef( 120 ['parseFunc' => '< lib.parseFunc'], 121 'parseFunc' 122 ); 123 return !empty($configuration['parseFunc.']) && is_array($configuration['parseFunc.']); 124 } 125 126 /** 127 * Helper method to a fallback method parsing HTML out of it 128 * 129 * @param string $originalLinkText the original string, if empty, the fallback link text 130 * @param string $fallbackLinkText the string to be used. 131 * @return string the final text 132 */ 133 protected function parseFallbackLinkTextIfLinkTextIsEmpty(string $originalLinkText, string $fallbackLinkText): string 134 { 135 if ($originalLinkText !== '') { 136 return $originalLinkText; 137 } 138 if ($this->isLibParseFuncDefined()) { 139 return $this->contentObjectRenderer->parseFunc($fallbackLinkText, ['makelinks' => 0], '< lib.parseFunc'); 140 } 141 // encode in case `lib.parseFunc` is not configured 142 return $this->encodeFallbackLinkTextIfLinkTextIsEmpty($originalLinkText, $fallbackLinkText); 143 } 144 145 /** 146 * Helper method to a fallback method properly encoding HTML. 147 * 148 * @param string $originalLinkText the original string, if empty, the fallback link text 149 * @param string $fallbackLinkText the string to be used. 150 * @return string the final text 151 */ 152 protected function encodeFallbackLinkTextIfLinkTextIsEmpty(string $originalLinkText, string $fallbackLinkText): string 153 { 154 if ($originalLinkText !== '') { 155 return $originalLinkText; 156 } 157 return htmlspecialchars($fallbackLinkText, ENT_QUOTES); 158 } 159 160 /** 161 * Creates the value for target="..." in a typolink configuration 162 * 163 * @param array $conf the typolink configuration 164 * @param string $name the key, usually "target", "extTarget" or "fileTarget" 165 * @param bool $respectFrameSetOption if set, then the fallback is only used as target if the doctype allows it 166 * @param string $fallbackTarget the string to be used when no target is found in the configuration 167 * @return string the value of the target attribute, if there is one 168 */ 169 protected function resolveTargetAttribute(array $conf, string $name, bool $respectFrameSetOption = false, string $fallbackTarget = ''): string 170 { 171 $tsfe = $this->getTypoScriptFrontendController(); 172 $targetAttributeAllowed = !$respectFrameSetOption 173 || (!isset($tsfe->config['config']['doctype']) || !$tsfe->config['config']['doctype']) 174 || in_array((string)$tsfe->config['config']['doctype'], ['xhtml_trans', 'xhtml_basic', 'html5'], true); 175 176 $target = ''; 177 if (isset($conf[$name])) { 178 $target = $conf[$name]; 179 } elseif ($targetAttributeAllowed && !$conf['directImageLink']) { 180 $target = $fallbackTarget; 181 } 182 if (isset($conf[$name . '.']) && $conf[$name . '.']) { 183 $target = (string)$this->contentObjectRenderer->stdWrap($target, $conf[$name . '.'] ?? []); 184 } 185 return $target; 186 } 187 188 /** 189 * Loops over all configured URL modifier hooks (if available) and returns the generated URL or NULL if no URL was generated. 190 * 191 * @param string $context The context in which the method is called (e.g. typoLink). 192 * @param string $url The URL that should be processed. 193 * @param array $typolinkConfiguration The current link configuration array. 194 * @return string|null Returns NULL if URL was not processed or the processed URL as a string. 195 * @throws \RuntimeException if a hook was registered but did not fulfill the correct parameters. 196 */ 197 protected function processUrl(string $context, string $url, array $typolinkConfiguration = []) 198 { 199 $urlProcessors = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['urlProcessing']['urlProcessors'] ?? false; 200 if (!$urlProcessors) { 201 return $url; 202 } 203 204 foreach ($urlProcessors as $identifier => $configuration) { 205 if (empty($configuration) || !is_array($configuration)) { 206 throw new \RuntimeException('Missing configuration for URI processor "' . $identifier . '".', 1491130459); 207 } 208 if (!is_string($configuration['processor']) || empty($configuration['processor']) || !class_exists($configuration['processor']) || !is_subclass_of($configuration['processor'], UrlProcessorInterface::class)) { 209 throw new \RuntimeException('The URI processor "' . $identifier . '" defines an invalid provider. Ensure the class exists and implements the "' . UrlProcessorInterface::class . '".', 1491130460); 210 } 211 } 212 213 $orderedProcessors = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($urlProcessors); 214 $keepProcessing = true; 215 216 foreach ($orderedProcessors as $configuration) { 217 /** @var UrlProcessorInterface $urlProcessor */ 218 $urlProcessor = GeneralUtility::makeInstance($configuration['processor']); 219 $url = $urlProcessor->process($context, $url, $typolinkConfiguration, $this->contentObjectRenderer, $keepProcessing); 220 if (!$keepProcessing) { 221 break; 222 } 223 } 224 225 return $url; 226 } 227 228 /** 229 * @return TypoScriptFrontendController 230 */ 231 public function getTypoScriptFrontendController(): TypoScriptFrontendController 232 { 233 if ($this->typoScriptFrontendController instanceof TypoScriptFrontendController) { 234 return $this->typoScriptFrontendController; 235 } 236 237 // This usually happens when typolink is created by the TYPO3 Backend, where no TSFE object 238 // is there. This functionality is currently completely internal, as these links cannot be 239 // created properly from the Backend. 240 // However, this is added to avoid any exceptions when trying to create a link 241 $this->typoScriptFrontendController = GeneralUtility::makeInstance( 242 TypoScriptFrontendController::class, 243 null, 244 GeneralUtility::_GP('id'), 245 (int)GeneralUtility::_GP('type') 246 ); 247 $this->typoScriptFrontendController->sys_page = GeneralUtility::makeInstance(PageRepository::class); 248 $this->typoScriptFrontendController->tmpl = GeneralUtility::makeInstance(TemplateService::class); 249 return $this->typoScriptFrontendController; 250 } 251} 252