d9fd6a7357c62bccb61a65a14d8f9d145a9233a6
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / TypoScript / Parser / TypoScriptParser.php
1 <?php
2 namespace TYPO3\CMS\Core\TypoScript\Parser;
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 TYPO3\CMS\Core\Utility\GeneralUtility;
18 use TYPO3\CMS\Core\Utility\PathUtility;
19 use TYPO3\CMS\Core\Utility\StringUtility;
20
21 /**
22 * The TypoScript parser
23 *
24 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
25 */
26 class TypoScriptParser {
27
28 /**
29 * If set, then key names cannot contain characters other than [:alnum:]_\.-
30 *
31 * @var bool
32 */
33 public $strict = 1;
34
35 /**
36 * TypoScript hierarchy being build during parsing.
37 *
38 * @var array
39 */
40 public $setup = array();
41
42 /**
43 * Raw data, the input string exploded by LF
44 *
45 * @var array
46 */
47 public $raw;
48
49 /**
50 * Pointer to entry in raw data array
51 *
52 * @var int
53 */
54 public $rawP;
55
56 /**
57 * Holding the value of the last comment
58 *
59 * @var string
60 */
61 public $lastComment = '';
62
63 /**
64 * Internally set, used as internal flag to create a multi-line comment (one of those like /* ... * /
65 *
66 * @var bool
67 */
68 public $commentSet = 0;
69
70 /**
71 * Internally set, when multiline value is accumulated
72 *
73 * @var bool
74 */
75 public $multiLineEnabled = 0;
76
77 /**
78 * Internally set, when multiline value is accumulated
79 *
80 * @var string
81 */
82 public $multiLineObject = '';
83
84 /**
85 * Internally set, when multiline value is accumulated
86 *
87 * @var array
88 */
89 public $multiLineValue = array();
90
91 /**
92 * Internally set, when in brace. Counter.
93 *
94 * @var int
95 */
96 public $inBrace = 0;
97
98 /**
99 * For each condition this flag is set, if the condition is TRUE,
100 * else it's cleared. Then it's used by the [ELSE] condition to determine if the next part should be parsed.
101 *
102 * @var bool
103 */
104 public $lastConditionTrue = 1;
105
106 /**
107 * Tracking all conditions found
108 *
109 * @var array
110 */
111 public $sections = array();
112
113 /**
114 * Tracking all matching conditions found
115 *
116 * @var array
117 */
118 public $sectionsMatch = array();
119
120 /**
121 * If set, then syntax highlight mode is on; Call the function syntaxHighlight() to use this function
122 *
123 * @var bool
124 */
125 public $syntaxHighLight = 0;
126
127 /**
128 * Syntax highlight data is accumulated in this array. Used by syntaxHighlight_print() to construct the output.
129 *
130 * @var array
131 */
132 public $highLightData = array();
133
134 /**
135 * Syntax highlight data keeping track of the curly brace level for each line
136 *
137 * @var array
138 */
139 public $highLightData_bracelevel = array();
140
141 /**
142 * DO NOT register the comments. This is default for the ordinary sitetemplate!
143 *
144 * @var bool
145 */
146 public $regComments = 0;
147
148 /**
149 * DO NOT register the linenumbers. This is default for the ordinary sitetemplate!
150 *
151 * @var bool
152 */
153 public $regLinenumbers = 0;
154
155 /**
156 * Error accumulation array.
157 *
158 * @var array
159 */
160 public $errors = array();
161
162 /**
163 * Used for the error messages line number reporting. Set externally.
164 *
165 * @var int
166 */
167 public $lineNumberOffset = 0;
168
169 /**
170 * Line for break point.
171 *
172 * @var int
173 */
174 public $breakPointLN = 0;
175
176 /**
177 * @var array
178 */
179 public $highLightStyles = array(
180 'prespace' => array('<span class="ts-prespace">', '</span>'),
181 // Space before any content on a line
182 'objstr_postspace' => array('<span class="ts-objstr_postspace">', '</span>'),
183 // Space after the object string on a line
184 'operator_postspace' => array('<span class="ts-operator_postspace">', '</span>'),
185 // Space after the operator on a line
186 'operator' => array('<span class="ts-operator">', '</span>'),
187 // The operator char
188 'value' => array('<span class="ts-value">', '</span>'),
189 // The value of a line
190 'objstr' => array('<span class="ts-objstr">', '</span>'),
191 // The object string of a line
192 'value_copy' => array('<span class="ts-value_copy">', '</span>'),
193 // The value when the copy syntax (<) is used; that means the object reference
194 'value_unset' => array('<span class="ts-value_unset">', '</span>'),
195 // The value when an object is unset. Should not exist.
196 'ignored' => array('<span class="ts-ignored">', '</span>'),
197 // The "rest" of a line which will be ignored.
198 'default' => array('<span class="ts-default">', '</span>'),
199 // The default style if none other is applied.
200 'comment' => array('<span class="ts-comment">', '</span>'),
201 // Comment lines
202 'condition' => array('<span class="ts-condition">', '</span>'),
203 // Conditions
204 'error' => array('<span class="ts-error">', '</span>'),
205 // Error messages
206 'linenum' => array('<span class="ts-linenum">', '</span>')
207 );
208
209 /**
210 * Additional attributes for the <span> tags for a blockmode line
211 *
212 * @var string
213 */
214 public $highLightBlockStyles = '';
215
216 /**
217 * The hex-HTML color for the blockmode
218 *
219 * @var string
220 */
221 public $highLightBlockStyles_basecolor = '#cccccc';
222
223 /**
224 * @var \TYPO3\CMS\Core\TypoScript\ExtendedTemplateService
225 */
226 public $parentObject;
227
228 /**
229 * Start parsing the input TypoScript text piece. The result is stored in $this->setup
230 *
231 * @param string $string The TypoScript text
232 * @param object|string $matchObj If is object, then this is used to match conditions found in the TypoScript code. If matchObj not specified, then no conditions will work! (Except [GLOBAL])
233 *
234 * @return void
235 */
236 public function parse($string, $matchObj = '') {
237 $this->raw = explode(LF, $string);
238 $this->rawP = 0;
239 $pre = '[GLOBAL]';
240 while ($pre) {
241 if ($this->breakPointLN && $pre === '[_BREAK]') {
242 $this->error('Breakpoint at ' . ($this->lineNumberOffset + $this->rawP - 2) . ': Line content was "' . $this->raw[($this->rawP - 2)] . '"', 1);
243 break;
244 }
245 $preUppercase = strtoupper($pre);
246 if ($pre[0] === '[' &&
247 ($preUppercase === '[GLOBAL]' ||
248 $preUppercase === '[END]' ||
249 !$this->lastConditionTrue && $preUppercase === '[ELSE]')
250 ) {
251 $pre = trim($this->parseSub($this->setup));
252 $this->lastConditionTrue = 1;
253 } else {
254 // We're in a specific section. Therefore we log this section
255 $specificSection = $preUppercase !== '[ELSE]';
256 if ($specificSection) {
257 $this->sections[md5($pre)] = $pre;
258 }
259 if (is_object($matchObj) && $matchObj->match($pre) || $this->syntaxHighLight) {
260 if ($specificSection) {
261 $this->sectionsMatch[md5($pre)] = $pre;
262 }
263 $pre = trim($this->parseSub($this->setup));
264 $this->lastConditionTrue = 1;
265 } else {
266 $pre = $this->nextDivider();
267 $this->lastConditionTrue = 0;
268 }
269 }
270 }
271 if ($this->inBrace) {
272 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': The script is short of ' . $this->inBrace . ' end brace(s)', 1);
273 }
274 if ($this->multiLineEnabled) {
275 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': A multiline value section is not ended with a parenthesis!', 1);
276 }
277 $this->lineNumberOffset += count($this->raw) + 1;
278 }
279
280 /**
281 * Will search for the next condition. When found it will return the line content (the condition value) and have advanced the internal $this->rawP pointer to point to the next line after the condition.
282 *
283 * @return string The condition value
284 * @see parse()
285 */
286 public function nextDivider() {
287 while (isset($this->raw[$this->rawP])) {
288 $line = trim($this->raw[$this->rawP]);
289 $this->rawP++;
290 if ($line && $line[0] === '[') {
291 return $line;
292 }
293 }
294 }
295
296 /**
297 * Parsing the $this->raw TypoScript lines from pointer, $this->rawP
298 *
299 * @param array $setup Reference to the setup array in which to accumulate the values.
300 * @return string|NULL Returns the string of the condition found, the exit signal or possible nothing (if it completed parsing with no interruptions)
301 */
302 public function parseSub(array &$setup) {
303 while (isset($this->raw[$this->rawP])) {
304 $line = ltrim($this->raw[$this->rawP]);
305 $lineP = $this->rawP;
306 $this->rawP++;
307 if ($this->syntaxHighLight) {
308 $this->regHighLight('prespace', $lineP, strlen($line));
309 }
310 // Breakpoint?
311 // By adding 1 we get that line processed
312 if ($this->breakPointLN && $this->lineNumberOffset + $this->rawP - 1 === $this->breakPointLN + 1) {
313 return '[_BREAK]';
314 }
315 // Set comment flag?
316 if (!$this->multiLineEnabled && strpos($line, '/*') === 0) {
317 $this->commentSet = 1;
318 }
319 // If $this->multiLineEnabled we will go and get the line values here because we know, the first if() will be TRUE.
320 if (!$this->commentSet && ($line || $this->multiLineEnabled)) {
321 // If multiline is enabled. Escape by ')'
322 if ($this->multiLineEnabled) {
323 // Multiline ends...
324 if ($line[0] === ')') {
325 if ($this->syntaxHighLight) {
326 $this->regHighLight('operator', $lineP, strlen($line) - 1);
327 }
328 // Disable multiline
329 $this->multiLineEnabled = 0;
330 $theValue = implode($this->multiLineValue, LF);
331 if (strpos($this->multiLineObject, '.') !== FALSE) {
332 // Set the value deeper.
333 $this->setVal($this->multiLineObject, $setup, array($theValue));
334 } else {
335 // Set value regularly
336 $setup[$this->multiLineObject] = $theValue;
337 if ($this->lastComment && $this->regComments) {
338 $setup[$this->multiLineObject . '..'] .= $this->lastComment;
339 }
340 if ($this->regLinenumbers) {
341 $setup[$this->multiLineObject . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
342 }
343 }
344 } else {
345 if ($this->syntaxHighLight) {
346 $this->regHighLight('value', $lineP);
347 }
348 $this->multiLineValue[] = $this->raw[$this->rawP - 1];
349 }
350 } elseif ($this->inBrace === 0 && $line[0] === '[') {
351 // Beginning of condition (only on level zero compared to brace-levels
352 if ($this->syntaxHighLight) {
353 $this->regHighLight('condition', $lineP);
354 }
355 return $line;
356 } else {
357 // Return if GLOBAL condition is set - no matter what.
358 if ($line[0] === '[' && stripos($line, '[GLOBAL]') !== FALSE) {
359 if ($this->syntaxHighLight) {
360 $this->regHighLight('condition', $lineP);
361 }
362 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': On return to [GLOBAL] scope, the script was short of ' . $this->inBrace . ' end brace(s)', 1);
363 $this->inBrace = 0;
364 return $line;
365 } elseif ($line[0] !== '}' && $line[0] !== '#' && $line[0] !== '/') {
366 // If not brace-end or comment
367 // Find object name string until we meet an operator
368 $varL = strcspn($line, TAB . ' {=<>(');
369 // check for special ":=" operator
370 if ($varL > 0 && substr($line, $varL-1, 2) === ':=') {
371 --$varL;
372 }
373 // also remove tabs after the object string name
374 $objStrName = substr($line, 0, $varL);
375 if ($this->syntaxHighLight) {
376 $this->regHighLight('objstr', $lineP, strlen(substr($line, $varL)));
377 }
378 if ($objStrName !== '') {
379 $r = array();
380 if ($this->strict && preg_match('/[^[:alnum:]_\\\\\\.:-]/i', $objStrName, $r)) {
381 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_:-\\."');
382 } else {
383 $line = ltrim(substr($line, $varL));
384 if ($this->syntaxHighLight) {
385 $this->regHighLight('objstr_postspace', $lineP, strlen($line));
386 if ($line !== '') {
387 $this->regHighLight('operator', $lineP, strlen($line) - 1);
388 $this->regHighLight('operator_postspace', $lineP, strlen(ltrim(substr($line, 1))));
389 }
390 }
391 if ($line === '') {
392 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({');
393 } else {
394 // Checking for special TSparser properties (to change TS values at parsetime)
395 $match = array();
396 if ($line[0] === ':' && preg_match('/^:=\\s*([[:alpha:]]+)\\s*\\((.*)\\).*/', $line, $match)) {
397 $tsFunc = $match[1];
398 $tsFuncArg = $match[2];
399 list($currentValue) = $this->getVal($objStrName, $setup);
400 $tsFuncArg = str_replace(array('\\\\', '\\n', '\\t'), array('\\', LF, TAB), $tsFuncArg);
401 $newValue = $this->executeValueModifier($tsFunc, $tsFuncArg, $currentValue);
402 if (isset($newValue)) {
403 $line = '= ' . $newValue;
404 }
405 }
406 switch ($line[0]) {
407 case '=':
408 if ($this->syntaxHighLight) {
409 $this->regHighLight('value', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
410 }
411 if (strpos($objStrName, '.') !== FALSE) {
412 $value = array();
413 $value[0] = trim(substr($line, 1));
414 $this->setVal($objStrName, $setup, $value);
415 } else {
416 $setup[$objStrName] = trim(substr($line, 1));
417 if ($this->lastComment && $this->regComments) {
418 // Setting comment..
419 $setup[$objStrName . '..'] .= $this->lastComment;
420 }
421 if ($this->regLinenumbers) {
422 $setup[$objStrName . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
423 }
424 }
425 break;
426 case '{':
427 $this->inBrace++;
428 if (strpos($objStrName, '.') !== FALSE) {
429 $exitSig = $this->rollParseSub($objStrName, $setup);
430 if ($exitSig) {
431 return $exitSig;
432 }
433 } else {
434 if (!isset($setup[($objStrName . '.')])) {
435 $setup[$objStrName . '.'] = array();
436 }
437 $exitSig = $this->parseSub($setup[$objStrName . '.']);
438 if ($exitSig) {
439 return $exitSig;
440 }
441 }
442 break;
443 case '(':
444 $this->multiLineObject = $objStrName;
445 $this->multiLineEnabled = 1;
446 $this->multiLineValue = array();
447 break;
448 case '<':
449 if ($this->syntaxHighLight) {
450 $this->regHighLight('value_copy', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
451 }
452 $theVal = trim(substr($line, 1));
453 if ($theVal[0] === '.') {
454 $res = $this->getVal(substr($theVal, 1), $setup);
455 } else {
456 $res = $this->getVal($theVal, $this->setup);
457 }
458 $this->setVal($objStrName, $setup, unserialize(serialize($res)), 1);
459 // 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.
460 break;
461 case '>':
462 if ($this->syntaxHighLight) {
463 $this->regHighLight('value_unset', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
464 }
465 $this->setVal($objStrName, $setup, 'UNSET');
466 break;
467 default:
468 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not followed by any operator, =<>({');
469 }
470 }
471 }
472 $this->lastComment = '';
473 }
474 } elseif ($line[0] === '}') {
475 $this->inBrace--;
476 $this->lastComment = '';
477 if ($this->syntaxHighLight) {
478 $this->regHighLight('operator', $lineP, strlen($line) - 1);
479 }
480 if ($this->inBrace < 0) {
481 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': An end brace is in excess.', 1);
482 $this->inBrace = 0;
483 } else {
484 break;
485 }
486 } else {
487 if ($this->syntaxHighLight) {
488 $this->regHighLight('comment', $lineP);
489 }
490 // Comment. The comments are concatenated in this temporary string:
491 if ($this->regComments) {
492 $this->lastComment .= rtrim($line) . LF;
493 }
494 }
495 if (StringUtility::beginsWith($line, '### ERROR')) {
496 $this->error(substr($line, 11));
497 }
498 }
499 }
500 // Unset comment
501 if ($this->commentSet) {
502 if ($this->syntaxHighLight) {
503 $this->regHighLight('comment', $lineP);
504 }
505 if (strpos($line, '*/') === 0) {
506 $this->commentSet = 0;
507 }
508 }
509 }
510 }
511
512 /**
513 * Executes operator functions, called from TypoScript
514 * example: page.10.value := appendString(!)
515 *
516 * @param string $modifierName TypoScript function called
517 * @param string $modifierArgument Function arguments; In case of multiple arguments, the method must split on its own
518 * @param string $currentValue Current TypoScript value
519 * @return string Modification result
520 */
521 protected function executeValueModifier($modifierName, $modifierArgument = NULL, $currentValue = NULL) {
522 $newValue = NULL;
523 switch ($modifierName) {
524 case 'prependString':
525 $newValue = $modifierArgument . $currentValue;
526 break;
527 case 'appendString':
528 $newValue = $currentValue . $modifierArgument;
529 break;
530 case 'removeString':
531 $newValue = str_replace($modifierArgument, '', $currentValue);
532 break;
533 case 'replaceString':
534 list($fromStr, $toStr) = explode('|', $modifierArgument, 2);
535 $newValue = str_replace($fromStr, $toStr, $currentValue);
536 break;
537 case 'addToList':
538 $newValue = ((string)$currentValue !== '' ? $currentValue . ',' : '') . $modifierArgument;
539 break;
540 case 'removeFromList':
541 $existingElements = GeneralUtility::trimExplode(',', $currentValue);
542 $removeElements = GeneralUtility::trimExplode(',', $modifierArgument);
543 if (count($removeElements)) {
544 $newValue = implode(',', array_diff($existingElements, $removeElements));
545 }
546 break;
547 case 'uniqueList':
548 $elements = GeneralUtility::trimExplode(',', $currentValue);
549 $newValue = implode(',', array_unique($elements));
550 break;
551 case 'reverseList':
552 $elements = GeneralUtility::trimExplode(',', $currentValue);
553 $newValue = implode(',', array_reverse($elements));
554 break;
555 case 'sortList':
556 $elements = GeneralUtility::trimExplode(',', $currentValue);
557 $arguments = GeneralUtility::trimExplode(',', $modifierArgument);
558 $arguments = array_map('strtolower', $arguments);
559 $sort_flags = SORT_REGULAR;
560 if (in_array('numeric', $arguments)) {
561 $sort_flags = SORT_NUMERIC;
562 }
563 sort($elements, $sort_flags);
564 if (in_array('descending', $arguments)) {
565 $elements = array_reverse($elements);
566 }
567 $newValue = implode(',', $elements);
568 break;
569 default:
570 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName])) {
571 $hookMethod = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName];
572 $params = array('currentValue' => $currentValue, 'functionArgument' => $modifierArgument);
573 $fakeThis = FALSE;
574 $newValue = GeneralUtility::callUserFunction($hookMethod, $params, $fakeThis);
575 } else {
576 GeneralUtility::sysLog(
577 'Missing function definition for ' . $modifierName . ' on TypoScript',
578 'Core',
579 GeneralUtility::SYSLOG_SEVERITY_WARNING
580 );
581 }
582 }
583 return $newValue;
584 }
585
586 /**
587 * Parsing of TypoScript keys inside a curly brace where the key is composite of at least two keys,
588 * thus having to recursively call itself to get the value
589 *
590 * @param string $string The object sub-path, eg "thisprop.another_prot
591 * @param array $setup The local setup array from the function calling this function
592 * @return string Returns the exitSignal
593 * @see parseSub()
594 */
595 public function rollParseSub($string, array &$setup) {
596 if ((string)$string === '') {
597 return '';
598 }
599
600 list($key, $remainingKey) = $this->parseNextKeySegment($string);
601 $key .= '.';
602 if (!isset($setup[$key])) {
603 $setup[$key] = array();
604 }
605 $exitSig = $remainingKey === ''
606 ? $this->parseSub($setup[$key])
607 : $this->rollParseSub($remainingKey, $setup[$key]);
608 return $exitSig ?: '';
609 }
610
611 /**
612 * Get a value/property pair for an object path in TypoScript, eg. "myobject.myvalue.mysubproperty".
613 * Here: Used by the "copy" operator, <
614 *
615 * @param string $string Object path for which to get the value
616 * @param array $setup Global setup code if $string points to a global object path. But if string is prefixed with "." then its the local setup array.
617 * @return array An array with keys 0/1 being value/property respectively
618 */
619 public function getVal($string, $setup) {
620 if ((string)$string === '') {
621 return array();
622 }
623
624 list($key, $remainingKey) = $this->parseNextKeySegment($string);
625 $subKey = $key . '.';
626 if ($remainingKey === '') {
627 $retArr = array();
628 if (isset($setup[$key])) {
629 $retArr[0] = $setup[$key];
630 }
631 if (isset($setup[$subKey])) {
632 $retArr[1] = $setup[$subKey];
633 }
634 return $retArr;
635 } else {
636 if ($setup[$subKey]) {
637 return $this->getVal($remainingKey, $setup[$subKey]);
638 }
639 }
640 return array();
641 }
642
643 /**
644 * Setting a value/property of an object string in the setup array.
645 *
646 * @param string $string The object sub-path, eg "thisprop.another_prot
647 * @param array $setup The local setup array from the function calling this function.
648 * @param array|string $value The value/property pair array to set. If only one of them is set, then the other is not touched (unless $wipeOut is set, which it is when copies are made which must include both value and property)
649 * @param bool $wipeOut If set, then both value and property is wiped out when a copy is made of another value.
650 * @return void
651 */
652 public function setVal($string, array &$setup, $value, $wipeOut = FALSE) {
653 if ((string)$string === '') {
654 return;
655 }
656
657 list($key, $remainingKey) = $this->parseNextKeySegment($string);
658 $subKey = $key . '.';
659 if ($remainingKey === '') {
660 if ($value === 'UNSET') {
661 unset($setup[$key]);
662 unset($setup[$subKey]);
663 if ($this->regLinenumbers) {
664 $setup[$key . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '>';
665 }
666 } else {
667 $lnRegisDone = 0;
668 if ($wipeOut && $this->strict) {
669 unset($setup[$key]);
670 unset($setup[$subKey]);
671 if ($this->regLinenumbers) {
672 $setup[$key . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '<';
673 $lnRegisDone = 1;
674 }
675 }
676 if (isset($value[0])) {
677 $setup[$key] = $value[0];
678 }
679 if (isset($value[1])) {
680 $setup[$subKey] = $value[1];
681 }
682 if ($this->lastComment && $this->regComments) {
683 $setup[$key . '..'] .= $this->lastComment;
684 }
685 if ($this->regLinenumbers && !$lnRegisDone) {
686 $setup[$key . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
687 }
688 }
689 } else {
690 if (!isset($setup[$subKey])) {
691 $setup[$subKey] = array();
692 }
693 $this->setVal($remainingKey, $setup[$subKey], $value);
694 }
695 }
696
697 /**
698 * Determines the first key segment of a TypoScript key by searching for the first
699 * unescaped dot in the given key string.
700 *
701 * Since the escape characters are only needed to correctly determine the key
702 * segment any escape characters before the first unescaped dot are
703 * stripped from the key.
704 *
705 * @param string $key The key, possibly consisting of multiple key segments separated by unescaped dots
706 * @return array Array with key segment and remaining part of $key
707 */
708 protected function parseNextKeySegment($key) {
709 // if no dot is in the key, nothing to do
710 $dotPosition = strpos($key, '.');
711 if ($dotPosition === FALSE) {
712 return array($key, '');
713 }
714
715 if (strpos($key, '\\') !== FALSE) {
716 // backslashes are in the key, so we do further parsing
717
718 while ($dotPosition !== FALSE) {
719 if ($dotPosition > 0 && $key[$dotPosition - 1] !== '\\' || $dotPosition > 1 && $key[$dotPosition - 2] === '\\') {
720 break;
721 }
722 // escaped dot found, continue
723 $dotPosition = strpos($key, '.', $dotPosition + 1);
724 }
725
726 if ($dotPosition === FALSE) {
727 // no regular dot found
728 $keySegment = $key;
729 $remainingKey = '';
730 } else {
731 if ($dotPosition > 1 && $key[$dotPosition - 2] === '\\' && $key[$dotPosition - 1] === '\\') {
732 $keySegment = substr($key, 0, $dotPosition - 1);
733 } else {
734 $keySegment = substr($key, 0, $dotPosition);
735 }
736 $remainingKey = substr($key, $dotPosition + 1);
737 }
738
739 // fix key segment by removing escape sequences
740 $keySegment = str_replace('\\.', '.', $keySegment);
741 } else {
742 // no backslash in the key, we're fine off
743 list($keySegment, $remainingKey) = explode('.', $key, 2);
744 }
745 return array($keySegment, $remainingKey);
746 }
747
748 /**
749 * Stacks errors/messages from the TypoScript parser into an internal array, $this->error
750 * If "TT" is a global object (as it is in the frontend when backend users are logged in) the message will be registered here as well.
751 *
752 * @param string $err The error message string
753 * @param int $num The error severity (in the scale of $GLOBALS['TT']->setTSlogMessage: Approx: 2=warning, 1=info, 0=nothing, 3=fatal.)
754 * @return void
755 */
756 public function error($err, $num = 2) {
757 if (is_object($GLOBALS['TT'])) {
758 $GLOBALS['TT']->setTSlogMessage($err, $num);
759 }
760 $this->errors[] = array($err, $num, $this->rawP - 1, $this->lineNumberOffset);
761 }
762
763 /**
764 * Checks the input string (un-parsed TypoScript) for include-commands ("<INCLUDE_TYPOSCRIPT: ....")
765 * Use: \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines()
766 *
767 * @param string $string Unparsed TypoScript
768 * @param int $cycle_counter Counter for detecting endless loops
769 * @param bool $returnFiles When set an array containing the resulting typoscript and all included files will get returned
770 * @param string $parentFilenameOrPath The parent file (with absolute path) or path for relative includes
771 * @return string Complete TypoScript with includes added.
772 * @static
773 */
774 static public function checkIncludeLines($string, $cycle_counter = 1, $returnFiles = FALSE, $parentFilenameOrPath = '') {
775 $includedFiles = array();
776 if ($cycle_counter > 100) {
777 GeneralUtility::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
778 if ($returnFiles) {
779 return array(
780 'typoscript' => '',
781 'files' => $includedFiles
782 );
783 }
784 return '
785 ###
786 ### ERROR: Recursion!
787 ###
788 ';
789 }
790
791 // If no tags found, no need to do slower preg_split
792 if (strpos($string, '<INCLUDE_TYPOSCRIPT:') !== FALSE) {
793 $splitRegEx = '/\r?\n\s*<INCLUDE_TYPOSCRIPT:\s*(?i)source\s*=\s*"((?i)file|dir):\s*([^"]*)"(.*)>[\ \t]*/';
794 $parts = preg_split($splitRegEx, LF . $string . LF, -1, PREG_SPLIT_DELIM_CAPTURE);
795 // First text part goes through
796 $newString = $parts[0] . LF;
797 $partCount = count($parts);
798 for ($i = 1; $i + 3 < $partCount; $i += 4) {
799 // $parts[$i] contains 'FILE' or 'DIR'
800 // $parts[$i+1] contains relative file or directory path to be included
801 // $parts[$i+2] optional properties of the INCLUDE statement
802 // $parts[$i+3] next part of the typoscript string (part in between include-tags)
803 $includeType = $parts[$i];
804 $filename = $parts[$i + 1];
805 $originalFilename = $filename;
806 $optionalProperties = $parts[$i + 2];
807 $tsContentsTillNextInclude = $parts[$i + 3];
808
809 // Resolve a possible relative paths if a parent file is given
810 if ($parentFilenameOrPath !== '' && $filename[0] === '.') {
811 $filename = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $filename);
812 }
813
814 // There must be a line-break char after - not sure why this check is necessary, kept it for being 100% backwards compatible
815 // An empty string is also ok (means that the next line is also a valid include_typoscript tag)
816 if (!preg_match('/(^\\s*\\r?\\n|^$)/', $tsContentsTillNextInclude)) {
817 $newString .= self::typoscriptIncludeError('Invalid characters after <INCLUDE_TYPOSCRIPT: source="' . $includeType . ':' . $filename . '">-tag (rest of line must be empty).');
818 } elseif (strpos('..', $filename) !== FALSE) {
819 $newString .= self::typoscriptIncludeError('Invalid filepath "' . $filename . '" (containing "..").');
820 } else {
821 switch (strtolower($includeType)) {
822 case 'file':
823 self::includeFile($originalFilename, $cycle_counter, $returnFiles, $newString, $includedFiles, $optionalProperties, $parentFilenameOrPath);
824 break;
825 case 'dir':
826 self::includeDirectory($originalFilename, $cycle_counter, $returnFiles, $newString, $includedFiles, $optionalProperties, $parentFilenameOrPath);
827 break;
828 default:
829 $newString .= self::typoscriptIncludeError('No valid option for INCLUDE_TYPOSCRIPT source property (valid options are FILE or DIR)');
830 }
831 }
832 // Prepend next normal (not file) part to output string
833 $newString .= $tsContentsTillNextInclude . LF;
834
835 // load default TypoScript for content rendering templates like
836 // css_styled_content if those have been included through f.e.
837 // <INCLUDE_TYPOSCRIPT: source="FILE:EXT:css_styled_content/static/setup.txt">
838 $filePointer = strtolower($filename);
839 if (StringUtility::beginsWith($filePointer, 'ext:')) {
840 $filePointerPathParts = explode('/', substr($filePointer, 4));
841
842 // remove file part, determine whether to load setup or constants
843 list($includeType, ) = explode('.', array_pop($filePointerPathParts));
844
845 if (in_array($includeType, array('setup', 'constants'))) {
846 // adapt extension key to required format (no underscores)
847 $filePointerPathParts[0] = str_replace('_', '', $filePointerPathParts[0]);
848
849 // load default TypoScript
850 $defaultTypoScriptKey = implode('/', $filePointerPathParts) . '/';
851 if (in_array($defaultTypoScriptKey, $GLOBALS['TYPO3_CONF_VARS']['FE']['contentRenderingTemplates'], TRUE)) {
852 $newString .= $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $includeType . '.']['defaultContentRendering'];
853 }
854 }
855 }
856
857 }
858 // Not the first/last linebreak char.
859 $string = substr($newString, 1, -1);
860 }
861 // When all included files should get returned, simply return an compound array containing
862 // the TypoScript with all "includes" processed and the files which got included
863 if ($returnFiles) {
864 return array(
865 'typoscript' => $string,
866 'files' => $includedFiles
867 );
868 }
869 return $string;
870 }
871
872 /**
873 * Include file $filename. Contents of the file will be prepended to &$newstring, filename to &$includedFiles
874 * Further include_typoscript tags in the contents are processed recursively
875 *
876 * @param string $filename Relative path to the typoscript file to be included
877 * @param int $cycle_counter Counter for detecting endless loops
878 * @param bool $returnFiles When set, filenames of included files will be prepended to the array &$includedFiles
879 * @param string &$newString The output string to which the content of the file will be prepended (referenced
880 * @param array &$includedFiles Array to which the filenames of included files will be prepended (referenced)
881 * @param string $optionalProperties
882 * @param string $parentFilenameOrPath The parent file (with absolute path) or path for relative includes
883 * @static
884 */
885 public static function includeFile($filename, $cycle_counter = 1, $returnFiles = FALSE, &$newString = '', array &$includedFiles = array(), $optionalProperties = '', $parentFilenameOrPath = '') {
886 // Resolve a possible relative paths if a parent file is given
887 if ($parentFilenameOrPath !== '' && $filename[0] === '.') {
888 $absfilename = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $filename);
889 } else {
890 $absfilename = $filename;
891 }
892 $absfilename = GeneralUtility::getFileAbsFileName($absfilename);
893
894 $newString .= LF . '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $filename . '"' . $optionalProperties . '> BEGIN:' . LF;
895 if ((string)$filename !== '') {
896 // Must exist and must not contain '..' and must be relative
897 // Check for allowed files
898 if (!GeneralUtility::verifyFilenameAgainstDenyPattern($absfilename)) {
899 $newString .= self::typoscriptIncludeError('File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern.');
900 } elseif (!@file_exists($absfilename)) {
901 $newString .= self::typoscriptIncludeError('File "' . $filename . '" was not found.');
902 } else {
903 $includedFiles[] = $absfilename;
904 // check for includes in included text
905 $included_text = self::checkIncludeLines(GeneralUtility::getUrl($absfilename), $cycle_counter + 1, $returnFiles, $absfilename);
906 // If the method also has to return all included files, merge currently included
907 // files with files included by recursively calling itself
908 if ($returnFiles && is_array($included_text)) {
909 $includedFiles = array_merge($includedFiles, $included_text['files']);
910 $included_text = $included_text['typoscript'];
911 }
912 $newString .= $included_text . LF;
913 }
914 }
915 $newString .= '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $filename . '"' . $optionalProperties . '> END:' . LF . LF;
916 }
917
918 /**
919 * Include all files with matching Typoscript extensions in directory $dirPath. Contents of the files are
920 * prepended to &$newstring, filename to &$includedFiles.
921 * Order of the directory items to be processed: files first, then directories, both in alphabetical order.
922 * Further include_typoscript tags in the contents of the files are processed recursively.
923 *
924 * @param string $dirPath Relative path to the directory to be included
925 * @param int $cycle_counter Counter for detecting endless loops
926 * @param bool $returnFiles When set, filenames of included files will be prepended to the array &$includedFiles
927 * @param string &$newString The output string to which the content of the file will be prepended (referenced)
928 * @param array &$includedFiles Array to which the filenames of included files will be prepended (referenced)
929 * @param string $optionalProperties
930 * @param string $parentFilenameOrPath The parent file (with absolute path) or path for relative includes
931 * @static
932 */
933 protected static function includeDirectory($dirPath, $cycle_counter = 1, $returnFiles = FALSE, &$newString = '', array &$includedFiles = array(), $optionalProperties = '', $parentFilenameOrPath = '') {
934 // Extract the value of the property extensions="..."
935 $matches = preg_split('#(?i)extensions\s*=\s*"([^"]*)"(\s*|>)#', $optionalProperties, 2, PREG_SPLIT_DELIM_CAPTURE);
936 if (count($matches) > 1) {
937 $includedFileExtensions = $matches[1];
938 } else {
939 $includedFileExtensions = '';
940 }
941
942 // Resolve a possible relative paths if a parent file is given
943 if ($parentFilenameOrPath !== '' && $dirPath[0] === '.') {
944 $resolvedDirPath = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $dirPath);
945 } else {
946 $resolvedDirPath = $dirPath;
947 }
948 $absDirPath = GeneralUtility::getFileAbsFileName($resolvedDirPath);
949 if ($absDirPath) {
950 $absDirPath = rtrim($absDirPath, '/') . '/';
951 $newString .= LF . '### <INCLUDE_TYPOSCRIPT: source="DIR:' . $dirPath . '"' . $optionalProperties . '> BEGIN:' . LF;
952 // Get alphabetically sorted file index in array
953 $fileIndex = GeneralUtility::getAllFilesAndFoldersInPath(array(), $absDirPath, $includedFileExtensions);
954 // Prepend file contents to $newString
955 $prefixLength = strlen(PATH_site);
956 foreach ($fileIndex as $absFileRef) {
957 $relFileRef = substr($absFileRef, $prefixLength);
958 self::includeFile($relFileRef, $cycle_counter, $returnFiles, $newString, $includedFiles, '', $absDirPath);
959 }
960 $newString .= '### <INCLUDE_TYPOSCRIPT: source="DIR:' . $dirPath . '"' . $optionalProperties . '> END:' . LF . LF;
961 } else {
962 $newString .= self::typoscriptIncludeError('The path "' . $resolvedDirPath . '" is invalid.');
963 }
964 }
965
966 /**
967 * Process errors in INCLUDE_TYPOSCRIPT tags
968 * Errors are logged in sysLog and printed in the concatenated Typoscript result (as can be seen in Template Analyzer)
969 *
970 * @param string $error Text of the error message
971 * @return string The error message encapsulated in comments
972 * @static
973 */
974 protected static function typoscriptIncludeError($error) {
975 GeneralUtility::sysLog($error, 'Core', 2);
976 return "\n###\n### ERROR: " . $error . "\n###\n\n";
977 }
978
979 /**
980 * Parses the string in each value of the input array for include-commands
981 *
982 * @param array $array Array with TypoScript in each value
983 * @return array Same array but where the values has been parsed for include-commands
984 */
985 static public function checkIncludeLines_array(array $array) {
986 foreach ($array as $k => $v) {
987 $array[$k] = self::checkIncludeLines($array[$k]);
988 }
989 return $array;
990 }
991
992 /**
993 * Search for commented INCLUDE_TYPOSCRIPT statements
994 * and save the content between the BEGIN and the END line to the specified file
995 *
996 * @param string $string Template content
997 * @param int $cycle_counter Counter for detecting endless loops
998 * @param array $extractedFileNames
999 * @param string $parentFilenameOrPath
1000 *
1001 * @throws \RuntimeException
1002 * @throws \UnexpectedValueException
1003 * @return string Template content with uncommented include statements
1004 */
1005 static public function extractIncludes($string, $cycle_counter = 1, array $extractedFileNames = array(), $parentFilenameOrPath = '') {
1006 if ($cycle_counter > 10) {
1007 GeneralUtility::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
1008 return '
1009 ###
1010 ### ERROR: Recursion!
1011 ###
1012 ';
1013 }
1014 $expectedEndTag = '';
1015 $fileContent = array();
1016 $restContent = array();
1017 $fileName = NULL;
1018 $inIncludePart = FALSE;
1019 $lines = preg_split("/\r\n|\n|\r/", $string);
1020 $skipNextLineIfEmpty = FALSE;
1021 $openingCommentedIncludeStatement = NULL;
1022 $optionalProperties = '';
1023 foreach ($lines as $line) {
1024 // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts
1025 // an additional empty line, remove this again
1026 if ($skipNextLineIfEmpty) {
1027 if (trim($line) === '') {
1028 continue;
1029 }
1030 $skipNextLineIfEmpty = FALSE;
1031 }
1032
1033 // Outside commented include statements
1034 if (!$inIncludePart) {
1035 // Search for beginning commented include statements
1036 if (preg_match('/###\\s*<INCLUDE_TYPOSCRIPT:\\s*source\\s*=\\s*"\\s*((?i)file|dir)\\s*:\\s*([^"]*)"(.*)>\\s*BEGIN/i', $line, $matches)) {
1037 // Found a commented include statement
1038
1039 // Save this line in case there is no ending tag
1040 $openingCommentedIncludeStatement = trim($line);
1041 $openingCommentedIncludeStatement = preg_replace('/\\s*### Warning: .*###\\s*/', '', $openingCommentedIncludeStatement);
1042
1043 // type of match: FILE or DIR
1044 $inIncludePart = strtoupper($matches[1]);
1045 $fileName = $matches[2];
1046 $optionalProperties = $matches[3];
1047
1048 $expectedEndTag = '### <INCLUDE_TYPOSCRIPT: source="' . $inIncludePart . ':' . $fileName . '"' . $optionalProperties . '> END';
1049 // Strip all whitespace characters to make comparison safer
1050 $expectedEndTag = strtolower(preg_replace('/\s/', '', $expectedEndTag));
1051 } else {
1052 // If this is not a beginning commented include statement this line goes into the rest content
1053 $restContent[] = $line;
1054 }
1055 //if (is_array($matches)) GeneralUtility::devLog('matches', 'TypoScriptParser', 0, $matches);
1056 } else {
1057 // Inside commented include statements
1058 // Search for the matching ending commented include statement
1059 $strippedLine = preg_replace('/\s/', '', $line);
1060 if (stripos($strippedLine, $expectedEndTag) !== FALSE) {
1061 // Found the matching ending include statement
1062 $fileContentString = implode(PHP_EOL, $fileContent);
1063
1064 // Write the content to the file
1065
1066 // Resolve a possible relative paths if a parent file is given
1067 if ($parentFilenameOrPath !== '' && $fileName[0] === '.') {
1068 $realFileName = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $fileName);
1069 } else {
1070 $realFileName = $fileName;
1071 }
1072 $realFileName = GeneralUtility::getFileAbsFileName($realFileName);
1073
1074 if ($inIncludePart === 'FILE') {
1075 // Some file checks
1076 if (!GeneralUtility::verifyFilenameAgainstDenyPattern($realFileName)) {
1077 throw new \UnexpectedValueException(sprintf('File "%s" was not included since it is not allowed due to fileDenyPattern.', $fileName), 1382651858);
1078 }
1079 if (empty($realFileName)) {
1080 throw new \UnexpectedValueException(sprintf('"%s" is not a valid file location.', $fileName), 1294586441);
1081 }
1082 if (!is_writable($realFileName)) {
1083 throw new \RuntimeException(sprintf('"%s" is not writable.', $fileName), 1294586442);
1084 }
1085 if (in_array($realFileName, $extractedFileNames)) {
1086 throw new \RuntimeException(sprintf('Recursive/multiple inclusion of file "%s"', $realFileName), 1294586443);
1087 }
1088 $extractedFileNames[] = $realFileName;
1089
1090 // Recursive call to detected nested commented include statements
1091 $fileContentString = self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName);
1092
1093 // Write the content to the file
1094 if (!GeneralUtility::writeFile($realFileName, $fileContentString)) {
1095 throw new \RuntimeException(sprintf('Could not write file "%s"', $realFileName), 1294586444);
1096 }
1097 // Insert reference to the file in the rest content
1098 $restContent[] = '<INCLUDE_TYPOSCRIPT: source="FILE:' . $fileName . '"' . $optionalProperties . '>';
1099 } else {
1100 // must be DIR
1101
1102 // Some file checks
1103 if (empty($realFileName)) {
1104 throw new \UnexpectedValueException(sprintf('"%s" is not a valid location.', $fileName), 1366493602);
1105 }
1106 if (!is_dir($realFileName)) {
1107 throw new \RuntimeException(sprintf('"%s" is not a directory.', $fileName), 1366493603);
1108 }
1109 if (in_array($realFileName, $extractedFileNames)) {
1110 throw new \RuntimeException(sprintf('Recursive/multiple inclusion of directory "%s"', $realFileName), 1366493604);
1111 }
1112 $extractedFileNames[] = $realFileName;
1113
1114 // Recursive call to detected nested commented include statements
1115 self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName);
1116
1117 // just drop content between tags since it should usually just contain individual files from that dir
1118
1119 // Insert reference to the dir in the rest content
1120 $restContent[] = '<INCLUDE_TYPOSCRIPT: source="DIR:' . $fileName . '"' . $optionalProperties . '>';
1121 }
1122
1123 // Reset variables (preparing for the next commented include statement)
1124 $fileContent = array();
1125 $fileName = NULL;
1126 $inIncludePart = FALSE;
1127 $openingCommentedIncludeStatement = NULL;
1128 // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts
1129 // an additional empty line, remove this again
1130 $skipNextLineIfEmpty = TRUE;
1131 } else {
1132 // If this is not a ending commented include statement this line goes into the file content
1133 $fileContent[] = $line;
1134 }
1135 }
1136 }
1137 // If we're still inside commented include statements copy the lines back to the rest content
1138 if ($inIncludePart) {
1139 $restContent[] = $openingCommentedIncludeStatement . ' ### Warning: Corresponding end line missing! ###';
1140 $restContent = array_merge($restContent, $fileContent);
1141 }
1142 $restContentString = implode(PHP_EOL, $restContent);
1143 return $restContentString;
1144 }
1145
1146 /**
1147 * Processes the string in each value of the input array with extractIncludes
1148 *
1149 * @param array $array Array with TypoScript in each value
1150 * @return array Same array but where the values has been processed with extractIncludes
1151 */
1152 static public function extractIncludes_array(array $array) {
1153 foreach ($array as $k => $v) {
1154 $array[$k] = self::extractIncludes($array[$k]);
1155 }
1156 return $array;
1157 }
1158
1159 /**********************************
1160 *
1161 * Syntax highlighting
1162 *
1163 *********************************/
1164 /**
1165 * Syntax highlight a TypoScript text
1166 * Will parse the content. Remember, the internal setup array may contain invalid parsed content since conditions are ignored!
1167 *
1168 * @param string $string The TypoScript text
1169 * @param mixed $lineNum If blank, linenumbers are NOT printed. If array then the first key is the linenumber offset to add to the internal counter.
1170 * @param bool $highlightBlockMode If set, then the highlighted output will be formatted in blocks based on the brace levels. prespace will be ignored and empty lines represented with a single no-break-space.
1171 * @return string HTML code for the syntax highlighted string
1172 */
1173 public function doSyntaxHighlight($string, $lineNum = '', $highlightBlockMode = FALSE) {
1174 $this->syntaxHighLight = 1;
1175 $this->highLightData = array();
1176 $this->error = array();
1177 // 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.
1178 $string = str_replace(CR, '', $string);
1179 $this->parse($string);
1180 return $this->syntaxHighlight_print($lineNum, $highlightBlockMode);
1181 }
1182
1183 /**
1184 * Registers a part of a TypoScript line for syntax highlighting.
1185 *
1186 * @param string $code Key from the internal array $this->highLightStyles
1187 * @param int $pointer Pointer to the line in $this->raw which this is about
1188 * @param int $strlen The number of chars LEFT on this line before the end is reached.
1189 * @return void
1190 * @access private
1191 * @see parse()
1192 */
1193 public function regHighLight($code, $pointer, $strlen = -1) {
1194 if ($strlen === -1) {
1195 $this->highLightData[$pointer] = array(array($code, 0));
1196 } else {
1197 $this->highLightData[$pointer][] = array($code, $strlen);
1198 }
1199 $this->highLightData_bracelevel[$pointer] = $this->inBrace;
1200 }
1201
1202 /**
1203 * Formatting the TypoScript code in $this->raw based on the data collected by $this->regHighLight in $this->highLightData
1204 *
1205 * @param mixed $lineNumDat If blank, linenumbers are NOT printed. If array then the first key is the linenumber offset to add to the internal counter.
1206 * @param bool $highlightBlockMode If set, then the highlighted output will be formatted in blocks based on the brace levels. prespace will be ignored and empty lines represented with a single no-break-space.
1207 * @return string HTML content
1208 * @access private
1209 * @see doSyntaxHighlight()
1210 */
1211 public function syntaxHighlight_print($lineNumDat, $highlightBlockMode) {
1212 // Registers all error messages in relation to their linenumber
1213 $errA = array();
1214 foreach ($this->errors as $err) {
1215 $errA[$err[2]][] = $err[0];
1216 }
1217 // Generates the syntax highlighted output:
1218 $lines = array();
1219 foreach ($this->raw as $rawP => $value) {
1220 $start = 0;
1221 $strlen = strlen($value);
1222 $lineC = '';
1223 if (is_array($this->highLightData[$rawP])) {
1224 foreach ($this->highLightData[$rawP] as $set) {
1225 $len = $strlen - $start - $set[1];
1226 if ($len > 0) {
1227 $part = substr($value, $start, $len);
1228 $start += $len;
1229 $st = $this->highLightStyles[isset($this->highLightStyles[$set[0]]) ? $set[0] : 'default'];
1230 if (!$highlightBlockMode || $set[0] !== 'prespace') {
1231 $lineC .= $st[0] . htmlspecialchars($part) . $st[1];
1232 }
1233 } elseif ($len < 0) {
1234 debug(array($len, $value, $rawP));
1235 }
1236 }
1237 } else {
1238 debug(array($value));
1239 }
1240 if (strlen(substr($value, $start))) {
1241 $lineC .= $this->highLightStyles['ignored'][0] . htmlspecialchars(substr($value, $start)) . $this->highLightStyles['ignored'][1];
1242 }
1243 if ($errA[$rawP]) {
1244 $lineC .= $this->highLightStyles['error'][0] . '<strong> - ERROR:</strong> ' . htmlspecialchars(implode(';', $errA[$rawP])) . $this->highLightStyles['error'][1];
1245 }
1246 if ($highlightBlockMode && $this->highLightData_bracelevel[$rawP]) {
1247 $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>';
1248 }
1249 if (is_array($lineNumDat)) {
1250 $lineNum = $rawP + $lineNumDat[0];
1251 if ($this->parentObject instanceof \TYPO3\CMS\Core\TypoScript\ExtendedTemplateService) {
1252 $lineNum = $this->parentObject->ext_lnBreakPointWrap($lineNum, $lineNum);
1253 }
1254 $lineC = $this->highLightStyles['linenum'][0] . str_pad($lineNum, 4, ' ', STR_PAD_LEFT) . ':' . $this->highLightStyles['linenum'][1] . ' ' . $lineC;
1255 }
1256 $lines[] = $lineC;
1257 }
1258 return '<pre class="ts-hl">' . implode(LF, $lines) . '</pre>';
1259 }
1260
1261 }