2 /***************************************************************
5 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
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.
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.
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.
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
28 * Contains the TypoScript parser class
30 * Revised for TYPO3 3.6 July/2003 by Kasper Skårhøj
32 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
35 * [CLASS/FUNCTION INDEX of SCRIPT]
39 * 80: class t3lib_TSparser
40 * 133: function parse($string,$matchObj='')
41 * 169: function nextDivider()
42 * 185: function parseSub(&$setup)
43 * 389: function rollParseSub($string,&$setup)
44 * 413: function getVal($string,$setup)
45 * 439: function setVal($string,&$setup,$value,$wipeOut=0)
46 * 485: function error($err,$num=2)
47 * 497: function checkIncludeLines($string, $cycle_counter=1, $returnFiles=false)
48 * 541: function checkIncludeLines_array($array)
50 * SECTION: Syntax highlighting
51 * 584: function doSyntaxHighlight($string,$lineNum='',$highlightBlockMode=0)
52 * 605: function regHighLight($code,$pointer,$strlen=-1)
53 * 623: function syntaxHighlight_print($lineNumDat,$highlightBlockMode)
56 * (This index is automatically created/updated by the extension "extdeveval")
62 * The TypoScript parser
64 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
67 * @see t3lib_tstemplate, t3lib_BEfunc::getPagesTSconfig(), t3lib_userAuthGroup::fetchGroupData(), t3lib_TStemplate::generateConfig()
69 class t3lib_TSparser
{
70 var $strict = 1; // If set, then key names cannot contain characters other than [:alnum:]_\.-
73 var $setup = array(); // TypoScript hierarchy being build during parsing.
74 var $raw; // raw data, the input string exploded by LF
75 var $rawP; // pointer to entry in raw data array
76 var $lastComment = ''; // Holding the value of the last comment
77 var $commentSet = 0; // Internally set, used as internal flag to create a multi-line comment (one of those like /*... */)
78 var $multiLineEnabled = 0; // Internally set, when multiline value is accumulated
79 var $multiLineObject = ''; // Internally set, when multiline value is accumulated
80 var $multiLineValue = array(); // Internally set, when multiline value is accumulated
81 var $inBrace = 0; // Internally set, when in brace. Counter.
82 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.
83 var $sections = array(); // Tracking all conditions found
84 var $sectionsMatch = array(); // Tracking all matching conditions found
85 var $syntaxHighLight = 0; // If set, then syntax highlight mode is on; Call the function syntaxHighlight() to use this function
86 var $highLightData = array(); // Syntax highlight data is accumulated in this array. Used by syntaxHighlight_print() to construct the output.
87 var $highLightData_bracelevel = array(); // Syntax highlight data keeping track of the curly brace level for each line
89 // Debugging, analysis:
90 var $regComments = 0; // DO NOT register the comments. This is default for the ordinary sitetemplate!
91 var $regLinenumbers = 0; // DO NOT register the linenumbers. This is default for the ordinary sitetemplate!
92 var $errors = array(); // Error accumulation array.
93 var $lineNumberOffset = 0; // Used for the error messages line number reporting. Set externally.
94 var $breakPointLN = 0; // Line for break point.
95 var $highLightStyles = array(
96 'prespace' => array('<span class="ts-prespace">', '</span>'), // Space before any content on a line
97 'objstr_postspace' => array('<span class="ts-objstr_postspace">', '</span>'), // Space after the object string on a line
98 'operator_postspace' => array('<span class="ts-operator_postspace">', '</span>'), // Space after the operator on a line
99 'operator' => array('<span class="ts-operator">', '</span>'), // The operator char
100 'value' => array('<span class="ts-value">', '</span>'), // The value of a line
101 'objstr' => array('<span class="ts-objstr">', '</span>'), // The object string of a line
102 'value_copy' => array('<span class="ts-value_copy">', '</span>'), // The value when the copy syntax (<) is used; that means the object reference
103 'value_unset' => array('<span class="ts-value_unset">', '</span>'), // The value when an object is unset. Should not exist.
104 'ignored' => array('<span class="ts-ignored">', '</span>'), // The "rest" of a line which will be ignored.
105 'default' => array('<span class="ts-default">', '</span>'), // The default style if none other is applied.
106 'comment' => array('<span class="ts-comment">', '</span>'), // Comment lines
107 'condition' => array('<span class="ts-condition">', '</span>'), // Conditions
108 'error' => array('<span class="ts-error">', '</span>'), // Error messages
109 'linenum' => array('<span class="ts-linenum">', '</span>'), // Line numbers
111 var $highLightBlockStyles = ''; // Additional attributes for the <span> tags for a blockmode line
112 var $highLightBlockStyles_basecolor = '#cccccc'; // The hex-HTML color for the blockmode
114 public $parentObject; //Instance of parentObject, used by t3lib_tsparser_ext
117 * Start parsing the input TypoScript text piece. The result is stored in $this->setup
119 * @param string The TypoScript text
120 * @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])
123 function parse($string, $matchObj = '') {
124 $this->raw
= explode(LF
, $string);
128 if ($this->breakPointLN
&& $pre == '[_BREAK]') {
129 $this->error('Breakpoint at ' . ($this->lineNumberOffset +
$this->rawP
- 2) . ': Line content was "' . $this->raw
[$this->rawP
- 2] . '"', 1);
133 if (strtoupper($pre) == '[GLOBAL]' ||
strtoupper($pre) == '[END]' ||
(!$this->lastConditionTrue
&& strtoupper($pre) == '[ELSE]')) {
134 $pre = trim($this->parseSub($this->setup
));
135 $this->lastConditionTrue
= 1;
137 if (strtoupper($pre) != '[ELSE]') {
138 $this->sections
[md5($pre)] = $pre;
139 } // we're in a specific section. Therefore we log this section
140 if ((is_object($matchObj) && $matchObj->match($pre)) ||
$this->syntaxHighLight
) {
141 if (strtoupper($pre) != '[ELSE]') {
142 $this->sectionsMatch
[md5($pre)] = $pre;
144 $pre = trim($this->parseSub($this->setup
));
145 $this->lastConditionTrue
= 1;
147 $pre = trim($this->nextDivider());
148 $this->lastConditionTrue
= 0;
152 if ($this->inBrace
) {
153 $this->error('Line ' . ($this->lineNumberOffset +
$this->rawP
- 1) . ': The script is short of ' . $this->inBrace
. ' end brace(s)', 1);
155 if ($this->multiLineEnabled
) {
156 $this->error('Line ' . ($this->lineNumberOffset +
$this->rawP
- 1) . ': A multiline value section is not ended with a parenthesis!', 1);
158 $this->lineNumberOffset +
= count($this->raw
) +
1;
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.
164 * @return string The condition value
167 function nextDivider() {
168 while (isset($this->raw
[$this->rawP
])) {
169 $line = ltrim($this->raw
[$this->rawP
]);
171 if ($line && substr($line, 0, 1) == '[') {
178 * Parsing the $this->raw TypoScript lines from pointer, $this->rawP
180 * @param array 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)
183 function parseSub(&$setup) {
184 global $TYPO3_CONF_VARS;
186 while (isset($this->raw
[$this->rawP
])) {
187 $line = ltrim($this->raw
[$this->rawP
]);
188 $lineP = $this->rawP
;
190 if ($this->syntaxHighLight
) {
191 $this->regHighLight("prespace", $lineP, strlen($line));
195 if ($this->breakPointLN
&& ($this->lineNumberOffset +
$this->rawP
- 1) == ($this->breakPointLN +
1)) { // by adding 1 we get that line processed
200 if (!$this->multiLineEnabled
&& substr($line, 0, 2) == '/*') {
201 $this->commentSet
= 1;
204 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.
205 if ($this->multiLineEnabled
) { // If multiline is enabled. Escape by ')'
206 if (substr($line, 0, 1) == ')') { // Multiline ends...
207 if ($this->syntaxHighLight
) {
208 $this->regHighLight("operator", $lineP, strlen($line) - 1);
210 $this->multiLineEnabled
= 0; // Disable multiline
211 $theValue = implode($this->multiLineValue
, LF
);
212 if (strstr($this->multiLineObject
, '.')) {
213 $this->setVal($this->multiLineObject
, $setup, array($theValue)); // Set the value deeper.
215 $setup[$this->multiLineObject
] = $theValue; // Set value regularly
216 if ($this->lastComment
&& $this->regComments
) {
217 $setup[$this->multiLineObject
. '..'] .= $this->lastComment
;
219 if ($this->regLinenumbers
) {
220 $setup[$this->multiLineObject
. '.ln..'][] = ($this->lineNumberOffset +
$this->rawP
- 1);
224 if ($this->syntaxHighLight
) {
225 $this->regHighLight("value", $lineP);
227 $this->multiLineValue
[] = $this->raw
[($this->rawP
- 1)];
229 } elseif ($this->inBrace
== 0 && substr($line, 0, 1) == '[') { // Beginning of condition (only on level zero compared to brace-levels
230 if ($this->syntaxHighLight
) {
231 $this->regHighLight("condition", $lineP);
235 if (substr($line, 0, 1) == '[' && strtoupper(trim($line)) == '[GLOBAL]') { // Return if GLOBAL condition is set - no matter what.
236 if ($this->syntaxHighLight
) {
237 $this->regHighLight("condition", $lineP);
239 $this->error('Line ' . ($this->lineNumberOffset +
$this->rawP
- 1) . ': On return to [GLOBAL] scope, the script was short of ' . $this->inBrace
. ' end brace(s)', 1);
242 } elseif (strcspn($line, '}#/') != 0) { // If not brace-end or comment
243 $varL = strcspn($line, ' {=<>:('); // Find object name string until we meet an operator
244 $objStrName = trim(substr($line, 0, $varL));
245 if ($this->syntaxHighLight
) {
246 $this->regHighLight("objstr", $lineP, strlen(substr($line, $varL)));
248 if (strlen($objStrName)) {
250 if ($this->strict
&& preg_match('/[^[:alnum:]_\.-]/i', $objStrName, $r)) {
251 $this->error('Line ' . ($this->lineNumberOffset +
$this->rawP
- 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_-."');
253 $line = ltrim(substr($line, $varL));
254 if ($this->syntaxHighLight
) {
255 $this->regHighLight("objstr_postspace", $lineP, strlen($line));
256 if (strlen($line) > 0) {
257 $this->regHighLight("operator", $lineP, strlen($line) - 1);
258 $this->regHighLight("operator_postspace", $lineP, strlen(ltrim(substr($line, 1))));
262 // Checking for special TSparser properties (to change TS values at parsetime)
264 if (preg_match('/^:=([^\(]+)\((.+)\).*/', $line, $match)) {
265 $tsFunc = trim($match[1]);
266 $tsFuncArg = $match[2];
267 list ($currentValue) = $this->getVal($objStrName, $setup);
269 $tsFuncArg = str_replace(
270 array('\\\\', '\n', '\t'),
271 array('\\', LF
, TAB
),
276 case 'prependString':
277 $newValue = $tsFuncArg . $currentValue;
280 $newValue = $currentValue . $tsFuncArg;
283 $newValue = str_replace($tsFuncArg, '', $currentValue);
285 case 'replaceString':
286 list($fromStr, $toStr) = explode('|', $tsFuncArg, 2);
287 $newValue = str_replace($fromStr, $toStr, $currentValue);
290 $newValue = (strcmp('', $currentValue) ?
$currentValue . ',' : '') . trim($tsFuncArg);
292 case 'removeFromList':
293 $existingElements = t3lib_div
::trimExplode(',', $currentValue);
294 $removeElements = t3lib_div
::trimExplode(',', $tsFuncArg);
295 if (count($removeElements)) {
296 $newValue = implode(',', array_diff($existingElements, $removeElements));
300 if (isset($TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$tsFunc])) {
301 $hookMethod = $TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$tsFunc];
302 $params = array('currentValue' => $currentValue, 'functionArgument' => $tsFuncArg);
304 $newValue = t3lib_div
::callUserFunction($hookMethod, $params, $fakeThis);
306 t3lib_div
::sysLog('Missing function definition for ' . $tsFunc . ' on TypoScript line ' . $lineP, 'Core', 2);
310 if (isset($newValue)) {
311 $line = '= ' . $newValue;
315 switch (substr($line, 0, 1)) {
317 if ($this->syntaxHighLight
) {
318 $this->regHighLight('value', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
321 if (strstr($objStrName, '.')) {
323 $value[0] = trim(substr($line, 1));
324 $this->setVal($objStrName, $setup, $value);
326 $setup[$objStrName] = trim(substr($line, 1));
327 if ($this->lastComment
&& $this->regComments
) { // Setting comment..
328 $setup[$objStrName . '..'] .= $this->lastComment
;
330 if ($this->regLinenumbers
) {
331 $setup[$objStrName . '.ln..'][] = ($this->lineNumberOffset +
$this->rawP
- 1);
337 if (strstr($objStrName, '.')) {
338 $exitSig = $this->rollParseSub($objStrName, $setup);
343 if (!isset($setup[$objStrName . '.'])) {
344 $setup[$objStrName . '.'] = array();
346 $exitSig = $this->parseSub($setup[$objStrName . '.']);
353 $this->multiLineObject
= $objStrName;
354 $this->multiLineEnabled
= 1;
355 $this->multiLineValue
= array();
358 if ($this->syntaxHighLight
) {
359 $this->regHighLight("value_copy", $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
361 $theVal = trim(substr($line, 1));
362 if (substr($theVal, 0, 1) == '.') {
363 $res = $this->getVal(substr($theVal, 1), $setup);
365 $res = $this->getVal($theVal, $this->setup
);
367 $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.
370 if ($this->syntaxHighLight
) {
371 $this->regHighLight("value_unset", $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
373 $this->setVal($objStrName, $setup, 'UNSET');
376 $this->error('Line ' . ($this->lineNumberOffset +
$this->rawP
- 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not preceded by any operator, =<>({');
380 $this->lastComment
= '';
382 } elseif (substr($line, 0, 1) == '}') {
384 $this->lastComment
= '';
385 if ($this->syntaxHighLight
) {
386 $this->regHighLight("operator", $lineP, strlen($line) - 1);
388 if ($this->inBrace
< 0) {
389 $this->error('Line ' . ($this->lineNumberOffset +
$this->rawP
- 1) . ': An end brace is in excess.', 1);
395 if ($this->syntaxHighLight
) {
396 $this->regHighLight("comment", $lineP);
399 // Comment. The comments are concatenated in this temporary string:
400 if ($this->regComments
) {
401 $this->lastComment
.= trim($line) . LF
;
408 if ($this->commentSet
) {
409 if ($this->syntaxHighLight
) {
410 $this->regHighLight("comment", $lineP);
412 if (substr($line, 0, 2) == '*/') {
413 $this->commentSet
= 0;
420 * Parsing of TypoScript keys inside a curly brace where the key is composite of at least two keys, thus having to recursively call itself to get the value
422 * @param string The object sub-path, eg "thisprop.another_prot"
423 * @param array The local setup array from the function calling this function
424 * @return string Returns the exitSignal
427 function rollParseSub($string, &$setup) {
428 if ((string) $string != '') {
429 $keyLen = strcspn($string, '.');
430 if ($keyLen == strlen($string)) {
431 $key = $string . '.';
432 if (!isset($setup[$key])) {
433 $setup[$key] = array();
435 $exitSig = $this->parseSub($setup[$key]);
440 $key = substr($string, 0, $keyLen) . '.';
441 if (!isset($setup[$key])) {
442 $setup[$key] = array();
444 $exitSig = $this->rollParseSub(substr($string, $keyLen +
1), $setup[$key]);
453 * Get a value/property pair for an object path in TypoScript, eg. "myobject.myvalue.mysubproperty". Here: Used by the "copy" operator, <
455 * @param string Object path for which to get the value
456 * @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.
457 * @return array An array with keys 0/1 being value/property respectively
459 function getVal($string, $setup) {
460 if ((string) $string != '') {
461 $keyLen = strcspn($string, '.');
462 if ($keyLen == strlen($string)) {
463 $retArr = array(); // Added 6/6/03. Shouldn't hurt
464 if (isset($setup[$string])) {
465 $retArr[0] = $setup[$string];
467 if (isset($setup[$string . '.'])) {
468 $retArr[1] = $setup[$string . '.'];
472 $key = substr($string, 0, $keyLen) . '.';
474 return $this->getVal(substr($string, $keyLen +
1), $setup[$key]);
481 * Setting a value/property of an object string in the setup array.
483 * @param string The object sub-path, eg "thisprop.another_prot"
484 * @param array The local setup array from the function calling this function.
485 * @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)
486 * @param boolean If set, then both value and property is wiped out when a copy is made of another value.
489 function setVal($string, &$setup, $value, $wipeOut = 0) {
490 if ((string) $string != '') {
491 $keyLen = strcspn($string, '.');
492 if ($keyLen == strlen($string)) {
493 if ($value == 'UNSET') {
494 unset($setup[$string]);
495 unset($setup[$string . '.']);
496 if ($this->regLinenumbers
) {
497 $setup[$string . '.ln..'][] = ($this->lineNumberOffset +
$this->rawP
- 1) . '>';
501 if ($wipeOut && $this->strict
) {
502 if ((isset($setup[$string]) && !isset($value[0])) ||
(isset($setup[$string . '.']) && !isset($value[1]))) {
503 $this->error('Line ' . ($this->lineNumberOffset +
$this->rawP
- 1) . ': Object copied in this line "' . trim($this->raw
[($this->rawP
- 1)]) . '" would leave either the value or properties untouched in TypoScript Version 1. Please check that this is not a problem for you.', 1);
505 unset($setup[$string]);
506 unset($setup[$string . '.']);
507 if ($this->regLinenumbers
) {
508 $setup[$string . '.ln..'][] = ($this->lineNumberOffset +
$this->rawP
- 1) . '<';
512 if (isset($value[0])) {
513 $setup[$string] = $value[0];
515 if (isset($value[1])) {
516 $setup[$string . '.'] = $value[1];
518 if ($this->lastComment
&& $this->regComments
) {
519 $setup[$string . '..'] .= $this->lastComment
;
521 if ($this->regLinenumbers
&& !$lnRegisDone) {
522 $setup[$string . '.ln..'][] = ($this->lineNumberOffset +
$this->rawP
- 1);
526 $key = substr($string, 0, $keyLen) . '.';
527 if (!isset($setup[$key])) {
528 $setup[$key] = array();
530 $this->setVal(substr($string, $keyLen +
1), $setup[$key], $value);
536 * Stacks errors/messages from the TypoScript parser into an internal array, $this->error
537 * If "TT" is a global object (as it is in the frontend when backend users are logged in) the message will be registered here as well.
539 * @param string The error message string
540 * @param integer The error severity (in the scale of $GLOBALS['TT']->setTSlogMessage: Approx: 2=warning, 1=info, 0=nothing, 3=fatal.)
543 function error($err, $num = 2) {
544 if (is_object($GLOBALS['TT'])) {
545 $GLOBALS['TT']->setTSlogMessage($err, $num);
547 $this->errors
[] = array($err, $num, $this->rawP
- 1, $this->lineNumberOffset
);
551 * Checks the input string (un-parsed TypoScript) for include-commands ("<INCLUDE_TYPOSCRIPT: ....")
552 * Use: t3lib_TSparser::checkIncludeLines()
554 * @param string Unparsed TypoScript
555 * @param integer Counter for detecting endless loops
556 * @param boolean When set an array containing the resulting typoscript and all included files will get returned
557 * @return string Complete TypoScript with includes added.
560 function checkIncludeLines($string, $cycle_counter = 1, $returnFiles = FALSE) {
561 $includedFiles = array();
562 if ($cycle_counter > 100) {
563 t3lib_div
::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "<INCLUDE_TYPOSCRIPT: ..." tags', 'Core', 2);
567 'files' => $includedFiles,
570 return "\n###\n### ERROR: Recursion!\n###\n";
572 $splitStr = '<INCLUDE_TYPOSCRIPT:';
573 if (strstr($string, $splitStr)) {
575 $allParts = explode($splitStr, LF
. $string . LF
); // adds line break char before/after
576 foreach ($allParts as $c => $v) {
577 if (!$c) { // first goes through
579 } elseif (preg_match('/\r?\n\s*$/', $allParts[$c - 1])) { // There must be a line-break char before.
580 $subparts = explode('>', $v, 2);
581 if (preg_match('/^\s*\r?\n/', $subparts[1])) { // There must be a line-break char after
582 // SO, the include was positively recognized:
583 $newString .= '### ' . $splitStr . $subparts[0] . '> BEGIN:' . LF
;
584 $params = t3lib_div
::get_tag_attributes($subparts[0]);
585 if ($params['source']) {
586 $sourceParts = explode(':', $params['source'], 2);
587 switch (strtolower(trim($sourceParts[0]))) {
589 $filename = t3lib_div
::getFileAbsFileName(trim($sourceParts[1]));
590 if (strcmp($filename, '')) { // Must exist and must not contain '..' and must be relative
591 if (t3lib_div
::verifyFilenameAgainstDenyPattern($filename)) { // Check for allowed files
592 if (@is_file
($filename) && filesize($filename) < 100000) { // Max. 100 KB include files!
593 // check for includes in included text
594 $includedFiles[] = $filename;
595 $included_text = self
::checkIncludeLines(t3lib_div
::getUrl($filename), $cycle_counter +
1, $returnFiles);
596 // If the method also has to return all included files, merge currently included
597 // files with files included by recursively calling itself
598 if ($returnFiles && is_array($included_text)) {
599 $includedFiles = array_merge($includedFiles, $included_text['files']);
600 $included_text = $included_text['typoscript'];
602 $newString .= $included_text . LF
;
605 t3lib_div
::sysLog('File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern', 'Core', 2);
611 $newString .= '### ' . $splitStr . $subparts[0] . '> END:' . LF
;
612 $newString .= $subparts[1];
614 $newString .= $splitStr . $v;
617 $newString .= $splitStr . $v;
620 $string = substr($newString, 1, -1); // not the first/last linebreak char.
622 // When all included files should get returned, simply return an compound array containing
623 // the TypoScript with all "includes" processed and the files which got included
626 'typoscript' => $string,
627 'files' => $includedFiles,
634 * Parses the string in each value of the input array for include-commands
636 * @param array Array with TypoScript in each value
637 * @return array Same array but where the values has been parsed for include-commands
639 function checkIncludeLines_array($array) {
640 foreach ($array as $k => $v) {
641 $array[$k] = t3lib_TSparser
::checkIncludeLines($array[$k]);
647 * Search for commented INCLUDE_TYPOSCRIPT statements
648 * and save the content between the BEGIN and the END line to the specified file
650 * @param string template content
651 * @param int Counter for detecting endless loops
652 * @return string template content with uncommented include statements
653 * @author Fabrizio Branca <typo3@fabrizio-branca.de>
655 function extractIncludes($string, $cycle_counter = 1, $extractedFileNames = array()) {
657 if ($cycle_counter > 10) {
658 t3lib_div
::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "<INCLUDE_TYPOSCRIPT: ..." tags', 'Core', 2);
659 return "\n###\n### ERROR: Recursion!\n###\n";
662 $fileContent = array();
663 $restContent = array();
665 $inIncludePart = FALSE;
666 $lines = explode("\n", $string);
667 $skipNextLineIfEmpty = FALSE;
668 $openingCommentedIncludeStatement = NULL;
669 foreach ($lines as $line) {
671 // t3lib_TSparser::checkIncludeLines inserts an additional empty line, remove this again
672 if ($skipNextLineIfEmpty) {
673 if (trim($line) == '') {
676 $skipNextLineIfEmpty = FALSE;
679 if (!$inIncludePart) { // outside commented include statements
681 // search for beginning commented include statements
683 if (preg_match('/###\s*<INCLUDE_TYPOSCRIPT:\s*source\s*=\s*"\s*FILE\s*:\s*(.*)\s*">\s*BEGIN/i', $line, $matches)) {
685 // save this line in case there is no ending tag
686 $openingCommentedIncludeStatement = trim($line);
687 $openingCommentedIncludeStatement = trim(preg_replace('/### Warning: .*###/', '', $openingCommentedIncludeStatement));
689 // found a commented include statement
690 $fileName = trim($matches[1]);
691 $inIncludePart = TRUE;
693 $expectedEndTag = '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $fileName . '"> END';
694 // strip all whitespace characters to make comparision safer
695 $expectedEndTag = strtolower(preg_replace('/\s/', '', $expectedEndTag));
697 // if this is not a beginning commented include statement this line goes into the rest content
698 $restContent[] = $line;
701 } else { // inside commented include statements
703 // search for the matching ending commented include statement
704 $strippedLine = strtolower(preg_replace('/\s/', '', $line));
705 if (strpos($strippedLine, $expectedEndTag) !== FALSE) {
707 // found the matching ending include statement
708 $fileContentString = implode("\n", $fileContent);
710 // write the content to the file
711 $realFileName = t3lib_div
::getFileAbsFileName($fileName);
714 if (empty($realFileName)) {
715 throw new UnexpectedValueException(sprintf('"%s" is not a valid file location.', $fileName), 1294586441);
718 if (!is_writable($realFileName)) {
719 throw new RuntimeException(sprintf('"%s" is not writable.', $fileName), 1294586442);
722 if (in_array($realFileName, $extractedFileNames)) {
723 throw new RuntimeException(sprintf('Recursive/multiple inclusion of file "%s"', $realFileName), 1294586443);
725 $extractedFileNames[] = $realFileName;
727 // recursive call to detected nested commented include statements
728 $fileContentString = self
::extractIncludes($fileContentString, ++
$cycle_counter, $extractedFileNames);
730 if (!t3lib_div
::writeFile($realFileName, $fileContentString)) {
731 throw new RuntimeException(sprintf('Could not write file "%s"', $realFileName), 1294586444);
734 // insert reference to the file in the rest content
735 $restContent[] = "<INCLUDE_TYPOSCRIPT: source=\"FILE:$fileName\">";
737 // reset variables (preparing for the next commented include statement)
738 $fileContent = array();
740 $inIncludePart = FALSE;
741 $openingCommentedIncludeStatement = NULL;
742 // t3lib_TSparser::checkIncludeLines inserts an additional empty line, remove this again
743 $skipNextLineIfEmpty = TRUE;
745 // if this is not a ending commented include statement this line goes into the file content
746 $fileContent[] = $line;
753 // if we're still inside commented include statements copy the lines back to the rest content
754 if ($inIncludePart) {
755 $restContent[] = $openingCommentedIncludeStatement . ' ### Warning: Corresponding end line missing! ###';
756 $restContent = array_merge($restContent, $fileContent);
759 $restContentString = implode("\n", $restContent);
760 return $restContentString;
764 * Processes the string in each value of the input array with extractIncludes
766 * @param array Array with TypoScript in each value
767 * @return array Same array but where the values has been processed with extractIncludes
768 * @author Fabrizio Branca <typo3@fabrizio-branca.de>
770 function extractIncludes_array($array) {
771 foreach ($array as $k => $v) {
772 $array[$k] = t3lib_TSparser
::extractIncludes($array[$k]);
778 /**********************************
780 * Syntax highlighting
782 *********************************/
785 * Syntax highlight a TypoScript text
786 * Will parse the content. Remember, the internal setup array may contain invalid parsed content since conditions are ignored!
788 * @param string The TypoScript text
789 * @param mixed If blank, linenumbers are NOT printed. If array then the first key is the linenumber offset to add to the internal counter.
790 * @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.
791 * @return string HTML code for the syntax highlighted string
793 function doSyntaxHighlight($string, $lineNum = '', $highlightBlockMode = 0) {
794 $this->syntaxHighLight
= 1;
795 $this->highLightData
= array();
796 $this->error
= array();
797 $string = str_replace(CR
, '', $string); // This is done in order to prevent empty <span>..</span> sections around CR content. Should not do anything but help lessen the amount of HTML code.
799 $this->parse($string);
801 return $this->syntaxHighlight_print($lineNum, $highlightBlockMode);
805 * Registers a part of a TypoScript line for syntax highlighting.
807 * @param string Key from the internal array $this->highLightStyles
808 * @param integer Pointer to the line in $this->raw which this is about
809 * @param integer The number of chars LEFT on this line before the end is reached.
814 function regHighLight($code, $pointer, $strlen = -1) {
816 $this->highLightData
[$pointer] = array(array($code, 0));
818 $this->highLightData
[$pointer][] = array($code, $strlen);
820 $this->highLightData_bracelevel
[$pointer] = $this->inBrace
;
824 * Formatting the TypoScript code in $this->raw based on the data collected by $this->regHighLight in $this->highLightData
826 * @param mixed If blank, linenumbers are NOT printed. If array then the first key is the linenumber offset to add to the internal counter.
827 * @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.
828 * @return string HTML content
830 * @see doSyntaxHighlight()
832 function syntaxHighlight_print($lineNumDat, $highlightBlockMode) {
833 // Registers all error messages in relation to their linenumber
835 foreach ($this->errors
as $err) {
836 $errA[$err[2]][] = $err[0];
838 // Generates the syntax highlighted output:
840 foreach ($this->raw
as $rawP => $value) {
842 $strlen = strlen($value);
845 if (is_array($this->highLightData
[$rawP])) {
846 foreach ($this->highLightData
[$rawP] as $set) {
847 $len = $strlen - $start - $set[1];
849 $part = substr($value, $start, $len);
851 $st = $this->highLightStyles
[(isset($this->highLightStyles
[$set[0]]) ?
$set[0] : 'default')];
852 if (!$highlightBlockMode ||
$set[0] != 'prespace') {
853 $lineC .= $st[0] . htmlspecialchars($part) . $st[1];
855 } elseif ($len < 0) {
856 debug(array($len, $value, $rawP));
860 debug(array($value));
863 if (strlen(substr($value, $start))) {
864 $lineC .= $this->highLightStyles
['ignored'][0] . htmlspecialchars(substr($value, $start)) . $this->highLightStyles
['ignored'][1];
868 $lineC .= $this->highLightStyles
['error'][0] . '<strong> - ERROR:</strong> ' . htmlspecialchars(implode(';', $errA[$rawP])) . $this->highLightStyles
['error'][1];
871 if ($highlightBlockMode && $this->highLightData_bracelevel
[$rawP]) {
872 $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 : ' ') . '</span>';
875 if (is_array($lineNumDat)) {
876 $lineNum = $rawP +
$lineNumDat[0];
877 if ($this->parentObject
instanceof t3lib_tsparser_ext
) {
878 $lineNum = $this->parentObject
->ext_lnBreakPointWrap($lineNum, $lineNum);
880 $lineC = $this->highLightStyles
['linenum'][0] . str_pad($lineNum, 4, ' ', STR_PAD_LEFT
) . ':' . $this->highLightStyles
['linenum'][1] . ' ' . $lineC;
887 return '<pre class="ts-hl">' . implode(LF
, $lines) . '</pre>';
892 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE
]['XCLASS']['t3lib/class.t3lib_tsparser.php'])) {
893 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE
]['XCLASS']['t3lib/class.t3lib_tsparser.php']);