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