1<?php 2 3/* 4 * This file is part of the TYPO3 CMS project. 5 * 6 * It is free software; you can redistribute it and/or modify it under 7 * the terms of the GNU General Public License, either version 2 8 * of the License, or any later version. 9 * 10 * For the full copyright and license information, please read the 11 * LICENSE.txt file that was distributed with this source code. 12 * 13 * The TYPO3 project - inspiring people to share! 14 */ 15 16namespace TYPO3\CMS\Core\Database; 17 18use Psr\EventDispatcher\EventDispatcherInterface; 19use TYPO3\CMS\Backend\Utility\BackendUtility; 20use TYPO3\CMS\Core\DataHandling\Event\AppendLinkHandlerElementsEvent; 21use TYPO3\CMS\Core\Html\HtmlParser; 22use TYPO3\CMS\Core\LinkHandling\Exception\UnknownLinkHandlerException; 23use TYPO3\CMS\Core\LinkHandling\LinkService; 24use TYPO3\CMS\Core\Resource\File; 25use TYPO3\CMS\Core\Resource\FileInterface; 26use TYPO3\CMS\Core\SingletonInterface; 27use TYPO3\CMS\Core\Utility\GeneralUtility; 28use TYPO3\CMS\Core\Utility\MathUtility; 29use TYPO3\CMS\Frontend\Service\TypoLinkCodecService; 30 31/** 32 * Soft Reference processing class 33 * "Soft References" are references to database elements, files, email addresses, URls etc. 34 * which are found in-text in content. The <link [page_id]> tag from typical bodytext fields 35 * are an example of this. 36 * This class contains generic parsers for the most well-known types 37 * which are default for most TYPO3 installations. Soft References can also be userdefined. 38 * The Soft Reference parsers are used by the system to find these references and process them accordingly in import/export actions and copy operations. 39 * 40 * Example of usage 41 * Soft References: 42 * if ($conf['softref'] && (strong)$value !== '')) { // Check if a TCA configured field has softreferences defined (see TYPO3 Core API document) 43 * $softRefs = \TYPO3\CMS\Backend\Utility\BackendUtility::explodeSoftRefParserList($conf['softref']); // Explode the list of softreferences/parameters 44 * if ($softRefs !== FALSE) { // If there are soft references 45 * foreach($softRefs as $spKey => $spParams) { // Traverse soft references 46 * $softRefObj = \TYPO3\CMS\Backend\Utility\BackendUtility::softRefParserObj($spKey); // create / get object 47 * if (is_object($softRefObj)) { // If there was an object returned...: 48 * $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams); // Do processing 49 * 50 * Result Array: 51 * The Result array should contain two keys: "content" and "elements". 52 * "content" is a string containing the input content but possibly with tokens inside. 53 * Tokens are strings like {softref:[tokenID]} which is a placeholder for a value extracted by a softref parser 54 * 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. 55 * "elements" is an array where the keys are insignificant, but the values are arrays with these keys: 56 * "matchString" => The value of the match. This is only for informational purposes to show what was found. 57 * "error" => An error message can be set here, like "file not found" etc. 58 * "subst" => array( // If this array is found there MUST be a token in the output content as well! 59 * "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. 60 * "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. 61 * "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) 62 * "relFileName" => (for "file" type): Relative filename. May not necessarily exist. This could be noticed in the error key. 63 * "recordRef" => (for "db" type) : Reference to DB record on the form [table]:[uid]. May not necessarily exist. 64 * "title" => Title of element (for backend information) 65 * "description" => Description of element (for backend information) 66 * ) 67 */ 68/** 69 * Class for processing of the default soft reference types for CMS: 70 * 71 * - 'substitute' : A full field value targeted for manual substitution (for import /export features) 72 * - 'notify' : Just report if a value is found, nothing more. 73 * - 'images' : HTML <img> tags for RTE images 74 * - 'typolink' : references to page id or file, possibly with anchor/target, possibly commaseparated list. 75 * - 'typolink_tag' : As typolink, but searching for <link> tag to encapsulate it. 76 * - 'email' : Email highlight 77 * - 'url' : URL highlights (with a scheme) 78 */ 79class SoftReferenceIndex implements SingletonInterface 80{ 81 /** 82 * @var string 83 */ 84 public $tokenID_basePrefix = ''; 85 86 /** 87 * @var EventDispatcherInterface 88 */ 89 protected $eventDispatcher; 90 91 /** 92 * @var int 93 */ 94 private $referenceUid = 0; 95 96 /** 97 * @var string 98 */ 99 private $referenceTable = ''; 100 101 public function __construct(EventDispatcherInterface $eventDispatcher) 102 { 103 $this->eventDispatcher = $eventDispatcher; 104 } 105 106 /** 107 * Main function through which all processing happens 108 * 109 * @param string $table Database table name 110 * @param string $field Field name for which processing occurs 111 * @param int $uid UID of the record 112 * @param string $content The content/value of the field 113 * @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. 114 * @param array $spParams Parameters of the softlink parser. Basically this is the content inside optional []-brackets after the softref keys. Parameters are exploded by "; 115 * @param string $structurePath If running from inside a FlexForm structure, this is the path of the tag. 116 * @return array|bool|null Result array on positive matches, see description above. Otherwise FALSE or null 117 */ 118 public function findRef($table, $field, $uid, $content, $spKey, $spParams, $structurePath = '') 119 { 120 $this->referenceUid = $uid; 121 $this->referenceTable = $table; 122 $this->tokenID_basePrefix = $table . ':' . $uid . ':' . $field . ':' . $structurePath . ':' . $spKey; 123 switch ($spKey) { 124 case 'notify': 125 // Simple notification 126 $resultArray = [ 127 'elements' => [ 128 [ 129 'matchString' => $content 130 ] 131 ] 132 ]; 133 $retVal = $resultArray; 134 break; 135 case 'substitute': 136 $tokenID = $this->makeTokenID(); 137 $resultArray = [ 138 'content' => '{softref:' . $tokenID . '}', 139 'elements' => [ 140 [ 141 'matchString' => $content, 142 'subst' => [ 143 'type' => 'string', 144 'tokenID' => $tokenID, 145 'tokenValue' => $content 146 ] 147 ] 148 ] 149 ]; 150 $retVal = $resultArray; 151 break; 152 case 'typolink': 153 $retVal = $this->findRef_typolink($content, $spParams); 154 break; 155 case 'typolink_tag': 156 $retVal = $this->findRef_typolink_tag($content); 157 break; 158 case 'ext_fileref': 159 $retVal = $this->findRef_extension_fileref($content); 160 break; 161 case 'email': 162 $retVal = $this->findRef_email($content, $spParams); 163 break; 164 case 'url': 165 $retVal = $this->findRef_url($content, $spParams); 166 break; 167 default: 168 $retVal = false; 169 } 170 $this->referenceUid = 0; 171 $this->referenceTable = ''; 172 return $retVal; 173 } 174 175 /** 176 * TypoLink value processing. 177 * Will process input value as a TypoLink value. 178 * 179 * @param string $content The input content to analyze 180 * @param array $spParams Parameters set for the softref parser key in TCA/columns. value "linkList" will split the string by comma before processing. 181 * @return array|null Result array on positive matches, see description above. Otherwise null 182 * @see \TYPO3\CMS\Frontend\ContentObject::typolink() 183 * @see getTypoLinkParts() 184 */ 185 public function findRef_typolink($content, $spParams) 186 { 187 // First, split the input string by a comma if the "linkList" parameter is set. 188 // 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. 189 if (is_array($spParams) && in_array('linkList', $spParams)) { 190 // Preserving whitespace on purpose. 191 $linkElement = explode(',', $content); 192 } else { 193 // If only one element, just set in this array to make it easy below. 194 $linkElement = [$content]; 195 } 196 // Traverse the links now: 197 $elements = []; 198 foreach ($linkElement as $k => $typolinkValue) { 199 $tLP = $this->getTypoLinkParts($typolinkValue); 200 $linkElement[$k] = $this->setTypoLinkPartsElement($tLP, $elements, $typolinkValue, $k); 201 } 202 // Return output: 203 if (!empty($elements)) { 204 $resultArray = [ 205 'content' => implode(',', $linkElement), 206 'elements' => $elements 207 ]; 208 return $resultArray; 209 } 210 211 return null; 212 } 213 214 /** 215 * TypoLink tag processing. 216 * Will search for <link ...> and <a> tags in the content string and process any found. 217 * 218 * @param string $content The input content to analyze 219 * @return array|null Result array on positive matches, see description above. Otherwise null 220 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink() 221 * @see getTypoLinkParts() 222 */ 223 public function findRef_typolink_tag($content) 224 { 225 // Parse string for special TYPO3 <link> tag: 226 $htmlParser = GeneralUtility::makeInstance(HtmlParser::class); 227 $linkService = GeneralUtility::makeInstance(LinkService::class); 228 $linkTags = $htmlParser->splitTags('a', $content); 229 // Traverse result: 230 $elements = []; 231 foreach ($linkTags as $key => $foundValue) { 232 if ($key % 2) { 233 if (preg_match('/href="([^"]+)"/', $foundValue, $matches)) { 234 try { 235 $linkDetails = $linkService->resolve($matches[1]); 236 if ($linkDetails['type'] === LinkService::TYPE_FILE && preg_match('/file\?uid=(\d+)/', $matches[1], $fileIdMatch)) { 237 $token = $this->makeTokenID($key); 238 $elements[$key]['matchString'] = $linkTags[$key]; 239 $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]); 240 $elements[$key]['subst'] = [ 241 'type' => 'db', 242 'recordRef' => 'sys_file:' . $fileIdMatch[1], 243 'tokenID' => $token, 244 'tokenValue' => 'file:' . ($linkDetails['file'] instanceof File ? $linkDetails['file']->getUid() : $fileIdMatch[1]) 245 ]; 246 } elseif ($linkDetails['type'] === LinkService::TYPE_PAGE && preg_match('/page\?uid=(\d+)#?(\d+)?/', $matches[1], $pageAndAnchorMatches)) { 247 $token = $this->makeTokenID($key); 248 $content = '{softref:' . $token . '}'; 249 $elements[$key]['matchString'] = $linkTags[$key]; 250 $elements[$key]['subst'] = [ 251 'type' => 'db', 252 'recordRef' => 'pages:' . $linkDetails['pageuid'], 253 'tokenID' => $token, 254 'tokenValue' => $linkDetails['pageuid'] 255 ]; 256 if (isset($pageAndAnchorMatches[2]) && $pageAndAnchorMatches[2] !== '') { 257 // Anchor is assumed to point to a content elements: 258 if (MathUtility::canBeInterpretedAsInteger($pageAndAnchorMatches[2])) { 259 // Initialize a new entry because we have a new relation: 260 $newTokenID = $this->makeTokenID('setTypoLinkPartsElement:anchor:' . $key); 261 $elements[$newTokenID . ':' . $key] = []; 262 $elements[$newTokenID . ':' . $key]['matchString'] = 'Anchor Content Element: ' . $pageAndAnchorMatches[2]; 263 $content .= '#{softref:' . $newTokenID . '}'; 264 $elements[$newTokenID . ':' . $key]['subst'] = [ 265 'type' => 'db', 266 'recordRef' => 'tt_content:' . $pageAndAnchorMatches[2], 267 'tokenID' => $newTokenID, 268 'tokenValue' => $pageAndAnchorMatches[2] 269 ]; 270 } else { 271 // Anchor is a hardcoded string 272 $content .= '#' . $pageAndAnchorMatches[2]; 273 } 274 } 275 $linkTags[$key] = str_replace($matches[1], $content, $linkTags[$key]); 276 } elseif ($linkDetails['type'] === LinkService::TYPE_URL) { 277 $token = $this->makeTokenID($key); 278 $elements[$key]['matchString'] = $linkTags[$key]; 279 $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]); 280 $elements[$key]['subst'] = [ 281 'type' => 'external', 282 'tokenID' => $token, 283 'tokenValue' => $linkDetails['url'] 284 ]; 285 } elseif ($linkDetails['type'] === LinkService::TYPE_EMAIL) { 286 $token = $this->makeTokenID($key); 287 $elements[$key]['matchString'] = $linkTags[$key]; 288 $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]); 289 $elements[$key]['subst'] = [ 290 'type' => 'string', 291 'tokenID' => $token, 292 'tokenValue' => $linkDetails['email'] 293 ]; 294 } elseif ($linkDetails['type'] === LinkService::TYPE_TELEPHONE) { 295 $token = $this->makeTokenID($key); 296 $elements[$key]['matchString'] = $linkTags[$key]; 297 $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]); 298 $elements[$key]['subst'] = [ 299 'type' => 'string', 300 'tokenID' => $token, 301 'tokenValue' => $linkDetails['telephone'] 302 ]; 303 } 304 } catch (\Exception $e) { 305 // skip invalid links 306 } 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 return null; 320 } 321 322 /** 323 * Finding email addresses in content and making them substitutable. 324 * 325 * @param string $content The input content to analyze 326 * @param array $spParams Parameters set for the softref parser key in TCA/columns 327 * @return array|null Result array on positive matches, see description above. Otherwise null 328 */ 329 public function findRef_email($content, $spParams) 330 { 331 $elements = []; 332 // Email: 333 $parts = preg_split('/([^[:alnum:]]+)([A-Za-z0-9\\._-]+[@][A-Za-z0-9\\._-]+[\\.].[A-Za-z0-9]+)/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE); 334 foreach ($parts as $idx => $value) { 335 if ($idx % 3 == 2) { 336 $tokenID = $this->makeTokenID($idx); 337 $elements[$idx] = []; 338 $elements[$idx]['matchString'] = $value; 339 if (is_array($spParams) && in_array('subst', $spParams)) { 340 $parts[$idx] = '{softref:' . $tokenID . '}'; 341 $elements[$idx]['subst'] = [ 342 'type' => 'string', 343 'tokenID' => $tokenID, 344 'tokenValue' => $value 345 ]; 346 } 347 } 348 } 349 // Return output: 350 if (!empty($elements)) { 351 $resultArray = [ 352 'content' => substr(implode('', $parts), 1, -1), 353 'elements' => $elements 354 ]; 355 return $resultArray; 356 } 357 358 return null; 359 } 360 361 /** 362 * Finding URLs in content 363 * 364 * @param string $content The input content to analyze 365 * @param array $spParams Parameters set for the softref parser key in TCA/columns 366 * @return array|null Result array on positive matches, see description above. Otherwise null 367 */ 368 public function findRef_url($content, $spParams) 369 { 370 $elements = []; 371 // URLs 372 $parts = preg_split('/([^[:alnum:]"\']+)((https?|ftp):\\/\\/[^[:space:]"\'<>]*)([[:space:]])/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE); 373 foreach ($parts as $idx => $value) { 374 if ($idx % 5 == 3) { 375 unset($parts[$idx]); 376 } 377 if ($idx % 5 == 2) { 378 $tokenID = $this->makeTokenID($idx); 379 $elements[$idx] = []; 380 $elements[$idx]['matchString'] = $value; 381 if (is_array($spParams) && in_array('subst', $spParams)) { 382 $parts[$idx] = '{softref:' . $tokenID . '}'; 383 $elements[$idx]['subst'] = [ 384 'type' => 'string', 385 'tokenID' => $tokenID, 386 'tokenValue' => $value 387 ]; 388 } 389 } 390 } 391 // Return output: 392 if (!empty($elements)) { 393 $resultArray = [ 394 'content' => substr(implode('', $parts), 1, -1), 395 'elements' => $elements 396 ]; 397 return $resultArray; 398 } 399 400 return null; 401 } 402 403 /** 404 * Finding reference to files from extensions in content, but only to notify about their existence. No substitution 405 * 406 * @param string $content The input content to analyze 407 * @return array|null Result array on positive matches, see description above. Otherwise null 408 */ 409 public function findRef_extension_fileref($content) 410 { 411 $elements = []; 412 // Files starting with EXT: 413 $parts = preg_split('/([^[:alnum:]"\']+)(EXT:[[:alnum:]_]+\\/[^[:space:]"\',]*)/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE) ?: []; 414 foreach ($parts as $idx => $value) { 415 if ($idx % 3 == 2) { 416 $this->makeTokenID((string)$idx); 417 $elements[$idx] = []; 418 $elements[$idx]['matchString'] = $value; 419 } 420 } 421 // Return output: 422 if (!empty($elements)) { 423 $resultArray = [ 424 'content' => substr(implode('', $parts), 1, -1), 425 'elements' => $elements 426 ]; 427 return $resultArray; 428 } 429 430 return null; 431 } 432 433 /************************* 434 * 435 * Helper functions 436 * 437 *************************/ 438 439 /** 440 * Analyze content as a TypoLink value and return an array with properties. 441 * TypoLinks format is: <link [typolink] [browser target] [css class] [title attribute] [additionalParams]>. 442 * See TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink() 443 * The syntax of the [typolink] part is: [typolink] = [page id][,[type value]][#[anchor, if integer = tt_content uid]] 444 * The extraction is based on how \TYPO3\CMS\Frontend\ContentObject::typolink() behaves. 445 * 446 * @param string $typolinkValue TypoLink value. 447 * @return array Array with the properties of the input link specified. The key "type" will reveal the type. If that is blank it could not be determined. 448 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink() 449 * @see setTypoLinkPartsElement() 450 */ 451 public function getTypoLinkParts($typolinkValue) 452 { 453 $finalTagParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($typolinkValue); 454 455 $link_param = $finalTagParts['url']; 456 // we define various keys below, "url" might be misleading 457 unset($finalTagParts['url']); 458 459 if (stripos(rawurldecode(trim($link_param)), 'phar://') === 0) { 460 throw new \RuntimeException( 461 'phar scheme not allowed as soft reference target', 462 1530030672 463 ); 464 } 465 466 $linkService = GeneralUtility::makeInstance(LinkService::class); 467 try { 468 $linkData = $linkService->resolve($link_param); 469 switch ($linkData['type']) { 470 case LinkService::TYPE_RECORD: 471 $referencePageId = $this->referenceTable === 'pages' 472 ? $this->referenceUid 473 : (int)(BackendUtility::getRecord($this->referenceTable, $this->referenceUid)['pid'] ?? 0); 474 if ($referencePageId) { 475 $pageTsConfig = BackendUtility::getPagesTSconfig($referencePageId); 476 $table = $pageTsConfig['TCEMAIN.']['linkHandler.'][$linkData['identifier'] . '.']['configuration.']['table'] ?? $linkData['identifier']; 477 } else { 478 // Backwards compatibility for the old behaviour, where the identifier was saved as the table. 479 $table = $linkData['identifier']; 480 } 481 $finalTagParts['table'] = $table; 482 $finalTagParts['uid'] = $linkData['uid']; 483 break; 484 case LinkService::TYPE_PAGE: 485 $linkData['pageuid'] = (int)$linkData['pageuid']; 486 if (isset($linkData['pagetype'])) { 487 $linkData['pagetype'] = (int)$linkData['pagetype']; 488 } 489 if (isset($linkData['fragment'])) { 490 $finalTagParts['anchor'] = $linkData['fragment']; 491 } 492 break; 493 case LinkService::TYPE_FILE: 494 case LinkService::TYPE_UNKNOWN: 495 if (isset($linkData['file'])) { 496 $finalTagParts['type'] = LinkService::TYPE_FILE; 497 $linkData['file'] = $linkData['file'] instanceof FileInterface ? $linkData['file']->getUid() : $linkData['file']; 498 } else { 499 $pU = parse_url($link_param); 500 parse_str($pU['query'] ?? '', $query); 501 if (isset($query['uid'])) { 502 $finalTagParts['type'] = LinkService::TYPE_FILE; 503 $finalTagParts['file'] = (int)$query['uid']; 504 } 505 } 506 break; 507 } 508 return array_merge($finalTagParts, $linkData); 509 } catch (UnknownLinkHandlerException $e) { 510 // Cannot handle anything 511 return $finalTagParts; 512 } 513 } 514 515 /** 516 * Recompile a TypoLink value from the array of properties made with getTypoLinkParts() into an elements array 517 * 518 * @param array $tLP TypoLink properties 519 * @param array $elements Array of elements to be modified with substitution / information entries. 520 * @param string $content The content to process. 521 * @param int $idx Index value of the found element - user to make unique but stable tokenID 522 * @return string The input content, possibly containing tokens now according to the added substitution entries in $elements 523 * @see getTypoLinkParts() 524 */ 525 public function setTypoLinkPartsElement($tLP, &$elements, $content, $idx) 526 { 527 // Initialize, set basic values. In any case a link will be shown 528 $tokenID = $this->makeTokenID('setTypoLinkPartsElement:' . $idx); 529 $elements[$tokenID . ':' . $idx] = []; 530 $elements[$tokenID . ':' . $idx]['matchString'] = $content; 531 // Based on link type, maybe do more: 532 switch ((string)$tLP['type']) { 533 case LinkService::TYPE_EMAIL: 534 // Mail addresses can be substituted manually: 535 $elements[$tokenID . ':' . $idx]['subst'] = [ 536 'type' => 'string', 537 'tokenID' => $tokenID, 538 'tokenValue' => $tLP['email'] 539 ]; 540 // Output content will be the token instead: 541 $content = '{softref:' . $tokenID . '}'; 542 break; 543 case LinkService::TYPE_TELEPHONE: 544 // phone number can be substituted manually: 545 $elements[$tokenID . ':' . $idx]['subst'] = [ 546 'type' => 'string', 547 'tokenID' => $tokenID, 548 'tokenValue' => $tLP['telephone'] 549 ]; 550 // Output content will be the token instead: 551 $content = '{softref:' . $tokenID . '}'; 552 break; 553 case LinkService::TYPE_URL: 554 // URLs can be substituted manually 555 $elements[$tokenID . ':' . $idx]['subst'] = [ 556 'type' => 'external', 557 'tokenID' => $tokenID, 558 'tokenValue' => $tLP['url'] 559 ]; 560 // Output content will be the token instead: 561 $content = '{softref:' . $tokenID . '}'; 562 break; 563 case LinkService::TYPE_FOLDER: 564 // This is a link to a folder... 565 unset($elements[$tokenID . ':' . $idx]); 566 return $content; 567 case LinkService::TYPE_FILE: 568 // Process files referenced by their FAL uid 569 if (isset($tLP['file'])) { 570 $fileId = $tLP['file'] instanceof FileInterface ? $tLP['file']->getUid() : $tLP['file']; 571 // Token and substitute value 572 $elements[$tokenID . ':' . $idx]['subst'] = [ 573 'type' => 'db', 574 'recordRef' => 'sys_file:' . $fileId, 575 'tokenID' => $tokenID, 576 'tokenValue' => 'file:' . $fileId, 577 ]; 578 // Output content will be the token instead: 579 $content = '{softref:' . $tokenID . '}'; 580 } elseif ($tLP['identifier']) { 581 [$linkHandlerKeyword, $linkHandlerValue] = explode(':', trim($tLP['identifier']), 2); 582 if (MathUtility::canBeInterpretedAsInteger($linkHandlerValue)) { 583 // Token and substitute value 584 $elements[$tokenID . ':' . $idx]['subst'] = [ 585 'type' => 'db', 586 'recordRef' => 'sys_file:' . $linkHandlerValue, 587 'tokenID' => $tokenID, 588 'tokenValue' => $tLP['identifier'], 589 ]; 590 // Output content will be the token instead: 591 $content = '{softref:' . $tokenID . '}'; 592 } else { 593 // This is a link to a folder... 594 return $content; 595 } 596 } else { 597 return $content; 598 } 599 break; 600 case LinkService::TYPE_PAGE: 601 // Rebuild page reference typolink part: 602 $content = ''; 603 // Set page id: 604 if ($tLP['pageuid']) { 605 $content .= '{softref:' . $tokenID . '}'; 606 $elements[$tokenID . ':' . $idx]['subst'] = [ 607 'type' => 'db', 608 'recordRef' => 'pages:' . $tLP['pageuid'], 609 'tokenID' => $tokenID, 610 'tokenValue' => $tLP['pageuid'] 611 ]; 612 } 613 // Add type if applicable 614 if ((string)($tLP['pagetype'] ?? '') !== '') { 615 $content .= ',' . $tLP['pagetype']; 616 } 617 // Add anchor if applicable 618 if ((string)($tLP['anchor'] ?? '') !== '') { 619 // Anchor is assumed to point to a content elements: 620 if (MathUtility::canBeInterpretedAsInteger($tLP['anchor'])) { 621 // Initialize a new entry because we have a new relation: 622 $newTokenID = $this->makeTokenID('setTypoLinkPartsElement:anchor:' . $idx); 623 $elements[$newTokenID . ':' . $idx] = []; 624 $elements[$newTokenID . ':' . $idx]['matchString'] = 'Anchor Content Element: ' . $tLP['anchor']; 625 $content .= '#{softref:' . $newTokenID . '}'; 626 $elements[$newTokenID . ':' . $idx]['subst'] = [ 627 'type' => 'db', 628 'recordRef' => 'tt_content:' . $tLP['anchor'], 629 'tokenID' => $newTokenID, 630 'tokenValue' => $tLP['anchor'] 631 ]; 632 } else { 633 // Anchor is a hardcoded string 634 $content .= '#' . $tLP['anchor']; 635 } 636 } 637 break; 638 case LinkService::TYPE_RECORD: 639 $elements[$tokenID . ':' . $idx]['subst'] = [ 640 'type' => 'db', 641 'recordRef' => $tLP['table'] . ':' . $tLP['uid'], 642 'tokenID' => $tokenID, 643 'tokenValue' => $content, 644 ]; 645 646 $content = '{softref:' . $tokenID . '}'; 647 break; 648 default: 649 $event = new AppendLinkHandlerElementsEvent($tLP, $content, $elements, $idx, $tokenID); 650 $this->eventDispatcher->dispatch($event); 651 652 $elements = $event->getElements(); 653 $tLP = $event->getLinkParts(); 654 $content = $event->getContent(); 655 656 if (!$event->isResolved()) { 657 $elements[$tokenID . ':' . $idx]['error'] = 'Couldn\'t decide typolink mode.'; 658 return $content; 659 } 660 } 661 // Finally, for all entries that was rebuild with tokens, add target, class, title and additionalParams in the end: 662 $tLP['url'] = $content; 663 $content = GeneralUtility::makeInstance(TypoLinkCodecService::class)->encode($tLP); 664 665 // Return rebuilt typolink value: 666 return $content; 667 } 668 669 /** 670 * Make Token ID for input index. 671 * 672 * @param string $index Suffix value. 673 * @return string Token ID 674 */ 675 public function makeTokenID($index = '') 676 { 677 return md5($this->tokenID_basePrefix . ':' . $index); 678 } 679} 680