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