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