TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
SoftReferenceIndex.php
Go to the documentation of this file.
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 
19 
69 {
73  public $tokenID_basePrefix = '';
74 
87  public function findRef($table, $field, $uid, $content, $spKey, $spParams, $structurePath = '')
88  {
89  $retVal = false;
90  $this->tokenID_basePrefix = $table . ':' . $uid . ':' . $field . ':' . $structurePath . ':' . $spKey;
91  switch ($spKey) {
92  case 'notify':
93  // Simple notification
94  $resultArray = [
95  'elements' => [
96  [
97  'matchString' => $content
98  ]
99  ]
100  ];
101  $retVal = $resultArray;
102  break;
103  case 'substitute':
104  $tokenID = $this->makeTokenID();
105  $resultArray = [
106  'content' => '{softref:' . $tokenID . '}',
107  'elements' => [
108  [
109  'matchString' => $content,
110  'subst' => [
111  'type' => 'string',
112  'tokenID' => $tokenID,
113  'tokenValue' => $content
114  ]
115  ]
116  ]
117  ];
118  $retVal = $resultArray;
119  break;
120  case 'images':
121  $retVal = $this->findRef_images($content, $spParams);
122  break;
123  case 'typolink':
124  $retVal = $this->findRef_typolink($content, $spParams);
125  break;
126  case 'typolink_tag':
127  $retVal = $this->findRef_typolink_tag($content, $spParams);
128  break;
129  case 'ext_fileref':
130  $retVal = $this->findRef_extension_fileref($content, $spParams);
131  break;
132  case 'email':
133  $retVal = $this->findRef_email($content, $spParams);
134  break;
135  case 'url':
136  $retVal = $this->findRef_url($content, $spParams);
137  break;
138  default:
139  $retVal = false;
140  }
141  return $retVal;
142  }
143 
154  public function findRef_images($content, $spParams)
155  {
156  // Start HTML parser and split content by image tag:
157  $htmlParser = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Html\HtmlParser::class);
158  $splitContent = $htmlParser->splitTags('img', $content);
159  $elements = [];
160  // Traverse splitted parts:
161  foreach ($splitContent as $k => $v) {
162  if ($k % 2) {
163  // Get file reference:
164  $attribs = $htmlParser->get_tag_attributes($v);
165  $srcRef = htmlspecialchars_decode($attribs[0]['src']);
166  $pI = pathinfo($srcRef);
167  // If it looks like a local image, continue. Otherwise ignore it.
168  $absPath = GeneralUtility::getFileAbsFileName(PATH_site . $srcRef);
169  if (!$pI['scheme'] && !$pI['query'] && $absPath && $srcRef !== 'clear.gif') {
170  // Initialize the element entry with info text here:
171  $tokenID = $this->makeTokenID($k);
172  $elements[$k] = [];
173  $elements[$k]['matchString'] = $v;
174  // If the image seems to be an RTE image, then proceed to set up substitution token:
175  if (GeneralUtility::isFirstPartOfStr($srcRef, 'uploads/') && preg_match('/^RTEmagicC_/', basename($srcRef))) {
176  // Token and substitute value:
177  // 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)
178  if (strstr($splitContent[$k], $attribs[0]['src'])) {
179  // Substitute value with token (this is not be an exact method if the value is in there twice, but we assume it will not)
180  $splitContent[$k] = str_replace($attribs[0]['src'], '{softref:' . $tokenID . '}', $splitContent[$k]);
181  $elements[$k]['subst'] = [
182  'type' => 'file',
183  'relFileName' => $srcRef,
184  'tokenID' => $tokenID,
185  'tokenValue' => $attribs[0]['src']
186  ];
187  // Finally, notice if the file does not exist.
188  if (!@is_file($absPath)) {
189  $elements[$k]['error'] = 'File does not exist!';
190  }
191  } else {
192  $elements[$k]['error'] = 'Could not substitute image source with token!';
193  }
194  }
195  }
196  }
197  }
198  // Return result:
199  if (!empty($elements)) {
200  $resultArray = [
201  'content' => implode('', $splitContent),
202  'elements' => $elements
203  ];
204  return $resultArray;
205  }
206  }
207 
217  public function findRef_typolink($content, $spParams)
218  {
219  // First, split the input string by a comma if the "linkList" parameter is set.
220  // 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.
221  if (is_array($spParams) && in_array('linkList', $spParams)) {
222  // Preserving whitespace on purpose.
223  $linkElement = explode(',', $content);
224  } else {
225  // If only one element, just set in this array to make it easy below.
226  $linkElement = [$content];
227  }
228  // Traverse the links now:
229  $elements = [];
230  foreach ($linkElement as $k => $typolinkValue) {
231  $tLP = $this->getTypoLinkParts($typolinkValue);
232  $linkElement[$k] = $this->setTypoLinkPartsElement($tLP, $elements, $typolinkValue, $k);
233  }
234  // Return output:
235  if (!empty($elements)) {
236  $resultArray = [
237  'content' => implode(',', $linkElement),
238  'elements' => $elements
239  ];
240  return $resultArray;
241  }
242  }
243 
253  public function findRef_typolink_tag($content, $spParams)
254  {
255  // Parse string for special TYPO3 <link> tag:
256  $htmlParser = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Html\HtmlParser::class);
257  $linkTags = $htmlParser->splitTags('link', $content);
258  // Traverse result:
259  $elements = [];
260  foreach ($linkTags as $k => $foundValue) {
261  if ($k % 2) {
262  $typolinkValue = preg_replace('/<LINK[[:space:]]+/i', '', substr($foundValue, 0, -1));
263  $tLP = $this->getTypoLinkParts($typolinkValue);
264  $linkTags[$k] = '<LINK ' . $this->setTypoLinkPartsElement($tLP, $elements, $typolinkValue, $k) . '>';
265  }
266  }
267  // Return output:
268  if (!empty($elements)) {
269  $resultArray = [
270  'content' => implode('', $linkTags),
271  'elements' => $elements
272  ];
273  return $resultArray;
274  }
275  }
276 
284  public function findRef_email($content, $spParams)
285  {
286  // Email:
287  $parts = preg_split('/([^[:alnum:]]+)([A-Za-z0-9\\._-]+[@][A-Za-z0-9\\._-]+[\\.].[A-Za-z0-9]+)/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
288  foreach ($parts as $idx => $value) {
289  if ($idx % 3 == 2) {
290  $tokenID = $this->makeTokenID($idx);
291  $elements[$idx] = [];
292  $elements[$idx]['matchString'] = $value;
293  if (is_array($spParams) && in_array('subst', $spParams)) {
294  $parts[$idx] = '{softref:' . $tokenID . '}';
295  $elements[$idx]['subst'] = [
296  'type' => 'string',
297  'tokenID' => $tokenID,
298  'tokenValue' => $value
299  ];
300  }
301  }
302  }
303  // Return output:
304  if (!empty($elements)) {
305  $resultArray = [
306  'content' => substr(implode('', $parts), 1, -1),
307  'elements' => $elements
308  ];
309  return $resultArray;
310  }
311  }
312 
320  public function findRef_url($content, $spParams)
321  {
322  // URLs
323  $parts = preg_split('/([^[:alnum:]"\']+)((http|ftp):\\/\\/[^[:space:]"\'<>]*)([[:space:]])/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
324  foreach ($parts as $idx => $value) {
325  if ($idx % 5 == 3) {
326  unset($parts[$idx]);
327  }
328  if ($idx % 5 == 2) {
329  $tokenID = $this->makeTokenID($idx);
330  $elements[$idx] = [];
331  $elements[$idx]['matchString'] = $value;
332  if (is_array($spParams) && in_array('subst', $spParams)) {
333  $parts[$idx] = '{softref:' . $tokenID . '}';
334  $elements[$idx]['subst'] = [
335  'type' => 'string',
336  'tokenID' => $tokenID,
337  'tokenValue' => $value
338  ];
339  }
340  }
341  }
342  // Return output:
343  if (!empty($elements)) {
344  $resultArray = [
345  'content' => substr(implode('', $parts), 1, -1),
346  'elements' => $elements
347  ];
348  return $resultArray;
349  }
350  }
351 
359  public function findRef_extension_fileref($content, $spParams)
360  {
361  // Files starting with EXT:
362  $parts = preg_split('/([^[:alnum:]"\']+)(EXT:[[:alnum:]_]+\\/[^[:space:]"\',]*)/', ' ' . $content . ' ', 10000, PREG_SPLIT_DELIM_CAPTURE);
363  foreach ($parts as $idx => $value) {
364  if ($idx % 3 == 2) {
365  $this->makeTokenID($idx);
366  $elements[$idx] = [];
367  $elements[$idx]['matchString'] = $value;
368  }
369  }
370  // Return output:
371  if (!empty($elements)) {
372  $resultArray = [
373  'content' => substr(implode('', $parts), 1, -1),
374  'elements' => $elements
375  ];
376  return $resultArray;
377  }
378  }
379 
380  /*************************
381  *
382  * Helper functions
383  *
384  *************************/
385 
397  public function getTypoLinkParts($typolinkValue)
398  {
399  $finalTagParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($typolinkValue);
400 
401  $link_param = $finalTagParts['url'];
402  // we define various keys below, "url" might be misleading
403  unset($finalTagParts['url']);
404 
405  // Parse URL:
406  $pU = @parse_url($link_param);
407 
408  // If it's a mail address:
409  if (strstr($link_param, '@') && !$pU['scheme']) {
410  $link_param = preg_replace('/^mailto:/i', '', $link_param);
411  $finalTagParts['LINK_TYPE'] = 'mailto';
412  $finalTagParts['url'] = trim($link_param);
413  return $finalTagParts;
414  }
415 
416  list($linkHandlerKeyword, $linkHandlerValue) = explode(':', trim($link_param), 2);
417 
418  // Dispatch available signal slots.
419  $linkHandlerFound = false;
420  list($linkHandlerFound, $finalTagParts) = $this->emitGetTypoLinkParts($linkHandlerFound, $finalTagParts, $linkHandlerKeyword, $linkHandlerValue);
421  if ($linkHandlerFound) {
422  return $finalTagParts;
423  }
424 
425  // Check for FAL link-handler keyword
426  if ($linkHandlerKeyword === 'file') {
427  $finalTagParts['LINK_TYPE'] = 'file';
428  $finalTagParts['identifier'] = trim($link_param);
429  return $finalTagParts;
430  }
431 
432  $isLocalFile = 0;
433  $fileChar = (int)strpos($link_param, '/');
434  $urlChar = (int)strpos($link_param, '.');
435 
436  // Detects if a file is found in site-root and if so it will be treated like a normal file.
437  list($rootFileDat) = explode('?', rawurldecode($link_param));
438  $containsSlash = strstr($rootFileDat, '/');
439  $rFD_fI = pathinfo($rootFileDat);
440  $fileExtension = strtolower($rFD_fI['extension']);
441  if (!$containsSlash && trim($rootFileDat) && (@is_file(PATH_site . $rootFileDat) || $fileExtension === 'php' || $fileExtension === 'html' || $fileExtension === 'htm')) {
442  $isLocalFile = 1;
443  } elseif ($containsSlash) {
444  // Adding this so realurl directories are linked right (non-existing).
445  $isLocalFile = 2;
446  }
447  if ($pU['scheme'] || ($isLocalFile != 1 && $urlChar && (!$containsSlash || $urlChar < $fileChar))) { // url (external): If doubleSlash or if a '.' comes before a '/'.
448  $finalTagParts['LINK_TYPE'] = 'url';
449  $finalTagParts['url'] = $link_param;
450  } elseif ($containsSlash || $isLocalFile) { // file (internal)
451  $splitLinkParam = explode('?', $link_param);
452  if (file_exists(rawurldecode($splitLinkParam[0])) || $isLocalFile) {
453  $finalTagParts['LINK_TYPE'] = 'file';
454  $finalTagParts['filepath'] = rawurldecode($splitLinkParam[0]);
455  $finalTagParts['query'] = $splitLinkParam[1];
456  }
457  } else {
458  // integer or alias (alias is without slashes or periods or commas, that is
459  // 'nospace,alphanum_x,lower,unique' according to definition in $GLOBALS['TCA']!)
460  $finalTagParts['LINK_TYPE'] = 'page';
461 
462  $link_params_parts = explode('#', $link_param);
463  // Link-data del
464  $link_param = trim($link_params_parts[0]);
465 
466  if ((string)$link_params_parts[1] !== '') {
467  $finalTagParts['anchor'] = trim($link_params_parts[1]);
468  }
469 
470  // Splitting the parameter by ',' and if the array counts more than 1 element it's a id/type/? pair
471  $pairParts = GeneralUtility::trimExplode(',', $link_param);
472  if (count($pairParts) > 1) {
473  $link_param = $pairParts[0];
474  $finalTagParts['type'] = $pairParts[1]; // Overruling 'type'
475  }
476 
477  // Checking if the id-parameter is an alias.
478  if ((string)$link_param !== '') {
479  if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($link_param)) {
480  $finalTagParts['alias'] = $link_param;
481  $link_param = $this->getPageIdFromAlias($link_param);
482  }
483 
484  $finalTagParts['page_id'] = (int)$link_param;
485  }
486  }
487 
488  return $finalTagParts;
489  }
490 
501  public function setTypoLinkPartsElement($tLP, &$elements, $content, $idx)
502  {
503  // Initialize, set basic values. In any case a link will be shown
504  $tokenID = $this->makeTokenID('setTypoLinkPartsElement:' . $idx);
505  $elements[$tokenID . ':' . $idx] = [];
506  $elements[$tokenID . ':' . $idx]['matchString'] = $content;
507  // Based on link type, maybe do more:
508  switch ((string)$tLP['LINK_TYPE']) {
509  case 'mailto':
510 
511  case 'url':
512  // Mail addresses and URLs can be substituted manually:
513  $elements[$tokenID . ':' . $idx]['subst'] = [
514  'type' => 'string',
515  'tokenID' => $tokenID,
516  'tokenValue' => $tLP['url']
517  ];
518  // Output content will be the token instead:
519  $content = '{softref:' . $tokenID . '}';
520  break;
521  case 'file':
522  // Process files referenced by their FAL uid
523  if ($tLP['identifier']) {
524  list($linkHandlerKeyword, $linkHandlerValue) = explode(':', trim($tLP['identifier']), 2);
525  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($linkHandlerValue)) {
526  // Token and substitute value
527  $elements[$tokenID . ':' . $idx]['subst'] = [
528  'type' => 'db',
529  'recordRef' => 'sys_file:' . $linkHandlerValue,
530  'tokenID' => $tokenID,
531  'tokenValue' => $tLP['identifier'],
532  ];
533  // Output content will be the token instead:
534  $content = '{softref:' . $tokenID . '}';
535  } else {
536  // This is a link to a folder...
537  return $content;
538  }
539  } else {
540  return $content;
541  }
542  break;
543  case 'page':
544  // Rebuild page reference typolink part:
545  $content = '';
546  // Set page id:
547  if ($tLP['page_id']) {
548  $content .= '{softref:' . $tokenID . '}';
549  $elements[$tokenID . ':' . $idx]['subst'] = [
550  'type' => 'db',
551  'recordRef' => 'pages:' . $tLP['page_id'],
552  'tokenID' => $tokenID,
553  'tokenValue' => $tLP['alias'] ? $tLP['alias'] : $tLP['page_id']
554  ];
555  }
556  // Add type if applicable
557  if ((string)$tLP['type'] !== '') {
558  $content .= ',' . $tLP['type'];
559  }
560  // Add anchor if applicable
561  if ((string)$tLP['anchor'] !== '') {
562  // Anchor is assumed to point to a content elements:
563  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($tLP['anchor'])) {
564  // Initialize a new entry because we have a new relation:
565  $newTokenID = $this->makeTokenID('setTypoLinkPartsElement:anchor:' . $idx);
566  $elements[$newTokenID . ':' . $idx] = [];
567  $elements[$newTokenID . ':' . $idx]['matchString'] = 'Anchor Content Element: ' . $tLP['anchor'];
568  $content .= '#{softref:' . $newTokenID . '}';
569  $elements[$newTokenID . ':' . $idx]['subst'] = [
570  'type' => 'db',
571  'recordRef' => 'tt_content:' . $tLP['anchor'],
572  'tokenID' => $newTokenID,
573  'tokenValue' => $tLP['anchor']
574  ];
575  } else {
576  // Anchor is a hardcoded string
577  $content .= '#' . $tLP['type'];
578  }
579  }
580  break;
581  default:
582  $linkHandlerFound = false;
583  list($linkHandlerFound, $tLP, $content, $newElements) = $this->emitSetTypoLinkPartsElement($linkHandlerFound, $tLP, $content, $elements, $idx, $tokenID);
584  // We need to merge the array, otherwise we would loose the reference.
586 
587  if (!$linkHandlerFound) {
588  $elements[$tokenID . ':' . $idx]['error'] = 'Couldn\'t decide typolink mode.';
589  return $content;
590  }
591  }
592  // Finally, for all entries that was rebuild with tokens, add target, class, title and additionalParams in the end:
593  $tLP['url'] = $content;
594  $content = GeneralUtility::makeInstance(TypoLinkCodecService::class)->encode($tLP);
595 
596  // Return rebuilt typolink value:
597  return $content;
598  }
599 
606  public function getPageIdFromAlias($link_param)
607  {
608  $pRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordsByField('pages', 'alias', $link_param);
609  return $pRec[0]['uid'];
610  }
611 
618  public function makeTokenID($index = '')
619  {
620  return md5($this->tokenID_basePrefix . ':' . $index);
621  }
622 
626  protected function getSignalSlotDispatcher()
627  {
628  return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
629  }
630 
638  protected function emitGetTypoLinkParts($linkHandlerFound, $finalTagParts, $linkHandlerKeyword, $linkHandlerValue)
639  {
640  return $this->getSignalSlotDispatcher()->dispatch(get_class($this), 'getTypoLinkParts', [$linkHandlerFound, $finalTagParts, $linkHandlerKeyword, $linkHandlerValue]);
641  }
642 
652  protected function emitSetTypoLinkPartsElement($linkHandlerFound, $tLP, $content, $elements, $idx, $tokenID)
653  {
654  return $this->getSignalSlotDispatcher()->dispatch(get_class($this), 'setTypoLinkPartsElement', [$linkHandlerFound, $tLP, $content, $elements, $idx, $tokenID, $this]);
655  }
656 }
setTypoLinkPartsElement($tLP, &$elements, $content, $idx)
findRef($table, $field, $uid, $content, $spKey, $spParams, $structurePath= '')
static isFirstPartOfStr($str, $partStr)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
emitSetTypoLinkPartsElement($linkHandlerFound, $tLP, $content, $elements, $idx, $tokenID)
static getRecordsByField($theTable, $theField, $theValue, $whereClause= '', $groupBy= '', $orderBy= '', $limit= '', $useDeleteClause=true, $queryBuilder=null)
emitGetTypoLinkParts($linkHandlerFound, $finalTagParts, $linkHandlerKeyword, $linkHandlerValue)
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
static makeInstance($className,...$constructorArguments)
static getFileAbsFileName($filename, $_=null, $_2=null)