TYPO3 CMS  TYPO3_7-6
HtmlParser.php
Go to the documentation of this file.
1 <?php
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 
25 {
29  protected $caseShift_cache = [];
30 
31  // Void elements that do not have closing tags, as defined by HTML5, except link element
32  const VOID_ELEMENTS = 'area|base|br|col|command|embed|hr|img|input|keygen|meta|param|source|track|wbr';
33 
44  public static function getSubpart($content, $marker)
45  {
47  $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
48  return $templateService->getSubpart($content, $marker);
49  }
50 
63  public static function substituteSubpart($content, $marker, $subpartContent, $recursive = true, $keepMarker = false)
64  {
66  $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
67  return $templateService->substituteSubpart($content, $marker, $subpartContent, $recursive, $keepMarker);
68  }
69 
79  public static function substituteSubpartArray($content, array $subpartsContent)
80  {
82  $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
83  return $templateService->substituteSubpartArray($content, $subpartsContent);
84  }
85 
97  public static function substituteMarker($content, $marker, $markContent)
98  {
100  $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
101  return $templateService->substituteMarker($content, $marker, $markContent);
102  }
103 
123  public static function substituteMarkerArray($content, $markContentArray, $wrap = '', $uppercase = false, $deleteUnused = false)
124  {
126  $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
127  return $templateService->substituteMarkerArray($content, $markContentArray, $wrap, $uppercase, $deleteUnused);
128  }
129 
168  public static function substituteMarkerAndSubpartArrayRecursive($content, array $markersAndSubparts, $wrap = '', $uppercase = false, $deleteUnused = false)
169  {
171  $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
172  return $templateService->substituteMarkerAndSubpartArrayRecursive($content, $markersAndSubparts, $wrap, $uppercase, $deleteUnused);
173  }
174 
175  /************************************
176  *
177  * Parsing HTML code
178  *
179  ************************************/
191  public function splitIntoBlock($tag, $content, $eliminateExtraEndTags = false)
192  {
193  $tags = array_unique(GeneralUtility::trimExplode(',', $tag, true));
194  foreach ($tags as &$tag) {
195  $tag = preg_quote($tag, '/');
196  }
197  $regexStr = '/\\<\\/?(' . implode('|', $tags) . ')(\\s*\\>|\\s[^\\>]*\\>)/si';
198  $parts = preg_split($regexStr, $content);
199  $newParts = [];
200  $pointer = strlen($parts[0]);
201  $buffer = $parts[0];
202  $nested = 0;
203  reset($parts);
204  // We skip the first element in foreach loop
205  $partsSliced = array_slice($parts, 1, null, true);
206  foreach ($partsSliced as $v) {
207  $isEndTag = substr($content, $pointer, 2) == '</' ? 1 : 0;
208  $tagLen = strcspn(substr($content, $pointer), '>') + 1;
209  // We meet a start-tag:
210  if (!$isEndTag) {
211  // Ground level:
212  if (!$nested) {
213  // Previous buffer stored
214  $newParts[] = $buffer;
215  $buffer = '';
216  }
217  // We are inside now!
218  $nested++;
219  // New buffer set and pointer increased
220  $mbuffer = substr($content, $pointer, strlen($v) + $tagLen);
221  $pointer += strlen($mbuffer);
222  $buffer .= $mbuffer;
223  } else {
224  // If we meet an endtag:
225  // Decrease nested-level
226  $nested--;
227  $eliminated = 0;
228  if ($eliminateExtraEndTags && $nested < 0) {
229  $nested = 0;
230  $eliminated = 1;
231  } else {
232  // In any case, add the endtag to current buffer and increase pointer
233  $buffer .= substr($content, $pointer, $tagLen);
234  }
235  $pointer += $tagLen;
236  // if we're back on ground level, (and not by eliminating tags...
237  if (!$nested && !$eliminated) {
238  $newParts[] = $buffer;
239  $buffer = '';
240  }
241  // New buffer set and pointer increased
242  $mbuffer = substr($content, $pointer, strlen($v));
243  $pointer += strlen($mbuffer);
244  $buffer .= $mbuffer;
245  }
246  }
247  $newParts[] = $buffer;
248  return $newParts;
249  }
250 
263  public function splitIntoBlockRecursiveProc($tag, $content, &$procObj, $callBackContent, $callBackTags, $level = 0)
264  {
265  $parts = $this->splitIntoBlock($tag, $content, true);
266  foreach ($parts as $k => $v) {
267  if ($k % 2) {
268  $firstTagName = $this->getFirstTagName($v, true);
269  $tagsArray = [];
270  $tagsArray['tag_start'] = $this->getFirstTag($v);
271  $tagsArray['tag_end'] = '</' . $firstTagName . '>';
272  $tagsArray['tag_name'] = strtolower($firstTagName);
273  $tagsArray['add_level'] = 1;
274  $tagsArray['content'] = $this->splitIntoBlockRecursiveProc($tag, $this->removeFirstAndLastTag($v), $procObj, $callBackContent, $callBackTags, $level + $tagsArray['add_level']);
275  if ($callBackTags) {
276  $tagsArray = $procObj->{$callBackTags}($tagsArray, $level);
277  }
278  $parts[$k] = $tagsArray['tag_start'] . $tagsArray['content'] . $tagsArray['tag_end'];
279  } else {
280  if ($callBackContent) {
281  $parts[$k] = $procObj->{$callBackContent}($parts[$k], $level);
282  }
283  }
284  }
285  return implode('', $parts);
286  }
287 
298  public function splitTags($tag, $content)
299  {
300  $tags = GeneralUtility::trimExplode(',', $tag, true);
301  foreach ($tags as &$tag) {
302  $tag = preg_quote($tag, '/');
303  }
304  $regexStr = '/\\<(' . implode('|', $tags) . ')(\\s[^>]*)?\\/?>/si';
305  $parts = preg_split($regexStr, $content);
306  $pointer = strlen($parts[0]);
307  $newParts = [];
308  $newParts[] = $parts[0];
309  reset($parts);
310  // We skip the first element in foreach loop
311  $partsSliced = array_slice($parts, 1, null, true);
312  foreach ($partsSliced as $v) {
313  $tagLen = strcspn(substr($content, $pointer), '>') + 1;
314  // Set tag:
315  // New buffer set and pointer increased
316  $tag = substr($content, $pointer, $tagLen);
317  $newParts[] = $tag;
318  $pointer += strlen($tag);
319  // Set content:
320  $newParts[] = $v;
321  $pointer += strlen($v);
322  }
323  return $newParts;
324  }
325 
335  public function getAllParts($parts, $tag_parts = true, $include_tag = true)
336  {
337  $newParts = [];
338  foreach ($parts as $k => $v) {
339  if (($k + ($tag_parts ? 0 : 1)) % 2) {
340  if (!$include_tag) {
341  $v = $this->removeFirstAndLastTag($v);
342  }
343  $newParts[] = $v;
344  }
345  }
346  return $newParts;
347  }
348 
356  public function removeFirstAndLastTag($str)
357  {
358  // End of first tag:
359  $start = strpos($str, '>');
360  // Begin of last tag:
361  $end = strrpos($str, '<');
362  // Return
363  return substr($str, $start + 1, $end - $start - 1);
364  }
365 
373  public function getFirstTag($str)
374  {
375  // First:
376  $endLen = strpos($str, '>');
377  return $endLen !== false ? substr($str, 0, $endLen + 1) : '';
378  }
379 
388  public function getFirstTagName($str, $preserveCase = false)
389  {
390  $matches = [];
391  if (preg_match('/^\\s*\\<([^\\s\\>]+)(\\s|\\>)/', $str, $matches) === 1) {
392  if (!$preserveCase) {
393  return strtoupper($matches[1]);
394  }
395  return $matches[1];
396  }
397  return '';
398  }
399 
408  public function get_tag_attributes($tag, $deHSC = 0)
409  {
410  list($components, $metaC) = $this->split_tag_attributes($tag);
411  // Attribute name is stored here
412  $name = '';
413  $valuemode = false;
414  $attributes = [];
415  $attributesMeta = [];
416  if (is_array($components)) {
417  foreach ($components as $key => $val) {
418  // Only if $name is set (if there is an attribute, that waits for a value), that valuemode is enabled. This ensures that the attribute is assigned it's value
419  if ($val != '=') {
420  if ($valuemode) {
421  if ($name) {
422  $attributes[$name] = $deHSC ? htmlspecialchars_decode($val) : $val;
423  $attributesMeta[$name]['dashType'] = $metaC[$key];
424  $name = '';
425  }
426  } else {
427  if ($namekey = preg_replace('/[^[:alnum:]_\\:\\-]/', '', $val)) {
428  $name = strtolower($namekey);
429  $attributesMeta[$name] = [];
430  $attributesMeta[$name]['origTag'] = $namekey;
431  $attributes[$name] = '';
432  }
433  }
434  $valuemode = false;
435  } else {
436  $valuemode = true;
437  }
438  }
439  return [$attributes, $attributesMeta];
440  }
441  }
442 
452  public function split_tag_attributes($tag)
453  {
454  $matches = [];
455  if (preg_match('/(\\<[^\\s]+\\s+)?(.*?)\\s*(\\>)?$/s', $tag, $matches) !== 1) {
456  return [[], []];
457  }
458  $tag_tmp = $matches[2];
459  $metaValue = [];
460  $value = [];
461  $matches = [];
462  if (preg_match_all('/("[^"]*"|\'[^\']*\'|[^\\s"\'\\=]+|\\=)/s', $tag_tmp, $matches) > 0) {
463  foreach ($matches[1] as $part) {
464  $firstChar = $part[0];
465  if ($firstChar == '"' || $firstChar == '\'') {
466  $metaValue[] = $firstChar;
467  $value[] = substr($part, 1, -1);
468  } else {
469  $metaValue[] = '';
470  $value[] = $part;
471  }
472  }
473  }
474  return [$value, $metaValue];
475  }
476 
490  public function checkTagTypeCounts($content, $blockTags = 'a,b,blockquote,body,div,em,font,form,h1,h2,h3,h4,h5,h6,i,li,map,ol,option,p,pre,select,span,strong,table,td,textarea,tr,u,ul', $soloTags = 'br,hr,img,input,area')
491  {
492  $content = strtolower($content);
493  $analyzedOutput = [];
494  // Counts appearances of start-tags
495  $analyzedOutput['counts'] = [];
496  // Lists ERRORS
497  $analyzedOutput['errors'] = [];
498  // Lists warnings.
499  $analyzedOutput['warnings'] = [];
500  // Lists stats for block-tags
501  $analyzedOutput['blocks'] = [];
502  // Lists stats for solo-tags
503  $analyzedOutput['solo'] = [];
504  // Block tags, must have endings...
505  $blockTags = explode(',', $blockTags);
506  foreach ($blockTags as $tagName) {
507  $countBegin = count(preg_split(('/\\<' . preg_quote($tagName, '/') . '(\\s|\\>)/s'), $content)) - 1;
508  $countEnd = count(preg_split(('/\\<\\/' . preg_quote($tagName, '/') . '(\\s|\\>)/s'), $content)) - 1;
509  $analyzedOutput['blocks'][$tagName] = [$countBegin, $countEnd, $countBegin - $countEnd];
510  if ($countBegin) {
511  $analyzedOutput['counts'][$tagName] = $countBegin;
512  }
513  if ($countBegin - $countEnd) {
514  if ($countBegin - $countEnd > 0) {
515  $analyzedOutput['errors'][$tagName] = 'There were more start-tags (' . $countBegin . ') than end-tags (' . $countEnd . ') for the element "' . $tagName . '". There should be an equal amount!';
516  } else {
517  $analyzedOutput['warnings'][$tagName] = 'There were more end-tags (' . $countEnd . ') than start-tags (' . $countBegin . ') for the element "' . $tagName . '". There should be an equal amount! However the problem is not fatal.';
518  }
519  }
520  }
521  // Solo tags, must NOT have endings...
522  $soloTags = explode(',', $soloTags);
523  foreach ($soloTags as $tagName) {
524  $countBegin = count(preg_split(('/\\<' . preg_quote($tagName, '/') . '(\\s|\\>)/s'), $content)) - 1;
525  $countEnd = count(preg_split(('/\\<\\/' . preg_quote($tagName, '/') . '(\\s|\\>)/s'), $content)) - 1;
526  $analyzedOutput['solo'][$tagName] = [$countBegin, $countEnd];
527  if ($countBegin) {
528  $analyzedOutput['counts'][$tagName] = $countBegin;
529  }
530  if ($countEnd) {
531  $analyzedOutput['warnings'][$tagName] = 'There were end-tags found (' . $countEnd . ') for the element "' . $tagName . '". This was not expected (although XHTML technically allows it).';
532  }
533  }
534  return $analyzedOutput;
535  }
536 
537  /*********************************
538  *
539  * Clean HTML code
540  *
541  *********************************/
578  public function HTMLcleaner($content, $tags = [], $keepAll = 0, $hSC = 0, $addConfig = [])
579  {
580  $newContent = [];
581  $tokArr = explode('<', $content);
582  $newContent[] = $this->processContent(current($tokArr), $hSC, $addConfig);
583  // We skip the first element in foreach loop
584  $tokArrSliced = array_slice($tokArr, 1, null, true);
585  $c = 1;
586  $tagRegister = [];
587  $tagStack = [];
588  $inComment = false;
589  $inCdata = false;
590  $skipTag = false;
591  foreach ($tokArrSliced as $tok) {
592  if ($inComment) {
593  if (($eocPos = strpos($tok, '-->')) === false) {
594  // End of comment is not found in the token. Go further until end of comment is found in other tokens.
595  $newContent[$c++] = '<' . $tok;
596  continue;
597  }
598  // Comment ends in the middle of the token: add comment and proceed with rest of the token
599  $newContent[$c++] = '<' . substr($tok, 0, ($eocPos + 3));
600  $tok = substr($tok, $eocPos + 3);
601  $inComment = false;
602  $skipTag = true;
603  } elseif ($inCdata) {
604  if (($eocPos = strpos($tok, '/*]]>*/')) === false) {
605  // End of comment is not found in the token. Go futher until end of comment is found in other tokens.
606  $newContent[$c++] = '<' . $tok;
607  continue;
608  }
609  // Comment ends in the middle of the token: add comment and proceed with rest of the token
610  $newContent[$c++] = '<' . substr($tok, 0, $eocPos + 10);
611  $tok = substr($tok, $eocPos + 10);
612  $inCdata = false;
613  $skipTag = true;
614  } elseif (substr($tok, 0, 3) == '!--') {
615  if (($eocPos = strpos($tok, '-->')) === false) {
616  // Comment started in this token but it does end in the same token. Set a flag to skip till the end of comment
617  $newContent[$c++] = '<' . $tok;
618  $inComment = true;
619  continue;
620  }
621  // Start and end of comment are both in the current token. Add comment and proceed with rest of the token
622  $newContent[$c++] = '<' . substr($tok, 0, ($eocPos + 3));
623  $tok = substr($tok, $eocPos + 3);
624  $skipTag = true;
625  } elseif (substr($tok, 0, 10) === '![CDATA[*/') {
626  if (($eocPos = strpos($tok, '/*]]>*/')) === false) {
627  // Comment started in this token but it does end in the same token. Set a flag to skip till the end of comment
628  $newContent[$c++] = '<' . $tok;
629  $inCdata = true;
630  continue;
631  }
632  // Start and end of comment are both in the current token. Add comment and proceed with rest of the token
633  $newContent[$c++] = '<' . substr($tok, 0, $eocPos + 10);
634  $tok = substr($tok, $eocPos + 10);
635  $skipTag = true;
636  }
637  $firstChar = $tok[0];
638  // It is a tag... (first char is a-z0-9 or /) (fixed 19/01 2004). This also avoids triggering on <?xml..> and <!DOCTYPE..>
639  if (!$skipTag && preg_match('/[[:alnum:]\\/]/', $firstChar) == 1) {
640  $tagEnd = strpos($tok, '>');
641  // If there is and end-bracket... tagEnd can't be 0 as the first character can't be a >
642  if ($tagEnd) {
643  $endTag = $firstChar == '/' ? 1 : 0;
644  $tagContent = substr($tok, $endTag, $tagEnd - $endTag);
645  $tagParts = preg_split('/\\s+/s', $tagContent, 2);
646  $tagName = strtolower($tagParts[0]);
647  $emptyTag = 0;
648  if (isset($tags[$tagName])) {
649  // If there is processing to do for the tag:
650  if (is_array($tags[$tagName])) {
651  if (preg_match('/^(' . self::VOID_ELEMENTS . ' )$/i', $tagName)) {
652  $emptyTag = 1;
653  }
654  // If NOT an endtag, do attribute processing (added dec. 2003)
655  if (!$endTag) {
656  // Override attributes
657  if ((string)$tags[$tagName]['overrideAttribs'] !== '') {
658  $tagParts[1] = $tags[$tagName]['overrideAttribs'];
659  }
660  // Allowed tags
661  if ((string)$tags[$tagName]['allowedAttribs'] !== '') {
662  // No attribs allowed
663  if ((string)$tags[$tagName]['allowedAttribs'] === '0') {
664  $tagParts[1] = '';
665  } elseif (trim($tagParts[1])) {
666  $tagAttrib = $this->get_tag_attributes($tagParts[1]);
667  $tagParts[1] = '';
668  $newTagAttrib = [];
669  if (!($tList = $tags[$tagName]['_allowedAttribs'])) {
670  // Just explode attribts for tag once
671  $tList = ($tags[$tagName]['_allowedAttribs'] = GeneralUtility::trimExplode(',', strtolower($tags[$tagName]['allowedAttribs']), true));
672  }
673  foreach ($tList as $allowTag) {
674  if (isset($tagAttrib[0][$allowTag])) {
675  $newTagAttrib[$allowTag] = $tagAttrib[0][$allowTag];
676  }
677  }
678  $tagParts[1] = $this->compileTagAttribs($newTagAttrib, $tagAttrib[1]);
679  }
680  }
681  // Fixed attrib values
682  if (is_array($tags[$tagName]['fixAttrib'])) {
683  $tagAttrib = $this->get_tag_attributes($tagParts[1]);
684  $tagParts[1] = '';
685  foreach ($tags[$tagName]['fixAttrib'] as $attr => $params) {
686  if (isset($params['set']) && $params['set'] !== '') {
687  $tagAttrib[0][$attr] = $params['set'];
688  }
689  if (!empty($params['unset'])) {
690  unset($tagAttrib[0][$attr]);
691  }
692  if (!isset($tagAttrib[0][$attr]) && (string)$params['default'] !== '') {
693  $tagAttrib[0][$attr] = $params['default'];
694  }
695  if ($params['always'] || isset($tagAttrib[0][$attr])) {
696  if ($params['trim']) {
697  $tagAttrib[0][$attr] = trim($tagAttrib[0][$attr]);
698  }
699  if ($params['intval']) {
700  $tagAttrib[0][$attr] = (int)$tagAttrib[0][$attr];
701  }
702  if ($params['lower']) {
703  $tagAttrib[0][$attr] = strtolower($tagAttrib[0][$attr]);
704  }
705  if ($params['upper']) {
706  $tagAttrib[0][$attr] = strtoupper($tagAttrib[0][$attr]);
707  }
708  if ($params['range']) {
709  if (isset($params['range'][1])) {
710  $tagAttrib[0][$attr] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($tagAttrib[0][$attr], (int)$params['range'][0], (int)$params['range'][1]);
711  } else {
712  $tagAttrib[0][$attr] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($tagAttrib[0][$attr], (int)$params['range'][0]);
713  }
714  }
715  if (is_array($params['list'])) {
716  // For the class attribute, remove from the attribute value any class not in the list
717  // Classes are case sensitive
718  if ($attr == 'class') {
719  $newClasses = [];
720  $classes = GeneralUtility::trimExplode(' ', $tagAttrib[0][$attr], true);
721  foreach ($classes as $class) {
722  if (in_array($class, $params['list'])) {
723  $newClasses[] = $class;
724  }
725  }
726  if (!empty($newClasses)) {
727  $tagAttrib[0][$attr] = implode(' ', $newClasses);
728  } else {
729  $tagAttrib[0][$attr] = $params['list'][0];
730  }
731  } else {
732  if (!in_array($this->caseShift($tagAttrib[0][$attr], $params['casesensitiveComp']), $this->caseShift($params['list'], $params['casesensitiveComp'], $tagName))) {
733  $tagAttrib[0][$attr] = $params['list'][0];
734  }
735  }
736  }
737  if ($params['removeIfFalse'] && $params['removeIfFalse'] != 'blank' && !$tagAttrib[0][$attr] || $params['removeIfFalse'] == 'blank' && (string)$tagAttrib[0][$attr] === '') {
738  unset($tagAttrib[0][$attr]);
739  }
740  if ((string)$params['removeIfEquals'] !== '' && $this->caseShift($tagAttrib[0][$attr], $params['casesensitiveComp']) === $this->caseShift($params['removeIfEquals'], $params['casesensitiveComp'])) {
741  unset($tagAttrib[0][$attr]);
742  }
743  if ($params['prefixLocalAnchors']) {
744  if ($tagAttrib[0][$attr][0] === '#') {
745  if ($params['prefixLocalAnchors'] == 2) {
747  $contentObjectRenderer = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
748  $prefix = $contentObjectRenderer->getUrlToCurrentLocation();
749  } else {
750  $prefix = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL');
751  }
752  $tagAttrib[0][$attr] = $prefix . $tagAttrib[0][$attr];
753  }
754  }
755  if ($params['prefixRelPathWith']) {
756  $urlParts = parse_url($tagAttrib[0][$attr]);
757  if (!$urlParts['scheme'] && $urlParts['path'][0] !== '/') {
758  // If it is NOT an absolute URL (by http: or starting "/")
759  $tagAttrib[0][$attr] = $params['prefixRelPathWith'] . $tagAttrib[0][$attr];
760  }
761  }
762  if ($params['userFunc']) {
763  if (is_array($params['userFunc.'])) {
764  $params['userFunc.']['attributeValue'] = $tagAttrib[0][$attr];
765  } else {
766  $params['userFunc.'] = $tagAttrib[0][$attr];
767  }
768  $tagAttrib[0][$attr] = GeneralUtility::callUserFunction($params['userFunc'], $params['userFunc.'], $this);
769  }
770  }
771  }
772  $tagParts[1] = $this->compileTagAttribs($tagAttrib[0], $tagAttrib[1]);
773  }
774  } else {
775  // If endTag, remove any possible attributes:
776  $tagParts[1] = '';
777  }
778  // Protecting the tag by converting < and > to &lt; and &gt; ??
779  if ($tags[$tagName]['protect']) {
780  $lt = '&lt;';
781  $gt = '&gt;';
782  } else {
783  $lt = '<';
784  $gt = '>';
785  }
786  // Remapping tag name?
787  if ($tags[$tagName]['remap']) {
788  $tagParts[0] = $tags[$tagName]['remap'];
789  }
790  // rmTagIfNoAttrib
791  if ($endTag || trim($tagParts[1]) || !$tags[$tagName]['rmTagIfNoAttrib']) {
792  $setTag = 1;
793  // Remove this closing tag if $tagName was among $TSconfig['removeTags']
794  if ($endTag && $tags[$tagName]['allowedAttribs'] === 0 && $tags[$tagName]['rmTagIfNoAttrib'] === 1) {
795  $setTag = 0;
796  }
797  if ($tags[$tagName]['nesting']) {
798  if (!is_array($tagRegister[$tagName])) {
799  $tagRegister[$tagName] = [];
800  }
801  if ($endTag) {
802  $correctTag = 1;
803  if ($tags[$tagName]['nesting'] == 'global') {
804  $lastEl = end($tagStack);
805  if ($tagName !== $lastEl) {
806  if (in_array($tagName, $tagStack)) {
807  while (!empty($tagStack) && $tagName !== $lastEl) {
808  $elPos = end($tagRegister[$lastEl]);
809  unset($newContent[$elPos]);
810  array_pop($tagRegister[$lastEl]);
811  array_pop($tagStack);
812  $lastEl = end($tagStack);
813  }
814  } else {
815  // In this case the
816  $correctTag = 0;
817  }
818  }
819  }
820  if (empty($tagRegister[$tagName]) || !$correctTag) {
821  $setTag = 0;
822  } else {
823  array_pop($tagRegister[$tagName]);
824  if ($tags[$tagName]['nesting'] == 'global') {
825  array_pop($tagStack);
826  }
827  }
828  } else {
829  array_push($tagRegister[$tagName], $c);
830  if ($tags[$tagName]['nesting'] == 'global') {
831  array_push($tagStack, $tagName);
832  }
833  }
834  }
835  if ($setTag) {
836  // Setting the tag
837  $newContent[$c++] = $this->processTag($lt . ($endTag ? '/' : '') . trim($tagParts[0] . ' ' . $tagParts[1]) . ($emptyTag ? ' /' : '') . $gt, $addConfig, $endTag, $lt == '&lt;');
838  }
839  }
840  } else {
841  $newContent[$c++] = $this->processTag('<' . ($endTag ? '/' : '') . $tagContent . '>', $addConfig, $endTag);
842  }
843  } elseif ($keepAll) {
844  // This is if the tag was not defined in the array for processing:
845  if ($keepAll === 'protect') {
846  $lt = '&lt;';
847  $gt = '&gt;';
848  } else {
849  $lt = '<';
850  $gt = '>';
851  }
852  $newContent[$c++] = $this->processTag($lt . ($endTag ? '/' : '') . $tagContent . $gt, $addConfig, $endTag, $lt == '&lt;');
853  }
854  $newContent[$c++] = $this->processContent(substr($tok, $tagEnd + 1), $hSC, $addConfig);
855  } else {
856  $newContent[$c++] = $this->processContent('<' . $tok, $hSC, $addConfig);
857  }
858  } else {
859  $newContent[$c++] = $this->processContent(($skipTag ? '' : '<') . $tok, $hSC, $addConfig);
860  // It was not a tag anyways
861  $skipTag = false;
862  }
863  }
864  // Unsetting tags:
865  foreach ($tagRegister as $tag => $positions) {
866  foreach ($positions as $pKey) {
867  unset($newContent[$pKey]);
868  }
869  }
870  $newContent = implode('', $newContent);
871  $newContent = $this->stripEmptyTagsIfConfigured($newContent, $addConfig);
872  return $newContent;
873  }
874 
882  public function bidir_htmlspecialchars($value, $dir)
883  {
884  $dir = (int)$dir;
885  if ($dir === 1) {
886  $value = htmlspecialchars($value);
887  } elseif ($dir === 2) {
888  $value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8', false);
889  } elseif ($dir === -1) {
890  $value = htmlspecialchars_decode($value);
891  }
892  return $value;
893  }
894 
904  public function prefixResourcePath($main_prefix, $content, $alternatives = [], $suffix = '')
905  {
906  $parts = $this->splitTags('embed,td,table,body,img,input,form,link,script,a,param', $content);
907  foreach ($parts as $k => $v) {
908  if ($k % 2) {
909  $params = $this->get_tag_attributes($v);
910  // Detect tag-ending so that it is re-applied correctly.
911  $tagEnd = substr($v, -2) == '/>' ? ' />' : '>';
912  // The 'name' of the first tag
913  $firstTagName = $this->getFirstTagName($v);
914  $somethingDone = 0;
915  $prefix = isset($alternatives[strtoupper($firstTagName)]) ? $alternatives[strtoupper($firstTagName)] : $main_prefix;
916  switch (strtolower($firstTagName)) {
917  case 'td':
918 
919  case 'body':
920 
921  case 'table':
922  $src = $params[0]['background'];
923  if ($src) {
924  $params[0]['background'] = $this->prefixRelPath($prefix, $params[0]['background'], $suffix);
925  $somethingDone = 1;
926  }
927  break;
928  case 'img':
929 
930  case 'input':
931 
932  case 'script':
933 
934  case 'embed':
935  $src = $params[0]['src'];
936  if ($src) {
937  $params[0]['src'] = $this->prefixRelPath($prefix, $params[0]['src'], $suffix);
938  $somethingDone = 1;
939  }
940  break;
941  case 'link':
942 
943  case 'a':
944  $src = $params[0]['href'];
945  if ($src) {
946  $params[0]['href'] = $this->prefixRelPath($prefix, $params[0]['href'], $suffix);
947  $somethingDone = 1;
948  }
949  break;
950  case 'form':
951  $src = $params[0]['action'];
952  if ($src) {
953  $params[0]['action'] = $this->prefixRelPath($prefix, $params[0]['action'], $suffix);
954  $somethingDone = 1;
955  }
956  break;
957  case 'param':
958  $test = $params[0]['name'];
959  if ($test && $test === 'movie') {
960  if ($params[0]['value']) {
961  $params[0]['value'] = $this->prefixRelPath($prefix, $params[0]['value'], $suffix);
962  $somethingDone = 1;
963  }
964  }
965  break;
966  }
967  if ($somethingDone) {
968  $tagParts = preg_split('/\\s+/s', $v, 2);
969  $tagParts[1] = $this->compileTagAttribs($params[0], $params[1]);
970  $parts[$k] = '<' . trim(strtolower($firstTagName) . ' ' . $tagParts[1]) . $tagEnd;
971  }
972  }
973  }
974  $content = implode('', $parts);
975  // Fix <style> section:
976  $prefix = isset($alternatives['style']) ? $alternatives['style'] : $main_prefix;
977  if ((string)$prefix !== '') {
978  $parts = $this->splitIntoBlock('style', $content);
979  foreach ($parts as $k => &$part) {
980  if ($k % 2) {
981  $part = preg_replace('/(url[[:space:]]*\\([[:space:]]*["\']?)([^"\')]*)(["\']?[[:space:]]*\\))/i', '\\1' . $prefix . '\\2' . $suffix . '\\3', $part);
982  }
983  }
984  unset($part);
985  $content = implode('', $parts);
986  }
987  return $content;
988  }
989 
999  public function prefixRelPath($prefix, $srcVal, $suffix = '')
1000  {
1001  // Only prefix if it's not an absolute URL or
1002  // only a link to a section within the page.
1003  if ($srcVal[0] !== '/' && $srcVal[0] !== '#') {
1004  $urlParts = parse_url($srcVal);
1005  // Only prefix URLs without a scheme
1006  if (!$urlParts['scheme']) {
1007  $srcVal = $prefix . $srcVal . $suffix;
1008  }
1009  }
1010  return $srcVal;
1011  }
1012 
1023  public function cleanFontTags($value, $keepFace = 0, $keepSize = 0, $keepColor = 0)
1024  {
1025  // ,1 ?? - could probably be more stable if splitTags() was used since this depends on end-tags being properly set!
1026  $fontSplit = $this->splitIntoBlock('font', $value);
1027  foreach ($fontSplit as $k => $v) {
1028  // Font
1029  if ($k % 2) {
1030  $attribArray = $this->get_tag_attributes_classic($this->getFirstTag($v));
1031  $newAttribs = [];
1032  if ($keepFace && $attribArray['face']) {
1033  $newAttribs[] = 'face="' . $attribArray['face'] . '"';
1034  }
1035  if ($keepSize && $attribArray['size']) {
1036  $newAttribs[] = 'size="' . $attribArray['size'] . '"';
1037  }
1038  if ($keepColor && $attribArray['color']) {
1039  $newAttribs[] = 'color="' . $attribArray['color'] . '"';
1040  }
1041  $innerContent = $this->cleanFontTags($this->removeFirstAndLastTag($v), $keepFace, $keepSize, $keepColor);
1042  if (!empty($newAttribs)) {
1043  $fontSplit[$k] = '<font ' . implode(' ', $newAttribs) . '>' . $innerContent . '</font>';
1044  } else {
1045  $fontSplit[$k] = $innerContent;
1046  }
1047  }
1048  }
1049  return implode('', $fontSplit);
1050  }
1051 
1061  public function mapTags($value, $tags = [], $ltChar = '<', $ltChar2 = '<')
1062  {
1063  foreach ($tags as $from => $to) {
1064  $value = preg_replace('/' . preg_quote($ltChar, '/') . '(\\/)?' . $from . '\\s([^\\>])*(\\/)?\\>/', $ltChar2 . '$1' . $to . ' $2$3>', $value);
1065  }
1066  return $value;
1067  }
1068 
1076  public function unprotectTags($content, $tagList = '')
1077  {
1078  $tagsArray = GeneralUtility::trimExplode(',', $tagList, true);
1079  $contentParts = explode('&lt;', $content);
1080  // bypass the first
1081  $contentPartsSliced = array_slice($contentParts, 1, null, true);
1082  foreach ($contentPartsSliced as $k => $tok) {
1083  $firstChar = $tok[0];
1084  if (trim($firstChar) !== '') {
1085  $subparts = explode('&gt;', $tok, 2);
1086  $tagEnd = strlen($subparts[0]);
1087  if (strlen($tok) != $tagEnd) {
1088  $endTag = $firstChar == '/' ? 1 : 0;
1089  $tagContent = substr($tok, $endTag, $tagEnd - $endTag);
1090  $tagParts = preg_split('/\\s+/s', $tagContent, 2);
1091  $tagName = strtolower($tagParts[0]);
1092  if ((string)$tagList === '' || in_array($tagName, $tagsArray)) {
1093  $contentParts[$k] = '<' . $subparts[0] . '>' . $subparts[1];
1094  } else {
1095  $contentParts[$k] = '&lt;' . $tok;
1096  }
1097  } else {
1098  $contentParts[$k] = '&lt;' . $tok;
1099  }
1100  } else {
1101  $contentParts[$k] = '&lt;' . $tok;
1102  }
1103  }
1104  return implode('', $contentParts);
1105  }
1106 
1116  public function caseShift($str, $flag, $cacheKey = '')
1117  {
1118  $cacheKey .= $flag ? 1 : 0;
1119  if (is_array($str)) {
1120  if (!$cacheKey || !isset($this->caseShift_cache[$cacheKey])) {
1121  foreach ($str as &$v) {
1122  if (!$flag) {
1123  $v = strtoupper($v);
1124  }
1125  }
1126  unset($v);
1127  if ($cacheKey) {
1128  $this->caseShift_cache[$cacheKey] = $str;
1129  }
1130  } else {
1131  $str = $this->caseShift_cache[$cacheKey];
1132  }
1133  } elseif (!$flag) {
1134  $str = strtoupper($str);
1135  }
1136  return $str;
1137  }
1138 
1148  public function compileTagAttribs($tagAttrib, $meta = [], $xhtmlClean = 0)
1149  {
1150  $accu = [];
1151  foreach ($tagAttrib as $k => $v) {
1152  if ($xhtmlClean) {
1153  $attr = strtolower($k);
1154  if ((string)$v !== '' || isset($meta[$k]['dashType'])) {
1155  $attr .= '="' . htmlspecialchars($v) . '"';
1156  }
1157  } else {
1158  $attr = $meta[$k]['origTag'] ?: $k;
1159  if (strcmp($v, '') || isset($meta[$k]['dashType'])) {
1160  $dash = $meta[$k]['dashType'] ?: (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($v) ? '' : '"');
1161  $attr .= '=' . $dash . $v . $dash;
1162  }
1163  }
1164  $accu[] = $attr;
1165  }
1166  return implode(' ', $accu);
1167  }
1168 
1177  public function get_tag_attributes_classic($tag, $deHSC = 0)
1178  {
1179  $attr = $this->get_tag_attributes($tag, $deHSC);
1180  return is_array($attr[0]) ? $attr[0] : [];
1181  }
1182 
1191  public function indentLines($content, $number = 1, $indentChar = TAB)
1192  {
1193  $preTab = str_pad('', $number * strlen($indentChar), $indentChar);
1194  $lines = explode(LF, str_replace(CR, '', $content));
1195  foreach ($lines as &$line) {
1196  $line = $preTab . $line;
1197  }
1198  unset($line);
1199  return implode(LF, $lines);
1200  }
1201 
1210  public function HTMLparserConfig($TSconfig, $keepTags = [])
1211  {
1212  // Allow tags (base list, merged with incoming array)
1213  $alTags = array_flip(GeneralUtility::trimExplode(',', strtolower($TSconfig['allowTags']), true));
1214  $keepTags = array_merge($alTags, $keepTags);
1215  // Set config properties.
1216  if (is_array($TSconfig['tags.'])) {
1217  foreach ($TSconfig['tags.'] as $key => $tagC) {
1218  if (!is_array($tagC) && $key == strtolower($key)) {
1219  if ((string)$tagC === '0') {
1220  unset($keepTags[$key]);
1221  }
1222  if ((string)$tagC === '1' && !isset($keepTags[$key])) {
1223  $keepTags[$key] = 1;
1224  }
1225  }
1226  }
1227  foreach ($TSconfig['tags.'] as $key => $tagC) {
1228  if (is_array($tagC) && $key == strtolower($key)) {
1229  $key = substr($key, 0, -1);
1230  if (!is_array($keepTags[$key])) {
1231  $keepTags[$key] = [];
1232  }
1233  if (is_array($tagC['fixAttrib.'])) {
1234  foreach ($tagC['fixAttrib.'] as $atName => $atConfig) {
1235  if (is_array($atConfig)) {
1236  $atName = substr($atName, 0, -1);
1237  if (!is_array($keepTags[$key]['fixAttrib'][$atName])) {
1238  $keepTags[$key]['fixAttrib'][$atName] = [];
1239  }
1240  $keepTags[$key]['fixAttrib'][$atName] = array_merge($keepTags[$key]['fixAttrib'][$atName], $atConfig);
1241  if ((string)$keepTags[$key]['fixAttrib'][$atName]['range'] !== '') {
1242  $keepTags[$key]['fixAttrib'][$atName]['range'] = GeneralUtility::trimExplode(',', $keepTags[$key]['fixAttrib'][$atName]['range']);
1243  }
1244  if ((string)$keepTags[$key]['fixAttrib'][$atName]['list'] !== '') {
1245  $keepTags[$key]['fixAttrib'][$atName]['list'] = GeneralUtility::trimExplode(',', $keepTags[$key]['fixAttrib'][$atName]['list']);
1246  }
1247  }
1248  }
1249  }
1250  unset($tagC['fixAttrib.']);
1251  unset($tagC['fixAttrib']);
1252  if (isset($tagC['rmTagIfNoAttrib']) && $tagC['rmTagIfNoAttrib'] && empty($tagC['nesting'])) {
1253  $tagC['nesting'] = 1;
1254  }
1255  $keepTags[$key] = array_merge($keepTags[$key], $tagC);
1256  }
1257  }
1258  }
1259  // LocalNesting
1260  if ($TSconfig['localNesting']) {
1261  $lN = GeneralUtility::trimExplode(',', strtolower($TSconfig['localNesting']), true);
1262  foreach ($lN as $tn) {
1263  if (isset($keepTags[$tn])) {
1264  if (!is_array($keepTags[$tn])) {
1265  $keepTags[$tn] = [];
1266  }
1267  $keepTags[$tn]['nesting'] = 1;
1268  }
1269  }
1270  }
1271  if ($TSconfig['globalNesting']) {
1272  $lN = GeneralUtility::trimExplode(',', strtolower($TSconfig['globalNesting']), true);
1273  foreach ($lN as $tn) {
1274  if (isset($keepTags[$tn])) {
1275  if (!is_array($keepTags[$tn])) {
1276  $keepTags[$tn] = [];
1277  }
1278  $keepTags[$tn]['nesting'] = 'global';
1279  }
1280  }
1281  }
1282  if ($TSconfig['rmTagIfNoAttrib']) {
1283  $lN = GeneralUtility::trimExplode(',', strtolower($TSconfig['rmTagIfNoAttrib']), true);
1284  foreach ($lN as $tn) {
1285  if (isset($keepTags[$tn])) {
1286  if (!is_array($keepTags[$tn])) {
1287  $keepTags[$tn] = [];
1288  }
1289  $keepTags[$tn]['rmTagIfNoAttrib'] = 1;
1290  if (empty($keepTags[$tn]['nesting'])) {
1291  $keepTags[$tn]['nesting'] = 1;
1292  }
1293  }
1294  }
1295  }
1296  if ($TSconfig['noAttrib']) {
1297  $lN = GeneralUtility::trimExplode(',', strtolower($TSconfig['noAttrib']), true);
1298  foreach ($lN as $tn) {
1299  if (isset($keepTags[$tn])) {
1300  if (!is_array($keepTags[$tn])) {
1301  $keepTags[$tn] = [];
1302  }
1303  $keepTags[$tn]['allowedAttribs'] = 0;
1304  }
1305  }
1306  }
1307  if ($TSconfig['removeTags']) {
1308  $lN = GeneralUtility::trimExplode(',', strtolower($TSconfig['removeTags']), true);
1309  foreach ($lN as $tn) {
1310  $keepTags[$tn] = [];
1311  $keepTags[$tn]['allowedAttribs'] = 0;
1312  $keepTags[$tn]['rmTagIfNoAttrib'] = 1;
1313  }
1314  }
1315  // Create additional configuration:
1316  $addConfig = [];
1317  if ($TSconfig['xhtml_cleaning']) {
1318  $addConfig['xhtml'] = 1;
1319  }
1320  if (isset($TSconfig['stripEmptyTags'])) {
1321  $addConfig['stripEmptyTags'] = $TSconfig['stripEmptyTags'];
1322  if (isset($TSconfig['stripEmptyTags.'])) {
1323  $addConfig['stripEmptyTags.'] = $TSconfig['stripEmptyTags.'];
1324  }
1325  }
1326  return [
1327  $keepTags,
1328  '' . $TSconfig['keepNonMatchedTags'],
1329  (int)$TSconfig['htmlSpecialChars'],
1330  $addConfig
1331  ];
1332  }
1333 
1360  public function XHTML_clean($content)
1361  {
1362  GeneralUtility::logDeprecatedFunction('TYPO3\CMS\Core\Html\HtmlParser::XHTML_clean has been deprecated with TYPO3 CMS 7 and will be removed with TYPO3 CMS 8.');
1363  return $this->HTMLcleaner($content, [], 1, 0, ['xhtml' => 1]);
1364  }
1365 
1377  public function processTag($value, $conf, $endTag, $protected = 0)
1378  {
1379  // Return immediately if protected or no parameters
1380  if ($protected || empty($conf)) {
1381  return $value;
1382  }
1383  // OK then, begin processing for XHTML output:
1384  // STILL VERY EXPERIMENTAL!!
1385  if ($conf['xhtml']) {
1386  GeneralUtility::deprecationLog('This section has been deprecated with TYPO3 CMS 7 and will be removed with CMS 8.');
1387  // Endtags are just set lowercase right away
1388  if ($endTag) {
1389  $value = strtolower($value);
1390  } elseif (substr($value, 0, 4) != '<!--') {
1391  // ... and comments are ignored.
1392  // Finding inner value with out < >
1393  $inValue = substr($value, 1, substr($value, -2) == '/>' ? -2 : -1);
1394  // Separate attributes and tagname
1395  list($tagName, $tagP) = preg_split('/\\s+/s', $inValue, 2);
1396  $tagName = strtolower($tagName);
1397  // Process attributes
1398  $tagAttrib = $this->get_tag_attributes($tagP);
1399  if ($tagName === 'img' && !isset($tagAttrib[0]['alt'])) {
1400  $tagAttrib[0]['alt'] = '';
1401  }
1402  // Set alt attribute for all images (not XHTML though...)
1403  if ($tagName === 'script' && !isset($tagAttrib[0]['type'])) {
1404  $tagAttrib[0]['type'] = 'text/javascript';
1405  }
1406  // Set type attribute for all script-tags
1407  $outA = [];
1408  foreach ($tagAttrib[0] as $attrib_name => $attrib_value) {
1409  // Set attributes: lowercase, always in quotes, with htmlspecialchars converted.
1410  $outA[] = $attrib_name . '="' . $this->bidir_htmlspecialchars($attrib_value, 2) . '"';
1411  }
1412  $newTag = '<' . trim($tagName . ' ' . implode(' ', $outA));
1413  // All tags that are standalone (not wrapping, not having endtags) should be ended with '/>'
1414  if (
1415  $tagName === 'img' || $tagName === 'br' || $tagName === 'hr' || $tagName === 'meta' || $tagName === 'link' || $tagName === 'base'
1416  || $tagName === 'area' || $tagName === 'input' || $tagName === 'param' || $tagName === 'col' || substr($value, -2) === '/>'
1417  ) {
1418  $newTag .= ' />';
1419  } else {
1420  $newTag .= '>';
1421  }
1422  $value = $newTag;
1423  }
1424  }
1425  return $value;
1426  }
1427 
1437  public function processContent($value, $dir, $conf)
1438  {
1439  if ($dir != 0) {
1440  $value = $this->bidir_htmlspecialchars($value, $dir);
1441  }
1442  return $value;
1443  }
1444 
1454  public function stripEmptyTags($content, $tagList = null, $treatNonBreakingSpaceAsEmpty = false)
1455  {
1456  $tagRegEx = '[^ >]+'; // all characters until you reach a > or space;
1457  if ($tagList) {
1458  $tags = preg_split('/,/', $tagList);
1459  $tagRegEx = preg_replace('/ */', '', implode('|', $tags));
1460  }
1461  $count = 1;
1462  $nbspRegex = $treatNonBreakingSpaceAsEmpty ? '|(&nbsp;)' : '';
1463  while ($count != 0) {
1464  $content = preg_replace(sprintf('/<(%s)[^>]*>( %s)*<\/\\1[^>]*>/i', $tagRegEx, $nbspRegex), '', $content, -1, $count);
1465  }
1466  return $content;
1467  }
1468 
1476  protected function stripEmptyTagsIfConfigured($value, $configuration)
1477  {
1478  if (isset($configuration['stripEmptyTags']) && $configuration['stripEmptyTags']) {
1479  $tags = null;
1480  if (
1481  isset($configuration['stripEmptyTags.']['tags'])
1482  && $configuration['stripEmptyTags.']['tags'] !== ''
1483  ) {
1484  $tags = $configuration['stripEmptyTags.']['tags'];
1485  }
1486 
1487  $treatNonBreakingSpaceAsEmpty = false;
1488  if (
1489  isset($configuration['stripEmptyTags.']['treatNonBreakingSpaceAsEmpty'])
1490  && $configuration['stripEmptyTags.']['treatNonBreakingSpaceAsEmpty']
1491  ) {
1492  $treatNonBreakingSpaceAsEmpty = (bool)$configuration['stripEmptyTags.']['treatNonBreakingSpaceAsEmpty'];
1493  }
1494 
1495  $value = $this->stripEmptyTags($value, $tags, $treatNonBreakingSpaceAsEmpty);
1496  }
1497 
1498  return $value;
1499  }
1500 }
static substituteSubpartArray($content, array $subpartsContent)
Definition: HtmlParser.php:79
static substituteMarkerAndSubpartArrayRecursive($content, array $markersAndSubparts, $wrap='', $uppercase=false, $deleteUnused=false)
Definition: HtmlParser.php:168
checkTagTypeCounts($content, $blockTags='a, b, blockquote, body, div, em, font, form, h1, h2, h3, h4, h5, h6, i, li, map, ol, option, p, pre, select, span, strong, table, td, textarea, tr, u, ul', $soloTags='br, hr, img, input, area')
Definition: HtmlParser.php:490
static substituteMarkerArray($content, $markContentArray, $wrap='', $uppercase=false, $deleteUnused=false)
Definition: HtmlParser.php:123
compileTagAttribs($tagAttrib, $meta=[], $xhtmlClean=0)
static substituteSubpart($content, $marker, $subpartContent, $recursive=true, $keepMarker=false)
Definition: HtmlParser.php:63
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
stripEmptyTags($content, $tagList=null, $treatNonBreakingSpaceAsEmpty=false)
stripEmptyTagsIfConfigured($value, $configuration)
getAllParts($parts, $tag_parts=true, $include_tag=true)
Definition: HtmlParser.php:335
indentLines($content, $number=1, $indentChar=TAB)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
prefixRelPath($prefix, $srcVal, $suffix='')
Definition: HtmlParser.php:999
static callUserFunction($funcName, &$params, &$ref, $checkPrefix='', $errorMode=0)
get_tag_attributes($tag, $deHSC=0)
Definition: HtmlParser.php:408
caseShift($str, $flag, $cacheKey='')
prefixResourcePath($main_prefix, $content, $alternatives=[], $suffix='')
Definition: HtmlParser.php:904
splitIntoBlockRecursiveProc($tag, $content, &$procObj, $callBackContent, $callBackTags, $level=0)
Definition: HtmlParser.php:263
static substituteMarker($content, $marker, $markContent)
Definition: HtmlParser.php:97
HTMLparserConfig($TSconfig, $keepTags=[])
processContent($value, $dir, $conf)
static getSubpart($content, $marker)
Definition: HtmlParser.php:44
bidir_htmlspecialchars($value, $dir)
Definition: HtmlParser.php:882
mapTags($value, $tags=[], $ltChar='<', $ltChar2='<')
cleanFontTags($value, $keepFace=0, $keepSize=0, $keepColor=0)
processTag($value, $conf, $endTag, $protected=0)
get_tag_attributes_classic($tag, $deHSC=0)
unprotectTags($content, $tagList='')
splitIntoBlock($tag, $content, $eliminateExtraEndTags=false)
Definition: HtmlParser.php:191
getFirstTagName($str, $preserveCase=false)
Definition: HtmlParser.php:388