[TASK] Remove superfluous parenthesis in sysext core
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / TypoScript / Parser / TypoScriptParser.php
1 <?php
2 namespace TYPO3\CMS\Core\TypoScript\Parser;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2011 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 * Contains the TypoScript parser class
31 *
32 * Revised for TYPO3 3.6 July/2003 by Kasper Skårhøj
33 *
34 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
35 */
36 /**
37 * The TypoScript parser
38 *
39 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
40 * @package TYPO3
41 * @subpackage t3lib
42 * @see t3lib_tstemplate, t3lib_BEfunc::getPagesTSconfig(), t3lib_beUserAuth::fetchGroupData(), t3lib_TStemplate::generateConfig()
43 */
44 class TypoScriptParser {
45
46 // If set, then key names cannot contain characters other than [:alnum:]_\.-
47 /**
48 * @todo Define visibility
49 */
50 public $strict = 1;
51
52 // Internal
53 // TypoScript hierarchy being build during parsing.
54 /**
55 * @todo Define visibility
56 */
57 public $setup = array();
58
59 // Raw data, the input string exploded by LF
60 /**
61 * @todo Define visibility
62 */
63 public $raw;
64
65 // Pointer to entry in raw data array
66 /**
67 * @todo Define visibility
68 */
69 public $rawP;
70
71 // Holding the value of the last comment
72 /**
73 * @todo Define visibility
74 */
75 public $lastComment = '';
76
77 // Internally set, used as internal flag to create a multi-line comment (one of those like /*... */)
78 /**
79 * @todo Define visibility
80 */
81 public $commentSet = 0;
82
83 // Internally set, when multiline value is accumulated
84 /**
85 * @todo Define visibility
86 */
87 public $multiLineEnabled = 0;
88
89 // Internally set, when multiline value is accumulated
90 /**
91 * @todo Define visibility
92 */
93 public $multiLineObject = '';
94
95 // Internally set, when multiline value is accumulated
96 /**
97 * @todo Define visibility
98 */
99 public $multiLineValue = array();
100
101 // Internally set, when in brace. Counter.
102 /**
103 * @todo Define visibility
104 */
105 public $inBrace = 0;
106
107 // 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.
108 /**
109 * @todo Define visibility
110 */
111 public $lastConditionTrue = 1;
112
113 // Tracking all conditions found
114 /**
115 * @todo Define visibility
116 */
117 public $sections = array();
118
119 // Tracking all matching conditions found
120 /**
121 * @todo Define visibility
122 */
123 public $sectionsMatch = array();
124
125 // If set, then syntax highlight mode is on; Call the function syntaxHighlight() to use this function
126 /**
127 * @todo Define visibility
128 */
129 public $syntaxHighLight = 0;
130
131 // Syntax highlight data is accumulated in this array. Used by syntaxHighlight_print() to construct the output.
132 /**
133 * @todo Define visibility
134 */
135 public $highLightData = array();
136
137 // Syntax highlight data keeping track of the curly brace level for each line
138 /**
139 * @todo Define visibility
140 */
141 public $highLightData_bracelevel = array();
142
143 // Debugging, analysis:
144 // DO NOT register the comments. This is default for the ordinary sitetemplate!
145 /**
146 * @todo Define visibility
147 */
148 public $regComments = 0;
149
150 // DO NOT register the linenumbers. This is default for the ordinary sitetemplate!
151 /**
152 * @todo Define visibility
153 */
154 public $regLinenumbers = 0;
155
156 // Error accumulation array.
157 /**
158 * @todo Define visibility
159 */
160 public $errors = array();
161
162 // Used for the error messages line number reporting. Set externally.
163 /**
164 * @todo Define visibility
165 */
166 public $lineNumberOffset = 0;
167
168 // Line for break point.
169 /**
170 * @todo Define visibility
171 */
172 public $breakPointLN = 0;
173
174 /**
175 * @todo Define visibility
176 */
177 public $highLightStyles = array(
178 'prespace' => array('<span class="ts-prespace">', '</span>'),
179 // Space before any content on a line
180 'objstr_postspace' => array('<span class="ts-objstr_postspace">', '</span>'),
181 // Space after the object string on a line
182 'operator_postspace' => array('<span class="ts-operator_postspace">', '</span>'),
183 // Space after the operator on a line
184 'operator' => array('<span class="ts-operator">', '</span>'),
185 // The operator char
186 'value' => array('<span class="ts-value">', '</span>'),
187 // The value of a line
188 'objstr' => array('<span class="ts-objstr">', '</span>'),
189 // The object string of a line
190 'value_copy' => array('<span class="ts-value_copy">', '</span>'),
191 // The value when the copy syntax (<) is used; that means the object reference
192 'value_unset' => array('<span class="ts-value_unset">', '</span>'),
193 // The value when an object is unset. Should not exist.
194 'ignored' => array('<span class="ts-ignored">', '</span>'),
195 // The "rest" of a line which will be ignored.
196 'default' => array('<span class="ts-default">', '</span>'),
197 // The default style if none other is applied.
198 'comment' => array('<span class="ts-comment">', '</span>'),
199 // Comment lines
200 'condition' => array('<span class="ts-condition">', '</span>'),
201 // Conditions
202 'error' => array('<span class="ts-error">', '</span>'),
203 // Error messages
204 'linenum' => array('<span class="ts-linenum">', '</span>')
205 );
206
207 // Additional attributes for the <span> tags for a blockmode line
208 /**
209 * @todo Define visibility
210 */
211 public $highLightBlockStyles = '';
212
213 // The hex-HTML color for the blockmode
214 /**
215 * @todo Define visibility
216 */
217 public $highLightBlockStyles_basecolor = '#cccccc';
218
219 //Instance of parentObject, used by t3lib_tsparser_ext
220 public $parentObject;
221
222 /**
223 * Start parsing the input TypoScript text piece. The result is stored in $this->setup
224 *
225 * @param string $string The TypoScript text
226 * @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])
227 * @return void
228 * @todo Define visibility
229 */
230 public function parse($string, $matchObj = '') {
231 $this->raw = explode(LF, $string);
232 $this->rawP = 0;
233 $pre = '[GLOBAL]';
234 while ($pre) {
235 if ($this->breakPointLN && $pre == '[_BREAK]') {
236 $this->error('Breakpoint at ' . ($this->lineNumberOffset + $this->rawP - 2) . ': Line content was "' . $this->raw[($this->rawP - 2)] . '"', 1);
237 break;
238 }
239 if (strtoupper($pre) == '[GLOBAL]' || strtoupper($pre) == '[END]' || !$this->lastConditionTrue && strtoupper($pre) == '[ELSE]') {
240 $pre = trim($this->parseSub($this->setup));
241 $this->lastConditionTrue = 1;
242 } else {
243 // We're in a specific section. Therefore we log this section
244 if (strtoupper($pre) != '[ELSE]') {
245 $this->sections[md5($pre)] = $pre;
246 }
247 if (is_object($matchObj) && $matchObj->match($pre) || $this->syntaxHighLight) {
248 if (strtoupper($pre) != '[ELSE]') {
249 $this->sectionsMatch[md5($pre)] = $pre;
250 }
251 $pre = trim($this->parseSub($this->setup));
252 $this->lastConditionTrue = 1;
253 } else {
254 $pre = trim($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 = ltrim($this->raw[$this->rawP]);
278 $this->rawP++;
279 if ($line && substr($line, 0, 1) == '[') {
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 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(&$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 && substr($line, 0, 2) == '/*') {
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 (substr($line, 0, 1) == ')') {
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 (strstr($this->multiLineObject, '.')) {
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 && substr($line, 0, 1) == '[') {
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 (substr($line, 0, 1) == '[' && strtoupper(trim($line)) == '[GLOBAL]') {
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 (strcspn($line, '}#/') != 0) {
356 // If not brace-end or comment
357 // Find object name string until we meet an operator
358 $varL = strcspn($line, ' {=<>:(');
359 $objStrName = trim(substr($line, 0, $varL));
360 if ($this->syntaxHighLight) {
361 $this->regHighLight('objstr', $lineP, strlen(substr($line, $varL)));
362 }
363 if (strlen($objStrName)) {
364 $r = array();
365 if ($this->strict && preg_match('/[^[:alnum:]_\\\\\\.-]/i', $objStrName, $r)) {
366 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_-\\."');
367 } else {
368 $line = ltrim(substr($line, $varL));
369 if ($this->syntaxHighLight) {
370 $this->regHighLight('objstr_postspace', $lineP, strlen($line));
371 if (strlen($line) > 0) {
372 $this->regHighLight('operator', $lineP, strlen($line) - 1);
373 $this->regHighLight('operator_postspace', $lineP, strlen(ltrim(substr($line, 1))));
374 }
375 }
376 // Checking for special TSparser properties (to change TS values at parsetime)
377 $match = array();
378 if (preg_match('/^:=([^\\(]+)\\((.+)\\).*/', $line, $match)) {
379 $tsFunc = trim($match[1]);
380 $tsFuncArg = $match[2];
381 list($currentValue) = $this->getVal($objStrName, $setup);
382 $tsFuncArg = str_replace(array('\\\\', '\\n', '\\t'), array('\\', LF, TAB), $tsFuncArg);
383 switch ($tsFunc) {
384 case 'prependString':
385 $newValue = $tsFuncArg . $currentValue;
386 break;
387 case 'appendString':
388 $newValue = $currentValue . $tsFuncArg;
389 break;
390 case 'removeString':
391 $newValue = str_replace($tsFuncArg, '', $currentValue);
392 break;
393 case 'replaceString':
394 list($fromStr, $toStr) = explode('|', $tsFuncArg, 2);
395 $newValue = str_replace($fromStr, $toStr, $currentValue);
396 break;
397 case 'addToList':
398 $newValue = (strcmp('', $currentValue) ? $currentValue . ',' : '') . trim($tsFuncArg);
399 break;
400 case 'removeFromList':
401 $existingElements = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $currentValue);
402 $removeElements = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $tsFuncArg);
403 if (count($removeElements)) {
404 $newValue = implode(',', array_diff($existingElements, $removeElements));
405 }
406 break;
407 default:
408 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$tsFunc])) {
409 $hookMethod = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$tsFunc];
410 $params = array('currentValue' => $currentValue, 'functionArgument' => $tsFuncArg);
411 $fakeThis = FALSE;
412 $newValue = \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($hookMethod, $params, $fakeThis);
413 } else {
414 \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog('Missing function definition for ' . $tsFunc . ' on TypoScript line ' . $lineP, 'Core', \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_WARNING);
415 }
416 }
417 if (isset($newValue)) {
418 $line = '= ' . $newValue;
419 }
420 }
421 switch (substr($line, 0, 1)) {
422 case '=':
423 if ($this->syntaxHighLight) {
424 $this->regHighLight('value', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
425 }
426 if (strstr($objStrName, '.')) {
427 $value = array();
428 $value[0] = trim(substr($line, 1));
429 $this->setVal($objStrName, $setup, $value);
430 } else {
431 $setup[$objStrName] = trim(substr($line, 1));
432 if ($this->lastComment && $this->regComments) {
433 // Setting comment..
434 $setup[$objStrName . '..'] .= $this->lastComment;
435 }
436 if ($this->regLinenumbers) {
437 $setup[$objStrName . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
438 }
439 }
440 break;
441 case '{':
442 $this->inBrace++;
443 if (strstr($objStrName, '.')) {
444 $exitSig = $this->rollParseSub($objStrName, $setup);
445 if ($exitSig) {
446 return $exitSig;
447 }
448 } else {
449 if (!isset($setup[($objStrName . '.')])) {
450 $setup[$objStrName . '.'] = array();
451 }
452 $exitSig = $this->parseSub($setup[$objStrName . '.']);
453 if ($exitSig) {
454 return $exitSig;
455 }
456 }
457 break;
458 case '(':
459 $this->multiLineObject = $objStrName;
460 $this->multiLineEnabled = 1;
461 $this->multiLineValue = array();
462 break;
463 case '<':
464 if ($this->syntaxHighLight) {
465 $this->regHighLight('value_copy', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
466 }
467 $theVal = trim(substr($line, 1));
468 if (substr($theVal, 0, 1) == '.') {
469 $res = $this->getVal(substr($theVal, 1), $setup);
470 } else {
471 $res = $this->getVal($theVal, $this->setup);
472 }
473 $this->setVal($objStrName, $setup, unserialize(serialize($res)), 1);
474 // 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.
475 break;
476 case '>':
477 if ($this->syntaxHighLight) {
478 $this->regHighLight('value_unset', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
479 }
480 $this->setVal($objStrName, $setup, 'UNSET');
481 break;
482 default:
483 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not preceded by any operator, =<>({');
484 break;
485 }
486 }
487 $this->lastComment = '';
488 }
489 } elseif (substr($line, 0, 1) == '}') {
490 $this->inBrace--;
491 $this->lastComment = '';
492 if ($this->syntaxHighLight) {
493 $this->regHighLight('operator', $lineP, strlen($line) - 1);
494 }
495 if ($this->inBrace < 0) {
496 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': An end brace is in excess.', 1);
497 $this->inBrace = 0;
498 } else {
499 break;
500 }
501 } else {
502 if ($this->syntaxHighLight) {
503 $this->regHighLight('comment', $lineP);
504 }
505 // Comment. The comments are concatenated in this temporary string:
506 if ($this->regComments) {
507 $this->lastComment .= trim($line) . LF;
508 }
509 }
510 }
511 }
512 // Unset comment
513 if ($this->commentSet) {
514 if ($this->syntaxHighLight) {
515 $this->regHighLight('comment', $lineP);
516 }
517 if (substr($line, 0, 2) == '*/') {
518 $this->commentSet = 0;
519 }
520 }
521 }
522 }
523
524 /**
525 * 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
526 *
527 * @param string $string The object sub-path, eg "thisprop.another_prot
528 * @param array $setup The local setup array from the function calling this function
529 * @return string Returns the exitSignal
530 * @see parseSub()
531 * @todo Define visibility
532 */
533 public function rollParseSub($string, &$setup) {
534 if ((string) $string != '') {
535 $keyLen = strcspn($string, '.');
536 if ($keyLen == strlen($string)) {
537 $key = $string . '.';
538 if (!isset($setup[$key])) {
539 $setup[$key] = array();
540 }
541 $exitSig = $this->parseSub($setup[$key]);
542 if ($exitSig) {
543 return $exitSig;
544 }
545 } else {
546 $key = substr($string, 0, $keyLen) . '.';
547 if (!isset($setup[$key])) {
548 $setup[$key] = array();
549 }
550 $exitSig = $this->rollParseSub(substr($string, $keyLen + 1), $setup[$key]);
551 if ($exitSig) {
552 return $exitSig;
553 }
554 }
555 }
556 }
557
558 /**
559 * Get a value/property pair for an object path in TypoScript, eg. "myobject.myvalue.mysubproperty". Here: Used by the "copy" operator, <
560 *
561 * @param string $string Object path for which to get the value
562 * @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.
563 * @return array An array with keys 0/1 being value/property respectively
564 * @todo Define visibility
565 */
566 public function getVal($string, $setup) {
567 if ((string) $string != '') {
568 $keyLen = strcspn($string, '.');
569 if ($keyLen == strlen($string)) {
570 // Added 6/6/03. Shouldn't hurt
571 $retArr = array();
572 if (isset($setup[$string])) {
573 $retArr[0] = $setup[$string];
574 }
575 if (isset($setup[$string . '.'])) {
576 $retArr[1] = $setup[$string . '.'];
577 }
578 return $retArr;
579 } else {
580 $key = substr($string, 0, $keyLen) . '.';
581 if ($setup[$key]) {
582 return $this->getVal(substr($string, $keyLen + 1), $setup[$key]);
583 }
584 }
585 }
586 }
587
588 /**
589 * Setting a value/property of an object string in the setup array.
590 *
591 * @param string $string The object sub-path, eg "thisprop.another_prot
592 * @param array $setup The local setup array from the function calling this function.
593 * @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)
594 * @param boolean $wipeOut If set, then both value and property is wiped out when a copy is made of another value.
595 * @return void
596 * @todo Define visibility
597 */
598 public function setVal($string, &$setup, $value, $wipeOut = 0) {
599 if ((string) $string != '') {
600 $keyLen = strcspn($string, '.');
601 if ($keyLen == strlen($string)) {
602 if ($value == 'UNSET') {
603 unset($setup[$string]);
604 unset($setup[$string . '.']);
605 if ($this->regLinenumbers) {
606 $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '>';
607 }
608 } else {
609 $lnRegisDone = 0;
610 if ($wipeOut && $this->strict) {
611 unset($setup[$string]);
612 unset($setup[$string . '.']);
613 if ($this->regLinenumbers) {
614 $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '<';
615 $lnRegisDone = 1;
616 }
617 }
618 if (isset($value[0])) {
619 $setup[$string] = $value[0];
620 }
621 if (isset($value[1])) {
622 $setup[$string . '.'] = $value[1];
623 }
624 if ($this->lastComment && $this->regComments) {
625 $setup[$string . '..'] .= $this->lastComment;
626 }
627 if ($this->regLinenumbers && !$lnRegisDone) {
628 $setup[$string . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
629 }
630 }
631 } else {
632 $key = substr($string, 0, $keyLen) . '.';
633 if (!isset($setup[$key])) {
634 $setup[$key] = array();
635 }
636 $this->setVal(substr($string, $keyLen + 1), $setup[$key], $value);
637 }
638 }
639 }
640
641 /**
642 * Stacks errors/messages from the TypoScript parser into an internal array, $this->error
643 * 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.
644 *
645 * @param string $err The error message string
646 * @param integer $num The error severity (in the scale of $GLOBALS['TT']->setTSlogMessage: Approx: 2=warning, 1=info, 0=nothing, 3=fatal.)
647 * @return void
648 * @todo Define visibility
649 */
650 public function error($err, $num = 2) {
651 if (is_object($GLOBALS['TT'])) {
652 $GLOBALS['TT']->setTSlogMessage($err, $num);
653 }
654 $this->errors[] = array($err, $num, $this->rawP - 1, $this->lineNumberOffset);
655 }
656
657 /**
658 * Checks the input string (un-parsed TypoScript) for include-commands ("<INCLUDE_TYPOSCRIPT: ....")
659 * Use: t3lib_TSparser::checkIncludeLines()
660 *
661 * @param string $string Unparsed TypoScript
662 * @param integer $cycle_counter Counter for detecting endless loops
663 * @param boolean $returnFiles When set an array containing the resulting typoscript and all included files will get returned
664 * @return string Complete TypoScript with includes added.
665 * @static
666 */
667 static public function checkIncludeLines($string, $cycle_counter = 1, $returnFiles = FALSE) {
668 $includedFiles = array();
669 if ($cycle_counter > 100) {
670 \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_WARNING);
671 if ($returnFiles) {
672 return array(
673 'typoscript' => '',
674 'files' => $includedFiles
675 );
676 }
677 return '
678 ###
679 ### ERROR: Recursion!
680 ###
681 ';
682 }
683 $splitStr = '<INCLUDE_TYPOSCRIPT:';
684 if (strstr($string, $splitStr)) {
685 $newString = '';
686 // Adds line break char before/after
687 $allParts = explode($splitStr, LF . $string . LF);
688 foreach ($allParts as $c => $v) {
689 // First goes through
690 if (!$c) {
691 $newString .= $v;
692 } elseif (preg_match('/\\r?\\n\\s*$/', $allParts[$c - 1])) {
693 $subparts = explode('>', $v, 2);
694 // There must be a line-break char after
695 if (preg_match('/^\\s*\\r?\\n/', $subparts[1])) {
696 // SO, the include was positively recognized:
697 $newString .= '### ' . $splitStr . $subparts[0] . '> BEGIN:' . LF;
698 $params = \TYPO3\CMS\Core\Utility\GeneralUtility::get_tag_attributes($subparts[0]);
699 if ($params['source']) {
700 $sourceParts = explode(':', $params['source'], 2);
701 switch (strtolower(trim($sourceParts[0]))) {
702 case 'file':
703 $filename = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName(trim($sourceParts[1]));
704 // Must exist and must not contain '..' and must be relative
705 if (strcmp($filename, '')) {
706 // Check for allowed files
707 if (\TYPO3\CMS\Core\Utility\GeneralUtility::verifyFilenameAgainstDenyPattern($filename)) {
708 if (@is_file($filename)) {
709 // Check for includes in included text
710 $includedFiles[] = $filename;
711 $included_text = self::checkIncludeLines(\TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($filename), $cycle_counter + 1, $returnFiles);
712 // If the method also has to return all included files, merge currently included
713 // files with files included by recursively calling itself
714 if ($returnFiles && is_array($included_text)) {
715 $includedFiles = array_merge($includedFiles, $included_text['files']);
716 $included_text = $included_text['typoscript'];
717 }
718 $newString .= $included_text . LF;
719 } else {
720 $newString .= '
721 ###
722 ### ERROR: File "' . $filename . '" was not was not found.
723 ###
724
725 ';
726 \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog('File "' . $filename . '" was not found.', 'Core', \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_WARNING);
727 }
728 } else {
729 $newString .= '
730 ###
731 ### ERROR: File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern
732 ###
733
734 ';
735 \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog('File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern', 'Core', \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_WARNING);
736 }
737 }
738 break;
739 }
740 }
741 $newString .= '### ' . $splitStr . $subparts[0] . '> END:' . LF;
742 $newString .= $subparts[1];
743 } else {
744 $newString .= $splitStr . $v;
745 }
746 } else {
747 $newString .= $splitStr . $v;
748 }
749 }
750 // Not the first/last linebreak char.
751 $string = substr($newString, 1, -1);
752 }
753 // When all included files should get returned, simply return an compound array containing
754 // the TypoScript with all "includes" processed and the files which got included
755 if ($returnFiles) {
756 return array(
757 'typoscript' => $string,
758 'files' => $includedFiles
759 );
760 }
761 return $string;
762 }
763
764 /**
765 * Parses the string in each value of the input array for include-commands
766 *
767 * @param array $array Array with TypoScript in each value
768 * @return array Same array but where the values has been parsed for include-commands
769 */
770 static public function checkIncludeLines_array($array) {
771 foreach ($array as $k => $v) {
772 $array[$k] = self::checkIncludeLines($array[$k]);
773 }
774 return $array;
775 }
776
777 /**
778 * Search for commented INCLUDE_TYPOSCRIPT statements
779 * and save the content between the BEGIN and the END line to the specified file
780 *
781 * @param string $string Template content
782 * @param integer $cycle_counter Counter for detecting endless loops
783 * @param array $extractedFileNames
784 * @return string Template content with uncommented include statements
785 */
786 static public function extractIncludes($string, $cycle_counter = 1, $extractedFileNames = array()) {
787 if ($cycle_counter > 10) {
788 \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_WARNING);
789 return '
790 ###
791 ### ERROR: Recursion!
792 ###
793 ';
794 }
795 $fileContent = array();
796 $restContent = array();
797 $fileName = NULL;
798 $inIncludePart = FALSE;
799 $lines = explode('
800 ', $string);
801 $skipNextLineIfEmpty = FALSE;
802 $openingCommentedIncludeStatement = NULL;
803 foreach ($lines as $line) {
804 // t3lib_TSparser::checkIncludeLines inserts an additional empty line, remove this again
805 if ($skipNextLineIfEmpty) {
806 if (trim($line) == '') {
807 continue;
808 }
809 $skipNextLineIfEmpty = FALSE;
810 }
811 // Outside commented include statements
812 if (!$inIncludePart) {
813 // Search for beginning commented include statements
814 $matches = array();
815 if (preg_match('/###\\s*<INCLUDE_TYPOSCRIPT:\\s*source\\s*=\\s*"\\s*FILE\\s*:\\s*(.*)\\s*">\\s*BEGIN/i', $line, $matches)) {
816 // Save this line in case there is no ending tag
817 $openingCommentedIncludeStatement = trim($line);
818 $openingCommentedIncludeStatement = trim(preg_replace('/### Warning: .*###/', '', $openingCommentedIncludeStatement));
819 // Found a commented include statement
820 $fileName = trim($matches[1]);
821 $inIncludePart = TRUE;
822 $expectedEndTag = '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $fileName . '"> END';
823 // Strip all whitespace characters to make comparision safer
824 $expectedEndTag = strtolower(preg_replace('/\\s/', '', $expectedEndTag));
825 } else {
826 // If this is not a beginning commented include statement this line goes into the rest content
827 $restContent[] = $line;
828 }
829 } else {
830 // Inside commented include statements
831 // Search for the matching ending commented include statement
832 $strippedLine = strtolower(preg_replace('/\\s/', '', $line));
833 if (strpos($strippedLine, $expectedEndTag) !== FALSE) {
834 // Found the matching ending include statement
835 $fileContentString = implode('
836 ', $fileContent);
837 // Write the content to the file
838 $realFileName = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($fileName);
839 // Some file checks
840 if (empty($realFileName)) {
841 throw new \UnexpectedValueException(sprintf('"%s" is not a valid file location.', $fileName), 1294586441);
842 }
843 if (!is_writable($realFileName)) {
844 throw new \RuntimeException(sprintf('"%s" is not writable.', $fileName), 1294586442);
845 }
846 if (in_array($realFileName, $extractedFileNames)) {
847 throw new \RuntimeException(sprintf('Recursive/multiple inclusion of file "%s"', $realFileName), 1294586443);
848 }
849 $extractedFileNames[] = $realFileName;
850 // Recursive call to detected nested commented include statements
851 $fileContentString = self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames);
852 if (!\TYPO3\CMS\Core\Utility\GeneralUtility::writeFile($realFileName, $fileContentString)) {
853 throw new \RuntimeException(sprintf('Could not write file "%s"', $realFileName), 1294586444);
854 }
855 // Insert reference to the file in the rest content
856 $restContent[] = '<INCLUDE_TYPOSCRIPT: source="FILE: ' . $fileName . '">';
857 // Reset variables (preparing for the next commented include statement)
858 $fileContent = array();
859 $fileName = NULL;
860 $inIncludePart = FALSE;
861 $openingCommentedIncludeStatement = NULL;
862 // t3lib_TSparser::checkIncludeLines inserts an additional empty line, remove this again
863 $skipNextLineIfEmpty = TRUE;
864 } else {
865 // If this is not a ending commented include statement this line goes into the file content
866 $fileContent[] = $line;
867 }
868 }
869 }
870 // If we're still inside commented include statements copy the lines back to the rest content
871 if ($inIncludePart) {
872 $restContent[] = $openingCommentedIncludeStatement . ' ### Warning: Corresponding end line missing! ###';
873 $restContent = array_merge($restContent, $fileContent);
874 }
875 $restContentString = implode('
876 ', $restContent);
877 return $restContentString;
878 }
879
880 /**
881 * Processes the string in each value of the input array with extractIncludes
882 *
883 * @param array $array Array with TypoScript in each value
884 * @return array Same array but where the values has been processed with extractIncludes
885 */
886 static public function extractIncludes_array($array) {
887 foreach ($array as $k => $v) {
888 $array[$k] = self::extractIncludes($array[$k]);
889 }
890 return $array;
891 }
892
893 /**********************************
894 *
895 * Syntax highlighting
896 *
897 *********************************/
898 /**
899 * Syntax highlight a TypoScript text
900 * Will parse the content. Remember, the internal setup array may contain invalid parsed content since conditions are ignored!
901 *
902 * @param string $string The TypoScript text
903 * @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.
904 * @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.
905 * @return string HTML code for the syntax highlighted string
906 * @todo Define visibility
907 */
908 public function doSyntaxHighlight($string, $lineNum = '', $highlightBlockMode = 0) {
909 $this->syntaxHighLight = 1;
910 $this->highLightData = array();
911 $this->error = array();
912 // 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.
913 $string = str_replace(CR, '', $string);
914 $this->parse($string);
915 return $this->syntaxHighlight_print($lineNum, $highlightBlockMode);
916 }
917
918 /**
919 * Registers a part of a TypoScript line for syntax highlighting.
920 *
921 * @param string $code Key from the internal array $this->highLightStyles
922 * @param integer $pointer Pointer to the line in $this->raw which this is about
923 * @param integer $strlen The number of chars LEFT on this line before the end is reached.
924 * @return void
925 * @access private
926 * @see parse()
927 * @todo Define visibility
928 */
929 public function regHighLight($code, $pointer, $strlen = -1) {
930 if ($strlen == -1) {
931 $this->highLightData[$pointer] = array(array($code, 0));
932 } else {
933 $this->highLightData[$pointer][] = array($code, $strlen);
934 }
935 $this->highLightData_bracelevel[$pointer] = $this->inBrace;
936 }
937
938 /**
939 * Formatting the TypoScript code in $this->raw based on the data collected by $this->regHighLight in $this->highLightData
940 *
941 * @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.
942 * @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.
943 * @return string HTML content
944 * @access private
945 * @see doSyntaxHighlight()
946 * @todo Define visibility
947 */
948 public function syntaxHighlight_print($lineNumDat, $highlightBlockMode) {
949 // Registers all error messages in relation to their linenumber
950 $errA = array();
951 foreach ($this->errors as $err) {
952 $errA[$err[2]][] = $err[0];
953 }
954 // Generates the syntax highlighted output:
955 $lines = array();
956 foreach ($this->raw as $rawP => $value) {
957 $start = 0;
958 $strlen = strlen($value);
959 $lineC = '';
960 if (is_array($this->highLightData[$rawP])) {
961 foreach ($this->highLightData[$rawP] as $set) {
962 $len = $strlen - $start - $set[1];
963 if ($len > 0) {
964 $part = substr($value, $start, $len);
965 $start += $len;
966 $st = $this->highLightStyles[isset($this->highLightStyles[$set[0]]) ? $set[0] : 'default'];
967 if (!$highlightBlockMode || $set[0] != 'prespace') {
968 $lineC .= $st[0] . htmlspecialchars($part) . $st[1];
969 }
970 } elseif ($len < 0) {
971 debug(array($len, $value, $rawP));
972 }
973 }
974 } else {
975 debug(array($value));
976 }
977 if (strlen(substr($value, $start))) {
978 $lineC .= $this->highLightStyles['ignored'][0] . htmlspecialchars(substr($value, $start)) . $this->highLightStyles['ignored'][1];
979 }
980 if ($errA[$rawP]) {
981 $lineC .= $this->highLightStyles['error'][0] . '<strong> - ERROR:</strong> ' . htmlspecialchars(implode(';', $errA[$rawP])) . $this->highLightStyles['error'][1];
982 }
983 if ($highlightBlockMode && $this->highLightData_bracelevel[$rawP]) {
984 $lineC = str_pad('', $this->highLightData_bracelevel[$rawP] * 2, ' ', STR_PAD_LEFT) . '<span style="' . $this->highLightBlockStyles . ($this->highLightBlockStyles_basecolor ? 'background-color: ' . \TYPO3\CMS\Core\Utility\GeneralUtility::modifyHTMLColorAll($this->highLightBlockStyles_basecolor, -$this->highLightData_bracelevel[$rawP] * 16) : '') . '">' . (strcmp($lineC, '') ? $lineC : '&nbsp;') . '</span>';
985 }
986 if (is_array($lineNumDat)) {
987 $lineNum = $rawP + $lineNumDat[0];
988 if ($this->parentObject instanceof \TYPO3\CMS\Core\TypoScript\ExtendedTemplateService) {
989 $lineNum = $this->parentObject->ext_lnBreakPointWrap($lineNum, $lineNum);
990 }
991 $lineC = $this->highLightStyles['linenum'][0] . str_pad($lineNum, 4, ' ', STR_PAD_LEFT) . ':' . $this->highLightStyles['linenum'][1] . ' ' . $lineC;
992 }
993 $lines[] = $lineC;
994 }
995 return '<pre class="ts-hl">' . implode(LF, $lines) . '</pre>';
996 }
997
998 }
999
1000
1001 ?>