TYPO3 CMS  TYPO3_6-2
TypoScriptParser.php
Go to the documentation of this file.
1 <?php
3 
19 
26 
27  // If set, then key names cannot contain characters other than [:alnum:]_\.-
31  public $strict = 1;
32 
33  // Internal
34  // TypoScript hierarchy being build during parsing.
38  public $setup = array();
39 
40  // Raw data, the input string exploded by LF
44  public $raw;
45 
46  // Pointer to entry in raw data array
50  public $rawP;
51 
52  // Holding the value of the last comment
56  public $lastComment = '';
57 
58  // Internally set, used as internal flag to create a multi-line comment (one of those like /*... */)
62  public $commentSet = 0;
63 
64  // Internally set, when multiline value is accumulated
68  public $multiLineEnabled = 0;
69 
70  // Internally set, when multiline value is accumulated
74  public $multiLineObject = '';
75 
76  // Internally set, when multiline value is accumulated
80  public $multiLineValue = array();
81 
82  // Internally set, when in brace. Counter.
86  public $inBrace = 0;
87 
88  // For each condition this flag is set, if the condition is TRUE, else it's cleared. Then it's used by the [ELSE] condition to determine if the next part should be parsed.
92  public $lastConditionTrue = 1;
93 
94  // Tracking all conditions found
98  public $sections = array();
99 
100  // Tracking all matching conditions found
104  public $sectionsMatch = array();
105 
106  // If set, then syntax highlight mode is on; Call the function syntaxHighlight() to use this function
110  public $syntaxHighLight = 0;
111 
112  // Syntax highlight data is accumulated in this array. Used by syntaxHighlight_print() to construct the output.
116  public $highLightData = array();
117 
118  // Syntax highlight data keeping track of the curly brace level for each line
122  public $highLightData_bracelevel = array();
123 
124  // Debugging, analysis:
125  // DO NOT register the comments. This is default for the ordinary sitetemplate!
129  public $regComments = 0;
130 
131  // DO NOT register the linenumbers. This is default for the ordinary sitetemplate!
135  public $regLinenumbers = 0;
136 
137  // Error accumulation array.
141  public $errors = array();
142 
143  // Used for the error messages line number reporting. Set externally.
147  public $lineNumberOffset = 0;
148 
149  // Line for break point.
153  public $breakPointLN = 0;
154 
158  public $highLightStyles = array(
159  'prespace' => array('<span class="ts-prespace">', '</span>'),
160  // Space before any content on a line
161  'objstr_postspace' => array('<span class="ts-objstr_postspace">', '</span>'),
162  // Space after the object string on a line
163  'operator_postspace' => array('<span class="ts-operator_postspace">', '</span>'),
164  // Space after the operator on a line
165  'operator' => array('<span class="ts-operator">', '</span>'),
166  // The operator char
167  'value' => array('<span class="ts-value">', '</span>'),
168  // The value of a line
169  'objstr' => array('<span class="ts-objstr">', '</span>'),
170  // The object string of a line
171  'value_copy' => array('<span class="ts-value_copy">', '</span>'),
172  // The value when the copy syntax (<) is used; that means the object reference
173  'value_unset' => array('<span class="ts-value_unset">', '</span>'),
174  // The value when an object is unset. Should not exist.
175  'ignored' => array('<span class="ts-ignored">', '</span>'),
176  // The "rest" of a line which will be ignored.
177  'default' => array('<span class="ts-default">', '</span>'),
178  // The default style if none other is applied.
179  'comment' => array('<span class="ts-comment">', '</span>'),
180  // Comment lines
181  'condition' => array('<span class="ts-condition">', '</span>'),
182  // Conditions
183  'error' => array('<span class="ts-error">', '</span>'),
184  // Error messages
185  'linenum' => array('<span class="ts-linenum">', '</span>')
186  );
187 
188  // Additional attributes for the <span> tags for a blockmode line
193 
194  // The hex-HTML color for the blockmode
199 
200  //Instance of parentObject, used by \TYPO3\CMS\Core\TypoScript\ExtendedTemplateService
202 
212  public function parse($string, $matchObj = '') {
213  $this->raw = explode(LF, $string);
214  $this->rawP = 0;
215  $pre = '[GLOBAL]';
216  while ($pre) {
217  if ($this->breakPointLN && $pre === '[_BREAK]') {
218  $this->error('Breakpoint at ' . ($this->lineNumberOffset + $this->rawP - 2) . ': Line content was "' . $this->raw[($this->rawP - 2)] . '"', 1);
219  break;
220  }
221  $preUppercase = strtoupper($pre);
222  if ($pre[0] === '[' &&
223  ($preUppercase === '[GLOBAL]' ||
224  $preUppercase === '[END]' ||
225  !$this->lastConditionTrue && $preUppercase === '[ELSE]')
226  ) {
227  $pre = trim($this->parseSub($this->setup));
228  $this->lastConditionTrue = 1;
229  } else {
230  // We're in a specific section. Therefore we log this section
231  $specificSection = $preUppercase !== '[ELSE]';
232  if ($specificSection) {
233  $this->sections[md5($pre)] = $pre;
234  }
235  if (is_object($matchObj) && $matchObj->match($pre) || $this->syntaxHighLight) {
236  if ($specificSection) {
237  $this->sectionsMatch[md5($pre)] = $pre;
238  }
239  $pre = trim($this->parseSub($this->setup));
240  $this->lastConditionTrue = 1;
241  } else {
242  $pre = $this->nextDivider();
243  $this->lastConditionTrue = 0;
244  }
245  }
246  }
247  if ($this->inBrace) {
248  $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': The script is short of ' . $this->inBrace . ' end brace(s)', 1);
249  }
250  if ($this->multiLineEnabled) {
251  $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': A multiline value section is not ended with a parenthesis!', 1);
252  }
253  $this->lineNumberOffset += count($this->raw) + 1;
254  }
255 
263  public function nextDivider() {
264  while (isset($this->raw[$this->rawP])) {
265  $line = trim($this->raw[$this->rawP]);
266  $this->rawP++;
267  if ($line && $line[0] === '[') {
268  return $line;
269  }
270  }
271  }
272 
280  public function parseSub(array &$setup) {
281  while (isset($this->raw[$this->rawP])) {
282  $line = ltrim($this->raw[$this->rawP]);
283  $lineP = $this->rawP;
284  $this->rawP++;
285  if ($this->syntaxHighLight) {
286  $this->regHighLight('prespace', $lineP, strlen($line));
287  }
288  // Breakpoint?
289  // By adding 1 we get that line processed
290  if ($this->breakPointLN && $this->lineNumberOffset + $this->rawP - 1 === $this->breakPointLN + 1) {
291  return '[_BREAK]';
292  }
293  // Set comment flag?
294  if (!$this->multiLineEnabled && strpos($line, '/*') === 0) {
295  $this->commentSet = 1;
296  }
297  // If $this->multiLineEnabled we will go and get the line values here because we know, the first if() will be TRUE.
298  if (!$this->commentSet && ($line || $this->multiLineEnabled)) {
299  // If multiline is enabled. Escape by ')'
300  if ($this->multiLineEnabled) {
301  // Multiline ends...
302  if ($line[0] === ')') {
303  if ($this->syntaxHighLight) {
304  $this->regHighLight('operator', $lineP, strlen($line) - 1);
305  }
306  // Disable multiline
307  $this->multiLineEnabled = 0;
308  $theValue = implode($this->multiLineValue, LF);
309  if (strpos($this->multiLineObject, '.') !== FALSE) {
310  // Set the value deeper.
311  $this->setVal($this->multiLineObject, $setup, array($theValue));
312  } else {
313  // Set value regularly
314  $setup[$this->multiLineObject] = $theValue;
315  if ($this->lastComment && $this->regComments) {
316  $setup[$this->multiLineObject . '..'] .= $this->lastComment;
317  }
318  if ($this->regLinenumbers) {
319  $setup[$this->multiLineObject . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
320  }
321  }
322  } else {
323  if ($this->syntaxHighLight) {
324  $this->regHighLight('value', $lineP);
325  }
326  $this->multiLineValue[] = $this->raw[$this->rawP - 1];
327  }
328  } elseif ($this->inBrace === 0 && $line[0] === '[') {
329  // Beginning of condition (only on level zero compared to brace-levels
330  if ($this->syntaxHighLight) {
331  $this->regHighLight('condition', $lineP);
332  }
333  return $line;
334  } else {
335  // Return if GLOBAL condition is set - no matter what.
336  if ($line[0] === '[' && stripos($line, '[GLOBAL]') !== FALSE) {
337  if ($this->syntaxHighLight) {
338  $this->regHighLight('condition', $lineP);
339  }
340  $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': On return to [GLOBAL] scope, the script was short of ' . $this->inBrace . ' end brace(s)', 1);
341  $this->inBrace = 0;
342  return $line;
343  } elseif ($line[0] !== '}' && $line[0] !== '#' && $line[0] !== '/') {
344  // If not brace-end or comment
345  // Find object name string until we meet an operator
346  $varL = strcspn($line, TAB . ' {=<>(');
347  // check for special ":=" operator
348  if ($varL > 0 && substr($line, $varL-1, 2) === ':=') {
349  --$varL;
350  }
351  // also remove tabs after the object string name
352  $objStrName = substr($line, 0, $varL);
353  if ($this->syntaxHighLight) {
354  $this->regHighLight('objstr', $lineP, strlen(substr($line, $varL)));
355  }
356  if ($objStrName !== '') {
357  $r = array();
358  if ($this->strict && preg_match('/[^[:alnum:]_\\\\\\.:-]/i', $objStrName, $r)) {
359  $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_:-\\."');
360  } else {
361  $line = ltrim(substr($line, $varL));
362  if ($this->syntaxHighLight) {
363  $this->regHighLight('objstr_postspace', $lineP, strlen($line));
364  if ($line !== '') {
365  $this->regHighLight('operator', $lineP, strlen($line) - 1);
366  $this->regHighLight('operator_postspace', $lineP, strlen(ltrim(substr($line, 1))));
367  }
368  }
369  if ($line === '') {
370  $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({');
371  } else {
372  // Checking for special TSparser properties (to change TS values at parsetime)
373  $match = array();
374  if ($line[0] === ':' && preg_match('/^:=\\s*([[:alpha:]]+)\\s*\\((.*)\\).*/', $line, $match)) {
375  $tsFunc = $match[1];
376  $tsFuncArg = $match[2];
377  list($currentValue) = $this->getVal($objStrName, $setup);
378  $tsFuncArg = str_replace(array('\\\\', '\\n', '\\t'), array('\\', LF, TAB), $tsFuncArg);
379  $newValue = $this->executeValueModifier($tsFunc, $tsFuncArg, $currentValue);
380  if (isset($newValue)) {
381  $line = '= ' . $newValue;
382  }
383  }
384  switch ($line[0]) {
385  case '=':
386  if ($this->syntaxHighLight) {
387  $this->regHighLight('value', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
388  }
389  if (strpos($objStrName, '.') !== FALSE) {
390  $value = array();
391  $value[0] = trim(substr($line, 1));
392  $this->setVal($objStrName, $setup, $value);
393  } else {
394  $setup[$objStrName] = trim(substr($line, 1));
395  if ($this->lastComment && $this->regComments) {
396  // Setting comment..
397  $setup[$objStrName . '..'] .= $this->lastComment;
398  }
399  if ($this->regLinenumbers) {
400  $setup[$objStrName . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
401  }
402  }
403  break;
404  case '{':
405  $this->inBrace++;
406  if (strpos($objStrName, '.') !== FALSE) {
407  $exitSig = $this->rollParseSub($objStrName, $setup);
408  if ($exitSig) {
409  return $exitSig;
410  }
411  } else {
412  if (!isset($setup[($objStrName . '.')])) {
413  $setup[$objStrName . '.'] = array();
414  }
415  $exitSig = $this->parseSub($setup[$objStrName . '.']);
416  if ($exitSig) {
417  return $exitSig;
418  }
419  }
420  break;
421  case '(':
422  $this->multiLineObject = $objStrName;
423  $this->multiLineEnabled = 1;
424  $this->multiLineValue = array();
425  break;
426  case '<':
427  if ($this->syntaxHighLight) {
428  $this->regHighLight('value_copy', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
429  }
430  $theVal = trim(substr($line, 1));
431  if ($theVal[0] === '.') {
432  $res = $this->getVal(substr($theVal, 1), $setup);
433  } else {
434  $res = $this->getVal($theVal, $this->setup);
435  }
436  $this->setVal($objStrName, $setup, unserialize(serialize($res)), 1);
437  // unserialize(serialize(...)) may look stupid but is needed because of some reference issues. See Kaspers reply to "[TYPO3-core] good question" from December 15 2005.
438  break;
439  case '>':
440  if ($this->syntaxHighLight) {
441  $this->regHighLight('value_unset', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
442  }
443  $this->setVal($objStrName, $setup, 'UNSET');
444  break;
445  default:
446  $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({');
447  }
448  }
449  }
450  $this->lastComment = '';
451  }
452  } elseif ($line[0] === '}') {
453  $this->inBrace--;
454  $this->lastComment = '';
455  if ($this->syntaxHighLight) {
456  $this->regHighLight('operator', $lineP, strlen($line) - 1);
457  }
458  if ($this->inBrace < 0) {
459  $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': An end brace is in excess.', 1);
460  $this->inBrace = 0;
461  } else {
462  break;
463  }
464  } else {
465  if ($this->syntaxHighLight) {
466  $this->regHighLight('comment', $lineP);
467  }
468  // Comment. The comments are concatenated in this temporary string:
469  if ($this->regComments) {
470  $this->lastComment .= rtrim($line) . LF;
471  }
472  }
473  if (GeneralUtility::isFirstPartOfStr($line, '### ERROR')) {
474  $this->error(substr($line, 11));
475  }
476  }
477  }
478  // Unset comment
479  if ($this->commentSet) {
480  if ($this->syntaxHighLight) {
481  $this->regHighLight('comment', $lineP);
482  }
483  if (strpos($line, '*/') === 0) {
484  $this->commentSet = 0;
485  }
486  }
487  }
488  }
489 
499  protected function executeValueModifier($modifierName, $modifierArgument = NULL, $currentValue = NULL) {
500  $newValue = NULL;
501  switch ($modifierName) {
502  case 'prependString':
503  $newValue = $modifierArgument . $currentValue;
504  break;
505  case 'appendString':
506  $newValue = $currentValue . $modifierArgument;
507  break;
508  case 'removeString':
509  $newValue = str_replace($modifierArgument, '', $currentValue);
510  break;
511  case 'replaceString':
512  list($fromStr, $toStr) = explode('|', $modifierArgument, 2);
513  $newValue = str_replace($fromStr, $toStr, $currentValue);
514  break;
515  case 'addToList':
516  $newValue = ((string)$currentValue !== '' ? $currentValue . ',' : '') . $modifierArgument;
517  break;
518  case 'removeFromList':
519  $existingElements = GeneralUtility::trimExplode(',', $currentValue);
520  $removeElements = GeneralUtility::trimExplode(',', $modifierArgument);
521  if (count($removeElements)) {
522  $newValue = implode(',', array_diff($existingElements, $removeElements));
523  }
524  break;
525  case 'uniqueList':
526  $elements = GeneralUtility::trimExplode(',', $currentValue);
527  $newValue = implode(',', array_unique($elements));
528  break;
529  case 'reverseList':
530  $elements = GeneralUtility::trimExplode(',', $currentValue);
531  $newValue = implode(',', array_reverse($elements));
532  break;
533  case 'sortList':
534  $elements = GeneralUtility::trimExplode(',', $currentValue);
535  $arguments = GeneralUtility::trimExplode(',', $modifierArgument);
536  $arguments = array_map('strtolower', $arguments);
537  $sort_flags = SORT_REGULAR;
538  if (in_array('numeric', $arguments)) {
539  $sort_flags = SORT_NUMERIC;
540  }
541  sort($elements, $sort_flags);
542  if (in_array('descending', $arguments)) {
543  $elements = array_reverse($elements);
544  }
545  $newValue = implode(',', $elements);
546  break;
547  default:
548  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName])) {
549  $hookMethod = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName];
550  $params = array('currentValue' => $currentValue, 'functionArgument' => $modifierArgument);
551  $fakeThis = FALSE;
552  $newValue = GeneralUtility::callUserFunction($hookMethod, $params, $fakeThis);
553  } else {
554  GeneralUtility::sysLog(
555  'Missing function definition for ' . $modifierName . ' on TypoScript',
556  'Core',
558  );
559  }
560  }
561  return $newValue;
562  }
563 
574  public function rollParseSub($string, array &$setup) {
575  if ((string)$string === '') {
576  return '';
577  }
578 
579  list($key, $remainingKey) = $this->parseNextKeySegment($string);
580  $key .= '.';
581  if (!isset($setup[$key])) {
582  $setup[$key] = array();
583  }
584  $exitSig = $remainingKey === ''
585  ? $this->parseSub($setup[$key])
586  : $this->rollParseSub($remainingKey, $setup[$key]);
587  return $exitSig ?: '';
588  }
589 
599  public function getVal($string, $setup) {
600  if ((string)$string === '') {
601  return array();
602  }
603 
604  list($key, $remainingKey) = $this->parseNextKeySegment($string);
605  $subKey = $key . '.';
606  if ($remainingKey === '') {
607  $retArr = array();
608  if (isset($setup[$key])) {
609  $retArr[0] = $setup[$key];
610  }
611  if (isset($setup[$subKey])) {
612  $retArr[1] = $setup[$subKey];
613  }
614  return $retArr;
615  } else {
616  if ($setup[$subKey]) {
617  return $this->getVal($remainingKey, $setup[$subKey]);
618  }
619  }
620  return array();
621  }
622 
633  public function setVal($string, array &$setup, $value, $wipeOut = FALSE) {
634  if ((string)$string === '') {
635  return;
636  }
637 
638  list($key, $remainingKey) = $this->parseNextKeySegment($string);
639  $subKey = $key . '.';
640  if ($remainingKey === '') {
641  if ($value === 'UNSET') {
642  unset($setup[$key]);
643  unset($setup[$subKey]);
644  if ($this->regLinenumbers) {
645  $setup[$key . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '>';
646  }
647  } else {
648  $lnRegisDone = 0;
649  if ($wipeOut && $this->strict) {
650  unset($setup[$key]);
651  unset($setup[$subKey]);
652  if ($this->regLinenumbers) {
653  $setup[$key . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '<';
654  $lnRegisDone = 1;
655  }
656  }
657  if (isset($value[0])) {
658  $setup[$key] = $value[0];
659  }
660  if (isset($value[1])) {
661  $setup[$subKey] = $value[1];
662  }
663  if ($this->lastComment && $this->regComments) {
664  $setup[$key . '..'] .= $this->lastComment;
665  }
666  if ($this->regLinenumbers && !$lnRegisDone) {
667  $setup[$key . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
668  }
669  }
670  } else {
671  if (!isset($setup[$subKey])) {
672  $setup[$subKey] = array();
673  }
674  $this->setVal($remainingKey, $setup[$subKey], $value);
675  }
676  }
677 
689  protected function parseNextKeySegment($key) {
690  // if no dot is in the key, nothing to do
691  $dotPosition = strpos($key, '.');
692  if ($dotPosition === FALSE) {
693  return array($key, '');
694  }
695 
696  if (strpos($key, '\\') !== FALSE) {
697  // backslashes are in the key, so we do further parsing
698 
699  while ($dotPosition !== FALSE) {
700  if ($dotPosition > 0 && $key[$dotPosition - 1] !== '\\' || $dotPosition > 1 && $key[$dotPosition - 2] === '\\') {
701  break;
702  }
703  // escaped dot found, continue
704  $dotPosition = strpos($key, '.', $dotPosition + 1);
705  }
706 
707  if ($dotPosition === FALSE) {
708  // no regular dot found
709  $keySegment = $key;
710  $remainingKey = '';
711  } else {
712  if ($dotPosition > 1 && $key[$dotPosition - 2] === '\\' && $key[$dotPosition - 1] === '\\') {
713  $keySegment = substr($key, 0, $dotPosition - 1);
714  } else {
715  $keySegment = substr($key, 0, $dotPosition);
716  }
717  $remainingKey = substr($key, $dotPosition + 1);
718  }
719 
720  // fix key segment by removing escape sequences
721  $keySegment = str_replace('\\.', '.', $keySegment);
722  } else {
723  // no backslash in the key, we're fine off
724  list($keySegment, $remainingKey) = explode('.', $key, 2);
725  }
726  return array($keySegment, $remainingKey);
727  }
728 
738  public function error($err, $num = 2) {
739  if (is_object($GLOBALS['TT'])) {
740  $GLOBALS['TT']->setTSlogMessage($err, $num);
741  }
742  $this->errors[] = array($err, $num, $this->rawP - 1, $this->lineNumberOffset);
743  }
744 
756  static public function checkIncludeLines($string, $cycle_counter = 1, $returnFiles = FALSE, $parentFilenameOrPath = '') {
757  $includedFiles = array();
758  if ($cycle_counter > 100) {
759  GeneralUtility::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
760  if ($returnFiles) {
761  return array(
762  'typoscript' => '',
763  'files' => $includedFiles
764  );
765  }
766  return '
767 ###
768 ### ERROR: Recursion!
769 ###
770 ';
771  }
772 
773  // If no tags found, no need to do slower preg_split
774  if (strpos($string, '<INCLUDE_TYPOSCRIPT:') !== FALSE) {
775  $splitRegEx = '/\r?\n\s*<INCLUDE_TYPOSCRIPT:\s*(?i)source\s*=\s*"((?i)file|dir):\s*([^"]*)"(.*)>[\ \t]*/';
776  $parts = preg_split($splitRegEx, LF . $string . LF, -1, PREG_SPLIT_DELIM_CAPTURE);
777  // First text part goes through
778  $newString = $parts[0] . LF;
779  $partCount = count($parts);
780  for ($i = 1; $i + 3 < $partCount; $i += 4) {
781  // $parts[$i] contains 'FILE' or 'DIR'
782  // $parts[$i+1] contains relative file or directory path to be included
783  // $parts[$i+2] optional properties of the INCLUDE statement
784  // $parts[$i+3] next part of the typoscript string (part in between include-tags)
785  $includeType = $parts[$i];
786  $filename = $parts[$i + 1];
787  $originalFilename = $filename;
788  $optionalProperties = $parts[$i + 2];
789  $tsContentsTillNextInclude = $parts[$i + 3];
790 
791  // Resolve a possible relative paths if a parent file is given
792  if ($parentFilenameOrPath !== '' && $filename[0] === '.') {
793  $filename = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $filename);
794  }
795 
796  // There must be a line-break char after - not sure why this check is necessary, kept it for being 100% backwards compatible
797  // An empty string is also ok (means that the next line is also a valid include_typoscript tag)
798  if (!preg_match('/(^\\s*\\r?\\n|^$)/', $tsContentsTillNextInclude)) {
799  $newString .= self::typoscriptIncludeError('Invalid characters after <INCLUDE_TYPOSCRIPT: source="' . $includeType . ':' . $filename . '">-tag (rest of line must be empty).');
800  } elseif (strpos('..', $filename) !== FALSE) {
801  $newString .= self::typoscriptIncludeError('Invalid filepath "' . $filename . '" (containing "..").');
802  } else {
803  switch (strtolower($includeType)) {
804  case 'file':
805  self::includeFile($originalFilename, $cycle_counter, $returnFiles, $newString, $includedFiles, $optionalProperties, $parentFilenameOrPath);
806  break;
807  case 'dir':
808  self::includeDirectory($originalFilename, $cycle_counter, $returnFiles, $newString, $includedFiles, $optionalProperties, $parentFilenameOrPath);
809  break;
810  default:
811  $newString .= self::typoscriptIncludeError('No valid option for INCLUDE_TYPOSCRIPT source property (valid options are FILE or DIR)');
812  }
813  }
814  // Prepend next normal (not file) part to output string
815  $newString .= $tsContentsTillNextInclude . LF;
816 
817  // load default TypoScript for content rendering templates like
818  // css_styled_content if those have been included through f.e.
819  // <INCLUDE_TYPOSCRIPT: source="FILE:EXT:css_styled_content/static/setup.txt">
820  $filePointer = strtolower($filename);
821  if (GeneralUtility::isFirstPartOfStr($filePointer, 'ext:')) {
822  $filePointerPathParts = explode('/', substr($filePointer, 4));
823 
824  // remove file part, determine whether to load setup or constants
825  list($includeType, ) = explode('.', array_pop($filePointerPathParts));
826 
827  if (in_array($includeType, array('setup', 'constants'))) {
828  // adapt extension key to required format (no underscores)
829  $filePointerPathParts[0] = str_replace('_', '', $filePointerPathParts[0]);
830 
831  // load default TypoScript
832  $defaultTypoScriptKey = implode('/', $filePointerPathParts) . '/';
833  if (in_array($defaultTypoScriptKey, $GLOBALS['TYPO3_CONF_VARS']['FE']['contentRenderingTemplates'], TRUE)) {
834  $newString .= $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $includeType . '.']['defaultContentRendering'];
835  }
836  }
837  }
838 
839  }
840  // Add a line break before and after the included code in order to make sure that the parser always has a LF.
841  $string = LF . trim($newString) . LF;
842  }
843  // When all included files should get returned, simply return an compound array containing
844  // the TypoScript with all "includes" processed and the files which got included
845  if ($returnFiles) {
846  return array(
847  'typoscript' => $string,
848  'files' => $includedFiles
849  );
850  }
851  return $string;
852  }
853 
867  public static function includeFile($filename, $cycle_counter = 1, $returnFiles = FALSE, &$newString = '', array &$includedFiles = array(), $optionalProperties = '', $parentFilenameOrPath = '') {
868  // Resolve a possible relative paths if a parent file is given
869  if ($parentFilenameOrPath !== '' && $filename[0] === '.') {
870  $absfilename = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $filename);
871  } else {
872  $absfilename = $filename;
873  }
874  $absfilename = GeneralUtility::getFileAbsFileName($absfilename);
875 
876  $newString .= LF . '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $filename . '"' . $optionalProperties . '> BEGIN:' . LF;
877  if ((string)$filename !== '') {
878  // Must exist and must not contain '..' and must be relative
879  // Check for allowed files
881  $newString .= self::typoscriptIncludeError('File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern.');
882  } elseif (!@file_exists($absfilename)) {
883  $newString .= self::typoscriptIncludeError('File "' . $filename . '" was not found.');
884  } else {
885  $includedFiles[] = $absfilename;
886  // check for includes in included text
887  $included_text = self::checkIncludeLines(GeneralUtility::getUrl($absfilename), $cycle_counter + 1, $returnFiles, $absfilename);
888  // If the method also has to return all included files, merge currently included
889  // files with files included by recursively calling itself
890  if ($returnFiles && is_array($included_text)) {
891  $includedFiles = array_merge($includedFiles, $included_text['files']);
892  $included_text = $included_text['typoscript'];
893  }
894  $newString .= $included_text . LF;
895  }
896  }
897  $newString .= '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $filename . '"' . $optionalProperties . '> END:' . LF . LF;
898  }
899 
915  protected static function includeDirectory($dirPath, $cycle_counter = 1, $returnFiles = FALSE, &$newString = '', array &$includedFiles = array(), $optionalProperties = '', $parentFilenameOrPath = '') {
916  // Extract the value of the property extensions="..."
917  $matches = preg_split('#(?i)extensions\s*=\s*"([^"]*)"(\s*|>)#', $optionalProperties, 2, PREG_SPLIT_DELIM_CAPTURE);
918  if (count($matches) > 1) {
919  $includedFileExtensions = $matches[1];
920  } else {
921  $includedFileExtensions = '';
922  }
923 
924  // Resolve a possible relative paths if a parent file is given
925  if ($parentFilenameOrPath !== '' && $dirPath[0] === '.') {
926  $resolvedDirPath = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $dirPath);
927  } else {
928  $resolvedDirPath = $dirPath;
929  }
930  $absDirPath = GeneralUtility::getFileAbsFileName($resolvedDirPath);
931  if ($absDirPath) {
932  $absDirPath = rtrim($absDirPath, '/') . '/';
933  $newString .= LF . '### <INCLUDE_TYPOSCRIPT: source="DIR:' . $dirPath . '"' . $optionalProperties . '> BEGIN:' . LF;
934  // Get alphabetically sorted file index in array
935  $fileIndex = GeneralUtility::getAllFilesAndFoldersInPath(array(), $absDirPath, $includedFileExtensions);
936  // Prepend file contents to $newString
937  $prefixLength = strlen(PATH_site);
938  foreach ($fileIndex as $absFileRef) {
939  $relFileRef = substr($absFileRef, $prefixLength);
940  self::includeFile($relFileRef, $cycle_counter, $returnFiles, $newString, $includedFiles, '', $absDirPath);
941  }
942  $newString .= '### <INCLUDE_TYPOSCRIPT: source="DIR:' . $dirPath . '"' . $optionalProperties . '> END:' . LF . LF;
943  } else {
944  $newString .= self::typoscriptIncludeError('The path "' . $resolvedDirPath . '" is invalid.');
945  }
946  }
947 
956  protected static function typoscriptIncludeError($error) {
957  GeneralUtility::sysLog($error, 'Core', 2);
958  return "\n###\n### ERROR: " . $error . "\n###\n\n";
959  }
960 
967  static public function checkIncludeLines_array(array $array) {
968  foreach ($array as $k => $v) {
969  $array[$k] = self::checkIncludeLines($array[$k]);
970  }
971  return $array;
972  }
973 
987  static public function extractIncludes($string, $cycle_counter = 1, array $extractedFileNames = array(), $parentFilenameOrPath = '') {
988  if ($cycle_counter > 10) {
989  GeneralUtility::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
990  return '
991 ###
992 ### ERROR: Recursion!
993 ###
994 ';
995  }
996  $expectedEndTag = '';
997  $fileContent = array();
998  $restContent = array();
999  $fileName = NULL;
1000  $inIncludePart = FALSE;
1001  $lines = preg_split("/\r\n|\n|\r/", $string);
1002  $skipNextLineIfEmpty = FALSE;
1003  $openingCommentedIncludeStatement = NULL;
1004  $optionalProperties = '';
1005  foreach ($lines as $line) {
1006  // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts
1007  // an additional empty line, remove this again
1008  if ($skipNextLineIfEmpty) {
1009  if (trim($line) === '') {
1010  continue;
1011  }
1012  $skipNextLineIfEmpty = FALSE;
1013  }
1014 
1015  // Outside commented include statements
1016  if (!$inIncludePart) {
1017  // Search for beginning commented include statements
1018  if (preg_match('/###\\s*<INCLUDE_TYPOSCRIPT:\\s*source\\s*=\\s*"\\s*((?i)file|dir)\\s*:\\s*([^"]*)"(.*)>\\s*BEGIN/i', $line, $matches)) {
1019  // Found a commented include statement
1020 
1021  // Save this line in case there is no ending tag
1022  $openingCommentedIncludeStatement = trim($line);
1023  $openingCommentedIncludeStatement = preg_replace('/\\s*### Warning: .*###\\s*/', '', $openingCommentedIncludeStatement);
1024 
1025  // type of match: FILE or DIR
1026  $inIncludePart = strtoupper($matches[1]);
1027  $fileName = $matches[2];
1028  $optionalProperties = $matches[3];
1029 
1030  $expectedEndTag = '### <INCLUDE_TYPOSCRIPT: source="' . $inIncludePart . ':' . $fileName . '"' . $optionalProperties . '> END';
1031  // Strip all whitespace characters to make comparision safer
1032  $expectedEndTag = strtolower(preg_replace('/\s/', '', $expectedEndTag));
1033  } else {
1034  // If this is not a beginning commented include statement this line goes into the rest content
1035  $restContent[] = $line;
1036  }
1037  //if (is_array($matches)) GeneralUtility::devLog('matches', 'TypoScriptParser', 0, $matches);
1038  } else {
1039  // Inside commented include statements
1040  // Search for the matching ending commented include statement
1041  $strippedLine = preg_replace('/\s/', '', $line);
1042  if (stripos($strippedLine, $expectedEndTag) !== FALSE) {
1043  // Found the matching ending include statement
1044  $fileContentString = implode(PHP_EOL, $fileContent);
1045 
1046  // Write the content to the file
1047 
1048  // Resolve a possible relative paths if a parent file is given
1049  if ($parentFilenameOrPath !== '' && $fileName[0] === '.') {
1050  $realFileName = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $fileName);
1051  } else {
1052  $realFileName = $fileName;
1053  }
1054  $realFileName = GeneralUtility::getFileAbsFileName($realFileName);
1055 
1056  if ($inIncludePart === 'FILE') {
1057  // Some file checks
1059  throw new \UnexpectedValueException(sprintf('File "%s" was not included since it is not allowed due to fileDenyPattern.', $fileName), 1382651858);
1060  }
1061  if (empty($realFileName)) {
1062  throw new \UnexpectedValueException(sprintf('"%s" is not a valid file location.', $fileName), 1294586441);
1063  }
1064  if (!is_writable($realFileName)) {
1065  throw new \RuntimeException(sprintf('"%s" is not writable.', $fileName), 1294586442);
1066  }
1067  if (in_array($realFileName, $extractedFileNames)) {
1068  throw new \RuntimeException(sprintf('Recursive/multiple inclusion of file "%s"', $realFileName), 1294586443);
1069  }
1070  $extractedFileNames[] = $realFileName;
1071 
1072  // Recursive call to detected nested commented include statements
1073  $fileContentString = self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName);
1074 
1075  // Write the content to the file
1076  if (!GeneralUtility::writeFile($realFileName, $fileContentString)) {
1077  throw new \RuntimeException(sprintf('Could not write file "%s"', $realFileName), 1294586444);
1078  }
1079  // Insert reference to the file in the rest content
1080  $restContent[] = '<INCLUDE_TYPOSCRIPT: source="FILE:' . $fileName . '"' . $optionalProperties . '>';
1081  } else {
1082  // must be DIR
1083 
1084  // Some file checks
1085  if (empty($realFileName)) {
1086  throw new \UnexpectedValueException(sprintf('"%s" is not a valid location.', $fileName), 1366493602);
1087  }
1088  if (!is_dir($realFileName)) {
1089  throw new \RuntimeException(sprintf('"%s" is not a directory.', $fileName), 1366493603);
1090  }
1091  if (in_array($realFileName, $extractedFileNames)) {
1092  throw new \RuntimeException(sprintf('Recursive/multiple inclusion of directory "%s"', $realFileName), 1366493604);
1093  }
1094  $extractedFileNames[] = $realFileName;
1095 
1096  // Recursive call to detected nested commented include statements
1097  self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName);
1098 
1099  // just drop content between tags since it should usually just contain individual files from that dir
1100 
1101  // Insert reference to the dir in the rest content
1102  $restContent[] = '<INCLUDE_TYPOSCRIPT: source="DIR:' . $fileName . '"' . $optionalProperties . '>';
1103  }
1104 
1105  // Reset variables (preparing for the next commented include statement)
1106  $fileContent = array();
1107  $fileName = NULL;
1108  $inIncludePart = FALSE;
1109  $openingCommentedIncludeStatement = NULL;
1110  // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts
1111  // an additional empty line, remove this again
1112  $skipNextLineIfEmpty = TRUE;
1113  } else {
1114  // If this is not a ending commented include statement this line goes into the file content
1115  $fileContent[] = $line;
1116  }
1117  }
1118  }
1119  // If we're still inside commented include statements copy the lines back to the rest content
1120  if ($inIncludePart) {
1121  $restContent[] = $openingCommentedIncludeStatement . ' ### Warning: Corresponding end line missing! ###';
1122  $restContent = array_merge($restContent, $fileContent);
1123  }
1124  $restContentString = implode(PHP_EOL, $restContent);
1125  return $restContentString;
1126  }
1127 
1134  static public function extractIncludes_array(array $array) {
1135  foreach ($array as $k => $v) {
1136  $array[$k] = self::extractIncludes($array[$k]);
1137  }
1138  return $array;
1139  }
1140 
1141  /**********************************
1142  *
1143  * Syntax highlighting
1144  *
1145  *********************************/
1156  public function doSyntaxHighlight($string, $lineNum = '', $highlightBlockMode = FALSE) {
1157  $this->syntaxHighLight = 1;
1158  $this->highLightData = array();
1159  $this->error = array();
1160  // 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.
1161  $string = str_replace(CR, '', $string);
1162  $this->parse($string);
1163  return $this->syntaxHighlight_print($lineNum, $highlightBlockMode);
1164  }
1165 
1177  public function regHighLight($code, $pointer, $strlen = -1) {
1178  if ($strlen === -1) {
1179  $this->highLightData[$pointer] = array(array($code, 0));
1180  } else {
1181  $this->highLightData[$pointer][] = array($code, $strlen);
1182  }
1183  $this->highLightData_bracelevel[$pointer] = $this->inBrace;
1184  }
1185 
1196  public function syntaxHighlight_print($lineNumDat, $highlightBlockMode) {
1197  // Registers all error messages in relation to their linenumber
1198  $errA = array();
1199  foreach ($this->errors as $err) {
1200  $errA[$err[2]][] = $err[0];
1201  }
1202  // Generates the syntax highlighted output:
1203  $lines = array();
1204  foreach ($this->raw as $rawP => $value) {
1205  $start = 0;
1206  $strlen = strlen($value);
1207  $lineC = '';
1208  if (is_array($this->highLightData[$rawP])) {
1209  foreach ($this->highLightData[$rawP] as $set) {
1210  $len = $strlen - $start - $set[1];
1211  if ($len > 0) {
1212  $part = substr($value, $start, $len);
1213  $start += $len;
1214  $st = $this->highLightStyles[isset($this->highLightStyles[$set[0]]) ? $set[0] : 'default'];
1215  if (!$highlightBlockMode || $set[0] !== 'prespace') {
1216  $lineC .= $st[0] . htmlspecialchars($part) . $st[1];
1217  }
1218  } elseif ($len < 0) {
1219  debug(array($len, $value, $rawP));
1220  }
1221  }
1222  } else {
1223  debug(array($value));
1224  }
1225  if (strlen(substr($value, $start))) {
1226  $lineC .= $this->highLightStyles['ignored'][0] . htmlspecialchars(substr($value, $start)) . $this->highLightStyles['ignored'][1];
1227  }
1228  if ($errA[$rawP]) {
1229  $lineC .= $this->highLightStyles['error'][0] . '<strong> - ERROR:</strong> ' . htmlspecialchars(implode(';', $errA[$rawP])) . $this->highLightStyles['error'][1];
1230  }
1231  if ($highlightBlockMode && $this->highLightData_bracelevel[$rawP]) {
1232  $lineC = str_pad('', $this->highLightData_bracelevel[$rawP] * 2, ' ', STR_PAD_LEFT) . '<span style="' . $this->highLightBlockStyles . ($this->highLightBlockStyles_basecolor ? 'background-color: ' . GeneralUtility::modifyHTMLColorAll($this->highLightBlockStyles_basecolor, -$this->highLightData_bracelevel[$rawP] * 16) : '') . '">' . ($lineC !== '' ? $lineC : '&nbsp;') . '</span>';
1233  }
1234  if (is_array($lineNumDat)) {
1235  $lineNum = $rawP + $lineNumDat[0];
1236  if ($this->parentObject instanceof \TYPO3\CMS\Core\TypoScript\ExtendedTemplateService) {
1237  $lineNum = $this->parentObject->ext_lnBreakPointWrap($lineNum, $lineNum);
1238  }
1239  $lineC = $this->highLightStyles['linenum'][0] . str_pad($lineNum, 4, ' ', STR_PAD_LEFT) . ':' . $this->highLightStyles['linenum'][1] . ' ' . $lineC;
1240  }
1241  $lines[] = $lineC;
1242  }
1243  return '<pre class="ts-hl">' . implode(LF, $lines) . '</pre>';
1244  }
1245 
1246 }
static getAllFilesAndFoldersInPath(array $fileArr, $path, $extList='', $regDirs=FALSE, $recursivityLevels=99, $excludePattern='')
static writeFile($file, $content, $changePermissions=FALSE)
static includeDirectory($dirPath, $cycle_counter=1, $returnFiles=FALSE, &$newString='', array &$includedFiles=array(), $optionalProperties='', $parentFilenameOrPath='')
static isFirstPartOfStr($str, $partStr)
static extractIncludes($string, $cycle_counter=1, array $extractedFileNames=array(), $parentFilenameOrPath='')
static getAbsolutePathOfRelativeReferencedFileOrPath($baseFilenameOrPath, $includeFileName)
static trimExplode($delim, $string, $removeEmptyValues=FALSE, $limit=0)
static verifyFilenameAgainstDenyPattern($filename)
static checkIncludeLines($string, $cycle_counter=1, $returnFiles=FALSE, $parentFilenameOrPath='')
static callUserFunction($funcName, &$params, &$ref, $checkPrefix='', $errorMode=0)
syntaxHighlight_print($lineNumDat, $highlightBlockMode)
static getUrl($url, $includeHeader=0, $requestHeaders=FALSE, &$report=NULL)
doSyntaxHighlight($string, $lineNum='', $highlightBlockMode=FALSE)
debug($variable='', $name=' *variable *', $line=' *line *', $file=' *file *', $recursiveDepth=3, $debugLevel=E_DEBUG)
executeValueModifier($modifierName, $modifierArgument=NULL, $currentValue=NULL)
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]
setVal($string, array &$setup, $value, $wipeOut=FALSE)
static getFileAbsFileName($filename, $onlyRelative=TRUE, $relToTYPO3_mainDir=FALSE)
static includeFile($filename, $cycle_counter=1, $returnFiles=FALSE, &$newString='', array &$includedFiles=array(), $optionalProperties='', $parentFilenameOrPath='')