TYPO3 API  SVNRelease
class.t3lib_tsparser.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *  A copy is found in the textfile GPL.txt and important notices to the license
00017  *  from the author is found in LICENSE.txt distributed with these scripts.
00018  *
00019  *
00020  *  This script is distributed in the hope that it will be useful,
00021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  *  GNU General Public License for more details.
00024  *
00025  *  This copyright notice MUST APPEAR in all copies of the script!
00026  ***************************************************************/
00027 /**
00028  * Contains the TypoScript parser class
00029  *
00030  * $Id: class.t3lib_tsparser.php 10663 2011-02-28 19:33:43Z lolli $
00031  * Revised for TYPO3 3.6 July/2003 by Kasper Skårhøj
00032  *
00033  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00034  */
00035 /**
00036  * [CLASS/FUNCTION INDEX of SCRIPT]
00037  *
00038  *
00039  *
00040  *   80: class t3lib_TSparser
00041  *  133:     function parse($string,$matchObj='')
00042  *  169:     function nextDivider()
00043  *  185:     function parseSub(&$setup)
00044  *  389:     function rollParseSub($string,&$setup)
00045  *  413:     function getVal($string,$setup)
00046  *  439:     function setVal($string,&$setup,$value,$wipeOut=0)
00047  *  485:     function error($err,$num=2)
00048  *  497:     function checkIncludeLines($string, $cycle_counter=1, $returnFiles=false)
00049  *  541:     function checkIncludeLines_array($array)
00050  *
00051  *            SECTION: Syntax highlighting
00052  *  584:     function doSyntaxHighlight($string,$lineNum='',$highlightBlockMode=0)
00053  *  605:     function regHighLight($code,$pointer,$strlen=-1)
00054  *  623:     function syntaxHighlight_print($lineNumDat,$highlightBlockMode)
00055  *
00056  * TOTAL FUNCTIONS: 12
00057  * (This index is automatically created/updated by the extension "extdeveval")
00058  *
00059  */
00060 
00061 
00062 /**
00063  * The TypoScript parser
00064  *
00065  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00066  * @package TYPO3
00067  * @subpackage t3lib
00068  * @see t3lib_tstemplate, t3lib_BEfunc::getPagesTSconfig(), t3lib_userAuthGroup::fetchGroupData(), t3lib_TStemplate::generateConfig()
00069  */
00070 class t3lib_TSparser {
00071     var $strict = 1; // If set, then key names cannot contain characters other than [:alnum:]_\.-
00072 
00073         // Internal
00074     var $setup = array(); // TypoScript hierarchy being build during parsing.
00075     var $raw; // raw data, the input string exploded by LF
00076     var $rawP; // pointer to entry in raw data array
00077     var $lastComment = ''; // Holding the value of the last comment
00078     var $commentSet = 0; // Internally set, used as internal flag to create a multi-line comment (one of those like /*... */)
00079     var $multiLineEnabled = 0; // Internally set, when multiline value is accumulated
00080     var $multiLineObject = ''; // Internally set, when multiline value is accumulated
00081     var $multiLineValue = array(); // Internally set, when multiline value is accumulated
00082     var $inBrace = 0; // Internally set, when in brace. Counter.
00083     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.
00084     var $sections = array(); // Tracking all conditions found
00085     var $sectionsMatch = array(); // Tracking all matching conditions found
00086     var $syntaxHighLight = 0; // If set, then syntax highlight mode is on; Call the function syntaxHighlight() to use this function
00087     var $highLightData = array(); // Syntax highlight data is accumulated in this array. Used by syntaxHighlight_print() to construct the output.
00088     var $highLightData_bracelevel = array(); // Syntax highlight data keeping track of the curly brace level for each line
00089 
00090         // Debugging, analysis:
00091     var $regComments = 0; // DO NOT register the comments. This is default for the ordinary sitetemplate!
00092     var $regLinenumbers = 0; // DO NOT register the linenumbers. This is default for the ordinary sitetemplate!
00093     var $errors = array(); // Error accumulation array.
00094     var $lineNumberOffset = 0; // Used for the error messages line number reporting. Set externally.
00095     var $breakPointLN = 0; // Line for break point.
00096     var $highLightStyles = array(
00097         'prespace' => array('<span class="ts-prespace">', '</span>'), // Space before any content on a line
00098         'objstr_postspace' => array('<span class="ts-objstr_postspace">', '</span>'), // Space after the object string on a line
00099         'operator_postspace' => array('<span class="ts-operator_postspace">', '</span>'), // Space after the operator on a line
00100         'operator' => array('<span class="ts-operator">', '</span>'), // The operator char
00101         'value' => array('<span class="ts-value">', '</span>'), // The value of a line
00102         'objstr' => array('<span class="ts-objstr">', '</span>'), // The object string of a line
00103         'value_copy' => array('<span class="ts-value_copy">', '</span>'), // The value when the copy syntax (<) is used; that means the object reference
00104         'value_unset' => array('<span class="ts-value_unset">', '</span>'), // The value when an object is unset. Should not exist.
00105         'ignored' => array('<span class="ts-ignored">', '</span>'), // The "rest" of a line which will be ignored.
00106         'default' => array('<span class="ts-default">', '</span>'), // The default style if none other is applied.
00107         'comment' => array('<span class="ts-comment">', '</span>'), // Comment lines
00108         'condition' => array('<span class="ts-condition">', '</span>'), // Conditions
00109         'error' => array('<span class="ts-error">', '</span>'), // Error messages
00110         'linenum' => array('<span class="ts-linenum">', '</span>'), // Line numbers
00111     );
00112     var $highLightBlockStyles = ''; // Additional attributes for the <span> tags for a blockmode line
00113     var $highLightBlockStyles_basecolor = '#cccccc'; // The hex-HTML color for the blockmode
00114 
00115     public $parentObject; //Instance of parentObject, used by t3lib_tsparser_ext
00116 
00117     /**
00118      * Start parsing the input TypoScript text piece. The result is stored in $this->setup
00119      *
00120      * @param   string      The TypoScript text
00121      * @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])
00122      * @return  void
00123      */
00124     function parse($string, $matchObj = '') {
00125         $this->raw = explode(LF, $string);
00126         $this->rawP = 0;
00127         $pre = '[GLOBAL]';
00128         while ($pre) {
00129             if ($this->breakPointLN && $pre == '[_BREAK]') {
00130                 $this->error('Breakpoint at ' . ($this->lineNumberOffset + $this->rawP - 2) . ': Line content was "' . $this->raw[$this->rawP - 2] . '"', 1);
00131                 break;
00132             }
00133 
00134             if (strtoupper($pre) == '[GLOBAL]' || strtoupper($pre) == '[END]' || (!$this->lastConditionTrue && strtoupper($pre) == '[ELSE]')) {
00135                 $pre = trim($this->parseSub($this->setup));
00136                 $this->lastConditionTrue = 1;
00137             } else {
00138                 if (strtoupper($pre) != '[ELSE]') {
00139                     $this->sections[md5($pre)] = $pre;
00140                 } // we're in a specific section. Therefore we log this section
00141                 if ((is_object($matchObj) && $matchObj->match($pre)) || $this->syntaxHighLight) {
00142                     if (strtoupper($pre) != '[ELSE]') {
00143                         $this->sectionsMatch[md5($pre)] = $pre;
00144                     }
00145                     $pre = trim($this->parseSub($this->setup));
00146                     $this->lastConditionTrue = 1;
00147                 } else {
00148                     $pre = trim($this->nextDivider());
00149                     $this->lastConditionTrue = 0;
00150                 }
00151             }
00152         }
00153         if ($this->inBrace) {
00154             $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': The script is short of ' . $this->inBrace . ' end brace(s)', 1);
00155         }
00156         if ($this->multiLineEnabled) {
00157             $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': A multiline value section is not ended with a parenthesis!', 1);
00158         }
00159         $this->lineNumberOffset += count($this->raw) + 1;
00160     }
00161 
00162     /**
00163      * 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.
00164      *
00165      * @return  string      The condition value
00166      * @see parse()
00167      */
00168     function nextDivider() {
00169         while (isset($this->raw[$this->rawP])) {
00170             $line = ltrim($this->raw[$this->rawP]);
00171             $this->rawP++;
00172             if ($line && substr($line, 0, 1) == '[') {
00173                 return $line;
00174             }
00175         }
00176     }
00177 
00178     /**
00179      * Parsing the $this->raw TypoScript lines from pointer, $this->rawP
00180      *
00181      * @param   array       Reference to the setup array in which to accumulate the values.
00182      * @return  string      Returns the string of the condition found, the exit signal or possible nothing (if it completed parsing with no interruptions)
00183      */
00184     function parseSub(&$setup) {
00185         global $TYPO3_CONF_VARS;
00186 
00187         while (isset($this->raw[$this->rawP])) {
00188             $line = ltrim($this->raw[$this->rawP]);
00189             $lineP = $this->rawP;
00190             $this->rawP++;
00191             if ($this->syntaxHighLight) {
00192                 $this->regHighLight("prespace", $lineP, strlen($line));
00193             }
00194 
00195                 // Breakpoint?
00196             if ($this->breakPointLN && ($this->lineNumberOffset + $this->rawP - 1) == ($this->breakPointLN + 1)) { // by adding 1 we get that line processed
00197                 return '[_BREAK]';
00198             }
00199 
00200                 // Set comment flag?
00201             if (!$this->multiLineEnabled && substr($line, 0, 2) == '/*') {
00202                 $this->commentSet = 1;
00203             }
00204 
00205             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.
00206                 if ($this->multiLineEnabled) { // If multiline is enabled. Escape by ')'
00207                     if (substr($line, 0, 1) == ')') { // Multiline ends...
00208                         if ($this->syntaxHighLight) {
00209                             $this->regHighLight("operator", $lineP, strlen($line) - 1);
00210                         }
00211                         $this->multiLineEnabled = 0; // Disable multiline
00212                         $theValue = implode($this->multiLineValue, LF);
00213                         if (strstr($this->multiLineObject, '.')) {
00214                             $this->setVal($this->multiLineObject, $setup, array($theValue)); // Set the value deeper.
00215                         } else {
00216                             $setup[$this->multiLineObject] = $theValue; // Set value regularly
00217                             if ($this->lastComment && $this->regComments) {
00218                                 $setup[$this->multiLineObject . '..'] .= $this->lastComment;
00219                             }
00220                             if ($this->regLinenumbers) {
00221                                 $setup[$this->multiLineObject . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1);
00222                             }
00223                         }
00224                     } else {
00225                         if ($this->syntaxHighLight) {
00226                             $this->regHighLight("value", $lineP);
00227                         }
00228                         $this->multiLineValue[] = $this->raw[($this->rawP - 1)];
00229                     }
00230                 } elseif ($this->inBrace == 0 && substr($line, 0, 1) == '[') { // Beginning of condition (only on level zero compared to brace-levels
00231                     if ($this->syntaxHighLight) {
00232                         $this->regHighLight("condition", $lineP);
00233                     }
00234                     return $line;
00235                 } else {
00236                     if (substr($line, 0, 1) == '[' && strtoupper(trim($line)) == '[GLOBAL]') { // Return if GLOBAL condition is set - no matter what.
00237                         if ($this->syntaxHighLight) {
00238                             $this->regHighLight("condition", $lineP);
00239                         }
00240                         $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': On return to [GLOBAL] scope, the script was short of ' . $this->inBrace . ' end brace(s)', 1);
00241                         $this->inBrace = 0;
00242                         return $line;
00243                     } elseif (strcspn($line, '}#/') != 0) { // If not brace-end or comment
00244                         $varL = strcspn($line, ' {=<>:('); // Find object name string until we meet an operator
00245                         $objStrName = trim(substr($line, 0, $varL));
00246                         if ($this->syntaxHighLight) {
00247                             $this->regHighLight("objstr", $lineP, strlen(substr($line, $varL)));
00248                         }
00249                         if (strlen($objStrName)) {
00250                             $r = array();
00251                             if ($this->strict && preg_match('/[^[:alnum:]_\.-]/i', $objStrName, $r)) {
00252                                 $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_-."');
00253                             } else {
00254                                 $line = ltrim(substr($line, $varL));
00255                                 if ($this->syntaxHighLight) {
00256                                     $this->regHighLight("objstr_postspace", $lineP, strlen($line));
00257                                     if (strlen($line) > 0) {
00258                                         $this->regHighLight("operator", $lineP, strlen($line) - 1);
00259                                         $this->regHighLight("operator_postspace", $lineP, strlen(ltrim(substr($line, 1))));
00260                                     }
00261                                 }
00262 
00263                                     // Checking for special TSparser properties (to change TS values at parsetime)
00264                                 $match = array();
00265                                 if (preg_match('/^:=([^\(]+)\((.+)\).*/', $line, $match)) {
00266                                     $tsFunc = trim($match[1]);
00267                                     $tsFuncArg = $match[2];
00268                                     list ($currentValue) = $this->getVal($objStrName, $setup);
00269 
00270                                     $tsFuncArg = str_replace(
00271                                         array('\\\\', '\n', '\t'),
00272                                         array('\\', LF, TAB),
00273                                         $tsFuncArg
00274                                     );
00275 
00276                                     switch ($tsFunc) {
00277                                         case 'prependString':
00278                                             $newValue = $tsFuncArg . $currentValue;
00279                                         break;
00280                                         case 'appendString':
00281                                             $newValue = $currentValue . $tsFuncArg;
00282                                         break;
00283                                         case 'removeString':
00284                                             $newValue = str_replace($tsFuncArg, '', $currentValue);
00285                                         break;
00286                                         case 'replaceString':
00287                                             list($fromStr, $toStr) = explode('|', $tsFuncArg, 2);
00288                                             $newValue = str_replace($fromStr, $toStr, $currentValue);
00289                                         break;
00290                                         case 'addToList':
00291                                             $newValue = (strcmp('', $currentValue) ? $currentValue . ',' : '') . trim($tsFuncArg);
00292                                         break;
00293                                         case 'removeFromList':
00294                                             $existingElements = t3lib_div::trimExplode(',', $currentValue);
00295                                             $removeElements = t3lib_div::trimExplode(',', $tsFuncArg);
00296                                             if (count($removeElements)) {
00297                                                 $newValue = implode(',', array_diff($existingElements, $removeElements));
00298                                             }
00299                                         break;
00300                                         default:
00301                                             if (isset($TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$tsFunc])) {
00302                                                 $hookMethod = $TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$tsFunc];
00303                                                 $params = array('currentValue' => $currentValue, 'functionArgument' => $tsFuncArg);
00304                                                 $fakeThis = FALSE;
00305                                                 $newValue = t3lib_div::callUserFunction($hookMethod, $params, $fakeThis);
00306                                             } else {
00307                                                 t3lib_div::sysLog('Missing function definition for ' . $tsFunc . ' on TypoScript line ' . $lineP, 'Core', 2);
00308                                             }
00309                                     }
00310 
00311                                     if (isset($newValue)) {
00312                                         $line = '= ' . $newValue;
00313                                     }
00314                                 }
00315 
00316                                 switch (substr($line, 0, 1)) {
00317                                     case '=':
00318                                         if ($this->syntaxHighLight) {
00319                                             $this->regHighLight('value', $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
00320                                         }
00321 
00322                                         if (strstr($objStrName, '.')) {
00323                                             $value = array();
00324                                             $value[0] = trim(substr($line, 1));
00325                                             $this->setVal($objStrName, $setup, $value);
00326                                         } else {
00327                                             $setup[$objStrName] = trim(substr($line, 1));
00328                                             if ($this->lastComment && $this->regComments) { // Setting comment..
00329                                                 $setup[$objStrName . '..'] .= $this->lastComment;
00330                                             }
00331                                             if ($this->regLinenumbers) {
00332                                                 $setup[$objStrName . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1);
00333                                             }
00334                                         }
00335                                     break;
00336                                     case '{':
00337                                         $this->inBrace++;
00338                                         if (strstr($objStrName, '.')) {
00339                                             $exitSig = $this->rollParseSub($objStrName, $setup);
00340                                             if ($exitSig) {
00341                                                 return $exitSig;
00342                                             }
00343                                         } else {
00344                                             if (!isset($setup[$objStrName . '.'])) {
00345                                                 $setup[$objStrName . '.'] = array();
00346                                             }
00347                                             $exitSig = $this->parseSub($setup[$objStrName . '.']);
00348                                             if ($exitSig) {
00349                                                 return $exitSig;
00350                                             }
00351                                         }
00352                                     break;
00353                                     case '(':
00354                                         $this->multiLineObject = $objStrName;
00355                                         $this->multiLineEnabled = 1;
00356                                         $this->multiLineValue = array();
00357                                     break;
00358                                     case '<':
00359                                         if ($this->syntaxHighLight) {
00360                                             $this->regHighLight("value_copy", $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
00361                                         }
00362                                         $theVal = trim(substr($line, 1));
00363                                         if (substr($theVal, 0, 1) == '.') {
00364                                             $res = $this->getVal(substr($theVal, 1), $setup);
00365                                         } else {
00366                                             $res = $this->getVal($theVal, $this->setup);
00367                                         }
00368                                         $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.
00369                                     break;
00370                                     case '>':
00371                                         if ($this->syntaxHighLight) {
00372                                             $this->regHighLight("value_unset", $lineP, strlen(ltrim(substr($line, 1))) - strlen(trim(substr($line, 1))));
00373                                         }
00374                                         $this->setVal($objStrName, $setup, 'UNSET');
00375                                     break;
00376                                     default:
00377                                         $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" was not preceded by any operator, =<>({');
00378                                     break;
00379                                 }
00380                             }
00381                             $this->lastComment = '';
00382                         }
00383                     } elseif (substr($line, 0, 1) == '}') {
00384                         $this->inBrace--;
00385                         $this->lastComment = '';
00386                         if ($this->syntaxHighLight) {
00387                             $this->regHighLight("operator", $lineP, strlen($line) - 1);
00388                         }
00389                         if ($this->inBrace < 0) {
00390                             $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': An end brace is in excess.', 1);
00391                             $this->inBrace = 0;
00392                         } else {
00393                             break;
00394                         }
00395                     } else {
00396                         if ($this->syntaxHighLight) {
00397                             $this->regHighLight("comment", $lineP);
00398                         }
00399 
00400                             // Comment. The comments are concatenated in this temporary string:
00401                         if ($this->regComments) {
00402                             $this->lastComment .= trim($line) . LF;
00403                         }
00404                     }
00405                 }
00406             }
00407 
00408                 // Unset comment
00409             if ($this->commentSet) {
00410                 if ($this->syntaxHighLight) {
00411                     $this->regHighLight("comment", $lineP);
00412                 }
00413                 if (substr($line, 0, 2) == '*/') {
00414                     $this->commentSet = 0;
00415                 }
00416             }
00417         }
00418     }
00419 
00420     /**
00421      * 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
00422      *
00423      * @param   string      The object sub-path, eg "thisprop.another_prot"
00424      * @param   array       The local setup array from the function calling this function
00425      * @return  string      Returns the exitSignal
00426      * @see parseSub()
00427      */
00428     function rollParseSub($string, &$setup) {
00429         if ((string) $string != '') {
00430             $keyLen = strcspn($string, '.');
00431             if ($keyLen == strlen($string)) {
00432                 $key = $string . '.';
00433                 if (!isset($setup[$key])) {
00434                     $setup[$key] = array();
00435                 }
00436                 $exitSig = $this->parseSub($setup[$key]);
00437                 if ($exitSig) {
00438                     return $exitSig;
00439                 }
00440             } else {
00441                 $key = substr($string, 0, $keyLen) . '.';
00442                 if (!isset($setup[$key])) {
00443                     $setup[$key] = array();
00444                 }
00445                 $exitSig = $this->rollParseSub(substr($string, $keyLen + 1), $setup[$key]);
00446                 if ($exitSig) {
00447                     return $exitSig;
00448                 }
00449             }
00450         }
00451     }
00452 
00453     /**
00454      * Get a value/property pair for an object path in TypoScript, eg. "myobject.myvalue.mysubproperty". Here: Used by the "copy" operator, <
00455      *
00456      * @param   string      Object path for which to get the value
00457      * @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.
00458      * @return  array       An array with keys 0/1 being value/property respectively
00459      */
00460     function getVal($string, $setup) {
00461         if ((string) $string != '') {
00462             $keyLen = strcspn($string, '.');
00463             if ($keyLen == strlen($string)) {
00464                 $retArr = array(); // Added 6/6/03. Shouldn't hurt
00465                 if (isset($setup[$string])) {
00466                     $retArr[0] = $setup[$string];
00467                 }
00468                 if (isset($setup[$string . '.'])) {
00469                     $retArr[1] = $setup[$string . '.'];
00470                 }
00471                 return $retArr;
00472             } else {
00473                 $key = substr($string, 0, $keyLen) . '.';
00474                 if ($setup[$key]) {
00475                     return $this->getVal(substr($string, $keyLen + 1), $setup[$key]);
00476                 }
00477             }
00478         }
00479     }
00480 
00481     /**
00482      * Setting a value/property of an object string in the setup array.
00483      *
00484      * @param   string      The object sub-path, eg "thisprop.another_prot"
00485      * @param   array       The local setup array from the function calling this function.
00486      * @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)
00487      * @param   boolean     If set, then both value and property is wiped out when a copy is made of another value.
00488      * @return  void
00489      */
00490     function setVal($string, &$setup, $value, $wipeOut = 0) {
00491         if ((string) $string != '') {
00492             $keyLen = strcspn($string, '.');
00493             if ($keyLen == strlen($string)) {
00494                 if ($value == 'UNSET') {
00495                     unset($setup[$string]);
00496                     unset($setup[$string . '.']);
00497                     if ($this->regLinenumbers) {
00498                         $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '>';
00499                     }
00500                 } else {
00501                     $lnRegisDone = 0;
00502                     if ($wipeOut && $this->strict) {
00503                         if ((isset($setup[$string]) && !isset($value[0])) || (isset($setup[$string . '.']) && !isset($value[1]))) {
00504                             $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);
00505                         }
00506                         unset($setup[$string]);
00507                         unset($setup[$string . '.']);
00508                         if ($this->regLinenumbers) {
00509                             $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '<';
00510                             $lnRegisDone = 1;
00511                         }
00512                     }
00513                     if (isset($value[0])) {
00514                         $setup[$string] = $value[0];
00515                     }
00516                     if (isset($value[1])) {
00517                         $setup[$string . '.'] = $value[1];
00518                     }
00519                     if ($this->lastComment && $this->regComments) {
00520                         $setup[$string . '..'] .= $this->lastComment;
00521                     }
00522                     if ($this->regLinenumbers && !$lnRegisDone) {
00523                         $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1);
00524                     }
00525                 }
00526             } else {
00527                 $key = substr($string, 0, $keyLen) . '.';
00528                 if (!isset($setup[$key])) {
00529                     $setup[$key] = array();
00530                 }
00531                 $this->setVal(substr($string, $keyLen + 1), $setup[$key], $value);
00532             }
00533         }
00534     }
00535 
00536     /**
00537      * Stacks errors/messages from the TypoScript parser into an internal array, $this->error
00538      * 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.
00539      *
00540      * @param   string      The error message string
00541      * @param   integer     The error severity (in the scale of $GLOBALS['TT']->setTSlogMessage: Approx: 2=warning, 1=info, 0=nothing, 3=fatal.)
00542      * @return  void
00543      */
00544     function error($err, $num = 2) {
00545         if (is_object($GLOBALS['TT'])) {
00546             $GLOBALS['TT']->setTSlogMessage($err, $num);
00547         }
00548         $this->errors[] = array($err, $num, $this->rawP - 1, $this->lineNumberOffset);
00549     }
00550 
00551     /**
00552      * Checks the input string (un-parsed TypoScript) for include-commands ("<INCLUDE_TYPOSCRIPT: ....")
00553      * Use: t3lib_TSparser::checkIncludeLines()
00554      *
00555      * @param   string      Unparsed TypoScript
00556      * @param   integer     Counter for detecting endless loops
00557      * @param   boolean     When set an array containing the resulting typoscript and all included files will get returned
00558      * @return  string      Complete TypoScript with includes added.
00559      * @static
00560      */
00561     function checkIncludeLines($string, $cycle_counter = 1, $returnFiles = FALSE) {
00562         $includedFiles = array();
00563         if ($cycle_counter > 100) {
00564             t3lib_div::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', 2);
00565             if ($returnFiles) {
00566                 return array(
00567                     'typoscript' => '',
00568                     'files' => $includedFiles,
00569                 );
00570             }
00571             return "\n###\n### ERROR: Recursion!\n###\n";
00572         }
00573         $splitStr = '<INCLUDE_TYPOSCRIPT:';
00574         if (strstr($string, $splitStr)) {
00575             $newString = '';
00576             $allParts = explode($splitStr, LF . $string . LF); // adds line break char before/after
00577             foreach ($allParts as $c => $v) {
00578                 if (!$c) { // first goes through
00579                     $newString .= $v;
00580                 } elseif (preg_match('/\r?\n\s*$/', $allParts[$c - 1])) { // There must be a line-break char before.
00581                     $subparts = explode('>', $v, 2);
00582                     if (preg_match('/^\s*\r?\n/', $subparts[1])) { // There must be a line-break char after
00583                             // SO, the include was positively recognized:
00584                         $newString .= '### ' . $splitStr . $subparts[0] . '> BEGIN:' . LF;
00585                         $params = t3lib_div::get_tag_attributes($subparts[0]);
00586                         if ($params['source']) {
00587                             $sourceParts = explode(':', $params['source'], 2);
00588                             switch (strtolower(trim($sourceParts[0]))) {
00589                                 case 'file':
00590                                     $filename = t3lib_div::getFileAbsFileName(trim($sourceParts[1]));
00591                                     if (strcmp($filename, '')) { // Must exist and must not contain '..' and must be relative
00592                                         if (t3lib_div::verifyFilenameAgainstDenyPattern($filename)) { // Check for allowed files
00593                                             if (@is_file($filename) && filesize($filename) < 100000) { // Max. 100 KB include files!
00594                                                     // check for includes in included text
00595                                                 $includedFiles[] = $filename;
00596                                                 $included_text = self::checkIncludeLines(t3lib_div::getUrl($filename), $cycle_counter + 1, $returnFiles);
00597                                                     // If the method also has to return all included files, merge currently included
00598                                                     // files with files included by recursively calling itself
00599                                                 if ($returnFiles && is_array($included_text)) {
00600                                                     $includedFiles = array_merge($includedFiles, $included_text['files']);
00601                                                     $included_text = $included_text['typoscript'];
00602                                                 }
00603                                                 $newString .= $included_text . LF;
00604                                             }
00605                                         } else {
00606                                             t3lib_div::sysLog('File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern', 'Core', 2);
00607                                         }
00608                                     }
00609                                 break;
00610                             }
00611                         }
00612                         $newString .= '### ' . $splitStr . $subparts[0] . '> END:' . LF;
00613                         $newString .= $subparts[1];
00614                     } else {
00615                         $newString .= $splitStr . $v;
00616                     }
00617                 } else {
00618                     $newString .= $splitStr . $v;
00619                 }
00620             }
00621             $string = substr($newString, 1, -1); // not the first/last linebreak char.
00622         }
00623             // When all included files should get returned, simply return an compound array containing
00624             // the TypoScript with all "includes" processed and the files which got included
00625         if ($returnFiles) {
00626             return array(
00627                 'typoscript' => $string,
00628                 'files' => $includedFiles,
00629             );
00630         }
00631         return $string;
00632     }
00633 
00634     /**
00635      * Parses the string in each value of the input array for include-commands
00636      *
00637      * @param   array       Array with TypoScript in each value
00638      * @return  array       Same array but where the values has been parsed for include-commands
00639      */
00640     function checkIncludeLines_array($array) {
00641         foreach ($array as $k => $v) {
00642             $array[$k] = t3lib_TSparser::checkIncludeLines($array[$k]);
00643         }
00644         return $array;
00645     }
00646 
00647     /**
00648      * Search for commented INCLUDE_TYPOSCRIPT statements
00649      * and save the content between the BEGIN and the END line to the specified file
00650      *
00651      * @param    string template content
00652      * @param    int        Counter for detecting endless loops
00653      * @return   string  template content with uncommented include statements
00654      * @author   Fabrizio Branca <typo3@fabrizio-branca.de>
00655      */
00656     function extractIncludes($string, $cycle_counter = 1, $extractedFileNames = array()) {
00657 
00658         if ($cycle_counter > 10) {
00659             t3lib_div::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "&lt;INCLUDE_TYPOSCRIPT: ..." tags', 'Core', 2);
00660             return "\n###\n### ERROR: Recursion!\n###\n";
00661         }
00662 
00663         $fileContent = array();
00664         $restContent = array();
00665         $fileName = NULL;
00666         $inIncludePart = FALSE;
00667         $lines = explode("\n", $string);
00668         $skipNextLineIfEmpty = FALSE;
00669         $openingCommentedIncludeStatement = NULL;
00670         foreach ($lines as $line) {
00671 
00672                 // t3lib_TSparser::checkIncludeLines inserts an additional empty line, remove this again
00673             if ($skipNextLineIfEmpty) {
00674                 if (trim($line) == '') {
00675                     continue;
00676                 }
00677                 $skipNextLineIfEmpty = FALSE;
00678             }
00679 
00680             if (!$inIncludePart) { // outside commented include statements
00681 
00682                     // search for beginning commented include statements
00683                 $matches = array();
00684                 if (preg_match('/###\s*<INCLUDE_TYPOSCRIPT:\s*source\s*=\s*"\s*FILE\s*:\s*(.*)\s*">\s*BEGIN/i', $line, $matches)) {
00685 
00686                         // save this line in case there is no ending tag
00687                     $openingCommentedIncludeStatement = trim($line);
00688                     $openingCommentedIncludeStatement = trim(preg_replace('/### Warning: .*###/', '', $openingCommentedIncludeStatement));
00689 
00690                         // found a commented include statement
00691                     $fileName = trim($matches[1]);
00692                     $inIncludePart = TRUE;
00693 
00694                     $expectedEndTag = '### <INCLUDE_TYPOSCRIPT: source="FILE:' . $fileName . '"> END';
00695                         // strip all whitespace characters to make comparision safer
00696                     $expectedEndTag = strtolower(preg_replace('/\s/', '', $expectedEndTag));
00697                 } else {
00698                         // if this is not a beginning commented include statement this line goes into the rest content
00699                     $restContent[] = $line;
00700                 }
00701 
00702             } else { // inside commented include statements
00703 
00704                     // search for the matching ending commented include statement
00705                 $strippedLine = strtolower(preg_replace('/\s/', '', $line));
00706                 if (strpos($strippedLine, $expectedEndTag) !== FALSE) {
00707 
00708                         // found the matching ending include statement
00709                     $fileContentString = implode("\n", $fileContent);
00710 
00711                         // write the content to the file
00712                     $realFileName = t3lib_div::getFileAbsFileName($fileName);
00713 
00714                         // some file checks
00715                     if (empty($realFileName)) {
00716                         throw new Exception(sprintf('"%s" is not a valid file location.', $fileName));
00717                     }
00718 
00719                     if (!is_writable($realFileName)) {
00720                         throw new Exception(sprintf('"%s" is not writable.', $fileName));
00721                     }
00722 
00723                     if (in_array($realFileName, $extractedFileNames)) {
00724                         throw new Exception(sprintf('Recursive/multiple inclusion of file "%s"', $realFileName));
00725                     }
00726                     $extractedFileNames[] = $realFileName;
00727 
00728                         // recursive call to detected nested commented include statements
00729                     $fileContentString = self::extractIncludes($fileContentString, ++$cycle_counter, $extractedFileNames);
00730 
00731                     if (!t3lib_div::writeFile($realFileName, $fileContentString)) {
00732                         throw new Exception(sprintf('Could not write file "%s"', $realFileName));
00733                     }
00734 
00735                         // insert reference to the file in the rest content
00736                     $restContent[] = "<INCLUDE_TYPOSCRIPT: source=\"FILE:$fileName\">";
00737 
00738                         // reset variables (preparing for the next commented include statement)
00739                     $fileContent = array();
00740                     $fileName = NULL;
00741                     $inIncludePart = FALSE;
00742                     $openingCommentedIncludeStatement = NULL;
00743                         // t3lib_TSparser::checkIncludeLines inserts an additional empty line, remove this again
00744                     $skipNextLineIfEmpty = TRUE;
00745                 } else {
00746                         // if this is not a ending commented include statement this line goes into the file content
00747                     $fileContent[] = $line;
00748                 }
00749 
00750             }
00751 
00752         }
00753 
00754             // if we're still inside commented include statements copy the lines back to the rest content
00755         if ($inIncludePart) {
00756             $restContent[] = $openingCommentedIncludeStatement . ' ### Warning: Corresponding end line missing! ###';
00757             $restContent = array_merge($restContent, $fileContent);
00758         }
00759 
00760         $restContentString = implode("\n", $restContent);
00761         return $restContentString;
00762     }
00763 
00764     /**
00765      * Processes the string in each value of the input array with extractIncludes
00766      *
00767      * @param   array       Array with TypoScript in each value
00768      * @return  array       Same array but where the values has been processed with extractIncludes
00769      * @author   Fabrizio Branca <typo3@fabrizio-branca.de>
00770      */
00771     function extractIncludes_array($array) {
00772         foreach ($array as $k => $v) {
00773             $array[$k] = t3lib_TSparser::extractIncludes($array[$k]);
00774         }
00775         return $array;
00776     }
00777 
00778 
00779     /**********************************
00780      *
00781      * Syntax highlighting
00782      *
00783      *********************************/
00784 
00785     /**
00786      * Syntax highlight a TypoScript text
00787      * Will parse the content. Remember, the internal setup array may contain invalid parsed content since conditions are ignored!
00788      *
00789      * @param   string      The TypoScript text
00790      * @param   mixed       If blank, linenumbers are NOT printed. If array then the first key is the linenumber offset to add to the internal counter.
00791      * @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.
00792      * @return  string      HTML code for the syntax highlighted string
00793      */
00794     function doSyntaxHighlight($string, $lineNum = '', $highlightBlockMode = 0) {
00795         $this->syntaxHighLight = 1;
00796         $this->highLightData = array();
00797         $this->error = array();
00798         $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.
00799 
00800         $this->parse($string);
00801 
00802         return $this->syntaxHighlight_print($lineNum, $highlightBlockMode);
00803     }
00804 
00805     /**
00806      * Registers a part of a TypoScript line for syntax highlighting.
00807      *
00808      * @param   string      Key from the internal array $this->highLightStyles
00809      * @param   integer     Pointer to the line in $this->raw which this is about
00810      * @param   integer     The number of chars LEFT on this line before the end is reached.
00811      * @return  void
00812      * @access private
00813      * @see parse()
00814      */
00815     function regHighLight($code, $pointer, $strlen = -1) {
00816         if ($strlen == -1) {
00817             $this->highLightData[$pointer] = array(array($code, 0));
00818         } else {
00819             $this->highLightData[$pointer][] = array($code, $strlen);
00820         }
00821         $this->highLightData_bracelevel[$pointer] = $this->inBrace;
00822     }
00823 
00824     /**
00825      * Formatting the TypoScript code in $this->raw based on the data collected by $this->regHighLight in $this->highLightData
00826      *
00827      * @param   mixed       If blank, linenumbers are NOT printed. If array then the first key is the linenumber offset to add to the internal counter.
00828      * @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.
00829      * @return  string      HTML content
00830      * @access private
00831      * @see doSyntaxHighlight()
00832      */
00833     function syntaxHighlight_print($lineNumDat, $highlightBlockMode) {
00834             // Registers all error messages in relation to their linenumber
00835         $errA = array();
00836         foreach ($this->errors as $err) {
00837             $errA[$err[2]][] = $err[0];
00838         }
00839             // Generates the syntax highlighted output:
00840         $lines = array();
00841         foreach ($this->raw as $rawP => $value) {
00842             $start = 0;
00843             $strlen = strlen($value);
00844             $lineC = '';
00845 
00846             if (is_array($this->highLightData[$rawP])) {
00847                 foreach ($this->highLightData[$rawP] as $set) {
00848                     $len = $strlen - $start - $set[1];
00849                     if ($len > 0) {
00850                         $part = substr($value, $start, $len);
00851                         $start += $len;
00852                         $st = $this->highLightStyles[(isset($this->highLightStyles[$set[0]]) ? $set[0] : 'default')];
00853                         if (!$highlightBlockMode || $set[0] != 'prespace') {
00854                             $lineC .= $st[0] . htmlspecialchars($part) . $st[1];
00855                         }
00856                     } elseif ($len < 0) {
00857                         debug(array($len, $value, $rawP));
00858                     }
00859                 }
00860             } else {
00861                 debug(array($value));
00862             }
00863 
00864             if (strlen(substr($value, $start))) {
00865                 $lineC .= $this->highLightStyles['ignored'][0] . htmlspecialchars(substr($value, $start)) . $this->highLightStyles['ignored'][1];
00866             }
00867 
00868             if ($errA[$rawP]) {
00869                 $lineC .= $this->highLightStyles['error'][0] . '<strong> - ERROR:</strong> ' . htmlspecialchars(implode(';', $errA[$rawP])) . $this->highLightStyles['error'][1];
00870             }
00871 
00872             if ($highlightBlockMode && $this->highLightData_bracelevel[$rawP]) {
00873                 $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>';
00874             }
00875 
00876             if (is_array($lineNumDat)) {
00877                 $lineNum = $rawP + $lineNumDat[0];
00878                 if ($this->parentObject instanceof t3lib_tsparser_ext) {
00879                     $lineNum = $this->parentObject->ext_lnBreakPointWrap($lineNum, $lineNum);
00880                 }
00881                 $lineC = $this->highLightStyles['linenum'][0] . str_pad($lineNum, 4, ' ', STR_PAD_LEFT) . ':' . $this->highLightStyles['linenum'][1] . ' ' . $lineC;
00882             }
00883 
00884 
00885             $lines[] = $lineC;
00886         }
00887 
00888         return '<pre class="ts-hl">' . implode(LF, $lines) . '</pre>';
00889     }
00890 }
00891 
00892 
00893 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_tsparser.php'])) {
00894     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_tsparser.php']);
00895 }
00896 
00897 ?>