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