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