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