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