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