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