1 : <?php
2 :
3 : include dirname(__FILE__) . '/Compilation/Exception.php';
4 :
5 : /**
6 : * default dwoo compiler class, compiles dwoo templates into php
7 : *
8 : * This software is provided 'as-is', without any express or implied warranty.
9 : * In no event will the authors be held liable for any damages arising from the use of this software.
10 : *
11 : * @author Jordi Boggiano <j.boggiano@seld.be>
12 : * @copyright Copyright (c) 2008, Jordi Boggiano
13 : * @license http://dwoo.org/LICENSE Modified BSD License
14 : * @link http://dwoo.org/
15 : * @version 1.1.0
16 : * @date 2009-07-18
17 : * @package Dwoo
18 : */
19 : class Dwoo_Compiler implements Dwoo_ICompiler
20 : {
21 : /**
22 : * constant that represents a php opening tag
23 : *
24 : * use it in case it needs to be adjusted
25 : *
26 : * @var string
27 : */
28 : const PHP_OPEN = "<?php ";
29 :
30 : /**
31 : * constant that represents a php closing tag
32 : *
33 : * use it in case it needs to be adjusted
34 : *
35 : * @var string
36 : */
37 : const PHP_CLOSE = "?>";
38 :
39 : /**
40 : * boolean flag to enable or disable debugging output
41 : *
42 : * @var bool
43 : */
44 : public $debug = false;
45 :
46 : /**
47 : * left script delimiter
48 : *
49 : * @var string
50 : */
51 : protected $ld = '{';
52 :
53 : /**
54 : * left script delimiter with escaped regex meta characters
55 : *
56 : * @var string
57 : */
58 : protected $ldr = '\\{';
59 :
60 : /**
61 : * right script delimiter
62 : *
63 : * @var string
64 : */
65 : protected $rd = '}';
66 :
67 : /**
68 : * right script delimiter with escaped regex meta characters
69 : *
70 : * @var string
71 : */
72 : protected $rdr = '\\}';
73 :
74 : /**
75 : * defines whether the nested comments should be parsed as nested or not
76 : *
77 : * defaults to false (classic block comment parsing as in all languages)
78 : *
79 : * @var bool
80 : */
81 : protected $allowNestedComments = false;
82 :
83 : /**
84 : * defines whether opening and closing tags can contain spaces before valid data or not
85 : *
86 : * turn to true if you want to be sloppy with the syntax, but when set to false it allows
87 : * to skip javascript and css tags as long as they are in the form "{ something", which is
88 : * nice. default is false.
89 : *
90 : * @var bool
91 : */
92 : protected $allowLooseOpenings = false;
93 :
94 : /**
95 : * defines whether the compiler will automatically html-escape variables or not
96 : *
97 : * default is false
98 : *
99 : * @var bool
100 : */
101 : protected $autoEscape = false;
102 :
103 : /**
104 : * security policy object
105 : *
106 : * @var Dwoo_Security_Policy
107 : */
108 : protected $securityPolicy;
109 :
110 : /**
111 : * stores the custom plugins registered with this compiler
112 : *
113 : * @var array
114 : */
115 : protected $customPlugins = array();
116 :
117 : /**
118 : * stores the template plugins registered with this compiler
119 : *
120 : * @var array
121 : */
122 : protected $templatePlugins = array();
123 :
124 : /**
125 : * stores the pre- and post-processors callbacks
126 : *
127 : * @var array
128 : */
129 : protected $processors = array('pre'=>array(), 'post'=>array());
130 :
131 : /**
132 : * stores a list of plugins that are used in the currently compiled
133 : * template, and that are not compilable. these plugins will be loaded
134 : * during the template's runtime if required.
135 : *
136 : * it is a 1D array formatted as key:pluginName value:pluginType
137 : *
138 : * @var array
139 : */
140 : protected $usedPlugins;
141 :
142 : /**
143 : * stores the template undergoing compilation
144 : *
145 : * @var string
146 : */
147 : protected $template;
148 :
149 : /**
150 : * stores the current pointer position inside the template
151 : *
152 : * @var int
153 : */
154 : protected $pointer;
155 :
156 : /**
157 : * stores the current line count inside the template for debugging purposes
158 : *
159 : * @var int
160 : */
161 : protected $line;
162 :
163 : /**
164 : * stores the current template source while compiling it
165 : *
166 : * @var string
167 : */
168 : protected $templateSource;
169 :
170 : /**
171 : * stores the data within which the scope moves
172 : *
173 : * @var array
174 : */
175 : protected $data;
176 :
177 : /**
178 : * variable scope of the compiler, set to null if
179 : * it can not be resolved to a static string (i.e. if some
180 : * plugin defines a new scope based on a variable array key)
181 : *
182 : * @var mixed
183 : */
184 : protected $scope;
185 :
186 : /**
187 : * variable scope tree, that allows to rebuild the current
188 : * scope if required, i.e. when going to a parent level
189 : *
190 : * @var array
191 : */
192 : protected $scopeTree;
193 :
194 : /**
195 : * block plugins stack, accessible through some methods
196 : *
197 : * @see findBlock
198 : * @see getCurrentBlock
199 : * @see addBlock
200 : * @see addCustomBlock
201 : * @see injectBlock
202 : * @see removeBlock
203 : * @see removeTopBlock
204 : *
205 : * @var array
206 : */
207 : protected $stack = array();
208 :
209 : /**
210 : * current block at the top of the block plugins stack,
211 : * accessible through getCurrentBlock
212 : *
213 : * @see getCurrentBlock
214 : *
215 : * @var Dwoo_Block_Plugin
216 : */
217 : protected $curBlock;
218 :
219 : /**
220 : * current dwoo object that uses this compiler, or null
221 : *
222 : * @var Dwoo
223 : */
224 : protected $dwoo;
225 :
226 : /**
227 : * holds an instance of this class, used by getInstance when you don't
228 : * provide a custom compiler in order to save resources
229 : *
230 : * @var Dwoo_Compiler
231 : */
232 : protected static $instance;
233 :
234 : /**
235 : * token types
236 : * @var int
237 : */
238 : const T_UNQUOTED_STRING = 1;
239 : const T_NUMERIC = 2;
240 : const T_NULL = 4;
241 : const T_BOOL = 8;
242 : const T_MATH = 16;
243 : const T_BREAKCHAR = 32;
244 :
245 : /**
246 : * constructor
247 : *
248 : * saves the created instance so that child templates get the same one
249 : */
250 : public function __construct()
251 : {
252 16 : self::$instance = $this;
253 16 : }
254 :
255 : /**
256 : * sets the delimiters to use in the templates
257 : *
258 : * delimiters can be multi-character strings but should not be one of those as they will
259 : * make it very hard to work with templates or might even break the compiler entirely : "\", "$", "|", ":" and finally "#" only if you intend to use config-vars with the #var# syntax.
260 : *
261 : * @param string $left left delimiter
262 : * @param string $right right delimiter
263 : */
264 : public function setDelimiters($left, $right)
265 : {
266 2 : $this->ld = $left;
267 2 : $this->rd = $right;
268 2 : $this->ldr = preg_quote($left, '/');
269 2 : $this->rdr = preg_quote($right, '/');
270 2 : }
271 :
272 : /**
273 : * returns the left and right template delimiters
274 : *
275 : * @return array containing the left and the right delimiters
276 : */
277 : public function getDelimiters()
278 : {
279 9 : return array($this->ld, $this->rd);
280 : }
281 :
282 : /**
283 : * sets the way to handle nested comments, if set to true
284 : * {* foo {* some other *} comment *} will be stripped correctly.
285 : *
286 : * if false it will remove {* foo {* some other *} and leave "comment *}" alone,
287 : * this is the default behavior
288 : *
289 : * @param bool $allow allow nested comments or not, defaults to true (but the default internal value is false)
290 : */
291 : public function setNestedCommentsHandling($allow = true) {
292 2 : $this->allowNestedComments = (bool) $allow;
293 2 : }
294 :
295 : /**
296 : * returns the nested comments handling setting
297 : *
298 : * @see setNestedCommentsHandling
299 : * @return bool true if nested comments are allowed
300 : */
301 : public function getNestedCommentsHandling() {
302 1 : return $this->allowNestedComments;
303 : }
304 :
305 : /**
306 : * sets the tag openings handling strictness, if set to true, template tags can
307 : * contain spaces before the first function/string/variable such as { $foo} is valid.
308 : *
309 : * if set to false (default setting), { $foo} is invalid but that is however a good thing
310 : * as it allows css (i.e. #foo { color:red; }) to be parsed silently without triggering
311 : * an error, same goes for javascript.
312 : *
313 : * @param bool $allow true to allow loose handling, false to restore default setting
314 : */
315 : public function setLooseOpeningHandling($allow = false)
316 : {
317 2 : $this->allowLooseOpenings = (bool) $allow;
318 2 : }
319 :
320 : /**
321 : * returns the tag openings handling strictness setting
322 : *
323 : * @see setLooseOpeningHandling
324 : * @return bool true if loose tags are allowed
325 : */
326 : public function getLooseOpeningHandling()
327 : {
328 4 : return $this->allowLooseOpenings;
329 : }
330 :
331 : /**
332 : * changes the auto escape setting
333 : *
334 : * if enabled, the compiler will automatically html-escape variables,
335 : * unless they are passed through the safe function such as {$var|safe}
336 : * or {safe $var}
337 : *
338 : * default setting is disabled/false
339 : *
340 : * @param bool $enabled set to true to enable, false to disable
341 : */
342 : public function setAutoEscape($enabled)
343 : {
344 5 : $this->autoEscape = (bool) $enabled;
345 5 : }
346 :
347 : /**
348 : * returns the auto escape setting
349 : *
350 : * default setting is disabled/false
351 : *
352 : * @return bool
353 : */
354 : public function getAutoEscape()
355 : {
356 3 : return $this->autoEscape;
357 : }
358 :
359 : /**
360 : * adds a preprocessor to the compiler, it will be called
361 : * before the template is compiled
362 : *
363 : * @param mixed $callback either a valid callback to the preprocessor or a simple name if the autoload is set to true
364 : * @param bool $autoload if set to true, the preprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback
365 : */
366 : public function addPreProcessor($callback, $autoload = false)
367 : {
368 3 : if ($autoload) {
369 3 : $name = str_replace('Dwoo_Processor_', '', $callback);
370 3 : $class = 'Dwoo_Processor_'.$name;
371 :
372 3 : if (class_exists($class, false)) {
373 1 : $callback = array(new $class($this), 'process');
374 3 : } elseif (function_exists($class)) {
375 0 : $callback = $class;
376 0 : } else {
377 2 : $callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name);
378 : }
379 :
380 3 : $this->processors['pre'][] = $callback;
381 3 : } else {
382 0 : $this->processors['pre'][] = $callback;
383 : }
384 3 : }
385 :
386 : /**
387 : * removes a preprocessor from the compiler
388 : *
389 : * @param mixed $callback either a valid callback to the preprocessor or a simple name if it was autoloaded
390 : */
391 : public function removePreProcessor($callback)
392 : {
393 0 : if (($index = array_search($callback, $this->processors['pre'], true)) !== false) {
394 0 : unset($this->processors['pre'][$index]);
395 0 : } elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['pre'], true)) !== false) {
396 0 : unset($this->processors['pre'][$index]);
397 0 : } else {
398 0 : $class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback);
399 0 : foreach ($this->processors['pre'] as $index=>$proc) {
400 0 : if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) {
401 0 : unset($this->processors['pre'][$index]);
402 0 : break;
403 : }
404 0 : }
405 : }
406 0 : }
407 :
408 : /**
409 : * adds a postprocessor to the compiler, it will be called
410 : * before the template is compiled
411 : *
412 : * @param mixed $callback either a valid callback to the postprocessor or a simple name if the autoload is set to true
413 : * @param bool $autoload if set to true, the postprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback
414 : */
415 : public function addPostProcessor($callback, $autoload = false)
416 : {
417 1 : if ($autoload) {
418 1 : $name = str_replace('Dwoo_Processor_', '', $callback);
419 1 : $class = 'Dwoo_Processor_'.$name;
420 :
421 1 : if (class_exists($class, false)) {
422 0 : $callback = array(new $class($this), 'process');
423 1 : } elseif (function_exists($class)) {
424 0 : $callback = $class;
425 0 : } else {
426 1 : $callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name);
427 : }
428 :
429 1 : $this->processors['post'][] = $callback;
430 1 : } else {
431 0 : $this->processors['post'][] = $callback;
432 : }
433 1 : }
434 :
435 : /**
436 : * removes a postprocessor from the compiler
437 : *
438 : * @param mixed $callback either a valid callback to the postprocessor or a simple name if it was autoloaded
439 : */
440 : public function removePostProcessor($callback)
441 : {
442 0 : if (($index = array_search($callback, $this->processors['post'], true)) !== false) {
443 0 : unset($this->processors['post'][$index]);
444 0 : } elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['post'], true)) !== false) {
445 0 : unset($this->processors['post'][$index]);
446 0 : } else {
447 0 : $class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback);
448 0 : foreach ($this->processors['post'] as $index=>$proc) {
449 0 : if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) {
450 0 : unset($this->processors['post'][$index]);
451 0 : break;
452 : }
453 0 : }
454 : }
455 0 : }
456 :
457 : /**
458 : * internal function to autoload processors at runtime if required
459 : *
460 : * @param string $class the class/function name
461 : * @param string $name the plugin name (without Dwoo_Plugin_ prefix)
462 : */
463 : protected function loadProcessor($class, $name)
464 : {
465 7 : if (!class_exists($class, false) && !function_exists($class)) {
466 : try {
467 3 : $this->dwoo->getLoader()->loadPlugin($name);
468 3 : } catch (Dwoo_Exception $e) {
469 2 : throw new Dwoo_Exception('Processor '.$name.' could not be found in your plugin directories, please ensure it is in a file named '.$name.'.php in the plugin directory');
470 : }
471 1 : }
472 :
473 5 : if (class_exists($class, false)) {
474 5 : return array(new $class($this), 'process');
475 : }
476 :
477 0 : if (function_exists($class)) {
478 0 : return $class;
479 : }
480 :
481 0 : throw new Dwoo_Exception('Wrong processor name, when using autoload the processor must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Processor_name"');
482 : }
483 :
484 : /**
485 : * adds an used plugin, this is reserved for use by the {template} plugin
486 : *
487 : * this is required so that plugin loading bubbles up from loaded
488 : * template files to the current one
489 : *
490 : * @private
491 : * @param string $name function name
492 : * @param int $type plugin type (Dwoo::*_PLUGIN)
493 : */
494 : public function addUsedPlugin($name, $type)
495 : {
496 48 : $this->usedPlugins[$name] = $type;
497 48 : }
498 :
499 : /**
500 : * returns all the plugins this template uses
501 : *
502 : * @private
503 : * @return array the list of used plugins in the parsed template
504 : */
505 : public function getUsedPlugins()
506 : {
507 3 : return $this->usedPlugins;
508 : }
509 :
510 : /**
511 : * adds a template plugin, this is reserved for use by the {template} plugin
512 : *
513 : * this is required because the template functions are not declared yet
514 : * during compilation, so we must have a way of validating their argument
515 : * signature without using the reflection api
516 : *
517 : * @private
518 : * @param string $name function name
519 : * @param array $params parameter array to help validate the function call
520 : * @param string $uuid unique id of the function
521 : * @param string $body function php code
522 : */
523 : public function addTemplatePlugin($name, array $params, $uuid, $body = null)
524 : {
525 3 : $this->templatePlugins[$name] = array('params'=> $params, 'body' => $body, 'uuid' => $uuid);
526 3 : }
527 :
528 : /**
529 : * returns all the parsed sub-templates
530 : *
531 : * @private
532 : * @return array the parsed sub-templates
533 : */
534 : public function getTemplatePlugins()
535 : {
536 3 : return $this->templatePlugins;
537 : }
538 :
539 : /**
540 : * marks a template plugin as being called, which means its source must be included in the compiled template
541 : *
542 : * @param string $name function name
543 : */
544 : public function useTemplatePlugin($name)
545 : {
546 0 : $this->templatePlugins[$name]['called'] = true;
547 0 : }
548 :
549 : /**
550 : * adds the custom plugins loaded into Dwoo to the compiler so it can load them
551 : *
552 : * @see Dwoo::addPlugin
553 : * @param array $customPlugins an array of custom plugins
554 : */
555 : public function setCustomPlugins(array $customPlugins)
556 : {
557 219 : $this->customPlugins = $customPlugins;
558 219 : }
559 :
560 : /**
561 : * sets the security policy object to enforce some php security settings
562 : *
563 : * use this if untrusted persons can modify templates,
564 : * set it on the Dwoo object as it will be passed onto the compiler automatically
565 : *
566 : * @param Dwoo_Security_Policy $policy the security policy object
567 : */
568 : public function setSecurityPolicy(Dwoo_Security_Policy $policy = null)
569 : {
570 220 : $this->securityPolicy = $policy;
571 220 : }
572 :
573 : /**
574 : * returns the current security policy object or null by default
575 : *
576 : * @return Dwoo_Security_Policy|null the security policy object if any
577 : */
578 : public function getSecurityPolicy()
579 : {
580 1 : return $this->securityPolicy;
581 : }
582 :
583 : /**
584 : * sets the pointer position
585 : *
586 : * @param int $position the new pointer position
587 : * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position
588 : */
589 : public function setPointer($position, $isOffset = false)
590 : {
591 5 : if ($isOffset) {
592 1 : $this->pointer += $position;
593 1 : } else {
594 5 : $this->pointer = $position;
595 : }
596 5 : }
597 :
598 : /**
599 : * returns the current pointer position, only available during compilation of a template
600 : *
601 : * @return int
602 : */
603 : public function getPointer()
604 : {
605 34 : return $this->pointer;
606 : }
607 :
608 : /**
609 : * sets the line number
610 : *
611 : * @param int $number the new line number
612 : * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position
613 : */
614 : public function setLine($number, $isOffset = false)
615 : {
616 1 : if ($isOffset) {
617 1 : $this->line += $number;
618 1 : } else {
619 1 : $this->line = $number;
620 : }
621 1 : }
622 :
623 : /**
624 : * returns the current line number, only available during compilation of a template
625 : *
626 : * @return int
627 : */
628 : public function getLine()
629 : {
630 10 : return $this->line;
631 : }
632 :
633 : /**
634 : * returns the dwoo object that initiated this template compilation, only available during compilation of a template
635 : *
636 : * @return Dwoo
637 : */
638 : public function getDwoo()
639 : {
640 18 : return $this->dwoo;
641 : }
642 :
643 : /**
644 : * overwrites the template that is being compiled
645 : *
646 : * @param string $newSource the template source that must replace the current one
647 : * @param bool $fromPointer if set to true, only the source from the current pointer position is replaced
648 : * @return string the template or partial template
649 : */
650 : public function setTemplateSource($newSource, $fromPointer = false)
651 : {
652 4 : if ($fromPointer === true) {
653 1 : $this->templateSource = substr($this->templateSource, 0, $this->pointer) . $newSource;
654 1 : } else {
655 4 : $this->templateSource = $newSource;
656 : }
657 4 : }
658 :
659 : /**
660 : * returns the template that is being compiled
661 : *
662 : * @param mixed $fromPointer if set to true, only the source from the current pointer
663 : * position is returned, if a number is given it overrides the current pointer
664 : * @return string the template or partial template
665 : */
666 : public function getTemplateSource($fromPointer = false)
667 : {
668 37 : if ($fromPointer === true) {
669 1 : return substr($this->templateSource, $this->pointer);
670 37 : } elseif (is_numeric($fromPointer)) {
671 31 : return substr($this->templateSource, $fromPointer);
672 : } else {
673 7 : return $this->templateSource;
674 : }
675 : }
676 :
677 : /**
678 : * resets the compilation pointer, effectively restarting the compilation process
679 : *
680 : * this is useful if a plugin modifies the template source since it might need to be recompiled
681 : */
682 : public function recompile()
683 : {
684 3 : $this->setPointer(0);
685 3 : }
686 :
687 : /**
688 : * compiles the provided string down to php code
689 : *
690 : * @param string $tpl the template to compile
691 : * @return string a compiled php string
692 : */
693 : public function compile(Dwoo $dwoo, Dwoo_ITemplate $template)
694 : {
695 : // init vars
696 221 : $tpl = $template->getSource();
697 221 : $ptr = 0;
698 221 : $this->dwoo = $dwoo;
699 221 : $this->template = $template;
700 221 : $this->templateSource =& $tpl;
701 221 : $this->pointer =& $ptr;
702 :
703 221 : while (true) {
704 : // if pointer is at the beginning, reset everything, that allows a plugin to externally reset the compiler if everything must be reparsed
705 221 : if ($ptr===0) {
706 : // resets variables
707 221 : $this->usedPlugins = array();
708 221 : $this->data = array();
709 221 : $this->scope =& $this->data;
710 221 : $this->scopeTree = array();
711 221 : $this->stack = array();
712 221 : $this->line = 1;
713 221 : $this->templatePlugins = array();
714 : // add top level block
715 221 : $compiled = $this->addBlock('topLevelBlock', array(), 0);
716 221 : $this->stack[0]['buffer'] = '';
717 :
718 221 : if ($this->debug) echo 'COMPILER INIT<br />';
719 :
720 221 : if ($this->debug) echo 'PROCESSING PREPROCESSORS ('.count($this->processors['pre']).')<br>';
721 :
722 : // runs preprocessors
723 221 : foreach ($this->processors['pre'] as $preProc) {
724 7 : if (is_array($preProc) && isset($preProc['autoload'])) {
725 6 : $preProc = $this->loadProcessor($preProc['class'], $preProc['name']);
726 5 : }
727 6 : if (is_array($preProc) && $preProc[0] instanceof Dwoo_Processor) {
728 6 : $tpl = call_user_func($preProc, $tpl);
729 6 : } else {
730 0 : $tpl = call_user_func($preProc, $this, $tpl);
731 : }
732 220 : }
733 220 : unset($preProc);
734 :
735 : // show template source if debug
736 220 : if ($this->debug) echo '<pre>'.print_r(htmlentities($tpl), true).'</pre><hr />';
737 :
738 : // strips php tags if required by the security policy
739 220 : if ($this->securityPolicy !== null) {
740 3 : $search = array('{<\?php.*?\?>}');
741 3 : if (ini_get('short_open_tags')) {
742 0 : $search = array('{<\?.*?\?>}', '{<%.*?%>}');
743 0 : }
744 3 : switch($this->securityPolicy->getPhpHandling()) {
745 :
746 3 : case Dwoo_Security_Policy::PHP_ALLOW:
747 1 : break;
748 3 : case Dwoo_Security_Policy::PHP_ENCODE:
749 1 : $tpl = preg_replace_callback($search, array($this, 'phpTagEncodingHelper'), $tpl);
750 1 : break;
751 3 : case Dwoo_Security_Policy::PHP_REMOVE:
752 3 : $tpl = preg_replace($search, '', $tpl);
753 :
754 3 : }
755 3 : }
756 220 : }
757 :
758 220 : $pos = strpos($tpl, $this->ld, $ptr);
759 :
760 220 : if ($pos === false) {
761 211 : $this->push(substr($tpl, $ptr), 0);
762 211 : break;
763 215 : } elseif (substr($tpl, $pos-1, 1) === '\\' && substr($tpl, $pos-2, 1) !== '\\') {
764 4 : $this->push(substr($tpl, $ptr, $pos-$ptr-1) . $this->ld);
765 4 : $ptr = $pos+strlen($this->ld);
766 215 : } elseif (preg_match('/^'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . 'literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', substr($tpl, $pos), $litOpen)) {
767 2 : if (!preg_match('/'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . '\/literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', $tpl, $litClose, PREG_OFFSET_CAPTURE, $pos)) {
768 1 : throw new Dwoo_Compilation_Exception($this, 'The {literal} blocks must be closed explicitly with {/literal}');
769 : }
770 1 : $endpos = $litClose[0][1];
771 1 : $this->push(substr($tpl, $ptr, $pos-$ptr) . substr($tpl, $pos + strlen($litOpen[0]), $endpos-$pos-strlen($litOpen[0])));
772 1 : $ptr = $endpos+strlen($litClose[0][0]);
773 1 : } else {
774 213 : if (substr($tpl, $pos-2, 1) === '\\' && substr($tpl, $pos-1, 1) === '\\') {
775 1 : $this->push(substr($tpl, $ptr, $pos-$ptr-1));
776 1 : $ptr = $pos;
777 1 : }
778 :
779 213 : $this->push(substr($tpl, $ptr, $pos-$ptr));
780 213 : $ptr = $pos;
781 :
782 213 : $pos += strlen($this->ld);
783 213 : if ($this->allowLooseOpenings) {
784 2 : while (substr($tpl, $pos, 1) === ' ') {
785 1 : $pos+=1;
786 1 : }
787 2 : } else {
788 212 : if (substr($tpl, $pos, 1) === ' ' || substr($tpl, $pos, 1) === "\r" || substr($tpl, $pos, 1) === "\n" || substr($tpl, $pos, 1) === "\t") {
789 2 : $ptr = $pos;
790 2 : $this->push($this->ld);
791 2 : continue;
792 : }
793 : }
794 :
795 : // check that there is an end tag present
796 213 : if (strpos($tpl, $this->rd, $pos) === false) {
797 1 : throw new Dwoo_Compilation_Exception($this, 'A template tag was not closed, started with "'.substr($tpl, $ptr, 30).'"');
798 : }
799 :
800 :
801 212 : $ptr += strlen($this->ld);
802 212 : $subptr = $ptr;
803 :
804 212 : while (true) {
805 212 : $parsed = $this->parse($tpl, $subptr, null, false, 'root', $subptr);
806 :
807 : // reload loop if the compiler was reset
808 206 : if ($ptr === 0) {
809 3 : continue 2;
810 : }
811 :
812 206 : $len = $subptr - $ptr;
813 206 : $this->push($parsed, substr_count(substr($tpl, $ptr, $len), "\n"));
814 206 : $ptr += $len;
815 :
816 206 : if ($parsed === false) {
817 206 : break;
818 : }
819 203 : }
820 :
821 : // adds additional line breaks between php closing and opening tags because the php parser removes those if there is just a single line break
822 206 : if (substr($this->curBlock['buffer'], -2) === '?>' && preg_match('{^(([\r\n])([\r\n]?))}', substr($tpl, $ptr, 3), $m)) {
823 8 : if ($m[3] === '') {
824 8 : $ptr+=1;
825 8 : $this->push($m[1].$m[1], 1);
826 8 : } else {
827 1 : $ptr+=2;
828 1 : $this->push($m[1]."\n", 2);
829 : }
830 8 : }
831 : }
832 207 : }
833 :
834 211 : $compiled .= $this->removeBlock('topLevelBlock');
835 :
836 211 : if ($this->debug) echo 'PROCESSING POSTPROCESSORS<br>';
837 :
838 211 : foreach ($this->processors['post'] as $postProc) {
839 1 : if (is_array($postProc) && isset($postProc['autoload'])) {
840 1 : $postProc = $this->loadProcessor($postProc['class'], $postProc['name']);
841 0 : }
842 0 : if (is_array($postProc) && $postProc[0] instanceof Dwoo_Processor) {
843 0 : $compiled = call_user_func($postProc, $compiled);
844 0 : } else {
845 0 : $compiled = call_user_func($postProc, $this, $compiled);
846 : }
847 210 : }
848 210 : unset($postProc);
849 :
850 210 : if ($this->debug) echo 'COMPILATION COMPLETE : MEM USAGE : '.memory_get_usage().'<br>';
851 :
852 210 : $output = "<?php\n/* template head */\n";
853 :
854 : // build plugin preloader
855 210 : foreach ($this->usedPlugins as $plugin=>$type) {
856 48 : if ($type & Dwoo::CUSTOM_PLUGIN) {
857 16 : continue;
858 : }
859 :
860 : switch($type) {
861 :
862 32 : case Dwoo::BLOCK_PLUGIN:
863 32 : case Dwoo::CLASS_PLUGIN:
864 8 : $output .= "if (class_exists('Dwoo_Plugin_$plugin', false)===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
865 8 : break;
866 24 : case Dwoo::FUNC_PLUGIN:
867 24 : $output .= "if (function_exists('Dwoo_Plugin_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
868 24 : break;
869 0 : case Dwoo::SMARTY_MODIFIER:
870 0 : $output .= "if (function_exists('smarty_modifier_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
871 0 : break;
872 0 : case Dwoo::SMARTY_FUNCTION:
873 0 : $output .= "if (function_exists('smarty_function_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
874 0 : break;
875 0 : case Dwoo::SMARTY_BLOCK:
876 0 : $output .= "if (function_exists('smarty_block_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
877 0 : break;
878 0 : case Dwoo::PROXY_PLUGIN:
879 0 : $output .= $this->getDwoo()->getPluginProxy()->getPreloader($plugin);
880 0 : break;
881 0 : default:
882 0 : throw new Dwoo_Compilation_Exception($this, 'Type error for '.$plugin.' with type'.$type);
883 :
884 0 : }
885 210 : }
886 :
887 210 : foreach ($this->templatePlugins as $function => $attr) {
888 3 : if (isset($attr['called']) && $attr['called'] === true && !isset($attr['checked'])) {
889 3 : $this->resolveSubTemplateDependencies($function);
890 3 : }
891 210 : }
892 210 : foreach ($this->templatePlugins as $function) {
893 3 : if (isset($function['called']) && $function['called'] === true) {
894 3 : $output .= $function['body'].PHP_EOL;
895 3 : }
896 210 : }
897 :
898 210 : $output .= $compiled."\n?>";
899 :
900 210 : $output = preg_replace('/(?<!;|\}|\*\/|\n|\{)(\s*'.preg_quote(self::PHP_CLOSE, '/') . preg_quote(self::PHP_OPEN, '/').')/', ";\n", $output);
901 210 : $output = str_replace(self::PHP_CLOSE . self::PHP_OPEN, "\n", $output);
902 :
903 : // handle <?xml tag at the beginning
904 210 : $output = preg_replace('#(/\* template body \*/ \?>\s*)<\?xml#is', '$1<?php echo \'<?xml\'; ?>', $output);
905 :
906 210 : if ($this->debug) {
907 0 : echo '<hr><pre>';
908 0 : $lines = preg_split('{\r\n|\n|<br />}', highlight_string(($output), true));
909 0 : array_shift($lines);
910 0 : foreach ($lines as $i=>$line) {
911 0 : echo ($i+1).'. '.$line."\r\n";
912 0 : }
913 0 : }
914 210 : if ($this->debug) echo '<hr></pre></pre>';
915 :
916 210 : $this->template = $this->dwoo = null;
917 210 : $tpl = null;
918 :
919 210 : return $output;
920 : }
921 :
922 : /**
923 : * checks what sub-templates are used in every sub-template so that we're sure they are all compiled
924 : *
925 : * @param string $function the sub-template name
926 : */
927 : protected function resolveSubTemplateDependencies($function)
928 : {
929 3 : $body = $this->templatePlugins[$function]['body'];
930 3 : foreach ($this->templatePlugins as $func => $attr) {
931 3 : if ($func !== $function && !isset($attr['called']) && strpos($body, 'Dwoo_Plugin_'.$func) !== false) {
932 0 : $this->templatePlugins[$func]['called'] = true;
933 0 : $this->resolveSubTemplateDependencies($func);
934 0 : }
935 3 : }
936 3 : $this->templatePlugins[$function]['checked'] = true;
937 3 : }
938 :
939 : /**
940 : * adds compiled content to the current block
941 : *
942 : * @param string $content the content to push
943 : * @param int $lineCount newlines count in content, optional
944 : */
945 : public function push($content, $lineCount = null)
946 : {
947 219 : if ($lineCount === null) {
948 214 : $lineCount = substr_count($content, "\n");
949 214 : }
950 :
951 219 : if ($this->curBlock['buffer'] === null && count($this->stack) > 1) {
952 : // buffer is not initialized yet (the block has just been created)
953 71 : $this->stack[count($this->stack)-2]['buffer'] .= (string) $content;
954 71 : $this->curBlock['buffer'] = '';
955 71 : } else {
956 219 : if (!isset($this->curBlock['buffer'])) {
957 0 : throw new Dwoo_Compilation_Exception($this, 'The template has been closed too early, you probably have an extra block-closing tag somewhere');
958 : }
959 : // append current content to current block's buffer
960 219 : $this->curBlock['buffer'] .= (string) $content;
961 : }
962 219 : $this->line += $lineCount;
963 219 : }
964 :
965 : /**
966 : * sets the scope
967 : *
968 : * set to null if the scope becomes "unstable" (i.e. too variable or unknown) so that
969 : * variables are compiled in a more evaluative way than just $this->scope['key']
970 : *
971 : * @param mixed $scope a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
972 : * @param bool $absolute if true, the scope is set from the top level scope and not from the current scope
973 : * @return array the current scope tree
974 : */
975 : public function setScope($scope, $absolute = false)
976 : {
977 4 : $old = $this->scopeTree;
978 :
979 4 : if ($scope===null) {
980 0 : unset($this->scope);
981 0 : $this->scope = null;
982 0 : }
983 :
984 4 : if (is_array($scope)===false) {
985 4 : $scope = explode('.', $scope);
986 4 : }
987 :
988 4 : if ($absolute===true) {
989 0 : $this->scope =& $this->data;
990 0 : $this->scopeTree = array();
991 0 : }
992 :
993 4 : while (($bit = array_shift($scope)) !== null) {
994 4 : if ($bit === '_parent' || $bit === '_') {
995 0 : array_pop($this->scopeTree);
996 0 : reset($this->scopeTree);
997 0 : $this->scope =& $this->data;
998 0 : $cnt = count($this->scopeTree);
999 0 : for ($i=0;$i<$cnt;$i++)
1000 0 : $this->scope =& $this->scope[$this->scopeTree[$i]];
1001 4 : } elseif ($bit === '_root' || $bit === '__') {
1002 0 : $this->scope =& $this->data;
1003 0 : $this->scopeTree = array();
1004 4 : } elseif (isset($this->scope[$bit])) {
1005 0 : $this->scope =& $this->scope[$bit];
1006 0 : $this->scopeTree[] = $bit;
1007 0 : } else {
1008 4 : $this->scope[$bit] = array();
1009 4 : $this->scope =& $this->scope[$bit];
1010 4 : $this->scopeTree[] = $bit;
1011 : }
1012 4 : }
1013 :
1014 4 : return $old;
1015 : }
1016 :
1017 : /**
1018 : * adds a block to the top of the block stack
1019 : *
1020 : * @param string $type block type (name)
1021 : * @param array $params the parameters array
1022 : * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2
1023 : * @return string the preProcessing() method's output
1024 : */
1025 : public function addBlock($type, array $params, $paramtype)
1026 : {
1027 221 : $class = 'Dwoo_Plugin_'.$type;
1028 221 : if (class_exists($class, false) === false) {
1029 1 : $this->dwoo->getLoader()->loadPlugin($type);
1030 1 : }
1031 :
1032 221 : $params = $this->mapParams($params, array($class, 'init'), $paramtype);
1033 :
1034 221 : $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null);
1035 221 : $this->curBlock =& $this->stack[count($this->stack)-1];
1036 221 : return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type);
1037 : }
1038 :
1039 : /**
1040 : * adds a custom block to the top of the block stack
1041 : *
1042 : * @param string $type block type (name)
1043 : * @param array $params the parameters array
1044 : * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2
1045 : * @return string the preProcessing() method's output
1046 : */
1047 : public function addCustomBlock($type, array $params, $paramtype)
1048 : {
1049 2 : $callback = $this->customPlugins[$type]['callback'];
1050 2 : if (is_array($callback)) {
1051 1 : $class = is_object($callback[0]) ? get_class($callback[0]) : $callback[0];
1052 1 : } else {
1053 1 : $class = $callback;
1054 : }
1055 :
1056 2 : $params = $this->mapParams($params, array($class, 'init'), $paramtype);
1057 :
1058 2 : $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => true, 'class' => $class, 'buffer' => null);
1059 2 : $this->curBlock =& $this->stack[count($this->stack)-1];
1060 2 : return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type);
1061 : }
1062 :
1063 : /**
1064 : * injects a block at the top of the plugin stack without calling its preProcessing method
1065 : *
1066 : * used by {else} blocks to re-add themselves after having closed everything up to their parent
1067 : *
1068 : * @param string $type block type (name)
1069 : * @param array $params parameters array
1070 : */
1071 : public function injectBlock($type, array $params)
1072 : {
1073 18 : $class = 'Dwoo_Plugin_'.$type;
1074 18 : if (class_exists($class, false) === false) {
1075 0 : $this->dwoo->getLoader()->loadPlugin($type);
1076 0 : }
1077 18 : $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null);
1078 18 : $this->curBlock =& $this->stack[count($this->stack)-1];
1079 18 : }
1080 :
1081 : /**
1082 : * removes the closest-to-top block of the given type and all other
1083 : * blocks encountered while going down the block stack
1084 : *
1085 : * @param string $type block type (name)
1086 : * @return string the output of all postProcessing() method's return values of the closed blocks
1087 : */
1088 : public function removeBlock($type)
1089 : {
1090 212 : $output = '';
1091 :
1092 212 : $pluginType = $this->getPluginType($type);
1093 212 : if ($pluginType & Dwoo::SMARTY_BLOCK) {
1094 0 : $type = 'smartyinterface';
1095 0 : }
1096 212 : while (true) {
1097 212 : while ($top = array_pop($this->stack)) {
1098 212 : if ($top['custom']) {
1099 2 : $class = $top['class'];
1100 2 : } else {
1101 212 : $class = 'Dwoo_Plugin_'.$top['type'];
1102 : }
1103 212 : if (count($this->stack)) {
1104 66 : $this->curBlock =& $this->stack[count($this->stack)-1];
1105 66 : $this->push(call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']), 0);
1106 66 : } else {
1107 212 : $null = null;
1108 212 : $this->curBlock =& $null;
1109 212 : $output = call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']);
1110 : }
1111 :
1112 212 : if ($top['type'] === $type) {
1113 211 : break 2;
1114 : }
1115 21 : }
1116 :
1117 1 : throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of type "'.$type.'" was closed but was not opened');
1118 : break;
1119 : }
1120 :
1121 211 : return $output;
1122 : }
1123 :
1124 : /**
1125 : * returns a reference to the first block of the given type encountered and
1126 : * optionally closes all blocks until it finds it
1127 : *
1128 : * this is mainly used by {else} plugins to close everything that was opened
1129 : * between their parent and themselves
1130 : *
1131 : * @param string $type the block type (name)
1132 : * @param bool $closeAlong whether to close all blocks encountered while going down the block stack or not
1133 : * @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
1134 : * 'custom'=>bool defining whether it's a custom plugin or not, for internal use)
1135 : */
1136 : public function &findBlock($type, $closeAlong = false)
1137 : {
1138 4 : if ($closeAlong===true) {
1139 4 : while ($b = end($this->stack)) {
1140 4 : if ($b['type']===$type) {
1141 4 : return $this->stack[key($this->stack)];
1142 : }
1143 4 : $this->push($this->removeTopBlock(), 0);
1144 4 : }
1145 0 : } else {
1146 0 : end($this->stack);
1147 0 : while ($b = current($this->stack)) {
1148 0 : if ($b['type']===$type) {
1149 0 : return $this->stack[key($this->stack)];
1150 : }
1151 0 : prev($this->stack);
1152 0 : }
1153 : }
1154 :
1155 0 : throw new Dwoo_Compilation_Exception($this, 'A parent block of type "'.$type.'" is required and can not be found');
1156 : }
1157 :
1158 : /**
1159 : * returns a reference to the current block array
1160 : *
1161 : * @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
1162 : * 'custom'=>bool defining whether it's a custom plugin or not, for internal use)
1163 : */
1164 : public function &getCurrentBlock()
1165 : {
1166 40 : return $this->curBlock;
1167 : }
1168 :
1169 : /**
1170 : * removes the block at the top of the stack and calls its postProcessing() method
1171 : *
1172 : * @return string the postProcessing() method's output
1173 : */
1174 : public function removeTopBlock()
1175 : {
1176 24 : $o = array_pop($this->stack);
1177 24 : if ($o === null) {
1178 0 : throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of unknown type was closed but was not opened.');
1179 : }
1180 24 : if ($o['custom']) {
1181 0 : $class = $o['class'];
1182 0 : } else {
1183 24 : $class = 'Dwoo_Plugin_'.$o['type'];
1184 : }
1185 :
1186 24 : $this->curBlock =& $this->stack[count($this->stack)-1];
1187 :
1188 24 : return call_user_func(array($class, 'postProcessing'), $this, $o['params'], '', '', $o['buffer']);
1189 : }
1190 :
1191 : /**
1192 : * returns the compiled parameters (for example a variable's compiled parameter will be "$this->scope['key']") out of the given parameter array
1193 : *
1194 : * @param array $params parameter array
1195 : * @return array filtered parameters
1196 : */
1197 : public function getCompiledParams(array $params)
1198 : {
1199 85 : foreach ($params as $k=>$p) {
1200 85 : if (is_array($p)) {
1201 85 : $params[$k] = $p[0];
1202 85 : }
1203 85 : }
1204 85 : return $params;
1205 : }
1206 :
1207 : /**
1208 : * returns the real parameters (for example a variable's real parameter will be its key, etc) out of the given parameter array
1209 : *
1210 : * @param array $params parameter array
1211 : * @return array filtered parameters
1212 : */
1213 : public function getRealParams(array $params)
1214 : {
1215 4 : foreach ($params as $k=>$p) {
1216 4 : if (is_array($p)) {
1217 4 : $params[$k] = $p[1];
1218 4 : }
1219 4 : }
1220 4 : return $params;
1221 : }
1222 :
1223 : /**
1224 : * returns the token of each parameter out of the given parameter array
1225 : *
1226 : * @param array $params parameter array
1227 : * @return array tokens
1228 : */
1229 : public function getParamTokens(array $params)
1230 : {
1231 47 : foreach ($params as $k=>$p) {
1232 47 : if (is_array($p)) {
1233 47 : $params[$k] = isset($p[2]) ? $p[2] : 0;
1234 47 : }
1235 47 : }
1236 47 : return $params;
1237 : }
1238 :
1239 : /**
1240 : * entry point of the parser, it redirects calls to other parse* functions
1241 : *
1242 : * @param string $in the string within which we must parse something
1243 : * @param int $from the starting offset of the parsed area
1244 : * @param int $to the ending offset of the parsed area
1245 : * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1246 : * @param string $curBlock the current parser-block being processed
1247 : * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1248 : * @return string parsed values
1249 : */
1250 : protected function parse($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
1251 : {
1252 212 : if ($to === null) {
1253 212 : $to = strlen($in);
1254 212 : }
1255 212 : $first = substr($in, $from, 1);
1256 :
1257 212 : if ($first === false) {
1258 0 : throw new Dwoo_Compilation_Exception($this, 'Unexpected EOF, a template tag was not closed');
1259 : }
1260 :
1261 212 : while ($first===" " || $first==="\n" || $first==="\t" || $first==="\r") {
1262 8 : if ($curBlock === 'root' && substr($in, $from, strlen($this->rd)) === $this->rd) {
1263 : // end template tag
1264 1 : $pointer += strlen($this->rd);
1265 1 : if ($this->debug) echo 'TEMPLATE PARSING ENDED<br />';
1266 1 : return false;
1267 : }
1268 7 : $from++;
1269 7 : if ($pointer !== null) {
1270 7 : $pointer++;
1271 7 : }
1272 7 : if ($from >= $to) {
1273 0 : if (is_array($parsingParams)) {
1274 0 : return $parsingParams;
1275 : } else {
1276 0 : return '';
1277 : }
1278 : }
1279 7 : $first = $in[$from];
1280 7 : }
1281 :
1282 212 : $substr = substr($in, $from, $to-$from);
1283 :
1284 212 : if ($this->debug) echo '<br />PARSE CALL : PARSING "<b>'.htmlentities(substr($in, $from, min($to-$from, 50))).(($to-$from) > 50 ? '...':'').'</b>" @ '.$from.':'.$to.' in '.$curBlock.' : pointer='.$pointer.'<br/>';
1285 212 : $parsed = "";
1286 :
1287 212 : if ($curBlock === 'root' && $first === '*') {
1288 3 : $src = $this->getTemplateSource();
1289 3 : $startpos = $this->getPointer() - strlen($this->ld);
1290 3 : if (substr($src, $startpos, strlen($this->ld)) === $this->ld) {
1291 3 : if ($startpos > 0) {
1292 : do {
1293 1 : $char = substr($src, --$startpos, 1);
1294 1 : if ($char == "\n") {
1295 0 : $startpos++;
1296 0 : $whitespaceStart = true;
1297 0 : break;
1298 : }
1299 1 : } while ($startpos > 0 && ($char == ' ' || $char == "\t"));
1300 1 : }
1301 :
1302 3 : if (!isset($whitespaceStart)) {
1303 3 : $startpos = $this->getPointer();
1304 3 : } else {
1305 0 : $pointer -= $this->getPointer() - $startpos;
1306 : }
1307 :
1308 3 : if ($this->allowNestedComments && strpos($src, $this->ld.'*', $this->getPointer()) !== false) {
1309 1 : $comOpen = $this->ld.'*';
1310 1 : $comClose = '*'.$this->rd;
1311 1 : $level = 1;
1312 1 : $start = $startpos;
1313 1 : $ptr = $this->getPointer() + '*';
1314 :
1315 1 : while ($level > 0 && $ptr < strlen($src)) {
1316 1 : $open = strpos($src, $comOpen, $ptr);
1317 1 : $close = strpos($src, $comClose, $ptr);
1318 :
1319 1 : if ($open !== false && $close !== false) {
1320 1 : if ($open < $close) {
1321 1 : $ptr = $open + strlen($comOpen);
1322 1 : $level++;
1323 1 : } else {
1324 0 : $ptr = $close + strlen($comClose);
1325 0 : $level--;
1326 : }
1327 1 : } elseif ($open !== false) {
1328 0 : $ptr = $open + strlen($comOpen);
1329 0 : $level++;
1330 1 : } elseif ($close !== false) {
1331 1 : $ptr = $close + strlen($comClose);
1332 1 : $level--;
1333 1 : } else {
1334 0 : $ptr = strlen($src);
1335 : }
1336 1 : }
1337 1 : $endpos = $ptr - strlen('*'.$this->rd);
1338 1 : } else {
1339 2 : $endpos = strpos($src, '*'.$this->rd, $startpos);
1340 2 : if ($endpos == false) {
1341 0 : throw new Dwoo_Compilation_Exception($this, 'Un-ended comment');
1342 : }
1343 : }
1344 3 : $pointer += $endpos - $startpos + strlen('*'.$this->rd);
1345 3 : if (isset($whitespaceStart) && preg_match('#^[\t ]*\r?\n#', substr($src, $endpos+strlen('*'.$this->rd)), $m)) {
1346 0 : $pointer += strlen($m[0]);
1347 0 : $this->curBlock['buffer'] = substr($this->curBlock['buffer'], 0, strlen($this->curBlock['buffer']) - ($this->getPointer() - $startpos - strlen($this->ld)));
1348 0 : }
1349 3 : return false;
1350 : }
1351 0 : }
1352 :
1353 209 : if ($first==='$') {
1354 : // var
1355 130 : $out = $this->parseVar($in, $from, $to, $parsingParams, $curBlock, $pointer);
1356 130 : $parsed = 'var';
1357 209 : } elseif ($first==='%' && preg_match('#^%[a-z]#i', $substr)) {
1358 : // const
1359 2 : $out = $this->parseConst($in, $from, $to, $parsingParams, $curBlock, $pointer);
1360 209 : } elseif ($first==='"' || $first==="'") {
1361 : // string
1362 90 : $out = $this->parseString($in, $from, $to, $parsingParams, $curBlock, $pointer);
1363 208 : } elseif (preg_match('/^[a-z][a-z0-9_]*(?:::[a-z][a-z0-9_]*)?('.(is_array($parsingParams)||$curBlock!='root'?'':'\s+[^(]|').'\s*\(|\s*'.$this->rdr.'|\s*;)/i', $substr)) {
1364 : // func
1365 162 : $out = $this->parseFunction($in, $from, $to, $parsingParams, $curBlock, $pointer);
1366 160 : $parsed = 'func';
1367 207 : } elseif ($first === ';') {
1368 : // instruction end
1369 4 : if ($this->debug) echo 'END OF INSTRUCTION<br />';
1370 4 : if ($pointer !== null) {
1371 4 : $pointer++;
1372 4 : }
1373 4 : return $this->parse($in, $from+1, $to, false, 'root', $pointer);
1374 207 : } elseif ($curBlock === 'root' && preg_match('#^/([a-z][a-z0-9_]*)?#i', $substr, $match)) {
1375 : // close block
1376 71 : if (!empty($match[1]) && $match[1] == 'else') {
1377 0 : throw new Dwoo_Compilation_Exception($this, 'Else blocks must not be closed explicitly, they are automatically closed when their parent block is closed');
1378 : }
1379 71 : if (!empty($match[1]) && $match[1] == 'elseif') {
1380 0 : throw new Dwoo_Compilation_Exception($this, 'Elseif blocks must not be closed explicitly, they are automatically closed when their parent block is closed or a new else/elseif block is declared after them');
1381 : }
1382 71 : if ($pointer !== null) {
1383 71 : $pointer += strlen($match[0]);
1384 71 : }
1385 71 : if (empty($match[1])) {
1386 6 : if ($this->curBlock['type'] == 'else' || $this->curBlock['type'] == 'elseif') {
1387 0 : $pointer -= strlen($match[0]);
1388 0 : }
1389 6 : if ($this->debug) echo 'TOP BLOCK CLOSED<br />';
1390 6 : return $this->removeTopBlock();
1391 : } else {
1392 65 : if ($this->debug) echo 'BLOCK OF TYPE '.$match[1].' CLOSED<br />';
1393 65 : return $this->removeBlock($match[1]);
1394 : }
1395 206 : } elseif ($curBlock === 'root' && substr($substr, 0, strlen($this->rd)) === $this->rd) {
1396 : // end template tag
1397 203 : if ($this->debug) echo 'TAG PARSING ENDED<br />';
1398 203 : $pointer += strlen($this->rd);
1399 203 : return false;
1400 107 : } elseif (is_array($parsingParams) && preg_match('#^([a-z0-9_]+\s*=)(?:\s+|[^=]).*#i', $substr, $match)) {
1401 : // named parameter
1402 37 : if ($this->debug) echo 'NAMED PARAM FOUND<br />';
1403 37 : $len = strlen($match[1]);
1404 37 : while (substr($in, $from+$len, 1)===' ') {
1405 0 : $len++;
1406 0 : }
1407 37 : if ($pointer !== null) {
1408 37 : $pointer += $len;
1409 37 : }
1410 :
1411 37 : $output = array(trim(substr(trim($match[1]), 0, -1)), $this->parse($in, $from+$len, $to, false, 'namedparam', $pointer));
1412 :
1413 37 : $parsingParams[] = $output;
1414 37 : return $parsingParams;
1415 96 : } elseif (preg_match('#^([a-z0-9_]+::\$[a-z0-9_]+)#i', $substr, $match)) {
1416 : // static member access
1417 1 : $parsed = 'var';
1418 1 : if (is_array($parsingParams)) {
1419 1 : $parsingParams[] = array($match[1], $match[1]);
1420 1 : $out = $parsingParams;
1421 1 : } else {
1422 1 : $out = $match[1];
1423 : }
1424 1 : $pointer += strlen($match[1]);
1425 96 : } elseif ($substr!=='' && (is_array($parsingParams) || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'expression')) {
1426 : // unquoted string, bool or number
1427 95 : $out = $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1428 95 : } else {
1429 : // parse error
1430 1 : throw new Dwoo_Compilation_Exception($this, 'Parse error in "'.substr($in, $from, $to-$from).'"');
1431 : }
1432 :
1433 205 : if (empty($out)) {
1434 67 : return '';
1435 : }
1436 :
1437 204 : $substr = substr($in, $pointer, $to-$pointer);
1438 :
1439 : // var parsed, check if any var-extension applies
1440 204 : if ($parsed==='var') {
1441 131 : if (preg_match('#^\s*([/%+*-])\s*([a-z0-9]|\$)#i', $substr, $match)) {
1442 4 : if($this->debug) echo 'PARSING POST-VAR EXPRESSION '.$substr.'<br />';
1443 : // parse expressions
1444 4 : $pointer += strlen($match[0]) - 1;
1445 4 : if (is_array($parsingParams)) {
1446 1 : if ($match[2] == '$') {
1447 0 : $expr = $this->parseVar($in, $pointer, $to, array(), $curBlock, $pointer);
1448 0 : } else {
1449 1 : $expr = $this->parse($in, $pointer, $to, array(), 'expression', $pointer);
1450 : }
1451 1 : $out[count($out)-1][0] .= $match[1] . $expr[0][0];
1452 1 : $out[count($out)-1][1] .= $match[1] . $expr[0][1];
1453 1 : } else {
1454 3 : if ($match[2] == '$') {
1455 2 : $expr = $this->parseVar($in, $pointer, $to, false, $curBlock, $pointer);
1456 2 : } else {
1457 2 : $expr = $this->parse($in, $pointer, $to, false, 'expression', $pointer);
1458 : }
1459 3 : if (is_array($out) && is_array($expr)) {
1460 1 : $out[0] .= $match[1] . $expr[0];
1461 1 : $out[1] .= $match[1] . $expr[1];
1462 3 : } elseif (is_array($out)) {
1463 0 : $out[0] .= $match[1] . $expr;
1464 0 : $out[1] .= $match[1] . $expr;
1465 2 : } elseif (is_array($expr)) {
1466 0 : $out .= $match[1] . $expr[0];
1467 0 : } else {
1468 2 : $out .= $match[1] . $expr;
1469 : }
1470 : }
1471 131 : } else if ($curBlock === 'root' && preg_match('#^(\s*(?:[+/*%-.]=|=|\+\+|--)\s*)(.*)#s', $substr, $match)) {
1472 19 : if($this->debug) echo 'PARSING POST-VAR ASSIGNMENT '.$substr.'<br />';
1473 : // parse assignment
1474 19 : $value = $match[2];
1475 19 : $operator = trim($match[1]);
1476 19 : if (substr($value, 0, 1) == '=') {
1477 0 : throw new Dwoo_Compilation_Exception($this, 'Unexpected "=" in <em>'.$substr.'</em>');
1478 : }
1479 :
1480 19 : if ($pointer !== null) {
1481 19 : $pointer += strlen($match[1]);
1482 19 : }
1483 :
1484 19 : if ($operator !== '++' && $operator !== '--') {
1485 18 : $parts = array();
1486 18 : $ptr = 0;
1487 18 : $parts = $this->parse($value, 0, strlen($value), $parts, 'condition', $ptr);
1488 18 : $pointer += $ptr;
1489 :
1490 : // load if plugin
1491 : try {
1492 18 : $this->getPluginType('if');
1493 18 : } catch (Dwoo_Exception $e) {
1494 0 : throw new Dwoo_Compilation_Exception($this, 'Assignments require the "if" plugin to be accessible');
1495 : }
1496 :
1497 18 : $parts = $this->mapParams($parts, array('Dwoo_Plugin_if', 'init'), 1);
1498 18 : $tokens = $this->getParamTokens($parts);
1499 18 : $parts = $this->getCompiledParams($parts);
1500 :
1501 18 : $value = Dwoo_Plugin_if::replaceKeywords($parts['*'], $tokens['*'], $this);
1502 18 : $echo = '';
1503 18 : } else {
1504 1 : $value = array();
1505 1 : $echo = 'echo ';
1506 : }
1507 :
1508 19 : if ($this->autoEscape) {
1509 1 : $out = preg_replace('#\(is_string\(\$tmp=(.+?)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $out);
1510 1 : }
1511 19 : $out = Dwoo_Compiler::PHP_OPEN. $echo . $out . $operator . implode(' ', $value) . Dwoo_Compiler::PHP_CLOSE;
1512 19 : }
1513 131 : }
1514 :
1515 204 : if ($curBlock !== 'modifier' && ($parsed === 'func' || $parsed === 'var') && preg_match('#^\|@?[a-z0-9_]+(:.*)?#i', $substr, $match)) {
1516 : // parse modifier on funcs or vars
1517 1 : $srcPointer = $pointer;
1518 1 : if (is_array($parsingParams)) {
1519 0 : $tmp = $this->replaceModifiers(array(null, null, $out[count($out)-1][0], $match[0]), 'var', $pointer);
1520 0 : $out[count($out)-1][0] = $tmp;
1521 0 : $out[count($out)-1][1] .= substr($substr, $srcPointer, $srcPointer - $pointer);
1522 0 : } else {
1523 1 : $out = $this->replaceModifiers(array(null, null, $out, $match[0]), 'var', $pointer);
1524 : }
1525 1 : }
1526 :
1527 : // func parsed, check if any func-extension applies
1528 204 : if ($parsed==='func' && preg_match('#^->[a-z0-9_]+(\s*\(.+|->[a-z].*)?#is', $substr, $match)) {
1529 : // parse method call or property read
1530 3 : $ptr = 0;
1531 :
1532 3 : if (is_array($parsingParams)) {
1533 1 : $output = $this->parseMethodCall($out[count($out)-1][1], $match[0], $curBlock, $ptr);
1534 :
1535 1 : $out[count($out)-1][0] = $output;
1536 1 : $out[count($out)-1][1] .= substr($match[0], 0, $ptr);
1537 1 : } else {
1538 2 : $out = $this->parseMethodCall($out, $match[0], $curBlock, $ptr);
1539 : }
1540 :
1541 3 : $pointer += $ptr;
1542 3 : }
1543 :
1544 204 : if ($curBlock === 'root' && substr($out, 0, strlen(self::PHP_OPEN)) !== self::PHP_OPEN) {
1545 172 : return self::PHP_OPEN .'echo '.$out.';'. self::PHP_CLOSE;
1546 : } else {
1547 177 : return $out;
1548 : }
1549 : }
1550 :
1551 : /**
1552 : * parses a function call
1553 : *
1554 : * @param string $in the string within which we must parse something
1555 : * @param int $from the starting offset of the parsed area
1556 : * @param int $to the ending offset of the parsed area
1557 : * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1558 : * @param string $curBlock the current parser-block being processed
1559 : * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1560 : * @return string parsed values
1561 : */
1562 : protected function parseFunction($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
1563 : {
1564 163 : $cmdstr = substr($in, $from, $to-$from);
1565 163 : preg_match('/^([a-z][a-z0-9_]*(?:::[a-z][a-z0-9_]*)?)(\s*'.$this->rdr.'|\s*;)?/i', $cmdstr, $match);
1566 :
1567 163 : if (empty($match[1])) {
1568 0 : throw new Dwoo_Compilation_Exception($this, 'Parse error, invalid function name : '.substr($cmdstr, 0, 15));
1569 : }
1570 :
1571 163 : $func = $match[1];
1572 :
1573 163 : if (!empty($match[2])) {
1574 60 : $cmdstr = $match[1];
1575 60 : }
1576 :
1577 163 : if ($this->debug) echo 'FUNC FOUND ('.$func.')<br />';
1578 :
1579 163 : $paramsep = '';
1580 :
1581 163 : if (is_array($parsingParams) || $curBlock != 'root') {
1582 65 : $paramspos = strpos($cmdstr, '(');
1583 65 : $paramsep = ')';
1584 163 : } elseif(preg_match_all('#[a-z0-9_]+(\s*\(|\s+[^(])#i', $cmdstr, $match, PREG_OFFSET_CAPTURE)) {
1585 152 : $paramspos = $match[1][0][1];
1586 152 : $paramsep = substr($match[1][0][0], -1) === '(' ? ')':'';
1587 152 : if($paramsep === ')') {
1588 15 : $paramspos += strlen($match[1][0][0]) - 1;
1589 15 : if(substr($cmdstr, 0, 2) === 'if' || substr($cmdstr, 0, 6) === 'elseif') {
1590 3 : $paramsep = '';
1591 3 : if(strlen($match[1][0][0]) > 1) {
1592 3 : $paramspos--;
1593 3 : }
1594 3 : }
1595 15 : }
1596 152 : } else {
1597 23 : $paramspos = false;
1598 : }
1599 :
1600 163 : $state = 0;
1601 :
1602 163 : if ($paramspos === false) {
1603 60 : $params = array();
1604 :
1605 60 : if ($curBlock !== 'root') {
1606 43 : return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1607 : }
1608 23 : } else {
1609 157 : if ($curBlock === 'condition') {
1610 : // load if plugin
1611 12 : $this->getPluginType('if');
1612 :
1613 12 : if (Dwoo_Plugin_if::replaceKeywords(array($func), array(self::T_UNQUOTED_STRING), $this) !== array($func)) {
1614 1 : return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1615 : }
1616 11 : }
1617 157 : $whitespace = strlen(substr($cmdstr, strlen($func), $paramspos-strlen($func)));
1618 157 : $paramstr = substr($cmdstr, $paramspos+1);
1619 157 : if (substr($paramstr, -1, 1) === $paramsep) {
1620 1 : $paramstr = substr($paramstr, 0, -1);
1621 1 : }
1622 :
1623 157 : if (strlen($paramstr)===0) {
1624 0 : $params = array();
1625 0 : $paramstr = '';
1626 0 : } else {
1627 157 : $ptr = 0;
1628 157 : $params = array();
1629 157 : if ($func === 'empty') {
1630 0 : $params = $this->parseVar($paramstr, $ptr, strlen($paramstr), $params, 'root', $ptr);
1631 0 : } else {
1632 157 : while ($ptr < strlen($paramstr)) {
1633 157 : while (true) {
1634 157 : if ($ptr >= strlen($paramstr)) {
1635 0 : break 2;
1636 : }
1637 :
1638 157 : if ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === ')') {
1639 41 : if ($this->debug) echo 'PARAM PARSING ENDED, ")" FOUND, POINTER AT '.$ptr.'<br/>';
1640 41 : break 2;
1641 156 : } elseif ($paramstr[$ptr] === ';') {
1642 1 : $ptr++;
1643 1 : if ($this->debug) echo 'PARAM PARSING ENDED, ";" FOUND, POINTER AT '.$ptr.'<br/>';
1644 1 : break 2;
1645 156 : } elseif ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === '/') {
1646 1 : if ($this->debug) echo 'PARAM PARSING ENDED, "/" FOUND, POINTER AT '.$ptr.'<br/>';
1647 1 : break 2;
1648 156 : } elseif (substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
1649 140 : if ($this->debug) echo 'PARAM PARSING ENDED, RIGHT DELIMITER FOUND, POINTER AT '.$ptr.'<br/>';
1650 140 : break 2;
1651 : }
1652 :
1653 156 : if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === ',' || $paramstr[$ptr] === "\r" || $paramstr[$ptr] === "\n" || $paramstr[$ptr] === "\t") {
1654 102 : $ptr++;
1655 102 : } else {
1656 155 : break;
1657 : }
1658 102 : }
1659 :
1660 155 : if ($this->debug) echo 'FUNC START PARAM PARSING WITH POINTER AT '.$ptr.'<br/>';
1661 :
1662 155 : if ($func === 'if' || $func === 'elseif' || $func === 'tif') {
1663 31 : $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'condition', $ptr);
1664 31 : } else {
1665 137 : $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'function', $ptr);
1666 : }
1667 :
1668 155 : if ($this->debug) echo 'PARAM PARSED, POINTER AT '.$ptr.' ('.substr($paramstr, $ptr-1, 3).')<br/>';
1669 155 : }
1670 : }
1671 157 : $paramstr = substr($paramstr, 0, $ptr);
1672 157 : $state = 0;
1673 157 : foreach ($params as $k=>$p) {
1674 155 : if (is_array($p) && is_array($p[1])) {
1675 37 : $state |= 2;
1676 37 : } else {
1677 135 : if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) {
1678 1 : $params[$k] = array($m[2], array('true', 'true'));
1679 1 : } else {
1680 134 : if ($state & 2) {
1681 1 : throw new Dwoo_Compilation_Exception($this, 'You can not use an unnamed parameter after a named one');
1682 : }
1683 133 : $state |= 1;
1684 : }
1685 : }
1686 157 : }
1687 : }
1688 : }
1689 :
1690 162 : if ($pointer !== null) {
1691 162 : $pointer += (isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func) + (isset($whitespace) ? $whitespace : 0);
1692 162 : if ($this->debug) echo 'FUNC ADDS '.((isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func)).' TO POINTER<br/>';
1693 162 : }
1694 :
1695 162 : if ($curBlock === 'method' || $func === 'do' || strstr($func, '::') !== false) {
1696 10 : $pluginType = Dwoo::NATIVE_PLUGIN;
1697 10 : } else {
1698 160 : $pluginType = $this->getPluginType($func);
1699 : }
1700 :
1701 : // blocks
1702 162 : if ($pluginType & Dwoo::BLOCK_PLUGIN) {
1703 72 : if ($curBlock !== 'root' || is_array($parsingParams)) {
1704 0 : throw new Dwoo_Compilation_Exception($this, 'Block plugins can not be used as other plugin\'s arguments');
1705 : }
1706 72 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1707 2 : return $this->addCustomBlock($func, $params, $state);
1708 : } else {
1709 70 : return $this->addBlock($func, $params, $state);
1710 : }
1711 107 : } elseif ($pluginType & Dwoo::SMARTY_BLOCK) {
1712 0 : if ($curBlock !== 'root' || is_array($parsingParams)) {
1713 0 : throw new Dwoo_Compilation_Exception($this, 'Block plugins can not be used as other plugin\'s arguments');
1714 : }
1715 :
1716 0 : if ($state & 2) {
1717 0 : array_unshift($params, array('__functype', array($pluginType, $pluginType)));
1718 0 : array_unshift($params, array('__funcname', array($func, $func)));
1719 0 : } else {
1720 0 : array_unshift($params, array($pluginType, $pluginType));
1721 0 : array_unshift($params, array($func, $func));
1722 : }
1723 :
1724 0 : return $this->addBlock('smartyinterface', $params, $state);
1725 : }
1726 :
1727 : // funcs
1728 107 : if ($pluginType & Dwoo::NATIVE_PLUGIN || $pluginType & Dwoo::SMARTY_FUNCTION || $pluginType & Dwoo::SMARTY_BLOCK) {
1729 17 : $params = $this->mapParams($params, null, $state);
1730 107 : } elseif ($pluginType & Dwoo::CLASS_PLUGIN) {
1731 19 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1732 10 : $params = $this->mapParams($params, array($this->customPlugins[$func]['class'], $this->customPlugins[$func]['function']), $state);
1733 10 : } else {
1734 9 : $params = $this->mapParams($params, array('Dwoo_Plugin_'.$func, ($pluginType & Dwoo::COMPILABLE_PLUGIN) ? 'compile' : 'process'), $state);
1735 : }
1736 101 : } elseif ($pluginType & Dwoo::FUNC_PLUGIN) {
1737 84 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1738 4 : $params = $this->mapParams($params, $this->customPlugins[$func]['callback'], $state);
1739 4 : } else {
1740 80 : $params = $this->mapParams($params, 'Dwoo_Plugin_'.$func.(($pluginType & Dwoo::COMPILABLE_PLUGIN) ? '_compile' : ''), $state);
1741 : }
1742 84 : } elseif ($pluginType & Dwoo::SMARTY_MODIFIER) {
1743 0 : $output = 'smarty_modifier_'.$func.'('.implode(', ', $params).')';
1744 6 : } elseif ($pluginType & Dwoo::PROXY_PLUGIN) {
1745 3 : $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
1746 6 : } elseif ($pluginType & Dwoo::TEMPLATE_PLUGIN) {
1747 : // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
1748 3 : $map = array();
1749 3 : foreach ($this->templatePlugins[$func]['params'] as $param=>$defValue) {
1750 3 : if ($param == 'rest') {
1751 0 : $param = '*';
1752 0 : }
1753 3 : $hasDefault = $defValue !== null;
1754 3 : if ($defValue === 'null') {
1755 0 : $defValue = null;
1756 3 : } elseif ($defValue === 'false') {
1757 0 : $defValue = false;
1758 3 : } elseif ($defValue === 'true') {
1759 0 : $defValue = true;
1760 3 : } elseif (preg_match('#^([\'"]).*?\1$#', $defValue)) {
1761 0 : $defValue = substr($defValue, 1, -1);
1762 0 : }
1763 3 : $map[] = array($param, $hasDefault, $defValue);
1764 3 : }
1765 :
1766 3 : $params = $this->mapParams($params, null, $state, $map);
1767 3 : }
1768 :
1769 : // only keep php-syntax-safe values for non-block plugins
1770 105 : $tokens = array();
1771 105 : foreach ($params as $k => $p) {
1772 100 : $tokens[$k] = isset($p[2]) ? $p[2] : 0;
1773 100 : $params[$k] = $p[0];
1774 105 : }
1775 105 : if ($pluginType & Dwoo::NATIVE_PLUGIN) {
1776 17 : if ($func === 'do') {
1777 5 : if (isset($params['*'])) {
1778 4 : $output = implode(';', $params['*']).';';
1779 4 : } else {
1780 1 : $output = '';
1781 : }
1782 :
1783 5 : if (is_array($parsingParams) || $curBlock !== 'root') {
1784 0 : throw new Dwoo_Compilation_Exception($this, 'Do can not be used inside another function or block');
1785 : } else {
1786 5 : return self::PHP_OPEN.$output.self::PHP_CLOSE;
1787 : }
1788 : } else {
1789 12 : if (isset($params['*'])) {
1790 12 : $output = $func.'('.implode(', ', $params['*']).')';
1791 12 : } else {
1792 0 : $output = $func.'()';
1793 : }
1794 : }
1795 104 : } elseif ($pluginType & Dwoo::FUNC_PLUGIN) {
1796 82 : if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
1797 64 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1798 1 : $funcCompiler = $this->customPlugins[$func]['callback'];
1799 1 : } else {
1800 63 : $funcCompiler = 'Dwoo_Plugin_'.$func.'_compile';
1801 : }
1802 64 : array_unshift($params, $this);
1803 64 : if ($func === 'tif') {
1804 1 : $params[] = $tokens;
1805 1 : }
1806 64 : $output = call_user_func_array($funcCompiler, $params);
1807 64 : } else {
1808 24 : array_unshift($params, '$this');
1809 24 : $params = self::implode_r($params);
1810 :
1811 24 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1812 3 : $callback = $this->customPlugins[$func]['callback'];
1813 3 : $output = 'call_user_func(\''.$callback.'\', '.$params.')';
1814 3 : } else {
1815 21 : $output = 'Dwoo_Plugin_'.$func.'('.$params.')';
1816 : }
1817 : }
1818 99 : } elseif ($pluginType & Dwoo::CLASS_PLUGIN) {
1819 19 : if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
1820 6 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1821 2 : $callback = $this->customPlugins[$func]['callback'];
1822 2 : if (!is_array($callback)) {
1823 0 : if (!method_exists($callback, 'compile')) {
1824 0 : throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use');
1825 : }
1826 0 : if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
1827 0 : $funcCompiler = array($callback, 'compile');
1828 0 : } else {
1829 0 : $funcCompiler = array(new $callback, 'compile');
1830 : }
1831 0 : } else {
1832 2 : $funcCompiler = $callback;
1833 : }
1834 2 : } else {
1835 4 : $funcCompiler = array('Dwoo_Plugin_'.$func, 'compile');
1836 4 : array_unshift($params, $this);
1837 : }
1838 6 : $output = call_user_func_array($funcCompiler, $params);
1839 6 : } else {
1840 13 : $params = self::implode_r($params);
1841 13 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1842 8 : $callback = $this->customPlugins[$func]['callback'];
1843 8 : if (!is_array($callback)) {
1844 1 : if (!method_exists($callback, 'process')) {
1845 0 : throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "process" method to be usable, or you should provide a full callback to the method to use');
1846 : }
1847 1 : if (($ref = new ReflectionMethod($callback, 'process')) && $ref->isStatic()) {
1848 0 : $output = 'call_user_func(array(\''.$callback.'\', \'process\'), '.$params.')';
1849 0 : } else {
1850 1 : $output = 'call_user_func(array($this->getObjectPlugin(\''.$callback.'\'), \'process\'), '.$params.')';
1851 : }
1852 8 : } elseif (is_object($callback[0])) {
1853 4 : $output = 'call_user_func(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), '.$params.')';
1854 7 : } elseif (($ref = new ReflectionMethod($callback[0], $callback[1])) && $ref->isStatic()) {
1855 1 : $output = 'call_user_func(array(\''.$callback[0].'\', \''.$callback[1].'\'), '.$params.')';
1856 1 : } else {
1857 2 : $output = 'call_user_func(array($this->getObjectPlugin(\''.$callback[0].'\'), \''.$callback[1].'\'), '.$params.')';
1858 : }
1859 8 : if (empty($params)) {
1860 2 : $output = substr($output, 0, -3).')';
1861 2 : }
1862 8 : } else {
1863 5 : $output = '$this->classCall(\''.$func.'\', array('.$params.'))';
1864 : }
1865 : }
1866 25 : } elseif ($pluginType & Dwoo::PROXY_PLUGIN) {
1867 3 : $output = call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params);
1868 6 : } elseif ($pluginType & Dwoo::SMARTY_FUNCTION) {
1869 0 : if (isset($params['*'])) {
1870 0 : $params = self::implode_r($params['*'], true);
1871 0 : } else {
1872 0 : $params = '';
1873 : }
1874 :
1875 0 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
1876 0 : $callback = $this->customPlugins[$func]['callback'];
1877 0 : if (is_array($callback)) {
1878 0 : if (is_object($callback[0])) {
1879 0 : $output = 'call_user_func_array(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array(array('.$params.'), $this))';
1880 0 : } else {
1881 0 : $output = 'call_user_func_array(array(\''.$callback[0].'\', \''.$callback[1].'\'), array(array('.$params.'), $this))';
1882 : }
1883 0 : } else {
1884 0 : $output = $callback.'(array('.$params.'), $this)';
1885 : }
1886 0 : } else {
1887 0 : $output = 'smarty_function_'.$func.'(array('.$params.'), $this)';
1888 : }
1889 3 : } elseif ($pluginType & Dwoo::TEMPLATE_PLUGIN) {
1890 3 : array_unshift($params, '$this');
1891 3 : $params = self::implode_r($params);
1892 3 : $output = 'Dwoo_Plugin_'.$func.'_'.$this->templatePlugins[$func]['uuid'].'('.$params.')';
1893 3 : $this->templatePlugins[$func]['called'] = true;
1894 3 : }
1895 :
1896 104 : if (is_array($parsingParams)) {
1897 27 : $parsingParams[] = array($output, $output);
1898 27 : return $parsingParams;
1899 93 : } elseif ($curBlock === 'namedparam') {
1900 4 : return array($output, $output);
1901 : } else {
1902 91 : return $output;
1903 : }
1904 : }
1905 :
1906 : /**
1907 : * parses a string
1908 : *
1909 : * @param string $in the string within which we must parse something
1910 : * @param int $from the starting offset of the parsed area
1911 : * @param int $to the ending offset of the parsed area
1912 : * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1913 : * @param string $curBlock the current parser-block being processed
1914 : * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1915 : * @return string parsed values
1916 : */
1917 : protected function parseString($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
1918 : {
1919 90 : $substr = substr($in, $from, $to-$from);
1920 90 : $first = $substr[0];
1921 :
1922 90 : if ($this->debug) echo 'STRING FOUND (in '.htmlentities(substr($in, $from, min($to-$from, 50))).(($to-$from) > 50 ? '...':'').')<br />';
1923 90 : $strend = false;
1924 90 : $o = $from+1;
1925 90 : while ($strend === false) {
1926 90 : $strend = strpos($in, $first, $o);
1927 90 : if ($strend === false) {
1928 1 : throw new Dwoo_Compilation_Exception($this, 'Unfinished string, started with '.substr($in, $from, $to-$from));
1929 : }
1930 89 : if (substr($in, $strend-1, 1) === '\\') {
1931 3 : $o = $strend+1;
1932 3 : $strend = false;
1933 3 : }
1934 89 : }
1935 89 : if ($this->debug) echo 'STRING DELIMITED: '.substr($in, $from, $strend+1-$from).'<br/>';
1936 :
1937 89 : $srcOutput = substr($in, $from, $strend+1-$from);
1938 :
1939 89 : if ($pointer !== null) {
1940 89 : $pointer += strlen($srcOutput);
1941 89 : }
1942 :
1943 89 : $output = $this->replaceStringVars($srcOutput, $first);
1944 :
1945 : // handle modifiers
1946 89 : if ($curBlock !== 'modifier' && preg_match('#^((?:\|(?:@?[a-z0-9_]+(?::.*)*))+)#i', substr($substr, $strend+1-$from), $match)) {
1947 6 : $modstr = $match[1];
1948 :
1949 6 : if ($curBlock === 'root' && substr($modstr, -1) === '}') {
1950 2 : $modstr = substr($modstr, 0, -1);
1951 2 : }
1952 6 : $modstr = str_replace('\\'.$first, $first, $modstr);
1953 6 : $ptr = 0;
1954 6 : $output = $this->replaceModifiers(array(null, null, $output, $modstr), 'string', $ptr);
1955 :
1956 6 : $strend += $ptr;
1957 6 : if ($pointer !== null) {
1958 6 : $pointer += $ptr;
1959 6 : }
1960 6 : $srcOutput .= substr($substr, $strend+1-$from, $ptr);
1961 6 : }
1962 :
1963 89 : if (is_array($parsingParams)) {
1964 72 : $parsingParams[] = array($output, substr($srcOutput, 1, -1));
1965 72 : return $parsingParams;
1966 25 : } elseif ($curBlock === 'namedparam') {
1967 16 : return array($output, substr($srcOutput, 1, -1));
1968 : } else {
1969 10 : return $output;
1970 : }
1971 : }
1972 :
1973 : /**
1974 : * parses a constant
1975 : *
1976 : * @param string $in the string within which we must parse something
1977 : * @param int $from the starting offset of the parsed area
1978 : * @param int $to the ending offset of the parsed area
1979 : * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1980 : * @param string $curBlock the current parser-block being processed
1981 : * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1982 : * @return string parsed values
1983 : */
1984 : protected function parseConst($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
1985 : {
1986 2 : $substr = substr($in, $from, $to-$from);
1987 :
1988 2 : if ($this->debug) {
1989 0 : echo 'CONST FOUND : '.$substr.'<br />';
1990 0 : }
1991 :
1992 2 : if (!preg_match('#^%([a-z0-9_:]+)#i', $substr, $m)) {
1993 0 : throw new Dwoo_Compilation_Exception($this, 'Invalid constant');
1994 : }
1995 :
1996 2 : if ($pointer !== null) {
1997 2 : $pointer += strlen($m[0]);
1998 2 : }
1999 :
2000 2 : $output = $this->parseConstKey($m[1], $curBlock);
2001 :
2002 2 : if (is_array($parsingParams)) {
2003 1 : $parsingParams[] = array($output, $m[1]);
2004 1 : return $parsingParams;
2005 2 : } elseif ($curBlock === 'namedparam') {
2006 0 : return array($output, $m[1]);
2007 : } else {
2008 2 : return $output;
2009 : }
2010 : }
2011 :
2012 : /**
2013 : * parses a constant
2014 : *
2015 : * @param string $key the constant to parse
2016 : * @param string $curBlock the current parser-block being processed
2017 : * @return string parsed constant
2018 : */
2019 : protected function parseConstKey($key, $curBlock)
2020 : {
2021 4 : if ($this->securityPolicy !== null && $this->securityPolicy->getConstantHandling() === Dwoo_Security_Policy::CONST_DISALLOW) {
2022 1 : return 'null';
2023 : }
2024 :
2025 4 : if ($curBlock !== 'root') {
2026 3 : $output = '(defined("'.$key.'") ? '.$key.' : null)';
2027 3 : } else {
2028 4 : $output = $key;
2029 : }
2030 :
2031 4 : return $output;
2032 : }
2033 :
2034 : /**
2035 : * parses a variable
2036 : *
2037 : * @param string $in the string within which we must parse something
2038 : * @param int $from the starting offset of the parsed area
2039 : * @param int $to the ending offset of the parsed area
2040 : * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
2041 : * @param string $curBlock the current parser-block being processed
2042 : * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
2043 : * @return string parsed values
2044 : */
2045 : protected function parseVar($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
2046 : {
2047 130 : $substr = substr($in, $from, $to-$from);
2048 :
2049 130 : if (preg_match('#(\$?\.?[a-z0-9_:]*(?:(?:(?:\.|->)(?:[a-z0-9_:]+|(?R))|\[(?:[a-z0-9_:]+|(?R)|(["\'])[^\2]*?\2)\]))*)' . // var key
2050 130 : ($curBlock==='root' || $curBlock==='function' || $curBlock==='namedparam' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='expression' ? '(\(.*)?' : '()') . // method call
2051 130 : ($curBlock==='root' || $curBlock==='function' || $curBlock==='namedparam' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='delimited_string' ? '((?:(?:[+/*%=-])(?:(?<!=)=?-?[$%][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|(?<!=)=?-?[0-9.,]*|[+-]))*)':'()') . // simple math expressions
2052 130 : ($curBlock!=='modifier' ? '((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').*?\5|:[^`]*))*))+)?':'(())') . // modifiers
2053 130 : '#i', $substr, $match)) {
2054 130 : $key = substr($match[1], 1);
2055 :
2056 130 : $matchedLength = strlen($match[0]);
2057 130 : $hasModifiers = !empty($match[5]);
2058 130 : $hasExpression = !empty($match[4]);
2059 130 : $hasMethodCall = !empty($match[3]);
2060 :
2061 130 : if (substr($key, -1) == ".") {
2062 1 : $key = substr($key, 0, -1);
2063 1 : $matchedLength--;
2064 1 : }
2065 :
2066 130 : if ($hasMethodCall) {
2067 1 : $matchedLength -= strlen($match[3]) + strlen(substr($match[1], strrpos($match[1], '->')));
2068 1 : $key = substr($match[1], 1, strrpos($match[1], '->')-1);
2069 1 : $methodCall = substr($match[1], strrpos($match[1], '->')) . $match[3];
2070 1 : }
2071 :
2072 130 : if ($hasModifiers) {
2073 28 : $matchedLength -= strlen($match[5]);
2074 28 : }
2075 :
2076 130 : if ($pointer !== null) {
2077 125 : $pointer += $matchedLength;
2078 125 : }
2079 :
2080 : // replace useless brackets by dot accessed vars
2081 130 : $key = preg_replace('#\[([^$%\[.>-]+)\]#', '.$1', $key);
2082 :
2083 : // prevent $foo->$bar calls because it doesn't seem worth the trouble
2084 130 : if (strpos($key, '->$') !== false) {
2085 0 : throw new Dwoo_Compilation_Exception($this, 'You can not access an object\'s property using a variable name.');
2086 : }
2087 :
2088 130 : if ($this->debug) {
2089 0 : if ($hasMethodCall) {
2090 0 : echo 'METHOD CALL FOUND : $'.$key.substr($methodCall, 0, 30).'<br />';
2091 0 : } else {
2092 0 : echo 'VAR FOUND : $'.$key.'<br />';
2093 : }
2094 0 : }
2095 :
2096 130 : $key = str_replace('"', '\\"', $key);
2097 :
2098 130 : $cnt=substr_count($key, '$');
2099 130 : if ($cnt > 0) {
2100 8 : $uid = 0;
2101 8 : $parsed = array($uid => '');
2102 8 : $current =& $parsed;
2103 8 : $curTxt =& $parsed[$uid++];
2104 8 : $tree = array();
2105 8 : $chars = str_split($key, 1);
2106 8 : $inSplittedVar = false;
2107 8 : $bracketCount = 0;
2108 :
2109 8 : while (($char = array_shift($chars)) !== null) {
2110 8 : if ($char === '[') {
2111 6 : if (count($tree) > 0) {
2112 1 : $bracketCount++;
2113 1 : } else {
2114 6 : $tree[] =& $current;
2115 6 : $current[$uid] = array($uid+1 => '');
2116 6 : $current =& $current[$uid++];
2117 6 : $curTxt =& $current[$uid++];
2118 6 : continue;
2119 : }
2120 8 : } elseif ($char === ']') {
2121 6 : if ($bracketCount > 0) {
2122 1 : $bracketCount--;
2123 1 : } else {
2124 6 : $current =& $tree[count($tree)-1];
2125 6 : array_pop($tree);
2126 6 : if (current($chars) !== '[' && current($chars) !== false && current($chars) !== ']') {
2127 2 : $current[$uid] = '';
2128 2 : $curTxt =& $current[$uid++];
2129 2 : }
2130 6 : continue;
2131 : }
2132 8 : } elseif ($char === '$') {
2133 8 : if (count($tree) == 0) {
2134 4 : $curTxt =& $current[$uid++];
2135 4 : $inSplittedVar = true;
2136 4 : }
2137 8 : } elseif (($char === '.' || $char === '-') && count($tree) == 0 && $inSplittedVar) {
2138 3 : $curTxt =& $current[$uid++];
2139 3 : $inSplittedVar = false;
2140 3 : }
2141 :
2142 8 : $curTxt .= $char;
2143 8 : }
2144 8 : unset($uid, $current, $curTxt, $tree, $chars);
2145 :
2146 8 : if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT : '.$key.'<br>';
2147 :
2148 8 : $key = $this->flattenVarTree($parsed);
2149 :
2150 8 : if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT DONE : '.$key.'<br>';
2151 :
2152 8 : $output = preg_replace('#(^""\.|""\.|\.""$|(\()""\.|\.""(\)))#', '$2$3', '$this->readVar("'.$key.'")');
2153 8 : } else {
2154 130 : $output = $this->parseVarKey($key, $hasModifiers ? 'modifier' : $curBlock);
2155 : }
2156 :
2157 : // methods
2158 130 : if ($hasMethodCall) {
2159 1 : $ptr = 0;
2160 :
2161 1 : $output = $this->parseMethodCall($output, $methodCall, $curBlock, $ptr);
2162 :
2163 1 : if ($pointer !== null) {
2164 1 : $pointer += $ptr;
2165 1 : }
2166 1 : $matchedLength += $ptr;
2167 1 : }
2168 :
2169 130 : if ($hasExpression) {
2170 : // expressions
2171 8 : preg_match_all('#(?:([+/*%=-])(=?-?[%$][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|=?-?[0-9.,]+|\1))#i', $match[4], $expMatch);
2172 :
2173 8 : foreach ($expMatch[1] as $k=>$operator) {
2174 8 : if (substr($expMatch[2][$k], 0, 1)==='=') {
2175 1 : $assign = true;
2176 1 : if ($operator === '=') {
2177 0 : throw new Dwoo_Compilation_Exception($this, 'Invalid expression <em>'.$substr.'</em>, can not use "==" in expressions');
2178 : }
2179 1 : if ($curBlock !== 'root') {
2180 0 : throw new Dwoo_Compilation_Exception($this, 'Invalid expression <em>'.$substr.'</em>, assignments can only be used in top level expressions like {$foo+=3} or {$foo="bar"}');
2181 : }
2182 1 : $operator .= '=';
2183 1 : $expMatch[2][$k] = substr($expMatch[2][$k], 1);
2184 1 : }
2185 :
2186 8 : if (substr($expMatch[2][$k], 0, 1)==='-' && strlen($expMatch[2][$k]) > 1) {
2187 0 : $operator .= '-';
2188 0 : $expMatch[2][$k] = substr($expMatch[2][$k], 1);
2189 0 : }
2190 8 : if (($operator==='+'||$operator==='-') && $expMatch[2][$k]===$operator) {
2191 1 : $output = '('.$output.$operator.$operator.')';
2192 1 : break;
2193 8 : } elseif (substr($expMatch[2][$k], 0, 1) === '$') {
2194 4 : $output = '('.$output.' '.$operator.' '.$this->parseVar($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').')';
2195 8 : } elseif (substr($expMatch[2][$k], 0, 1) === '%') {
2196 1 : $output = '('.$output.' '.$operator.' '.$this->parseConst($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').')';
2197 6 : } elseif (!empty($expMatch[2][$k])) {
2198 5 : $output = '('.$output.' '.$operator.' '.str_replace(',', '.', $expMatch[2][$k]).')';
2199 5 : } else {
2200 0 : throw new Dwoo_Compilation_Exception($this, 'Unfinished expression <em>'.$substr.'</em>, missing var or number after math operator');
2201 : }
2202 8 : }
2203 8 : }
2204 :
2205 130 : if ($this->autoEscape === true) {
2206 6 : $output = '(is_string($tmp='.$output.') ? htmlspecialchars($tmp, ENT_QUOTES, $this->charset) : $tmp)';
2207 6 : }
2208 :
2209 : // handle modifiers
2210 130 : if ($curBlock !== 'modifier' && $hasModifiers) {
2211 28 : $ptr = 0;
2212 28 : $output = $this->replaceModifiers(array(null, null, $output, $match[5]), 'var', $ptr);
2213 28 : if ($pointer !== null) {
2214 25 : $pointer += $ptr;
2215 25 : }
2216 28 : $matchedLength += $ptr;
2217 28 : }
2218 :
2219 130 : if (is_array($parsingParams)) {
2220 59 : $parsingParams[] = array($output, $key);
2221 59 : return $parsingParams;
2222 105 : } elseif ($curBlock === 'namedparam') {
2223 12 : return array($output, $key);
2224 101 : } elseif ($curBlock === 'string' || $curBlock === 'delimited_string') {
2225 6 : return array($matchedLength, $output);
2226 97 : } elseif ($curBlock === 'expression' || $curBlock === 'variable') {
2227 11 : return $output;
2228 96 : } elseif (isset($assign)) {
2229 1 : return self::PHP_OPEN.$output.';'.self::PHP_CLOSE;
2230 : } else {
2231 96 : return $output;
2232 : }
2233 : } else {
2234 0 : if ($curBlock === 'string' || $curBlock === 'delimited_string') {
2235 0 : return array(0, '');
2236 : } else {
2237 0 : throw new Dwoo_Compilation_Exception($this, 'Invalid variable name <em>'.$substr.'</em>');
2238 : }
2239 : }
2240 : }
2241 :
2242 : /**
2243 : * parses any number of chained method calls/property reads
2244 : *
2245 : * @param string $output the variable or whatever upon which the method are called
2246 : * @param string $methodCall method call source, starting at "->"
2247 : * @param string $curBlock the current parser-block being processed
2248 : * @param int $pointer a reference to a pointer that will be increased by the amount of characters parsed
2249 : * @return string parsed call(s)/read(s)
2250 : */
2251 : protected function parseMethodCall($output, $methodCall, $curBlock, &$pointer)
2252 : {
2253 4 : $ptr = 0;
2254 4 : $len = strlen($methodCall);
2255 :
2256 4 : while ($ptr < $len) {
2257 4 : if (strpos($methodCall, '->', $ptr) === $ptr) {
2258 4 : $ptr += 2;
2259 4 : }
2260 :
2261 4 : if (in_array($methodCall[$ptr], array(';', '/', ' ', "\t", "\r", "\n", ')', '+', '*', '%', '=', '-', '|')) || substr($methodCall, $ptr, strlen($this->rd)) === $this->rd) {
2262 : // break char found
2263 4 : break;
2264 : }
2265 :
2266 4 : if(!preg_match('/^([a-z0-9_]+)(\(.*?\))?/i', substr($methodCall, $ptr), $methMatch)) {
2267 0 : throw new Dwoo_Compilation_Exception($this, 'Invalid method name : '.substr($methodCall, $ptr, 20));
2268 : }
2269 :
2270 4 : if (empty($methMatch[2])) {
2271 : // property
2272 2 : if ($curBlock === 'root') {
2273 2 : $output .= '->'.$methMatch[1];
2274 2 : } else {
2275 0 : $output = '(($tmp = '.$output.') ? $tmp->'.$methMatch[1].' : null)';
2276 : }
2277 2 : $ptr += strlen($methMatch[1]);
2278 2 : } else {
2279 : // method
2280 4 : if (substr($methMatch[2], 0, 2) === '()') {
2281 2 : $parsedCall = '->'.$methMatch[1].'()';
2282 2 : $ptr += strlen($methMatch[1]) + 2;
2283 2 : } else {
2284 4 : $parsedCall = '->'.$this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr);
2285 : }
2286 4 : if ($curBlock === 'root') {
2287 3 : $output .= $parsedCall;
2288 3 : } else {
2289 1 : $output = '(($tmp = '.$output.') ? $tmp'.$parsedCall.' : null)';
2290 : }
2291 : }
2292 4 : }
2293 :
2294 4 : $pointer += $ptr;
2295 4 : return $output;
2296 : }
2297 :
2298 : /**
2299 : * parses a constant variable (a variable that doesn't contain another variable) and preprocesses it to save runtime processing time
2300 : *
2301 : * @param string $key the variable to parse
2302 : * @param string $curBlock the current parser-block being processed
2303 : * @return string parsed variable
2304 : */
2305 : protected function parseVarKey($key, $curBlock)
2306 : {
2307 130 : if ($key === '') {
2308 3 : return '$this->scope';
2309 : }
2310 130 : if (substr($key, 0, 1) === '.') {
2311 6 : $key = 'dwoo'.$key;
2312 6 : }
2313 130 : if (preg_match('#dwoo\.(get|post|server|cookies|session|env|request)((?:\.[a-z0-9_-]+)+)#i', $key, $m)) {
2314 2 : $global = strtoupper($m[1]);
2315 2 : if ($global === 'COOKIES') {
2316 0 : $global = 'COOKIE';
2317 0 : }
2318 2 : $key = '$_'.$global;
2319 2 : foreach (explode('.', ltrim($m[2], '.')) as $part)
2320 2 : $key .= '['.var_export($part, true).']';
2321 2 : if ($curBlock === 'root') {
2322 2 : $output = $key;
2323 2 : } else {
2324 0 : $output = '(isset('.$key.')?'.$key.':null)';
2325 : }
2326 130 : } elseif (preg_match('#dwoo\.const\.([a-z0-9_:]+)#i', $key, $m)) {
2327 3 : return $this->parseConstKey($m[1], $curBlock);
2328 127 : } elseif ($this->scope !== null) {
2329 127 : if (strstr($key, '.') === false && strstr($key, '[') === false && strstr($key, '->') === false) {
2330 120 : if ($key === 'dwoo') {
2331 0 : $output = '$this->globals';
2332 120 : } elseif ($key === '_root' || $key === '__') {
2333 0 : $output = '$this->data';
2334 120 : } elseif ($key === '_parent' || $key === '_') {
2335 0 : $output = '$this->readParentVar(1)';
2336 120 : } elseif ($key === '_key') {
2337 3 : $output = '$tmp_key';
2338 3 : } else {
2339 118 : if ($curBlock === 'root') {
2340 73 : $output = '$this->scope["'.$key.'"]';
2341 73 : } else {
2342 92 : $output = '(isset($this->scope["'.$key.'"]) ? $this->scope["'.$key.'"] : null)';
2343 : }
2344 : }
2345 120 : } else {
2346 22 : preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+|(\\\?[\'"])[^\3]*?\3)\]?#i', $key, $m);
2347 :
2348 22 : $i = $m[2][0];
2349 22 : if ($i === '_parent' || $i === '_') {
2350 1 : $parentCnt = 0;
2351 :
2352 1 : while (true) {
2353 1 : $parentCnt++;
2354 1 : array_shift($m[2]);
2355 1 : array_shift($m[1]);
2356 1 : if (current($m[2]) === '_parent') {
2357 0 : continue;
2358 : }
2359 1 : break;
2360 : }
2361 :
2362 1 : $output = '$this->readParentVar('.$parentCnt.')';
2363 1 : } else {
2364 22 : if ($i === 'dwoo') {
2365 13 : $output = '$this->globals';
2366 13 : array_shift($m[2]);
2367 13 : array_shift($m[1]);
2368 22 : } elseif ($i === '_root' || $i === '__') {
2369 1 : $output = '$this->data';
2370 1 : array_shift($m[2]);
2371 1 : array_shift($m[1]);
2372 9 : } elseif ($i === '_key') {
2373 0 : $output = '$tmp_key';
2374 0 : } else {
2375 8 : $output = '$this->scope';
2376 : }
2377 :
2378 22 : while (count($m[1]) && $m[1][0] !== '->') {
2379 22 : $m[2][0] = preg_replace('/(^\\\([\'"])|\\\([\'"])$)/x', '$2$3', $m[2][0]);
2380 22 : if(substr($m[2][0], 0, 1) == '"' || substr($m[2][0], 0, 1) == "'") {
2381 1 : $output .= '['.$m[2][0].']';
2382 1 : } else {
2383 22 : $output .= '["'.$m[2][0].'"]';
2384 : }
2385 22 : array_shift($m[2]);
2386 22 : array_shift($m[1]);
2387 22 : }
2388 :
2389 22 : if ($curBlock !== 'root') {
2390 7 : $output = '(isset('.$output.') ? '.$output.':null)';
2391 7 : }
2392 : }
2393 :
2394 22 : if (count($m[2])) {
2395 4 : unset($m[0]);
2396 4 : $output = '$this->readVarInto('.str_replace("\n", '', var_export($m, true)).', '.$output.', '.($curBlock == 'root' ? 'false': 'true').')';
2397 4 : }
2398 : }
2399 127 : } else {
2400 0 : preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+)\]?#i', $key, $m);
2401 0 : unset($m[0]);
2402 0 : $output = '$this->readVar('.str_replace("\n", '', var_export($m, true)).')';
2403 : }
2404 :
2405 127 : return $output;
2406 : }
2407 :
2408 : /**
2409 : * flattens a variable tree, this helps in parsing very complex variables such as $var.foo[$foo.bar->baz].baz,
2410 : * it computes the contents of the brackets first and works out from there
2411 : *
2412 : * @param array $tree the variable tree parsed by he parseVar() method that must be flattened
2413 : * @param bool $recursed leave that to false by default, it is only for internal use
2414 : * @return string flattened tree
2415 : */
2416 : protected function flattenVarTree(array $tree, $recursed=false)
2417 : {
2418 8 : $out = $recursed ? '".$this->readVarInto(' : '';
2419 8 : foreach ($tree as $bit) {
2420 8 : if (is_array($bit)) {
2421 6 : $out.='.'.$this->flattenVarTree($bit, false);
2422 6 : } else {
2423 8 : $key = str_replace('"', '\\"', $bit);
2424 :
2425 8 : if (substr($key, 0, 1)==='$') {
2426 8 : $out .= '".'.$this->parseVar($key, 0, strlen($key), false, 'variable').'."';
2427 8 : } else {
2428 8 : $cnt = substr_count($key, '$');
2429 :
2430 8 : if ($this->debug) echo 'PARSING SUBVARS IN : '.$key.'<br>';
2431 8 : if ($cnt > 0) {
2432 0 : while (--$cnt >= 0) {
2433 0 : if (isset($last)) {
2434 0 : $last = strrpos($key, '$', - (strlen($key) - $last + 1));
2435 0 : } else {
2436 0 : $last = strrpos($key, '$');
2437 : }
2438 0 : preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*'.
2439 0 : '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', substr($key, $last), $submatch);
2440 :
2441 0 : $len = strlen($submatch[0]);
2442 0 : $key = substr_replace(
2443 0 : $key,
2444 0 : preg_replace_callback(
2445 : '#(\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)'.
2446 0 : '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i',
2447 0 : array($this, 'replaceVarKeyHelper'), substr($key, $last, $len)
2448 0 : ),
2449 0 : $last,
2450 : $len
2451 0 : );
2452 0 : if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT DONE : '.$key.'<br>';
2453 0 : }
2454 0 : unset($last);
2455 :
2456 0 : $out .= $key;
2457 0 : } else {
2458 8 : $out .= $key;
2459 : }
2460 : }
2461 : }
2462 8 : }
2463 8 : $out .= $recursed ? ', true)."' : '';
2464 8 : return $out;
2465 : }
2466 :
2467 : /**
2468 : * helper function that parses a variable
2469 : *
2470 : * @param array $match the matched variable, array(1=>"string match")
2471 : * @return string parsed variable
2472 : */
2473 : protected function replaceVarKeyHelper($match)
2474 : {
2475 0 : return '".'.$this->parseVar($match[0], 0, strlen($match[0]), false, 'variable').'."';
2476 : }
2477 :
2478 : /**
2479 : * parses various constants, operators or non-quoted strings
2480 : *
2481 : * @param string $in the string within which we must parse something
2482 : * @param int $from the starting offset of the parsed area
2483 : * @param int $to the ending offset of the parsed area
2484 : * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
2485 : * @param string $curBlock the current parser-block being processed
2486 : * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
2487 : * @return string parsed values
2488 : */
2489 : protected function parseOthers($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
2490 : {
2491 113 : $first = $in[$from];
2492 113 : $substr = substr($in, $from, $to-$from);
2493 :
2494 113 : $end = strlen($substr);
2495 :
2496 113 : if ($curBlock === 'condition') {
2497 34 : $breakChars = array('(', ')', ' ', '||', '&&', '|', '&', '>=', '<=', '===', '==', '=', '!==', '!=', '<<', '<', '>>', '>', '^', '~', ',', '+', '-', '*', '/', '%', '!', '?', ':', $this->rd, ';');
2498 113 : } elseif ($curBlock === 'modifier') {
2499 12 : $breakChars = array(' ', ',', ')', ':', '|', "\r", "\n", "\t", ";", $this->rd);
2500 93 : } elseif ($curBlock === 'expression') {
2501 3 : $breakChars = array('/', '%', '+', '-', '*', ' ', ',', ')', "\r", "\n", "\t", ";", $this->rd);
2502 3 : } else {
2503 81 : $breakChars = array(' ', ',', ')', "\r", "\n", "\t", ";", $this->rd);
2504 : }
2505 :
2506 113 : $breaker = false;
2507 113 : while (list($k,$char) = each($breakChars)) {
2508 113 : $test = strpos($substr, $char);
2509 113 : if ($test !== false && $test < $end) {
2510 113 : $end = $test;
2511 113 : $breaker = $k;
2512 113 : }
2513 113 : }
2514 :
2515 113 : if ($curBlock === 'condition') {
2516 34 : if ($end === 0 && $breaker !== false) {
2517 20 : $end = strlen($breakChars[$breaker]);
2518 20 : }
2519 34 : }
2520 :
2521 113 : if ($end !== false) {
2522 113 : $substr = substr($substr, 0, $end);
2523 113 : }
2524 :
2525 113 : if ($pointer !== null) {
2526 113 : $pointer += strlen($substr);
2527 113 : }
2528 :
2529 113 : $src = $substr;
2530 113 : $substr = trim($substr);
2531 :
2532 113 : if (strtolower($substr) === 'false' || strtolower($substr) === 'no' || strtolower($substr) === 'off') {
2533 7 : if ($this->debug) echo 'BOOLEAN(FALSE) PARSED<br />';
2534 7 : $substr = 'false';
2535 7 : $type = self::T_BOOL;
2536 113 : } elseif (strtolower($substr) === 'true' || strtolower($substr) === 'yes' || strtolower($substr) === 'on') {
2537 13 : if ($this->debug) echo 'BOOLEAN(TRUE) PARSED<br />';
2538 13 : $substr = 'true';
2539 13 : $type = self::T_BOOL;
2540 111 : } elseif ($substr === 'null' || $substr === 'NULL') {
2541 0 : if ($this->debug) echo 'NULL PARSED<br />';
2542 0 : $substr = 'null';
2543 0 : $type = self::T_NULL;
2544 107 : } elseif (is_numeric($substr)) {
2545 65 : $substr = (float) $substr;
2546 65 : if ((int) $substr == $substr) {
2547 64 : $substr = (int) $substr;
2548 64 : }
2549 65 : $type = self::T_NUMERIC;
2550 65 : if ($this->debug) echo 'NUMBER ('.$substr.') PARSED<br />';
2551 107 : } elseif (preg_match('{^-?(\d+|\d*(\.\d+))\s*([/*%+-]\s*-?(\d+|\d*(\.\d+)))+$}', $substr)) {
2552 0 : if ($this->debug) echo 'SIMPLE MATH PARSED<br />';
2553 0 : $type = self::T_MATH;
2554 0 : $substr = '('.$substr.')';
2555 70 : } elseif ($curBlock === 'condition' && array_search($substr, $breakChars, true) !== false) {
2556 20 : if ($this->debug) echo 'BREAKCHAR ('.$substr.') PARSED<br />';
2557 20 : $type = self::T_BREAKCHAR;
2558 : //$substr = '"'.$substr.'"';
2559 20 : } else {
2560 55 : $substr = $this->replaceStringVars('\''.str_replace('\'', '\\\'', $substr).'\'', '\'', $curBlock);
2561 55 : $type = self::T_UNQUOTED_STRING;
2562 55 : if ($this->debug) echo 'BLABBER ('.$substr.') CASTED AS STRING<br />';
2563 : }
2564 :
2565 113 : if (is_array($parsingParams)) {
2566 97 : $parsingParams[] = array($substr, $src, $type);
2567 97 : return $parsingParams;
2568 27 : } elseif ($curBlock === 'namedparam') {
2569 25 : return array($substr, $src, $type);
2570 2 : } elseif ($curBlock === 'expression') {
2571 2 : return $substr;
2572 : } else {
2573 0 : throw new Exception('Something went wrong');
2574 : }
2575 : }
2576 :
2577 : /**
2578 : * replaces variables within a parsed string
2579 : *
2580 : * @param string $string the parsed string
2581 : * @param string $first the first character parsed in the string, which is the string delimiter (' or ")
2582 : * @param string $curBlock the current parser-block being processed
2583 : * @return string the original string with variables replaced
2584 : */
2585 : protected function replaceStringVars($string, $first, $curBlock='')
2586 : {
2587 120 : $pos = 0;
2588 120 : if ($this->debug) echo 'STRING VAR REPLACEMENT : '.$string.'<br>';
2589 : // replace vars
2590 120 : while (($pos = strpos($string, '$', $pos)) !== false) {
2591 7 : $prev = substr($string, $pos-1, 1);
2592 7 : if ($prev === '\\') {
2593 1 : $pos++;
2594 1 : continue;
2595 : }
2596 :
2597 6 : $var = $this->parse($string, $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string':'string')));
2598 6 : $len = $var[0];
2599 6 : $var = $this->parse(str_replace('\\'.$first, $first, $string), $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string':'string')));
2600 :
2601 6 : if ($prev === '`' && substr($string, $pos+$len, 1) === '`') {
2602 4 : $string = substr_replace($string, $first.'.'.$var[1].'.'.$first, $pos-1, $len+2);
2603 4 : } else {
2604 2 : $string = substr_replace($string, $first.'.'.$var[1].'.'.$first, $pos, $len);
2605 : }
2606 6 : $pos += strlen($var[1]) + 2;
2607 6 : if ($this->debug) echo 'STRING VAR REPLACEMENT DONE : '.$string.'<br>';
2608 6 : }
2609 :
2610 : // handle modifiers
2611 : // TODO Obsolete?
2612 120 : $string = preg_replace_callback('#("|\')\.(.+?)\.\1((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\4|:[^`]*))*))+)#i', array($this, 'replaceModifiers'), $string);
2613 :
2614 : // replace escaped dollar operators by unescaped ones if required
2615 120 : if ($first==="'") {
2616 57 : $string = str_replace('\\$', '$', $string);
2617 57 : }
2618 :
2619 120 : return $string;
2620 : }
2621 :
2622 : /**
2623 : * replaces the modifiers applied to a string or a variable
2624 : *
2625 : * @param array $m the regex matches that must be array(1=>"double or single quotes enclosing a string, when applicable", 2=>"the string or var", 3=>"the modifiers matched")
2626 : * @param string $curBlock the current parser-block being processed
2627 : * @return string the input enclosed with various function calls according to the modifiers found
2628 : */
2629 : protected function replaceModifiers(array $m, $curBlock = null, &$pointer = null)
2630 : {
2631 33 : if ($this->debug) echo 'PARSING MODIFIERS : '.$m[3].'<br />';
2632 :
2633 33 : if ($pointer !== null) {
2634 33 : $pointer += strlen($m[3]);
2635 33 : }
2636 : // remove first pipe
2637 33 : $cmdstrsrc = substr($m[3], 1);
2638 : // remove last quote if present
2639 33 : if (substr($cmdstrsrc, -1, 1) === $m[1]) {
2640 0 : $cmdstrsrc = substr($cmdstrsrc, 0, -1);
2641 0 : $add = $m[1];
2642 0 : }
2643 :
2644 33 : $output = $m[2];
2645 :
2646 33 : $continue = true;
2647 33 : while (strlen($cmdstrsrc) > 0 && $continue) {
2648 33 : if ($cmdstrsrc[0] === '|') {
2649 3 : $cmdstrsrc = substr($cmdstrsrc, 1);
2650 3 : continue;
2651 : }
2652 33 : if ($cmdstrsrc[0] === ' ' || $cmdstrsrc[0] === ';' || substr($cmdstrsrc, 0, strlen($this->rd)) === $this->rd) {
2653 2 : if ($this->debug) echo 'MODIFIER PARSING ENDED, RIGHT DELIMITER or ";" FOUND<br/>';
2654 2 : $continue = false;
2655 2 : if ($pointer !== null) {
2656 2 : $pointer -= strlen($cmdstrsrc);
2657 2 : }
2658 2 : break;
2659 : }
2660 33 : $cmdstr = $cmdstrsrc;
2661 33 : $paramsep = ':';
2662 33 : if (!preg_match('/^(@{0,2}[a-z][a-z0-9_]*)(:)?/i', $cmdstr, $match)) {
2663 0 : throw new Dwoo_Compilation_Exception($this, 'Invalid modifier name, started with : '.substr($cmdstr, 0, 10));
2664 : }
2665 33 : $paramspos = !empty($match[2]) ? strlen($match[1]) : false;
2666 33 : $func = $match[1];
2667 :
2668 33 : $state = 0;
2669 33 : if ($paramspos === false) {
2670 18 : $cmdstrsrc = substr($cmdstrsrc, strlen($func));
2671 18 : $params = array();
2672 18 : if ($this->debug) echo 'MODIFIER ('.$func.') CALLED WITH NO PARAMS<br/>';
2673 18 : } else {
2674 21 : $paramstr = substr($cmdstr, $paramspos+1);
2675 21 : if (substr($paramstr, -1, 1) === $paramsep) {
2676 0 : $paramstr = substr($paramstr, 0, -1);
2677 0 : }
2678 :
2679 21 : $ptr = 0;
2680 21 : $params = array();
2681 21 : while ($ptr < strlen($paramstr)) {
2682 21 : if ($this->debug) echo 'MODIFIER ('.$func.') START PARAM PARSING WITH POINTER AT '.$ptr.'<br/>';
2683 21 : if ($this->debug) echo $paramstr.'--'.$ptr.'--'.strlen($paramstr).'--modifier<br/>';
2684 21 : $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'modifier', $ptr);
2685 21 : if ($this->debug) echo 'PARAM PARSED, POINTER AT '.$ptr.'<br/>';
2686 :
2687 21 : if ($ptr >= strlen($paramstr)) {
2688 2 : if ($this->debug) echo 'PARAM PARSING ENDED, PARAM STRING CONSUMED<br/>';
2689 2 : break;
2690 : }
2691 :
2692 20 : if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === '|' || $paramstr[$ptr] === ';' || substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
2693 19 : if ($this->debug) echo 'PARAM PARSING ENDED, " ", "|", RIGHT DELIMITER or ";" FOUND, POINTER AT '.$ptr.'<br/>';
2694 19 : if ($paramstr[$ptr] !== '|') {
2695 15 : $continue = false;
2696 15 : if ($pointer !== null) {
2697 15 : $pointer -= strlen($paramstr) - $ptr;
2698 15 : }
2699 15 : }
2700 19 : $ptr++;
2701 19 : break;
2702 : }
2703 5 : if ($ptr < strlen($paramstr) && $paramstr[$ptr] === ':') {
2704 5 : $ptr++;
2705 5 : }
2706 5 : }
2707 21 : $cmdstrsrc = substr($cmdstrsrc, strlen($func)+1+$ptr);
2708 21 : $paramstr = substr($paramstr, 0, $ptr);
2709 21 : foreach ($params as $k=>$p) {
2710 21 : if (is_array($p) && is_array($p[1])) {
2711 0 : $state |= 2;
2712 0 : } else {
2713 21 : if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) {
2714 0 : $params[$k] = array($m[2], array('true', 'true'));
2715 0 : } else {
2716 21 : if ($state & 2) {
2717 0 : throw new Dwoo_Compilation_Exception($this, 'You can not use an unnamed parameter after a named one');
2718 : }
2719 21 : $state |= 1;
2720 : }
2721 : }
2722 21 : }
2723 : }
2724 :
2725 : // check if we must use array_map with this plugin or not
2726 33 : $mapped = false;
2727 33 : if (substr($func, 0, 1) === '@') {
2728 2 : $func = substr($func, 1);
2729 2 : $mapped = true;
2730 2 : }
2731 :
2732 33 : $pluginType = $this->getPluginType($func);
2733 :
2734 33 : if ($state & 2) {
2735 0 : array_unshift($params, array('value', array($output, $output)));
2736 0 : } else {
2737 33 : array_unshift($params, array($output, $output));
2738 : }
2739 :
2740 33 : if ($pluginType & Dwoo::NATIVE_PLUGIN) {
2741 2 : $params = $this->mapParams($params, null, $state);
2742 :
2743 2 : $params = $params['*'][0];
2744 :
2745 2 : $params = self::implode_r($params);
2746 :
2747 2 : if ($mapped) {
2748 1 : $output = '$this->arrayMap(\''.$func.'\', array('.$params.'))';
2749 1 : } else {
2750 2 : $output = $func.'('.$params.')';
2751 : }
2752 33 : } elseif ($pluginType & Dwoo::PROXY_PLUGIN) {
2753 0 : $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
2754 0 : foreach ($params as &$p)
2755 0 : $p = $p[0];
2756 0 : $output = call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params);
2757 31 : } elseif ($pluginType & Dwoo::SMARTY_MODIFIER) {
2758 0 : $params = $this->mapParams($params, null, $state);
2759 0 : $params = $params['*'][0];
2760 :
2761 0 : $params = self::implode_r($params);
2762 :
2763 0 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
2764 0 : $callback = $this->customPlugins[$func]['callback'];
2765 0 : if (is_array($callback)) {
2766 0 : if (is_object($callback[0])) {
2767 0 : $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array('.$params.'))';
2768 0 : } else {
2769 0 : $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array(\''.$callback[0].'\', \''.$callback[1].'\'), array('.$params.'))';
2770 : }
2771 0 : } elseif ($mapped) {
2772 0 : $output = '$this->arrayMap(\''.$callback.'\', array('.$params.'))';
2773 0 : } else {
2774 0 : $output = $callback.'('.$params.')';
2775 : }
2776 0 : } elseif ($mapped) {
2777 0 : $output = '$this->arrayMap(\'smarty_modifier_'.$func.'\', array('.$params.'))';
2778 0 : } else {
2779 0 : $output = 'smarty_modifier_'.$func.'('.$params.')';
2780 : }
2781 0 : } else {
2782 31 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
2783 6 : $callback = $this->customPlugins[$func]['callback'];
2784 6 : $pluginName = $callback;
2785 6 : } else {
2786 25 : $pluginName = 'Dwoo_Plugin_'.$func;
2787 :
2788 25 : if ($pluginType & Dwoo::CLASS_PLUGIN) {
2789 2 : $callback = array($pluginName, ($pluginType & Dwoo::COMPILABLE_PLUGIN) ? 'compile' : 'process');
2790 2 : } else {
2791 23 : $callback = $pluginName . (($pluginType & Dwoo::COMPILABLE_PLUGIN) ? '_compile' : '');
2792 : }
2793 : }
2794 :
2795 31 : $params = $this->mapParams($params, $callback, $state);
2796 :
2797 31 : foreach ($params as &$p)
2798 31 : $p = $p[0];
2799 :
2800 31 : if ($pluginType & Dwoo::FUNC_PLUGIN) {
2801 25 : if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
2802 22 : if ($mapped) {
2803 0 : throw new Dwoo_Compilation_Exception($this, 'The @ operator can not be used on compiled plugins.');
2804 : }
2805 22 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
2806 1 : $funcCompiler = $this->customPlugins[$func]['callback'];
2807 1 : } else {
2808 21 : $funcCompiler = 'Dwoo_Plugin_'.$func.'_compile';
2809 : }
2810 22 : array_unshift($params, $this);
2811 22 : $output = call_user_func_array($funcCompiler, $params);
2812 22 : } else {
2813 6 : array_unshift($params, '$this');
2814 :
2815 6 : $params = self::implode_r($params);
2816 6 : if ($mapped) {
2817 1 : $output = '$this->arrayMap(\''.$pluginName.'\', array('.$params.'))';
2818 1 : } else {
2819 6 : $output = $pluginName.'('.$params.')';
2820 : }
2821 : }
2822 25 : } else {
2823 6 : if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
2824 3 : if ($mapped) {
2825 0 : throw new Dwoo_Compilation_Exception($this, 'The @ operator can not be used on compiled plugins.');
2826 : }
2827 3 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
2828 2 : $callback = $this->customPlugins[$func]['callback'];
2829 2 : if (!is_array($callback)) {
2830 0 : if (!method_exists($callback, 'compile')) {
2831 0 : throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use');
2832 : }
2833 0 : if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
2834 0 : $funcCompiler = array($callback, 'compile');
2835 0 : } else {
2836 0 : $funcCompiler = array(new $callback, 'compile');
2837 : }
2838 0 : } else {
2839 2 : $funcCompiler = $callback;
2840 : }
2841 2 : } else {
2842 1 : $funcCompiler = array('Dwoo_Plugin_'.$func, 'compile');
2843 1 : array_unshift($params, $this);
2844 : }
2845 3 : $output = call_user_func_array($funcCompiler, $params);
2846 3 : } else {
2847 3 : $params = self::implode_r($params);
2848 :
2849 3 : if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
2850 2 : if (is_object($callback[0])) {
2851 1 : $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array('.$params.'))';
2852 1 : } else {
2853 1 : $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array(\''.$callback[0].'\', \''.$callback[1].'\'), array('.$params.'))';
2854 : }
2855 3 : } elseif ($mapped) {
2856 0 : $output = '$this->arrayMap(array($this->getObjectPlugin(\'Dwoo_Plugin_'.$func.'\'), \'process\'), array('.$params.'))';
2857 0 : } else {
2858 1 : $output = '$this->classCall(\''.$func.'\', array('.$params.'))';
2859 : }
2860 : }
2861 : }
2862 : }
2863 33 : }
2864 :
2865 33 : if ($curBlock === 'var' || $m[1] === null) {
2866 33 : return $output;
2867 0 : } elseif ($curBlock === 'string' || $curBlock === 'root') {
2868 0 : return $m[1].'.'.$output.'.'.$m[1].(isset($add)?$add:null);
2869 : }
2870 0 : }
2871 :
2872 : /**
2873 : * recursively implodes an array in a similar manner as var_export() does but with some tweaks
2874 : * to handle pre-compiled values and the fact that we do not need to enclose everything with
2875 : * "array" and do not require top-level keys to be displayed
2876 : *
2877 : * @param array $params the array to implode
2878 : * @param bool $recursiveCall if set to true, the function outputs key names for the top level
2879 : * @return string the imploded array
2880 : */
2881 : public static function implode_r(array $params, $recursiveCall = false)
2882 : {
2883 55 : $out = '';
2884 55 : foreach ($params as $k=>$p) {
2885 53 : if (is_array($p)) {
2886 4 : $out2 = 'array(';
2887 4 : foreach ($p as $k2=>$v)
2888 4 : $out2 .= var_export($k2, true).' => '.(is_array($v) ? 'array('.self::implode_r($v, true).')' : $v).', ';
2889 4 : $p = rtrim($out2, ', ').')';
2890 4 : }
2891 53 : if ($recursiveCall) {
2892 0 : $out .= var_export($k, true).' => '.$p.', ';
2893 0 : } else {
2894 53 : $out .= $p.', ';
2895 : }
2896 55 : }
2897 55 : return rtrim($out, ', ');
2898 : }
2899 :
2900 : /**
2901 : * returns the plugin type of a plugin and adds it to the used plugins array if required
2902 : *
2903 : * @param string $name plugin name, as found in the template
2904 : * @return int type as a multi bit flag composed of the Dwoo plugin types constants
2905 : */
2906 : protected function getPluginType($name)
2907 : {
2908 215 : $pluginType = -1;
2909 :
2910 215 : if (($this->securityPolicy === null && (function_exists($name) || strtolower($name) === 'isset' || strtolower($name) === 'empty')) ||
2911 215 : ($this->securityPolicy !== null && in_array(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) !== false)) {
2912 15 : $phpFunc = true;
2913 15 : }
2914 :
2915 215 : while ($pluginType <= 0) {
2916 215 : if (isset($this->templatePlugins[$name])) {
2917 3 : $pluginType = Dwoo::TEMPLATE_PLUGIN | Dwoo::COMPILABLE_PLUGIN;
2918 215 : } elseif (isset($this->customPlugins[$name])) {
2919 22 : $pluginType = $this->customPlugins[$name]['type'] | Dwoo::CUSTOM_PLUGIN;
2920 215 : } elseif (class_exists('Dwoo_Plugin_'.$name, false) !== false) {
2921 213 : if (is_subclass_of('Dwoo_Plugin_'.$name, 'Dwoo_Block_Plugin')) {
2922 213 : $pluginType = Dwoo::BLOCK_PLUGIN;
2923 213 : } else {
2924 11 : $pluginType = Dwoo::CLASS_PLUGIN;
2925 : }
2926 213 : $interfaces = class_implements('Dwoo_Plugin_'.$name, false);
2927 213 : if (in_array('Dwoo_ICompilable', $interfaces) !== false || in_array('Dwoo_ICompilable_Block', $interfaces) !== false) {
2928 213 : $pluginType |= Dwoo::COMPILABLE_PLUGIN;
2929 213 : }
2930 215 : } elseif (function_exists('Dwoo_Plugin_'.$name) !== false) {
2931 24 : $pluginType = Dwoo::FUNC_PLUGIN;
2932 114 : } elseif (function_exists('Dwoo_Plugin_'.$name.'_compile')) {
2933 76 : $pluginType = Dwoo::FUNC_PLUGIN | Dwoo::COMPILABLE_PLUGIN;
2934 109 : } elseif (function_exists('smarty_modifier_'.$name) !== false) {
2935 0 : $pluginType = Dwoo::SMARTY_MODIFIER;
2936 64 : } elseif (function_exists('smarty_function_'.$name) !== false) {
2937 0 : $pluginType = Dwoo::SMARTY_FUNCTION;
2938 64 : } elseif (function_exists('smarty_block_'.$name) !== false) {
2939 0 : $pluginType = Dwoo::SMARTY_BLOCK;
2940 0 : } else {
2941 64 : if ($pluginType===-1) {
2942 : try {
2943 64 : $this->dwoo->getLoader()->loadPlugin($name, isset($phpFunc)===false);
2944 64 : } catch (Exception $e) {
2945 11 : if (isset($phpFunc)) {
2946 8 : $pluginType = Dwoo::NATIVE_PLUGIN;
2947 11 : } elseif (is_object($this->dwoo->getPluginProxy()) && $this->dwoo->getPluginProxy()->handles($name)) {
2948 3 : $pluginType = Dwoo::PROXY_PLUGIN;
2949 3 : break;
2950 : } else {
2951 0 : throw $e;
2952 : }
2953 : }
2954 61 : } else {
2955 0 : throw new Dwoo_Exception('Plugin "'.$name.'" could not be found');
2956 : }
2957 61 : $pluginType++;
2958 : }
2959 215 : }
2960 :
2961 215 : if (($pluginType & Dwoo::COMPILABLE_PLUGIN) === 0 && ($pluginType & Dwoo::NATIVE_PLUGIN) === 0 && ($pluginType & Dwoo::PROXY_PLUGIN) === 0) {
2962 48 : $this->addUsedPlugin($name, $pluginType);
2963 48 : }
2964 :
2965 215 : return $pluginType;
2966 : }
2967 :
2968 : /**
2969 : * allows a plugin to load another one at compile time, this will also mark
2970 : * it as used by this template so it will be loaded at runtime (which can be
2971 : * useful for compiled plugins that rely on another plugin when their compiled
2972 : * code runs)
2973 : *
2974 : * @param string $name the plugin name
2975 : */
2976 : public function loadPlugin($name) {
2977 0 : $this->getPluginType($name);
2978 0 : }
2979 :
2980 : /**
2981 : * runs htmlentities over the matched <?php ?> blocks when the security policy enforces that
2982 : *
2983 : * @param array $match matched php block
2984 : * @return string the htmlentities-converted string
2985 : */
2986 : protected function phpTagEncodingHelper($match)
2987 : {
2988 1 : return htmlspecialchars($match[0]);
2989 : }
2990 :
2991 : /**
2992 : * maps the parameters received from the template onto the parameters required by the given callback
2993 : *
2994 : * @param array $params the array of parameters
2995 : * @param callback $callback the function or method to reflect on to find out the required parameters
2996 : * @param int $callType the type of call in the template, 0 = no params, 1 = php-style call, 2 = named parameters call
2997 : * @param array $map the parameter map to use, if not provided it will be built from the callback
2998 : * @return array parameters sorted in the correct order with missing optional parameters filled
2999 : */
3000 : protected function mapParams(array $params, $callback, $callType=2, $map = null)
3001 : {
3002 221 : if (!$map) {
3003 221 : $map = $this->getParamMap($callback);
3004 221 : }
3005 :
3006 221 : $paramlist = array();
3007 :
3008 : // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
3009 221 : $ps = array();
3010 221 : foreach ($params as $p) {
3011 175 : if (is_array($p[1])) {
3012 36 : $ps[$p[0]] = $p[1];
3013 36 : } else {
3014 156 : $ps[] = $p;
3015 : }
3016 221 : }
3017 :
3018 : // loops over the param map and assigns values from the template or default value for unset optional params
3019 221 : while (list($k,$v) = each($map)) {
3020 179 : if ($v[0] === '*') {
3021 : // "rest" array parameter, fill every remaining params in it and then break
3022 79 : if (count($ps) === 0) {
3023 9 : if ($v[1]===false) {
3024 0 : throw new Dwoo_Compilation_Exception($this, 'Rest argument missing for '.str_replace(array('Dwoo_Plugin_', '_compile'), '', (is_array($callback) ? $callback[0] : $callback)));
3025 : } else {
3026 9 : break;
3027 : }
3028 : }
3029 77 : $tmp = array();
3030 77 : $tmp2 = array();
3031 77 : $tmp3 = array();
3032 77 : foreach ($ps as $i=>$p) {
3033 77 : $tmp[$i] = $p[0];
3034 77 : $tmp2[$i] = $p[1];
3035 77 : $tmp3[$i] = isset($p[2]) ? $p[2] : 0;
3036 77 : unset($ps[$i]);
3037 77 : }
3038 77 : $paramlist[$v[0]] = array($tmp, $tmp2, $tmp3);
3039 77 : unset($tmp, $tmp2, $i, $p);
3040 77 : break;
3041 150 : } elseif (isset($ps[$v[0]])) {
3042 : // parameter is defined as named param
3043 29 : $paramlist[$v[0]] = $ps[$v[0]];
3044 29 : unset($ps[$v[0]]);
3045 150 : } elseif (isset($ps[$k])) {
3046 : // parameter is defined as ordered param
3047 125 : $paramlist[$v[0]] = $ps[$k];
3048 125 : unset($ps[$k]);
3049 148 : } elseif ($v[1]===false) {
3050 : // parameter is not defined and not optional, throw error
3051 2 : if (is_array($callback)) {
3052 0 : if (is_object($callback[0])) {
3053 0 : $name = get_class($callback[0]) . '::' . $callback[1];
3054 0 : } else {
3055 0 : $name = $callback[0];
3056 : }
3057 0 : } else {
3058 2 : $name = $callback;
3059 : }
3060 :
3061 2 : throw new Dwoo_Compilation_Exception($this, 'Argument '.$k.'/'.$v[0].' missing for '.str_replace(array('Dwoo_Plugin_', '_compile'), '', $name));
3062 78 : } elseif ($v[2]===null) {
3063 : // enforce lowercased null if default value is null (php outputs NULL with var export)
3064 35 : $paramlist[$v[0]] = array('null', null, self::T_NULL);
3065 35 : } else {
3066 : // outputs default value with var_export
3067 72 : $paramlist[$v[0]] = array(var_export($v[2], true), $v[2]);
3068 : }
3069 148 : }
3070 :
3071 221 : if (count($ps)) {
3072 2 : foreach ($ps as $i=>$p) {
3073 2 : array_push($paramlist, $p);
3074 2 : }
3075 2 : }
3076 :
3077 221 : return $paramlist;
3078 : }
3079 :
3080 : /**
3081 : * returns the parameter map of the given callback, it filters out entries typed as Dwoo and Dwoo_Compiler and turns the rest parameter into a "*"
3082 : *
3083 : * @param callback $callback the function/method to reflect on
3084 : * @return array processed parameter map
3085 : */
3086 : protected function getParamMap($callback)
3087 : {
3088 221 : if (is_null($callback)) {
3089 18 : return array(array('*', true));
3090 : }
3091 221 : if (is_array($callback)) {
3092 221 : $ref = new ReflectionMethod($callback[0], $callback[1]);
3093 221 : } else {
3094 98 : $ref = new ReflectionFunction($callback);
3095 : }
3096 :
3097 221 : $out = array();
3098 221 : foreach ($ref->getParameters() as $param) {
3099 176 : if (($class = $param->getClass()) !== null && $class->name === 'Dwoo') {
3100 28 : continue;
3101 : }
3102 174 : if (($class = $param->getClass()) !== null && $class->name === 'Dwoo_Compiler') {
3103 80 : continue;
3104 : }
3105 173 : if ($param->getName() === 'rest' && $param->isArray() === true) {
3106 65 : $out[] = array('*', $param->isOptional(), null);
3107 65 : }
3108 173 : $out[] = array($param->getName(), $param->isOptional(), $param->isOptional() ? $param->getDefaultValue() : null);
3109 221 : }
3110 :
3111 221 : return $out;
3112 : }
3113 :
3114 : /**
3115 : * returns a default instance of this compiler, used by default by all Dwoo templates that do not have a
3116 : * specific compiler assigned and when you do not override the default compiler factory function
3117 : *
3118 : * @see Dwoo::setDefaultCompilerFactory()
3119 : * @return Dwoo_Compiler
3120 : */
3121 : public static function compilerFactory()
3122 : {
3123 28 : if (self::$instance === null) {
3124 0 : new self;
3125 0 : }
3126 28 : return self::$instance;
3127 : }
3128 : }
|