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