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