‪TYPO3CMS  10.4
TypoScriptParser.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Psr\Log\LoggerInterface;
19 use Symfony\Component\Finder\Finder;
32 
37 {
43  public ‪$setup = [];
44 
50  protected ‪$raw;
51 
57  protected ‪$rawP;
58 
64  protected ‪$lastComment = '';
65 
71  protected ‪$commentSet = false;
72 
78  protected ‪$multiLineEnabled = false;
79 
85  protected ‪$multiLineObject = '';
86 
92  protected ‪$multiLineValue = [];
93 
99  protected ‪$inBrace = 0;
100 
107  protected ‪$lastConditionTrue = true;
108 
114  public ‪$sections = [];
115 
121  public ‪$sectionsMatch = [];
122 
128  protected ‪$syntaxHighLight = false;
129 
135  protected ‪$highLightData = [];
136 
142  protected ‪$highLightData_bracelevel = [];
143 
149  public ‪$regComments = false;
150 
156  public ‪$regLinenumbers = false;
157 
163  public ‪$errors = [];
164 
170  public ‪$lineNumberOffset = 0;
171 
177  public ‪$breakPointLN = 0;
178 
182  protected ‪$highLightStyles = [
183  'prespace' => ['<span class="ts-prespace">', '</span>'],
184  // Space before any content on a line
185  'objstr_postspace' => ['<span class="ts-objstr_postspace">', '</span>'],
186  // Space after the object string on a line
187  'operator_postspace' => ['<span class="ts-operator_postspace">', '</span>'],
188  // Space after the operator on a line
189  'operator' => ['<span class="ts-operator">', '</span>'],
190  // The operator char
191  'value' => ['<span class="ts-value">', '</span>'],
192  // The value of a line
193  'objstr' => ['<span class="ts-objstr">', '</span>'],
194  // The object string of a line
195  'value_copy' => ['<span class="ts-value_copy">', '</span>'],
196  // The value when the copy syntax (<) is used; that means the object reference
197  'value_unset' => ['<span class="ts-value_unset">', '</span>'],
198  // The value when an object is unset. Should not exist.
199  'ignored' => ['<span class="ts-ignored">', '</span>'],
200  // The "rest" of a line which will be ignored.
201  'default' => ['<span class="ts-default">', '</span>'],
202  // The default style if none other is applied.
203  'comment' => ['<span class="ts-comment">', '</span>'],
204  // Comment lines
205  'condition' => ['<span class="ts-condition">', '</span>'],
206  // Conditions
207  'error' => ['<span class="ts-error">', '</span>'],
208  // Error messages
209  'linenum' => ['<span class="ts-linenum">', '</span>']
210  ];
211 
217  protected ‪$highLightBlockStyles = '';
218 
224  protected ‪$highLightBlockStyles_basecolor = '#cccccc';
225 
229  public ‪$parentObject;
230 
237  public function ‪parse($string, $matchObj = '')
238  {
239  $this->raw = explode(LF, $string);
240  $this->rawP = 0;
241  $pre = '[GLOBAL]';
242  while ($pre) {
243  if ($this->breakPointLN && $pre === '[_BREAK]') {
244  $this->‪error('Breakpoint at ' . ($this->lineNumberOffset + $this->rawP - 2) . ': Line content was "' . $this->raw[$this->rawP - 2] . '"', 1);
245  break;
246  }
247  if ($pre === '[]') {
248  $this->‪error('Empty condition is always false, this does not make sense. At line ' . ($this->lineNumberOffset + $this->rawP - 1), 2);
249  break;
250  }
251  $preUppercase = strtoupper($pre);
252  if ($pre[0] === '[' &&
253  ($preUppercase === '[GLOBAL]' ||
254  $preUppercase === '[END]' ||
255  !$this->lastConditionTrue && $preUppercase === '[ELSE]')
256  ) {
257  $pre = trim($this->‪parseSub($this->setup));
258  $this->lastConditionTrue = true;
259  } else {
260  // We're in a specific section. Therefore we log this section
261  $specificSection = $preUppercase !== '[ELSE]';
262  if ($specificSection) {
263  $this->sections[md5($pre)] = $pre;
264  }
265  if (is_object($matchObj) && $matchObj->match($pre) || $this->syntaxHighLight) {
266  if ($specificSection) {
267  $this->sectionsMatch[md5($pre)] = $pre;
268  }
269  $pre = trim($this->‪parseSub($this->setup));
270  $this->lastConditionTrue = true;
271  } else {
272  $pre = $this->‪nextDivider();
273  $this->lastConditionTrue = false;
274  }
275  }
276  }
277  if ($this->inBrace) {
278  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': The script is short of ' . $this->inBrace . ' end brace(s)', 1);
279  }
280  if ($this->multiLineEnabled) {
281  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': A multiline value section is not ended with a parenthesis!', 1);
282  }
283  $this->lineNumberOffset += count($this->raw) + 1;
284  }
285 
292  protected function ‪nextDivider()
293  {
294  while (isset($this->raw[$this->rawP])) {
295  $line = trim($this->raw[$this->rawP]);
296  $this->rawP++;
297  if ($line && $line[0] === '[') {
298  return $line;
299  }
300  }
301  return '';
302  }
303 
310  protected function ‪parseSub(array &‪$setup)
311  {
312  while (isset($this->raw[$this->rawP])) {
313  $line = ltrim($this->raw[$this->rawP]);
314  $lineP = ‪$this->rawP;
315  $this->rawP++;
316  if ($this->syntaxHighLight) {
317  $this->‪regHighLight('prespace', $lineP, strlen($line));
318  }
319  // Breakpoint?
320  // By adding 1 we get that line processed
321  if ($this->breakPointLN && $this->lineNumberOffset + $this->rawP - 1 === $this->breakPointLN + 1) {
322  return '[_BREAK]';
323  }
324  // Set comment flag?
325  if (!$this->multiLineEnabled && strpos($line, '/*') === 0) {
326  $this->commentSet = true;
327  }
328  // If $this->multiLineEnabled we will go and get the line values here because we know, the first if() will be TRUE.
329  if (!$this->commentSet && ($line || $this->multiLineEnabled)) {
330  // If multiline is enabled. Escape by ')'
331  if ($this->multiLineEnabled) {
332  // Multiline ends...
333  if (!empty($line[0]) && $line[0] === ')') {
334  if ($this->syntaxHighLight) {
335  $this->‪regHighLight('operator', $lineP, strlen($line) - 1);
336  }
337  // Disable multiline
338  $this->multiLineEnabled = false;
339  $theValue = implode(LF, $this->multiLineValue);
340  if (strpos($this->multiLineObject, '.') !== false) {
341  // Set the value deeper.
342  $this->‪setVal($this->multiLineObject, ‪$setup, [$theValue]);
343  } else {
344  // Set value regularly
346  if ($this->lastComment && $this->regComments) {
347  ‪$setup[$this->multiLineObject . '..'] .= ‪$this->lastComment;
348  }
349  if ($this->regLinenumbers) {
350  ‪$setup[$this->multiLineObject . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
351  }
352  }
353  } else {
354  if ($this->syntaxHighLight) {
355  $this->‪regHighLight('value', $lineP);
356  }
357  $this->multiLineValue[] = $this->raw[$this->rawP - 1];
358  }
359  } elseif ($this->inBrace === 0 && $line[0] === '[') {
360  if (substr(trim($line), -1, 1) !== ']') {
361  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Invalid condition found, any condition must end with "]": ' . $line);
362  return $line;
363  }
364  // Beginning of condition (only on level zero compared to brace-levels
365  if ($this->syntaxHighLight) {
366  $this->‪regHighLight('condition', $lineP);
367  }
368  return $line;
369  } else {
370  // Return if GLOBAL condition is set - no matter what.
371  if ($line[0] === '[' && stripos($line, '[GLOBAL]') !== false) {
372  if ($this->syntaxHighLight) {
373  $this->‪regHighLight('condition', $lineP);
374  }
375  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': On return to [GLOBAL] scope, the script was short of ' . $this->inBrace . ' end brace(s)', 1);
376  $this->inBrace = 0;
377  return $line;
378  }
379  if ($line[0] !== '}' && $line[0] !== '#' && $line[0] !== '/') {
380  // If not brace-end or comment
381  // Find object name string until we meet an operator
382  $varL = strcspn($line, "\t" . ' {=<>(');
383  // check for special ":=" operator
384  if ($varL > 0 && substr($line, $varL - 1, 2) === ':=') {
385  --$varL;
386  }
387  // also remove tabs after the object string name
388  $objStrName = substr($line, 0, $varL);
389  if ($this->syntaxHighLight) {
390  $this->‪regHighLight('objstr', $lineP, strlen(substr($line, $varL)));
391  }
392  if ($objStrName !== '') {
393  $r = [];
394  if (preg_match('/[^[:alnum:]\\/_\\\\\\.:-]/i', $objStrName, $r)) {
395  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_:-/\\."');
396  } else {
397  $line = ltrim(substr($line, $varL));
398  if ($this->syntaxHighLight) {
399  $this->‪regHighLight('objstr_postspace', $lineP, strlen($line));
400  if ($line !== '') {
401  $this->‪regHighLight('operator', $lineP, strlen($line) - 1);
402  $this->‪regHighLight('operator_postspace', $lineP, strlen(ltrim(substr($line, 1))));
403  }
404  }
405  if ($line === '') {
406  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({');
407  } else {
408  // Checking for special TSparser properties (to change TS values at parsetime)
409  $match = [];
410  if ($line[0] === ':' && preg_match('/^:=\\s*([[:alpha:]]+)\\s*\\((.*)\\).*/', $line, $match)) {
411  $tsFunc = $match[1];
412  $tsFuncArg = $match[2];
413  $val = $this->‪getVal($objStrName, ‪$setup);
414  $currentValue = $val[0] ?? null;
415  $tsFuncArg = str_replace(['\\\\', '\\n', '\\t'], ['\\', LF, "\t"], $tsFuncArg);
416  $newValue = $this->‪executeValueModifier($tsFunc, $tsFuncArg, $currentValue);
417  if (isset($newValue)) {
418  $line = '= ' . $newValue;
419  }
420  }
421  switch ($line[0]) {
422  case '=':
423  if ($this->syntaxHighLight) {
424  $this->‪regHighLight('value', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
425  }
426  if (strpos($objStrName, '.') !== false) {
427  $value = [];
428  $value[0] = trim(substr($line, 1));
429  $this->‪setVal($objStrName, ‪$setup, $value);
430  } else {
431  ‪$setup[$objStrName] = trim(substr($line, 1));
432  if ($this->lastComment && $this->regComments) {
433  // Setting comment..
434  $matchingCommentKey = $objStrName . '..';
435  if (isset(‪$setup[$matchingCommentKey])) {
436  ‪$setup[$matchingCommentKey] .= ‪$this->lastComment;
437  } else {
438  ‪$setup[$matchingCommentKey] = ‪$this->lastComment;
439  }
440  }
441  if ($this->regLinenumbers) {
442  ‪$setup[$objStrName . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
443  }
444  }
445  break;
446  case '{':
447  $this->inBrace++;
448  if (strpos($objStrName, '.') !== false) {
449  $exitSig = $this->‪rollParseSub($objStrName, ‪$setup);
450  if ($exitSig) {
451  return $exitSig;
452  }
453  } else {
454  if (!isset(‪$setup[$objStrName . '.'])) {
455  ‪$setup[$objStrName . '.'] = [];
456  }
457  $exitSig = $this->‪parseSub($setup[$objStrName . '.']);
458  if ($exitSig) {
459  return $exitSig;
460  }
461  }
462  break;
463  case '(':
464  $this->multiLineObject = $objStrName;
465  $this->multiLineEnabled = true;
466  $this->multiLineValue = [];
467  break;
468  case '<':
469  if ($this->syntaxHighLight) {
470  $this->‪regHighLight('value_copy', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
471  }
472  $theVal = trim(substr($line, 1));
473  if ($theVal[0] === '.') {
474  $res = $this->‪getVal(substr($theVal, 1), ‪$setup);
475  } else {
476  $res = $this->‪getVal($theVal, $this->setup);
477  }
478  // unserialize(serialize(...)) may look stupid but is needed because of some reference issues.
479  // See forge issue #76919 and functional test hasFlakyReferences()
480  $this->‪setVal($objStrName, ‪$setup, unserialize(serialize($res), ['allowed_classes' => false]), true);
481  break;
482  case '>':
483  if ($this->syntaxHighLight) {
484  $this->‪regHighLight('value_unset', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
485  }
486  $this->‪setVal($objStrName, ‪$setup, 'UNSET');
487  break;
488  default:
489  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({');
490  }
491  }
492  }
493  $this->lastComment = '';
494  }
495  } elseif ($line[0] === '}') {
496  $this->inBrace--;
497  $this->lastComment = '';
498  if ($this->syntaxHighLight) {
499  $this->‪regHighLight('operator', $lineP, strlen($line) - 1);
500  }
501  if ($this->inBrace < 0) {
502  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': An end brace is in excess.', 1);
503  $this->inBrace = 0;
504  } else {
505  break;
506  }
507  } else {
508  if ($this->syntaxHighLight) {
509  $this->‪regHighLight('comment', $lineP);
510  }
511  // Comment. The comments are concatenated in this temporary string:
512  if ($this->regComments) {
513  $this->lastComment .= rtrim($line) . LF;
514  }
515  }
516  if (strpos($line, '### ERROR') === 0) {
517  $this->‪error(substr($line, 11));
518  }
519  }
520  }
521  // Unset comment
522  if ($this->commentSet) {
523  if ($this->syntaxHighLight) {
524  $this->‪regHighLight('comment', $lineP);
525  }
526  if (strpos($line, '*/') !== false) {
527  $this->commentSet = false;
528  }
529  }
530  }
531  return '';
532  }
533 
543  protected function ‪executeValueModifier($modifierName, $modifierArgument = null, $currentValue = null)
544  {
545  $modifierArgumentAsString = (string)$modifierArgument;
546  $currentValueAsString = (string)$currentValue;
547  $newValue = null;
548  switch ($modifierName) {
549  case 'prependString':
550  $newValue = $modifierArgumentAsString . $currentValueAsString;
551  break;
552  case 'appendString':
553  $newValue = $currentValueAsString . $modifierArgumentAsString;
554  break;
555  case 'removeString':
556  $newValue = str_replace($modifierArgumentAsString, '', $currentValueAsString);
557  break;
558  case 'replaceString':
559  $modifierArgumentArray = explode('|', $modifierArgumentAsString, 2);
560  $fromStr = $modifierArgumentArray[0] ?? '';
561  $toStr = $modifierArgumentArray[1] ?? '';
562  $newValue = str_replace($fromStr, $toStr, $currentValueAsString);
563  break;
564  case 'addToList':
565  $newValue = ($currentValueAsString !== '' ? $currentValueAsString . ',' : '') . $modifierArgumentAsString;
566  break;
567  case 'removeFromList':
568  $existingElements = ‪GeneralUtility::trimExplode(',', $currentValueAsString);
569  $removeElements = ‪GeneralUtility::trimExplode(',', $modifierArgumentAsString);
570  if (!empty($removeElements)) {
571  $newValue = implode(',', array_diff($existingElements, $removeElements));
572  }
573  break;
574  case 'uniqueList':
575  $elements = ‪GeneralUtility::trimExplode(',', $currentValueAsString);
576  $newValue = implode(',', array_unique($elements));
577  break;
578  case 'reverseList':
579  $elements = ‪GeneralUtility::trimExplode(',', $currentValueAsString);
580  $newValue = implode(',', array_reverse($elements));
581  break;
582  case 'sortList':
583  $elements = ‪GeneralUtility::trimExplode(',', $currentValueAsString);
584  $arguments = ‪GeneralUtility::trimExplode(',', $modifierArgumentAsString);
585  $arguments = array_map('strtolower', $arguments);
586  $sort_flags = SORT_REGULAR;
587  if (in_array('numeric', $arguments)) {
588  $sort_flags = SORT_NUMERIC;
589  // If the sorting modifier "numeric" is given, all values
590  // are checked and an exception is thrown if a non-numeric value is given
591  // otherwise there is a different behaviour between PHP7 and PHP 5.x
592  // See also the warning on http://us.php.net/manual/en/function.sort.php
593  foreach ($elements as $element) {
594  if (!is_numeric($element)) {
595  throw new \InvalidArgumentException('The list "' . $currentValueAsString . '" should be sorted numerically but contains a non-numeric value', 1438191758);
596  }
597  }
598  }
599  sort($elements, $sort_flags);
600  if (in_array('descending', $arguments)) {
601  $elements = array_reverse($elements);
602  }
603  $newValue = implode(',', $elements);
604  break;
605  case 'getEnv':
606  $environmentValue = getenv(trim($modifierArgumentAsString));
607  if ($environmentValue !== false) {
608  $newValue = $environmentValue;
609  }
610  break;
611  default:
612  if (isset(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName])) {
613  $hookMethod = ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName];
614  $params = ['currentValue' => $currentValue, 'functionArgument' => $modifierArgument];
615  $fakeThis = null;
616  $newValue = GeneralUtility::callUserFunction($hookMethod, $params, $fakeThis);
617  } else {
618  ‪self::getLogger()->warning('Missing function definition for ' . $modifierName . ' on TypoScript');
619  }
620  }
621  return $newValue;
622  }
623 
633  protected function ‪rollParseSub($string, array &‪$setup)
634  {
635  if ((string)$string === '') {
636  return '';
637  }
638 
639  [$key, $remainingKey] = $this->‪parseNextKeySegment($string);
640  $key .= '.';
641  if (!isset(‪$setup[$key])) {
642  ‪$setup[$key] = [];
643  }
644  $exitSig = $remainingKey === ''
645  ? $this->‪parseSub($setup[$key])
646  : $this->‪rollParseSub($remainingKey, ‪$setup[$key]);
647  return $exitSig ?: '';
648  }
649 
658  public function ‪getVal($string, ‪$setup)
659  {
660  if ((string)$string === '') {
661  return [];
662  }
663 
664  [$key, $remainingKey] = $this->‪parseNextKeySegment($string);
665  $subKey = $key . '.';
666  if ($remainingKey === '') {
667  $retArr = [];
668  if (isset(‪$setup[$key])) {
669  $retArr[0] = ‪$setup[$key];
670  }
671  if (isset(‪$setup[$subKey])) {
672  $retArr[1] = ‪$setup[$subKey];
673  }
674  return $retArr;
675  }
676  if (‪$setup[$subKey]) {
677  return $this->‪getVal($remainingKey, ‪$setup[$subKey]);
678  }
679 
680  return [];
681  }
682 
691  protected function ‪setVal($string, array &‪$setup, $value, $wipeOut = false)
692  {
693  if ((string)$string === '') {
694  return;
695  }
696 
697  [$key, $remainingKey] = $this->‪parseNextKeySegment($string);
698  $subKey = $key . '.';
699  if ($remainingKey === '') {
700  if ($value === 'UNSET') {
701  unset(‪$setup[$key]);
702  unset(‪$setup[$subKey]);
703  if ($this->regLinenumbers) {
704  ‪$setup[$key . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '>';
705  }
706  } else {
707  $lnRegisDone = 0;
708  if ($wipeOut) {
709  unset(‪$setup[$key]);
710  unset(‪$setup[$subKey]);
711  if ($this->regLinenumbers) {
712  ‪$setup[$key . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '<';
713  $lnRegisDone = 1;
714  }
715  }
716  if (isset($value[0])) {
717  ‪$setup[$key] = $value[0];
718  }
719  if (isset($value[1])) {
720  ‪$setup[$subKey] = $value[1];
721  }
722  if ($this->lastComment && $this->regComments) {
723  ‪$setup[$key . '..'] .= ‪$this->lastComment;
724  }
725  if ($this->regLinenumbers && !$lnRegisDone) {
726  ‪$setup[$key . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
727  }
728  }
729  } else {
730  if (!isset(‪$setup[$subKey])) {
731  ‪$setup[$subKey] = [];
732  }
733  $this->‪setVal($remainingKey, ‪$setup[$subKey], $value);
734  }
735  }
736 
748  protected function ‪parseNextKeySegment($key)
749  {
750  // if no dot is in the key, nothing to do
751  $dotPosition = strpos($key, '.');
752  if ($dotPosition === false) {
753  return [$key, ''];
754  }
755 
756  if (strpos($key, '\\') !== false) {
757  // backslashes are in the key, so we do further parsing
758 
759  while ($dotPosition !== false) {
760  if ($dotPosition > 0 && $key[$dotPosition - 1] !== '\\' || $dotPosition > 1 && $key[$dotPosition - 2] === '\\') {
761  break;
762  }
763  // escaped dot found, continue
764  $dotPosition = strpos($key, '.', $dotPosition + 1);
765  }
766 
767  if ($dotPosition === false) {
768  // no regular dot found
769  $keySegment = $key;
770  $remainingKey = '';
771  } else {
772  if ($dotPosition > 1 && $key[$dotPosition - 2] === '\\' && $key[$dotPosition - 1] === '\\') {
773  $keySegment = substr($key, 0, $dotPosition - 1);
774  } else {
775  $keySegment = substr($key, 0, $dotPosition);
776  }
777  $remainingKey = substr($key, $dotPosition + 1);
778  }
779 
780  // fix key segment by removing escape sequences
781  $keySegment = str_replace('\\.', '.', $keySegment);
782  } else {
783  // no backslash in the key, we're fine off
784  [$keySegment, $remainingKey] = explode('.', $key, 2);
785  }
786  return [$keySegment, $remainingKey];
787  }
788 
796  protected function ‪error($err, $num = 2)
797  {
798  $tt = $this->‪getTimeTracker();
799  if ($tt !== null) {
800  $tt->setTSlogMessage($err, $num);
801  }
802  $this->errors[] = [$err, $num, $this->rawP - 1, ‪$this->lineNumberOffset];
803  }
804 
816  public static function ‪checkIncludeLines($string, $cycle_counter = 1, $returnFiles = false, $parentFilenameOrPath = '')
817  {
818  $includedFiles = [];
819  if ($cycle_counter > 100) {
820  ‪self::getLogger()->warning('It appears like TypoScript code is looping over itself. Check your templates for "<INCLUDE_TYPOSCRIPT: ..." tags');
821  if ($returnFiles) {
822  return [
823  'typoscript' => '',
824  'files' => $includedFiles
825  ];
826  }
827  return '
828 ###
829 ### ERROR: Recursion!
830 ###
831 ';
832  }
833 
834  if ($string !== null) {
835  $string = ‪StringUtility::removeByteOrderMark($string);
836  }
837 
838  // Checking for @import syntax imported files
839  $string = ‪self::addImportsFromExternalFiles($string, $cycle_counter, $returnFiles, $includedFiles, $parentFilenameOrPath);
840 
841  // If no tags found, no need to do slower preg_split
842  if (strpos($string, '<INCLUDE_TYPOSCRIPT:') !== false) {
843  $splitRegEx = '/\r?\n\s*<INCLUDE_TYPOSCRIPT:\s*(?i)source\s*=\s*"((?i)file|dir):\s*([^"]*)"(.*)>[\ \t]*/';
844  $parts = preg_split($splitRegEx, LF . $string . LF, -1, PREG_SPLIT_DELIM_CAPTURE);
845  $parts = is_array($parts) ? $parts : [];
846 
847  // First text part goes through
848  $newString = ($parts[0] ?? '') . LF;
849  $partCount = count($parts);
850  for ($i = 1; $i + 3 < $partCount; $i += 4) {
851  // $parts[$i] contains 'FILE' or 'DIR'
852  // $parts[$i+1] contains relative file or directory path to be included
853  // $parts[$i+2] optional properties of the INCLUDE statement
854  // $parts[$i+3] next part of the typoscript string (part in between include-tags)
855  $includeType = $parts[$i];
856  $filename = $parts[$i + 1];
857  $originalFilename = $filename;
858  $optionalProperties = $parts[$i + 2];
859  $tsContentsTillNextInclude = $parts[$i + 3];
860 
861  // Check condition
862  $matches = preg_split('#(?i)condition\\s*=\\s*"((?:\\\\\\\\|\\\\"|[^\\"])*)"(\\s*|>)#', $optionalProperties, 2, PREG_SPLIT_DELIM_CAPTURE);
863  $matches = is_array($matches) ? $matches : [];
864 
865  // If there was a condition
866  if (count($matches) > 1) {
867  // Unescape the condition
868  $condition = trim(stripslashes($matches[1]));
869  // If necessary put condition in square brackets
870  if ($condition[0] !== '[') {
871  $condition = '[' . $condition . ']';
872  }
873 
875  $conditionMatcher = null;
876  if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_FE) {
877  $conditionMatcher = GeneralUtility::makeInstance(FrontendConditionMatcher::class);
878  } else {
879  $conditionMatcher = GeneralUtility::makeInstance(BackendConditionMatcher::class);
880  }
881 
882  // If it didn't match then proceed to the next include, but prepend next normal (not file) part to output string
883  if (!$conditionMatcher->match($condition)) {
884  $newString .= $tsContentsTillNextInclude . LF;
885  continue;
886  }
887  }
888 
889  // Resolve a possible relative paths if a parent file is given
890  if ($parentFilenameOrPath !== '' && $filename[0] === '.') {
891  $filename = ‪PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $filename);
892  }
893 
894  // There must be a line-break char after - not sure why this check is necessary, kept it for being 100% backwards compatible
895  // An empty string is also ok (means that the next line is also a valid include_typoscript tag)
896  if (!preg_match('/(^\\s*\\r?\\n|^$)/', $tsContentsTillNextInclude)) {
897  $newString .= ‪self::typoscriptIncludeError('Invalid characters after <INCLUDE_TYPOSCRIPT: source="' . $includeType . ':' . $filename . '">-tag (rest of line must be empty).');
898  } elseif (strpos('..', $filename) !== false) {
899  $newString .= ‪self::typoscriptIncludeError('Invalid filepath "' . $filename . '" (containing "..").');
900  } else {
901  switch (strtolower($includeType)) {
902  case 'file':
903  ‪self::includeFile($originalFilename, $cycle_counter, $returnFiles, $newString, $includedFiles, $optionalProperties, $parentFilenameOrPath);
904  break;
905  case 'dir':
906  ‪self::includeDirectory($originalFilename, $cycle_counter, $returnFiles, $newString, $includedFiles, $optionalProperties, $parentFilenameOrPath);
907  break;
908  default:
909  $newString .= ‪self::typoscriptIncludeError('No valid option for INCLUDE_TYPOSCRIPT source property (valid options are FILE or DIR)');
910  }
911  }
912  // Prepend next normal (not file) part to output string
913  $newString .= $tsContentsTillNextInclude . LF;
914 
915  // load default TypoScript for content rendering templates like
916  // fluid_styled_content if those have been included through f.e.
917  // <INCLUDE_TYPOSCRIPT: source="FILE:EXT:fluid_styled_content/Configuration/TypoScript/setup.typoscript">
918  if (strpos(strtolower($filename), 'ext:') === 0) {
919  $filePointerPathParts = explode('/', substr($filename, 4));
920 
921  // remove file part, determine whether to load setup or constants
922  [$includeType, ] = explode('.', (string)array_pop($filePointerPathParts));
923 
924  if (in_array($includeType, ['setup', 'constants'])) {
925  // adapt extension key to required format (no underscores)
926  $filePointerPathParts[0] = str_replace('_', '', $filePointerPathParts[0]);
927 
928  // load default TypoScript
929  $defaultTypoScriptKey = implode('/', $filePointerPathParts) . '/';
930  if (in_array($defaultTypoScriptKey, ‪$GLOBALS['TYPO3_CONF_VARS']['FE']['contentRenderingTemplates'], true)) {
931  $newString .= ‪$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $includeType . '.']['defaultContentRendering'];
932  }
933  }
934  }
935  }
936  // Add a line break before and after the included code in order to make sure that the parser always has a LF.
937  $string = LF . trim($newString) . LF;
938  }
939  // When all included files should get returned, simply return a compound array containing
940  // the TypoScript with all "includes" processed and the files which got included
941  if ($returnFiles) {
942  return [
943  'typoscript' => $string,
944  'files' => $includedFiles
945  ];
946  }
947  return $string;
948  }
949 
960  protected static function ‪addImportsFromExternalFiles($typoScript, $cycleCounter, $returnFiles, &$includedFiles, &$parentFilenameOrPath)
961  {
962  // Check for new syntax "@import 'EXT:bennilove/Configuration/TypoScript/*'"
963  if (strpos($typoScript, '@import \'') !== false || strpos($typoScript, '@import "') !== false) {
964  $splitRegEx = '/\r?\n\s*@import\s[\'"]([^\'"]*)[\'"][\ \t]?/';
965  $parts = preg_split($splitRegEx, LF . $typoScript . LF, -1, PREG_SPLIT_DELIM_CAPTURE);
966  $parts = is_array($parts) ? $parts : [];
967  // First text part goes through
968  $newString = $parts[0] . LF;
969  $partCount = count($parts);
970  for ($i = 1; $i + 2 <= $partCount; $i += 2) {
971  $filename = $parts[$i];
972  $tsContentsTillNextInclude = $parts[$i + 1];
973  // Resolve a possible relative paths if a parent file is given
974  if ($parentFilenameOrPath !== '' && $filename[0] === '.') {
975  $filename = ‪PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $filename);
976  }
977  $newString .= ‪self::importExternalTypoScriptFile($filename, $cycleCounter, $returnFiles, $includedFiles);
978  // Prepend next normal (not file) part to output string
979  $newString .= $tsContentsTillNextInclude;
980  }
981  // Add a line break before and after the included code in order to make sure that the parser always has a LF.
982  $typoScript = LF . trim($newString) . LF;
983  }
984  return $typoScript;
985  }
986 
997  protected static function ‪importExternalTypoScriptFile($filename, $cycleCounter, $returnFiles, array &$includedFiles)
998  {
999  if (strpos('..', $filename) !== false) {
1000  return ‪self::typoscriptIncludeError('Invalid filepath "' . $filename . '" (containing "..").');
1001  }
1002 
1003  $content = '';
1004  $absoluteFileName = GeneralUtility::getFileAbsFileName($filename);
1005  if ((string)$absoluteFileName === '') {
1006  return ‪self::typoscriptIncludeError('Illegal filepath "' . $filename . '".');
1007  }
1008 
1009  ‪$finder = new Finder();
1010  ‪$finder
1011  // no recursive mode on purpose
1012  ->depth(0)
1013  // no directories should be fetched
1014  ->files()
1015  ->sortByName();
1016 
1017  // Search all files in the folder
1018  if (is_dir($absoluteFileName)) {
1019  ‪$finder
1020  ->in($absoluteFileName)
1021  ->name('*.typoscript');
1022  // Used for the TypoScript comments
1023  $readableFilePrefix = $filename;
1024  } else {
1025  try {
1026  // Apparently this is not a folder, so the restriction
1027  // is the folder so we restrict into this folder
1028  ‪$finder->in(‪PathUtility::dirname($absoluteFileName));
1029  if (!is_file($absoluteFileName)
1030  && strpos(‪PathUtility::basename($absoluteFileName), '*') === false
1031  && substr(‪PathUtility::basename($absoluteFileName), -11) !== '.typoscript') {
1032  $absoluteFileName .= '*.typoscript';
1033  }
1034  ‪$finder->name(‪PathUtility::basename($absoluteFileName));
1035  $readableFilePrefix = ‪PathUtility::dirname($filename);
1036  } catch (\InvalidArgumentException $e) {
1037  return ‪self::typoscriptIncludeError($e->getMessage());
1038  }
1039  }
1040 
1041  foreach (‪$finder as $fileObject) {
1042  // Clean filename output for comments
1043  $readableFileName = rtrim($readableFilePrefix, '/') . '/' . $fileObject->getFilename();
1044  $content .= LF . '### @import \'' . $readableFileName . '\' begin ###' . LF;
1045  // Check for allowed files
1046  if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($fileObject->getFilename())) {
1047  $content .= self::typoscriptIncludeError('File "' . $readableFileName . '" was not included since it is not allowed due to fileDenyPattern.');
1048  } else {
1049  $includedFiles[] = $fileObject->getPathname();
1050  // check for includes in included text
1051  $included_text = self::checkIncludeLines($fileObject->getContents(), $cycleCounter++, $returnFiles, $absoluteFileName);
1052  // If the method also has to return all included files, merge currently included
1053  // files with files included by recursively calling itself
1054  if ($returnFiles && is_array($included_text)) {
1055  $includedFiles = array_merge($includedFiles, $included_text['files']);
1056  $included_text = $included_text['typoscript'];
1057  }
1058  $content .= $included_text . LF;
1059  }
1060  $content .= '### @import \'' . $readableFileName . '\' end ###' . LF . LF;
1061 
1062  // load default TypoScript for content rendering templates like
1063  // fluid_styled_content if those have been included through e.g.
1064  // @import "fluid_styled_content/Configuration/TypoScript/setup.typoscript"
1065  if (strpos(strtoupper($filename), 'EXT:') === 0) {
1066  $filePointerPathParts = explode('/', substr($filename, 4));
1067  // remove file part, determine whether to load setup or constants
1068  [$includeType] = explode('.', (string)array_pop($filePointerPathParts));
1069 
1070  if (in_array($includeType, ['setup', 'constants'], true)) {
1071  // adapt extension key to required format (no underscores)
1072  $filePointerPathParts[0] = str_replace('_', '', $filePointerPathParts[0]);
1073 
1074  // load default TypoScript
1075  $defaultTypoScriptKey = implode('/', $filePointerPathParts) . '/';
1076  if (in_array($defaultTypoScriptKey, $GLOBALS['TYPO3_CONF_VARS']['FE']['contentRenderingTemplates'], true)) {
1077  $content .= $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $includeType . '.']['defaultContentRendering'];
1078  }
1079  }
1080  }
1081  }
1082 
1083  if (empty($content)) {
1084  return self::typoscriptIncludeError('No file or folder found for importing TypoScript on "' . $filename . '".');
1085  }
1086  return $content;
1087  }
1088 
1103  public static function includeFile($filename, $cycle_counter = 1, $returnFiles = false, &$newString = '', array &$includedFiles = [], $optionalProperties = '', $parentFilenameOrPath = '')
1104  {
1105  // Resolve a possible relative paths if a parent file is given
1106  if ($parentFilenameOrPath !== '' && $filename[0] === '.') {
1107  $absfilename = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $filename);
1108  } else {
1109  $absfilename = $filename;
1110  }
1111  $absfilename = GeneralUtility::getFileAbsFileName($absfilename);
1112 
1113  $newString .= LF . '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $filename . '"' . $optionalProperties . '> BEGIN:' . LF;
1114  if ((string)$filename !== '') {
1115  // Must exist and must not contain '..' and must be relative
1116  // Check for allowed files
1117  if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($absfilename)) {
1118  $newString .= self::typoscriptIncludeError('File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern.');
1119  } else {
1120  $fileExists = false;
1121  if (@file_exists($absfilename)) {
1122  $fileExists = true;
1123  }
1124 
1125  if ($fileExists) {
1126  $includedFiles[] = $absfilename;
1127  // check for includes in included text
1128  $included_text = self::checkIncludeLines((string)file_get_contents($absfilename), $cycle_counter + 1, $returnFiles, $absfilename);
1129  // If the method also has to return all included files, merge currently included
1130  // files with files included by recursively calling itself
1131  if ($returnFiles && is_array($included_text)) {
1132  $includedFiles = array_merge($includedFiles, $included_text['files']);
1133  $included_text = $included_text['typoscript'];
1134  }
1135  $newString .= $included_text . LF;
1136  } else {
1137  $newString .= self::typoscriptIncludeError('File "' . $filename . '" was not found.');
1138  }
1139  }
1140  }
1141  $newString .= '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $filename . '"' . $optionalProperties . '> END:' . LF . LF;
1142  }
1143 
1159  protected static function includeDirectory($dirPath, $cycle_counter = 1, $returnFiles = false, &$newString = '', array &$includedFiles = [], $optionalProperties = '', $parentFilenameOrPath = '')
1160  {
1161  // Extract the value of the property extensions="..."
1162  $matches = preg_split('#(?i)extensions\s*=\s*"([^"]*)"(\s*|>)#', $optionalProperties, 2, PREG_SPLIT_DELIM_CAPTURE);
1163  $matches = is_array($matches) ? $matches : [];
1164  if (count($matches) > 1) {
1165  $includedFileExtensions = $matches[1];
1166  } else {
1167  $includedFileExtensions = '';
1168  }
1169 
1170  // Resolve a possible relative paths if a parent file is given
1171  if ($parentFilenameOrPath !== '' && $dirPath[0] === '.') {
1172  $resolvedDirPath = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $dirPath);
1173  } else {
1174  $resolvedDirPath = $dirPath;
1175  }
1176  $absDirPath = GeneralUtility::getFileAbsFileName($resolvedDirPath);
1177  if ($absDirPath) {
1178  $absDirPath = rtrim($absDirPath, '/') . '/';
1179  $newString .= LF . '### <INCLUDE_TYPOSCRIPT: source="DIR:' . $dirPath . '"' . $optionalProperties . '> BEGIN:' . LF;
1180  // Get alphabetically sorted file index in array
1181  $fileIndex = GeneralUtility::getAllFilesAndFoldersInPath([], $absDirPath, $includedFileExtensions);
1182  // Prepend file contents to $newString
1183  $prefixLength = strlen(Environment::getPublicPath() . '/');
1184  foreach ($fileIndex as $absFileRef) {
1185  $relFileRef = substr($absFileRef, $prefixLength);
1186  self::includeFile($relFileRef, $cycle_counter, $returnFiles, $newString, $includedFiles, '', $absDirPath);
1187  }
1188  $newString .= '### <INCLUDE_TYPOSCRIPT: source="DIR:' . $dirPath . '"' . $optionalProperties . '> END:' . LF . LF;
1189  } else {
1190  $newString .= self::typoscriptIncludeError('The path "' . $resolvedDirPath . '" is invalid.');
1191  }
1192  }
1193 
1202  protected static function typoscriptIncludeError($error)
1203  {
1204  self::getLogger()->warning($error);
1205  return "\n###\n### ERROR: " . $error . "\n###\n\n";
1206  }
1207 
1214  public static function checkIncludeLines_array(array $array)
1215  {
1216  foreach ($array as $k => $v) {
1217  $array[$k] = self::checkIncludeLines($array[$k]);
1218  }
1219  return $array;
1220  }
1221 
1236  public static function extractIncludes($string, $cycle_counter = 1, array $extractedFileNames = [], $parentFilenameOrPath = '')
1237  {
1238  if ($cycle_counter > 10) {
1239  self::getLogger()->warning('It appears like TypoScript code is looping over itself. Check your templates for "<INCLUDE_TYPOSCRIPT: ..." tags');
1240  return '
1241 ###
1242 ### ERROR: Recursion!
1243 ###
1244 ';
1245  }
1246  $expectedEndTag = '';
1247  $fileContent = [];
1248  $restContent = [];
1249  $fileName = null;
1250  $inIncludePart = false;
1251  $lines = preg_split("/\r\n|\n|\r/", $string);
1252  $skipNextLineIfEmpty = false;
1253  $openingCommentedIncludeStatement = null;
1254  $optionalProperties = '';
1255  foreach ($lines as $line) {
1256  // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts
1257  // an additional empty line, remove this again
1258  if ($skipNextLineIfEmpty) {
1259  if (trim($line) === '') {
1260  continue;
1261  }
1262  $skipNextLineIfEmpty = false;
1263  }
1264 
1265  // Outside commented include statements
1266  if (!$inIncludePart) {
1267  // Search for beginning commented include statements
1268  if (preg_match('/###\\s*<INCLUDE_TYPOSCRIPT:\\s*source\\s*=\\s*"\\s*((?i)file|dir)\\s*:\\s*([^"]*)"(.*)>\\s*BEGIN/i', $line, $matches)) {
1269  // Found a commented include statement
1270 
1271  // Save this line in case there is no ending tag
1272  $openingCommentedIncludeStatement = trim($line);
1273  $openingCommentedIncludeStatement = preg_replace('/\\s*### Warning: .*###\\s*/', '', $openingCommentedIncludeStatement);
1274 
1275  // type of match: FILE or DIR
1276  $inIncludePart = strtoupper($matches[1]);
1277  $fileName = $matches[2];
1278  $optionalProperties = $matches[3];
1279 
1280  $expectedEndTag = '### <INCLUDE_TYPOSCRIPT: source="' . $inIncludePart . ':' . $fileName . '"' . $optionalProperties . '> END';
1281  // Strip all whitespace characters to make comparison safer
1282  $expectedEndTag = strtolower(preg_replace('/\s/', '', $expectedEndTag) ?? '');
1283  } else {
1284  // If this is not a beginning commented include statement this line goes into the rest content
1285  $restContent[] = $line;
1286  }
1287  } else {
1288  // Inside commented include statements
1289  // Search for the matching ending commented include statement
1290  $strippedLine = preg_replace('/\s/', '', $line);
1291  if (stripos($strippedLine, $expectedEndTag) !== false) {
1292  // Found the matching ending include statement
1293  $fileContentString = implode(PHP_EOL, $fileContent);
1294 
1295  // Write the content to the file
1296 
1297  // Resolve a possible relative paths if a parent file is given
1298  if ($parentFilenameOrPath !== '' && $fileName[0] === '.') {
1299  $realFileName = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $fileName);
1300  } else {
1301  $realFileName = $fileName;
1302  }
1303  $realFileName = GeneralUtility::getFileAbsFileName($realFileName);
1304 
1305  if ($inIncludePart === 'FILE') {
1306  // Some file checks
1307  if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($realFileName)) {
1308  throw new \UnexpectedValueException(sprintf('File "%s" was not included since it is not allowed due to fileDenyPattern.', $fileName), 1382651858);
1309  }
1310  if (empty($realFileName)) {
1311  throw new \UnexpectedValueException(sprintf('"%s" is not a valid file location.', $fileName), 1294586441);
1312  }
1313  if (!is_writable($realFileName)) {
1314  throw new \RuntimeException(sprintf('"%s" is not writable.', $fileName), 1294586442);
1315  }
1316  if (in_array($realFileName, $extractedFileNames)) {
1317  throw new \RuntimeException(sprintf('Recursive/multiple inclusion of file "%s"', $realFileName), 1294586443);
1318  }
1319  $extractedFileNames[] = $realFileName;
1320 
1321  // Recursive call to detected nested commented include statements
1322  $fileContentString = self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName);
1323 
1324  // Write the content to the file
1325  if (!GeneralUtility::writeFile($realFileName, $fileContentString)) {
1326  throw new \RuntimeException(sprintf('Could not write file "%s"', $realFileName), 1294586444);
1327  }
1328  // Insert reference to the file in the rest content
1329  $restContent[] = '<INCLUDE_TYPOSCRIPT: source="FILE:' . $fileName . '"' . $optionalProperties . '>';
1330  } else {
1331  // must be DIR
1332 
1333  // Some file checks
1334  if (empty($realFileName)) {
1335  throw new \UnexpectedValueException(sprintf('"%s" is not a valid location.', $fileName), 1366493602);
1336  }
1337  if (!is_dir($realFileName)) {
1338  throw new \RuntimeException(sprintf('"%s" is not a directory.', $fileName), 1366493603);
1339  }
1340  if (in_array($realFileName, $extractedFileNames)) {
1341  throw new \RuntimeException(sprintf('Recursive/multiple inclusion of directory "%s"', $realFileName), 1366493604);
1342  }
1343  $extractedFileNames[] = $realFileName;
1344 
1345  // Recursive call to detected nested commented include statements
1346  self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName);
1347 
1348  // just drop content between tags since it should usually just contain individual files from that dir
1349 
1350  // Insert reference to the dir in the rest content
1351  $restContent[] = '<INCLUDE_TYPOSCRIPT: source="DIR:' . $fileName . '"' . $optionalProperties . '>';
1352  }
1353 
1354  // Reset variables (preparing for the next commented include statement)
1355  $fileContent = [];
1356  $fileName = null;
1357  $inIncludePart = false;
1358  $openingCommentedIncludeStatement = null;
1359  // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts
1360  // an additional empty line, remove this again
1361  $skipNextLineIfEmpty = true;
1362  } else {
1363  // If this is not an ending commented include statement this line goes into the file content
1364  $fileContent[] = $line;
1365  }
1366  }
1367  }
1368  // If we're still inside commented include ‪statements copy the lines back to the rest content
1369  if ($inIncludePart) {
1370  $restContent[] = $openingCommentedIncludeStatement . ' ### Warning: Corresponding end line missing! ###';
1371  $restContent = array_merge($restContent, $fileContent);
1372  }
1373  $restContentString = implode(PHP_EOL, $restContent);
1374  return $restContentString;
1375  }
1376 
1383  public static function ‪extractIncludes_array(array $array)
1384  {
1385  foreach ($array as $k => $v) {
1386  $array[$k] = ‪self::extractIncludes($array[$k]);
1387  }
1388  return $array;
1389  }
1390 
1391  /**********************************
1392  *
1393  * Syntax highlighting
1394  *
1395  *********************************/
1405  public function ‪doSyntaxHighlight($string, $lineNum = '', $highlightBlockMode = false)
1406  {
1407  $this->syntaxHighLight = true;
1408  $this->highLightData = [];
1409  $this->errors = [];
1410  // This is done in order to prevent empty <span>..</span> sections around CR content. Should not do anything but help lessen the amount of HTML code.
1411  $string = str_replace(CR, '', $string);
1412  $this->‪parse($string);
1413  return $this->‪syntaxHighlight_print($lineNum, $highlightBlockMode);
1414  }
1415 
1424  protected function ‪regHighLight($code, $pointer, $strlen = -1)
1425  {
1426  if ($strlen === -1) {
1427  $this->highLightData[$pointer] = [[$code, 0]];
1428  } else {
1429  $this->highLightData[$pointer][] = [$code, $strlen];
1430  }
1431  $this->highLightData_bracelevel[$pointer] = ‪$this->inBrace;
1432  }
1433 
1442  protected function ‪syntaxHighlight_print($lineNumDat, $highlightBlockMode)
1443  {
1444  // Registers all error messages in relation to their linenumber
1445  $errA = [];
1446  foreach ($this->errors as $err) {
1447  $errA[$err[2]][] = $err[0];
1448  }
1449  // Generates the syntax highlighted output:
1450  $lines = [];
1451  foreach ($this->raw as ‪$rawP => $value) {
1452  $start = 0;
1453  $strlen = strlen($value);
1454  $lineC = '';
1455  if (is_array($this->highLightData[‪$rawP])) {
1456  foreach ($this->highLightData[‪$rawP] as $set) {
1457  $len = $strlen - $start - (int)$set[1];
1458  if ($len > 0) {
1459  $part = substr($value, $start, $len);
1460  $start += $len;
1461  $st = $this->highLightStyles[isset($this->highLightStyles[$set[0]]) ? $set[0] : 'default'];
1462  if (!$highlightBlockMode || $set[0] !== 'prespace') {
1463  $lineC .= $st[0] . htmlspecialchars($part) . $st[1];
1464  }
1465  } elseif ($len < 0) {
1466  ‪debug([$len, $value, ‪$rawP]);
1467  }
1468  }
1469  } else {
1470  ‪debug([$value]);
1471  }
1472  if (strlen($value) > $start) {
1473  $lineC .= $this->highLightStyles['ignored'][0] . htmlspecialchars(substr($value, $start)) . $this->highLightStyles['ignored'][1];
1474  }
1475  if ($errA[‪$rawP]) {
1476  $lineC .= $this->highLightStyles['error'][0] . '<strong> - ERROR:</strong> ' . htmlspecialchars(implode(';', $errA[‪$rawP])) . $this->highLightStyles['error'][1];
1477  }
1478  if ($highlightBlockMode && $this->highLightData_bracelevel[‪$rawP]) {
1479  $lineC = str_pad('', $this->highLightData_bracelevel[‪$rawP] * 2, ' ', STR_PAD_LEFT) . '<span style="' . $this->highLightBlockStyles . ($this->highLightBlockStyles_basecolor ? 'background-color: ' . $this->‪modifyHTMLColorAll($this->highLightBlockStyles_basecolor, -(int)($this->highLightData_bracelevel[‪$rawP] * 16)) : '') . '">' . ($lineC !== '' ? $lineC : '&nbsp;') . '</span>';
1480  }
1481  if (is_array($lineNumDat)) {
1482  $lineNum = ‪$rawP + $lineNumDat[0];
1483  if ($this->parentObject instanceof ExtendedTemplateService) {
1484  $lineNum = $this->parentObject->ext_lnBreakPointWrap($lineNum, $lineNum);
1485  }
1486  $lineC = $this->highLightStyles['linenum'][0] . str_pad($lineNum, 4, ' ', STR_PAD_LEFT) . ':' . $this->highLightStyles['linenum'][1] . ' ' . $lineC;
1487  }
1488  $lines[] = $lineC;
1489  }
1490  return '<pre class="ts-hl">' . implode(LF, $lines) . '</pre>';
1491  }
1492 
1496  protected function ‪getTimeTracker()
1497  {
1498  return GeneralUtility::makeInstance(TimeTracker::class);
1499  }
1500 
1511  protected function ‪modifyHTMLColor($color, $R, $G, $B)
1512  {
1513  // This takes a hex-color (# included!) and adds $R, $G and $B to the HTML-color (format: #xxxxxx) and returns the new color
1514  $nR = ‪MathUtility::forceIntegerInRange((int)hexdec(substr($color, 1, 2)) + $R, 0, 255);
1515  $nG = ‪MathUtility::forceIntegerInRange((int)hexdec(substr($color, 3, 2)) + $G, 0, 255);
1516  $nB = ‪MathUtility::forceIntegerInRange((int)hexdec(substr($color, 5, 2)) + $B, 0, 255);
1517  return '#' . substr('0' . dechex($nR), -2) . substr('0' . dechex($nG), -2) . substr('0' . dechex($nB), -2);
1518  }
1519 
1528  protected function ‪modifyHTMLColorAll($color, $all)
1529  {
1530  return $this->‪modifyHTMLColor($color, $all, $all, $all);
1531  }
1532 
1541  protected static function ‪getLogger()
1542  {
1543  return GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
1544  }
1545 }
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\parseSub
‪string parseSub(array &$setup)
Definition: TypoScriptParser.php:286
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:24
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\modifyHTMLColorAll
‪string modifyHTMLColorAll($color, $all)
Definition: TypoScriptParser.php:1504
‪$finder
‪if(PHP_SAPI !=='cli') $finder
Definition: header-comment.php:22
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\regHighLight
‪regHighLight($code, $pointer, $strlen=-1)
Definition: TypoScriptParser.php:1400
‪TYPO3\CMS\Core\Utility\PathUtility\dirname
‪static string dirname($path)
Definition: PathUtility.php:186
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$commentSet
‪bool $commentSet
Definition: TypoScriptParser.php:66
‪TYPO3\CMS\Core\Resource\Security\FileNameValidator
Definition: FileNameValidator.php:25
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
Definition: TypoScriptParser.php:37
‪TYPO3\CMS\Core\TypoScript\Parser
Definition: ConstantConfigurationParser.php:18
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$sections
‪array $sections
Definition: TypoScriptParser.php:103
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\importExternalTypoScriptFile
‪static string importExternalTypoScriptFile($filename, $cycleCounter, $returnFiles, array &$includedFiles)
Definition: TypoScriptParser.php:973
‪TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher
Definition: ConditionMatcher.php:30
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:32
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$highLightData
‪array $highLightData
Definition: TypoScriptParser.php:121
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$multiLineObject
‪string $multiLineObject
Definition: TypoScriptParser.php:78
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$highLightData_bracelevel
‪array $highLightData_bracelevel
Definition: TypoScriptParser.php:127
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$sectionsMatch
‪array $sectionsMatch
Definition: TypoScriptParser.php:109
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\typoscriptIncludeError
‪static string typoscriptIncludeError($error)
Definition: TypoScriptParser.php:1178
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$lastConditionTrue
‪bool $lastConditionTrue
Definition: TypoScriptParser.php:97
‪TYPO3\CMS\Core\TypoScript\ExtendedTemplateService
Definition: ExtendedTemplateService.php:43
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\executeValueModifier
‪string null executeValueModifier($modifierName, $modifierArgument=null, $currentValue=null)
Definition: TypoScriptParser.php:519
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$breakPointLN
‪int $breakPointLN
Definition: TypoScriptParser.php:157
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$lineNumberOffset
‪int $lineNumberOffset
Definition: TypoScriptParser.php:151
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\doSyntaxHighlight
‪string doSyntaxHighlight($string, $lineNum='', $highlightBlockMode=false)
Definition: TypoScriptParser.php:1381
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\includeDirectory
‪static includeDirectory($dirPath, $cycle_counter=1, $returnFiles=false, &$newString='', array &$includedFiles=[], $optionalProperties='', $parentFilenameOrPath='')
Definition: TypoScriptParser.php:1135
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static string basename($path)
Definition: PathUtility.php:165
‪TYPO3\CMS\Core\Utility\StringUtility\removeByteOrderMark
‪static string removeByteOrderMark(string $input)
Definition: StringUtility.php:118
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\getVal
‪array getVal($string, $setup)
Definition: TypoScriptParser.php:634
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$raw
‪string[] $raw
Definition: TypoScriptParser.php:48
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\modifyHTMLColor
‪string modifyHTMLColor($color, $R, $G, $B)
Definition: TypoScriptParser.php:1487
‪TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractConditionMatcher
Definition: AbstractConditionMatcher.php:34
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$highLightBlockStyles_basecolor
‪string $highLightBlockStyles_basecolor
Definition: TypoScriptParser.php:201
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\rollParseSub
‪string rollParseSub($string, array &$setup)
Definition: TypoScriptParser.php:609
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$rawP
‪int $rawP
Definition: TypoScriptParser.php:54
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$syntaxHighLight
‪bool $syntaxHighLight
Definition: TypoScriptParser.php:115
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$setup
‪array $setup
Definition: TypoScriptParser.php:42
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$errors
‪array $errors
Definition: TypoScriptParser.php:145
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\checkIncludeLines
‪static string array checkIncludeLines($string, $cycle_counter=1, $returnFiles=false, $parentFilenameOrPath='')
Definition: TypoScriptParser.php:792
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$parentObject
‪TYPO3 CMS Core TypoScript ExtendedTemplateService $parentObject
Definition: TypoScriptParser.php:205
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$regLinenumbers
‪bool $regLinenumbers
Definition: TypoScriptParser.php:139
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$highLightStyles
‪array $highLightStyles
Definition: TypoScriptParser.php:161
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\extractIncludes
‪static string extractIncludes($string, $cycle_counter=1, array $extractedFileNames=[], $parentFilenameOrPath='')
Definition: TypoScriptParser.php:1212
‪TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher
Definition: ConditionMatcher.php:29
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪debug
‪debug($variable='', $title=null, $group=null)
Definition: GlobalDebugFunctions.php:19
‪statements
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$multiLineValue
‪array $multiLineValue
Definition: TypoScriptParser.php:84
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$lastComment
‪string $lastComment
Definition: TypoScriptParser.php:60
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\syntaxHighlight_print
‪string syntaxHighlight_print($lineNumDat, $highlightBlockMode)
Definition: TypoScriptParser.php:1418
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Log\LogManager
Definition: LogManager.php:30
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:40
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\parseNextKeySegment
‪array parseNextKeySegment($key)
Definition: TypoScriptParser.php:724
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\parse
‪parse($string, $matchObj='')
Definition: TypoScriptParser.php:213
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\extractIncludes_array
‪static array extractIncludes_array(array $array)
Definition: TypoScriptParser.php:1359
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$multiLineEnabled
‪bool $multiLineEnabled
Definition: TypoScriptParser.php:72
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$regComments
‪bool $regComments
Definition: TypoScriptParser.php:133
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\getLogger
‪static LoggerInterface getLogger()
Definition: TypoScriptParser.php:1517
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:22
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\error
‪error($err, $num=2)
Definition: TypoScriptParser.php:772
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\nextDivider
‪string nextDivider()
Definition: TypoScriptParser.php:268
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\getTimeTracker
‪TimeTracker getTimeTracker()
Definition: TypoScriptParser.php:1472
‪TYPO3\CMS\Core\TimeTracker\TimeTracker
Definition: TimeTracker.php:30
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\setVal
‪setVal($string, array &$setup, $value, $wipeOut=false)
Definition: TypoScriptParser.php:667
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\includeFile
‪static includeFile($filename, $cycle_counter=1, $returnFiles=false, &$newString='', array &$includedFiles=[], $optionalProperties='', $parentFilenameOrPath='')
Definition: TypoScriptParser.php:1079
‪TYPO3\CMS\Core\Utility\PathUtility\getAbsolutePathOfRelativeReferencedFileOrPath
‪static string getAbsolutePathOfRelativeReferencedFileOrPath($baseFilenameOrPath, $includeFileName)
Definition: PathUtility.php:256
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$highLightBlockStyles
‪string $highLightBlockStyles
Definition: TypoScriptParser.php:195
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\addImportsFromExternalFiles
‪static string addImportsFromExternalFiles($typoScript, $cycleCounter, $returnFiles, &$includedFiles, &$parentFilenameOrPath)
Definition: TypoScriptParser.php:936
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$inBrace
‪int $inBrace
Definition: TypoScriptParser.php:90