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