17 use Psr\Log\LoggerInterface;
18 use Symfony\Component\Finder\Finder;
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.',
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.',
214 'prespace' => [
'<span class="ts-prespace">',
'</span>'],
216 'objstr_postspace' => [
'<span class="ts-objstr_postspace">',
'</span>'],
218 'operator_postspace' => [
'<span class="ts-operator_postspace">',
'</span>'],
220 'operator' => [
'<span class="ts-operator">',
'</span>'],
222 'value' => [
'<span class="ts-value">',
'</span>'],
224 'objstr' => [
'<span class="ts-objstr">',
'</span>'],
226 'value_copy' => [
'<span class="ts-value_copy">',
'</span>'],
228 'value_unset' => [
'<span class="ts-value_unset">',
'</span>'],
230 'ignored' => [
'<span class="ts-ignored">',
'</span>'],
232 'default' => [
'<span class="ts-default">',
'</span>'],
234 'comment' => [
'<span class="ts-comment">',
'</span>'],
236 'condition' => [
'<span class="ts-condition">',
'</span>'],
238 'error' => [
'<span class="ts-error">',
'</span>'],
240 'linenum' => [
'<span class="ts-linenum">',
'</span>']
268 public function parse($string, $matchObj =
'')
270 $this->raw = explode(LF, $string);
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);
279 $this->
error(
'Empty condition is always false, this does not make sense. At line ' . ($this->lineNumberOffset + $this->rawP - 1), 2);
282 $preUppercase = strtoupper($pre);
283 if ($pre[0] ===
'[' &&
284 ($preUppercase ===
'[GLOBAL]' ||
285 $preUppercase ===
'[END]' ||
286 !$this->lastConditionTrue && $preUppercase ===
'[ELSE]')
288 $pre = trim($this->
parseSub($this->setup));
289 $this->lastConditionTrue =
true;
292 $specificSection = $preUppercase !==
'[ELSE]';
293 if ($specificSection) {
294 $this->sections[md5($pre)] = $pre;
296 if (is_object($matchObj) && $matchObj->match($pre) || $this->syntaxHighLight) {
297 if ($specificSection) {
298 $this->sectionsMatch[md5($pre)] = $pre;
300 $pre = trim($this->
parseSub($this->setup));
301 $this->lastConditionTrue =
true;
304 $this->lastConditionTrue =
false;
308 if ($this->inBrace) {
309 $this->
error(
'Line ' . ($this->lineNumberOffset + $this->rawP - 1) .
': The script is short of ' . $this->inBrace .
' end brace(s)', 1);
311 if ($this->multiLineEnabled) {
312 $this->
error(
'Line ' . ($this->lineNumberOffset + $this->rawP - 1) .
': A multiline value section is not ended with a parenthesis!', 1);
314 $this->lineNumberOffset += count($this->raw) + 1;
325 while (isset($this->raw[$this->rawP])) {
326 $line = trim($this->raw[$this->rawP]);
328 if ($line && $line[0] ===
'[') {
343 while (isset($this->raw[$this->rawP])) {
344 $line = ltrim($this->raw[$this->rawP]);
347 if ($this->syntaxHighLight) {
352 if ($this->breakPointLN && $this->lineNumberOffset + $this->rawP - 1 === $this->breakPointLN + 1) {
356 if (!$this->multiLineEnabled && strpos($line,
'/*') === 0) {
357 $this->commentSet =
true;
360 if (!$this->commentSet && ($line || $this->multiLineEnabled)) {
362 if ($this->multiLineEnabled) {
364 if (!empty($line[0]) && $line[0] ===
')') {
365 if ($this->syntaxHighLight) {
369 $this->multiLineEnabled =
false;
370 $theValue = implode(LF, $this->multiLineValue);
371 if (strpos($this->multiLineObject,
'.') !==
false) {
377 if ($this->lastComment && $this->regComments) {
380 if ($this->regLinenumbers) {
381 $setup[$this->multiLineObject .
'.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
385 if ($this->syntaxHighLight) {
388 $this->multiLineValue[] = $this->raw[$this->rawP - 1];
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);
396 if ($this->syntaxHighLight) {
402 if ($line[0] ===
'[' && stripos($line,
'[GLOBAL]') !==
false) {
403 if ($this->syntaxHighLight) {
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);
410 if ($line[0] !==
'}' && $line[0] !==
'#' && $line[0] !==
'/') {
413 $varL = strcspn($line,
"\t" .
' {=<>(');
415 if ($varL > 0 && substr($line, $varL - 1, 2) ===
':=') {
419 $objStrName = substr($line, 0, $varL);
420 if ($this->syntaxHighLight) {
421 $this->
regHighLight(
'objstr', $lineP, strlen(substr($line, $varL)));
423 if ($objStrName !==
'') {
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: "_:-\\."');
428 $line = ltrim(substr($line, $varL));
429 if ($this->syntaxHighLight) {
430 $this->
regHighLight(
'objstr_postspace', $lineP, strlen($line));
433 $this->
regHighLight(
'operator_postspace', $lineP, strlen(ltrim(substr($line, 1))));
437 $this->
error(
'Line ' . ($this->lineNumberOffset + $this->rawP - 1) .
': Object Name String, "' . htmlspecialchars($objStrName) .
'" was not followed by any operator, =<>({');
441 if ($line[0] ===
':' && preg_match(
'/^:=\\s*([[:alpha:]]+)\\s*\\((.*)\\).*/', $line, $match)) {
443 $tsFuncArg = $match[2];
445 $currentValue = $val[0] ??
null;
446 $tsFuncArg = str_replace([
'\\\\',
'\\n',
'\\t'], [
'\\', LF,
"\t"], $tsFuncArg);
448 if (isset($newValue)) {
449 $line =
'= ' . $newValue;
454 if ($this->syntaxHighLight) {
455 $this->
regHighLight(
'value', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
457 if (strpos($objStrName,
'.') !==
false) {
459 $value[0] = trim(substr($line, 1));
462 $setup[$objStrName] = trim(substr($line, 1));
463 if ($this->lastComment && $this->regComments) {
467 if ($this->regLinenumbers) {
468 $setup[$objStrName .
'.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
474 if (strpos($objStrName,
'.') !==
false) {
480 if (!isset(
$setup[$objStrName .
'.'])) {
481 $setup[$objStrName .
'.'] = [];
483 $exitSig = $this->
parseSub($setup[$objStrName .
'.']);
490 $this->multiLineObject = $objStrName;
491 $this->multiLineEnabled =
true;
492 $this->multiLineValue = [];
495 if ($this->syntaxHighLight) {
496 $this->
regHighLight(
'value_copy', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
498 $theVal = trim(substr($line, 1));
499 if ($theVal[0] ===
'.') {
502 $res = $this->
getVal($theVal, $this->setup);
506 $this->
setVal($objStrName,
$setup, unserialize(serialize($res), [
'allowed_classes' =>
false]), 1);
509 if ($this->syntaxHighLight) {
510 $this->
regHighLight(
'value_unset', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
515 $this->
error(
'Line ' . ($this->lineNumberOffset + $this->rawP - 1) .
': Object Name String, "' . htmlspecialchars($objStrName) .
'" was not followed by any operator, =<>({');
519 $this->lastComment =
'';
521 } elseif ($line[0] ===
'}') {
523 $this->lastComment =
'';
524 if ($this->syntaxHighLight) {
527 if ($this->inBrace < 0) {
528 $this->
error(
'Line ' . ($this->lineNumberOffset + $this->rawP - 1) .
': An end brace is in excess.', 1);
534 if ($this->syntaxHighLight) {
538 if ($this->regComments) {
539 $this->lastComment .= rtrim($line) . LF;
542 if (strpos($line,
'### ERROR') === 0) {
548 if ($this->commentSet) {
549 if ($this->syntaxHighLight) {
552 if (strpos($line,
'*/') !==
false) {
553 $this->commentSet =
false;
569 protected function executeValueModifier($modifierName, $modifierArgument =
null, $currentValue =
null)
572 switch ($modifierName) {
573 case 'prependString':
574 $newValue = $modifierArgument . $currentValue;
577 $newValue = $currentValue . $modifierArgument;
580 $newValue = str_replace($modifierArgument,
'', $currentValue);
582 case 'replaceString':
583 $modifierArgumentArray = explode(
'|', $modifierArgument, 2);
584 $fromStr = $modifierArgumentArray[0] ??
'';
585 $toStr = $modifierArgumentArray[1] ??
'';
586 $newValue = str_replace($fromStr, $toStr, $currentValue);
589 $newValue = ((string)$currentValue !==
'' ? $currentValue .
',' :
'') . $modifierArgument;
591 case 'removeFromList':
592 $existingElements = GeneralUtility::trimExplode(
',', $currentValue);
593 $removeElements = GeneralUtility::trimExplode(
',', $modifierArgument);
594 if (!empty($removeElements)) {
595 $newValue = implode(
',', array_diff($existingElements, $removeElements));
599 $elements = GeneralUtility::trimExplode(
',', $currentValue);
600 $newValue = implode(
',', array_unique($elements));
603 $elements = GeneralUtility::trimExplode(
',', $currentValue);
604 $newValue = implode(
',', array_reverse($elements));
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;
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);
623 sort($elements, $sort_flags);
624 if (in_array(
'descending', $arguments)) {
625 $elements = array_reverse($elements);
627 $newValue = implode(
',', $elements);
630 $environmentValue = getenv(trim($modifierArgument));
631 if ($environmentValue !==
false) {
632 $newValue = $environmentValue;
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];
640 $newValue = GeneralUtility::callUserFunction($hookMethod, $params, $fakeThis);
642 self::getLogger()->warning(
'Missing function definition for ' . $modifierName .
' on TypoScript');
659 if ((
string)$string ===
'') {
668 $exitSig = $remainingKey ===
''
671 return $exitSig ?:
'';
684 if ((
string)$string ===
'') {
689 $subKey = $key .
'.';
690 if ($remainingKey ===
'') {
715 protected function setVal($string, array &
$setup, $value, $wipeOut =
false)
717 if ((
string)$string ===
'') {
722 $subKey = $key .
'.';
723 if ($remainingKey ===
'') {
724 if ($value ===
'UNSET') {
727 if ($this->regLinenumbers) {
728 $setup[$key .
'.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) .
'>';
735 if ($this->regLinenumbers) {
736 $setup[$key .
'.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) .
'<';
740 if (isset($value[0])) {
743 if (isset($value[1])) {
746 if ($this->lastComment && $this->regComments) {
749 if ($this->regLinenumbers && !$lnRegisDone) {
750 $setup[$key .
'.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
754 if (!isset(
$setup[$subKey])) {
775 $dotPosition = strpos($key,
'.');
776 if ($dotPosition ===
false) {
780 if (strpos($key,
'\\') !==
false) {
783 while ($dotPosition !==
false) {
784 if ($dotPosition > 0 && $key[$dotPosition - 1] !==
'\\' || $dotPosition > 1 && $key[$dotPosition - 2] ===
'\\') {
788 $dotPosition = strpos($key,
'.', $dotPosition + 1);
791 if ($dotPosition ===
false) {
796 if ($dotPosition > 1 && $key[$dotPosition - 2] ===
'\\' && $key[$dotPosition - 1] ===
'\\') {
797 $keySegment = substr($key, 0, $dotPosition - 1);
799 $keySegment = substr($key, 0, $dotPosition);
801 $remainingKey = substr($key, $dotPosition + 1);
805 $keySegment = str_replace(
'\\.',
'.', $keySegment);
808 list($keySegment, $remainingKey) = explode(
'.', $key, 2);
810 return [$keySegment, $remainingKey];
820 protected function error($err, $num = 2)
824 $tt->setTSlogMessage($err, $num);
840 public static function checkIncludeLines($string, $cycle_counter = 1, $returnFiles =
false, $parentFilenameOrPath =
'')
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');
848 'files' => $includedFiles
853 ### ERROR: Recursion!
858 if ($string !==
null) {
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);
870 $newString = $parts[0] . LF;
871 $partCount = count($parts);
872 for ($i = 1; $i + 3 < $partCount; $i += 4) {
877 $includeType = $parts[$i];
878 $filename = $parts[$i + 1];
879 $originalFilename = $filename;
880 $optionalProperties = $parts[$i + 2];
881 $tsContentsTillNextInclude = $parts[$i + 3];
884 $matches = preg_split(
'#(?i)condition\\s*=\\s*"((?:\\\\\\\\|\\\\"|[^\\"])*)"(\\s*|>)#', $optionalProperties, 2, PREG_SPLIT_DELIM_CAPTURE);
886 if (count($matches) > 1) {
888 $condition = trim(stripslashes($matches[1]));
890 if ($condition[0] !==
'[') {
891 $condition =
'[' . $condition .
']';
895 $conditionMatcher =
null;
896 if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_FE) {
897 $conditionMatcher = GeneralUtility::makeInstance(FrontendConditionMatcher::class);
899 $conditionMatcher = GeneralUtility::makeInstance(BackendConditionMatcher::class);
903 if (!$conditionMatcher->match($condition)) {
904 $newString .= $tsContentsTillNextInclude . LF;
910 if ($parentFilenameOrPath !==
'' && $filename[0] ===
'.') {
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) {
921 switch (strtolower($includeType)) {
923 self::includeFile($originalFilename, $cycle_counter, $returnFiles, $newString, $includedFiles, $optionalProperties, $parentFilenameOrPath);
926 self::includeDirectory($originalFilename, $cycle_counter, $returnFiles, $newString, $includedFiles, $optionalProperties, $parentFilenameOrPath);
933 $newString .= $tsContentsTillNextInclude . LF;
938 if (strpos(strtolower($filename),
'ext:') === 0) {
939 $filePointerPathParts = explode(
'/', substr($filename, 4));
942 list($includeType, ) = explode(
'.', array_pop($filePointerPathParts));
944 if (in_array($includeType, [
'setup',
'constants'])) {
946 $filePointerPathParts[0] = str_replace(
'_',
'', $filePointerPathParts[0]);
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'];
957 $string = LF . trim($newString) . LF;
963 'typoscript' => $string,
964 'files' => $includedFiles
980 protected static function addImportsFromExternalFiles($typoScript, $cycleCounter, $returnFiles, &$includedFiles, &$parentFilenameOrPath)
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);
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];
993 if ($parentFilenameOrPath !==
'' && $filename[0] ===
'.') {
998 $newString .= $tsContentsTillNextInclude;
1001 $typoScript = LF . trim($newString) . LF;
1018 if (strpos(
'..', $filename) !==
false) {
1023 $absoluteFileName = GeneralUtility::getFileAbsFileName($filename);
1024 if ((
string)$absoluteFileName ===
'') {
1037 if (is_dir($absoluteFileName)) {
1040 $readableFilePrefix = $filename;
1046 if (!is_file($absoluteFileName)
1049 $absoluteFileName .=
'*.typoscript';
1053 }
catch (\InvalidArgumentException $e) {
1058 foreach (
$finder as $fileObject) {
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.
');
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
'];
1075 $content .= $included_text . LF;
1077 $content .= '### @
import \
'' . $readableFileName .
'\' end ###
' . LF . LF;
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));
1087 if (in_array($includeType, ['setup
', 'constants
'], true)) {
1088 // adapt extension key to required format (no underscores)
1089 $filePointerPathParts[0] = str_replace('_
', '', $filePointerPathParts[0]);
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
'];
1100 if (empty($content)) {
1101 return self::typoscriptIncludeError('No file or folder found
for importing TypoScript on
"' . $filename . '".
');
1120 public static function includeFile($filename, $cycle_counter = 1, $returnFiles = false, &$newString = '', array &$includedFiles = [], $optionalProperties = '', $parentFilenameOrPath = '')
1122 // Resolve a possible relative paths if a parent file is given
1123 if ($parentFilenameOrPath !== '' && $filename[0] === '.
') {
1124 $absfilename = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $filename);
1126 $absfilename = $filename;
1128 $absfilename = GeneralUtility::getFileAbsFileName($absfilename);
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.
');
1137 $fileExists = false;
1138 if (@file_exists($absfilename)) {
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);
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
'];
1162 $newString .= $included_text . LF;
1164 $newString .= self::typoscriptIncludeError('File
"' . $filename . '" was not found.
');
1168 $newString .= '### <INCLUDE_TYPOSCRIPT: source=
"FILE:' . $filename . '"' . $optionalProperties . '> END:
' . LF . LF;
1186 protected static function includeDirectory($dirPath, $cycle_counter = 1, $returnFiles = false, &$newString = '', array &$includedFiles = [], $optionalProperties = '', $parentFilenameOrPath = '')
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];
1193 $includedFileExtensions = '';
1196 // Resolve a possible relative paths if a parent file is given
1197 if ($parentFilenameOrPath !== '' && $dirPath[0] === '.') {
1198 $resolvedDirPath = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $dirPath);
1200 $resolvedDirPath = $dirPath;
1202 $absDirPath = GeneralUtility::getFileAbsFileName($resolvedDirPath);
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);
1214 $newString .= '### <INCLUDE_TYPOSCRIPT: source="DIR:
' . $dirPath . '"' . $optionalProperties . '> END:' . LF . LF;
1216 $newString .= self::typoscriptIncludeError('The path "' . $resolvedDirPath . '" is invalid.');
1228 protected static function typoscriptIncludeError($error)
1230 self::getLogger()->warning($error);
1231 return "\n###\n### ERROR:
" . $error . "\n###\n\n
";
1240 public static function checkIncludeLines_array(array $array)
1242 foreach ($array as $k => $v) {
1243 $array[$k] = self::checkIncludeLines($array[$k]);
1262 public static function extractIncludes($string, $cycle_counter = 1, array $extractedFileNames = [], $parentFilenameOrPath = '')
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');
1268 ### ERROR: Recursion!
1272 $expectedEndTag = '';
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) === '') {
1288 $skipNextLineIfEmpty = false;
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
1297 // Save this line in case there is no ending tag
1298 $openingCommentedIncludeStatement = trim($line);
1299 $openingCommentedIncludeStatement = preg_replace('/\\s*### Warning: .*###\\s*/
', '', $openingCommentedIncludeStatement);
1301 // type of match: FILE or DIR
1302 $inIncludePart = strtoupper($matches[1]);
1303 $fileName = $matches[2];
1304 $optionalProperties = $matches[3];
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));
1310 // If this is not a beginning commented include statement this line goes into the rest content
1311 $restContent[] = $line;
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);
1321 // Write the content to the file
1323 // Resolve a possible relative paths if a parent file is given
1324 if ($parentFilenameOrPath !== '' && $fileName[0] === '.
') {
1325 $realFileName = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $fileName);
1327 $realFileName = $fileName;
1329 $realFileName = GeneralUtility::getFileAbsFileName($realFileName);
1331 if ($inIncludePart === 'FILE
') {
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);
1336 if (empty($realFileName)) {
1337 throw new \UnexpectedValueException(sprintf('"%s" is not a valid file location.
', $fileName), 1294586441);
1339 if (!is_writable($realFileName)) {
1340 throw new \RuntimeException(sprintf('"%s" is not writable.
', $fileName), 1294586442);
1342 if (in_array($realFileName, $extractedFileNames)) {
1343 throw new \RuntimeException(sprintf('Recursive/multiple inclusion of file
"%s"', $realFileName), 1294586443);
1345 $extractedFileNames[] = $realFileName;
1347 // Recursive call to detected nested commented include statements
1348 $fileContentString = self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName);
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);
1354 // Insert reference to the file in the rest content
1355 $restContent[] = '<INCLUDE_TYPOSCRIPT: source=
"FILE:' . $fileName . '"' . $optionalProperties . '>
';
1360 if (empty($realFileName)) {
1361 throw new \UnexpectedValueException(sprintf('"%s" is not a valid location.
', $fileName), 1366493602);
1363 if (!is_dir($realFileName)) {
1364 throw new \RuntimeException(sprintf('"%s" is not a directory.
', $fileName), 1366493603);
1366 if (in_array($realFileName, $extractedFileNames)) {
1367 throw new \RuntimeException(sprintf('Recursive/multiple inclusion of directory
"%s"', $realFileName), 1366493604);
1369 $extractedFileNames[] = $realFileName;
1371 // Recursive call to detected nested commented include statements
1372 self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName);
1374 // just drop content between tags since it should usually just contain individual files from that dir
1376 // Insert reference to the dir in the rest content
1377 $restContent[] = '<INCLUDE_TYPOSCRIPT: source=
"DIR:' . $fileName . '"' . $optionalProperties . '>
';
1380 // Reset variables (preparing for the next commented include statement)
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;
1389 // If this is not an ending commented include statement this line goes into the file content
1390 $fileContent[] = $line;
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);
1399 $restContentString = implode(PHP_EOL, $restContent);
1400 return $restContentString;
1411 foreach ($array as $k => $v) {
1431 public function doSyntaxHighlight($string, $lineNum =
'', $highlightBlockMode =
false)
1433 $this->syntaxHighLight =
true;
1434 $this->highLightData = [];
1437 $string = str_replace(CR,
'', $string);
1450 protected function regHighLight($code, $pointer, $strlen = -1)
1452 if ($strlen === -1) {
1453 $this->highLightData[$pointer] = [[$code, 0]];
1455 $this->highLightData[$pointer][] = [$code, $strlen];
1472 foreach ($this->errors as $err) {
1473 $errA[$err[2]][] = $err[0];
1477 foreach ($this->raw as
$rawP => $value) {
1479 $strlen = strlen($value);
1481 if (is_array($this->highLightData[
$rawP])) {
1482 foreach ($this->highLightData[
$rawP] as $set) {
1483 $len = $strlen - $start - $set[1];
1485 $part = substr($value, $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];
1491 } elseif ($len < 0) {
1498 if (strlen($value) > $start) {
1499 $lineC .= $this->highLightStyles[
'ignored'][0] . htmlspecialchars(substr($value, $start)) . $this->highLightStyles[
'ignored'][1];
1502 $lineC .= $this->highLightStyles[
'error'][0] .
'<strong> - ERROR:</strong> ' . htmlspecialchars(implode(
';', $errA[
$rawP])) . $this->highLightStyles[
'error'][1];
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 :
' ') .
'</span>';
1507 if (is_array($lineNumDat)) {
1508 $lineNum =
$rawP + $lineNumDat[0];
1509 if ($this->parentObject instanceof ExtendedTemplateService) {
1510 $lineNum = $this->parentObject->ext_lnBreakPointWrap($lineNum, $lineNum);
1512 $lineC = $this->highLightStyles[
'linenum'][0] . str_pad($lineNum, 4,
' ', STR_PAD_LEFT) .
':' . $this->highLightStyles[
'linenum'][1] .
' ' . $lineC;
1516 return '<pre class="ts-hl">' . implode(LF, $lines) .
'</pre>';
1524 return GeneralUtility::makeInstance(TimeTracker::class);
1543 return '#' . substr(
'0' . dechex($nR), -2) . substr(
'0' . dechex($nG), -2) . substr(
'0' . dechex($nB), -2);
1569 return GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);