‪TYPO3CMS  10.4
SoftReferenceIndex.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Psr\EventDispatcher\EventDispatcherInterface;
30 
80 {
84  public ‪$tokenID_basePrefix = '';
85 
89  protected ‪$eventDispatcher;
90 
94  private ‪$referenceUid = 0;
95 
99  private ‪$referenceTable = '';
100 
101  public function ‪__construct(EventDispatcherInterface ‪$eventDispatcher)
102  {
103  $this->eventDispatcher = ‪$eventDispatcher;
104  }
105 
118  public function ‪findRef($table, $field, $uid, $content, $spKey, $spParams, $structurePath = '')
119  {
120  $this->referenceUid = $uid;
121  $this->referenceTable = $table;
122  $this->tokenID_basePrefix = $table . ':' . $uid . ':' . $field . ':' . $structurePath . ':' . $spKey;
123  switch ($spKey) {
124  case 'notify':
125  // Simple notification
126  $resultArray = [
127  'elements' => [
128  [
129  'matchString' => $content
130  ]
131  ]
132  ];
133  $retVal = $resultArray;
134  break;
135  case 'substitute':
136  $tokenID = $this->‪makeTokenID();
137  $resultArray = [
138  'content' => '{softref:' . $tokenID . '}',
139  'elements' => [
140  [
141  'matchString' => $content,
142  'subst' => [
143  'type' => 'string',
144  'tokenID' => $tokenID,
145  'tokenValue' => $content
146  ]
147  ]
148  ]
149  ];
150  $retVal = $resultArray;
151  break;
152  case 'typolink':
153  $retVal = $this->‪findRef_typolink($content, $spParams);
154  break;
155  case 'typolink_tag':
156  $retVal = $this->‪findRef_typolink_tag($content);
157  break;
158  case 'ext_fileref':
159  $retVal = $this->‪findRef_extension_fileref($content);
160  break;
161  case 'email':
162  $retVal = $this->‪findRef_email($content, $spParams);
163  break;
164  case 'url':
165  $retVal = $this->‪findRef_url($content, $spParams);
166  break;
167  default:
168  $retVal = false;
169  }
170  $this->referenceUid = 0;
171  $this->referenceTable = '';
172  return $retVal;
173  }
174 
185  public function ‪findRef_typolink($content, $spParams)
186  {
187  // First, split the input string by a comma if the "linkList" parameter is set.
188  // An example: the link field for images in content elements of type "textpic" or "image". This field CAN be configured to define a link per image, separated by comma.
189  if (is_array($spParams) && in_array('linkList', $spParams)) {
190  // Preserving whitespace on purpose.
191  $linkElement = explode(',', $content);
192  } else {
193  // If only one element, just set in this array to make it easy below.
194  $linkElement = [$content];
195  }
196  // Traverse the links now:
197  $elements = [];
198  foreach ($linkElement as $k => $typolinkValue) {
199  $tLP = $this->‪getTypoLinkParts($typolinkValue);
200  $linkElement[$k] = $this->‪setTypoLinkPartsElement($tLP, $elements, $typolinkValue, $k);
201  }
202  // Return output:
203  if (!empty($elements)) {
204  $resultArray = [
205  'content' => implode(',', $linkElement),
206  'elements' => $elements
207  ];
208  return $resultArray;
209  }
210 
211  return null;
212  }
213 
223  public function ‪findRef_typolink_tag($content)
224  {
225  // Parse string for special TYPO3 <link> tag:
226  $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
227  $linkService = GeneralUtility::makeInstance(LinkService::class);
228  $linkTags = $htmlParser->splitTags('a', $content);
229  // Traverse result:
230  $elements = [];
231  foreach ($linkTags as $key => $foundValue) {
232  if ($key % 2) {
233  if (preg_match('/href="([^"]+)"/', $foundValue, $matches)) {
234  try {
235  $linkDetails = $linkService->resolve($matches[1]);
236  if ($linkDetails['type'] === ‪LinkService::TYPE_FILE && preg_match('/file\?uid=(\d+)/', $matches[1], $fileIdMatch)) {
237  $token = $this->‪makeTokenID($key);
238  $elements[$key]['matchString'] = $linkTags[$key];
239  $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]);
240  $elements[$key]['subst'] = [
241  'type' => 'db',
242  'recordRef' => 'sys_file:' . $fileIdMatch[1],
243  'tokenID' => $token,
244  'tokenValue' => 'file:' . ($linkDetails['file'] instanceof File ? $linkDetails['file']->getUid() : $fileIdMatch[1])
245  ];
246  } elseif ($linkDetails['type'] === ‪LinkService::TYPE_PAGE && preg_match('/page\?uid=(\d+)#?(\d+)?/', $matches[1], $pageAndAnchorMatches)) {
247  $token = $this->‪makeTokenID($key);
248  $content = '{softref:' . $token . '}';
249  $elements[$key]['matchString'] = $linkTags[$key];
250  $elements[$key]['subst'] = [
251  'type' => 'db',
252  'recordRef' => 'pages:' . $linkDetails['pageuid'],
253  'tokenID' => $token,
254  'tokenValue' => $linkDetails['pageuid']
255  ];
256  if (isset($pageAndAnchorMatches[2]) && $pageAndAnchorMatches[2] !== '') {
257  // Anchor is assumed to point to a content elements:
258  if (‪MathUtility::canBeInterpretedAsInteger($pageAndAnchorMatches[2])) {
259  // Initialize a new entry because we have a new relation:
260  $newTokenID = $this->‪makeTokenID('setTypoLinkPartsElement:anchor:' . $key);
261  $elements[$newTokenID . ':' . $key] = [];
262  $elements[$newTokenID . ':' . $key]['matchString'] = 'Anchor Content Element: ' . $pageAndAnchorMatches[2];
263  $content .= '#{softref:' . $newTokenID . '}';
264  $elements[$newTokenID . ':' . $key]['subst'] = [
265  'type' => 'db',
266  'recordRef' => 'tt_content:' . $pageAndAnchorMatches[2],
267  'tokenID' => $newTokenID,
268  'tokenValue' => $pageAndAnchorMatches[2]
269  ];
270  } else {
271  // Anchor is a hardcoded string
272  $content .= '#' . $pageAndAnchorMatches[2];
273  }
274  }
275  $linkTags[$key] = str_replace($matches[1], $content, $linkTags[$key]);
276  } elseif ($linkDetails['type'] === ‪LinkService::TYPE_URL) {
277  $token = $this->‪makeTokenID($key);
278  $elements[$key]['matchString'] = $linkTags[$key];
279  $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]);
280  $elements[$key]['subst'] = [
281  'type' => 'external',
282  'tokenID' => $token,
283  'tokenValue' => $linkDetails['url']
284  ];
285  } elseif ($linkDetails['type'] === ‪LinkService::TYPE_EMAIL) {
286  $token = $this->‪makeTokenID($key);
287  $elements[$key]['matchString'] = $linkTags[$key];
288  $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]);
289  $elements[$key]['subst'] = [
290  'type' => 'string',
291  'tokenID' => $token,
292  'tokenValue' => $linkDetails['email']
293  ];
294  } elseif ($linkDetails['type'] === ‪LinkService::TYPE_TELEPHONE) {
295  $token = $this->‪makeTokenID($key);
296  $elements[$key]['matchString'] = $linkTags[$key];
297  $linkTags[$key] = str_replace($matches[1], '{softref:' . $token . '}', $linkTags[$key]);
298  $elements[$key]['subst'] = [
299  'type' => 'string',
300  'tokenID' => $token,
301  'tokenValue' => $linkDetails['telephone']
302  ];
303  }
304  } catch (\Exception $e) {
305  // skip invalid links
306  }
307  }
308  }
309  }
310  // Return output:
311  if (!empty($elements)) {
312  $resultArray = [
313  'content' => implode('', $linkTags),
314  'elements' => $elements
315  ];
316  return $resultArray;
317  }
318 
319  return null;
320  }
321 
329  public function ‪findRef_email($content, $spParams)
330  {
331  $elements = [];
332  // Email:
333  $parts = preg_split('/([^[:alnum:]]+)([A-Za-z0-9\\._-]+[@][A-Za-z0-9\\._-]+[\\.].[A-Za-z0-9]+)/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
334  foreach ($parts as $idx => $value) {
335  if ($idx % 3 == 2) {
336  $tokenID = $this->‪makeTokenID($idx);
337  $elements[$idx] = [];
338  $elements[$idx]['matchString'] = $value;
339  if (is_array($spParams) && in_array('subst', $spParams)) {
340  $parts[$idx] = '{softref:' . $tokenID . '}';
341  $elements[$idx]['subst'] = [
342  'type' => 'string',
343  'tokenID' => $tokenID,
344  'tokenValue' => $value
345  ];
346  }
347  }
348  }
349  // Return output:
350  if (!empty($elements)) {
351  $resultArray = [
352  'content' => substr(implode('', $parts), 1, -1),
353  'elements' => $elements
354  ];
355  return $resultArray;
356  }
357 
358  return null;
359  }
360 
368  public function ‪findRef_url($content, $spParams)
369  {
370  $elements = [];
371  // URLs
372  $parts = preg_split('/([^[:alnum:]"\']+)((https?|ftp):\\/\\/[^[:space:]"\'<>]*)([[:space:]])/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
373  foreach ($parts as $idx => $value) {
374  if ($idx % 5 == 3) {
375  unset($parts[$idx]);
376  }
377  if ($idx % 5 == 2) {
378  $tokenID = $this->‪makeTokenID($idx);
379  $elements[$idx] = [];
380  $elements[$idx]['matchString'] = $value;
381  if (is_array($spParams) && in_array('subst', $spParams)) {
382  $parts[$idx] = '{softref:' . $tokenID . '}';
383  $elements[$idx]['subst'] = [
384  'type' => 'string',
385  'tokenID' => $tokenID,
386  'tokenValue' => $value
387  ];
388  }
389  }
390  }
391  // Return output:
392  if (!empty($elements)) {
393  $resultArray = [
394  'content' => substr(implode('', $parts), 1, -1),
395  'elements' => $elements
396  ];
397  return $resultArray;
398  }
399 
400  return null;
401  }
402 
409  public function ‪findRef_extension_fileref($content)
410  {
411  $elements = [];
412  // Files starting with EXT:
413  $parts = preg_split('/([^[:alnum:]"\']+)(EXT:[[:alnum:]_]+\\/[^[:space:]"\',]*)/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE) ?: [];
414  foreach ($parts as $idx => $value) {
415  if ($idx % 3 == 2) {
416  $this->‪makeTokenID((string)$idx);
417  $elements[$idx] = [];
418  $elements[$idx]['matchString'] = $value;
419  }
420  }
421  // Return output:
422  if (!empty($elements)) {
423  $resultArray = [
424  'content' => substr(implode('', $parts), 1, -1),
425  'elements' => $elements
426  ];
427  return $resultArray;
428  }
429 
430  return null;
431  }
432 
433  /*************************
434  *
435  * Helper functions
436  *
437  *************************/
438 
451  public function ‪getTypoLinkParts($typolinkValue)
452  {
453  $finalTagParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($typolinkValue);
454 
455  $link_param = $finalTagParts['url'];
456  // we define various keys below, "url" might be misleading
457  unset($finalTagParts['url']);
458 
459  if (stripos(rawurldecode(trim($link_param)), 'phar://') === 0) {
460  throw new \RuntimeException(
461  'phar scheme not allowed as soft reference target',
462  1530030672
463  );
464  }
465 
466  $linkService = GeneralUtility::makeInstance(LinkService::class);
467  try {
468  $linkData = $linkService->resolve($link_param);
469  switch ($linkData['type']) {
471  $referencePageId = $this->referenceTable === 'pages'
472  ? $this->referenceUid
473  : (int)(‪BackendUtility::getRecord($this->referenceTable, $this->referenceUid)['pid'] ?? 0);
474  if ($referencePageId) {
475  $pageTsConfig = ‪BackendUtility::getPagesTSconfig($referencePageId);
476  $table = $pageTsConfig['TCEMAIN.']['linkHandler.'][$linkData['identifier'] . '.']['configuration.']['table'] ?? $linkData['identifier'];
477  } else {
478  // Backwards compatibility for the old behaviour, where the identifier was saved as the table.
479  $table = $linkData['identifier'];
480  }
481  $finalTagParts['table'] = $table;
482  $finalTagParts['uid'] = $linkData['uid'];
483  break;
485  $linkData['pageuid'] = (int)$linkData['pageuid'];
486  if (isset($linkData['pagetype'])) {
487  $linkData['pagetype'] = (int)$linkData['pagetype'];
488  }
489  if (isset($linkData['fragment'])) {
490  $finalTagParts['anchor'] = $linkData['fragment'];
491  }
492  break;
495  if (isset($linkData['file'])) {
496  $finalTagParts['type'] = ‪LinkService::TYPE_FILE;
497  $linkData['file'] = $linkData['file'] instanceof FileInterface ? $linkData['file']->getUid() : $linkData['file'];
498  } else {
499  $pU = parse_url($link_param);
500  parse_str($pU['query'] ?? '', $query);
501  if (isset($query['uid'])) {
502  $finalTagParts['type'] = ‪LinkService::TYPE_FILE;
503  $finalTagParts['file'] = (int)$query['uid'];
504  }
505  }
506  break;
507  }
508  return array_merge($finalTagParts, $linkData);
509  } catch (UnknownLinkHandlerException $e) {
510  // Cannot handle anything
511  return $finalTagParts;
512  }
513  }
514 
525  public function ‪setTypoLinkPartsElement($tLP, &$elements, $content, $idx)
526  {
527  // Initialize, set basic values. In any case a link will be shown
528  $tokenID = $this->‪makeTokenID('setTypoLinkPartsElement:' . $idx);
529  $elements[$tokenID . ':' . $idx] = [];
530  $elements[$tokenID . ':' . $idx]['matchString'] = $content;
531  // Based on link type, maybe do more:
532  switch ((string)$tLP['type']) {
534  // Mail addresses can be substituted manually:
535  $elements[$tokenID . ':' . $idx]['subst'] = [
536  'type' => 'string',
537  'tokenID' => $tokenID,
538  'tokenValue' => $tLP['email']
539  ];
540  // Output content will be the token instead:
541  $content = '{softref:' . $tokenID . '}';
542  break;
544  // phone number can be substituted manually:
545  $elements[$tokenID . ':' . $idx]['subst'] = [
546  'type' => 'string',
547  'tokenID' => $tokenID,
548  'tokenValue' => $tLP['telephone']
549  ];
550  // Output content will be the token instead:
551  $content = '{softref:' . $tokenID . '}';
552  break;
554  // URLs can be substituted manually
555  $elements[$tokenID . ':' . $idx]['subst'] = [
556  'type' => 'external',
557  'tokenID' => $tokenID,
558  'tokenValue' => $tLP['url']
559  ];
560  // Output content will be the token instead:
561  $content = '{softref:' . $tokenID . '}';
562  break;
564  // This is a link to a folder...
565  unset($elements[$tokenID . ':' . $idx]);
566  return $content;
568  // Process files referenced by their FAL uid
569  if (isset($tLP['file'])) {
570  $fileId = $tLP['file'] instanceof FileInterface ? $tLP['file']->getUid() : $tLP['file'];
571  // Token and substitute value
572  $elements[$tokenID . ':' . $idx]['subst'] = [
573  'type' => 'db',
574  'recordRef' => 'sys_file:' . $fileId,
575  'tokenID' => $tokenID,
576  'tokenValue' => 'file:' . $fileId,
577  ];
578  // Output content will be the token instead:
579  $content = '{softref:' . $tokenID . '}';
580  } elseif ($tLP['identifier']) {
581  [$linkHandlerKeyword, $linkHandlerValue] = explode(':', trim($tLP['identifier']), 2);
582  if (‪MathUtility::canBeInterpretedAsInteger($linkHandlerValue)) {
583  // Token and substitute value
584  $elements[$tokenID . ':' . $idx]['subst'] = [
585  'type' => 'db',
586  'recordRef' => 'sys_file:' . $linkHandlerValue,
587  'tokenID' => $tokenID,
588  'tokenValue' => $tLP['identifier'],
589  ];
590  // Output content will be the token instead:
591  $content = '{softref:' . $tokenID . '}';
592  } else {
593  // This is a link to a folder...
594  return $content;
595  }
596  } else {
597  return $content;
598  }
599  break;
601  // Rebuild page reference typolink part:
602  $content = '';
603  // Set page id:
604  if ($tLP['pageuid']) {
605  $content .= '{softref:' . $tokenID . '}';
606  $elements[$tokenID . ':' . $idx]['subst'] = [
607  'type' => 'db',
608  'recordRef' => 'pages:' . $tLP['pageuid'],
609  'tokenID' => $tokenID,
610  'tokenValue' => $tLP['pageuid']
611  ];
612  }
613  // Add type if applicable
614  if ((string)($tLP['pagetype'] ?? '') !== '') {
615  $content .= ',' . $tLP['pagetype'];
616  }
617  // Add anchor if applicable
618  if ((string)($tLP['anchor'] ?? '') !== '') {
619  // Anchor is assumed to point to a content elements:
620  if (‪MathUtility::canBeInterpretedAsInteger($tLP['anchor'])) {
621  // Initialize a new entry because we have a new relation:
622  $newTokenID = $this->‪makeTokenID('setTypoLinkPartsElement:anchor:' . $idx);
623  $elements[$newTokenID . ':' . $idx] = [];
624  $elements[$newTokenID . ':' . $idx]['matchString'] = 'Anchor Content Element: ' . $tLP['anchor'];
625  $content .= '#{softref:' . $newTokenID . '}';
626  $elements[$newTokenID . ':' . $idx]['subst'] = [
627  'type' => 'db',
628  'recordRef' => 'tt_content:' . $tLP['anchor'],
629  'tokenID' => $newTokenID,
630  'tokenValue' => $tLP['anchor']
631  ];
632  } else {
633  // Anchor is a hardcoded string
634  $content .= '#' . $tLP['anchor'];
635  }
636  }
637  break;
639  $elements[$tokenID . ':' . $idx]['subst'] = [
640  'type' => 'db',
641  'recordRef' => $tLP['table'] . ':' . $tLP['uid'],
642  'tokenID' => $tokenID,
643  'tokenValue' => $content,
644  ];
645 
646  $content = '{softref:' . $tokenID . '}';
647  break;
648  default:
649  $event = new AppendLinkHandlerElementsEvent($tLP, $content, $elements, $idx, $tokenID);
650  $this->eventDispatcher->dispatch($event);
651 
652  $elements = $event->getElements();
653  $tLP = $event->getLinkParts();
654  $content = $event->getContent();
655 
656  if (!$event->isResolved()) {
657  $elements[$tokenID . ':' . $idx]['error'] = 'Couldn\'t decide typolink mode.';
658  return $content;
659  }
660  }
661  // Finally, for all entries that was rebuild with tokens, add target, class, title and additionalParams in the end:
662  $tLP['url'] = $content;
663  $content = GeneralUtility::makeInstance(TypoLinkCodecService::class)->encode($tLP);
664 
665  // Return rebuilt typolink value:
666  return $content;
667  }
668 
675  public function ‪makeTokenID($index = '')
676  {
677  return md5($this->tokenID_basePrefix . ':' . $index);
678  }
679 }
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\getTypoLinkParts
‪array getTypoLinkParts($typolinkValue)
Definition: SoftReferenceIndex.php:447
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\setTypoLinkPartsElement
‪string setTypoLinkPartsElement($tLP, &$elements, $content, $idx)
Definition: SoftReferenceIndex.php:521
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\__construct
‪__construct(EventDispatcherInterface $eventDispatcher)
Definition: SoftReferenceIndex.php:97
‪TYPO3\CMS\Core\Resource\FileInterface
Definition: FileInterface.php:22
‪TYPO3\CMS\Core\Html\HtmlParser
Definition: HtmlParser.php:27
‪TYPO3\CMS\Core\Exception
Definition: Exception.php:22
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\$referenceUid
‪int $referenceUid
Definition: SoftReferenceIndex.php:91
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\$eventDispatcher
‪EventDispatcherInterface $eventDispatcher
Definition: SoftReferenceIndex.php:87
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\findRef_email
‪array null findRef_email($content, $spParams)
Definition: SoftReferenceIndex.php:325
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\makeTokenID
‪string makeTokenID($index='')
Definition: SoftReferenceIndex.php:671
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\findRef_typolink_tag
‪array null findRef_typolink_tag($content)
Definition: SoftReferenceIndex.php:219
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\$referenceTable
‪string $referenceTable
Definition: SoftReferenceIndex.php:95
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:24
‪TYPO3\CMS\Backend\Utility\BackendUtility\getPagesTSconfig
‪static array getPagesTSconfig($id)
Definition: BackendUtility.php:698
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\findRef_url
‪array null findRef_url($content, $spParams)
Definition: SoftReferenceIndex.php:364
‪TYPO3\CMS\Core\Resource\AbstractFile\getUid
‪int getUid()
Definition: AbstractFile.php:202
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:75
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:95
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:23
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\findRef_extension_fileref
‪array null findRef_extension_fileref($content)
Definition: SoftReferenceIndex.php:405
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\$tokenID_basePrefix
‪string $tokenID_basePrefix
Definition: SoftReferenceIndex.php:83
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\findRef
‪array bool null findRef($table, $field, $uid, $content, $spKey, $spParams, $structurePath='')
Definition: SoftReferenceIndex.php:114
‪TYPO3\CMS\Core\Database\SoftReferenceIndex\findRef_typolink
‪array null findRef_typolink($content, $spParams)
Definition: SoftReferenceIndex.php:181
‪TYPO3\CMS\Core\Database\SoftReferenceIndex
Definition: SoftReferenceIndex.php:80
‪TYPO3\CMS\Core\Database
Definition: Connection.php:18