TYPO3 CMS  TYPO3_8-7
RteHtmlParser.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Html;
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 
25 
35 {
40  public $blockElementList = 'DIV,TABLE,BLOCKQUOTE,PRE,UL,OL,H1,H2,H3,H4,H5,H6,ADDRESS,DL,DD,HEADER,SECTION,FOOTER,NAV,ARTICLE,ASIDE';
41 
46  protected $defaultAllowedTagsList = 'b,i,u,a,img,br,div,center,pre,font,hr,sub,sup,p,strong,em,li,ul,ol,blockquote,strike,span,abbr,acronym,dfn';
47 
53  public $recPid = 0;
54 
60  public $elRef = '';
61 
67  public $tsConfig = [];
68 
74  public $procOptions = [];
75 
82 
88  public $getKeepTags_cache = [];
89 
95  public $allowedClasses = [];
96 
104  'class',
105  'align',
106  'id',
107  'title',
108  'dir',
109  'lang',
110  'xml:lang',
111  'itemscope',
112  'itemtype',
113  'itemprop'
114  ];
115 
124  'address',
125  'article',
126  'aside',
127  'blockquote',
128  'div',
129  'footer',
130  'header',
131  'hr',
132  'nav',
133  'section'
134  ];
135 
142  public function init($elRef = '', $recPid = 0)
143  {
144  $this->recPid = $recPid;
145  $this->elRef = $elRef;
146  }
147 
148  /**********************************************
149  *
150  * Main function
151  *
152  **********************************************/
163  public function RTE_transform($value, $specConf = [], $direction = 'rte', $thisConfig = [])
164  {
165  $this->tsConfig = $thisConfig;
166  $this->procOptions = (array)$thisConfig['proc.'];
167  if (isset($this->procOptions['allowedClasses.'])) {
168  $this->allowedClasses = (array)$this->procOptions['allowedClasses.'];
169  } else {
170  $this->allowedClasses = GeneralUtility::trimExplode(',', $this->procOptions['allowedClasses'], true);
171  }
172 
173  // Dynamic configuration of blockElementList
174  if ($this->procOptions['blockElementList']) {
175  $this->blockElementList = $this->procOptions['blockElementList'];
176  }
177 
178  // Define which attributes are allowed on <p> tags
179  if (isset($this->procOptions['allowAttributes.'])) {
180  $this->allowedAttributesForParagraphTags = $this->procOptions['allowAttributes.'];
181  } elseif (isset($this->procOptions['keepPDIVattribs'])) {
182  $this->allowedAttributesForParagraphTags = GeneralUtility::trimExplode(',', strtolower($this->procOptions['keepPDIVattribs']), true);
183  }
184  // Override tags which are allowed outside of <p> tags
185  if (isset($this->procOptions['allowTagsOutside'])) {
186  if (!isset($this->procOptions['allowTagsOutside.'])) {
187  $this->allowedTagsOutsideOfParagraphs = GeneralUtility::trimExplode(',', strtolower($this->procOptions['allowTagsOutside']), true);
188  } else {
189  $this->allowedTagsOutsideOfParagraphs = (array)$this->procOptions['allowTagsOutside.'];
190  }
191  }
192 
193  // Setting modes / transformations to be called
194  if ((string)$this->procOptions['overruleMode'] !== '') {
195  $modes = GeneralUtility::trimExplode(',', $this->procOptions['overruleMode']);
196  } elseif (!empty($this->procOptions['mode'])) {
197  $modes = [$this->procOptions['mode']];
198  } else {
199  // Get parameters for rte_transformation:
200  // @deprecated since TYPO3 v8, will be removed in TYPO3 v9 - the else{} part can be removed in v9
202  'Argument 2 of RteHtmlParser::RTE_transform() is deprecated. Transformations should be given in $thisConfig[\'proc.\'][\'overruleMode\']'
203  );
204  $specialFieldConfiguration = BackendUtility::getSpecConfParametersFromArray($specConf['rte_transform']['parameters']);
205  $modes = GeneralUtility::trimExplode('-', $specialFieldConfiguration['mode']);
206  }
207  $modes = $this->resolveAppliedTransformationModes($direction, $modes);
208 
209  $value = $this->streamlineLineBreaksForProcessing($value);
210 
211  // If an entry HTML cleaner was configured, pass the content through the HTMLcleaner
212  $value = $this->runHtmlParserIfConfigured($value, 'entryHTMLparser_' . $direction);
213 
214  // Traverse modes
215  foreach ($modes as $cmd) {
216  if ($direction === 'db') {
217  // Checking for user defined transformation:
218  if ($_classRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['transformation'][$cmd]) {
219  $_procObj = GeneralUtility::getUserObj($_classRef);
220  $_procObj->pObj = $this;
221  $_procObj->transformationKey = $cmd;
222  $value = $_procObj->transform_db($value, $this);
223  } else {
224  // ... else use defaults:
225  switch ($cmd) {
226  case 'detectbrokenlinks':
227  $value = $this->removeBrokenLinkMarkers($value);
228  break;
229  case 'ts_images':
230  $value = $this->TS_images_db($value);
231  break;
232  case 'ts_links':
233  $value = $this->TS_links_db($value);
234  break;
235  case 'css_transform':
236  // Transform empty paragraphs into spacing paragraphs
237  $value = str_replace('<p></p>', '<p>&nbsp;</p>', $value);
238  // Double any trailing spacing paragraph so that it does not get removed by divideIntoLines()
239  $value = preg_replace('/<p>&nbsp;<\/p>$/', '<p>&nbsp;</p>' . '<p>&nbsp;</p>', $value);
240  $value = $this->TS_transform_db($value);
241  break;
242  default:
243  // Do nothing
244  }
245  }
246  } elseif ($direction === 'rte') {
247  // Checking for user defined transformation:
248  if ($_classRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['transformation'][$cmd]) {
249  $_procObj = GeneralUtility::getUserObj($_classRef);
250  $_procObj->pObj = $this;
251  $value = $_procObj->transform_rte($value, $this);
252  } else {
253  // ... else use defaults:
254  switch ($cmd) {
255  case 'detectbrokenlinks':
256  $value = $this->markBrokenLinks($value);
257  break;
258  case 'ts_images':
259  $value = $this->TS_images_rte($value);
260  break;
261  case 'ts_links':
262  $value = $this->TS_links_rte($value);
263  break;
264  case 'css_transform':
265  $value = $this->TS_transform_rte($value);
266  break;
267  default:
268  // Do nothing
269  }
270  }
271  }
272  }
273 
274  // If an exit HTML cleaner was configured, pass the content through the HTMLcleaner
275  $value = $this->runHtmlParserIfConfigured($value, 'exitHTMLparser_' . $direction);
276 
277  // Final clean up of linebreaks
278  $value = $this->streamlineLineBreaksAfterProcessing($value);
279 
280  return $value;
281  }
282 
290  protected function resolveAppliedTransformationModes(string $direction, array $modes)
291  {
292  $modeList = implode(',', $modes);
293 
294  // Replace the shortcut "default" with all custom modes
295  $modeList = str_replace('default', 'detectbrokenlinks,css_transform,ts_images,ts_links', $modeList);
296  // Replace the shortcut "ts_css" with all custom modes
297  // @deprecated since TYPO3 v8, will be removed in TYPO3 v9 - NEXT line can be removed in v9
298  $modeList = str_replace('ts_css', 'detectbrokenlinks,css_transform,ts_images,ts_links', $modeList);
299 
300  // Make list unique
301  $modes = array_unique(GeneralUtility::trimExplode(',', $modeList, true));
302  // Reverse order if direction is "rte"
303  if ($direction === 'rte') {
304  $modes = array_reverse($modes);
305  }
306 
307  return $modes;
308  }
309 
321  protected function runHtmlParserIfConfigured($content, $configurationDirective)
322  {
323  if ($this->procOptions[$configurationDirective]) {
324  list($keepTags, $keepNonMatchedTags, $hscMode, $additionalConfiguration) = $this->HTMLparserConfig($this->procOptions[$configurationDirective . '.']);
325  $content = $this->HTMLcleaner($content, $keepTags, $keepNonMatchedTags, $hscMode, $additionalConfiguration);
326  }
327  return $content;
328  }
329 
330  /************************************
331  *
332  * Specific RTE TRANSFORMATION functions
333  *
334  *************************************/
346  public function TS_images_db($value)
347  {
348  // Split content by <img> tags and traverse the resulting array for processing:
349  $imgSplit = $this->splitTags('img', $value);
350  if (count($imgSplit) > 1) {
351  $siteUrl = GeneralUtility::getIndpEnv('TYPO3_SITE_URL');
352  $sitePath = str_replace(GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST'), '', $siteUrl);
354  $resourceFactory = Resource\ResourceFactory::getInstance();
356  $magicImageService = GeneralUtility::makeInstance(Resource\Service\MagicImageService::class);
357  $magicImageService->setMagicImageMaximumDimensions($this->tsConfig);
358  foreach ($imgSplit as $k => $v) {
359  // Image found, do processing:
360  if ($k % 2) {
361  // Get attributes
362  list($attribArray) = $this->get_tag_attributes($v, true);
363  // It's always an absolute URL coming from the RTE into the Database.
364  $absoluteUrl = trim($attribArray['src']);
365  // Make path absolute if it is relative and we have a site path which is not '/'
366  $pI = pathinfo($absoluteUrl);
367  if ($sitePath && !$pI['scheme'] && GeneralUtility::isFirstPartOfStr($absoluteUrl, $sitePath)) {
368  // If site is in a subpath (eg. /~user_jim/) this path needs to be removed because it will be added with $siteUrl
369  $absoluteUrl = substr($absoluteUrl, strlen($sitePath));
370  $absoluteUrl = $siteUrl . $absoluteUrl;
371  }
372  // Image dimensions set in the img tag, if any
373  $imgTagDimensions = $this->getWHFromAttribs($attribArray);
374  if ($imgTagDimensions[0]) {
375  $attribArray['width'] = $imgTagDimensions[0];
376  }
377  if ($imgTagDimensions[1]) {
378  $attribArray['height'] = $imgTagDimensions[1];
379  }
380  $originalImageFile = null;
381  if ($attribArray['data-htmlarea-file-uid']) {
382  // An original image file uid is available
383  try {
385  $originalImageFile = $resourceFactory->getFileObject((int)$attribArray['data-htmlarea-file-uid']);
386  } catch (Resource\Exception\FileDoesNotExistException $fileDoesNotExistException) {
387  // Log the fact the file could not be retrieved.
388  $message = sprintf('Could not find file with uid "%s"', $attribArray['data-htmlarea-file-uid']);
389  $this->getLogger()->error($message);
390  }
391  }
392  if ($originalImageFile instanceof Resource\File) {
393  // Public url of local file is relative to the site url, absolute otherwise
394  if ($absoluteUrl == $originalImageFile->getPublicUrl() || $absoluteUrl == $siteUrl . $originalImageFile->getPublicUrl()) {
395  // This is a plain image, i.e. reference to the original image
396  if ($this->procOptions['plainImageMode']) {
397  // "plain image mode" is configured
398  // Find the dimensions of the original image
399  $imageInfo = [
400  $originalImageFile->getProperty('width'),
401  $originalImageFile->getProperty('height')
402  ];
403  if (!$imageInfo[0] || !$imageInfo[1]) {
404  $filePath = $originalImageFile->getForLocalProcessing(false);
405  $imageInfoObject = GeneralUtility::makeInstance(ImageInfo::class, $filePath);
406  $imageInfo = [
407  $imageInfoObject->getWidth(),
408  $imageInfoObject->getHeight()
409  ];
410  }
411  $attribArray = $this->applyPlainImageModeSettings($imageInfo, $attribArray);
412  }
413  } else {
414  // Magic image case: get a processed file with the requested configuration
415  $imageConfiguration = [
416  'width' => $imgTagDimensions[0],
417  'height' => $imgTagDimensions[1]
418  ];
419  $magicImage = $magicImageService->createMagicImage($originalImageFile, $imageConfiguration);
420  $attribArray['width'] = $magicImage->getProperty('width');
421  $attribArray['height'] = $magicImage->getProperty('height');
422  $attribArray['src'] = $magicImage->getPublicUrl();
423  }
424  } elseif (!GeneralUtility::isFirstPartOfStr($absoluteUrl, $siteUrl) && !$this->procOptions['dontFetchExtPictures'] && TYPO3_MODE === 'BE') {
425  // External image from another URL: in that case, fetch image, unless the feature is disabled or we are not in backend mode
426  // Fetch the external image
427  $externalFile = GeneralUtility::getUrl($absoluteUrl);
428  if ($externalFile) {
429  $pU = parse_url($absoluteUrl);
430  $pI = pathinfo($pU['path']);
431  $extension = strtolower($pI['extension']);
432  if ($extension === 'jpg' || $extension === 'jpeg' || $extension === 'gif' || $extension === 'png') {
433  $fileName = GeneralUtility::shortMD5($absoluteUrl) . '.' . $pI['extension'];
434  // We insert this image into the user default upload folder
435  list($table, $field) = explode(':', $this->elRef);
437  $folder = $GLOBALS['BE_USER']->getDefaultUploadFolder($this->recPid, $table, $field);
439  $fileObject = $folder->createFile($fileName)->setContents($externalFile);
440  $imageConfiguration = [
441  'width' => $attribArray['width'],
442  'height' => $attribArray['height']
443  ];
444  $magicImage = $magicImageService->createMagicImage($fileObject, $imageConfiguration);
445  $attribArray['width'] = $magicImage->getProperty('width');
446  $attribArray['height'] = $magicImage->getProperty('height');
447  $attribArray['data-htmlarea-file-uid'] = $fileObject->getUid();
448  $attribArray['src'] = $magicImage->getPublicUrl();
449  }
450  }
451  } elseif (GeneralUtility::isFirstPartOfStr($absoluteUrl, $siteUrl)) {
452  // Finally, check image as local file (siteURL equals the one of the image)
453  // Image has no data-htmlarea-file-uid attribute
454  // Relative path, rawurldecoded for special characters.
455  $path = rawurldecode(substr($absoluteUrl, strlen($siteUrl)));
456  // Absolute filepath, locked to relative path of this project
457  $filepath = GeneralUtility::getFileAbsFileName($path);
458  // Check file existence (in relative directory to this installation!)
459  if ($filepath && @is_file($filepath)) {
460  // Treat it as a plain image
461  if ($this->procOptions['plainImageMode']) {
462  // If "plain image mode" has been configured
463  // Find the original dimensions of the image
464  $imageInfoObject = GeneralUtility::makeInstance(ImageInfo::class, $filepath);
465  $imageInfo = [
466  $imageInfoObject->getWidth(),
467  $imageInfoObject->getHeight()
468  ];
469  $attribArray = $this->applyPlainImageModeSettings($imageInfo, $attribArray);
470  }
471  // Let's try to find a file uid for this image
472  try {
473  $fileOrFolderObject = $resourceFactory->retrieveFileOrFolderObject($path);
474  if ($fileOrFolderObject instanceof Resource\FileInterface) {
475  $fileIdentifier = $fileOrFolderObject->getIdentifier();
477  $fileObject = $fileOrFolderObject->getStorage()->getFile($fileIdentifier);
478  // @todo if the retrieved file is a processed file, get the original file...
479  $attribArray['data-htmlarea-file-uid'] = $fileObject->getUid();
480  }
481  } catch (Resource\Exception\ResourceDoesNotExistException $resourceDoesNotExistException) {
482  // Nothing to be done if file/folder not found
483  }
484  }
485  }
486  // Remove width and height from style attribute
487  $attribArray['style'] = preg_replace('/(?:^|[^-])(\\s*(?:width|height)\\s*:[^;]*(?:$|;))/si', '', $attribArray['style']);
488  // Must have alt attribute
489  if (!isset($attribArray['alt'])) {
490  $attribArray['alt'] = '';
491  }
492  // Convert absolute to relative url
493  if (GeneralUtility::isFirstPartOfStr($attribArray['src'], $siteUrl)) {
494  $attribArray['src'] = substr($attribArray['src'], strlen($siteUrl));
495  }
496  $imgSplit[$k] = '<img ' . GeneralUtility::implodeAttributes($attribArray, true, true) . ' />';
497  }
498  }
499  }
500  return implode('', $imgSplit);
501  }
502 
511  public function TS_images_rte($value)
512  {
513  // Split content by <img> tags and traverse the resulting array for processing:
514  $imgSplit = $this->splitTags('img', $value);
515  if (count($imgSplit) > 1) {
516  $siteUrl = GeneralUtility::getIndpEnv('TYPO3_SITE_URL');
517  $sitePath = str_replace(GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST'), '', $siteUrl);
518  foreach ($imgSplit as $k => $v) {
519  // Image found
520  if ($k % 2) {
521  // Get the attributes of the img tag
522  list($attribArray) = $this->get_tag_attributes($v, true);
523  $absoluteUrl = trim($attribArray['src']);
524  // Transform the src attribute into an absolute url, if it not already
525  if (strtolower(substr($absoluteUrl, 0, 4)) !== 'http') {
526  // If site is in a subpath (eg. /~user_jim/) this path needs to be removed because it will be added with $siteUrl
527  $attribArray['src'] = preg_replace('#^' . preg_quote($sitePath, '#') . '#', '', $attribArray['src']);
528  $attribArray['src'] = $siteUrl . $attribArray['src'];
529  }
530  // Must have alt attribute
531  if (!isset($attribArray['alt'])) {
532  $attribArray['alt'] = '';
533  }
534  $imgSplit[$k] = '<img ' . GeneralUtility::implodeAttributes($attribArray, true, true) . ' />';
535  }
536  }
537  }
538  // Return processed content:
539  return implode('', $imgSplit);
540  }
541 
553  public function TS_links_db($value)
554  {
555  $blockSplit = $this->splitIntoBlock('A', $value);
556  foreach ($blockSplit as $k => $v) {
557  if ($k % 2) {
558  list($tagAttributes) = $this->get_tag_attributes($this->getFirstTag($v), true);
559  $linkService = GeneralUtility::makeInstance(LinkService::class);
560  $linkInformation = $linkService->resolve($tagAttributes['href'] ?? '');
561 
562  // Modify parameters, this hook should be deprecated
563  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['modifyParams_LinksDb_PostProc'])
564  && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['modifyParams_LinksDb_PostProc'])) {
565  $parameters = [
566  'currentBlock' => $v,
567  'linkInformation' => $linkInformation,
568  'url' => $linkInformation['href'],
569  'attributes' => $tagAttributes
570  ];
571  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['modifyParams_LinksDb_PostProc'] as $objRef) {
572  $processor = GeneralUtility::getUserObj($objRef);
573  $blockSplit[$k] = $processor->modifyParamsLinksDb($parameters, $this);
574  }
575  } else {
576  // Otherwise store the link as <a> tag as default by TYPO3, with the new link service syntax
577  try {
578  $tagAttributes['href'] = $linkService->asString($linkInformation);
579  } catch (UnknownLinkHandlerException $e) {
580  $tagAttributes['href'] = $linkInformation['href'] ?? $tagAttributes['href'];
581  }
582 
583  $blockSplit[$k] = '<a ' . GeneralUtility::implodeAttributes($tagAttributes, true) . '>'
584  . $this->TS_links_db($this->removeFirstAndLastTag($blockSplit[$k])) . '</a>';
585  }
586  }
587  }
588  return implode('', $blockSplit);
589  }
590 
601  public function TS_links_rte($value)
602  {
603  $value = $this->TS_AtagToAbs($value);
604  // Split content by the TYPO3 pseudo tag "<link>"
605  $blockSplit = $this->splitIntoBlock('link', $value, true);
606  foreach ($blockSplit as $k => $v) {
607  // Block
608  if ($k % 2) {
609  // Split away the first "<link " part
610  $typoLinkData = explode(' ', substr($this->getFirstTag($v), 0, -1), 2)[1];
611  $tagCode = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($typoLinkData);
612 
613  // Parsing the TypoLink data. This parsing is done like in \TYPO3\CMS\Frontend\ContentObject->typoLink()
614  $linkService = GeneralUtility::makeInstance(LinkService::class);
615  $linkInformation = $linkService->resolve($tagCode['url']);
616 
617  try {
618  $href = $linkService->asString($linkInformation);
619  } catch (UnknownLinkHandlerException $e) {
620  $href = '';
621  }
622 
623  // Modify parameters by a hook
624  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['modifyParams_LinksRte_PostProc']) && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['modifyParams_LinksRte_PostProc'])) {
625  // backwards-compatibility: show an error message if the page is not found
626  $error = '';
627  if ($linkInformation['type'] === LinkService::TYPE_PAGE) {
628  $pageRecord = BackendUtility::getRecord('pages', $linkInformation['pageuid']);
629  // Page does not exist
630  if (!is_array($pageRecord)) {
631  $error = 'Page with ID ' . $linkInformation['pageuid'] . ' not found';
632  }
633  }
634  $parameters = [
635  'currentBlock' => $v,
636  'url' => $href,
637  'tagCode' => $tagCode,
638  'external' => $linkInformation['type'] === LinkService::TYPE_URL,
639  'error' => $error
640  ];
641  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['modifyParams_LinksRte_PostProc'] as $objRef) {
642  $processor = GeneralUtility::getUserObj($objRef);
643  $blockSplit[$k] = $processor->modifyParamsLinksRte($parameters, $this);
644  }
645  } else {
646  $anchorAttributes = [
647  'href' => $href,
648  'target' => $tagCode['target'],
649  'class' => $tagCode['class'],
650  'title' => $tagCode['title']
651  ];
652 
653  // Setting the <a> tag
654  $blockSplit[$k] = '<a ' . GeneralUtility::implodeAttributes($anchorAttributes, true) . '>'
655  . $this->TS_links_rte($this->removeFirstAndLastTag($blockSplit[$k]))
656  . '</a>';
657  }
658  }
659  }
660  return implode('', $blockSplit);
661  }
662 
671  public function TS_transform_db($value)
672  {
673  // Safety... so forever loops are avoided (they should not occur, but an error would potentially do this...)
674  $this->TS_transform_db_safecounter--;
675  if ($this->TS_transform_db_safecounter < 0) {
676  return $value;
677  }
678  // Split the content from RTE by the occurrence of these blocks:
679  $blockSplit = $this->splitIntoBlock($this->blockElementList, $value);
680 
681  // Avoid superfluous linebreaks by transform_db after ending headListTag
682  while (count($blockSplit) > 0 && trim(end($blockSplit)) === '') {
683  array_pop($blockSplit);
684  }
685 
686  // Traverse the blocks
687  foreach ($blockSplit as $k => $v) {
688  if ($k % 2) {
689  // Inside block:
690  // Init:
691  $tag = $this->getFirstTag($v);
692  $tagName = strtolower($this->getFirstTagName($v));
693  // Process based on the tag:
694  switch ($tagName) {
695  case 'blockquote':
696  case 'dd':
697  case 'div':
698  case 'header':
699  case 'section':
700  case 'footer':
701  case 'nav':
702  case 'article':
703  case 'aside':
704  $blockSplit[$k] = $tag . $this->TS_transform_db($this->removeFirstAndLastTag($blockSplit[$k])) . '</' . $tagName . '>';
705  break;
706  case 'pre':
707  break;
708  default:
709  // usually <hx> tags and <table> tags where no other block elements are within the tags
710  // Eliminate true linebreaks inside block element tags
711  $blockSplit[$k] = preg_replace(('/[' . LF . ']+/'), ' ', $blockSplit[$k]);
712  }
713  } else {
714  // NON-block:
715  if (trim($blockSplit[$k]) !== '') {
716  $blockSplit[$k] = str_replace('<hr/>', '<hr />', $blockSplit[$k]);
717  // Remove linebreaks preceding hr tags
718  $blockSplit[$k] = preg_replace('/[' . LF . ']+<(hr)(\\s[^>\\/]*)?[[:space:]]*\\/?>/', '<$1$2/>', $blockSplit[$k]);
719  // Remove linebreaks following hr tags
720  $blockSplit[$k] = preg_replace('/<(hr)(\\s[^>\\/]*)?[[:space:]]*\\/?>[' . LF . ']+/', '<$1$2/>', $blockSplit[$k]);
721  // Replace other linebreaks with space
722  $blockSplit[$k] = preg_replace('/[' . LF . ']+/', ' ', $blockSplit[$k]);
723  $blockSplit[$k] = $this->divideIntoLines($blockSplit[$k]);
724  } else {
725  unset($blockSplit[$k]);
726  }
727  }
728  }
729  $this->TS_transform_db_safecounter++;
730  return implode(LF, $blockSplit);
731  }
732 
743  public function transformStyledATags($value)
744  {
745  $blockSplit = $this->splitIntoBlock('A', $value);
746  foreach ($blockSplit as $k => $v) {
747  // If an A-tag was found
748  if ($k % 2) {
749  list($attribArray) = $this->get_tag_attributes($this->getFirstTag($v), true);
750  // If "style" attribute is set and rteerror is not set!
751  if ($attribArray['style'] && !$attribArray['rteerror']) {
752  $attribArray_copy['style'] = $attribArray['style'];
753  unset($attribArray['style']);
754  $bTag = '<span ' . GeneralUtility::implodeAttributes($attribArray_copy, true) . '><a ' . GeneralUtility::implodeAttributes($attribArray, true) . '>';
755  $eTag = '</a></span>';
756  $blockSplit[$k] = $bTag . $this->removeFirstAndLastTag($blockSplit[$k]) . $eTag;
757  }
758  }
759  }
760  return implode('', $blockSplit);
761  }
762 
771  public function TS_transform_rte($value)
772  {
773  // Split the content from database by the occurrence of the block elements
774  $blockSplit = $this->splitIntoBlock($this->blockElementList, $value);
775  // Traverse the blocks
776  foreach ($blockSplit as $k => $v) {
777  if ($k % 2) {
778  // Inside one of the blocks:
779  // Init:
780  $tag = $this->getFirstTag($v);
781  $tagName = strtolower($this->getFirstTagName($v));
782  // Based on tagname, we do transformations:
783  switch ($tagName) {
784  case 'blockquote':
785  case 'dd':
786  case 'div':
787  case 'header':
788  case 'section':
789  case 'footer':
790  case 'nav':
791  case 'article':
792  case 'aside':
793  $blockSplit[$k] = $tag . $this->TS_transform_rte($this->removeFirstAndLastTag($blockSplit[$k])) . '</' . $tagName . '>';
794  break;
795  }
796  $blockSplit[$k + 1] = preg_replace('/^[ ]*' . LF . '/', '', $blockSplit[$k + 1]);
797  } else {
798  // NON-block:
799  $nextFTN = $this->getFirstTagName($blockSplit[$k + 1]);
800  $onlyLineBreaks = (preg_match('/^[ ]*' . LF . '+[ ]*$/', $blockSplit[$k]) == 1);
801  // If the line is followed by a block or is the last line:
802  if (GeneralUtility::inList($this->blockElementList, $nextFTN) || !isset($blockSplit[$k + 1])) {
803  // If the line contains more than just linebreaks, reduce the number of trailing linebreaks by 1
804  if (!$onlyLineBreaks) {
805  $blockSplit[$k] = preg_replace('/(' . LF . '*)' . LF . '[ ]*$/', '$1', $blockSplit[$k]);
806  } else {
807  // If the line contains only linebreaks, remove the leading linebreak
808  $blockSplit[$k] = preg_replace('/^[ ]*' . LF . '/', '', $blockSplit[$k]);
809  }
810  }
811  // If $blockSplit[$k] is blank then unset the line, unless the line only contained linebreaks
812  if ((string)$blockSplit[$k] === '' && !$onlyLineBreaks) {
813  unset($blockSplit[$k]);
814  } else {
815  $blockSplit[$k] = $this->setDivTags($blockSplit[$k]);
816  }
817  }
818  }
819  return implode(LF, $blockSplit);
820  }
821 
822  /***************************************************************
823  *
824  * Generic RTE transformation, analysis and helper functions
825  *
826  **************************************************************/
827 
837  public function HTMLcleaner_db($content)
838  {
839  $keepTags = $this->getKeepTags('db');
840  // Default: remove unknown tags.
841  $keepUnknownTags = (bool)$this->procOptions['dontRemoveUnknownTags_db'];
842  return $this->HTMLcleaner($content, $keepTags, $keepUnknownTags);
843  }
844 
853  public function getKeepTags($direction = 'rte')
854  {
855  if (!is_array($this->getKeepTags_cache[$direction])) {
856  // Setting up allowed tags:
857  // Default is to get allowed/denied tags from internal array of processing options:
858  // Construct default list of tags to keep:
859  if (is_array($this->procOptions['allowTags.'])) {
860  $keepTags = implode(',', $this->procOptions['allowTags.']);
861  } else {
862  $keepTags = $this->procOptions['allowTags'];
863  }
864  $keepTags = array_flip(GeneralUtility::trimExplode(',', $this->defaultAllowedTagsList . ',' . strtolower($keepTags), true));
865  // For tags to deny, remove them from $keepTags array:
866  $denyTags = GeneralUtility::trimExplode(',', $this->procOptions['denyTags'], true);
867  foreach ($denyTags as $dKe) {
868  unset($keepTags[$dKe]);
869  }
870  // Based on the direction of content, set further options:
871  switch ($direction) {
872  case 'rte':
873  // Transforming keepTags array so it can be understood by the HTMLcleaner function.
874  // This basically converts the format of the array from TypoScript (having dots) to plain multi-dimensional array.
875  list($keepTags) = $this->HTMLparserConfig($this->procOptions['HTMLparser_rte.'], $keepTags);
876  break;
877  case 'db':
878  // Setting up span tags if they are allowed:
879  if (isset($keepTags['span'])) {
880  $keepTags['span'] = [
881  'allowedAttribs' => 'id,class,style,title,lang,xml:lang,dir,itemscope,itemtype,itemprop',
882  'fixAttrib' => [
883  'class' => [
884  'removeIfFalse' => 1
885  ]
886  ],
887  'rmTagIfNoAttrib' => 1
888  ];
889  if (!empty($this->allowedClasses)) {
890  $keepTags['span']['fixAttrib']['class']['list'] = $this->allowedClasses;
891  }
892  }
893  // Setting further options, getting them from the processing options
894  $TSc = $this->procOptions['HTMLparser_db.'];
895  if (!$TSc['globalNesting']) {
896  $TSc['globalNesting'] = 'b,i,u,a,center,font,sub,sup,strong,em,strike,span';
897  }
898  if (!$TSc['noAttrib']) {
899  $TSc['noAttrib'] = 'b,i,u,br,center,hr,sub,sup,strong,em,li,ul,ol,blockquote,strike';
900  }
901  // Transforming the array from TypoScript to regular array:
902  list($keepTags) = $this->HTMLparserConfig($TSc, $keepTags);
903  break;
904  }
905  // Caching (internally, in object memory) the result
906  $this->getKeepTags_cache[$direction] = $keepTags;
907  }
908  // Return result:
909  return $this->getKeepTags_cache[$direction];
910  }
911 
924  public function divideIntoLines($value, $count = 5, $returnArray = false)
925  {
926  // Setting the third param will eliminate false end-tags. Maybe this is a good thing to do...?
927  $paragraphBlocks = $this->splitIntoBlock('p', $value, true);
928  // Returns plainly the content if there was no p sections in it
929  if (count($paragraphBlocks) <= 1 || $count <= 0) {
930  return $this->sanitizeLineBreaksForContentOnly($value);
931  }
932 
933  // Traverse the splitted sections
934  foreach ($paragraphBlocks as $k => $v) {
935  if ($k % 2) {
936  // Inside a <p> section
937  $v = $this->removeFirstAndLastTag($v);
938  // Fetching 'sub-lines' - which will explode any further p nesting recursively
939  $subLines = $this->divideIntoLines($v, $count - 1, true);
940  // So, if there happened to be sub-nesting of p, this is written directly as the new content of THIS section. (This would be considered 'an error')
941  if (is_array($subLines)) {
942  $paragraphBlocks[$k] = implode(LF, $subLines);
943  } else {
944  //... but if NO subsection was found, we process it as a TRUE line without erroneous content:
945  $paragraphBlocks[$k] = $this->processContentWithinParagraph($subLines, $paragraphBlocks[$k]);
946  }
947  // If it turns out the line is just blank (containing a &nbsp; possibly) then just make it pure blank.
948  // But, prevent filtering of lines that are blank in sense above, but whose tags contain attributes.
949  // Those attributes should have been filtered before; if they are still there they must be considered as possible content.
950  if (trim(strip_tags($paragraphBlocks[$k])) === '&nbsp;' && !preg_match('/\\<(img)(\\s[^>]*)?\\/?>/si', $paragraphBlocks[$k]) && !preg_match('/\\<([^>]*)?( align| class| style| id| title| dir| lang| xml:lang)([^>]*)?>/si', trim($paragraphBlocks[$k]))) {
951  $paragraphBlocks[$k] = '';
952  }
953  } else {
954  // Outside a paragraph, if there is still something in there, just add a <p> tag
955  // Remove positions which are outside <p> tags and without content
956  $paragraphBlocks[$k] = trim(strip_tags($paragraphBlocks[$k], '<' . implode('><', $this->allowedTagsOutsideOfParagraphs) . '>'));
957  $paragraphBlocks[$k] = $this->sanitizeLineBreaksForContentOnly($paragraphBlocks[$k]);
958  if ((string)$paragraphBlocks[$k] === '') {
959  unset($paragraphBlocks[$k]);
960  } else {
961  // add <p> tags around the content
962  $paragraphBlocks[$k] = str_replace(strip_tags($paragraphBlocks[$k]), '<p>' . strip_tags($paragraphBlocks[$k]) . '</p>', $paragraphBlocks[$k]);
963  }
964  }
965  }
966  return $returnArray ? $paragraphBlocks : implode(LF, $paragraphBlocks);
967  }
968 
977  public function setDivTags($value)
978  {
979  // First, setting configuration for the HTMLcleaner function. This will process each line between the <div>/<p> section on their way to the RTE
980  $keepTags = $this->getKeepTags('rte');
981  // Divide the content into lines
982  $parts = explode(LF, $value);
983  foreach ($parts as $k => $v) {
984  // Processing of line content:
985  // If the line is blank, set it to &nbsp;
986  if (trim($parts[$k]) === '') {
987  $parts[$k] = '&nbsp;';
988  } else {
989  // Clean the line content, keeping unknown tags (as they can be removed in the entryHTMLparser)
990  $parts[$k] = $this->HTMLcleaner($parts[$k], $keepTags, 'protect');
991  // convert double-encoded &nbsp; into regular &nbsp; however this could also be reversed via the exitHTMLparser
992  // This was previously an option to disable called "dontConvAmpInNBSP_rte"
993  $parts[$k] = str_replace('&amp;nbsp;', '&nbsp;', $parts[$k]);
994  }
995  // Wrapping the line in <p> tags if not already wrapped and does not contain an hr tag
996  if (!preg_match('/<(hr)(\\s[^>\\/]*)?[[:space:]]*\\/?>/i', $parts[$k])) {
997  $testStr = strtolower(trim($parts[$k]));
998  if (substr($testStr, 0, 4) !== '<div' || substr($testStr, -6) !== '</div>') {
999  if (substr($testStr, 0, 2) !== '<p' || substr($testStr, -4) !== '</p>') {
1000  // Only set p-tags if there is not already div or p tags:
1001  $parts[$k] = '<p>' . $parts[$k] . '</p>';
1002  }
1003  }
1004  }
1005  }
1006  // Implode result:
1007  return implode(LF, $parts);
1008  }
1009 
1022  protected function processContentWithinParagraph(string $content, string $fullContentWithTag)
1023  {
1024  // clean up the content
1025  $content = $this->HTMLcleaner_db($content);
1026  // Get the <p> tag, and validate the attributes
1027  $fTag = $this->getFirstTag($fullContentWithTag);
1028  // Check which attributes of the <p> tag to keep attributes
1029  if (!empty($this->allowedAttributesForParagraphTags)) {
1030  list($tagAttributes) = $this->get_tag_attributes($fTag);
1031  // Make sure the tag attributes only contain the ones that are defined to be allowed
1032  $tagAttributes = array_intersect_key($tagAttributes, array_flip($this->allowedAttributesForParagraphTags));
1033 
1034  // Only allow classes that are whitelisted in $this->allowedClasses
1035  if (trim($tagAttributes['class']) !== '' && !empty($this->allowedClasses) && !in_array($tagAttributes['class'], $this->allowedClasses, true)) {
1036  $classes = GeneralUtility::trimExplode(' ', $tagAttributes['class'], true);
1037  $classes = array_intersect($classes, $this->allowedClasses);
1038  if (!empty($classes)) {
1039  $tagAttributes['class'] = implode(' ', $classes);
1040  } else {
1041  unset($tagAttributes['class']);
1042  }
1043  }
1044  } else {
1045  $tagAttributes = [];
1046  }
1047  // Remove any line break
1048  $content = str_replace(LF, '', $content);
1049  // Compile the surrounding <p> tag
1050  $content = '<' . rtrim('p ' . $this->compileTagAttribs($tagAttributes)) . '>' . $content . '</p>';
1051  return $content;
1052  }
1053 
1060  protected function sanitizeLineBreaksForContentOnly(string $content)
1061  {
1062  $content = preg_replace('/<(hr)(\\s[^>\\/]*)?[[:space:]]*\\/?>/i', LF . '<$1$2/>' . LF, $content);
1063  $content = str_replace(LF . LF, LF, $content);
1064  $content = preg_replace('/(^' . LF . ')|(' . LF . '$)/i', '', $content);
1065  return $content;
1066  }
1067 
1075  public function getWHFromAttribs($attribArray)
1076  {
1077  $style = trim($attribArray['style']);
1078  $w = 0;
1079  $h = 0;
1080  if ($style) {
1081  $regex = '[[:space:]]*:[[:space:]]*([0-9]*)[[:space:]]*px';
1082  // Width
1083  $reg = [];
1084  preg_match('/width' . $regex . '/i', $style, $reg);
1085  $w = (int)$reg[1];
1086  // Height
1087  preg_match('/height' . $regex . '/i', $style, $reg);
1088  $h = (int)$reg[1];
1089  }
1090  if (!$w) {
1091  $w = $attribArray['width'];
1092  }
1093  if (!$h) {
1094  $h = $attribArray['height'];
1095  }
1096  return [(int)$w, (int)$h];
1097  }
1098 
1106  public function urlInfoForLinkTags($url)
1107  {
1108  $info = [];
1109  $url = trim($url);
1110  if (substr(strtolower($url), 0, 7) === 'mailto:') {
1111  $info['url'] = trim(substr($url, 7));
1112  $info['type'] = 'email';
1113  } elseif (strpos($url, '?file:') !== false) {
1114  $info['type'] = 'file';
1115  $info['url'] = rawurldecode(substr($url, strpos($url, '?file:') + 1));
1116  } else {
1117  $curURL = GeneralUtility::getIndpEnv('TYPO3_SITE_URL');
1118  $urlLength = strlen($url);
1119  $a = 0;
1120  for (; $a < $urlLength; $a++) {
1121  if ($url[$a] != $curURL[$a]) {
1122  break;
1123  }
1124  }
1125  $info['relScriptPath'] = substr($curURL, $a);
1126  $info['relUrl'] = substr($url, $a);
1127  $info['url'] = $url;
1128  $info['type'] = 'ext';
1129  $siteUrl_parts = parse_url($url);
1130  $curUrl_parts = parse_url($curURL);
1131  // Hosts should match
1132  if ($siteUrl_parts['host'] == $curUrl_parts['host'] && (!$info['relScriptPath'] || defined('TYPO3_mainDir') && substr($info['relScriptPath'], 0, strlen(TYPO3_mainDir)) == TYPO3_mainDir)) {
1133  // If the script path seems to match or is empty (FE-EDIT)
1134  // New processing order 100502
1135  $uP = parse_url($info['relUrl']);
1136  if ($info['relUrl'] === '#' . $siteUrl_parts['fragment']) {
1137  $info['url'] = $info['relUrl'];
1138  $info['type'] = 'anchor';
1139  } elseif (!trim($uP['path']) || $uP['path'] === 'index.php') {
1140  // URL is a page (id parameter)
1141  $pp = preg_split('/^id=/', $uP['query']);
1142  $pp[1] = preg_replace('/&id=[^&]*/', '', $pp[1]);
1143  $parameters = explode('&', $pp[1]);
1144  $id = array_shift($parameters);
1145  if ($id) {
1146  $info['pageid'] = $id;
1147  $info['cElement'] = $uP['fragment'];
1148  $info['url'] = $id . ($info['cElement'] ? '#' . $info['cElement'] : '');
1149  $info['type'] = 'page';
1150  $info['query'] = $parameters[0] ? '&' . implode('&', $parameters) : '';
1151  }
1152  } else {
1153  $info['url'] = $info['relUrl'];
1154  $info['type'] = 'file';
1155  }
1156  } else {
1157  unset($info['relScriptPath']);
1158  unset($info['relUrl']);
1159  }
1160  }
1161  return $info;
1162  }
1163 
1171  public function TS_AtagToAbs($value, $dontSetRTEKEEP = false)
1172  {
1173  $blockSplit = $this->splitIntoBlock('A', $value);
1174  foreach ($blockSplit as $k => $v) {
1175  // Block
1176  if ($k % 2) {
1177  list($attribArray) = $this->get_tag_attributes($this->getFirstTag($v), true);
1178  // Checking if there is a scheme, and if not, prepend the current url.
1179  // ONLY do this if href has content - the <a> tag COULD be an anchor and if so, it should be preserved...
1180  if ($attribArray['href'] !== '') {
1181  $uP = parse_url(strtolower($attribArray['href']));
1182  if (!$uP['scheme']) {
1183  $attribArray['href'] = GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . $attribArray['href'];
1184  }
1185  }
1186  $bTag = '<a ' . GeneralUtility::implodeAttributes($attribArray, true) . '>';
1187  $eTag = '</a>';
1188  $blockSplit[$k] = $bTag . $this->TS_AtagToAbs($this->removeFirstAndLastTag($blockSplit[$k])) . $eTag;
1189  }
1190  }
1191  return implode('', $blockSplit);
1192  }
1193 
1202  protected function applyPlainImageModeSettings($imageInfo, $attribArray)
1203  {
1204  if ($this->procOptions['plainImageMode']) {
1205  // Perform corrections to aspect ratio based on configuration
1206  switch ((string)$this->procOptions['plainImageMode']) {
1207  case 'lockDimensions':
1208  $attribArray['width'] = $imageInfo[0];
1209  $attribArray['height'] = $imageInfo[1];
1210  break;
1211  case 'lockRatioWhenSmaller':
1212  if ($attribArray['width'] > $imageInfo[0]) {
1213  $attribArray['width'] = $imageInfo[0];
1214  }
1215  if ($imageInfo[0] > 0) {
1216  $attribArray['height'] = round($attribArray['width'] * ($imageInfo[1] / $imageInfo[0]));
1217  }
1218  break;
1219  case 'lockRatio':
1220  if ($imageInfo[0] > 0) {
1221  $attribArray['height'] = round($attribArray['width'] * ($imageInfo[1] / $imageInfo[0]));
1222  }
1223  break;
1224  }
1225  }
1226  return $attribArray;
1227  }
1228 
1239  protected function streamlineLineBreaksForProcessing(string $content)
1240  {
1241  return str_replace(CR, '', $content);
1242  }
1243 
1254  protected function streamlineLineBreaksAfterProcessing(string $content)
1255  {
1256  // Make sure no \r\n sequences has entered in the meantime
1257  $content = $this->streamlineLineBreaksForProcessing($content);
1258  // ... and then change all \n into \r\n
1259  return str_replace(LF, CRLF, $content);
1260  }
1261 
1270  protected function markBrokenLinks(string $content): string
1271  {
1272  $blocks = $this->splitIntoBlock('A', $content);
1273  $linkService = GeneralUtility::makeInstance(LinkService::class);
1274  foreach ($blocks as $position => $value) {
1275  if ($position % 2 === 0) {
1276  continue;
1277  }
1278  list($attributes) = $this->get_tag_attributes($this->getFirstTag($value), true);
1279  if (empty($attributes['href'])) {
1280  continue;
1281  }
1282  $hrefInformation = $linkService->resolve($attributes['href']);
1283  if ($hrefInformation['type'] === LinkService::TYPE_PAGE && $hrefInformation['pageuid'] !== 'current') {
1284  $pageRecord = BackendUtility::getRecord('pages', $hrefInformation['pageuid']);
1285  if (!is_array($pageRecord)) {
1286  // Page does not exist
1287  $attributes['data-rte-error'] = 'Page with ID ' . $hrefInformation['pageuid'] . ' not found';
1288  $styling = 'background-color: yellow; border:2px red solid; color: black;';
1289  if (empty($attributes['style'])) {
1290  $attributes['style'] = $styling;
1291  } else {
1292  $attributes['style'] .= ' ' . $styling;
1293  }
1294  }
1295  }
1296  // Always rewrite the block to allow the nested calling even if a page is found
1297  $blocks[$position] =
1298  '<a ' . GeneralUtility::implodeAttributes($attributes, true, true) . '>'
1299  . $this->markBrokenLinks($this->removeFirstAndLastTag($blocks[$position]))
1300  . '</a>';
1301  }
1302  return implode('', $blocks);
1303  }
1304 
1312  protected function removeBrokenLinkMarkers(string $content): string
1313  {
1314  $blocks = $this->splitIntoBlock('A', $content);
1315  foreach ($blocks as $position => $value) {
1316  if ($position % 2 === 0) {
1317  continue;
1318  }
1319  list($attributes) = $this->get_tag_attributes($this->getFirstTag($value), true);
1320  if (empty($attributes['href'])) {
1321  continue;
1322  }
1323  // Always remove the styling again (regardless of the page was found or not)
1324  // so the database does not contain ugly stuff
1325  unset($attributes['data-rte-error']);
1326  if (isset($attributes['style'])) {
1327  $attributes['style'] = trim(str_replace('background-color: yellow; border:2px red solid; color: black;', '', $attributes['style']));
1328  if (empty($attributes['style'])) {
1329  unset($attributes['style']);
1330  }
1331  }
1332  $blocks[$position] =
1333  '<a ' . GeneralUtility::implodeAttributes($attributes, true, true) . '>'
1334  . $this->removeBrokenLinkMarkers($this->removeFirstAndLastTag($blocks[$position]))
1335  . '</a>';
1336  }
1337  return implode('', $blocks);
1338  }
1339 
1345  protected function getLogger()
1346  {
1348  $logManager = GeneralUtility::makeInstance(LogManager::class);
1349  return $logManager->getLogger(get_class($this));
1350  }
1351 }
static implodeAttributes(array $arr, $xhtmlSafe=false, $dontOmitBlankAttribs=false)
static isFirstPartOfStr($str, $partStr)
compileTagAttribs($tagAttrib, $meta=[])
Definition: HtmlParser.php:839
streamlineLineBreaksAfterProcessing(string $content)
resolveAppliedTransformationModes(string $direction, array $modes)
applyPlainImageModeSettings($imageInfo, $attribArray)
TS_AtagToAbs($value, $dontSetRTEKEEP=false)
static getFileAbsFileName($filename, $_=null, $_2=null)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
get_tag_attributes($tag, $deHSC=false)
Definition: HtmlParser.php:248
RTE_transform($value, $specConf=[], $direction='rte', $thisConfig=[])
HTMLparserConfig($TSconfig, $keepTags=[])
Definition: HtmlParser.php:861
streamlineLineBreaksForProcessing(string $content)
sanitizeLineBreaksForContentOnly(string $content)
divideIntoLines($value, $count=5, $returnArray=false)
runHtmlParserIfConfigured($content, $configurationDirective)
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
splitIntoBlock($tag, $content, $eliminateExtraEndTags=false)
Definition: HtmlParser.php:51
processContentWithinParagraph(string $content, string $fullContentWithTag)
getFirstTagName($str, $preserveCase=false)
Definition: HtmlParser.php:224