‪TYPO3CMS  9.5
TypoScriptParser.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 
17 use Psr\Log\LoggerInterface;
18 use Symfony\Component\Finder\Finder;
32 
37 {
40 
41  protected ‪$deprecatedPublicProperties = [
42  'raw' => 'Using $raw of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
43  'rawP' => 'Using $rawP of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
44  'lastComment' => 'Using $lastComment of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
45  'commentSet' => 'Using $commentSet of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
46  'multiLineEnabled' => 'Using $multiLineEnabled of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
47  'multiLineObject' => 'Using $multiLineObject of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
48  'multiLineValue' => 'Using $multiLineValue of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
49  'inBrace' => 'Using $inBrace of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
50  'lastConditionTrue' => 'Using $lastConditionTrue of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
51  'syntaxHighLight' => 'Using $syntaxHighLight of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
52  'highLightData' => 'Using $highLightData of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
53  'highLightData_bracelevel' => 'Using $highLightData_bracelevel of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
54  'highLightStyles' => 'Using $highLightStyles of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
55  'highLightBlockStyles' => 'Using $highLightBlockStyles of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
56  'highLightBlockStyles_basecolor' => 'Using $highLightBlockStyles_basecolor of class TypoScriptParser from the outside is discouraged, as this variable is only used for internal storage.',
57  ];
58 
59  protected ‪$deprecatedPublicMethods = [
60  'nextDivider' => 'Using nextDivider() of class TypoScriptParser from the outside is discouraged, as this method is only meant to be used internally.',
61  'parseSub' => 'Using parseSub() of class TypoScriptParser from the outside is discouraged, as this method is only meant to be used internally.',
62  'rollParseSub' => 'Using rollParseSub() of class TypoScriptParser from the outside is discouraged, as this method is only meant to be used internally.',
63  'setVal' => 'Using setVal() of class TypoScriptParser from the outside is discouraged, as this method is only meant to be used internally.',
64  'error' => 'Using error() of class TypoScriptParser from the outside is discouraged, as this method is only meant to be used internally.',
65  'regHighLight' => 'Using regHighLight() of class TypoScriptParser from the outside is discouraged, as this method is only meant to be used internally.',
66  'syntaxHighlight_print' => 'Using syntaxHighlight_print() of class TypoScriptParser from the outside is discouraged, as this method is only meant to be used internally.',
67  ];
68 
74  public ‪$setup = [];
75 
81  protected ‪$raw;
82 
88  protected ‪$rawP;
89 
95  protected ‪$lastComment = '';
96 
102  protected ‪$commentSet = false;
103 
109  protected ‪$multiLineEnabled = false;
110 
116  protected ‪$multiLineObject = '';
117 
123  protected ‪$multiLineValue = [];
124 
130  protected ‪$inBrace = 0;
131 
138  protected ‪$lastConditionTrue = true;
139 
145  public ‪$sections = [];
146 
152  public ‪$sectionsMatch = [];
153 
159  protected ‪$syntaxHighLight = false;
160 
166  protected ‪$highLightData = [];
167 
173  protected ‪$highLightData_bracelevel = [];
174 
180  public ‪$regComments = false;
181 
187  public ‪$regLinenumbers = false;
188 
194  public ‪$errors = [];
195 
201  public ‪$lineNumberOffset = 0;
202 
208  public ‪$breakPointLN = 0;
209 
213  protected ‪$highLightStyles = [
214  'prespace' => ['<span class="ts-prespace">', '</span>'],
215  // Space before any content on a line
216  'objstr_postspace' => ['<span class="ts-objstr_postspace">', '</span>'],
217  // Space after the object string on a line
218  'operator_postspace' => ['<span class="ts-operator_postspace">', '</span>'],
219  // Space after the operator on a line
220  'operator' => ['<span class="ts-operator">', '</span>'],
221  // The operator char
222  'value' => ['<span class="ts-value">', '</span>'],
223  // The value of a line
224  'objstr' => ['<span class="ts-objstr">', '</span>'],
225  // The object string of a line
226  'value_copy' => ['<span class="ts-value_copy">', '</span>'],
227  // The value when the copy syntax (<) is used; that means the object reference
228  'value_unset' => ['<span class="ts-value_unset">', '</span>'],
229  // The value when an object is unset. Should not exist.
230  'ignored' => ['<span class="ts-ignored">', '</span>'],
231  // The "rest" of a line which will be ignored.
232  'default' => ['<span class="ts-default">', '</span>'],
233  // The default style if none other is applied.
234  'comment' => ['<span class="ts-comment">', '</span>'],
235  // Comment lines
236  'condition' => ['<span class="ts-condition">', '</span>'],
237  // Conditions
238  'error' => ['<span class="ts-error">', '</span>'],
239  // Error messages
240  'linenum' => ['<span class="ts-linenum">', '</span>']
241  ];
242 
248  protected ‪$highLightBlockStyles = '';
249 
255  protected ‪$highLightBlockStyles_basecolor = '#cccccc';
256 
260  public ‪$parentObject;
261 
268  public function ‪parse($string, $matchObj = '')
269  {
270  $this->raw = explode(LF, $string);
271  $this->rawP = 0;
272  $pre = '[GLOBAL]';
273  while ($pre) {
274  if ($this->breakPointLN && $pre === '[_BREAK]') {
275  $this->‪error('Breakpoint at ' . ($this->lineNumberOffset + $this->rawP - 2) . ': Line content was "' . $this->raw[$this->rawP - 2] . '"', 1);
276  break;
277  }
278  if ($pre === '[]') {
279  $this->‪error('Empty condition is always false, this does not make sense. At line ' . ($this->lineNumberOffset + $this->rawP - 1), 2);
280  break;
281  }
282  $preUppercase = strtoupper($pre);
283  if ($pre[0] === '[' &&
284  ($preUppercase === '[GLOBAL]' ||
285  $preUppercase === '[END]' ||
286  !$this->lastConditionTrue && $preUppercase === '[ELSE]')
287  ) {
288  $pre = trim($this->‪parseSub($this->setup));
289  $this->lastConditionTrue = true;
290  } else {
291  // We're in a specific section. Therefore we log this section
292  $specificSection = $preUppercase !== '[ELSE]';
293  if ($specificSection) {
294  $this->sections[md5($pre)] = $pre;
295  }
296  if (is_object($matchObj) && $matchObj->match($pre) || $this->syntaxHighLight) {
297  if ($specificSection) {
298  $this->sectionsMatch[md5($pre)] = $pre;
299  }
300  $pre = trim($this->‪parseSub($this->setup));
301  $this->lastConditionTrue = true;
302  } else {
303  $pre = $this->‪nextDivider();
304  $this->lastConditionTrue = false;
305  }
306  }
307  }
308  if ($this->inBrace) {
309  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': The script is short of ' . $this->inBrace . ' end brace(s)', 1);
310  }
311  if ($this->multiLineEnabled) {
312  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': A multiline value section is not ended with a parenthesis!', 1);
313  }
314  $this->lineNumberOffset += count($this->raw) + 1;
315  }
316 
323  protected function ‪nextDivider()
324  {
325  while (isset($this->raw[$this->rawP])) {
326  $line = trim($this->raw[$this->rawP]);
327  $this->rawP++;
328  if ($line && $line[0] === '[') {
329  return $line;
330  }
331  }
332  return '';
333  }
334 
341  protected function ‪parseSub(array &‪$setup)
342  {
343  while (isset($this->raw[$this->rawP])) {
344  $line = ltrim($this->raw[$this->rawP]);
345  $lineP = ‪$this->rawP;
346  $this->rawP++;
347  if ($this->syntaxHighLight) {
348  $this->‪regHighLight('prespace', $lineP, strlen($line));
349  }
350  // Breakpoint?
351  // By adding 1 we get that line processed
352  if ($this->breakPointLN && $this->lineNumberOffset + $this->rawP - 1 === $this->breakPointLN + 1) {
353  return '[_BREAK]';
354  }
355  // Set comment flag?
356  if (!$this->multiLineEnabled && strpos($line, '/*') === 0) {
357  $this->commentSet = true;
358  }
359  // If $this->multiLineEnabled we will go and get the line values here because we know, the first if() will be TRUE.
360  if (!$this->commentSet && ($line || $this->multiLineEnabled)) {
361  // If multiline is enabled. Escape by ')'
362  if ($this->multiLineEnabled) {
363  // Multiline ends...
364  if (!empty($line[0]) && $line[0] === ')') {
365  if ($this->syntaxHighLight) {
366  $this->‪regHighLight('operator', $lineP, strlen($line) - 1);
367  }
368  // Disable multiline
369  $this->multiLineEnabled = false;
370  $theValue = implode(LF, $this->multiLineValue);
371  if (strpos($this->multiLineObject, '.') !== false) {
372  // Set the value deeper.
373  $this->‪setVal($this->multiLineObject, ‪$setup, [$theValue]);
374  } else {
375  // Set value regularly
377  if ($this->lastComment && $this->regComments) {
378  ‪$setup[$this->multiLineObject . '..'] .= ‪$this->lastComment;
379  }
380  if ($this->regLinenumbers) {
381  ‪$setup[$this->multiLineObject . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
382  }
383  }
384  } else {
385  if ($this->syntaxHighLight) {
386  $this->‪regHighLight('value', $lineP);
387  }
388  $this->multiLineValue[] = $this->raw[$this->rawP - 1];
389  }
390  } elseif ($this->inBrace === 0 && $line[0] === '[') {
391  if (substr(trim($line), -1, 1) !== ']') {
392  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Invalid condition found, any condition must end with "]": ' . $line);
393  return $line;
394  }
395  // Beginning of condition (only on level zero compared to brace-levels
396  if ($this->syntaxHighLight) {
397  $this->‪regHighLight('condition', $lineP);
398  }
399  return $line;
400  } else {
401  // Return if GLOBAL condition is set - no matter what.
402  if ($line[0] === '[' && stripos($line, '[GLOBAL]') !== false) {
403  if ($this->syntaxHighLight) {
404  $this->‪regHighLight('condition', $lineP);
405  }
406  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': On return to [GLOBAL] scope, the script was short of ' . $this->inBrace . ' end brace(s)', 1);
407  $this->inBrace = 0;
408  return $line;
409  }
410  if ($line[0] !== '}' && $line[0] !== '#' && $line[0] !== '/') {
411  // If not brace-end or comment
412  // Find object name string until we meet an operator
413  $varL = strcspn($line, "\t" . ' {=<>(');
414  // check for special ":=" operator
415  if ($varL > 0 && substr($line, $varL - 1, 2) === ':=') {
416  --$varL;
417  }
418  // also remove tabs after the object string name
419  $objStrName = substr($line, 0, $varL);
420  if ($this->syntaxHighLight) {
421  $this->‪regHighLight('objstr', $lineP, strlen(substr($line, $varL)));
422  }
423  if ($objStrName !== '') {
424  $r = [];
425  if (preg_match('/[^[:alnum:]_\\\\\\.:-]/i', $objStrName, $r)) {
426  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_:-\\."');
427  } else {
428  $line = ltrim(substr($line, $varL));
429  if ($this->syntaxHighLight) {
430  $this->‪regHighLight('objstr_postspace', $lineP, strlen($line));
431  if ($line !== '') {
432  $this->‪regHighLight('operator', $lineP, strlen($line) - 1);
433  $this->‪regHighLight('operator_postspace', $lineP, strlen(ltrim(substr($line, 1))));
434  }
435  }
436  if ($line === '') {
437  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({');
438  } else {
439  // Checking for special TSparser properties (to change TS values at parsetime)
440  $match = [];
441  if ($line[0] === ':' && preg_match('/^:=\\s*([[:alpha:]]+)\\s*\\((.*)\\).*/', $line, $match)) {
442  $tsFunc = $match[1];
443  $tsFuncArg = $match[2];
444  $val = $this->‪getVal($objStrName, ‪$setup);
445  $currentValue = $val[0] ?? null;
446  $tsFuncArg = str_replace(['\\\\', '\\n', '\\t'], ['\\', LF, "\t"], $tsFuncArg);
447  $newValue = $this->‪executeValueModifier($tsFunc, $tsFuncArg, $currentValue);
448  if (isset($newValue)) {
449  $line = '= ' . $newValue;
450  }
451  }
452  switch ($line[0]) {
453  case '=':
454  if ($this->syntaxHighLight) {
455  $this->‪regHighLight('value', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
456  }
457  if (strpos($objStrName, '.') !== false) {
458  $value = [];
459  $value[0] = trim(substr($line, 1));
460  $this->‪setVal($objStrName, ‪$setup, $value);
461  } else {
462  ‪$setup[$objStrName] = trim(substr($line, 1));
463  if ($this->lastComment && $this->regComments) {
464  // Setting comment..
465  ‪$setup[$objStrName . '..'] .= ‪$this->lastComment;
466  }
467  if ($this->regLinenumbers) {
468  ‪$setup[$objStrName . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
469  }
470  }
471  break;
472  case '{':
473  $this->inBrace++;
474  if (strpos($objStrName, '.') !== false) {
475  $exitSig = $this->‪rollParseSub($objStrName, ‪$setup);
476  if ($exitSig) {
477  return $exitSig;
478  }
479  } else {
480  if (!isset(‪$setup[$objStrName . '.'])) {
481  ‪$setup[$objStrName . '.'] = [];
482  }
483  $exitSig = $this->‪parseSub($setup[$objStrName . '.']);
484  if ($exitSig) {
485  return $exitSig;
486  }
487  }
488  break;
489  case '(':
490  $this->multiLineObject = $objStrName;
491  $this->multiLineEnabled = true;
492  $this->multiLineValue = [];
493  break;
494  case '<':
495  if ($this->syntaxHighLight) {
496  $this->‪regHighLight('value_copy', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
497  }
498  $theVal = trim(substr($line, 1));
499  if ($theVal[0] === '.') {
500  $res = $this->‪getVal(substr($theVal, 1), ‪$setup);
501  } else {
502  $res = $this->‪getVal($theVal, $this->setup);
503  }
504  // unserialize(serialize(...)) may look stupid but is needed because of some reference issues.
505  // See forge issue #76919 and functional test hasFlakyReferences()
506  $this->‪setVal($objStrName, ‪$setup, unserialize(serialize($res), ['allowed_classes' => false]), 1);
507  break;
508  case '>':
509  if ($this->syntaxHighLight) {
510  $this->‪regHighLight('value_unset', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
511  }
512  $this->‪setVal($objStrName, ‪$setup, 'UNSET');
513  break;
514  default:
515  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({');
516  }
517  }
518  }
519  $this->lastComment = '';
520  }
521  } elseif ($line[0] === '}') {
522  $this->inBrace--;
523  $this->lastComment = '';
524  if ($this->syntaxHighLight) {
525  $this->‪regHighLight('operator', $lineP, strlen($line) - 1);
526  }
527  if ($this->inBrace < 0) {
528  $this->‪error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': An end brace is in excess.', 1);
529  $this->inBrace = 0;
530  } else {
531  break;
532  }
533  } else {
534  if ($this->syntaxHighLight) {
535  $this->‪regHighLight('comment', $lineP);
536  }
537  // Comment. The comments are concatenated in this temporary string:
538  if ($this->regComments) {
539  $this->lastComment .= rtrim($line) . LF;
540  }
541  }
542  if (strpos($line, '### ERROR') === 0) {
543  $this->‪error(substr($line, 11));
544  }
545  }
546  }
547  // Unset comment
548  if ($this->commentSet) {
549  if ($this->syntaxHighLight) {
550  $this->‪regHighLight('comment', $lineP);
551  }
552  if (strpos($line, '*/') !== false) {
553  $this->commentSet = false;
554  }
555  }
556  }
557  return null;
558  }
559 
569  protected function ‪executeValueModifier($modifierName, $modifierArgument = null, $currentValue = null)
570  {
571  $newValue = null;
572  switch ($modifierName) {
573  case 'prependString':
574  $newValue = $modifierArgument . $currentValue;
575  break;
576  case 'appendString':
577  $newValue = $currentValue . $modifierArgument;
578  break;
579  case 'removeString':
580  $newValue = str_replace($modifierArgument, '', $currentValue);
581  break;
582  case 'replaceString':
583  $modifierArgumentArray = explode('|', $modifierArgument, 2);
584  $fromStr = $modifierArgumentArray[0] ?? '';
585  $toStr = $modifierArgumentArray[1] ?? '';
586  $newValue = str_replace($fromStr, $toStr, $currentValue);
587  break;
588  case 'addToList':
589  $newValue = ((string)$currentValue !== '' ? $currentValue . ',' : '') . $modifierArgument;
590  break;
591  case 'removeFromList':
592  $existingElements = GeneralUtility::trimExplode(',', $currentValue);
593  $removeElements = GeneralUtility::trimExplode(',', $modifierArgument);
594  if (!empty($removeElements)) {
595  $newValue = implode(',', array_diff($existingElements, $removeElements));
596  }
597  break;
598  case 'uniqueList':
599  $elements = GeneralUtility::trimExplode(',', $currentValue);
600  $newValue = implode(',', array_unique($elements));
601  break;
602  case 'reverseList':
603  $elements = GeneralUtility::trimExplode(',', $currentValue);
604  $newValue = implode(',', array_reverse($elements));
605  break;
606  case 'sortList':
607  $elements = GeneralUtility::trimExplode(',', $currentValue);
608  $arguments = GeneralUtility::trimExplode(',', $modifierArgument);
609  $arguments = array_map('strtolower', $arguments);
610  $sort_flags = SORT_REGULAR;
611  if (in_array('numeric', $arguments)) {
612  $sort_flags = SORT_NUMERIC;
613  // If the sorting modifier "numeric" is given, all values
614  // are checked and an exception is thrown if a non-numeric value is given
615  // otherwise there is a different behaviour between PHP7 and PHP 5.x
616  // See also the warning on http://us.php.net/manual/en/function.sort.php
617  foreach ($elements as $element) {
618  if (!is_numeric($element)) {
619  throw new \InvalidArgumentException('The list "' . $currentValue . '" should be sorted numerically but contains a non-numeric value', 1438191758);
620  }
621  }
622  }
623  sort($elements, $sort_flags);
624  if (in_array('descending', $arguments)) {
625  $elements = array_reverse($elements);
626  }
627  $newValue = implode(',', $elements);
628  break;
629  case 'getEnv':
630  $environmentValue = getenv(trim($modifierArgument));
631  if ($environmentValue !== false) {
632  $newValue = $environmentValue;
633  }
634  break;
635  default:
636  if (isset(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName])) {
637  $hookMethod = ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName];
638  $params = ['currentValue' => $currentValue, 'functionArgument' => $modifierArgument];
639  $fakeThis = false;
640  $newValue = GeneralUtility::callUserFunction($hookMethod, $params, $fakeThis);
641  } else {
642  ‪self::getLogger()->warning('Missing function definition for ' . $modifierName . ' on TypoScript');
643  }
644  }
645  return $newValue;
646  }
647 
657  protected function ‪rollParseSub($string, array &‪$setup)
658  {
659  if ((string)$string === '') {
660  return '';
661  }
662 
663  list($key, $remainingKey) = $this->‪parseNextKeySegment($string);
664  $key .= '.';
665  if (!isset(‪$setup[$key])) {
666  ‪$setup[$key] = [];
667  }
668  $exitSig = $remainingKey === ''
669  ? $this->‪parseSub($setup[$key])
670  : $this->‪rollParseSub($remainingKey, ‪$setup[$key]);
671  return $exitSig ?: '';
672  }
673 
682  public function ‪getVal($string, ‪$setup)
683  {
684  if ((string)$string === '') {
685  return [];
686  }
687 
688  list($key, $remainingKey) = $this->‪parseNextKeySegment($string);
689  $subKey = $key . '.';
690  if ($remainingKey === '') {
691  $retArr = [];
692  if (isset(‪$setup[$key])) {
693  $retArr[0] = ‪$setup[$key];
694  }
695  if (isset(‪$setup[$subKey])) {
696  $retArr[1] = ‪$setup[$subKey];
697  }
698  return $retArr;
699  }
700  if (‪$setup[$subKey]) {
701  return $this->‪getVal($remainingKey, ‪$setup[$subKey]);
702  }
703 
704  return [];
705  }
706 
715  protected function ‪setVal($string, array &‪$setup, $value, $wipeOut = false)
716  {
717  if ((string)$string === '') {
718  return;
719  }
720 
721  list($key, $remainingKey) = $this->‪parseNextKeySegment($string);
722  $subKey = $key . '.';
723  if ($remainingKey === '') {
724  if ($value === 'UNSET') {
725  unset(‪$setup[$key]);
726  unset(‪$setup[$subKey]);
727  if ($this->regLinenumbers) {
728  ‪$setup[$key . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '>';
729  }
730  } else {
731  $lnRegisDone = 0;
732  if ($wipeOut) {
733  unset(‪$setup[$key]);
734  unset(‪$setup[$subKey]);
735  if ($this->regLinenumbers) {
736  ‪$setup[$key . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '<';
737  $lnRegisDone = 1;
738  }
739  }
740  if (isset($value[0])) {
741  ‪$setup[$key] = $value[0];
742  }
743  if (isset($value[1])) {
744  ‪$setup[$subKey] = $value[1];
745  }
746  if ($this->lastComment && $this->regComments) {
747  ‪$setup[$key . '..'] .= ‪$this->lastComment;
748  }
749  if ($this->regLinenumbers && !$lnRegisDone) {
750  ‪$setup[$key . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
751  }
752  }
753  } else {
754  if (!isset(‪$setup[$subKey])) {
755  ‪$setup[$subKey] = [];
756  }
757  $this->‪setVal($remainingKey, ‪$setup[$subKey], $value);
758  }
759  }
760 
772  protected function ‪parseNextKeySegment($key)
773  {
774  // if no dot is in the key, nothing to do
775  $dotPosition = strpos($key, '.');
776  if ($dotPosition === false) {
777  return [$key, ''];
778  }
779 
780  if (strpos($key, '\\') !== false) {
781  // backslashes are in the key, so we do further parsing
782 
783  while ($dotPosition !== false) {
784  if ($dotPosition > 0 && $key[$dotPosition - 1] !== '\\' || $dotPosition > 1 && $key[$dotPosition - 2] === '\\') {
785  break;
786  }
787  // escaped dot found, continue
788  $dotPosition = strpos($key, '.', $dotPosition + 1);
789  }
790 
791  if ($dotPosition === false) {
792  // no regular dot found
793  $keySegment = $key;
794  $remainingKey = '';
795  } else {
796  if ($dotPosition > 1 && $key[$dotPosition - 2] === '\\' && $key[$dotPosition - 1] === '\\') {
797  $keySegment = substr($key, 0, $dotPosition - 1);
798  } else {
799  $keySegment = substr($key, 0, $dotPosition);
800  }
801  $remainingKey = substr($key, $dotPosition + 1);
802  }
803 
804  // fix key segment by removing escape sequences
805  $keySegment = str_replace('\\.', '.', $keySegment);
806  } else {
807  // no backslash in the key, we're fine off
808  list($keySegment, $remainingKey) = explode('.', $key, 2);
809  }
810  return [$keySegment, $remainingKey];
811  }
812 
820  protected function ‪error($err, $num = 2)
821  {
822  $tt = $this->‪getTimeTracker();
823  if ($tt !== null) {
824  $tt->setTSlogMessage($err, $num);
825  }
826  $this->errors[] = [$err, $num, $this->rawP - 1, ‪$this->lineNumberOffset];
827  }
828 
840  public static function ‪checkIncludeLines($string, $cycle_counter = 1, $returnFiles = false, $parentFilenameOrPath = '')
841  {
842  $includedFiles = [];
843  if ($cycle_counter > 100) {
844  ‪self::getLogger()->warning('It appears like TypoScript code is looping over itself. Check your templates for "<INCLUDE_TYPOSCRIPT: ..." tags');
845  if ($returnFiles) {
846  return [
847  'typoscript' => '',
848  'files' => $includedFiles
849  ];
850  }
851  return '
852 ###
853 ### ERROR: Recursion!
854 ###
855 ';
856  }
857 
858  if ($string !== null) {
859  $string = ‪StringUtility::removeByteOrderMark($string);
860  }
861 
862  // Checking for @import syntax imported files
863  $string = ‪self::addImportsFromExternalFiles($string, $cycle_counter, $returnFiles, $includedFiles, $parentFilenameOrPath);
864 
865  // If no tags found, no need to do slower preg_split
866  if (strpos($string, '<INCLUDE_TYPOSCRIPT:') !== false) {
867  $splitRegEx = '/\r?\n\s*<INCLUDE_TYPOSCRIPT:\s*(?i)source\s*=\s*"((?i)file|dir):\s*([^"]*)"(.*)>[\ \t]*/';
868  $parts = preg_split($splitRegEx, LF . $string . LF, -1, PREG_SPLIT_DELIM_CAPTURE);
869  // First text part goes through
870  $newString = $parts[0] . LF;
871  $partCount = count($parts);
872  for ($i = 1; $i + 3 < $partCount; $i += 4) {
873  // $parts[$i] contains 'FILE' or 'DIR'
874  // $parts[$i+1] contains relative file or directory path to be included
875  // $parts[$i+2] optional properties of the INCLUDE statement
876  // $parts[$i+3] next part of the typoscript string (part in between include-tags)
877  $includeType = $parts[$i];
878  $filename = $parts[$i + 1];
879  $originalFilename = $filename;
880  $optionalProperties = $parts[$i + 2];
881  $tsContentsTillNextInclude = $parts[$i + 3];
882 
883  // Check condition
884  $matches = preg_split('#(?i)condition\\s*=\\s*"((?:\\\\\\\\|\\\\"|[^\\"])*)"(\\s*|>)#', $optionalProperties, 2, PREG_SPLIT_DELIM_CAPTURE);
885  // If there was a condition
886  if (count($matches) > 1) {
887  // Unescape the condition
888  $condition = trim(stripslashes($matches[1]));
889  // If necessary put condition in square brackets
890  if ($condition[0] !== '[') {
891  $condition = '[' . $condition . ']';
892  }
893 
895  $conditionMatcher = null;
896  if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_FE) {
897  $conditionMatcher = GeneralUtility::makeInstance(FrontendConditionMatcher::class);
898  } else {
899  $conditionMatcher = GeneralUtility::makeInstance(BackendConditionMatcher::class);
900  }
901 
902  // If it didn't match then proceed to the next include, but prepend next normal (not file) part to output string
903  if (!$conditionMatcher->match($condition)) {
904  $newString .= $tsContentsTillNextInclude . LF;
905  continue;
906  }
907  }
908 
909  // Resolve a possible relative paths if a parent file is given
910  if ($parentFilenameOrPath !== '' && $filename[0] === '.') {
911  $filename = ‪PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $filename);
912  }
913 
914  // There must be a line-break char after - not sure why this check is necessary, kept it for being 100% backwards compatible
915  // An empty string is also ok (means that the next line is also a valid include_typoscript tag)
916  if (!preg_match('/(^\\s*\\r?\\n|^$)/', $tsContentsTillNextInclude)) {
917  $newString .= ‪self::typoscriptIncludeError('Invalid characters after <INCLUDE_TYPOSCRIPT: source="' . $includeType . ':' . $filename . '">-tag (rest of line must be empty).');
918  } elseif (strpos('..', $filename) !== false) {
919  $newString .= ‪self::typoscriptIncludeError('Invalid filepath "' . $filename . '" (containing "..").');
920  } else {
921  switch (strtolower($includeType)) {
922  case 'file':
923  ‪self::includeFile($originalFilename, $cycle_counter, $returnFiles, $newString, $includedFiles, $optionalProperties, $parentFilenameOrPath);
924  break;
925  case 'dir':
926  ‪self::includeDirectory($originalFilename, $cycle_counter, $returnFiles, $newString, $includedFiles, $optionalProperties, $parentFilenameOrPath);
927  break;
928  default:
929  $newString .= ‪self::typoscriptIncludeError('No valid option for INCLUDE_TYPOSCRIPT source property (valid options are FILE or DIR)');
930  }
931  }
932  // Prepend next normal (not file) part to output string
933  $newString .= $tsContentsTillNextInclude . LF;
934 
935  // load default TypoScript for content rendering templates like
936  // fluid_styled_content if those have been included through f.e.
937  // <INCLUDE_TYPOSCRIPT: source="FILE:EXT:fluid_styled_content/Configuration/TypoScript/setup.typoscript">
938  if (strpos(strtolower($filename), 'ext:') === 0) {
939  $filePointerPathParts = explode('/', substr($filename, 4));
940 
941  // remove file part, determine whether to load setup or constants
942  list($includeType, ) = explode('.', array_pop($filePointerPathParts));
943 
944  if (in_array($includeType, ['setup', 'constants'])) {
945  // adapt extension key to required format (no underscores)
946  $filePointerPathParts[0] = str_replace('_', '', $filePointerPathParts[0]);
947 
948  // load default TypoScript
949  $defaultTypoScriptKey = implode('/', $filePointerPathParts) . '/';
950  if (in_array($defaultTypoScriptKey, ‪$GLOBALS['TYPO3_CONF_VARS']['FE']['contentRenderingTemplates'], true)) {
951  $newString .= ‪$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $includeType . '.']['defaultContentRendering'];
952  }
953  }
954  }
955  }
956  // Add a line break before and after the included code in order to make sure that the parser always has a LF.
957  $string = LF . trim($newString) . LF;
958  }
959  // When all included files should get returned, simply return an compound array containing
960  // the TypoScript with all "includes" processed and the files which got included
961  if ($returnFiles) {
962  return [
963  'typoscript' => $string,
964  'files' => $includedFiles
965  ];
966  }
967  return $string;
968  }
969 
980  protected static function ‪addImportsFromExternalFiles($typoScript, $cycleCounter, $returnFiles, &$includedFiles, &$parentFilenameOrPath)
981  {
982  // Check for new syntax "@import 'EXT:bennilove/Configuration/TypoScript/*'"
983  if (strpos($typoScript, '@import \'') !== false || strpos($typoScript, '@import "') !== false) {
984  $splitRegEx = '/\r?\n\s*@import\s[\'"]([^\'"]*)[\'"][\ \t]?/';
985  $parts = preg_split($splitRegEx, LF . $typoScript . LF, -1, PREG_SPLIT_DELIM_CAPTURE);
986  // First text part goes through
987  $newString = $parts[0] . LF;
988  $partCount = count($parts);
989  for ($i = 1; $i + 2 <= $partCount; $i += 2) {
990  $filename = $parts[$i];
991  $tsContentsTillNextInclude = $parts[$i + 1];
992  // Resolve a possible relative paths if a parent file is given
993  if ($parentFilenameOrPath !== '' && $filename[0] === '.') {
994  $filename = ‪PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $filename);
995  }
996  $newString .= ‪self::importExternalTypoScriptFile($filename, $cycleCounter, $returnFiles, $includedFiles);
997  // Prepend next normal (not file) part to output string
998  $newString .= $tsContentsTillNextInclude;
999  }
1000  // Add a line break before and after the included code in order to make sure that the parser always has a LF.
1001  $typoScript = LF . trim($newString) . LF;
1002  }
1003  return $typoScript;
1004  }
1005 
1016  protected static function ‪importExternalTypoScriptFile($filename, $cycleCounter, $returnFiles, array &$includedFiles)
1017  {
1018  if (strpos('..', $filename) !== false) {
1019  return ‪self::typoscriptIncludeError('Invalid filepath "' . $filename . '" (containing "..").');
1020  }
1021 
1022  $content = '';
1023  $absoluteFileName = GeneralUtility::getFileAbsFileName($filename);
1024  if ((string)$absoluteFileName === '') {
1025  return ‪self::typoscriptIncludeError('Illegal filepath "' . $filename . '".');
1026  }
1027 
1028  ‪$finder = new Finder();
1029  ‪$finder
1030  // no recursive mode on purpose
1031  ->depth(0)
1032  // no directories should be fetched
1033  ->files()
1034  ->sortByName();
1035 
1036  // Search all files in the folder
1037  if (is_dir($absoluteFileName)) {
1038  ‪$finder->in($absoluteFileName);
1039  // Used for the TypoScript comments
1040  $readableFilePrefix = $filename;
1041  } else {
1042  try {
1043  // Apparently this is not a folder, so the restriction
1044  // is the folder so we restrict into this folder
1045  ‪$finder->in(‪PathUtility::dirname($absoluteFileName));
1046  if (!is_file($absoluteFileName)
1047  && strpos(‪PathUtility::basename($absoluteFileName), '*') === false
1048  && substr(‪PathUtility::basename($absoluteFileName), -11) !== '.typoscript') {
1049  $absoluteFileName .= '*.typoscript';
1050  }
1051  ‪$finder->name(‪PathUtility::basename($absoluteFileName));
1052  $readableFilePrefix = ‪PathUtility::dirname($filename);
1053  } catch (\InvalidArgumentException $e) {
1054  return ‪self::typoscriptIncludeError($e->getMessage());
1055  }
1056  }
1057 
1058  foreach (‪$finder as $fileObject) {
1059  // Clean filename output for comments
1060  $readableFileName = rtrim($readableFilePrefix, '/') . '/' . $fileObject->getFilename();
1061  $content .= LF . '### @import \'' . $readableFileName . '\' begin ###' . LF;
1062  // Check for allowed files
1063  if (!GeneralUtility::verifyFilenameAgainstDenyPattern($fileObject->getFilename())) {
1064  $content .= self::typoscriptIncludeError('File "' . $readableFileName . '" was not included since it is not allowed due to fileDenyPattern.');
1065  } else {
1066  $includedFiles[] = $fileObject->getPathname();
1067  // check for includes in included text
1068  $included_text = self::checkIncludeLines($fileObject->getContents(), $cycleCounter++, $returnFiles, $absoluteFileName);
1069  // If the method also has to return all included files, merge currently included
1070  // files with files included by recursively calling itself
1071  if ($returnFiles && is_array($included_text)) {
1072  $includedFiles = array_merge($includedFiles, $included_text['files']);
1073  $included_text = $included_text['typoscript'];
1074  }
1075  $content .= $included_text . LF;
1076  }
1077  $content .= '### @import \'' . $readableFileName . '\' end ###' . LF . LF;
1078 
1079  // load default TypoScript for content rendering templates like
1080  // fluid_styled_content if those have been included through e.g.
1081  // @import "fluid_styled_content/Configuration/TypoScript/setup.typoscript"
1082  if (strpos(strtoupper($filename), 'EXT:') === 0) {
1083  $filePointerPathParts = explode('/', substr($filename, 4));
1084  // remove file part, determine whether to load setup or constants
1085  list($includeType) = explode('.', array_pop($filePointerPathParts));
1086 
1087  if (in_array($includeType, ['setup', 'constants'], true)) {
1088  // adapt extension key to required format (no underscores)
1089  $filePointerPathParts[0] = str_replace('_', '', $filePointerPathParts[0]);
1090 
1091  // load default TypoScript
1092  $defaultTypoScriptKey = implode('/', $filePointerPathParts) . '/';
1093  if (in_array($defaultTypoScriptKey, $GLOBALS['TYPO3_CONF_VARS']['FE']['contentRenderingTemplates'], true)) {
1094  $content .= $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $includeType . '.']['defaultContentRendering'];
1095  }
1096  }
1097  }
1098  }
1099 
1100  if (empty($content)) {
1101  return self::typoscriptIncludeError('No file or folder found for importing TypoScript on "' . $filename . '".');
1102  }
1103  return $content;
1104  }
1105 
1120  public static function includeFile($filename, $cycle_counter = 1, $returnFiles = false, &$newString = '', array &$includedFiles = [], $optionalProperties = '', $parentFilenameOrPath = '')
1121  {
1122  // Resolve a possible relative paths if a parent file is given
1123  if ($parentFilenameOrPath !== '' && $filename[0] === '.') {
1124  $absfilename = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $filename);
1125  } else {
1126  $absfilename = $filename;
1127  }
1128  $absfilename = GeneralUtility::getFileAbsFileName($absfilename);
1129 
1130  $newString .= LF . '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $filename . '"' . $optionalProperties . '> BEGIN:' . LF;
1131  if ((string)$filename !== '') {
1132  // Must exist and must not contain '..' and must be relative
1133  // Check for allowed files
1134  if (!GeneralUtility::verifyFilenameAgainstDenyPattern($absfilename)) {
1135  $newString .= self::typoscriptIncludeError('File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern.');
1136  } else {
1137  $fileExists = false;
1138  if (@file_exists($absfilename)) {
1139  $fileExists = true;
1140  } else {
1141  // BC layer after renaming core TypoScript files from .txt to .typoscript
1142  if (substr($absfilename, -4, 4) === '.txt') {
1143  $absfilename = substr($absfilename, 0, -4) . '.typoscript';
1144  if (@file_exists($absfilename)) {
1145  trigger_error('The TypoScript file ' . $filename . ' was renamed to .typoscript extension.'
1146  . ' Update your "<INCLUDE_TYPOSCRIPT" ‪statements.', E_USER_DEPRECATED);
1147  $fileExists = true;
1148  }
1149  }
1150  }
1151 
1152  if ($fileExists) {
1153  $includedFiles[] = $absfilename;
1154  // check for includes in included text
1155  $included_text = self::checkIncludeLines(file_get_contents($absfilename), $cycle_counter + 1, $returnFiles, $absfilename);
1156  // If the method also has to return all included files, merge currently included
1157  // files with files included by recursively calling itself
1158  if ($returnFiles && is_array($included_text)) {
1159  $includedFiles = array_merge($includedFiles, $included_text['files']);
1160  $included_text = $included_text['typoscript'];
1161  }
1162  $newString .= $included_text . LF;
1163  } else {
1164  $newString .= self::typoscriptIncludeError('File "' . $filename . '" was not found.');
1165  }
1166  }
1167  }
1168  $newString .= '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $filename . '"' . $optionalProperties . '> END:' . LF . LF;
1169  }
1170 
1186  protected static function includeDirectory($dirPath, $cycle_counter = 1, $returnFiles = false, &$newString = '', array &$includedFiles = [], $optionalProperties = '', $parentFilenameOrPath = '')
1187  {
1188  // Extract the value of the property extensions="..."
1189  $matches = preg_split('#(?i)extensions\s*=\s*"([^"]*)"(\s*|>)#', $optionalProperties, 2, PREG_SPLIT_DELIM_CAPTURE);
1190  if (count($matches) > 1) {
1191  $includedFileExtensions = $matches[1];
1192  } else {
1193  $includedFileExtensions = '';
1194  }
1195 
1196  // Resolve a possible relative paths if a parent file is given
1197  if ($parentFilenameOrPath !== '' && $dirPath[0] === '.') {
1198  $resolvedDirPath = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $dirPath);
1199  } else {
1200  $resolvedDirPath = $dirPath;
1201  }
1202  $absDirPath = GeneralUtility::getFileAbsFileName($resolvedDirPath);
1203  if ($absDirPath) {
1204  $absDirPath = rtrim($absDirPath, '/') . '/';
1205  $newString .= LF . '### <INCLUDE_TYPOSCRIPT: source="DIR:' . $dirPath . '"' . $optionalProperties . '> BEGIN:' . LF;
1206  // Get alphabetically sorted file index in array
1207  $fileIndex = GeneralUtility::getAllFilesAndFoldersInPath([], $absDirPath, $includedFileExtensions);
1208  // Prepend file contents to $newString
1209  $prefixLength = strlen(Environment::getPublicPath() . '/');
1210  foreach ($fileIndex as $absFileRef) {
1211  $relFileRef = substr($absFileRef, $prefixLength);
1212  self::includeFile($relFileRef, $cycle_counter, $returnFiles, $newString, $includedFiles, '', $absDirPath);
1213  }
1214  $newString .= '### <INCLUDE_TYPOSCRIPT: source="DIR:' . $dirPath . '"' . $optionalProperties . '> END:' . LF . LF;
1215  } else {
1216  $newString .= self::typoscriptIncludeError('The path "' . $resolvedDirPath . '" is invalid.');
1217  }
1218  }
1219 
1228  protected static function typoscriptIncludeError($error)
1229  {
1230  self::getLogger()->warning($error);
1231  return "\n###\n### ERROR: " . $error . "\n###\n\n";
1232  }
1233 
1240  public static function checkIncludeLines_array(array $array)
1241  {
1242  foreach ($array as $k => $v) {
1243  $array[$k] = self::checkIncludeLines($array[$k]);
1244  }
1245  return $array;
1246  }
1247 
1262  public static function extractIncludes($string, $cycle_counter = 1, array $extractedFileNames = [], $parentFilenameOrPath = '')
1263  {
1264  if ($cycle_counter > 10) {
1265  self::getLogger()->warning('It appears like TypoScript code is looping over itself. Check your templates for "<INCLUDE_TYPOSCRIPT: ..." tags');
1266  return '
1267 ###
1268 ### ERROR: Recursion!
1269 ###
1270 ';
1271  }
1272  $expectedEndTag = '';
1273  $fileContent = [];
1274  $restContent = [];
1275  $fileName = null;
1276  $inIncludePart = false;
1277  $lines = preg_split("/\r\n|\n|\r/", $string);
1278  $skipNextLineIfEmpty = false;
1279  $openingCommentedIncludeStatement = null;
1280  $optionalProperties = '';
1281  foreach ($lines as $line) {
1282  // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts
1283  // an additional empty line, remove this again
1284  if ($skipNextLineIfEmpty) {
1285  if (trim($line) === '') {
1286  continue;
1287  }
1288  $skipNextLineIfEmpty = false;
1289  }
1290 
1291  // Outside commented include statements
1292  if (!$inIncludePart) {
1293  // Search for beginning commented include statements
1294  if (preg_match('/###\\s*<INCLUDE_TYPOSCRIPT:\\s*source\\s*=\\s*"\\s*((?i)file|dir)\\s*:\\s*([^"]*)"(.*)>\\s*BEGIN/i', $line, $matches)) {
1295  // Found a commented include statement
1296 
1297  // Save this line in case there is no ending tag
1298  $openingCommentedIncludeStatement = trim($line);
1299  $openingCommentedIncludeStatement = preg_replace('/\\s*### Warning: .*###\\s*/', '', $openingCommentedIncludeStatement);
1300 
1301  // type of match: FILE or DIR
1302  $inIncludePart = strtoupper($matches[1]);
1303  $fileName = $matches[2];
1304  $optionalProperties = $matches[3];
1305 
1306  $expectedEndTag = '### <INCLUDE_TYPOSCRIPT: source="' . $inIncludePart . ':' . $fileName . '"' . $optionalProperties . '> END';
1307  // Strip all whitespace characters to make comparison safer
1308  $expectedEndTag = strtolower(preg_replace('/\s/', '', $expectedEndTag));
1309  } else {
1310  // If this is not a beginning commented include statement this line goes into the rest content
1311  $restContent[] = $line;
1312  }
1313  } else {
1314  // Inside commented include statements
1315  // Search for the matching ending commented include statement
1316  $strippedLine = preg_replace('/\s/', '', $line);
1317  if (stripos($strippedLine, $expectedEndTag) !== false) {
1318  // Found the matching ending include statement
1319  $fileContentString = implode(PHP_EOL, $fileContent);
1320 
1321  // Write the content to the file
1322 
1323  // Resolve a possible relative paths if a parent file is given
1324  if ($parentFilenameOrPath !== '' && $fileName[0] === '.') {
1325  $realFileName = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $fileName);
1326  } else {
1327  $realFileName = $fileName;
1328  }
1329  $realFileName = GeneralUtility::getFileAbsFileName($realFileName);
1330 
1331  if ($inIncludePart === 'FILE') {
1332  // Some file checks
1333  if (!GeneralUtility::verifyFilenameAgainstDenyPattern($realFileName)) {
1334  throw new \UnexpectedValueException(sprintf('File "%s" was not included since it is not allowed due to fileDenyPattern.', $fileName), 1382651858);
1335  }
1336  if (empty($realFileName)) {
1337  throw new \UnexpectedValueException(sprintf('"%s" is not a valid file location.', $fileName), 1294586441);
1338  }
1339  if (!is_writable($realFileName)) {
1340  throw new \RuntimeException(sprintf('"%s" is not writable.', $fileName), 1294586442);
1341  }
1342  if (in_array($realFileName, $extractedFileNames)) {
1343  throw new \RuntimeException(sprintf('Recursive/multiple inclusion of file "%s"', $realFileName), 1294586443);
1344  }
1345  $extractedFileNames[] = $realFileName;
1346 
1347  // Recursive call to detected nested commented include statements
1348  $fileContentString = self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName);
1349 
1350  // Write the content to the file
1351  if (!GeneralUtility::writeFile($realFileName, $fileContentString)) {
1352  throw new \RuntimeException(sprintf('Could not write file "%s"', $realFileName), 1294586444);
1353  }
1354  // Insert reference to the file in the rest content
1355  $restContent[] = '<INCLUDE_TYPOSCRIPT: source="FILE:' . $fileName . '"' . $optionalProperties . '>';
1356  } else {
1357  // must be DIR
1358 
1359  // Some file checks
1360  if (empty($realFileName)) {
1361  throw new \UnexpectedValueException(sprintf('"%s" is not a valid location.', $fileName), 1366493602);
1362  }
1363  if (!is_dir($realFileName)) {
1364  throw new \RuntimeException(sprintf('"%s" is not a directory.', $fileName), 1366493603);
1365  }
1366  if (in_array($realFileName, $extractedFileNames)) {
1367  throw new \RuntimeException(sprintf('Recursive/multiple inclusion of directory "%s"', $realFileName), 1366493604);
1368  }
1369  $extractedFileNames[] = $realFileName;
1370 
1371  // Recursive call to detected nested commented include statements
1372  self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName);
1373 
1374  // just drop content between tags since it should usually just contain individual files from that dir
1375 
1376  // Insert reference to the dir in the rest content
1377  $restContent[] = '<INCLUDE_TYPOSCRIPT: source="DIR:' . $fileName . '"' . $optionalProperties . '>';
1378  }
1379 
1380  // Reset variables (preparing for the next commented include statement)
1381  $fileContent = [];
1382  $fileName = null;
1383  $inIncludePart = false;
1384  $openingCommentedIncludeStatement = null;
1385  // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts
1386  // an additional empty line, remove this again
1387  $skipNextLineIfEmpty = true;
1388  } else {
1389  // If this is not an ending commented include statement this line goes into the file content
1390  $fileContent[] = $line;
1391  }
1392  }
1393  }
1394  // If we're still inside commented include ‪statements copy the lines back to the rest content
1395  if ($inIncludePart) {
1396  $restContent[] = $openingCommentedIncludeStatement . ' ### Warning: Corresponding end line missing! ###';
1397  $restContent = array_merge($restContent, $fileContent);
1398  }
1399  $restContentString = implode(PHP_EOL, $restContent);
1400  return $restContentString;
1401  }
1402 
1409  public static function ‪extractIncludes_array(array $array)
1410  {
1411  foreach ($array as $k => $v) {
1412  $array[$k] = ‪self::extractIncludes($array[$k]);
1413  }
1414  return $array;
1415  }
1416 
1417  /**********************************
1418  *
1419  * Syntax highlighting
1420  *
1421  *********************************/
1431  public function ‪doSyntaxHighlight($string, $lineNum = '', $highlightBlockMode = false)
1432  {
1433  $this->syntaxHighLight = true;
1434  $this->highLightData = [];
1435  $this->errors = [];
1436  // 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.
1437  $string = str_replace(CR, '', $string);
1438  $this->‪parse($string);
1439  return $this->‪syntaxHighlight_print($lineNum, $highlightBlockMode);
1440  }
1441 
1450  protected function ‪regHighLight($code, $pointer, $strlen = -1)
1451  {
1452  if ($strlen === -1) {
1453  $this->highLightData[$pointer] = [[$code, 0]];
1454  } else {
1455  $this->highLightData[$pointer][] = [$code, $strlen];
1456  }
1457  $this->highLightData_bracelevel[$pointer] = ‪$this->inBrace;
1458  }
1459 
1468  protected function ‪syntaxHighlight_print($lineNumDat, $highlightBlockMode)
1469  {
1470  // Registers all error messages in relation to their linenumber
1471  $errA = [];
1472  foreach ($this->errors as $err) {
1473  $errA[$err[2]][] = $err[0];
1474  }
1475  // Generates the syntax highlighted output:
1476  $lines = [];
1477  foreach ($this->raw as ‪$rawP => $value) {
1478  $start = 0;
1479  $strlen = strlen($value);
1480  $lineC = '';
1481  if (is_array($this->highLightData[‪$rawP])) {
1482  foreach ($this->highLightData[‪$rawP] as $set) {
1483  $len = $strlen - $start - $set[1];
1484  if ($len > 0) {
1485  $part = substr($value, $start, $len);
1486  $start += $len;
1487  $st = $this->highLightStyles[isset($this->highLightStyles[$set[0]]) ? $set[0] : 'default'];
1488  if (!$highlightBlockMode || $set[0] !== 'prespace') {
1489  $lineC .= $st[0] . htmlspecialchars($part) . $st[1];
1490  }
1491  } elseif ($len < 0) {
1492  ‪debug([$len, $value, ‪$rawP]);
1493  }
1494  }
1495  } else {
1496  ‪debug([$value]);
1497  }
1498  if (strlen($value) > $start) {
1499  $lineC .= $this->highLightStyles['ignored'][0] . htmlspecialchars(substr($value, $start)) . $this->highLightStyles['ignored'][1];
1500  }
1501  if ($errA[‪$rawP]) {
1502  $lineC .= $this->highLightStyles['error'][0] . '<strong> - ERROR:</strong> ' . htmlspecialchars(implode(';', $errA[‪$rawP])) . $this->highLightStyles['error'][1];
1503  }
1504  if ($highlightBlockMode && $this->highLightData_bracelevel[‪$rawP]) {
1505  $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, -$this->highLightData_bracelevel[‪$rawP] * 16) : '') . '">' . ($lineC !== '' ? $lineC : '&nbsp;') . '</span>';
1506  }
1507  if (is_array($lineNumDat)) {
1508  $lineNum = ‪$rawP + $lineNumDat[0];
1509  if ($this->parentObject instanceof ExtendedTemplateService) {
1510  $lineNum = $this->parentObject->ext_lnBreakPointWrap($lineNum, $lineNum);
1511  }
1512  $lineC = $this->highLightStyles['linenum'][0] . str_pad($lineNum, 4, ' ', STR_PAD_LEFT) . ':' . $this->highLightStyles['linenum'][1] . ' ' . $lineC;
1513  }
1514  $lines[] = $lineC;
1515  }
1516  return '<pre class="ts-hl">' . implode(LF, $lines) . '</pre>';
1517  }
1518 
1522  protected function ‪getTimeTracker()
1523  {
1524  return GeneralUtility::makeInstance(TimeTracker::class);
1525  }
1526 
1537  protected function ‪modifyHTMLColor($color, $R, $G, $B)
1538  {
1539  // This takes a hex-color (# included!) and adds $R, $G and $B to the HTML-color (format: #xxxxxx) and returns the new color
1540  $nR = ‪MathUtility::forceIntegerInRange(hexdec(substr($color, 1, 2)) + $R, 0, 255);
1541  $nG = ‪MathUtility::forceIntegerInRange(hexdec(substr($color, 3, 2)) + $G, 0, 255);
1542  $nB = ‪MathUtility::forceIntegerInRange(hexdec(substr($color, 5, 2)) + $B, 0, 255);
1543  return '#' . substr('0' . dechex($nR), -2) . substr('0' . dechex($nG), -2) . substr('0' . dechex($nB), -2);
1544  }
1545 
1554  protected function ‪modifyHTMLColorAll($color, $all)
1555  {
1556  return $this->‪modifyHTMLColor($color, $all, $all, $all);
1557  }
1558 
1567  protected static function ‪getLogger()
1568  {
1569  return GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
1570  }
1571 }
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:23
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\modifyHTMLColorAll
‪string modifyHTMLColorAll($color, $all)
Definition: TypoScriptParser.php:1528
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\regHighLight
‪regHighLight($code, $pointer, $strlen=-1)
Definition: TypoScriptParser.php:1424
‪TYPO3\CMS\Core\Utility\PathUtility\dirname
‪static string dirname($path)
Definition: PathUtility.php:185
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$commentSet
‪bool $commentSet
Definition: TypoScriptParser.php:95
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
Definition: TypoScriptParser.php:37
‪TYPO3\CMS\Core\TypoScript\Parser
Definition: TypoScriptParser.php:2
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$sections
‪array $sections
Definition: TypoScriptParser.php:132
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\importExternalTypoScriptFile
‪static string importExternalTypoScriptFile($filename, $cycleCounter, $returnFiles, array &$includedFiles)
Definition: TypoScriptParser.php:990
‪TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher
Definition: ConditionMatcher.php:30
‪$finder
‪$finder
Definition: annotationChecker.php:102
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$deprecatedPublicProperties
‪$deprecatedPublicProperties
Definition: TypoScriptParser.php:39
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$highLightData
‪array $highLightData
Definition: TypoScriptParser.php:150
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$multiLineObject
‪string $multiLineObject
Definition: TypoScriptParser.php:107
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$highLightData_bracelevel
‪array $highLightData_bracelevel
Definition: TypoScriptParser.php:156
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$sectionsMatch
‪array $sectionsMatch
Definition: TypoScriptParser.php:138
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\typoscriptIncludeError
‪static string typoscriptIncludeError($error)
Definition: TypoScriptParser.php:1202
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$lastConditionTrue
‪bool $lastConditionTrue
Definition: TypoScriptParser.php:126
‪TYPO3\CMS\Core\TypoScript\ExtendedTemplateService
Definition: ExtendedTemplateService.php:39
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\executeValueModifier
‪string null executeValueModifier($modifierName, $modifierArgument=null, $currentValue=null)
Definition: TypoScriptParser.php:543
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$breakPointLN
‪int $breakPointLN
Definition: TypoScriptParser.php:186
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$lineNumberOffset
‪int $lineNumberOffset
Definition: TypoScriptParser.php:180
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\doSyntaxHighlight
‪string doSyntaxHighlight($string, $lineNum='', $highlightBlockMode=false)
Definition: TypoScriptParser.php:1405
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\includeDirectory
‪static includeDirectory($dirPath, $cycle_counter=1, $returnFiles=false, &$newString='', array &$includedFiles=[], $optionalProperties='', $parentFilenameOrPath='')
Definition: TypoScriptParser.php:1160
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static string basename($path)
Definition: PathUtility.php:164
‪TYPO3\CMS\Core\Utility\StringUtility\removeByteOrderMark
‪static string removeByteOrderMark(string $input)
Definition: StringUtility.php:117
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\getVal
‪array getVal($string, $setup)
Definition: TypoScriptParser.php:656
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$deprecatedPublicMethods
‪$deprecatedPublicMethods
Definition: TypoScriptParser.php:57
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\modifyHTMLColor
‪string modifyHTMLColor($color, $R, $G, $B)
Definition: TypoScriptParser.php:1511
‪TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractConditionMatcher
Definition: AbstractConditionMatcher.php:37
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$highLightBlockStyles_basecolor
‪string $highLightBlockStyles_basecolor
Definition: TypoScriptParser.php:230
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\rollParseSub
‪string rollParseSub($string, array &$setup)
Definition: TypoScriptParser.php:631
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$rawP
‪int $rawP
Definition: TypoScriptParser.php:83
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$syntaxHighLight
‪bool $syntaxHighLight
Definition: TypoScriptParser.php:144
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$setup
‪array $setup
Definition: TypoScriptParser.php:71
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$errors
‪array $errors
Definition: TypoScriptParser.php:174
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$raw
‪array $raw
Definition: TypoScriptParser.php:77
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\checkIncludeLines
‪static string array checkIncludeLines($string, $cycle_counter=1, $returnFiles=false, $parentFilenameOrPath='')
Definition: TypoScriptParser.php:814
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$parentObject
‪TYPO3 CMS Core TypoScript ExtendedTemplateService $parentObject
Definition: TypoScriptParser.php:234
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$regLinenumbers
‪bool $regLinenumbers
Definition: TypoScriptParser.php:168
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$highLightStyles
‪array $highLightStyles
Definition: TypoScriptParser.php:190
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\extractIncludes
‪static string extractIncludes($string, $cycle_counter=1, array $extractedFileNames=[], $parentFilenameOrPath='')
Definition: TypoScriptParser.php:1236
‪TYPO3\CMS\Core\Compatibility\PublicMethodDeprecationTrait
Definition: PublicMethodDeprecationTrait.php:68
‪TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher
Definition: ConditionMatcher.php:33
‪debug
‪debug($variable='', $title=null, $group=null)
Definition: GlobalDebugFunctions.php:5
‪statements
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$multiLineValue
‪array $multiLineValue
Definition: TypoScriptParser.php:113
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$lastComment
‪string $lastComment
Definition: TypoScriptParser.php:89
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\syntaxHighlight_print
‪string syntaxHighlight_print($lineNumDat, $highlightBlockMode)
Definition: TypoScriptParser.php:1442
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Log\LogManager
Definition: LogManager.php:25
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:39
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\parseNextKeySegment
‪array parseNextKeySegment($key)
Definition: TypoScriptParser.php:746
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:21
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\parse
‪parse($string, $matchObj='')
Definition: TypoScriptParser.php:242
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\extractIncludes_array
‪static array extractIncludes_array(array $array)
Definition: TypoScriptParser.php:1383
‪TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait
Definition: PublicPropertyDeprecationTrait.php:66
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$multiLineEnabled
‪bool $multiLineEnabled
Definition: TypoScriptParser.php:101
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$regComments
‪bool $regComments
Definition: TypoScriptParser.php:162
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\getLogger
‪static LoggerInterface getLogger()
Definition: TypoScriptParser.php:1541
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\parseSub
‪string null parseSub(array &$setup)
Definition: TypoScriptParser.php:315
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:21
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\error
‪error($err, $num=2)
Definition: TypoScriptParser.php:794
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\nextDivider
‪string nextDivider()
Definition: TypoScriptParser.php:297
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\getTimeTracker
‪TimeTracker getTimeTracker()
Definition: TypoScriptParser.php:1496
‪TYPO3\CMS\Core\TimeTracker\TimeTracker
Definition: TimeTracker.php:27
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\setVal
‪setVal($string, array &$setup, $value, $wipeOut=false)
Definition: TypoScriptParser.php:689
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\includeFile
‪static includeFile($filename, $cycle_counter=1, $returnFiles=false, &$newString='', array &$includedFiles=[], $optionalProperties='', $parentFilenameOrPath='')
Definition: TypoScriptParser.php:1094
‪TYPO3\CMS\Core\Utility\PathUtility\getAbsolutePathOfRelativeReferencedFileOrPath
‪static string getAbsolutePathOfRelativeReferencedFileOrPath($baseFilenameOrPath, $includeFileName)
Definition: PathUtility.php:255
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$highLightBlockStyles
‪string $highLightBlockStyles
Definition: TypoScriptParser.php:224
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\addImportsFromExternalFiles
‪static string addImportsFromExternalFiles($typoScript, $cycleCounter, $returnFiles, &$includedFiles, &$parentFilenameOrPath)
Definition: TypoScriptParser.php:954
‪TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser\$inBrace
‪int $inBrace
Definition: TypoScriptParser.php:119