[BUGFIX] Cannot load css_styled_content TS using INCLUDE_TYPOSCRIPT
[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 textfile 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
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
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 $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 * @return void
221 * @todo Define visibility
222 */
223 public function parse($string, $matchObj = '') {
224 $this->raw = explode(LF, $string);
225 $this->rawP = 0;
226 $pre = '[GLOBAL]';
227 while ($pre) {
228 if ($this->breakPointLN && $pre == '[_BREAK]') {
229 $this->error('Breakpoint at ' . ($this->lineNumberOffset + $this->rawP - 2) . ': Line content was "' . $this->raw[($this->rawP - 2)] . '"', 1);
230 break;
231 }
232 if (strtoupper($pre) == '[GLOBAL]' || strtoupper($pre) == '[END]' || !$this->lastConditionTrue && strtoupper($pre) == '[ELSE]') {
233 $pre = trim($this->parseSub($this->setup));
234 $this->lastConditionTrue = 1;
235 } else {
236 // We're in a specific section. Therefore we log this section
237 if (strtoupper($pre) != '[ELSE]') {
238 $this->sections[md5($pre)] = $pre;
239 }
240 if (is_object($matchObj) && $matchObj->match($pre) || $this->syntaxHighLight) {
241 if (strtoupper($pre) != '[ELSE]') {
242 $this->sectionsMatch[md5($pre)] = $pre;
243 }
244 $pre = trim($this->parseSub($this->setup));
245 $this->lastConditionTrue = 1;
246 } else {
247 $pre = trim($this->nextDivider());
248 $this->lastConditionTrue = 0;
249 }
250 }
251 }
252 if ($this->inBrace) {
253 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': The script is short of ' . $this->inBrace . ' end brace(s)', 1);
254 }
255 if ($this->multiLineEnabled) {
256 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': A multiline value section is not ended with a parenthesis!', 1);
257 }
258 $this->lineNumberOffset += count($this->raw) + 1;
259 }
260
261 /**
262 * 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.
263 *
264 * @return string The condition value
265 * @see parse()
266 * @todo Define visibility
267 */
268 public function nextDivider() {
269 while (isset($this->raw[$this->rawP])) {
270 $line = ltrim($this->raw[$this->rawP]);
271 $this->rawP++;
272 if ($line && substr($line, 0, 1) == '[') {
273 return $line;
274 }
275 }
276 }
277
278 /**
279 * Parsing the $this->raw TypoScript lines from pointer, $this->rawP
280 *
281 * @param array $setup Reference to the setup array in which to accumulate the values.
282 * @return string Returns the string of the condition found, the exit signal or possible nothing (if it completed parsing with no interruptions)
283 * @todo Define visibility
284 */
285 public function parseSub(&$setup) {
286 while (isset($this->raw[$this->rawP])) {
287 $line = ltrim($this->raw[$this->rawP]);
288 $lineP = $this->rawP;
289 $this->rawP++;
290 if ($this->syntaxHighLight) {
291 $this->regHighLight('prespace', $lineP, strlen($line));
292 }
293 // Breakpoint?
294 // By adding 1 we get that line processed
295 if ($this->breakPointLN && $this->lineNumberOffset + $this->rawP - 1 == $this->breakPointLN + 1) {
296 return '[_BREAK]';
297 }
298 // Set comment flag?
299 if (!$this->multiLineEnabled && substr($line, 0, 2) == '/*') {
300 $this->commentSet = 1;
301 }
302 // If $this->multiLineEnabled we will go and get the line values here because we know, the first if() will be TRUE.
303 if (!$this->commentSet && ($line || $this->multiLineEnabled)) {
304 // If multiline is enabled. Escape by ')'
305 if ($this->multiLineEnabled) {
306 // Multiline ends...
307 if (substr($line, 0, 1) == ')') {
308 if ($this->syntaxHighLight) {
309 $this->regHighLight('operator', $lineP, strlen($line) - 1);
310 }
311 // Disable multiline
312 $this->multiLineEnabled = 0;
313 $theValue = implode($this->multiLineValue, LF);
314 if (strstr($this->multiLineObject, '.')) {
315 // Set the value deeper.
316 $this->setVal($this->multiLineObject, $setup, array($theValue));
317 } else {
318 // Set value regularly
319 $setup[$this->multiLineObject] = $theValue;
320 if ($this->lastComment && $this->regComments) {
321 $setup[$this->multiLineObject . '..'] .= $this->lastComment;
322 }
323 if ($this->regLinenumbers) {
324 $setup[$this->multiLineObject . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
325 }
326 }
327 } else {
328 if ($this->syntaxHighLight) {
329 $this->regHighLight('value', $lineP);
330 }
331 $this->multiLineValue[] = $this->raw[$this->rawP - 1];
332 }
333 } elseif ($this->inBrace == 0 && substr($line, 0, 1) == '[') {
334 // Beginning of condition (only on level zero compared to brace-levels
335 if ($this->syntaxHighLight) {
336 $this->regHighLight('condition', $lineP);
337 }
338 return $line;
339 } else {
340 // Return if GLOBAL condition is set - no matter what.
341 if (substr($line, 0, 1) == '[' && strtoupper(trim($line)) == '[GLOBAL]') {
342 if ($this->syntaxHighLight) {
343 $this->regHighLight('condition', $lineP);
344 }
345 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': On return to [GLOBAL] scope, the script was short of ' . $this->inBrace . ' end brace(s)', 1);
346 $this->inBrace = 0;
347 return $line;
348 } elseif (strcspn($line, '}#/') != 0) {
349 // If not brace-end or comment
350 // Find object name string until we meet an operator
351 $varL = strcspn($line, ' {=<>:(');
352 $objStrName = trim(substr($line, 0, $varL));
353 if ($this->syntaxHighLight) {
354 $this->regHighLight('objstr', $lineP, strlen(substr($line, $varL)));
355 }
356 if (strlen($objStrName)) {
357 $r = array();
358 if ($this->strict && preg_match('/[^[:alnum:]_\\\\\\.-]/i', $objStrName, $r)) {
359 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_-\\."');
360 } else {
361 $line = ltrim(substr($line, $varL));
362 if ($this->syntaxHighLight) {
363 $this->regHighLight('objstr_postspace', $lineP, strlen($line));
364 if (strlen($line) > 0) {
365 $this->regHighLight('operator', $lineP, strlen($line) - 1);
366 $this->regHighLight('operator_postspace', $lineP, strlen(ltrim(substr($line, 1))));
367 }
368 }
369 // Checking for special TSparser properties (to change TS values at parsetime)
370 $match = array();
371 if (preg_match('/^:=([^\\(]+)\\((.*)\\).*/', $line, $match)) {
372 $tsFunc = trim($match[1]);
373 $tsFuncArg = $match[2];
374 list($currentValue) = $this->getVal($objStrName, $setup);
375 $tsFuncArg = str_replace(array('\\\\', '\\n', '\\t'), array('\\', LF, TAB), $tsFuncArg);
376 $newValue = $this->executeValueModifier($tsFunc, $tsFuncArg, $currentValue);
377 if (isset($newValue)) {
378 $line = '= ' . $newValue;
379 }
380 }
381 switch (substr($line, 0, 1)) {
382 case '=':
383 if ($this->syntaxHighLight) {
384 $this->regHighLight('value', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
385 }
386 if (strstr($objStrName, '.')) {
387 $value = array();
388 $value[0] = trim(substr($line, 1));
389 $this->setVal($objStrName, $setup, $value);
390 } else {
391 $setup[$objStrName] = trim(substr($line, 1));
392 if ($this->lastComment && $this->regComments) {
393 // Setting comment..
394 $setup[$objStrName . '..'] .= $this->lastComment;
395 }
396 if ($this->regLinenumbers) {
397 $setup[$objStrName . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
398 }
399 }
400 break;
401 case '{':
402 $this->inBrace++;
403 if (strstr($objStrName, '.')) {
404 $exitSig = $this->rollParseSub($objStrName, $setup);
405 if ($exitSig) {
406 return $exitSig;
407 }
408 } else {
409 if (!isset($setup[($objStrName . '.')])) {
410 $setup[$objStrName . '.'] = array();
411 }
412 $exitSig = $this->parseSub($setup[$objStrName . '.']);
413 if ($exitSig) {
414 return $exitSig;
415 }
416 }
417 break;
418 case '(':
419 $this->multiLineObject = $objStrName;
420 $this->multiLineEnabled = 1;
421 $this->multiLineValue = array();
422 break;
423 case '<':
424 if ($this->syntaxHighLight) {
425 $this->regHighLight('value_copy', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
426 }
427 $theVal = trim(substr($line, 1));
428 if (substr($theVal, 0, 1) == '.') {
429 $res = $this->getVal(substr($theVal, 1), $setup);
430 } else {
431 $res = $this->getVal($theVal, $this->setup);
432 }
433 $this->setVal($objStrName, $setup, unserialize(serialize($res)), 1);
434 // 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.
435 break;
436 case '>':
437 if ($this->syntaxHighLight) {
438 $this->regHighLight('value_unset', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
439 }
440 $this->setVal($objStrName, $setup, 'UNSET');
441 break;
442 default:
443 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not preceded by any operator, =<>({');
444 }
445 }
446 $this->lastComment = '';
447 }
448 } elseif (substr($line, 0, 1) == '}') {
449 $this->inBrace--;
450 $this->lastComment = '';
451 if ($this->syntaxHighLight) {
452 $this->regHighLight('operator', $lineP, strlen($line) - 1);
453 }
454 if ($this->inBrace < 0) {
455 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': An end brace is in excess.', 1);
456 $this->inBrace = 0;
457 } else {
458 break;
459 }
460 } else {
461 if ($this->syntaxHighLight) {
462 $this->regHighLight('comment', $lineP);
463 }
464 // Comment. The comments are concatenated in this temporary string:
465 if ($this->regComments) {
466 $this->lastComment .= trim($line) . LF;
467 }
468 }
469 }
470 }
471 // Unset comment
472 if ($this->commentSet) {
473 if ($this->syntaxHighLight) {
474 $this->regHighLight('comment', $lineP);
475 }
476 if (substr($line, 0, 2) == '*/') {
477 $this->commentSet = 0;
478 }
479 }
480 }
481 }
482
483 /**
484 * Executes operator functions, called from TypoScript
485 * example: page.10.value := appendString(!)
486 *
487 * @param string $modifierName TypoScript function called
488 * @param string $modifierArgument Function arguments; In case of multiple arguments, the method must split on its own
489 * @param string $currentValue Current TypoScript value
490 * @return string Modification result
491 */
492 protected function executeValueModifier($modifierName, $modifierArgument = NULL, $currentValue = NULL) {
493 $newValue = NULL;
494 switch ($modifierName) {
495 case 'prependString':
496 $newValue = $modifierArgument . $currentValue;
497 break;
498 case 'appendString':
499 $newValue = $currentValue . $modifierArgument;
500 break;
501 case 'removeString':
502 $newValue = str_replace($modifierArgument, '', $currentValue);
503 break;
504 case 'replaceString':
505 list($fromStr, $toStr) = explode('|', $modifierArgument, 2);
506 $newValue = str_replace($fromStr, $toStr, $currentValue);
507 break;
508 case 'addToList':
509 $newValue = (strcmp('', $currentValue) ? $currentValue . ',' : '') . trim($modifierArgument);
510 break;
511 case 'removeFromList':
512 $existingElements = GeneralUtility::trimExplode(',', $currentValue);
513 $removeElements = GeneralUtility::trimExplode(',', $modifierArgument);
514 if (count($removeElements)) {
515 $newValue = implode(',', array_diff($existingElements, $removeElements));
516 }
517 break;
518 case 'uniqueList':
519 $elements = GeneralUtility::trimExplode(',', $currentValue);
520 $newValue = implode(',', array_unique($elements));
521 break;
522 case 'reverseList':
523 $elements = GeneralUtility::trimExplode(',', $currentValue);
524 $newValue = implode(',', array_reverse($elements));
525 break;
526 case 'sortList':
527 $elements = GeneralUtility::trimExplode(',', $currentValue);
528 $arguments = GeneralUtility::trimExplode(',', $modifierArgument);
529 $arguments = array_map('strtolower', $arguments);
530 $sort_flags = SORT_REGULAR;
531 if (in_array('numeric', $arguments)) {
532 $sort_flags = SORT_NUMERIC;
533 }
534 sort($elements, $sort_flags);
535 if (in_array('descending', $arguments)) {
536 $elements = array_reverse($elements);
537 }
538 $newValue = implode(',', $elements);
539 break;
540 default:
541 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName])) {
542 $hookMethod = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName];
543 $params = array('currentValue' => $currentValue, 'functionArgument' => $modifierArgument);
544 $fakeThis = FALSE;
545 $newValue = GeneralUtility::callUserFunction($hookMethod, $params, $fakeThis);
546 } else {
547 GeneralUtility::sysLog(
548 'Missing function definition for ' . $modifierName . ' on TypoScript',
549 'Core',
550 GeneralUtility::SYSLOG_SEVERITY_WARNING
551 );
552 }
553 }
554 return $newValue;
555 }
556
557 /**
558 * Parsing of TypoScript keys inside a curly brace where the key is composite of at least two keys, thus having to recursively call itself to get the value
559 *
560 * @param string $string The object sub-path, eg "thisprop.another_prot
561 * @param array $setup The local setup array from the function calling this function
562 * @return string Returns the exitSignal
563 * @see parseSub()
564 * @todo Define visibility
565 */
566 public function rollParseSub($string, &$setup) {
567 if ((string) $string != '') {
568 $keyLen = strcspn($string, '.');
569 if ($keyLen == strlen($string)) {
570 $key = $string . '.';
571 if (!isset($setup[$key])) {
572 $setup[$key] = array();
573 }
574 $exitSig = $this->parseSub($setup[$key]);
575 if ($exitSig) {
576 return $exitSig;
577 }
578 } else {
579 $key = substr($string, 0, $keyLen) . '.';
580 if (!isset($setup[$key])) {
581 $setup[$key] = array();
582 }
583 $exitSig = $this->rollParseSub(substr($string, $keyLen + 1), $setup[$key]);
584 if ($exitSig) {
585 return $exitSig;
586 }
587 }
588 }
589 }
590
591 /**
592 * Get a value/property pair for an object path in TypoScript, eg. "myobject.myvalue.mysubproperty". Here: Used by the "copy" operator, <
593 *
594 * @param string $string Object path for which to get the value
595 * @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.
596 * @return array An array with keys 0/1 being value/property respectively
597 * @todo Define visibility
598 */
599 public function getVal($string, $setup) {
600 if ((string) $string != '') {
601 $keyLen = strcspn($string, '.');
602 if ($keyLen == strlen($string)) {
603 // Added 6/6/03. Shouldn't hurt
604 $retArr = array();
605 if (isset($setup[$string])) {
606 $retArr[0] = $setup[$string];
607 }
608 if (isset($setup[$string . '.'])) {
609 $retArr[1] = $setup[$string . '.'];
610 }
611 return $retArr;
612 } else {
613 $key = substr($string, 0, $keyLen) . '.';
614 if ($setup[$key]) {
615 return $this->getVal(substr($string, $keyLen + 1), $setup[$key]);
616 }
617 }
618 }
619 }
620
621 /**
622 * Setting a value/property of an object string in the setup array.
623 *
624 * @param string $string The object sub-path, eg "thisprop.another_prot
625 * @param array $setup The local setup array from the function calling this function.
626 * @param array $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)
627 * @param boolean $wipeOut If set, then both value and property is wiped out when a copy is made of another value.
628 * @return void
629 * @todo Define visibility
630 */
631 public function setVal($string, &$setup, $value, $wipeOut = 0) {
632 if ((string) $string != '') {
633 $keyLen = strcspn($string, '.');
634 if ($keyLen == strlen($string)) {
635 if ($value == 'UNSET') {
636 unset($setup[$string]);
637 unset($setup[$string . '.']);
638 if ($this->regLinenumbers) {
639 $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '>';
640 }
641 } else {
642 $lnRegisDone = 0;
643 if ($wipeOut && $this->strict) {
644 unset($setup[$string]);
645 unset($setup[$string . '.']);
646 if ($this->regLinenumbers) {
647 $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '<';
648 $lnRegisDone = 1;
649 }
650 }
651 if (isset($value[0])) {
652 $setup[$string] = $value[0];
653 }
654 if (isset($value[1])) {
655 $setup[$string . '.'] = $value[1];
656 }
657 if ($this->lastComment && $this->regComments) {
658 $setup[$string . '..'] .= $this->lastComment;
659 }
660 if ($this->regLinenumbers && !$lnRegisDone) {
661 $setup[$string . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
662 }
663 }
664 } else {
665 $key = substr($string, 0, $keyLen) . '.';
666 if (!isset($setup[$key])) {
667 $setup[$key] = array();
668 }
669 $this->setVal(substr($string, $keyLen + 1), $setup[$key], $value);
670 }
671 }
672 }
673
674 /**
675 * Stacks errors/messages from the TypoScript parser into an internal array, $this->error
676 * 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.
677 *
678 * @param string $err The error message string
679 * @param integer $num The error severity (in the scale of $GLOBALS['TT']->setTSlogMessage: Approx: 2=warning, 1=info, 0=nothing, 3=fatal.)
680 * @return void
681 * @todo Define visibility
682 */
683 public function error($err, $num = 2) {
684 if (is_object($GLOBALS['TT'])) {
685 $GLOBALS['TT']->setTSlogMessage($err, $num);
686 }
687 $this->errors[] = array($err, $num, $this->rawP - 1, $this->lineNumberOffset);
688 }
689
690 /**
691 * Checks the input string (un-parsed TypoScript) for include-commands ("<INCLUDE_TYPOSCRIPT: ....")
692 * Use: \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines()
693 *
694 * @param string $string Unparsed TypoScript
695 * @param integer $cycle_counter Counter for detecting endless loops
696 * @param boolean $returnFiles When set an array containing the resulting typoscript and all included files will get returned
697 * @return string Complete TypoScript with includes added.
698 * @static
699 */
700 static public function checkIncludeLines($string, $cycle_counter = 1, $returnFiles = FALSE) {
701 $includedFiles = array();
702 if ($cycle_counter > 100) {
703 GeneralUtility::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
704 if ($returnFiles) {
705 return array(
706 'typoscript' => '',
707 'files' => $includedFiles
708 );
709 }
710 return '
711 ###
712 ### ERROR: Recursion!
713 ###
714 ';
715 }
716 $splitStr = '<INCLUDE_TYPOSCRIPT:';
717 if (strstr($string, $splitStr)) {
718 $newString = '';
719 // Adds line break char before/after
720 $allParts = explode($splitStr, LF . $string . LF);
721 foreach ($allParts as $c => $v) {
722 // First goes through
723 if (!$c) {
724 $newString .= $v;
725 } elseif (preg_match('/\\r?\\n\\s*$/', $allParts[$c - 1])) {
726 $subparts = explode('>', $v, 2);
727 // There must be a line-break char after
728 if (preg_match('/^\\s*\\r?\\n/', $subparts[1])) {
729 // SO, the include was positively recognized:
730 $newString .= '### ' . $splitStr . $subparts[0] . '> BEGIN:' . LF;
731 $params = GeneralUtility::get_tag_attributes($subparts[0]);
732 if ($params['source']) {
733 $sourceParts = explode(':', $params['source'], 2);
734 switch (strtolower(trim($sourceParts[0]))) {
735 case 'file':
736 $filename = GeneralUtility::getFileAbsFileName(trim($sourceParts[1]));
737 // Must exist and must not contain '..' and must be relative
738 if (strcmp($filename, '')) {
739 // Check for allowed files
740 if (GeneralUtility::verifyFilenameAgainstDenyPattern($filename)) {
741 if (@is_file($filename)) {
742 // Check for includes in included text
743 $includedFiles[] = $filename;
744 $included_text = self::checkIncludeLines(GeneralUtility::getUrl($filename), $cycle_counter + 1, $returnFiles);
745 // If the method also has to return all included files, merge currently included
746 // files with files included by recursively calling itself
747 if ($returnFiles && is_array($included_text)) {
748 $includedFiles = array_merge($includedFiles, $included_text['files']);
749 $included_text = $included_text['typoscript'];
750 }
751 $newString .= $included_text . LF;
752
753 // load default TypoScript for content rendering templates like
754 // css_styled_content if those have been included through f.e.
755 // <INCLUDE_TYPOSCRIPT: source="FILE:EXT:css_styled_content/static/setup.txt">
756 $filePointer = strtolower(trim($sourceParts[1]));
757 if (GeneralUtility::isFirstPartOfStr($filePointer, 'ext:')) {
758 $filePointerPathParts = explode('/', substr($filePointer, 4));
759
760 // remove file part, determine whether to load setup or constants
761 list($includeType, ) = explode('.', array_pop($filePointerPathParts));
762
763 if (in_array($includeType, array('setup', 'constants'))) {
764 // adapt extension key to required format (no underscores)
765 $filePointerPathParts[0] = str_replace('_', '', $filePointerPathParts[0]);
766
767 // load default TypoScript
768 $defaultTypoScriptKey = implode('/', $filePointerPathParts) . '/';
769 if (isset($GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $includeType . '.'][$defaultTypoScriptKey])) {
770 $newString .= $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $includeType . '.'][$defaultTypoScriptKey];
771 }
772 }
773 }
774 } else {
775 $newString .= '
776 ###
777 ### ERROR: File "' . $filename . '" was not was not found.
778 ###
779
780 ';
781 GeneralUtility::sysLog('File "' . $filename . '" was not found.', 'Core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
782 }
783 } else {
784 $newString .= '
785 ###
786 ### ERROR: File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern
787 ###
788
789 ';
790 GeneralUtility::sysLog('File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern', 'Core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
791 }
792 }
793 break;
794 }
795 }
796 $newString .= '### ' . $splitStr . $subparts[0] . '> END:' . LF;
797 $newString .= $subparts[1];
798 } else {
799 $newString .= $splitStr . $v;
800 }
801 } else {
802 $newString .= $splitStr . $v;
803 }
804 }
805 // Not the first/last linebreak char.
806 $string = substr($newString, 1, -1);
807 }
808 // When all included files should get returned, simply return an compound array containing
809 // the TypoScript with all "includes" processed and the files which got included
810 if ($returnFiles) {
811 return array(
812 'typoscript' => $string,
813 'files' => $includedFiles
814 );
815 }
816 return $string;
817 }
818
819 /**
820 * Parses the string in each value of the input array for include-commands
821 *
822 * @param array $array Array with TypoScript in each value
823 * @return array Same array but where the values has been parsed for include-commands
824 */
825 static public function checkIncludeLines_array($array) {
826 foreach ($array as $k => $v) {
827 $array[$k] = self::checkIncludeLines($array[$k]);
828 }
829 return $array;
830 }
831
832 /**
833 * Search for commented INCLUDE_TYPOSCRIPT statements
834 * and save the content between the BEGIN and the END line to the specified file
835 *
836 * @param string $string Template content
837 * @param integer $cycle_counter Counter for detecting endless loops
838 * @param array $extractedFileNames
839 * @return string Template content with uncommented include statements
840 */
841 static public function extractIncludes($string, $cycle_counter = 1, $extractedFileNames = array()) {
842 if ($cycle_counter > 10) {
843 GeneralUtility::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', GeneralUtility::SYSLOG_SEVERITY_WARNING);
844 return '
845 ###
846 ### ERROR: Recursion!
847 ###
848 ';
849 }
850 $fileContent = array();
851 $restContent = array();
852 $fileName = NULL;
853 $inIncludePart = FALSE;
854 $lines = explode('
855 ', $string);
856 $skipNextLineIfEmpty = FALSE;
857 $openingCommentedIncludeStatement = NULL;
858 foreach ($lines as $line) {
859 // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts
860 // an additional empty line, remove this again
861 if ($skipNextLineIfEmpty) {
862 if (trim($line) == '') {
863 continue;
864 }
865 $skipNextLineIfEmpty = FALSE;
866 }
867 // Outside commented include statements
868 if (!$inIncludePart) {
869 // Search for beginning commented include statements
870 $matches = array();
871 if (preg_match('/###\\s*<INCLUDE_TYPOSCRIPT:\\s*source\\s*=\\s*"\\s*FILE\\s*:\\s*(.*)\\s*">\\s*BEGIN/i', $line, $matches)) {
872 // Save this line in case there is no ending tag
873 $openingCommentedIncludeStatement = trim($line);
874 $openingCommentedIncludeStatement = trim(preg_replace('/### Warning: .*###/', '', $openingCommentedIncludeStatement));
875 // Found a commented include statement
876 $fileName = trim($matches[1]);
877 $inIncludePart = TRUE;
878 $expectedEndTag = '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $fileName . '"> END';
879 // Strip all whitespace characters to make comparision safer
880 $expectedEndTag = strtolower(preg_replace('/\\s/', '', $expectedEndTag));
881 } else {
882 // If this is not a beginning commented include statement this line goes into the rest content
883 $restContent[] = $line;
884 }
885 } else {
886 // Inside commented include statements
887 // Search for the matching ending commented include statement
888 $strippedLine = strtolower(preg_replace('/\\s/', '', $line));
889 if (strpos($strippedLine, $expectedEndTag) !== FALSE) {
890 // Found the matching ending include statement
891 $fileContentString = implode('
892 ', $fileContent);
893 // Write the content to the file
894 $realFileName = GeneralUtility::getFileAbsFileName($fileName);
895 // Some file checks
896 if (empty($realFileName)) {
897 throw new \UnexpectedValueException(sprintf('"%s" is not a valid file location.', $fileName), 1294586441);
898 }
899 if (!is_writable($realFileName)) {
900 throw new \RuntimeException(sprintf('"%s" is not writable.', $fileName), 1294586442);
901 }
902 if (in_array($realFileName, $extractedFileNames)) {
903 throw new \RuntimeException(sprintf('Recursive/multiple inclusion of file "%s"', $realFileName), 1294586443);
904 }
905 $extractedFileNames[] = $realFileName;
906 // Recursive call to detected nested commented include statements
907 $fileContentString = self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames);
908 if (!GeneralUtility::writeFile($realFileName, $fileContentString)) {
909 throw new \RuntimeException(sprintf('Could not write file "%s"', $realFileName), 1294586444);
910 }
911 // Insert reference to the file in the rest content
912 $restContent[] = '<INCLUDE_TYPOSCRIPT: source="FILE: ' . $fileName . '">';
913 // Reset variables (preparing for the next commented include statement)
914 $fileContent = array();
915 $fileName = NULL;
916 $inIncludePart = FALSE;
917 $openingCommentedIncludeStatement = NULL;
918 // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts
919 // an additional empty line, remove this again
920 $skipNextLineIfEmpty = TRUE;
921 } else {
922 // If this is not a ending commented include statement this line goes into the file content
923 $fileContent[] = $line;
924 }
925 }
926 }
927 // If we're still inside commented include statements copy the lines back to the rest content
928 if ($inIncludePart) {
929 $restContent[] = $openingCommentedIncludeStatement . ' ### Warning: Corresponding end line missing! ###';
930 $restContent = array_merge($restContent, $fileContent);
931 }
932 $restContentString = implode('
933 ', $restContent);
934 return $restContentString;
935 }
936
937 /**
938 * Processes the string in each value of the input array with extractIncludes
939 *
940 * @param array $array Array with TypoScript in each value
941 * @return array Same array but where the values has been processed with extractIncludes
942 */
943 static public function extractIncludes_array($array) {
944 foreach ($array as $k => $v) {
945 $array[$k] = self::extractIncludes($array[$k]);
946 }
947 return $array;
948 }
949
950 /**********************************
951 *
952 * Syntax highlighting
953 *
954 *********************************/
955 /**
956 * Syntax highlight a TypoScript text
957 * Will parse the content. Remember, the internal setup array may contain invalid parsed content since conditions are ignored!
958 *
959 * @param string $string The TypoScript text
960 * @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.
961 * @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.
962 * @return string HTML code for the syntax highlighted string
963 * @todo Define visibility
964 */
965 public function doSyntaxHighlight($string, $lineNum = '', $highlightBlockMode = 0) {
966 $this->syntaxHighLight = 1;
967 $this->highLightData = array();
968 $this->error = array();
969 // 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.
970 $string = str_replace(CR, '', $string);
971 $this->parse($string);
972 return $this->syntaxHighlight_print($lineNum, $highlightBlockMode);
973 }
974
975 /**
976 * Registers a part of a TypoScript line for syntax highlighting.
977 *
978 * @param string $code Key from the internal array $this->highLightStyles
979 * @param integer $pointer Pointer to the line in $this->raw which this is about
980 * @param integer $strlen The number of chars LEFT on this line before the end is reached.
981 * @return void
982 * @access private
983 * @see parse()
984 * @todo Define visibility
985 */
986 public function regHighLight($code, $pointer, $strlen = -1) {
987 if ($strlen == -1) {
988 $this->highLightData[$pointer] = array(array($code, 0));
989 } else {
990 $this->highLightData[$pointer][] = array($code, $strlen);
991 }
992 $this->highLightData_bracelevel[$pointer] = $this->inBrace;
993 }
994
995 /**
996 * Formatting the TypoScript code in $this->raw based on the data collected by $this->regHighLight in $this->highLightData
997 *
998 * @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.
999 * @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.
1000 * @return string HTML content
1001 * @access private
1002 * @see doSyntaxHighlight()
1003 * @todo Define visibility
1004 */
1005 public function syntaxHighlight_print($lineNumDat, $highlightBlockMode) {
1006 // Registers all error messages in relation to their linenumber
1007 $errA = array();
1008 foreach ($this->errors as $err) {
1009 $errA[$err[2]][] = $err[0];
1010 }
1011 // Generates the syntax highlighted output:
1012 $lines = array();
1013 foreach ($this->raw as $rawP => $value) {
1014 $start = 0;
1015 $strlen = strlen($value);
1016 $lineC = '';
1017 if (is_array($this->highLightData[$rawP])) {
1018 foreach ($this->highLightData[$rawP] as $set) {
1019 $len = $strlen - $start - $set[1];
1020 if ($len > 0) {
1021 $part = substr($value, $start, $len);
1022 $start += $len;
1023 $st = $this->highLightStyles[isset($this->highLightStyles[$set[0]]) ? $set[0] : 'default'];
1024 if (!$highlightBlockMode || $set[0] != 'prespace') {
1025 $lineC .= $st[0] . htmlspecialchars($part) . $st[1];
1026 }
1027 } elseif ($len < 0) {
1028 debug(array($len, $value, $rawP));
1029 }
1030 }
1031 } else {
1032 debug(array($value));
1033 }
1034 if (strlen(substr($value, $start))) {
1035 $lineC .= $this->highLightStyles['ignored'][0] . htmlspecialchars(substr($value, $start)) . $this->highLightStyles['ignored'][1];
1036 }
1037 if ($errA[$rawP]) {
1038 $lineC .= $this->highLightStyles['error'][0] . '<strong> - ERROR:</strong> ' . htmlspecialchars(implode(';', $errA[$rawP])) . $this->highLightStyles['error'][1];
1039 }
1040 if ($highlightBlockMode && $this->highLightData_bracelevel[$rawP]) {
1041 $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) : '') . '">' . (strcmp($lineC, '') ? $lineC : '&nbsp;') . '</span>';
1042 }
1043 if (is_array($lineNumDat)) {
1044 $lineNum = $rawP + $lineNumDat[0];
1045 if ($this->parentObject instanceof \TYPO3\CMS\Core\TypoScript\ExtendedTemplateService) {
1046 $lineNum = $this->parentObject->ext_lnBreakPointWrap($lineNum, $lineNum);
1047 }
1048 $lineC = $this->highLightStyles['linenum'][0] . str_pad($lineNum, 4, ' ', STR_PAD_LEFT) . ':' . $this->highLightStyles['linenum'][1] . ' ' . $lineC;
1049 }
1050 $lines[] = $lineC;
1051 }
1052 return '<pre class="ts-hl">' . implode(LF, $lines) . '</pre>';
1053 }
1054
1055 }
1056
1057
1058 ?>