[SECURITY] Mitigate phar stream wrapper
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Database / SoftReferenceIndex.php
1 <?php
2 namespace TYPO3\CMS\Core\Database;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Core\Environment;
18 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
19 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
20 use TYPO3\CMS\Core\LinkHandling\LinkService;
21 use TYPO3\CMS\Core\Resource\File;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23 use TYPO3\CMS\Core\Utility\PathUtility;
24 use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
25
26 /**
27 * Soft Reference processing class
28 * "Soft References" are references to database elements, files, email addresses, URls etc.
29 * which are found in-text in content. The <link [page_id]> tag from typical bodytext fields
30 * are an example of this.
31 * This class contains generic parsers for the most well-known types
32 * which are default for most TYPO3 installations. Soft References can also be userdefined.
33 * The Soft Reference parsers are used by the system to find these references and process them accordingly in import/export actions and copy operations.
34 *
35 * Example of usage
36 * Soft References:
37 * if ($conf['softref'] && (strong)$value !== '')) { // Check if a TCA configured field has softreferences defined (see TYPO3 Core API document)
38 * $softRefs = \TYPO3\CMS\Backend\Utility\BackendUtility::explodeSoftRefParserList($conf['softref']); // Explode the list of softreferences/parameters
39 * if ($softRefs !== FALSE) { // If there are soft references
40 * foreach($softRefs as $spKey => $spParams) { // Traverse soft references
41 * $softRefObj = \TYPO3\CMS\Backend\Utility\BackendUtility::softRefParserObj($spKey); // create / get object
42 * if (is_object($softRefObj)) { // If there was an object returned...:
43 * $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams); // Do processing
44 *
45 * Result Array:
46 * The Result array should contain two keys: "content" and "elements".
47 * "content" is a string containing the input content but possibly with tokens inside.
48 * Tokens are strings like {softref:[tokenID]} which is a placeholder for a value extracted by a softref parser
49 * For each token there MUST be an entry in the "elements" key which has a "subst" key defining the tokenID and the tokenValue. See below.
50 * "elements" is an array where the keys are insignificant, but the values are arrays with these keys:
51 * "matchString" => The value of the match. This is only for informational purposes to show what was found.
52 * "error" => An error message can be set here, like "file not found" etc.
53 * "subst" => array( // If this array is found there MUST be a token in the output content as well!
54 * "tokenID" => The tokenID string corresponding to the token in output content, {softref:[tokenID]}. This is typically an md5 hash of a string defining uniquely the position of the element.
55 * "tokenValue" => The value that the token substitutes in the text. Basically, if this value is inserted instead of the token the content should match what was inputted originally.
56 * "type" => file / db / string = the type of substitution. "file" means it is a relative file [automatically mapped], "db" means a database record reference [automatically mapped], "string" means it is manually modified string content (eg. an email address)
57 * "relFileName" => (for "file" type): Relative filename. May not necessarily exist. This could be noticed in the error key.
58 * "recordRef" => (for "db" type) : Reference to DB record on the form [table]:[uid]. May not necessarily exist.
59 * "title" => Title of element (for backend information)
60 * "description" => Description of element (for backend information)
61 * )
62 */
63 /**
64 * Class for processing of the default soft reference types for CMS:
65 *
66 * - 'substitute' : A full field value targeted for manual substitution (for import /export features)
67 * - 'notify' : Just report if a value is found, nothing more.
68 * - 'images' : HTML <img> tags for RTE images
69 * - 'typolink' : references to page id or file, possibly with anchor/target, possibly commaseparated list.
70 * - 'typolink_tag' : As typolink, but searching for <link> tag to encapsulate it.
71 * - 'email' : Email highlight
72 * - 'url' : URL highlights (with a scheme)
73 */
74 class SoftReferenceIndex
75 {
76 /**
77 * @var string
78 */
79 public $tokenID_basePrefix = '';
80
81 /**
82 * Main function through which all processing happens
83 *
84 * @param string $table Database table name
85 * @param string $field Field name for which processing occurs
86 * @param int $uid UID of the record
87 * @param string $content The content/value of the field
88 * @param string $spKey The softlink parser key. This is only interesting if more than one parser is grouped in the same class. That is the case with this parser.
89 * @param array $spParams Parameters of the softlink parser. Basically this is the content inside optional []-brackets after the softref keys. Parameters are exploded by ";
90 * @param string $structurePath If running from inside a FlexForm structure, this is the path of the tag.
91 * @return array|bool Result array on positive matches, see description above. Otherwise FALSE
92 */
93 public function findRef($table, $field, $uid, $content, $spKey, $spParams, $structurePath = '')
94 {
95 $retVal = false;
96 $this->tokenID_basePrefix = $table . ':' . $uid . ':' . $field . ':' . $structurePath . ':' . $spKey;
97 switch ($spKey) {
98 case 'notify':
99 // Simple notification
100 $resultArray = [
101 'elements' => [
102 [
103 'matchString' => $content
104 ]
105 ]
106 ];
107 $retVal = $resultArray;
108 break;
109 case 'substitute':
110 $tokenID = $this->makeTokenID();
111 $resultArray = [
112 'content' => '{softref:' . $tokenID . '}',
113 'elements' => [
114 [
115 'matchString' => $content,
116 'subst' => [
117 'type' => 'string',
118 'tokenID' => $tokenID,
119 'tokenValue' => $content
120 ]
121 ]
122 ]
123 ];
124 $retVal = $resultArray;
125 break;
126 case 'images':
127 $retVal = $this->findRef_images($content, $spParams);
128 break;
129 case 'typolink':
130 $retVal = $this->findRef_typolink($content, $spParams);
131 break;
132 case 'typolink_tag':
133 $retVal = $this->findRef_typolink_tag($content, $spParams);
134 break;
135 case 'ext_fileref':
136 $retVal = $this->findRef_extension_fileref($content, $spParams);
137 break;
138 case 'email':
139 $retVal = $this->findRef_email($content, $spParams);
140 break;
141 case 'url':
142 $retVal = $this->findRef_url($content, $spParams);
143 break;
144 default:
145 $retVal = false;
146 }
147 return $retVal;
148 }
149
150 /**
151 * Finding image tags in the content.
152 * All images that are not from external URLs will be returned with an info text
153 * Will only return files in uploads/ folders which are prefixed with "RTEmagic[C|P]_" for substitution
154 * Any "clear.gif" images are ignored.
155 *
156 * @param string $content The input content to analyse
157 * @param array $spParams Parameters set for the softref parser key in TCA/columns
158 * @return array Result array on positive matches, see description above. Otherwise FALSE
159 */
160 public function findRef_images($content, $spParams)
161 {
162 // Start HTML parser and split content by image tag:
163 $htmlParser = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Html\HtmlParser::class);
164 $splitContent = $htmlParser->splitTags('img', $content);
165 $elements = [];
166 // Traverse splitted parts:
167 foreach ($splitContent as $k => $v) {
168 if ($k % 2) {
169 // Get file reference:
170 $attribs = $htmlParser->get_tag_attributes($v);
171 $srcRef = htmlspecialchars_decode($attribs[0]['src']);
172 $pI = pathinfo($srcRef);
173 // If it looks like a local image, continue. Otherwise ignore it.
174 $absPath = GeneralUtility::getFileAbsFileName(Environment::getPublicPath() . '/' . $srcRef);
175 if (!$pI['scheme'] && !$pI['query'] && $absPath && $srcRef !== 'clear.gif') {
176 // Initialize the element entry with info text here:
177 $tokenID = $this->makeTokenID($k);
178 $elements[$k] = [];
179 $elements[$k]['matchString'] = $v;
180 // If the image seems to be an RTE image, then proceed to set up substitution token:
181 if (GeneralUtility::isFirstPartOfStr($srcRef, 'uploads/') && preg_match('/^RTEmagicC_/', PathUtility::basename($srcRef))) {
182 // Token and substitute value:
183 // Make sure the value we work on is found and will get substituted in the content (Very important that the src-value is not DeHSC'ed)
184 if (strstr($splitContent[$k], $attribs[0]['src'])) {
185 // Substitute value with token (this is not be an exact method if the value is in there twice, but we assume it will not)
186 $splitContent[$k] = str_replace($attribs[0]['src'], '{softref:' . $tokenID . '}', $splitContent[$k]);
187 $elements[$k]['subst'] = [
188 'type' => 'file',
189 'relFileName' => $srcRef,
190 'tokenID' => $tokenID,
191 'tokenValue' => $attribs[0]['src']
192 ];
193 // Finally, notice if the file does not exist.
194 if (!@is_file($absPath)) {
195 $elements[$k]['error'] = 'File does not exist!';
196 }
197 } else {
198 $elements[$k]['error'] = 'Could not substitute image source with token!';
199 }
200 }
201 }
202 }
203 }
204 // Return result:
205 if (!empty($elements)) {
206 $resultArray = [
207 'content' => implode('', $splitContent),
208 'elements' => $elements
209 ];
210 return $resultArray;
211 }
212 }
213
214 /**
215 * TypoLink value processing.
216 * Will process input value as a TypoLink value.
217 *
218 * @param string $content The input content to analyse
219 * @param array $spParams Parameters set for the softref parser key in TCA/columns. value "linkList" will split the string by comma before processing.
220 * @return array Result array on positive matches, see description above. Otherwise FALSE
221 * @see \TYPO3\CMS\Frontend\ContentObject::typolink(), getTypoLinkParts()
222 */
223 public function findRef_typolink($content, $spParams)
224 {
225 // First, split the input string by a comma if the "linkList" parameter is set.
226 // An example: the link field for images in content elements of type "textpic" or "image". This field CAN be configured to define a link per image, separated by comma.
227 if (is_array($spParams) && in_array('linkList', $spParams)) {
228 // Preserving whitespace on purpose.
229 $linkElement = explode(',', $content);
230 } else {
231 // If only one element, just set in this array to make it easy below.
232 $linkElement = [$content];
233 }
234 // Traverse the links now:
235 $elements = [];
236 foreach ($linkElement as $k => $typolinkValue) {
237 $tLP = $this->getTypoLinkParts($typolinkValue);
238 $linkElement[$k] = $this->setTypoLinkPartsElement($tLP, $elements, $typolinkValue, $k);
239 }
240 // Return output:
241 if (!empty($elements)) {
242 $resultArray = [
243 'content' => implode(',', $linkElement),
244 'elements' => $elements
245 ];
246 return $resultArray;
247 }
248 }
249
250 /**
251 * TypoLink tag processing.
252 * Will search for <link ...> and <a> tags in the content string and process any found.
253 *
254 * @param string $content The input content to analyse
255 * @param array $spParams Parameters set for the softref parser key in TCA/columns
256 * @return array Result array on positive matches, see description above. Otherwise FALSE
257 * @see \TYPO3\CMS\Frontend\ContentObject::typolink(), getTypoLinkParts()
258 */
259 public function findRef_typolink_tag($content, $spParams)
260 {
261 // Parse string for special TYPO3 <link> tag:
262 $htmlParser = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Html\HtmlParser::class);
263 $linkService = GeneralUtility::makeInstance(LinkService::class);
264 $linkTags = $htmlParser->splitTags('a', $content);
265 // Traverse result:
266 $elements = [];
267 foreach ($linkTags as $key => $foundValue) {
268 if ($key % 2) {
269 if (preg_match('/href="([^"]+)"/', $foundValue, $matches)) {
270 try {
271 $linkDetails = $linkService->resolve($matches[1]);
272 if ($linkDetails['type'] === LinkService::TYPE_FILE && preg_match('/file\?uid=(\d+)/', $matches[1], $fileIdMatch)) {
273 $token = $this->makeTokenID($key);
274 $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]);
275 $elements[$key]['subst'] = [
276 'type' => 'db',
277 'recordRef' => 'sys_file:' . $fileIdMatch[1],
278 'tokenID' => $token,
279 'tokenValue' => 'file:' . ($linkDetails['file'] instanceof File ? $linkDetails['file']->getUid() : $fileIdMatch[1])
280 ];
281 } elseif ($linkDetails['type'] === LinkService::TYPE_PAGE && preg_match('/page\?uid=(\d+)#?(\d+)?/', $matches[1], $pageAndAnchorMatches)) {
282 $token = $this->makeTokenID($key);
283 $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]);
284 $elements[$key]['subst'] = [
285 'type' => 'db',
286 'recordRef' => 'pages:' . $linkDetails['pageuid'] . (isset($pageAndAnchorMatches[2]) ? '#c' . $pageAndAnchorMatches[2] : ''),
287 'tokenID' => $token,
288 'tokenValue' => $linkDetails['pageuid'] . (isset($pageAndAnchorMatches[2]) ? '#c' . $pageAndAnchorMatches[2] : '')
289 ];
290 } elseif ($linkDetails['type'] === LinkService::TYPE_URL) {
291 $token = $this->makeTokenID($key);
292 $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]);
293 $elements[$key]['subst'] = [
294 'type' => 'external',
295 'tokenID' => $token,
296 'tokenValue' => $linkDetails['url']
297 ];
298 }
299 } catch (\Exception $e) {
300 // skip invalid links
301 }
302 } else {
303 // keep the legacy code for now
304 $typolinkValue = preg_replace('/<LINK[[:space:]]+/i', '', substr($foundValue, 0, -1));
305 $tLP = $this->getTypoLinkParts($typolinkValue);
306 $linkTags[$k] = '<LINK ' . $this->setTypoLinkPartsElement($tLP, $elements, $typolinkValue, $k) . '>';
307 }
308 }
309 }
310 // Return output:
311 if (!empty($elements)) {
312 $resultArray = [
313 'content' => implode('', $linkTags),
314 'elements' => $elements
315 ];
316 return $resultArray;
317 }
318 }
319
320 /**
321 * Finding email addresses in content and making them substitutable.
322 *
323 * @param string $content The input content to analyse
324 * @param array $spParams Parameters set for the softref parser key in TCA/columns
325 * @return array Result array on positive matches, see description above. Otherwise FALSE
326 */
327 public function findRef_email($content, $spParams)
328 {
329 // Email:
330 $parts = preg_split('/([^[:alnum:]]+)([A-Za-z0-9\\._-]+[@][A-Za-z0-9\\._-]+[\\.].[A-Za-z0-9]+)/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
331 foreach ($parts as $idx => $value) {
332 if ($idx % 3 == 2) {
333 $tokenID = $this->makeTokenID($idx);
334 $elements[$idx] = [];
335 $elements[$idx]['matchString'] = $value;
336 if (is_array($spParams) && in_array('subst', $spParams)) {
337 $parts[$idx] = '{softref:' . $tokenID . '}';
338 $elements[$idx]['subst'] = [
339 'type' => 'string',
340 'tokenID' => $tokenID,
341 'tokenValue' => $value
342 ];
343 }
344 }
345 }
346 // Return output:
347 if (!empty($elements)) {
348 $resultArray = [
349 'content' => substr(implode('', $parts), 1, -1),
350 'elements' => $elements
351 ];
352 return $resultArray;
353 }
354 }
355
356 /**
357 * Finding URLs in content
358 *
359 * @param string $content The input content to analyse
360 * @param array $spParams Parameters set for the softref parser key in TCA/columns
361 * @return array Result array on positive matches, see description above. Otherwise FALSE
362 */
363 public function findRef_url($content, $spParams)
364 {
365 // URLs
366 $parts = preg_split('/([^[:alnum:]"\']+)((https?|ftp):\\/\\/[^[:space:]"\'<>]*)([[:space:]])/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
367 foreach ($parts as $idx => $value) {
368 if ($idx % 5 == 3) {
369 unset($parts[$idx]);
370 }
371 if ($idx % 5 == 2) {
372 $tokenID = $this->makeTokenID($idx);
373 $elements[$idx] = [];
374 $elements[$idx]['matchString'] = $value;
375 if (is_array($spParams) && in_array('subst', $spParams)) {
376 $parts[$idx] = '{softref:' . $tokenID . '}';
377 $elements[$idx]['subst'] = [
378 'type' => 'string',
379 'tokenID' => $tokenID,
380 'tokenValue' => $value
381 ];
382 }
383 }
384 }
385 // Return output:
386 if (!empty($elements)) {
387 $resultArray = [
388 'content' => substr(implode('', $parts), 1, -1),
389 'elements' => $elements
390 ];
391 return $resultArray;
392 }
393 }
394
395 /**
396 * Finding reference to files from extensions in content, but only to notify about their existence. No substitution
397 *
398 * @param string $content The input content to analyse
399 * @param array $spParams Parameters set for the softref parser key in TCA/columns
400 * @return array Result array on positive matches, see description above. Otherwise FALSE
401 */
402 public function findRef_extension_fileref($content, $spParams)
403 {
404 // Files starting with EXT:
405 $parts = preg_split('/([^[:alnum:]"\']+)(EXT:[[:alnum:]_]+\\/[^[:space:]"\',]*)/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
406 foreach ($parts as $idx => $value) {
407 if ($idx % 3 == 2) {
408 $this->makeTokenID($idx);
409 $elements[$idx] = [];
410 $elements[$idx]['matchString'] = $value;
411 }
412 }
413 // Return output:
414 if (!empty($elements)) {
415 $resultArray = [
416 'content' => substr(implode('', $parts), 1, -1),
417 'elements' => $elements
418 ];
419 return $resultArray;
420 }
421 }
422
423 /*************************
424 *
425 * Helper functions
426 *
427 *************************/
428
429 /**
430 * Analyse content as a TypoLink value and return an array with properties.
431 * TypoLinks format is: <link [typolink] [browser target] [css class] [title attribute] [additionalParams]>.
432 * See TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink()
433 * The syntax of the [typolink] part is: [typolink] = [page id or alias][,[type value]][#[anchor, if integer = tt_content uid]]
434 * The extraction is based on how \TYPO3\CMS\Frontend\ContentObject::typolink() behaves.
435 *
436 * @param string $typolinkValue TypoLink value.
437 * @return array Array with the properties of the input link specified. The key "LINK_TYPE" will reveal the type. If that is blank it could not be determined.
438 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink(), setTypoLinkPartsElement()
439 */
440 public function getTypoLinkParts($typolinkValue)
441 {
442 $finalTagParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($typolinkValue);
443
444 $link_param = $finalTagParts['url'];
445 // we define various keys below, "url" might be misleading
446 unset($finalTagParts['url']);
447
448 if (stripos(rawurldecode(trim($link_param)), 'phar://') === 0) {
449 throw new \RuntimeException(
450 'phar scheme not allowed as soft reference target',
451 1530030672
452 );
453 }
454
455 // Parse URL:
456 $pU = @parse_url($link_param);
457
458 // If it's a mail address:
459 if (strstr($link_param, '@') && !$pU['scheme']) {
460 $link_param = preg_replace('/^mailto:/i', '', $link_param);
461 $finalTagParts['LINK_TYPE'] = 'mailto';
462 $finalTagParts['url'] = trim($link_param);
463 return $finalTagParts;
464 }
465
466 if ($pU['scheme'] === 't3' && $pU['host'] === LinkService::TYPE_RECORD) {
467 $finalTagParts['LINK_TYPE'] = LinkService::TYPE_RECORD;
468 $finalTagParts['url'] = $link_param;
469 }
470
471 list($linkHandlerKeyword, $linkHandlerValue) = explode(':', trim($link_param), 2);
472
473 // Dispatch available signal slots.
474 $linkHandlerFound = false;
475 list($linkHandlerFound, $finalTagParts) = $this->emitGetTypoLinkParts($linkHandlerFound, $finalTagParts, $linkHandlerKeyword, $linkHandlerValue);
476 if ($linkHandlerFound) {
477 return $finalTagParts;
478 }
479
480 // Check for FAL link-handler keyword
481 if ($linkHandlerKeyword === 'file') {
482 $finalTagParts['LINK_TYPE'] = 'file';
483 $finalTagParts['identifier'] = trim($link_param);
484 return $finalTagParts;
485 }
486
487 $isLocalFile = 0;
488 $fileChar = (int)strpos($link_param, '/');
489 $urlChar = (int)strpos($link_param, '.');
490
491 // Detects if a file is found in site-root and if so it will be treated like a normal file.
492 list($rootFileDat) = explode('?', rawurldecode($link_param));
493 $containsSlash = strstr($rootFileDat, '/');
494 $rFD_fI = pathinfo($rootFileDat);
495 $fileExtension = strtolower($rFD_fI['extension']);
496 if (!$containsSlash && trim($rootFileDat) && (@is_file(Environment::getPublicPath() . '/' . $rootFileDat) || $fileExtension === 'php' || $fileExtension === 'html' || $fileExtension === 'htm')) {
497 $isLocalFile = 1;
498 } elseif ($containsSlash) {
499 // Adding this so realurl directories are linked right (non-existing).
500 $isLocalFile = 2;
501 }
502 if ($pU['scheme'] || ($isLocalFile != 1 && $urlChar && (!$containsSlash || $urlChar < $fileChar))) { // url (external): If doubleSlash or if a '.' comes before a '/'.
503 $finalTagParts['LINK_TYPE'] = 'url';
504 $finalTagParts['url'] = $link_param;
505 } elseif ($containsSlash || $isLocalFile) { // file (internal)
506 $splitLinkParam = explode('?', $link_param);
507 if (file_exists(rawurldecode($splitLinkParam[0])) || $isLocalFile) {
508 $finalTagParts['LINK_TYPE'] = 'file';
509 $finalTagParts['filepath'] = rawurldecode($splitLinkParam[0]);
510 $finalTagParts['query'] = $splitLinkParam[1];
511 }
512 } else {
513 // integer or alias (alias is without slashes or periods or commas, that is
514 // 'nospace,alphanum_x,lower,unique' according to definition in $GLOBALS['TCA']!)
515 $finalTagParts['LINK_TYPE'] = 'page';
516
517 $link_params_parts = explode('#', $link_param);
518 // Link-data del
519 $link_param = trim($link_params_parts[0]);
520
521 if ((string)$link_params_parts[1] !== '') {
522 $finalTagParts['anchor'] = trim($link_params_parts[1]);
523 }
524
525 // Splitting the parameter by ',' and if the array counts more than 1 element it's a id/type/? pair
526 $pairParts = GeneralUtility::trimExplode(',', $link_param);
527 if (count($pairParts) > 1) {
528 $link_param = $pairParts[0];
529 $finalTagParts['type'] = $pairParts[1]; // Overruling 'type'
530 }
531
532 // Checking if the id-parameter is an alias.
533 if ((string)$link_param !== '') {
534 if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($link_param)) {
535 $finalTagParts['alias'] = $link_param;
536 $link_param = $this->getPageIdFromAlias($link_param);
537 }
538
539 $finalTagParts['page_id'] = (int)$link_param;
540 }
541 }
542
543 return $finalTagParts;
544 }
545
546 /**
547 * Recompile a TypoLink value from the array of properties made with getTypoLinkParts() into an elements array
548 *
549 * @param array $tLP TypoLink properties
550 * @param array $elements Array of elements to be modified with substitution / information entries.
551 * @param string $content The content to process.
552 * @param int $idx Index value of the found element - user to make unique but stable tokenID
553 * @return string The input content, possibly containing tokens now according to the added substitution entries in $elements
554 * @see getTypoLinkParts()
555 */
556 public function setTypoLinkPartsElement($tLP, &$elements, $content, $idx)
557 {
558 // Initialize, set basic values. In any case a link will be shown
559 $tokenID = $this->makeTokenID('setTypoLinkPartsElement:' . $idx);
560 $elements[$tokenID . ':' . $idx] = [];
561 $elements[$tokenID . ':' . $idx]['matchString'] = $content;
562 // Based on link type, maybe do more:
563 switch ((string)$tLP['LINK_TYPE']) {
564 case 'mailto':
565
566 case 'url':
567 // Mail addresses and URLs can be substituted manually:
568 $elements[$tokenID . ':' . $idx]['subst'] = [
569 'type' => 'string',
570 'tokenID' => $tokenID,
571 'tokenValue' => $tLP['url']
572 ];
573 // Output content will be the token instead:
574 $content = '{softref:' . $tokenID . '}';
575 break;
576 case 'file':
577 // Process files referenced by their FAL uid
578 if ($tLP['identifier']) {
579 list($linkHandlerKeyword, $linkHandlerValue) = explode(':', trim($tLP['identifier']), 2);
580 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($linkHandlerValue)) {
581 // Token and substitute value
582 $elements[$tokenID . ':' . $idx]['subst'] = [
583 'type' => 'db',
584 'recordRef' => 'sys_file:' . $linkHandlerValue,
585 'tokenID' => $tokenID,
586 'tokenValue' => $tLP['identifier'],
587 ];
588 // Output content will be the token instead:
589 $content = '{softref:' . $tokenID . '}';
590 } else {
591 // This is a link to a folder...
592 return $content;
593 }
594 } else {
595 return $content;
596 }
597 break;
598 case 'page':
599 // Rebuild page reference typolink part:
600 $content = '';
601 // Set page id:
602 if ($tLP['page_id']) {
603 $content .= '{softref:' . $tokenID . '}';
604 $elements[$tokenID . ':' . $idx]['subst'] = [
605 'type' => 'db',
606 'recordRef' => 'pages:' . $tLP['page_id'],
607 'tokenID' => $tokenID,
608 'tokenValue' => $tLP['alias'] ? $tLP['alias'] : $tLP['page_id']
609 ];
610 }
611 // Add type if applicable
612 if ((string)$tLP['type'] !== '') {
613 $content .= ',' . $tLP['type'];
614 }
615 // Add anchor if applicable
616 if ((string)$tLP['anchor'] !== '') {
617 // Anchor is assumed to point to a content elements:
618 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($tLP['anchor'])) {
619 // Initialize a new entry because we have a new relation:
620 $newTokenID = $this->makeTokenID('setTypoLinkPartsElement:anchor:' . $idx);
621 $elements[$newTokenID . ':' . $idx] = [];
622 $elements[$newTokenID . ':' . $idx]['matchString'] = 'Anchor Content Element: ' . $tLP['anchor'];
623 $content .= '#{softref:' . $newTokenID . '}';
624 $elements[$newTokenID . ':' . $idx]['subst'] = [
625 'type' => 'db',
626 'recordRef' => 'tt_content:' . $tLP['anchor'],
627 'tokenID' => $newTokenID,
628 'tokenValue' => $tLP['anchor']
629 ];
630 } else {
631 // Anchor is a hardcoded string
632 $content .= '#' . $tLP['type'];
633 }
634 }
635 break;
636 case LinkService::TYPE_RECORD:
637 $elements[$tokenID . ':' . $idx]['subst'] = [
638 'type' => 'db',
639 'recordRef' => $tLP['table'] . ':' . $tLP['uid'],
640 'tokenID' => $tokenID,
641 'tokenValue' => $content,
642 ];
643
644 $content = '{softref:' . $tokenID . '}';
645 break;
646 default:
647 $linkHandlerFound = false;
648 list($linkHandlerFound, $tLP, $content, $newElements) = $this->emitSetTypoLinkPartsElement($linkHandlerFound, $tLP, $content, $elements, $idx, $tokenID);
649 // We need to merge the array, otherwise we would loose the reference.
650 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($elements, $newElements);
651
652 if (!$linkHandlerFound) {
653 $elements[$tokenID . ':' . $idx]['error'] = 'Couldn\'t decide typolink mode.';
654 return $content;
655 }
656 }
657 // Finally, for all entries that was rebuild with tokens, add target, class, title and additionalParams in the end:
658 $tLP['url'] = $content;
659 $content = GeneralUtility::makeInstance(TypoLinkCodecService::class)->encode($tLP);
660
661 // Return rebuilt typolink value:
662 return $content;
663 }
664
665 /**
666 * Look up and return page uid for alias
667 *
668 * @param string $link_param Page alias string value
669 * @return int Page uid corresponding to alias value.
670 */
671 public function getPageIdFromAlias($link_param)
672 {
673 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
674 $queryBuilder->getRestrictions()
675 ->removeAll()
676 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
677 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
678
679 $pageUid = $queryBuilder->select('uid')
680 ->from('pages')
681 ->where(
682 $queryBuilder->expr()->eq('alias', $queryBuilder->createNamedParameter($link_param, \PDO::PARAM_STR))
683 )
684 ->setMaxResults(1)
685 ->execute()
686 ->fetchColumn(0);
687
688 return (int)$pageUid;
689 }
690
691 /**
692 * Make Token ID for input index.
693 *
694 * @param string $index Suffix value.
695 * @return string Token ID
696 */
697 public function makeTokenID($index = '')
698 {
699 return md5($this->tokenID_basePrefix . ':' . $index);
700 }
701
702 /**
703 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
704 */
705 protected function getSignalSlotDispatcher()
706 {
707 return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
708 }
709
710 /**
711 * @param bool $linkHandlerFound
712 * @param array $finalTagParts
713 * @param string $linkHandlerKeyword
714 * @param string $linkHandlerValue
715 * @return array
716 */
717 protected function emitGetTypoLinkParts($linkHandlerFound, $finalTagParts, $linkHandlerKeyword, $linkHandlerValue)
718 {
719 return $this->getSignalSlotDispatcher()->dispatch(static::class, 'getTypoLinkParts', [$linkHandlerFound, $finalTagParts, $linkHandlerKeyword, $linkHandlerValue]);
720 }
721
722 /**
723 * @param bool $linkHandlerFound
724 * @param array $tLP
725 * @param string $content
726 * @param array $elements
727 * @param int $idx
728 * @param string $tokenID
729 * @return array
730 */
731 protected function emitSetTypoLinkPartsElement($linkHandlerFound, $tLP, $content, $elements, $idx, $tokenID)
732 {
733 return $this->getSignalSlotDispatcher()->dispatch(static::class, 'setTypoLinkPartsElement', [$linkHandlerFound, $tLP, $content, $elements, $idx, $tokenID, $this]);
734 }
735 }