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