Fixed bug #17133: Pagetree - qtip can be used to execute custom javascript (XSS)...
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_tsparser.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Contains the TypoScript parser class
29 *
30 * $Id$
31 * Revised for TYPO3 3.6 July/2003 by Kasper Skårhøj
32 *
33 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
34 */
35 /**
36 * [CLASS/FUNCTION INDEX of SCRIPT]
37 *
38 *
39 *
40 * 80: class t3lib_TSparser
41 * 133: function parse($string,$matchObj='')
42 * 169: function nextDivider()
43 * 185: function parseSub(&$setup)
44 * 389: function rollParseSub($string,&$setup)
45 * 413: function getVal($string,$setup)
46 * 439: function setVal($string,&$setup,$value,$wipeOut=0)
47 * 485: function error($err,$num=2)
48 * 497: function checkIncludeLines($string, $cycle_counter=1, $returnFiles=false)
49 * 541: function checkIncludeLines_array($array)
50 *
51 * SECTION: Syntax highlighting
52 * 584: function doSyntaxHighlight($string,$lineNum='',$highlightBlockMode=0)
53 * 605: function regHighLight($code,$pointer,$strlen=-1)
54 * 623: function syntaxHighlight_print($lineNumDat,$highlightBlockMode)
55 *
56 * TOTAL FUNCTIONS: 12
57 * (This index is automatically created/updated by the extension "extdeveval")
58 *
59 */
60
61
62 /**
63 * The TypoScript parser
64 *
65 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
66 * @package TYPO3
67 * @subpackage t3lib
68 * @see t3lib_tstemplate, t3lib_matchcondition, t3lib_BEfunc::getPagesTSconfig(), t3lib_userAuthGroup::fetchGroupData(), t3lib_TStemplate::generateConfig()
69 */
70 class t3lib_TSparser {
71 var $strict = 1; // If set, then key names cannot contain characters other than [:alnum:]_\.-
72
73 // Internal
74 var $setup = array(); // TypoScript hierarchy being build during parsing.
75 var $raw; // raw data, the input string exploded by LF
76 var $rawP; // pointer to entry in raw data array
77 var $lastComment = ''; // Holding the value of the last comment
78 var $commentSet = 0; // Internally set, used as internal flag to create a multi-line comment (one of those like /*... */)
79 var $multiLineEnabled = 0; // Internally set, when multiline value is accumulated
80 var $multiLineObject = ''; // Internally set, when multiline value is accumulated
81 var $multiLineValue = array(); // Internally set, when multiline value is accumulated
82 var $inBrace = 0; // Internally set, when in brace. Counter.
83 var $lastConditionTrue = 1; // 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.
84 var $sections = array(); // Tracking all conditions found
85 var $sectionsMatch = array(); // Tracking all matching conditions found
86 var $syntaxHighLight = 0; // If set, then syntax highlight mode is on; Call the function syntaxHighlight() to use this function
87 var $highLightData = array(); // Syntax highlight data is accumulated in this array. Used by syntaxHighlight_print() to construct the output.
88 var $highLightData_bracelevel = array(); // Syntax highlight data keeping track of the curly brace level for each line
89
90 // Debugging, analysis:
91 var $regComments = 0; // DO NOT register the comments. This is default for the ordinary sitetemplate!
92 var $regLinenumbers = 0; // DO NOT register the linenumbers. This is default for the ordinary sitetemplate!
93 var $errors = array(); // Error accumulation array.
94 var $lineNumberOffset = 0; // Used for the error messages line number reporting. Set externally.
95 var $breakPointLN = 0; // Line for break point.
96 var $highLightStyles = array(
97 'prespace' => array('<span class="ts-prespace">', '</span>'), // Space before any content on a line
98 'objstr_postspace' => array('<span class="ts-objstr_postspace">', '</span>'), // Space after the object string on a line
99 'operator_postspace' => array('<span class="ts-operator_postspace">', '</span>'), // Space after the operator on a line
100 'operator' => array('<span class="ts-operator">', '</span>'), // The operator char
101 'value' => array('<span class="ts-value">', '</span>'), // The value of a line
102 'objstr' => array('<span class="ts-objstr">', '</span>'), // The object string of a line
103 'value_copy' => array('<span class="ts-value_copy">', '</span>'), // The value when the copy syntax (<) is used; that means the object reference
104 'value_unset' => array('<span class="ts-value_unset">', '</span>'), // The value when an object is unset. Should not exist.
105 'ignored' => array('<span class="ts-ignored">', '</span>'), // The "rest" of a line which will be ignored.
106 'default' => array('<span class="ts-default">', '</span>'), // The default style if none other is applied.
107 'comment' => array('<span class="ts-comment">', '</span>'), // Comment lines
108 'condition' => array('<span class="ts-condition">', '</span>'), // Conditions
109 'error' => array('<span class="ts-error">', '</span>'), // Error messages
110 'linenum' => array('<span class="ts-linenum">', '</span>'), // Line numbers
111 );
112 var $highLightBlockStyles = ''; // Additional attributes for the <span> tags for a blockmode line
113 var $highLightBlockStyles_basecolor = '#cccccc'; // The hex-HTML color for the blockmode
114
115 public $parentObject; //Instance of parentObject, used by t3lib_tsparser_ext
116
117 /**
118 * Start parsing the input TypoScript text piece. The result is stored in $this->setup
119 *
120 * @param string The TypoScript text
121 * @param object If is object (instance of t3lib_matchcondition), then this is used to match conditions found in the TypoScript code. If matchObj not specified, then no conditions will work! (Except [GLOBAL])
122 * @return void
123 */
124 function parse($string, $matchObj = '') {
125 $this->raw = explode(LF, $string);
126 $this->rawP = 0;
127 $pre = '[GLOBAL]';
128 while ($pre) {
129 if ($this->breakPointLN && $pre == '[_BREAK]') {
130 $this->error('Breakpoint at ' . ($this->lineNumberOffset + $this->rawP - 2) . ': Line content was "' . $this->raw[$this->rawP - 2] . '"', 1);
131 break;
132 }
133
134 if (strtoupper($pre) == '[GLOBAL]' || strtoupper($pre) == '[END]' || (!$this->lastConditionTrue && strtoupper($pre) == '[ELSE]')) {
135 $pre = trim($this->parseSub($this->setup));
136 $this->lastConditionTrue = 1;
137 } else {
138 if (strtoupper($pre) != '[ELSE]') {
139 $this->sections[md5($pre)] = $pre;
140 } // we're in a specific section. Therefore we log this section
141 if ((is_object($matchObj) && $matchObj->match($pre)) || $this->syntaxHighLight) {
142 if (strtoupper($pre) != '[ELSE]') {
143 $this->sectionsMatch[md5($pre)] = $pre;
144 }
145 $pre = trim($this->parseSub($this->setup));
146 $this->lastConditionTrue = 1;
147 } else {
148 $pre = trim($this->nextDivider());
149 $this->lastConditionTrue = 0;
150 }
151 }
152 }
153 if ($this->inBrace) {
154 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': The script is short of ' . $this->inBrace . ' end brace(s)', 1);
155 }
156 if ($this->multiLineEnabled) {
157 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': A multiline value section is not ended with a parenthesis!', 1);
158 }
159 $this->lineNumberOffset += count($this->raw) + 1;
160 }
161
162 /**
163 * 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.
164 *
165 * @return string The condition value
166 * @see parse()
167 */
168 function nextDivider() {
169 while (isset($this->raw[$this->rawP])) {
170 $line = ltrim($this->raw[$this->rawP]);
171 $this->rawP++;
172 if ($line && substr($line, 0, 1) == '[') {
173 return $line;
174 }
175 }
176 }
177
178 /**
179 * Parsing the $this->raw TypoScript lines from pointer, $this->rawP
180 *
181 * @param array Reference to the setup array in which to accumulate the values.
182 * @return string Returns the string of the condition found, the exit signal or possible nothing (if it completed parsing with no interruptions)
183 */
184 function parseSub(&$setup) {
185 global $TYPO3_CONF_VARS;
186
187 while (isset($this->raw[$this->rawP])) {
188 $line = ltrim($this->raw[$this->rawP]);
189 $lineP = $this->rawP;
190 $this->rawP++;
191 if ($this->syntaxHighLight) {
192 $this->regHighLight("prespace", $lineP, strlen($line));
193 }
194
195 // Breakpoint?
196 if ($this->breakPointLN && ($this->lineNumberOffset + $this->rawP - 1) == ($this->breakPointLN + 1)) { // by adding 1 we get that line processed
197 return '[_BREAK]';
198 }
199
200 // Set comment flag?
201 if (!$this->multiLineEnabled && substr($line, 0, 2) == '/*') {
202 $this->commentSet = 1;
203 }
204
205 if (!$this->commentSet && ($line || $this->multiLineEnabled)) { // If $this->multiLineEnabled we will go and get the line values here because we know, the first if() will be true.
206 if ($this->multiLineEnabled) { // If multiline is enabled. Escape by ')'
207 if (substr($line, 0, 1) == ')') { // Multiline ends...
208 if ($this->syntaxHighLight) {
209 $this->regHighLight("operator", $lineP, strlen($line) - 1);
210 }
211 $this->multiLineEnabled = 0; // Disable multiline
212 $theValue = implode($this->multiLineValue, LF);
213 if (strstr($this->multiLineObject, '.')) {
214 $this->setVal($this->multiLineObject, $setup, array($theValue)); // Set the value deeper.
215 } else {
216 $setup[$this->multiLineObject] = $theValue; // Set value regularly
217 if ($this->lastComment && $this->regComments) {
218 $setup[$this->multiLineObject . '..'] .= $this->lastComment;
219 }
220 if ($this->regLinenumbers) {
221 $setup[$this->multiLineObject . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1);
222 }
223 }
224 } else {
225 if ($this->syntaxHighLight) {
226 $this->regHighLight("value", $lineP);
227 }
228 $this->multiLineValue[] = $this->raw[($this->rawP - 1)];
229 }
230 } elseif ($this->inBrace == 0 && substr($line, 0, 1) == '[') { // Beginning of condition (only on level zero compared to brace-levels
231 if ($this->syntaxHighLight) {
232 $this->regHighLight("condition", $lineP);
233 }
234 return $line;
235 } else {
236 if (substr($line, 0, 1) == '[' && strtoupper(trim($line)) == '[GLOBAL]') { // Return if GLOBAL condition is set - no matter what.
237 if ($this->syntaxHighLight) {
238 $this->regHighLight("condition", $lineP);
239 }
240 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': On return to [GLOBAL] scope, the script was short of ' . $this->inBrace . ' end brace(s)', 1);
241 $this->inBrace = 0;
242 return $line;
243 } elseif (strcspn($line, '}#/') != 0) { // If not brace-end or comment
244 $varL = strcspn($line, ' {=<>:('); // Find object name string until we meet an operator
245 $objStrName = trim(substr($line, 0, $varL));
246 if ($this->syntaxHighLight) {
247 $this->regHighLight("objstr", $lineP, strlen(substr($line, $varL)));
248 }
249 if (strlen($objStrName)) {
250 $r = array();
251 if ($this->strict && preg_match('/[^[:alnum:]_\.-]/i', $objStrName, $r)) {
252 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_-."');
253 } else {
254 $line = ltrim(substr($line, $varL));
255 if ($this->syntaxHighLight) {
256 $this->regHighLight("objstr_postspace", $lineP, strlen($line));
257 if (strlen($line) > 0) {
258 $this->regHighLight("operator", $lineP, strlen($line) - 1);
259 $this->regHighLight("operator_postspace", $lineP, strlen(ltrim(substr($line, 1))));
260 }
261 }
262
263 // Checking for special TSparser properties (to change TS values at parsetime)
264 $match = array();
265 if (preg_match('/^:=([^\(]+)\((.+)\).*/', $line, $match)) {
266 $tsFunc = trim($match[1]);
267 $tsFuncArg = $match[2];
268 list ($currentValue) = $this->getVal($objStrName, $setup);
269
270 $tsFuncArg = str_replace(
271 array('\\\\', '\n', '\t'),
272 array('\\', LF, TAB),
273 $tsFuncArg
274 );
275
276 switch ($tsFunc) {
277 case 'prependString':
278 $newValue = $tsFuncArg . $currentValue;
279 break;
280 case 'appendString':
281 $newValue = $currentValue . $tsFuncArg;
282 break;
283 case 'removeString':
284 $newValue = str_replace($tsFuncArg, '', $currentValue);
285 break;
286 case 'replaceString':
287 list($fromStr, $toStr) = explode('|', $tsFuncArg, 2);
288 $newValue = str_replace($fromStr, $toStr, $currentValue);
289 break;
290 case 'addToList':
291 $newValue = (strcmp('', $currentValue) ? $currentValue . ',' : '') . trim($tsFuncArg);
292 break;
293 case 'removeFromList':
294 $existingElements = t3lib_div::trimExplode(',', $currentValue);
295 $removeElements = t3lib_div::trimExplode(',', $tsFuncArg);
296 if (count($removeElements)) {
297 $newValue = implode(',', array_diff($existingElements, $removeElements));
298 }
299 break;
300 default:
301 if (isset($TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$tsFunc])) {
302 $hookMethod = $TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$tsFunc];
303 $params = array('currentValue' => $currentValue, 'functionArgument' => $tsFuncArg);
304 $fakeThis = FALSE;
305 $newValue = t3lib_div::callUserFunction($hookMethod, $params, $fakeThis);
306 } else {
307 t3lib_div::sysLog('Missing function definition for ' . $tsFunc . ' on TypoScript line ' . $lineP, 'Core', 2);
308 }
309 }
310
311 if (isset($newValue)) {
312 $line = '= ' . $newValue;
313 }
314 }
315
316 switch (substr($line, 0, 1)) {
317 case '=':
318 if ($this->syntaxHighLight) {
319 $this->regHighLight('value', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
320 }
321
322 if (strstr($objStrName, '.')) {
323 $value = array();
324 $value[0] = trim(substr($line, 1));
325 $this->setVal($objStrName, $setup, $value);
326 } else {
327 $setup[$objStrName] = trim(substr($line, 1));
328 if ($this->lastComment && $this->regComments) { // Setting comment..
329 $setup[$objStrName . '..'] .= $this->lastComment;
330 }
331 if ($this->regLinenumbers) {
332 $setup[$objStrName . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1);
333 }
334 }
335 break;
336 case '{':
337 $this->inBrace++;
338 if (strstr($objStrName, '.')) {
339 $exitSig = $this->rollParseSub($objStrName, $setup);
340 if ($exitSig) {
341 return $exitSig;
342 }
343 } else {
344 if (!isset($setup[$objStrName . '.'])) {
345 $setup[$objStrName . '.'] = array();
346 }
347 $exitSig = $this->parseSub($setup[$objStrName . '.']);
348 if ($exitSig) {
349 return $exitSig;
350 }
351 }
352 break;
353 case '(':
354 $this->multiLineObject = $objStrName;
355 $this->multiLineEnabled = 1;
356 $this->multiLineValue = array();
357 break;
358 case '<':
359 if ($this->syntaxHighLight) {
360 $this->regHighLight("value_copy", $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
361 }
362 $theVal = trim(substr($line, 1));
363 if (substr($theVal, 0, 1) == '.') {
364 $res = $this->getVal(substr($theVal, 1), $setup);
365 } else {
366 $res = $this->getVal($theVal, $this->setup);
367 }
368 $this->setVal($objStrName, $setup, unserialize(serialize($res)), 1); // 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.
369 break;
370 case '>':
371 if ($this->syntaxHighLight) {
372 $this->regHighLight("value_unset", $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
373 }
374 $this->setVal($objStrName, $setup, 'UNSET');
375 break;
376 default:
377 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not preceded by any operator, =<>({');
378 break;
379 }
380 }
381 $this->lastComment = '';
382 }
383 } elseif (substr($line, 0, 1) == '}') {
384 $this->inBrace--;
385 $this->lastComment = '';
386 if ($this->syntaxHighLight) {
387 $this->regHighLight("operator", $lineP, strlen($line) - 1);
388 }
389 if ($this->inBrace < 0) {
390 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': An end brace is in excess.', 1);
391 $this->inBrace = 0;
392 } else {
393 break;
394 }
395 } else {
396 if ($this->syntaxHighLight) {
397 $this->regHighLight("comment", $lineP);
398 }
399
400 // Comment. The comments are concatenated in this temporary string:
401 if ($this->regComments) {
402 $this->lastComment .= trim($line) . LF;
403 }
404 }
405 }
406 }
407
408 // Unset comment
409 if ($this->commentSet) {
410 if ($this->syntaxHighLight) {
411 $this->regHighLight("comment", $lineP);
412 }
413 if (substr($line, 0, 2) == '*/') {
414 $this->commentSet = 0;
415 }
416 }
417 }
418 }
419
420 /**
421 * 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
422 *
423 * @param string The object sub-path, eg "thisprop.another_prot"
424 * @param array The local setup array from the function calling this function
425 * @return string Returns the exitSignal
426 * @see parseSub()
427 */
428 function rollParseSub($string, &$setup) {
429 if ((string) $string != '') {
430 $keyLen = strcspn($string, '.');
431 if ($keyLen == strlen($string)) {
432 $key = $string . '.';
433 if (!isset($setup[$key])) {
434 $setup[$key] = array();
435 }
436 $exitSig = $this->parseSub($setup[$key]);
437 if ($exitSig) {
438 return $exitSig;
439 }
440 } else {
441 $key = substr($string, 0, $keyLen) . '.';
442 if (!isset($setup[$key])) {
443 $setup[$key] = array();
444 }
445 $exitSig = $this->rollParseSub(substr($string, $keyLen + 1), $setup[$key]);
446 if ($exitSig) {
447 return $exitSig;
448 }
449 }
450 }
451 }
452
453 /**
454 * Get a value/property pair for an object path in TypoScript, eg. "myobject.myvalue.mysubproperty". Here: Used by the "copy" operator, <
455 *
456 * @param string Object path for which to get the value
457 * @param array Global setup code if $string points to a global object path. But if string is prefixed with "." then its the local setup array.
458 * @return array An array with keys 0/1 being value/property respectively
459 */
460 function getVal($string, $setup) {
461 if ((string) $string != '') {
462 $keyLen = strcspn($string, '.');
463 if ($keyLen == strlen($string)) {
464 $retArr = array(); // Added 6/6/03. Shouldn't hurt
465 if (isset($setup[$string])) {
466 $retArr[0] = $setup[$string];
467 }
468 if (isset($setup[$string . '.'])) {
469 $retArr[1] = $setup[$string . '.'];
470 }
471 return $retArr;
472 } else {
473 $key = substr($string, 0, $keyLen) . '.';
474 if ($setup[$key]) {
475 return $this->getVal(substr($string, $keyLen + 1), $setup[$key]);
476 }
477 }
478 }
479 }
480
481 /**
482 * Setting a value/property of an object string in the setup array.
483 *
484 * @param string The object sub-path, eg "thisprop.another_prot"
485 * @param array The local setup array from the function calling this function.
486 * @param array 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)
487 * @param boolean If set, then both value and property is wiped out when a copy is made of another value.
488 * @return void
489 */
490 function setVal($string, &$setup, $value, $wipeOut = 0) {
491 if ((string) $string != '') {
492 $keyLen = strcspn($string, '.');
493 if ($keyLen == strlen($string)) {
494 if ($value == 'UNSET') {
495 unset($setup[$string]);
496 unset($setup[$string . '.']);
497 if ($this->regLinenumbers) {
498 $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '>';
499 }
500 } else {
501 $lnRegisDone = 0;
502 if ($wipeOut && $this->strict) {
503 if ((isset($setup[$string]) && !isset($value[0])) || (isset($setup[$string . '.']) && !isset($value[1]))) {
504 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object copied in this line "' . trim($this->raw[($this->rawP - 1)]) . '" would leave either the value or properties untouched in TypoScript Version 1. Please check that this is not a problem for you.', 1);
505 }
506 unset($setup[$string]);
507 unset($setup[$string . '.']);
508 if ($this->regLinenumbers) {
509 $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '<';
510 $lnRegisDone = 1;
511 }
512 }
513 if (isset($value[0])) {
514 $setup[$string] = $value[0];
515 }
516 if (isset($value[1])) {
517 $setup[$string . '.'] = $value[1];
518 }
519 if ($this->lastComment && $this->regComments) {
520 $setup[$string . '..'] .= $this->lastComment;
521 }
522 if ($this->regLinenumbers && !$lnRegisDone) {
523 $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1);
524 }
525 }
526 } else {
527 $key = substr($string, 0, $keyLen) . '.';
528 if (!isset($setup[$key])) {
529 $setup[$key] = array();
530 }
531 $this->setVal(substr($string, $keyLen + 1), $setup[$key], $value);
532 }
533 }
534 }
535
536 /**
537 * Stacks errors/messages from the TypoScript parser into an internal array, $this->error
538 * 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.
539 *
540 * @param string The error message string
541 * @param integer The error severity (in the scale of $GLOBALS['TT']->setTSlogMessage: Approx: 2=warning, 1=info, 0=nothing, 3=fatal.)
542 * @return void
543 */
544 function error($err, $num = 2) {
545 if (is_object($GLOBALS['TT'])) {
546 $GLOBALS['TT']->setTSlogMessage($err, $num);
547 }
548 $this->errors[] = array($err, $num, $this->rawP - 1, $this->lineNumberOffset);
549 }
550
551 /**
552 * Checks the input string (un-parsed TypoScript) for include-commands ("<INCLUDE_TYPOSCRIPT: ....")
553 * Use: t3lib_TSparser::checkIncludeLines()
554 *
555 * @param string Unparsed TypoScript
556 * @param integer Counter for detecting endless loops
557 * @param boolean When set an array containing the resulting typoscript and all included files will get returned
558 * @return string Complete TypoScript with includes added.
559 * @static
560 */
561 function checkIncludeLines($string, $cycle_counter = 1, $returnFiles = FALSE) {
562 $includedFiles = array();
563 if ($cycle_counter > 100) {
564 t3lib_div::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', 2);
565 if ($returnFiles) {
566 return array(
567 'typoscript' => '',
568 'files' => $includedFiles,
569 );
570 }
571 return "\n###\n### ERROR: Recursion!\n###\n";
572 }
573 $splitStr = '<INCLUDE_TYPOSCRIPT:';
574 if (strstr($string, $splitStr)) {
575 $newString = '';
576 $allParts = explode($splitStr, LF . $string . LF); // adds line break char before/after
577 foreach ($allParts as $c => $v) {
578 if (!$c) { // first goes through
579 $newString .= $v;
580 } elseif (preg_match('/\r?\n\s*$/', $allParts[$c - 1])) { // There must be a line-break char before.
581 $subparts = explode('>', $v, 2);
582 if (preg_match('/^\s*\r?\n/', $subparts[1])) { // There must be a line-break char after
583 // SO, the include was positively recognized:
584 $newString .= '### ' . $splitStr . $subparts[0] . '> BEGIN:' . LF;
585 $params = t3lib_div::get_tag_attributes($subparts[0]);
586 if ($params['source']) {
587 $sourceParts = explode(':', $params['source'], 2);
588 switch (strtolower(trim($sourceParts[0]))) {
589 case 'file':
590 $filename = t3lib_div::getFileAbsFileName(trim($sourceParts[1]));
591 if (strcmp($filename, '')) { // Must exist and must not contain '..' and must be relative
592 if (t3lib_div::verifyFilenameAgainstDenyPattern($filename)) { // Check for allowed files
593 if (@is_file($filename) && filesize($filename) < 100000) { // Max. 100 KB include files!
594 // check for includes in included text
595 $includedFiles[] = $filename;
596 $included_text = self::checkIncludeLines(t3lib_div::getUrl($filename), $cycle_counter + 1, $returnFiles);
597 // If the method also has to return all included files, merge currently included
598 // files with files included by recursively calling itself
599 if ($returnFiles && is_array($included_text)) {
600 $includedFiles = array_merge($includedFiles, $included_text['files']);
601 $included_text = $included_text['typoscript'];
602 }
603 $newString .= $included_text . LF;
604 }
605 } else {
606 t3lib_div::sysLog('File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern', 'Core', 2);
607 }
608 }
609 break;
610 }
611 }
612 $newString .= '### ' . $splitStr . $subparts[0] . '> END:' . LF;
613 $newString .= $subparts[1];
614 } else {
615 $newString .= $splitStr . $v;
616 }
617 } else {
618 $newString .= $splitStr . $v;
619 }
620 }
621 $string = substr($newString, 1, -1); // not the first/last linebreak char.
622 }
623 // When all included files should get returned, simply return an compound array containing
624 // the TypoScript with all "includes" processed and the files which got included
625 if ($returnFiles) {
626 return array(
627 'typoscript' => $string,
628 'files' => $includedFiles,
629 );
630 }
631 return $string;
632 }
633
634 /**
635 * Parses the string in each value of the input array for include-commands
636 *
637 * @param array Array with TypoScript in each value
638 * @return array Same array but where the values has been parsed for include-commands
639 */
640 function checkIncludeLines_array($array) {
641 foreach ($array as $k => $v) {
642 $array[$k] = t3lib_TSparser::checkIncludeLines($array[$k]);
643 }
644 return $array;
645 }
646
647 /**
648 * Search for commented INCLUDE_TYPOSCRIPT statements
649 * and save the content between the BEGIN and the END line to the specified file
650 *
651 * @param string template content
652 * @param int Counter for detecting endless loops
653 * @return string template content with uncommented include statements
654 * @author Fabrizio Branca <typo3@fabrizio-branca.de>
655 */
656 function extractIncludes($string, $cycle_counter = 1, $extractedFileNames = array()) {
657
658 if ($cycle_counter > 10) {
659 t3lib_div::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', 2);
660 return "\n###\n### ERROR: Recursion!\n###\n";
661 }
662
663 $fileContent = array();
664 $restContent = array();
665 $fileName = NULL;
666 $inIncludePart = FALSE;
667 $lines = explode("\n", $string);
668 $skipNextLineIfEmpty = FALSE;
669 $openingCommentedIncludeStatement = NULL;
670 foreach ($lines as $line) {
671
672 // t3lib_TSparser::checkIncludeLines inserts an additional empty line, remove this again
673 if ($skipNextLineIfEmpty) {
674 if (trim($line) == '') {
675 continue;
676 }
677 $skipNextLineIfEmpty = FALSE;
678 }
679
680 if (!$inIncludePart) { // outside commented include statements
681
682 // search for beginning commented include statements
683 $matches = array();
684 if (preg_match('/###\s*<INCLUDE_TYPOSCRIPT:\s*source\s*=\s*"\s*FILE\s*:\s*(.*)\s*">\s*BEGIN/i', $line, $matches)) {
685
686 // save this line in case there is no ending tag
687 $openingCommentedIncludeStatement = trim($line);
688 $openingCommentedIncludeStatement = trim(preg_replace('/### Warning: .*###/', '', $openingCommentedIncludeStatement));
689
690 // found a commented include statement
691 $fileName = trim($matches[1]);
692 $inIncludePart = TRUE;
693
694 $expectedEndTag = '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $fileName . '"> END';
695 // strip all whitespace characters to make comparision safer
696 $expectedEndTag = strtolower(preg_replace('/\s/', '', $expectedEndTag));
697 } else {
698 // if this is not a beginning commented include statement this line goes into the rest content
699 $restContent[] = $line;
700 }
701
702 } else { // inside commented include statements
703
704 // search for the matching ending commented include statement
705 $strippedLine = strtolower(preg_replace('/\s/', '', $line));
706 if (strpos($strippedLine, $expectedEndTag) !== FALSE) {
707
708 // found the matching ending include statement
709 $fileContentString = implode("\n", $fileContent);
710
711 // write the content to the file
712 $realFileName = t3lib_div::getFileAbsFileName($fileName);
713
714 // some file checks
715 if (empty($realFileName)) {
716 throw new Exception(sprintf('"%s" is not a valid file location.', $fileName));
717 }
718
719 if (!is_writable($realFileName)) {
720 throw new Exception(sprintf('"%s" is not writable.', $fileName));
721 }
722
723 if (in_array($realFileName, $extractedFileNames)) {
724 throw new Exception(sprintf('Recursive/multiple inclusion of file "%s"', $realFileName));
725 }
726 $extractedFileNames[] = $realFileName;
727
728 // recursive call to detected nested commented include statements
729 $fileContentString = self::extractIncludes($fileContentString, ++$cycle_counter, $extractedFileNames);
730
731 if (!t3lib_div::writeFile($realFileName, $fileContentString)) {
732 throw new Exception(sprintf('Could not write file "%s"', $realFileName));
733 }
734
735 // insert reference to the file in the rest content
736 $restContent[] = "<INCLUDE_TYPOSCRIPT: source=\"FILE:$fileName\">";
737
738 // reset variables (preparing for the next commented include statement)
739 $fileContent = array();
740 $fileName = NULL;
741 $inIncludePart = FALSE;
742 $openingCommentedIncludeStatement = NULL;
743 // t3lib_TSparser::checkIncludeLines inserts an additional empty line, remove this again
744 $skipNextLineIfEmpty = TRUE;
745 } else {
746 // if this is not a ending commented include statement this line goes into the file content
747 $fileContent[] = $line;
748 }
749
750 }
751
752 }
753
754 // if we're still inside commented include statements copy the lines back to the rest content
755 if ($inIncludePart) {
756 $restContent[] = $openingCommentedIncludeStatement . ' ### Warning: Corresponding end line missing! ###';
757 $restContent = array_merge($restContent, $fileContent);
758 }
759
760 $restContentString = implode("\n", $restContent);
761 return $restContentString;
762 }
763
764 /**
765 * Processes the string in each value of the input array with extractIncludes
766 *
767 * @param array Array with TypoScript in each value
768 * @return array Same array but where the values has been processed with extractIncludes
769 * @author Fabrizio Branca <typo3@fabrizio-branca.de>
770 */
771 function extractIncludes_array($array) {
772 foreach ($array as $k => $v) {
773 $array[$k] = t3lib_TSparser::extractIncludes($array[$k]);
774 }
775 return $array;
776 }
777
778
779 /**********************************
780 *
781 * Syntax highlighting
782 *
783 *********************************/
784
785 /**
786 * Syntax highlight a TypoScript text
787 * Will parse the content. Remember, the internal setup array may contain invalid parsed content since conditions are ignored!
788 *
789 * @param string The TypoScript text
790 * @param mixed If blank, linenumbers are NOT printed. If array then the first key is the linenumber offset to add to the internal counter.
791 * @param boolean 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.
792 * @return string HTML code for the syntax highlighted string
793 */
794 function doSyntaxHighlight($string, $lineNum = '', $highlightBlockMode = 0) {
795 $this->syntaxHighLight = 1;
796 $this->highLightData = array();
797 $this->error = array();
798 $string = str_replace(CR, '', $string); // 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.
799
800 $this->parse($string);
801
802 return $this->syntaxHighlight_print($lineNum, $highlightBlockMode);
803 }
804
805 /**
806 * Registers a part of a TypoScript line for syntax highlighting.
807 *
808 * @param string Key from the internal array $this->highLightStyles
809 * @param integer Pointer to the line in $this->raw which this is about
810 * @param integer The number of chars LEFT on this line before the end is reached.
811 * @return void
812 * @access private
813 * @see parse()
814 */
815 function regHighLight($code, $pointer, $strlen = -1) {
816 if ($strlen == -1) {
817 $this->highLightData[$pointer] = array(array($code, 0));
818 } else {
819 $this->highLightData[$pointer][] = array($code, $strlen);
820 }
821 $this->highLightData_bracelevel[$pointer] = $this->inBrace;
822 }
823
824 /**
825 * Formatting the TypoScript code in $this->raw based on the data collected by $this->regHighLight in $this->highLightData
826 *
827 * @param mixed If blank, linenumbers are NOT printed. If array then the first key is the linenumber offset to add to the internal counter.
828 * @param boolean 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.
829 * @return string HTML content
830 * @access private
831 * @see doSyntaxHighlight()
832 */
833 function syntaxHighlight_print($lineNumDat, $highlightBlockMode) {
834 // Registers all error messages in relation to their linenumber
835 $errA = array();
836 foreach ($this->errors as $err) {
837 $errA[$err[2]][] = $err[0];
838 }
839 // Generates the syntax highlighted output:
840 $lines = array();
841 foreach ($this->raw as $rawP => $value) {
842 $start = 0;
843 $strlen = strlen($value);
844 $lineC = '';
845
846 if (is_array($this->highLightData[$rawP])) {
847 foreach ($this->highLightData[$rawP] as $set) {
848 $len = $strlen - $start - $set[1];
849 if ($len > 0) {
850 $part = substr($value, $start, $len);
851 $start += $len;
852 $st = $this->highLightStyles[(isset($this->highLightStyles[$set[0]]) ? $set[0] : 'default')];
853 if (!$highlightBlockMode || $set[0] != 'prespace') {
854 $lineC .= $st[0] . htmlspecialchars($part) . $st[1];
855 }
856 } elseif ($len < 0) {
857 debug(array($len, $value, $rawP));
858 }
859 }
860 } else {
861 debug(array($value));
862 }
863
864 if (strlen(substr($value, $start))) {
865 $lineC .= $this->highLightStyles['ignored'][0] . htmlspecialchars(substr($value, $start)) . $this->highLightStyles['ignored'][1];
866 }
867
868 if ($errA[$rawP]) {
869 $lineC .= $this->highLightStyles['error'][0] . '<strong> - ERROR:</strong> ' . htmlspecialchars(implode(';', $errA[$rawP])) . $this->highLightStyles['error'][1];
870 }
871
872 if ($highlightBlockMode && $this->highLightData_bracelevel[$rawP]) {
873 $lineC = str_pad('', $this->highLightData_bracelevel[$rawP] * 2, ' ', STR_PAD_LEFT) . '<span style="' . $this->highLightBlockStyles . ($this->highLightBlockStyles_basecolor ? 'background-color: ' . t3lib_div::modifyHTMLColorAll($this->highLightBlockStyles_basecolor, -$this->highLightData_bracelevel[$rawP] * 16) : '') . '">' . (strcmp($lineC, '') ? $lineC : '&nbsp;') . '</span>';
874 }
875
876 if (is_array($lineNumDat)) {
877 $lineNum = $rawP + $lineNumDat[0];
878 if ($this->parentObject instanceof t3lib_tsparser_ext) {
879 $lineNum = $this->parentObject->ext_lnBreakPointWrap($lineNum, $lineNum);
880 }
881 $lineC = $this->highLightStyles['linenum'][0] . str_pad($lineNum, 4, ' ', STR_PAD_LEFT) . ':' . $this->highLightStyles['linenum'][1] . ' ' . $lineC;
882 }
883
884
885 $lines[] = $lineC;
886 }
887
888 return '<pre class="ts-hl">' . implode(LF, $lines) . '</pre>';
889 }
890 }
891
892
893 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_tsparser.php'])) {
894 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_tsparser.php']);
895 }
896
897 ?>