SoftReferenceIndex.php

Ralph Brugger, 2019-02-05 09:56

Download (35.3 KB)

 
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\Database\Query\Restriction\BackendWorkspaceRestriction;
18
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
19
use TYPO3\CMS\Core\LinkHandling\LinkService;
20
use TYPO3\CMS\Core\Resource\File;
21
use TYPO3\CMS\Core\Utility\GeneralUtility;
22
use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
23

    
24
/**
25
 * Soft Reference processing class
26
 * "Soft References" are references to database elements, files, email addresses, URls etc.
27
 * which are found in-text in content. The <link [page_id]> tag from typical bodytext fields
28
 * are an example of this.
29
 * This class contains generic parsers for the most well-known types
30
 * which are default for most TYPO3 installations. Soft References can also be userdefined.
31
 * The Soft Reference parsers are used by the system to find these references and process them accordingly in import/export actions and copy operations.
32
 *
33
 * Example of usage
34
 * Soft References:
35
 * if ($conf['softref'] && (strong)$value !== ''))        {        // Check if a TCA configured field has softreferences defined (see TYPO3 Core API document)
36
 * $softRefs = \TYPO3\CMS\Backend\Utility\BackendUtility::explodeSoftRefParserList($conf['softref']);                // Explode the list of softreferences/parameters
37
 * if ($softRefs !== FALSE) { // If there are soft references
38
 * foreach($softRefs as $spKey => $spParams)        {        // Traverse soft references
39
 * $softRefObj = \TYPO3\CMS\Backend\Utility\BackendUtility::softRefParserObj($spKey);        // create / get object
40
 * if (is_object($softRefObj))        {        // If there was an object returned...:
41
 * $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams);        // Do processing
42
 *
43
 * Result Array:
44
 * The Result array should contain two keys: "content" and "elements".
45
 * "content" is a string containing the input content but possibly with tokens inside.
46
 * Tokens are strings like {softref:[tokenID]} which is a placeholder for a value extracted by a softref parser
47
 * 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.
48
 * "elements" is an array where the keys are insignificant, but the values are arrays with these keys:
49
 * "matchString" => The value of the match. This is only for informational purposes to show what was found.
50
 * "error"        => An error message can be set here, like "file not found" etc.
51
 * "subst" => array(        // If this array is found there MUST be a token in the output content as well!
52
 * "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.
53
 * "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.
54
 * "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)
55
 * "relFileName" => (for "file" type): Relative filename. May not necessarily exist. This could be noticed in the error key.
56
 * "recordRef" => (for "db" type) : Reference to DB record on the form [table]:[uid]. May not necessarily exist.
57
 * "title" => Title of element (for backend information)
58
 * "description" => Description of element (for backend information)
59
 * )
60
 */
61
/**
62
 * Class for processing of the default soft reference types for CMS:
63
 *
64
 * - 'substitute' : A full field value targeted for manual substitution (for import /export features)
65
 * - 'notify' : Just report if a value is found, nothing more.
66
 * - 'images' : HTML <img> tags for RTE images
67
 * - 'typolink' : references to page id or file, possibly with anchor/target, possibly commaseparated list.
68
 * - 'typolink_tag' : As typolink, but searching for <link> tag to encapsulate it.
69
 * - 'email' : Email highlight
70
 * - 'url' : URL highlights (with a scheme)
71
 */
72
class SoftReferenceIndex
73
{
74
    /**
75
     * @var string
76
     */
77
    public $tokenID_basePrefix = '';
78

    
79
    /**
80
     * Main function through which all processing happens
81
     *
82
     * @param string $table Database table name
83
     * @param string $field Field name for which processing occurs
84
     * @param int $uid UID of the record
85
     * @param string $content The content/value of the field
86
     * @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.
87
     * @param array $spParams Parameters of the softlink parser. Basically this is the content inside optional []-brackets after the softref keys. Parameters are exploded by ";
88
     * @param string $structurePath If running from inside a FlexForm structure, this is the path of the tag.
89
     * @return array|bool Result array on positive matches, see description above. Otherwise FALSE
90
     */
91
    public function findRef($table, $field, $uid, $content, $spKey, $spParams, $structurePath = '')
92
    {
93
        $retVal = false;
94
        $this->tokenID_basePrefix = $table . ':' . $uid . ':' . $field . ':' . $structurePath . ':' . $spKey;
95
        switch ($spKey) {
96
            case 'notify':
97
                // Simple notification
98
                $resultArray = [
99
                    'elements' => [
100
                        [
101
                            'matchString' => $content
102
                        ]
103
                    ]
104
                ];
105
                $retVal = $resultArray;
106
                break;
107
            case 'substitute':
108
                $tokenID = $this->makeTokenID();
109
                $resultArray = [
110
                    'content' => '{softref:' . $tokenID . '}',
111
                    'elements' => [
112
                        [
113
                            'matchString' => $content,
114
                            'subst' => [
115
                                'type' => 'string',
116
                                'tokenID' => $tokenID,
117
                                'tokenValue' => $content
118
                            ]
119
                        ]
120
                    ]
121
                ];
122
                $retVal = $resultArray;
123
                break;
124
            case 'images':
125
                $retVal = $this->findRef_images($content, $spParams);
126
                break;
127
            case 'typolink':
128
                $retVal = $this->findRef_typolink($content, $spParams);
129
                break;
130
            case 'typolink_tag':
131
                $retVal = $this->findRef_typolink_tag($content, $spParams);
132
                break;
133
            case 'ext_fileref':
134
                $retVal = $this->findRef_extension_fileref($content, $spParams);
135
                break;
136
            case 'email':
137
                $retVal = $this->findRef_email($content, $spParams);
138
                break;
139
            case 'url':
140
                $retVal = $this->findRef_url($content, $spParams);
141
                break;
142
            default:
143
                $retVal = false;
144
        }
145
        return $retVal;
146
    }
147

    
148
    /**
149
     * Finding image tags in the content.
150
     * All images that are not from external URLs will be returned with an info text
151
     * Will only return files in uploads/ folders which are prefixed with "RTEmagic[C|P]_" for substitution
152
     * Any "clear.gif" images are ignored.
153
     *
154
     * @param string $content The input content to analyse
155
     * @param array $spParams Parameters set for the softref parser key in TCA/columns
156
     * @return array Result array on positive matches, see description above. Otherwise FALSE
157
     */
158
    public function findRef_images($content, $spParams)
159
    {
160
        // Start HTML parser and split content by image tag:
161
        $htmlParser = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Html\HtmlParser::class);
162
        $splitContent = $htmlParser->splitTags('img', $content);
163
        $elements = [];
164
        // Traverse splitted parts:
165
        foreach ($splitContent as $k => $v) {
166
            if ($k % 2) {
167
                // Get file reference:
168
                $attribs = $htmlParser->get_tag_attributes($v);
169
                $srcRef = htmlspecialchars_decode($attribs[0]['src']);
170
                $pI = pathinfo($srcRef);
171
                // If it looks like a local image, continue. Otherwise ignore it.
172
                $absPath = GeneralUtility::getFileAbsFileName(PATH_site . $srcRef);
173
                if (!$pI['scheme'] && !$pI['query'] && $absPath && $srcRef !== 'clear.gif') {
174
                    // Initialize the element entry with info text here:
175
                    $tokenID = $this->makeTokenID($k);
176
                    $elements[$k] = [];
177
                    $elements[$k]['matchString'] = $v;
178
                    // If the image seems to be an RTE image, then proceed to set up substitution token:
179
                    if (GeneralUtility::isFirstPartOfStr($srcRef, 'uploads/') && preg_match('/^RTEmagicC_/', basename($srcRef))) {
180
                        // Token and substitute value:
181
                        // 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)
182
                        if (strstr($splitContent[$k], $attribs[0]['src'])) {
183
                            // Substitute value with token (this is not be an exact method if the value is in there twice, but we assume it will not)
184
                            $splitContent[$k] = str_replace($attribs[0]['src'], '{softref:' . $tokenID . '}', $splitContent[$k]);
185
                            $elements[$k]['subst'] = [
186
                                'type' => 'file',
187
                                'relFileName' => $srcRef,
188
                                'tokenID' => $tokenID,
189
                                'tokenValue' => $attribs[0]['src']
190
                            ];
191
                            // Finally, notice if the file does not exist.
192
                            if (!@is_file($absPath)) {
193
                                $elements[$k]['error'] = 'File does not exist!';
194
                            }
195
                        } else {
196
                            $elements[$k]['error'] = 'Could not substitute image source with token!';
197
                        }
198
                    }
199
                }
200
            }
201
        }
202
        // Return result:
203
        if (!empty($elements)) {
204
            $resultArray = [
205
                'content' => implode('', $splitContent),
206
                'elements' => $elements
207
            ];
208
            return $resultArray;
209
        }
210
    }
211

    
212
    /**
213
     * TypoLink value processing.
214
     * Will process input value as a TypoLink value.
215
     *
216
     * @param string $content The input content to analyse
217
     * @param array $spParams Parameters set for the softref parser key in TCA/columns. value "linkList" will split the string by comma before processing.
218
     * @return array Result array on positive matches, see description above. Otherwise FALSE
219
     * @see \TYPO3\CMS\Frontend\ContentObject::typolink(), getTypoLinkParts()
220
     */
221
    public function findRef_typolink($content, $spParams)
222
    {
223
        // First, split the input string by a comma if the "linkList" parameter is set.
224
        // 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.
225
        if (is_array($spParams) && in_array('linkList', $spParams)) {
226
            // Preserving whitespace on purpose.
227
            $linkElement = explode(',', $content);
228
        } else {
229
            // If only one element, just set in this array to make it easy below.
230
            $linkElement = [$content];
231
        }
232
        // Traverse the links now:
233
        $elements = [];
234
        foreach ($linkElement as $k => $typolinkValue) {
235
            $tLP = $this->getTypoLinkParts($typolinkValue);
236
            $linkElement[$k] = $this->setTypoLinkPartsElement($tLP, $elements, $typolinkValue, $k);
237
        }
238
        // Return output:
239
        if (!empty($elements)) {
240
            $resultArray = [
241
                'content' => implode(',', $linkElement),
242
                'elements' => $elements
243
            ];
244
            return $resultArray;
245
        }
246
    }
247

    
248
    /**
249
     * TypoLink tag processing.
250
     * Will search for <link ...> and <a> tags in the content string and process any found.
251
     *
252
     * @param string $content The input content to analyse
253
     * @param array $spParams Parameters set for the softref parser key in TCA/columns
254
     * @return array Result array on positive matches, see description above. Otherwise FALSE
255
     * @see \TYPO3\CMS\Frontend\ContentObject::typolink(), getTypoLinkParts()
256
     */
257
    public function findRef_typolink_tag($content, $spParams)
258
    {
259
        // Parse string for special TYPO3 <link> tag:
260
        $htmlParser = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Html\HtmlParser::class);
261
        $linkService = GeneralUtility::makeInstance(LinkService::class);
262
        $linkTags = $htmlParser->splitTags('a', $content);
263
        // Traverse result:
264
        $elements = [];
265
        foreach ($linkTags as $key => $foundValue) {
266
            if ($key % 2) {
267
                if (preg_match('/href="([^"]+)"/', $foundValue, $matches)) {
268
                    try {
269
                        $linkDetails = $linkService->resolve($matches[1]);
270
                        if ($linkDetails['type'] === LinkService::TYPE_FILE && preg_match('/file\?uid=(\d+)/', $matches[1], $fileIdMatch)) {
271
                            $token = $this->makeTokenID($key);
272
                            $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]);
273
                            $elements[$key]['subst'] = [
274
                                'type' => 'db',
275
                                'recordRef' => 'sys_file:' . $fileIdMatch[1],
276
                                'tokenID' => $token,
277
                                'tokenValue' => 'file:' . ($linkDetails['file'] instanceof File ? $linkDetails['file']->getUid() : $fileIdMatch[1])
278
                            ];
279
                        } elseif ($linkDetails['type'] === LinkService::TYPE_PAGE && preg_match('/page\?uid=(\d+)#?(\d+)?/', $matches[1], $pageAndAnchorMatches)) {
280
                            $token = $this->makeTokenID($key);
281
                            $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]);
282
                            $elements[$key]['subst'] = [
283
                                'type' => 'db',
284
                                'recordRef' => 'pages:' . $linkDetails['pageuid'] . (isset($pageAndAnchorMatches[2]) ? '#c' . $pageAndAnchorMatches[2] : ''),
285
                                'tokenID' => $token,
286
                                'tokenValue' => $linkDetails['pageuid'] . (isset($pageAndAnchorMatches[2]) ? '#c' . $pageAndAnchorMatches[2] : '')
287
                            ];
288
                        } elseif ($linkDetails['type'] === LinkService::TYPE_URL) {
289
                            $token = $this->makeTokenID($key);
290
                            $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]);
291
                            $elements[$key]['subst'] = [
292
                                'type' => 'external',
293
                                'tokenID' => $token,
294
                                'tokenValue' => $linkDetails['url']
295
                            ];
296
                        }
297
                    } catch (\Exception $e) {
298
                        // skip invalid links
299
                    }
300
                } else {
301
                    // keep the legacy code for now
302
                    $typolinkValue = preg_replace('/<LINK[[:space:]]+/i', '', substr($foundValue, 0, -1));
303
                    $tLP = $this->getTypoLinkParts($typolinkValue);
304
                    $linkTags[$k] = '<LINK ' . $this->setTypoLinkPartsElement($tLP, $elements, $typolinkValue, $k) . '>';
305
                }
306
            }
307
        }
308
        // Return output:
309
        if (!empty($elements)) {
310
            $resultArray = [
311
                'content' => implode('', $linkTags),
312
                'elements' => $elements
313
            ];
314
            return $resultArray;
315
        }
316
    }
317

    
318
    /**
319
     * Finding email addresses in content and making them substitutable.
320
     *
321
     * @param string $content The input content to analyse
322
     * @param array $spParams Parameters set for the softref parser key in TCA/columns
323
     * @return array Result array on positive matches, see description above. Otherwise FALSE
324
     */
325
    public function findRef_email($content, $spParams)
326
    {
327
        // Email:
328
        $parts = preg_split('/([^[:alnum:]]+)([A-Za-z0-9\\._-]+[@][A-Za-z0-9\\._-]+[\\.].[A-Za-z0-9]+)/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
329
        foreach ($parts as $idx => $value) {
330
            if ($idx % 3 == 2) {
331
                $tokenID = $this->makeTokenID($idx);
332
                $elements[$idx] = [];
333
                $elements[$idx]['matchString'] = $value;
334
                if (is_array($spParams) && in_array('subst', $spParams)) {
335
                    $parts[$idx] = '{softref:' . $tokenID . '}';
336
                    $elements[$idx]['subst'] = [
337
                        'type' => 'string',
338
                        'tokenID' => $tokenID,
339
                        'tokenValue' => $value
340
                    ];
341
                }
342
            }
343
        }
344
        // Return output:
345
        if (!empty($elements)) {
346
            $resultArray = [
347
                'content' => substr(implode('', $parts), 1, -1),
348
                'elements' => $elements
349
            ];
350
            return $resultArray;
351
        }
352
    }
353

    
354
    /**
355
     * Finding URLs in content
356
     *
357
     * @param string $content The input content to analyse
358
     * @param array $spParams Parameters set for the softref parser key in TCA/columns
359
     * @return array Result array on positive matches, see description above. Otherwise FALSE
360
     */
361
    public function findRef_url($content, $spParams)
362
    {
363
        // URLs
364
        $parts = preg_split('/([^[:alnum:]"\']+)((https?|ftp):\\/\\/[^[:space:]"\'<>]*)([[:space:]])/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
365
        foreach ($parts as $idx => $value) {
366
            if ($idx % 5 == 3) {
367
                unset($parts[$idx]);
368
            }
369
            if ($idx % 5 == 2) {
370
                $tokenID = $this->makeTokenID($idx);
371
                $elements[$idx] = [];
372
                $elements[$idx]['matchString'] = $value;
373
                if (is_array($spParams) && in_array('subst', $spParams)) {
374
                    $parts[$idx] = '{softref:' . $tokenID . '}';
375
                    $elements[$idx]['subst'] = [
376
                        'type' => 'string',
377
                        'tokenID' => $tokenID,
378
                        'tokenValue' => $value
379
                    ];
380
                }
381
            }
382
        }
383
        // Return output:
384
        if (!empty($elements)) {
385
            $resultArray = [
386
                'content' => substr(implode('', $parts), 1, -1),
387
                'elements' => $elements
388
            ];
389
            return $resultArray;
390
        }
391
    }
392

    
393
    /**
394
     * Finding reference to files from extensions in content, but only to notify about their existence. No substitution
395
     *
396
     * @param string $content The input content to analyse
397
     * @param array $spParams Parameters set for the softref parser key in TCA/columns
398
     * @return array Result array on positive matches, see description above. Otherwise FALSE
399
     */
400
    public function findRef_extension_fileref($content, $spParams)
401
    {
402
        // Files starting with EXT:
403
        $parts = preg_split('/([^[:alnum:]"\']+)(EXT:[[:alnum:]_]+\\/[^[:space:]"\',]*)/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
404
        foreach ($parts as $idx => $value) {
405
            if ($idx % 3 == 2) {
406
                $this->makeTokenID($idx);
407
                $elements[$idx] = [];
408
                $elements[$idx]['matchString'] = $value;
409
            }
410
        }
411
        // Return output:
412
        if (!empty($elements)) {
413
            $resultArray = [
414
                'content' => substr(implode('', $parts), 1, -1),
415
                'elements' => $elements
416
            ];
417
            return $resultArray;
418
        }
419
    }
420

    
421
    /*************************
422
     *
423
     * Helper functions
424
     *
425
     *************************/
426

    
427
    /**
428
     * Analyse content as a TypoLink value and return an array with properties.
429
     * TypoLinks format is: <link [typolink] [browser target] [css class] [title attribute] [additionalParams]>.
430
     * See TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink()
431
     * The syntax of the [typolink] part is: [typolink] = [page id or alias][,[type value]][#[anchor, if integer = tt_content uid]]
432
     * The extraction is based on how \TYPO3\CMS\Frontend\ContentObject::typolink() behaves.
433
     *
434
     * @param string $typolinkValue TypoLink value.
435
     * @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.
436
     * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typolink(), setTypoLinkPartsElement()
437
     */
438
    public function getTypoLinkParts($typolinkValue)
439
    {
440
        $finalTagParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($typolinkValue);
441

    
442
        $link_param = $finalTagParts['url'];
443
        // we define various keys below, "url" might be misleading
444
        unset($finalTagParts['url']);
445

    
446
        if (stripos(rawurldecode(trim($link_param)), 'phar://') === 0) {
447
            throw new \RuntimeException(
448
                'phar scheme not allowed as soft reference target',
449
                1530030672
450
            );
451
        }
452

    
453
        // Parse URL:
454
        $pU = @parse_url($link_param);
455

    
456
        // If it's a mail address:
457
        if (strstr($link_param, '@') && !$pU['scheme']) {
458
            $link_param = preg_replace('/^mailto:/i', '', $link_param);
459
            $finalTagParts['LINK_TYPE'] = 'mailto';
460
            $finalTagParts['url'] = trim($link_param);
461
            return $finalTagParts;
462
        }
463

    
464
        if ($pU['scheme'] === 't3' && $pU['host'] === LinkService::TYPE_RECORD) {
465
            $finalTagParts['LINK_TYPE'] = LinkService::TYPE_RECORD;
466
            $finalTagParts['url'] = $link_param;
467
        }
468

    
469
        list($linkHandlerKeyword, $linkHandlerValue) = explode(':', trim($link_param), 2);
470

    
471
        // Dispatch available signal slots.
472
        $linkHandlerFound = false;
473
        list($linkHandlerFound, $finalTagParts) = $this->emitGetTypoLinkParts($linkHandlerFound, $finalTagParts, $linkHandlerKeyword, $linkHandlerValue);
474
        if ($linkHandlerFound) {
475
            return $finalTagParts;
476
        }
477

    
478
        // Ralph Brugger, 2019-01-31
479
        if ($linkHandlerKeyword === 't3') {
480
        
481
                        // linking to any t3:// syntax
482
                        if (stripos($link_param, 't3://') === 0) {
483
                        
484
                    // lets parse the urn
485
                    $linkParsed = parse_url($link_param);
486
                    if (isset($linkParsed['host']) && isset($linkParsed['scheme']) && isset($linkParsed['query'])){
487
                            $identifier = $linkParsed['host'];
488
                            $scheme = $linkParsed['scheme'];
489
                            $query = $linkParsed['query'];
490
                        $finalTagParts['LINK_TYPE'] = $identifier;
491
                        if($query && $identifier === 'file' ){
492
                                $queryparts = explode('=', $query);
493
                                if($queryparts){
494
                                        if(is_array($queryparts)){
495
                                                $queryKey = $queryparts[0];
496
                                                $queryValue = (integer) $queryparts[1];
497
                                                if (is_int($queryValue)){
498
                                                        $finalTagParts['identifier'] = "$identifier:$queryValue";
499
                                                        unset($linkParsed);
500
                                                        unset($queryparts);
501
                                                        return $finalTagParts;
502
                                                                }
503
                                                        }
504
                                                }
505
                                        }
506
                                }
507
            }
508
        }
509
        // Ralph Brugger, 2019-01-31
510

    
511
        // Check for FAL link-handler keyword
512
        if ($linkHandlerKeyword === 'file') {
513
            $finalTagParts['LINK_TYPE'] = 'file';
514
            $finalTagParts['identifier'] = trim($link_param);
515
            return $finalTagParts;
516
        }
517

    
518
        $isLocalFile = 0;
519
        $fileChar = (int)strpos($link_param, '/');
520
        $urlChar = (int)strpos($link_param, '.');
521

    
522
        // Detects if a file is found in site-root and if so it will be treated like a normal file.
523
        list($rootFileDat) = explode('?', rawurldecode($link_param));
524
        $containsSlash = strstr($rootFileDat, '/');
525
        $rFD_fI = pathinfo($rootFileDat);
526
        $fileExtension = strtolower($rFD_fI['extension']);
527
        if (!$containsSlash && trim($rootFileDat) && (@is_file(PATH_site . $rootFileDat) || $fileExtension === 'php' || $fileExtension === 'html' || $fileExtension === 'htm')) {
528
            $isLocalFile = 1;
529
        } elseif ($containsSlash) {
530
            // Adding this so realurl directories are linked right (non-existing).
531
            $isLocalFile = 2;
532
        }
533
        if ($pU['scheme'] || ($isLocalFile != 1 && $urlChar && (!$containsSlash || $urlChar < $fileChar))) { // url (external): If doubleSlash or if a '.' comes before a '/'.
534
            $finalTagParts['LINK_TYPE'] = 'url';
535
            $finalTagParts['url'] = $link_param;
536
        } elseif ($containsSlash || $isLocalFile) { // file (internal)
537
            $splitLinkParam = explode('?', $link_param);
538
            if (file_exists(rawurldecode($splitLinkParam[0])) || $isLocalFile) {
539
                $finalTagParts['LINK_TYPE'] = 'file';
540
                $finalTagParts['filepath'] = rawurldecode($splitLinkParam[0]);
541
                $finalTagParts['query'] = $splitLinkParam[1];
542
            }
543
        } else {
544
            // integer or alias (alias is without slashes or periods or commas, that is
545
            // 'nospace,alphanum_x,lower,unique' according to definition in $GLOBALS['TCA']!)
546
            $finalTagParts['LINK_TYPE'] = 'page';
547

    
548
            $link_params_parts = explode('#', $link_param);
549
            // Link-data del
550
            $link_param = trim($link_params_parts[0]);
551

    
552
            if ((string)$link_params_parts[1] !== '') {
553
                $finalTagParts['anchor'] = trim($link_params_parts[1]);
554
            }
555

    
556
            // Splitting the parameter by ',' and if the array counts more than 1 element it's a id/type/? pair
557
            $pairParts = GeneralUtility::trimExplode(',', $link_param);
558
            if (count($pairParts) > 1) {
559
                $link_param = $pairParts[0];
560
                $finalTagParts['type'] = $pairParts[1]; // Overruling 'type'
561
            }
562

    
563
            // Checking if the id-parameter is an alias.
564
            if ((string)$link_param !== '') {
565
                if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($link_param)) {
566
                    $finalTagParts['alias'] = $link_param;
567
                    $link_param = $this->getPageIdFromAlias($link_param);
568
                }
569

    
570
                $finalTagParts['page_id'] = (int)$link_param;
571
            }
572
        }
573

    
574
        return $finalTagParts;
575
    }
576

    
577
    /**
578
     * Recompile a TypoLink value from the array of properties made with getTypoLinkParts() into an elements array
579
     *
580
     * @param array $tLP TypoLink properties
581
     * @param array $elements Array of elements to be modified with substitution / information entries.
582
     * @param string $content The content to process.
583
     * @param int $idx Index value of the found element - user to make unique but stable tokenID
584
     * @return string The input content, possibly containing tokens now according to the added substitution entries in $elements
585
     * @see getTypoLinkParts()
586
     */
587
    public function setTypoLinkPartsElement($tLP, &$elements, $content, $idx)
588
    {
589
        // Initialize, set basic values. In any case a link will be shown
590
        $tokenID = $this->makeTokenID('setTypoLinkPartsElement:' . $idx);
591
        $elements[$tokenID . ':' . $idx] = [];
592
        $elements[$tokenID . ':' . $idx]['matchString'] = $content;
593
        // Based on link type, maybe do more:
594
        switch ((string)$tLP['LINK_TYPE']) {
595
            case 'mailto':
596

    
597
            case 'url':
598
                // Mail addresses and URLs can be substituted manually:
599
                $elements[$tokenID . ':' . $idx]['subst'] = [
600
                    'type' => 'string',
601
                    'tokenID' => $tokenID,
602
                    'tokenValue' => $tLP['url']
603
                ];
604
                // Output content will be the token instead:
605
                $content = '{softref:' . $tokenID . '}';
606
                break;
607
            case 'file':
608
                // Process files referenced by their FAL uid
609
                if ($tLP['identifier']) {
610
                    list($linkHandlerKeyword, $linkHandlerValue) = explode(':', trim($tLP['identifier']), 2);
611
                    if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($linkHandlerValue)) {
612
                        // Token and substitute value
613
                        $elements[$tokenID . ':' . $idx]['subst'] = [
614
                            'type' => 'db',
615
                            'recordRef' => 'sys_file:' . $linkHandlerValue,
616
                            'tokenID' => $tokenID,
617
                            'tokenValue' => $tLP['identifier'],
618
                        ];
619
                        // Output content will be the token instead:
620
                        $content = '{softref:' . $tokenID . '}';
621
                    } else {
622
                        // This is a link to a folder...
623
                        return $content;
624
                    }
625
                } else {
626
                    return $content;
627
                }
628
                break;
629
            case 'page':
630
                // Rebuild page reference typolink part:
631
                $content = '';
632
                // Set page id:
633
                if ($tLP['page_id']) {
634
                    $content .= '{softref:' . $tokenID . '}';
635
                    $elements[$tokenID . ':' . $idx]['subst'] = [
636
                        'type' => 'db',
637
                        'recordRef' => 'pages:' . $tLP['page_id'],
638
                        'tokenID' => $tokenID,
639
                        'tokenValue' => $tLP['alias'] ? $tLP['alias'] : $tLP['page_id']
640
                    ];
641
                }
642
                // Add type if applicable
643
                if ((string)$tLP['type'] !== '') {
644
                    $content .= ',' . $tLP['type'];
645
                }
646
                // Add anchor if applicable
647
                if ((string)$tLP['anchor'] !== '') {
648
                    // Anchor is assumed to point to a content elements:
649
                    if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($tLP['anchor'])) {
650
                        // Initialize a new entry because we have a new relation:
651
                        $newTokenID = $this->makeTokenID('setTypoLinkPartsElement:anchor:' . $idx);
652
                        $elements[$newTokenID . ':' . $idx] = [];
653
                        $elements[$newTokenID . ':' . $idx]['matchString'] = 'Anchor Content Element: ' . $tLP['anchor'];
654
                        $content .= '#{softref:' . $newTokenID . '}';
655
                        $elements[$newTokenID . ':' . $idx]['subst'] = [
656
                            'type' => 'db',
657
                            'recordRef' => 'tt_content:' . $tLP['anchor'],
658
                            'tokenID' => $newTokenID,
659
                            'tokenValue' => $tLP['anchor']
660
                        ];
661
                    } else {
662
                        // Anchor is a hardcoded string
663
                        $content .= '#' . $tLP['type'];
664
                    }
665
                }
666
                break;
667
            case LinkService::TYPE_RECORD:
668
                $elements[$tokenID . ':' . $idx]['subst'] = [
669
                    'type' => 'db',
670
                    'recordRef' => $tLP['table'] . ':' . $tLP['uid'],
671
                    'tokenID' => $tokenID,
672
                    'tokenValue' => $content,
673
                ];
674

    
675
                $content = '{softref:' . $tokenID . '}';
676
                break;
677
            default:
678
                $linkHandlerFound = false;
679
                list($linkHandlerFound, $tLP, $content, $newElements) = $this->emitSetTypoLinkPartsElement($linkHandlerFound, $tLP, $content, $elements, $idx, $tokenID);
680
                // We need to merge the array, otherwise we would loose the reference.
681
                \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($elements, $newElements);
682

    
683
                if (!$linkHandlerFound) {
684
                    $elements[$tokenID . ':' . $idx]['error'] = 'Couldn\'t decide typolink mode.';
685
                    return $content;
686
                }
687
        }
688
        // Finally, for all entries that was rebuild with tokens, add target, class, title and additionalParams in the end:
689
        $tLP['url'] = $content;
690
        $content = GeneralUtility::makeInstance(TypoLinkCodecService::class)->encode($tLP);
691

    
692
        // Return rebuilt typolink value:
693
        return $content;
694
    }
695

    
696
    /**
697
     * Look up and return page uid for alias
698
     *
699
     * @param string $link_param Page alias string value
700
     * @return int Page uid corresponding to alias value.
701
     */
702
    public function getPageIdFromAlias($link_param)
703
    {
704
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
705
        $queryBuilder->getRestrictions()
706
            ->removeAll()
707
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
708
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
709

    
710
        $pageUid = $queryBuilder->select('uid')
711
            ->from('pages')
712
            ->where(
713
                $queryBuilder->expr()->eq('alias', $queryBuilder->createNamedParameter($link_param, \PDO::PARAM_STR))
714
            )
715
            ->setMaxResults(1)
716
            ->execute()
717
            ->fetchColumn(0);
718

    
719
        return (int)$pageUid;
720
    }
721

    
722
    /**
723
     * Make Token ID for input index.
724
     *
725
     * @param string $index Suffix value.
726
     * @return string Token ID
727
     */
728
    public function makeTokenID($index = '')
729
    {
730
        return md5($this->tokenID_basePrefix . ':' . $index);
731
    }
732

    
733
    /**
734
     * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
735
     */
736
    protected function getSignalSlotDispatcher()
737
    {
738
        return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
739
    }
740

    
741
    /**
742
     * @param bool $linkHandlerFound
743
     * @param array $finalTagParts
744
     * @param string $linkHandlerKeyword
745
     * @param string $linkHandlerValue
746
     * @return array
747
     */
748
    protected function emitGetTypoLinkParts($linkHandlerFound, $finalTagParts, $linkHandlerKeyword, $linkHandlerValue)
749
    {
750
        return $this->getSignalSlotDispatcher()->dispatch(get_class($this), 'getTypoLinkParts', [$linkHandlerFound, $finalTagParts, $linkHandlerKeyword, $linkHandlerValue]);
751
    }
752

    
753
    /**
754
     * @param bool $linkHandlerFound
755
     * @param array $tLP
756
     * @param string $content
757
     * @param array $elements
758
     * @param int $idx
759
     * @param string $tokenID
760
     * @return array
761
     */
762
    protected function emitSetTypoLinkPartsElement($linkHandlerFound, $tLP, $content, $elements, $idx, $tokenID)
763
    {
764
        return $this->getSignalSlotDispatcher()->dispatch(get_class($this), 'setTypoLinkPartsElement', [$linkHandlerFound, $tLP, $content, $elements, $idx, $tokenID, $this]);
765
    }
766
}