source: branches/0.4/web/addons/job_monarch/dwoo/Dwoo/Compiler.php @ 755

Last change on this file since 755 was 755, checked in by ramonb, 11 years ago
  • add Dwoo
File size: 102.8 KB
Line 
1<?php
2
3include 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 */
19class 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                self::$instance = $this;
253        }
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                $this->ld = $left;
267                $this->rd = $right;
268                $this->ldr = preg_quote($left, '/');
269                $this->rdr = preg_quote($right, '/');
270        }
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                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                $this->allowNestedComments = (bool) $allow;
293        }
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                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                $this->allowLooseOpenings = (bool) $allow;
318        }
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                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                $this->autoEscape = (bool) $enabled;
345        }
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                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                if ($autoload) {
369                        $name = str_replace('Dwoo_Processor_', '', $callback);
370                        $class = 'Dwoo_Processor_'.$name;
371
372                        if (class_exists($class, false)) {
373                                $callback = array(new $class($this), 'process');
374                        } elseif (function_exists($class)) {
375                                $callback = $class;
376                        } else {
377                                $callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name);
378                        }
379
380                        $this->processors['pre'][] = $callback;
381                } else {
382                        $this->processors['pre'][] = $callback;
383                }
384        }
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                if (($index = array_search($callback, $this->processors['pre'], true)) !== false) {
394                        unset($this->processors['pre'][$index]);
395                } elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['pre'], true)) !== false) {
396                        unset($this->processors['pre'][$index]);
397                } else {
398                        $class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback);
399                        foreach ($this->processors['pre'] as $index=>$proc) {
400                                if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) {
401                                        unset($this->processors['pre'][$index]);
402                                        break;
403                                }
404                        }
405                }
406        }
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                if ($autoload) {
418                        $name = str_replace('Dwoo_Processor_', '', $callback);
419                        $class = 'Dwoo_Processor_'.$name;
420
421                        if (class_exists($class, false)) {
422                                $callback = array(new $class($this), 'process');
423                        } elseif (function_exists($class)) {
424                                $callback = $class;
425                        } else {
426                                $callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name);
427                        }
428
429                        $this->processors['post'][] = $callback;
430                } else {
431                        $this->processors['post'][] = $callback;
432                }
433        }
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                if (($index = array_search($callback, $this->processors['post'], true)) !== false) {
443                        unset($this->processors['post'][$index]);
444                } elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['post'], true)) !== false) {
445                        unset($this->processors['post'][$index]);
446                } else  {
447                        $class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback);
448                        foreach ($this->processors['post'] as $index=>$proc) {
449                                if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) {
450                                        unset($this->processors['post'][$index]);
451                                        break;
452                                }
453                        }
454                }
455        }
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                if (!class_exists($class, false) && !function_exists($class)) {
466                        try {
467                                $this->dwoo->getLoader()->loadPlugin($name);
468                        } catch (Dwoo_Exception $e) {
469                                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                }
472
473                if (class_exists($class, false)) {
474                        return array(new $class($this), 'process');
475                }
476
477                if (function_exists($class)) {
478                        return $class;
479                }
480
481                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_Core::*_PLUGIN)
493         */
494        public function addUsedPlugin($name, $type)
495        {
496                $this->usedPlugins[$name] = $type;
497        }
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                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                $this->templatePlugins[$name] = array('params'=> $params, 'body' => $body, 'uuid' => $uuid);
526        }
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                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                $this->templatePlugins[$name]['called'] = true;
547        }
548
549        /**
550         * adds the custom plugins loaded into Dwoo to the compiler so it can load them
551         *
552         * @see Dwoo_Core::addPlugin
553         * @param array $customPlugins an array of custom plugins
554         */
555        public function setCustomPlugins(array $customPlugins)
556        {
557                $this->customPlugins = $customPlugins;
558        }
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                $this->securityPolicy = $policy;
571        }
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                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                if ($isOffset) {
592                        $this->pointer += $position;
593                } else {
594                        $this->pointer = $position;
595                }
596        }
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                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                if ($isOffset) {
617                        $this->line += $number;
618                } else {
619                        $this->line = $number;
620                }
621        }
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                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                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                if ($fromPointer === true) {
653                        $this->templateSource = substr($this->templateSource, 0, $this->pointer) . $newSource;
654                } else {
655                        $this->templateSource = $newSource;
656                }
657        }
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                if ($fromPointer === true) {
669                        return substr($this->templateSource, $this->pointer);
670                } elseif (is_numeric($fromPointer)) {
671                        return substr($this->templateSource, $fromPointer);
672                } else {
673                        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                $this->setPointer(0);
685        }
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_Core $dwoo, Dwoo_ITemplate $template)
694        {
695                // init vars
696                $tpl = $template->getSource();
697                $ptr = 0;
698                $this->dwoo = $dwoo;
699                $this->template = $template;
700                $this->templateSource =& $tpl;
701                $this->pointer =& $ptr;
702
703                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                        if ($ptr===0) {
706                                // resets variables
707                                $this->usedPlugins = array();
708                                $this->data = array();
709                                $this->scope =& $this->data;
710                                $this->scopeTree = array();
711                                $this->stack = array();
712                                $this->line = 1;
713                                $this->templatePlugins = array();
714                                // add top level block
715                                $compiled = $this->addBlock('topLevelBlock', array(), 0);
716                                $this->stack[0]['buffer'] = '';
717
718                                if ($this->debug) echo 'COMPILER INIT<br />';
719
720                                if ($this->debug) echo 'PROCESSING PREPROCESSORS ('.count($this->processors['pre']).')<br>';
721
722                                // runs preprocessors
723                                foreach ($this->processors['pre'] as $preProc) {
724                                        if (is_array($preProc) && isset($preProc['autoload'])) {
725                                                $preProc = $this->loadProcessor($preProc['class'], $preProc['name']);
726                                        }
727                                        if (is_array($preProc) && $preProc[0] instanceof Dwoo_Processor) {
728                                                $tpl = call_user_func($preProc, $tpl);
729                                        } else {
730                                                $tpl = call_user_func($preProc, $this, $tpl);
731                                        }
732                                }
733                                unset($preProc);
734
735                                // show template source if debug
736                                if ($this->debug) echo '<pre>'.print_r(htmlentities($tpl), true).'</pre><hr />';
737
738                                // strips php tags if required by the security policy
739                                if ($this->securityPolicy !== null) {
740                                        $search = array('{<\?php.*?\?>}');
741                                        if (ini_get('short_open_tags')) {
742                                                $search = array('{<\?.*?\?>}', '{<%.*?%>}');
743                                        }
744                                        switch($this->securityPolicy->getPhpHandling()) {
745
746                                        case Dwoo_Security_Policy::PHP_ALLOW:
747                                                break;
748                                        case Dwoo_Security_Policy::PHP_ENCODE:
749                                                $tpl = preg_replace_callback($search, array($this, 'phpTagEncodingHelper'), $tpl);
750                                                break;
751                                        case Dwoo_Security_Policy::PHP_REMOVE:
752                                                $tpl = preg_replace($search, '', $tpl);
753
754                                        }
755                                }
756                        }
757
758                        $pos = strpos($tpl, $this->ld, $ptr);
759
760                        if ($pos === false) {
761                                $this->push(substr($tpl, $ptr), 0);
762                                break;
763                        } elseif (substr($tpl, $pos-1, 1) === '\\' && substr($tpl, $pos-2, 1) !== '\\') {
764                                $this->push(substr($tpl, $ptr, $pos-$ptr-1) . $this->ld);
765                                $ptr = $pos+strlen($this->ld);
766                        } elseif (preg_match('/^'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . 'literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', substr($tpl, $pos), $litOpen)) {
767                                if (!preg_match('/'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . '\/literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', $tpl, $litClose, PREG_OFFSET_CAPTURE, $pos)) {
768                                        throw new Dwoo_Compilation_Exception($this, 'The {literal} blocks must be closed explicitly with {/literal}');
769                                }
770                                $endpos = $litClose[0][1];
771                                $this->push(substr($tpl, $ptr, $pos-$ptr) . substr($tpl, $pos + strlen($litOpen[0]), $endpos-$pos-strlen($litOpen[0])));
772                                $ptr = $endpos+strlen($litClose[0][0]);
773                        } else {
774                                if (substr($tpl, $pos-2, 1) === '\\' && substr($tpl, $pos-1, 1) === '\\') {
775                                        $this->push(substr($tpl, $ptr, $pos-$ptr-1));
776                                        $ptr = $pos;
777                                }
778
779                                $this->push(substr($tpl, $ptr, $pos-$ptr));
780                                $ptr = $pos;
781
782                                $pos += strlen($this->ld);
783                                if ($this->allowLooseOpenings) {
784                                        while (substr($tpl, $pos, 1) === ' ') {
785                                                $pos+=1;
786                                        }
787                                } else {
788                                        if (substr($tpl, $pos, 1) === ' ' || substr($tpl, $pos, 1) === "\r" || substr($tpl, $pos, 1) === "\n" || substr($tpl, $pos, 1) === "\t") {
789                                                $ptr = $pos;
790                                                $this->push($this->ld);
791                                                continue;
792                                        }
793                                }
794
795                                // check that there is an end tag present
796                                if (strpos($tpl, $this->rd, $pos) === false) {
797                                        throw new Dwoo_Compilation_Exception($this, 'A template tag was not closed, started with "'.substr($tpl, $ptr, 30).'"');
798                                }
799
800
801                                $ptr += strlen($this->ld);
802                                $subptr = $ptr;
803
804                                while (true) {
805                                        $parsed = $this->parse($tpl, $subptr, null, false, 'root', $subptr);
806
807                                        // reload loop if the compiler was reset
808                                        if ($ptr === 0) {
809                                                continue 2;
810                                        }
811
812                                        $len = $subptr - $ptr;
813                                        $this->push($parsed, substr_count(substr($tpl, $ptr, $len), "\n"));
814                                        $ptr += $len;
815
816                                        if ($parsed === false) {
817                                                break;
818                                        }
819                                }
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                                if (substr($this->curBlock['buffer'], -2) === '?>' && preg_match('{^(([\r\n])([\r\n]?))}', substr($tpl, $ptr, 3), $m)) {
823                                        if ($m[3] === '') {
824                                                $ptr+=1;
825                                                $this->push($m[1].$m[1], 1);
826                                        } else {
827                                                $ptr+=2;
828                                                $this->push($m[1]."\n", 2);
829                                        }
830                                }
831                        }
832                }
833
834                $compiled .= $this->removeBlock('topLevelBlock');
835
836                if ($this->debug) echo 'PROCESSING POSTPROCESSORS<br>';
837
838                foreach ($this->processors['post'] as $postProc) {
839                        if (is_array($postProc) && isset($postProc['autoload'])) {
840                                $postProc = $this->loadProcessor($postProc['class'], $postProc['name']);
841                        }
842                        if (is_array($postProc) && $postProc[0] instanceof Dwoo_Processor) {
843                                $compiled = call_user_func($postProc, $compiled);
844                        } else {
845                                $compiled = call_user_func($postProc, $this, $compiled);
846                        }
847                }
848                unset($postProc);
849
850                if ($this->debug) echo 'COMPILATION COMPLETE : MEM USAGE : '.memory_get_usage().'<br>';
851
852                $output = "<?php\n/* template head */\n";
853
854                // build plugin preloader
855                foreach ($this->usedPlugins as $plugin=>$type) {
856                        if ($type & Dwoo_Core::CUSTOM_PLUGIN) {
857                                continue;
858                        }
859
860                        switch($type) {
861
862                        case Dwoo_Core::BLOCK_PLUGIN:
863                        case Dwoo_Core::CLASS_PLUGIN:
864                                $output .= "if (class_exists('Dwoo_Plugin_$plugin', false)===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
865                                break;
866                        case Dwoo_Core::FUNC_PLUGIN:
867                                $output .= "if (function_exists('Dwoo_Plugin_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
868                                break;
869                        case Dwoo_Core::SMARTY_MODIFIER:
870                                $output .= "if (function_exists('smarty_modifier_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
871                                break;
872                        case Dwoo_Core::SMARTY_FUNCTION:
873                                $output .= "if (function_exists('smarty_function_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
874                                break;
875                        case Dwoo_Core::SMARTY_BLOCK:
876                                $output .= "if (function_exists('smarty_block_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
877                                break;
878                        case Dwoo_Core::PROXY_PLUGIN:
879                                $output .= $this->getDwoo()->getPluginProxy()->getPreloader($plugin);
880                                break;
881                        default:
882                                throw new Dwoo_Compilation_Exception($this, 'Type error for '.$plugin.' with type'.$type);
883
884                        }
885                }
886
887                foreach ($this->templatePlugins as $function => $attr) {
888                        if (isset($attr['called']) && $attr['called'] === true && !isset($attr['checked'])) {
889                                $this->resolveSubTemplateDependencies($function);
890                        }
891                }
892                foreach ($this->templatePlugins as $function) {
893                        if (isset($function['called']) && $function['called'] === true) {
894                                $output .= $function['body'].PHP_EOL;
895                        }
896                }
897
898                $output .= $compiled."\n?>";
899
900                $output = preg_replace('/(?<!;|\}|\*\/|\n|\{)(\s*'.preg_quote(self::PHP_CLOSE, '/') . preg_quote(self::PHP_OPEN, '/').')/', ";\n", $output);
901                $output = str_replace(self::PHP_CLOSE . self::PHP_OPEN, "\n", $output);
902
903                // handle <?xml tag at the beginning
904                $output = preg_replace('#(/\* template body \*/ \?>\s*)<\?xml#is', '$1<?php echo \'<?xml\'; ?>', $output);
905
906                if ($this->debug) {
907                        echo '<hr><pre>';
908                        $lines = preg_split('{\r\n|\n|<br />}', highlight_string(($output), true));
909                        array_shift($lines);
910                        foreach ($lines as $i=>$line) {
911                                echo ($i+1).'. '.$line."\r\n";
912                        }
913                }
914                if ($this->debug) echo '<hr></pre></pre>';
915
916                $this->template = $this->dwoo = null;
917                $tpl = null;
918
919                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                $body = $this->templatePlugins[$function]['body'];
930                foreach ($this->templatePlugins as $func => $attr) {
931                        if ($func !== $function && !isset($attr['called']) && strpos($body, 'Dwoo_Plugin_'.$func) !== false) {
932                                $this->templatePlugins[$func]['called'] = true;
933                                $this->resolveSubTemplateDependencies($func);
934                        }
935                }
936                $this->templatePlugins[$function]['checked'] = true;
937        }
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                if ($lineCount === null) {
948                        $lineCount = substr_count($content, "\n");
949                }
950
951                if ($this->curBlock['buffer'] === null && count($this->stack) > 1) {
952                        // buffer is not initialized yet (the block has just been created)
953                        $this->stack[count($this->stack)-2]['buffer'] .= (string) $content;
954                        $this->curBlock['buffer'] = '';
955                } else {
956                        if (!isset($this->curBlock['buffer'])) {
957                                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                        $this->curBlock['buffer'] .= (string) $content;
961                }
962                $this->line += $lineCount;
963        }
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                $old = $this->scopeTree;
978
979                if ($scope===null) {
980                        unset($this->scope);
981                        $this->scope = null;
982                }
983
984                if (is_array($scope)===false) {
985                        $scope = explode('.', $scope);
986                }
987
988                if ($absolute===true) {
989                        $this->scope =& $this->data;
990                        $this->scopeTree = array();
991                }
992
993                while (($bit = array_shift($scope)) !== null) {
994                        if ($bit === '_parent' || $bit === '_') {
995                                array_pop($this->scopeTree);
996                                reset($this->scopeTree);
997                                $this->scope =& $this->data;
998                                $cnt = count($this->scopeTree);
999                                for ($i=0;$i<$cnt;$i++)
1000                                        $this->scope =& $this->scope[$this->scopeTree[$i]];
1001                        } elseif ($bit === '_root' || $bit === '__') {
1002                                $this->scope =& $this->data;
1003                                $this->scopeTree = array();
1004                        } elseif (isset($this->scope[$bit])) {
1005                                $this->scope =& $this->scope[$bit];
1006                                $this->scopeTree[] = $bit;
1007                        } else {
1008                                $this->scope[$bit] = array();
1009                                $this->scope =& $this->scope[$bit];
1010                                $this->scopeTree[] = $bit;
1011                        }
1012                }
1013
1014                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                $class = 'Dwoo_Plugin_'.$type;
1028                if (class_exists($class, false) === false) {
1029                        $this->dwoo->getLoader()->loadPlugin($type);
1030                }
1031
1032                $params = $this->mapParams($params, array($class, 'init'), $paramtype);
1033
1034                $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null);
1035                $this->curBlock =& $this->stack[count($this->stack)-1];
1036                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                $callback = $this->customPlugins[$type]['callback'];
1050                if (is_array($callback)) {
1051                        $class = is_object($callback[0]) ? get_class($callback[0]) : $callback[0];
1052                } else {
1053                        $class = $callback;
1054                }
1055
1056                $params = $this->mapParams($params, array($class, 'init'), $paramtype);
1057
1058                $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => true, 'class' => $class, 'buffer' => null);
1059                $this->curBlock =& $this->stack[count($this->stack)-1];
1060                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                $class = 'Dwoo_Plugin_'.$type;
1074                if (class_exists($class, false) === false) {
1075                        $this->dwoo->getLoader()->loadPlugin($type);
1076                }
1077                $this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null);
1078                $this->curBlock =& $this->stack[count($this->stack)-1];
1079        }
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                $output = '';
1091
1092                $pluginType = $this->getPluginType($type);
1093                if ($pluginType & Dwoo_Core::SMARTY_BLOCK) {
1094                        $type = 'smartyinterface';
1095                }
1096                while (true) {
1097                        while ($top = array_pop($this->stack)) {
1098                                if ($top['custom']) {
1099                                        $class = $top['class'];
1100                                } else {
1101                                        $class = 'Dwoo_Plugin_'.$top['type'];
1102                                }
1103                                if (count($this->stack)) {
1104                                        $this->curBlock =& $this->stack[count($this->stack)-1];
1105                                        $this->push(call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']), 0);
1106                                } else {
1107                                        $null = null;
1108                                        $this->curBlock =& $null;
1109                                        $output = call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']);
1110                                }
1111
1112                                if ($top['type'] === $type) {
1113                                        break 2;
1114                                }
1115                        }
1116
1117                        throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of type "'.$type.'" was closed but was not opened');
1118                        break;
1119                }
1120
1121                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                if ($closeAlong===true) {
1139                        while ($b = end($this->stack)) {
1140                                if ($b['type']===$type) {
1141                                        return $this->stack[key($this->stack)];
1142                                }
1143                                $this->push($this->removeTopBlock(), 0);
1144                        }
1145                } else {
1146                        end($this->stack);
1147                        while ($b = current($this->stack)) {
1148                                if ($b['type']===$type) {
1149                                        return $this->stack[key($this->stack)];
1150                                }
1151                                prev($this->stack);
1152                        }
1153                }
1154
1155                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                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                $o = array_pop($this->stack);
1177                if ($o === null) {
1178                        throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of unknown type was closed but was not opened.');
1179                }
1180                if ($o['custom']) {
1181                        $class = $o['class'];
1182                } else {
1183                        $class = 'Dwoo_Plugin_'.$o['type'];
1184                }
1185
1186                $this->curBlock =& $this->stack[count($this->stack)-1];
1187
1188                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                foreach ($params as $k=>$p) {
1200                        if (is_array($p)) {
1201                                $params[$k] = $p[0];
1202                        }
1203                }
1204                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                foreach ($params as $k=>$p) {
1216                        if (is_array($p)) {
1217                                $params[$k] = $p[1];
1218                        }
1219                }
1220                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                foreach ($params as $k=>$p) {
1232                        if (is_array($p)) {
1233                                $params[$k] = isset($p[2]) ? $p[2] : 0;
1234                        }
1235                }
1236                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                if ($to === null) {
1253                        $to = strlen($in);
1254                }
1255                $first = substr($in, $from, 1);
1256
1257                if ($first === false) {
1258                        throw new Dwoo_Compilation_Exception($this, 'Unexpected EOF, a template tag was not closed');
1259                }
1260
1261                while ($first===" " || $first==="\n" || $first==="\t" || $first==="\r") {
1262                        if ($curBlock === 'root' && substr($in, $from, strlen($this->rd)) === $this->rd) {
1263                                // end template tag
1264                                $pointer += strlen($this->rd);
1265                                if ($this->debug) echo 'TEMPLATE PARSING ENDED<br />';
1266                                return false;
1267                        }
1268                        $from++;
1269                        if ($pointer !== null) {
1270                                $pointer++;
1271                        }
1272                        if ($from >= $to) {
1273                                if (is_array($parsingParams)) {
1274                                        return $parsingParams;
1275                                } else {
1276                                        return '';
1277                                }
1278                        }
1279                        $first = $in[$from];
1280                }
1281
1282                $substr = substr($in, $from, $to-$from);
1283
1284                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                $parsed = "";
1286
1287                if ($curBlock === 'root' && $first === '*') {
1288                        $src = $this->getTemplateSource();
1289                        $startpos = $this->getPointer() - strlen($this->ld);
1290                        if (substr($src, $startpos, strlen($this->ld)) === $this->ld) {
1291                                if ($startpos > 0) {
1292                                        do {
1293                                                $char = substr($src, --$startpos, 1);
1294                                                if ($char == "\n") {
1295                                                        $startpos++;
1296                                                        $whitespaceStart = true;
1297                                                        break;
1298                                                }
1299                                        } while ($startpos > 0 && ($char == ' ' || $char == "\t"));
1300                                }
1301
1302                                if (!isset($whitespaceStart)) {
1303                                        $startpos = $this->getPointer();
1304                                } else {
1305                                        $pointer -= $this->getPointer() - $startpos;
1306                                }
1307
1308                                if ($this->allowNestedComments && strpos($src, $this->ld.'*', $this->getPointer()) !== false) {
1309                                        $comOpen = $this->ld.'*';
1310                                        $comClose = '*'.$this->rd;
1311                                        $level = 1;
1312                                        $start = $startpos;
1313                                        $ptr = $this->getPointer() + '*';
1314
1315                                        while ($level > 0 && $ptr < strlen($src)) {
1316                                                $open = strpos($src, $comOpen, $ptr);
1317                                                $close = strpos($src, $comClose, $ptr);
1318
1319                                                if ($open !== false && $close !== false) {
1320                                                        if ($open < $close) {
1321                                                                $ptr = $open + strlen($comOpen);
1322                                                                $level++;
1323                                                        } else {
1324                                                                $ptr = $close + strlen($comClose);
1325                                                                $level--;
1326                                                        }
1327                                                } elseif ($open !== false) {
1328                                                        $ptr = $open + strlen($comOpen);
1329                                                        $level++;
1330                                                } elseif ($close !== false) {
1331                                                        $ptr = $close + strlen($comClose);
1332                                                        $level--;
1333                                                } else {
1334                                                        $ptr = strlen($src);
1335                                                }
1336                                        }
1337                                        $endpos = $ptr - strlen('*'.$this->rd);
1338                                } else {
1339                                        $endpos = strpos($src, '*'.$this->rd, $startpos);
1340                                        if ($endpos == false) {
1341                                                throw new Dwoo_Compilation_Exception($this, 'Un-ended comment');
1342                                        }
1343                                }
1344                                $pointer += $endpos - $startpos + strlen('*'.$this->rd);
1345                                if (isset($whitespaceStart) && preg_match('#^[\t ]*\r?\n#', substr($src, $endpos+strlen('*'.$this->rd)), $m)) {
1346                                        $pointer += strlen($m[0]);
1347                                        $this->curBlock['buffer'] = substr($this->curBlock['buffer'], 0, strlen($this->curBlock['buffer']) - ($this->getPointer() - $startpos - strlen($this->ld)));
1348                                }
1349                                return false;
1350                        }
1351                }
1352
1353                if ($first==='$') {
1354                        // var
1355                        $out = $this->parseVar($in, $from, $to, $parsingParams, $curBlock, $pointer);
1356                        $parsed = 'var';
1357                } elseif ($first==='%' && preg_match('#^%[a-z_]#i', $substr)) {
1358                        // const
1359                        $out = $this->parseConst($in, $from, $to, $parsingParams, $curBlock, $pointer);
1360                } elseif (($first==='"' || $first==="'") && !(is_array($parsingParams) && preg_match('#^([\'"])[a-z0-9_]+\1\s*=>?(?:\s+|[^=])#i', $substr))) {
1361                        // string
1362                        $out = $this->parseString($in, $from, $to, $parsingParams, $curBlock, $pointer);
1363                } 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                        $out = $this->parseFunction($in, $from, $to, $parsingParams, $curBlock, $pointer);
1366                        $parsed = 'func';
1367                } elseif ($first === ';') {
1368                        // instruction end
1369                        if ($this->debug) echo 'END OF INSTRUCTION<br />';
1370                        if ($pointer !== null) {
1371                                $pointer++;
1372                        }
1373                        return $this->parse($in, $from+1, $to, false, 'root', $pointer);
1374                } elseif ($curBlock === 'root' && preg_match('#^/([a-z_][a-z0-9_]*)?#i', $substr, $match)) {
1375                        // close block
1376                        if (!empty($match[1]) && $match[1] == 'else') {
1377                                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                        if (!empty($match[1]) && $match[1] == 'elseif') {
1380                                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                        if ($pointer !== null) {
1383                                $pointer += strlen($match[0]);
1384                        }
1385                        if (empty($match[1])) {
1386                                if ($this->curBlock['type'] == 'else' || $this->curBlock['type'] == 'elseif') {
1387                                        $pointer -= strlen($match[0]);
1388                                }
1389                                if ($this->debug) echo 'TOP BLOCK CLOSED<br />';
1390                                return $this->removeTopBlock();
1391                        } else {
1392                                if ($this->debug) echo 'BLOCK OF TYPE '.$match[1].' CLOSED<br />';
1393                                return $this->removeBlock($match[1]);
1394                        }
1395                } elseif ($curBlock === 'root' && substr($substr, 0, strlen($this->rd)) === $this->rd) {
1396                        // end template tag
1397                        if ($this->debug) echo 'TAG PARSING ENDED<br />';
1398                        $pointer += strlen($this->rd);
1399                        return false;
1400                } elseif (is_array($parsingParams) && preg_match('#^(([\'"]?)[a-z0-9_]+\2\s*='.($curBlock === 'array' ? '>?':'').')(?:\s+|[^=]).*#i', $substr, $match)) {
1401                        // named parameter
1402                        if ($this->debug) echo 'NAMED PARAM FOUND<br />';
1403                        $len = strlen($match[1]);
1404                        while (substr($in, $from+$len, 1)===' ') {
1405                                $len++;
1406                        }
1407                        if ($pointer !== null) {
1408                                $pointer += $len;
1409                        }
1410
1411                        $output = array(trim($match[1], " \t\r\n=>'\""), $this->parse($in, $from+$len, $to, false, 'namedparam', $pointer));
1412
1413                        $parsingParams[] = $output;
1414                        return $parsingParams;
1415                } elseif (preg_match('#^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*::\$[a-z0-9_]+)#i', $substr, $match)) {
1416                        // static member access
1417                        $parsed = 'var';
1418                        if (is_array($parsingParams)) {
1419                                $parsingParams[] = array($match[1], $match[1]);
1420                                $out = $parsingParams;
1421                        } else {
1422                                $out = $match[1];
1423                        }
1424                        $pointer += strlen($match[1]);
1425                } elseif ($substr!=='' && (is_array($parsingParams) || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'expression')) {
1426                        // unquoted string, bool or number
1427                        $out = $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1428                } else {
1429                        // parse error
1430                        throw new Dwoo_Compilation_Exception($this, 'Parse error in "'.substr($in, $from, $to-$from).'"');
1431                }
1432
1433                if (empty($out)) {
1434                        return '';
1435                }
1436
1437                $substr = substr($in, $pointer, $to-$pointer);
1438
1439                // var parsed, check if any var-extension applies
1440                if ($parsed==='var') {
1441                        if (preg_match('#^\s*([/%+*-])\s*([a-z0-9]|\$)#i', $substr, $match)) {
1442                                if($this->debug) echo 'PARSING POST-VAR EXPRESSION '.$substr.'<br />';
1443                                // parse expressions
1444                                $pointer += strlen($match[0]) - 1;
1445                                if (is_array($parsingParams)) {
1446                                        if ($match[2] == '$') {
1447                                                $expr = $this->parseVar($in, $pointer, $to, array(), $curBlock, $pointer);
1448                                        } else {
1449                                                $expr = $this->parse($in, $pointer, $to, array(), 'expression', $pointer);
1450                                        }
1451                                        $out[count($out)-1][0] .= $match[1] . $expr[0][0];
1452                                        $out[count($out)-1][1] .= $match[1] . $expr[0][1];
1453                                } else {
1454                                        if ($match[2] == '$') {
1455                                                $expr = $this->parseVar($in, $pointer, $to, false, $curBlock, $pointer);
1456                                        } else {
1457                                                $expr = $this->parse($in, $pointer, $to, false, 'expression', $pointer);
1458                                        }
1459                                        if (is_array($out) && is_array($expr)) {
1460                                                $out[0] .= $match[1] . $expr[0];
1461                                                $out[1] .= $match[1] . $expr[1];
1462                                        } elseif (is_array($out)) {
1463                                                $out[0] .= $match[1] . $expr;
1464                                                $out[1] .= $match[1] . $expr;
1465                                        } elseif (is_array($expr)) {
1466                                                $out .= $match[1] . $expr[0];
1467                                        } else {
1468                                                $out .= $match[1] . $expr;
1469                                        }
1470                                }
1471                        } else if ($curBlock === 'root' && preg_match('#^(\s*(?:[+/*%-.]=|=|\+\+|--)\s*)(.*)#s', $substr, $match)) {
1472                                if($this->debug) echo 'PARSING POST-VAR ASSIGNMENT '.$substr.'<br />';
1473                                // parse assignment
1474                                $value = $match[2];
1475                                $operator = trim($match[1]);
1476                                if (substr($value, 0, 1) == '=') {
1477                                        throw new Dwoo_Compilation_Exception($this, 'Unexpected "=" in <em>'.$substr.'</em>');
1478                                }
1479
1480                                if ($pointer !== null) {
1481                                        $pointer += strlen($match[1]);
1482                                }
1483
1484                                if ($operator !== '++' && $operator !== '--') {
1485                                        $parts = array();
1486                                        $ptr = 0;
1487                                        $parts = $this->parse($value, 0, strlen($value), $parts, 'condition', $ptr);
1488                                        $pointer += $ptr;
1489
1490                                        // load if plugin
1491                                        try {
1492                                                $this->getPluginType('if');
1493                                        } catch (Dwoo_Exception $e) {
1494                                                throw new Dwoo_Compilation_Exception($this, 'Assignments require the "if" plugin to be accessible');
1495                                        }
1496
1497                                        $parts = $this->mapParams($parts, array('Dwoo_Plugin_if', 'init'), 1);
1498                                        $tokens = $this->getParamTokens($parts);
1499                                        $parts = $this->getCompiledParams($parts);
1500
1501                                        $value = Dwoo_Plugin_if::replaceKeywords($parts['*'], $tokens['*'], $this);
1502                                        $echo = '';
1503                                } else {
1504                                        $value = array();
1505                                        $echo = 'echo ';
1506                                }
1507
1508                                if ($this->autoEscape) {
1509                                        $out = preg_replace('#\(is_string\(\$tmp=(.+?)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $out);
1510                                }
1511                                $out = Dwoo_Compiler::PHP_OPEN. $echo . $out . $operator . implode(' ', $value) . Dwoo_Compiler::PHP_CLOSE;
1512                        } else if ($curBlock === 'array' && is_array($parsingParams) && preg_match('#^(\s*=>?\s*)#', $substr, $match)) {
1513                                // parse namedparam with var as name (only for array)
1514                                if ($this->debug) echo 'VARIABLE NAMED PARAM (FOR ARRAY) FOUND<br />';
1515                                $len = strlen($match[1]);
1516                                $var = $out[count($out)-1];
1517                                $pointer += $len;
1518
1519                                $output = array($var[0], $this->parse($substr, $len, null, false, 'namedparam', $pointer));
1520
1521                                $parsingParams[] = $output;
1522                                return $parsingParams;
1523                        }
1524                }
1525
1526                if ($curBlock !== 'modifier' && ($parsed === 'func' || $parsed === 'var') && preg_match('#^\|@?[a-z0-9_]+(:.*)?#i', $substr, $match)) {
1527                        // parse modifier on funcs or vars
1528                        $srcPointer = $pointer;
1529                        if (is_array($parsingParams)) {
1530                                $tmp = $this->replaceModifiers(array(null, null, $out[count($out)-1][0], $match[0]), 'var', $pointer);
1531                                $out[count($out)-1][0] = $tmp;
1532                                $out[count($out)-1][1] .= substr($substr, $srcPointer, $srcPointer - $pointer);
1533                        } else {
1534                                $out = $this->replaceModifiers(array(null, null, $out, $match[0]), 'var', $pointer);
1535                        }
1536                }
1537
1538                // func parsed, check if any func-extension applies
1539                if ($parsed==='func' && preg_match('#^->[a-z0-9_]+(\s*\(.+|->[a-z_].*)?#is', $substr, $match)) {
1540                        // parse method call or property read
1541                        $ptr = 0;
1542
1543                        if (is_array($parsingParams)) {
1544                                $output = $this->parseMethodCall($out[count($out)-1][1], $match[0], $curBlock, $ptr);
1545
1546                                $out[count($out)-1][0] = $output;
1547                                $out[count($out)-1][1] .= substr($match[0], 0, $ptr);
1548                        } else {
1549                                $out = $this->parseMethodCall($out, $match[0], $curBlock, $ptr);
1550                        }
1551
1552                        $pointer += $ptr;
1553                }
1554
1555                if ($curBlock === 'root' && substr($out, 0, strlen(self::PHP_OPEN)) !== self::PHP_OPEN) {
1556                        return self::PHP_OPEN .'echo '.$out.';'. self::PHP_CLOSE;
1557                } else {
1558                        return $out;
1559                }
1560        }
1561
1562        /**
1563         * parses a function call
1564         *
1565         * @param string $in the string within which we must parse something
1566         * @param int $from the starting offset of the parsed area
1567         * @param int $to the ending offset of the parsed area
1568         * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1569         * @param string $curBlock the current parser-block being processed
1570         * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1571         * @return string parsed values
1572         */
1573        protected function parseFunction($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
1574        {
1575                $cmdstr = substr($in, $from, $to-$from);
1576                preg_match('/^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?)(\s*'.$this->rdr.'|\s*;)?/i', $cmdstr, $match);
1577
1578                if (empty($match[1])) {
1579                        throw new Dwoo_Compilation_Exception($this, 'Parse error, invalid function name : '.substr($cmdstr, 0, 15));
1580                }
1581
1582                $func = $match[1];
1583
1584                if (!empty($match[2])) {
1585                        $cmdstr = $match[1];
1586                }
1587
1588                if ($this->debug) echo 'FUNC FOUND ('.$func.')<br />';
1589
1590                $paramsep = '';
1591
1592                if (is_array($parsingParams) || $curBlock != 'root') {
1593                        $paramspos = strpos($cmdstr, '(');
1594                        $paramsep = ')';
1595                } elseif(preg_match_all('#^\s*[\\\\:a-z0-9_]+(\s*\(|\s+[^(])#i', $cmdstr, $match, PREG_OFFSET_CAPTURE)) {
1596                        $paramspos = $match[1][0][1];
1597                        $paramsep = substr($match[1][0][0], -1) === '(' ? ')':'';
1598                        if($paramsep === ')') {
1599                                $paramspos += strlen($match[1][0][0]) - 1;
1600                                if(substr($cmdstr, 0, 2) === 'if' || substr($cmdstr, 0, 6) === 'elseif') {
1601                                        $paramsep = '';
1602                                        if(strlen($match[1][0][0]) > 1) {
1603                                                $paramspos--;
1604                                        }
1605                                }
1606                        }
1607                } else {
1608                        $paramspos = false;
1609                }
1610
1611                $state = 0;
1612
1613                if ($paramspos === false) {
1614                        $params = array();
1615
1616                        if ($curBlock !== 'root') {
1617                                return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1618                        }
1619                } else {
1620                        if ($curBlock === 'condition') {
1621                                // load if plugin
1622                                $this->getPluginType('if');
1623
1624                                if (Dwoo_Plugin_if::replaceKeywords(array($func), array(self::T_UNQUOTED_STRING), $this) !== array($func)) {
1625                                        return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
1626                                }
1627                        }
1628                        $whitespace = strlen(substr($cmdstr, strlen($func), $paramspos-strlen($func)));
1629                        $paramstr = substr($cmdstr, $paramspos+1);
1630                        if (substr($paramstr, -1, 1) === $paramsep) {
1631                                $paramstr = substr($paramstr, 0, -1);
1632                        }
1633
1634                        if (strlen($paramstr)===0) {
1635                                $params = array();
1636                                $paramstr = '';
1637                        } else {
1638                                $ptr = 0;
1639                                $params = array();
1640                                if ($func === 'empty') {
1641                                        $params = $this->parseVar($paramstr, $ptr, strlen($paramstr), $params, 'root', $ptr);
1642                                } else {
1643                                        while ($ptr < strlen($paramstr)) {
1644                                                while (true) {
1645                                                        if ($ptr >= strlen($paramstr)) {
1646                                                                break 2;
1647                                                        }
1648
1649                                                        if ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === ')') {
1650                                                                if ($this->debug) echo 'PARAM PARSING ENDED, ")" FOUND, POINTER AT '.$ptr.'<br/>';
1651                                                                break 2;
1652                                                        } elseif ($paramstr[$ptr] === ';') {
1653                                                                $ptr++;
1654                                                                if ($this->debug) echo 'PARAM PARSING ENDED, ";" FOUND, POINTER AT '.$ptr.'<br/>';
1655                                                                break 2;
1656                                                        } elseif ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === '/') {
1657                                                                if ($this->debug) echo 'PARAM PARSING ENDED, "/" FOUND, POINTER AT '.$ptr.'<br/>';
1658                                                                break 2;
1659                                                        } elseif (substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
1660                                                                if ($this->debug) echo 'PARAM PARSING ENDED, RIGHT DELIMITER FOUND, POINTER AT '.$ptr.'<br/>';
1661                                                                break 2;
1662                                                        }
1663
1664                                                        if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === ',' || $paramstr[$ptr] === "\r" || $paramstr[$ptr] === "\n" || $paramstr[$ptr] === "\t") {
1665                                                                $ptr++;
1666                                                        } else {
1667                                                                break;
1668                                                        }
1669                                                }
1670
1671                                                if ($this->debug) echo 'FUNC START PARAM PARSING WITH POINTER AT '.$ptr.'<br/>';
1672
1673                                                if ($func === 'if' || $func === 'elseif' || $func === 'tif') {
1674                                                        $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'condition', $ptr);
1675                                                } elseif ($func === 'array') {
1676                                                        $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'array', $ptr);
1677                                                } else {
1678                                                        $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'function', $ptr);
1679                                                }
1680
1681                                                if ($this->debug) echo 'PARAM PARSED, POINTER AT '.$ptr.' ('.substr($paramstr, $ptr-1, 3).')<br/>';
1682                                        }
1683                                }
1684                                $paramstr = substr($paramstr, 0, $ptr);
1685                                $state = 0;
1686                                foreach ($params as $k=>$p) {
1687                                        if (is_array($p) && is_array($p[1])) {
1688                                                $state |= 2;
1689                                        } else {
1690                                                if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m) && $func !== 'array') {
1691                                                        $params[$k] = array($m[2], array('true', 'true'));
1692                                                } else {
1693                                                        if ($state & 2 && $func !== 'array') {
1694                                                                throw new Dwoo_Compilation_Exception($this, 'You can not use an unnamed parameter after a named one');
1695                                                        }
1696                                                        $state |= 1;
1697                                                }
1698                                        }
1699                                }
1700                        }
1701                }
1702
1703                if ($pointer !== null) {
1704                        $pointer += (isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func) + (isset($whitespace) ? $whitespace : 0);
1705                        if ($this->debug) echo 'FUNC ADDS '.((isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func)).' TO POINTER<br/>';
1706                }
1707
1708                if ($curBlock === 'method' || $func === 'do' || strstr($func, '::') !== false) {
1709                        // handle static method calls with security policy
1710                        if (strstr($func, '::') !== false && $this->securityPolicy !== null && $this->securityPolicy->isMethodAllowed(explode('::', strtolower($func))) !== true) {
1711                                throw new Dwoo_Security_Exception('Call to a disallowed php function : '.$func);
1712                        }
1713                        $pluginType = Dwoo::NATIVE_PLUGIN;
1714                } else {
1715                        $pluginType = $this->getPluginType($func);
1716                }
1717
1718                // blocks
1719                if ($pluginType & Dwoo_Core::BLOCK_PLUGIN) {
1720                        if ($curBlock !== 'root' || is_array($parsingParams)) {
1721                                throw new Dwoo_Compilation_Exception($this, 'Block plugins can not be used as other plugin\'s arguments');
1722                        }
1723                        if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
1724                                return $this->addCustomBlock($func, $params, $state);
1725                        } else {
1726                                return $this->addBlock($func, $params, $state);
1727                        }
1728                } elseif ($pluginType & Dwoo_Core::SMARTY_BLOCK) {
1729                        if ($curBlock !== 'root' || is_array($parsingParams)) {
1730                                throw new Dwoo_Compilation_Exception($this, 'Block plugins can not be used as other plugin\'s arguments');
1731                        }
1732
1733                        if ($state & 2) {
1734                                array_unshift($params, array('__functype', array($pluginType, $pluginType)));
1735                                array_unshift($params, array('__funcname', array($func, $func)));
1736                        } else {
1737                                array_unshift($params, array($pluginType, $pluginType));
1738                                array_unshift($params, array($func, $func));
1739                        }
1740
1741                        return $this->addBlock('smartyinterface', $params, $state);
1742                }
1743
1744                // funcs
1745                if ($pluginType & Dwoo_Core::NATIVE_PLUGIN || $pluginType & Dwoo_Core::SMARTY_FUNCTION || $pluginType & Dwoo_Core::SMARTY_BLOCK) {
1746                        $params = $this->mapParams($params, null, $state);
1747                } elseif ($pluginType & Dwoo_Core::CLASS_PLUGIN) {
1748                        if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
1749                                $params = $this->mapParams($params, array($this->customPlugins[$func]['class'], $this->customPlugins[$func]['function']), $state);
1750                        } else {
1751                                $params = $this->mapParams($params, array('Dwoo_Plugin_'.$func, ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'), $state);
1752                        }
1753                } elseif ($pluginType & Dwoo_Core::FUNC_PLUGIN) {
1754                        if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
1755                                $params = $this->mapParams($params, $this->customPlugins[$func]['callback'], $state);
1756                        } else {
1757                                $params = $this->mapParams($params, 'Dwoo_Plugin_'.$func.(($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? '_compile' : ''), $state);
1758                        }
1759                } elseif ($pluginType & Dwoo_Core::SMARTY_MODIFIER) {
1760                        $output = 'smarty_modifier_'.$func.'('.implode(', ', $params).')';
1761                } elseif ($pluginType & Dwoo_Core::PROXY_PLUGIN) {
1762                        $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
1763                } elseif ($pluginType & Dwoo_Core::TEMPLATE_PLUGIN) {
1764                        // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
1765                        $map = array();
1766                        foreach ($this->templatePlugins[$func]['params'] as $param=>$defValue) {
1767                                if ($param == 'rest') {
1768                                        $param = '*';
1769                                }
1770                                $hasDefault = $defValue !== null;
1771                                if ($defValue === 'null') {
1772                                        $defValue = null;
1773                                } elseif ($defValue === 'false') {
1774                                        $defValue = false;
1775                                } elseif ($defValue === 'true') {
1776                                        $defValue = true;
1777                                } elseif (preg_match('#^([\'"]).*?\1$#', $defValue)) {
1778                                        $defValue = substr($defValue, 1, -1);
1779                                }
1780                                $map[] = array($param, $hasDefault, $defValue);
1781                        }
1782
1783                        $params = $this->mapParams($params, null, $state, $map);
1784                }
1785
1786                // only keep php-syntax-safe values for non-block plugins
1787                $tokens = array();
1788                foreach ($params as $k => $p) {
1789                        $tokens[$k] = isset($p[2]) ? $p[2] : 0;
1790                        $params[$k] = $p[0];
1791                }
1792                if ($pluginType & Dwoo_Core::NATIVE_PLUGIN) {
1793                        if ($func === 'do') {
1794                                if (isset($params['*'])) {
1795                                        $output = implode(';', $params['*']).';';
1796                                } else {
1797                                        $output = '';
1798                                }
1799
1800                                if (is_array($parsingParams) || $curBlock !== 'root') {
1801                                        throw new Dwoo_Compilation_Exception($this, 'Do can not be used inside another function or block');
1802                                } else {
1803                                        return self::PHP_OPEN.$output.self::PHP_CLOSE;
1804                                }
1805                        } else {
1806                                if (isset($params['*'])) {
1807                                        $output = $func.'('.implode(', ', $params['*']).')';
1808                                } else {
1809                                        $output = $func.'()';
1810                                }
1811                        }
1812                } elseif ($pluginType & Dwoo_Core::FUNC_PLUGIN) {
1813                        if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) {
1814                                if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
1815                                        $funcCompiler = $this->customPlugins[$func]['callback'];
1816                                } else {
1817                                        $funcCompiler = 'Dwoo_Plugin_'.$func.'_compile';
1818                                }
1819                                array_unshift($params, $this);
1820                                if ($func === 'tif') {
1821                                        $params[] = $tokens;
1822                                }
1823                                $output = call_user_func_array($funcCompiler, $params);
1824                        } else {
1825                                array_unshift($params, '$this');
1826                                $params = self::implode_r($params);
1827
1828                                if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
1829                                        $callback = $this->customPlugins[$func]['callback'];
1830                                        $output = 'call_user_func(\''.$callback.'\', '.$params.')';
1831                                } else {
1832                                        $output = 'Dwoo_Plugin_'.$func.'('.$params.')';
1833                                }
1834                        }
1835                } elseif ($pluginType & Dwoo_Core::CLASS_PLUGIN) {
1836                        if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) {
1837                                if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
1838                                        $callback = $this->customPlugins[$func]['callback'];
1839                                        if (!is_array($callback)) {
1840                                                if (!method_exists($callback, 'compile')) {
1841                                                        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');
1842                                                }
1843                                                if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
1844                                                        $funcCompiler = array($callback, 'compile');
1845                                                } else {
1846                                                        $funcCompiler = array(new $callback, 'compile');
1847                                                }
1848                                        } else {
1849                                                $funcCompiler = $callback;
1850                                        }
1851                                } else {
1852                                        $funcCompiler = array('Dwoo_Plugin_'.$func, 'compile');
1853                                        array_unshift($params, $this);
1854                                }
1855                                $output = call_user_func_array($funcCompiler, $params);
1856                        } else {
1857                                $params = self::implode_r($params);
1858                                if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
1859                                        $callback = $this->customPlugins[$func]['callback'];
1860                                        if (!is_array($callback)) {
1861                                                if (!method_exists($callback, 'process')) {
1862                                                        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');
1863                                                }
1864                                                if (($ref = new ReflectionMethod($callback, 'process')) && $ref->isStatic()) {
1865                                                        $output = 'call_user_func(array(\''.$callback.'\', \'process\'), '.$params.')';
1866                                                } else {
1867                                                        $output = 'call_user_func(array($this->getObjectPlugin(\''.$callback.'\'), \'process\'), '.$params.')';
1868                                                }
1869                                        } elseif (is_object($callback[0])) {
1870                                                $output = 'call_user_func(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), '.$params.')';
1871                                        } elseif (($ref = new ReflectionMethod($callback[0], $callback[1])) && $ref->isStatic()) {
1872                                                $output = 'call_user_func(array(\''.$callback[0].'\', \''.$callback[1].'\'), '.$params.')';
1873                                        } else {
1874                                                $output = 'call_user_func(array($this->getObjectPlugin(\''.$callback[0].'\'), \''.$callback[1].'\'), '.$params.')';
1875                                        }
1876                                        if (empty($params)) {
1877                                                $output = substr($output, 0, -3).')';
1878                                        }
1879                                } else {
1880                                        $output = '$this->classCall(\''.$func.'\', array('.$params.'))';
1881                                }
1882                        }
1883                } elseif ($pluginType & Dwoo_Core::PROXY_PLUGIN) {
1884                        $output = call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params);
1885                } elseif ($pluginType & Dwoo_Core::SMARTY_FUNCTION) {
1886                        if (isset($params['*'])) {
1887                                $params = self::implode_r($params['*'], true);
1888                        } else {
1889                                $params = '';
1890                        }
1891
1892                        if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
1893                                $callback = $this->customPlugins[$func]['callback'];
1894                                if (is_array($callback)) {
1895                                        if (is_object($callback[0])) {
1896                                                $output = 'call_user_func_array(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array(array('.$params.'), $this))';
1897                                        } else {
1898                                                $output = 'call_user_func_array(array(\''.$callback[0].'\', \''.$callback[1].'\'), array(array('.$params.'), $this))';
1899                                        }
1900                                } else {
1901                                        $output = $callback.'(array('.$params.'), $this)';
1902                                }
1903                        } else {
1904                                $output = 'smarty_function_'.$func.'(array('.$params.'), $this)';
1905                        }
1906                } elseif ($pluginType & Dwoo_Core::TEMPLATE_PLUGIN) {
1907                        array_unshift($params, '$this');
1908                        $params = self::implode_r($params);
1909                        $output = 'Dwoo_Plugin_'.$func.'_'.$this->templatePlugins[$func]['uuid'].'('.$params.')';
1910                        $this->templatePlugins[$func]['called'] = true;
1911                }
1912
1913                if (is_array($parsingParams)) {
1914                        $parsingParams[] = array($output, $output);
1915                        return $parsingParams;
1916                } elseif ($curBlock === 'namedparam') {
1917                        return array($output, $output);
1918                } else {
1919                        return $output;
1920                }
1921        }
1922
1923        /**
1924         * parses a string
1925         *
1926         * @param string $in the string within which we must parse something
1927         * @param int $from the starting offset of the parsed area
1928         * @param int $to the ending offset of the parsed area
1929         * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1930         * @param string $curBlock the current parser-block being processed
1931         * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1932         * @return string parsed values
1933         */
1934        protected function parseString($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
1935        {
1936                $substr = substr($in, $from, $to-$from);
1937                $first = $substr[0];
1938
1939                if ($this->debug) echo 'STRING FOUND (in '.htmlentities(substr($in, $from, min($to-$from, 50))).(($to-$from) > 50 ? '...':'').')<br />';
1940                $strend = false;
1941                $o = $from+1;
1942                while ($strend === false) {
1943                        $strend = strpos($in, $first, $o);
1944                        if ($strend === false) {
1945                                throw new Dwoo_Compilation_Exception($this, 'Unfinished string, started with '.substr($in, $from, $to-$from));
1946                        }
1947                        if (substr($in, $strend-1, 1) === '\\') {
1948                                $o = $strend+1;
1949                                $strend = false;
1950                        }
1951                }
1952                if ($this->debug) echo 'STRING DELIMITED: '.substr($in, $from, $strend+1-$from).'<br/>';
1953
1954                $srcOutput = substr($in, $from, $strend+1-$from);
1955
1956                if ($pointer !== null) {
1957                        $pointer += strlen($srcOutput);
1958                }
1959
1960                $output = $this->replaceStringVars($srcOutput, $first);
1961
1962                // handle modifiers
1963                if ($curBlock !== 'modifier' && preg_match('#^((?:\|(?:@?[a-z0-9_]+(?::.*)*))+)#i', substr($substr, $strend+1-$from), $match)) {
1964                        $modstr = $match[1];
1965
1966                        if ($curBlock === 'root' && substr($modstr, -1) === '}') {
1967                                $modstr = substr($modstr, 0, -1);
1968                        }
1969                        $modstr = str_replace('\\'.$first, $first, $modstr);
1970                        $ptr = 0;
1971                        $output = $this->replaceModifiers(array(null, null, $output, $modstr), 'string', $ptr);
1972
1973                        $strend += $ptr;
1974                        if ($pointer !== null) {
1975                                $pointer += $ptr;
1976                        }
1977                        $srcOutput .= substr($substr, $strend+1-$from, $ptr);
1978                }
1979
1980                if (is_array($parsingParams)) {
1981                        $parsingParams[] = array($output, substr($srcOutput, 1, -1));
1982                        return $parsingParams;
1983                } elseif ($curBlock === 'namedparam') {
1984                        return array($output, substr($srcOutput, 1, -1));
1985                } else {
1986                        return $output;
1987                }
1988        }
1989
1990        /**
1991         * parses a constant
1992         *
1993         * @param string $in the string within which we must parse something
1994         * @param int $from the starting offset of the parsed area
1995         * @param int $to the ending offset of the parsed area
1996         * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
1997         * @param string $curBlock the current parser-block being processed
1998         * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
1999         * @return string parsed values
2000         */
2001        protected function parseConst($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
2002        {
2003                $substr = substr($in, $from, $to-$from);
2004
2005                if ($this->debug) {
2006                        echo 'CONST FOUND : '.$substr.'<br />';
2007                }
2008
2009                if (!preg_match('#^%([a-z0-9_:]+)#i', $substr, $m)) {
2010                        throw new Dwoo_Compilation_Exception($this, 'Invalid constant');
2011                }
2012
2013                if ($pointer !== null) {
2014                        $pointer += strlen($m[0]);
2015                }
2016
2017                $output = $this->parseConstKey($m[1], $curBlock);
2018
2019                if (is_array($parsingParams)) {
2020                        $parsingParams[] = array($output, $m[1]);
2021                        return $parsingParams;
2022                } elseif ($curBlock === 'namedparam') {
2023                        return array($output, $m[1]);
2024                } else {
2025                        return $output;
2026                }
2027        }
2028
2029        /**
2030         * parses a constant
2031         *
2032         * @param string $key the constant to parse
2033         * @param string $curBlock the current parser-block being processed
2034         * @return string parsed constant
2035         */
2036        protected function parseConstKey($key, $curBlock)
2037        {
2038                if ($this->securityPolicy !== null && $this->securityPolicy->getConstantHandling() === Dwoo_Security_Policy::CONST_DISALLOW) {
2039                        return 'null';
2040                }
2041
2042                if ($curBlock !== 'root') {
2043                        $output = '(defined("'.$key.'") ? '.$key.' : null)';
2044                } else {
2045                        $output = $key;
2046                }
2047
2048                return $output;
2049        }
2050
2051        /**
2052         * parses a variable
2053         *
2054         * @param string $in the string within which we must parse something
2055         * @param int $from the starting offset of the parsed area
2056         * @param int $to the ending offset of the parsed area
2057         * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
2058         * @param string $curBlock the current parser-block being processed
2059         * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
2060         * @return string parsed values
2061         */
2062        protected function parseVar($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
2063        {
2064                $substr = substr($in, $from, $to-$from);
2065
2066                if (preg_match('#(\$?\.?[a-z0-9_:]*(?:(?:(?:\.|->)(?:[a-z0-9_:]+|(?R))|\[(?:[a-z0-9_:]+|(?R)|(["\'])[^\2]*?\2)\]))*)' . // var key
2067                        ($curBlock==='root' || $curBlock==='function' || $curBlock==='namedparam' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='expression' ? '(\(.*)?' : '()') . // method call
2068                        ($curBlock==='root' || $curBlock==='function' || $curBlock==='namedparam' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='delimited_string' ? '((?:(?:[+/*%=-])(?:(?<!=)=?-?[$%][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|(?<!=)=?-?[0-9.,]*|[+-]))*)':'()') . // simple math expressions
2069                        ($curBlock!=='modifier' ? '((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').*?\5|:[^`]*))*))+)?':'(())') . // modifiers
2070                        '#i', $substr, $match)) {
2071                        $key = substr($match[1], 1);
2072
2073                        $matchedLength = strlen($match[0]);
2074                        $hasModifiers = !empty($match[5]);
2075                        $hasExpression = !empty($match[4]);
2076                        $hasMethodCall = !empty($match[3]);
2077
2078                        if (substr($key, -1) == ".") {
2079                                $key = substr($key, 0, -1);
2080                                $matchedLength--;
2081                        }
2082
2083                        if ($hasMethodCall) {
2084                                $matchedLength -= strlen($match[3]) + strlen(substr($match[1], strrpos($match[1], '->')));
2085                                $key = substr($match[1], 1, strrpos($match[1], '->')-1);
2086                                $methodCall = substr($match[1], strrpos($match[1], '->')) . $match[3];
2087                        }
2088
2089                        if ($hasModifiers) {
2090                                $matchedLength -= strlen($match[5]);
2091                        }
2092
2093                        if ($pointer !== null) {
2094                                $pointer += $matchedLength;
2095                        }
2096
2097                        // replace useless brackets by dot accessed vars and strip enclosing quotes if present
2098                        $key = preg_replace('#\[(["\']?)([^$%\[.>-]+)\1\]#', '.$2', $key);
2099
2100                        if ($this->debug) {
2101                                if ($hasMethodCall) {
2102                                        echo 'METHOD CALL FOUND : $'.$key.substr($methodCall, 0, 30).'<br />';
2103                                } else {
2104                                        echo 'VAR FOUND : $'.$key.'<br />';
2105                                }
2106                        }
2107
2108                        $key = str_replace('"', '\\"', $key);
2109
2110                        $cnt=substr_count($key, '$');
2111                        if ($cnt > 0) {
2112                                $uid = 0;
2113                                $parsed = array($uid => '');
2114                                $current =& $parsed;
2115                                $curTxt =& $parsed[$uid++];
2116                                $tree = array();
2117                                $chars = str_split($key, 1);
2118                                $inSplittedVar = false;
2119                                $bracketCount = 0;
2120
2121                                while (($char = array_shift($chars)) !== null) {
2122                                        if ($char === '[') {
2123                                                if (count($tree) > 0) {
2124                                                        $bracketCount++;
2125                                                } else {
2126                                                        $tree[] =& $current;
2127                                                        $current[$uid] = array($uid+1 => '');
2128                                                        $current =& $current[$uid++];
2129                                                        $curTxt =& $current[$uid++];
2130                                                        continue;
2131                                                }
2132                                        } elseif ($char === ']') {
2133                                                if ($bracketCount > 0) {
2134                                                        $bracketCount--;
2135                                                } else {
2136                                                        $current =& $tree[count($tree)-1];
2137                                                        array_pop($tree);
2138                                                        if (current($chars) !== '[' && current($chars) !== false && current($chars) !== ']') {
2139                                                                $current[$uid] = '';
2140                                                                $curTxt =& $current[$uid++];
2141                                                        }
2142                                                        continue;
2143                                                }
2144                                        } elseif ($char === '$') {
2145                                                if (count($tree) == 0) {
2146                                                        $curTxt =& $current[$uid++];
2147                                                        $inSplittedVar = true;
2148                                                }
2149                                        } elseif (($char === '.' || $char === '-') && count($tree) == 0 && $inSplittedVar) {
2150                                                $curTxt =& $current[$uid++];
2151                                                $inSplittedVar = false;
2152                                        }
2153
2154                                        $curTxt .= $char;
2155                                }
2156                                unset($uid, $current, $curTxt, $tree, $chars);
2157
2158                                if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT : '.$key.'<br>';
2159
2160                                $key = $this->flattenVarTree($parsed);
2161
2162                                if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT DONE : '.$key.'<br>';
2163
2164                                $output = preg_replace('#(^""\.|""\.|\.""$|(\()""\.|\.""(\)))#', '$2$3', '$this->readVar("'.$key.'")');
2165                        } else {
2166                                $output = $this->parseVarKey($key, $hasModifiers ? 'modifier' : $curBlock);
2167                        }
2168
2169                        // methods
2170                        if ($hasMethodCall) {
2171                                $ptr = 0;
2172
2173                                $output = $this->parseMethodCall($output, $methodCall, $curBlock, $ptr);
2174
2175                                if ($pointer !== null) {
2176                                        $pointer += $ptr;
2177                                }
2178                                $matchedLength += $ptr;
2179                        }
2180
2181                        if ($hasExpression) {
2182                                // expressions
2183                                preg_match_all('#(?:([+/*%=-])(=?-?[%$][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|=?-?[0-9.,]+|\1))#i', $match[4], $expMatch);
2184
2185                                foreach ($expMatch[1] as $k=>$operator) {
2186                                        if (substr($expMatch[2][$k], 0, 1)==='=') {
2187                                                $assign = true;
2188                                                if ($operator === '=') {
2189                                                        throw new Dwoo_Compilation_Exception($this, 'Invalid expression <em>'.$substr.'</em>, can not use "==" in expressions');
2190                                                }
2191                                                if ($curBlock !== 'root') {
2192                                                        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"}');
2193                                                }
2194                                                $operator .= '=';
2195                                                $expMatch[2][$k] = substr($expMatch[2][$k], 1);
2196                                        }
2197
2198                                        if (substr($expMatch[2][$k], 0, 1)==='-' && strlen($expMatch[2][$k]) > 1) {
2199                                                $operator .= '-';
2200                                                $expMatch[2][$k] = substr($expMatch[2][$k], 1);
2201                                        }
2202                                        if (($operator==='+'||$operator==='-') && $expMatch[2][$k]===$operator) {
2203                                                $output = '('.$output.$operator.$operator.')';
2204                                                break;
2205                                        } elseif (substr($expMatch[2][$k], 0, 1) === '$') {
2206                                                $output = '('.$output.' '.$operator.' '.$this->parseVar($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').')';
2207                                        } elseif (substr($expMatch[2][$k], 0, 1) === '%') {
2208                                                $output = '('.$output.' '.$operator.' '.$this->parseConst($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').')';
2209                                        } elseif (!empty($expMatch[2][$k])) {
2210                                                $output = '('.$output.' '.$operator.' '.str_replace(',', '.', $expMatch[2][$k]).')';
2211                                        } else {
2212                                                throw new Dwoo_Compilation_Exception($this, 'Unfinished expression <em>'.$substr.'</em>, missing var or number after math operator');
2213                                        }
2214                                }
2215                        }
2216
2217                        if ($this->autoEscape === true && $curBlock !== 'condition') {
2218                                $output = '(is_string($tmp='.$output.') ? htmlspecialchars($tmp, ENT_QUOTES, $this->charset) : $tmp)';
2219                        }
2220
2221                        // handle modifiers
2222                        if ($curBlock !== 'modifier' && $hasModifiers) {
2223                                $ptr = 0;
2224                                $output = $this->replaceModifiers(array(null, null, $output, $match[5]), 'var', $ptr);
2225                                if ($pointer !== null) {
2226                                        $pointer += $ptr;
2227                                }
2228                                $matchedLength += $ptr;
2229                        }
2230
2231                        if (is_array($parsingParams)) {
2232                                $parsingParams[] = array($output, $key);
2233                                return $parsingParams;
2234                        } elseif ($curBlock === 'namedparam') {
2235                                return array($output, $key);
2236                        } elseif ($curBlock === 'string' || $curBlock === 'delimited_string') {
2237                                return array($matchedLength, $output);
2238                        } elseif ($curBlock === 'expression' || $curBlock === 'variable') {
2239                                return $output;
2240                        } elseif (isset($assign)) {
2241                                return self::PHP_OPEN.$output.';'.self::PHP_CLOSE;
2242                        } else {
2243                                return $output;
2244                        }
2245                } else {
2246                        if ($curBlock === 'string' || $curBlock === 'delimited_string') {
2247                                return array(0, '');
2248                        } else {
2249                                throw new Dwoo_Compilation_Exception($this, 'Invalid variable name <em>'.$substr.'</em>');
2250                        }
2251                }
2252        }
2253
2254        /**
2255         * parses any number of chained method calls/property reads
2256         *
2257         * @param string $output the variable or whatever upon which the method are called
2258         * @param string $methodCall method call source, starting at "->"
2259         * @param string $curBlock the current parser-block being processed
2260         * @param int $pointer a reference to a pointer that will be increased by the amount of characters parsed
2261         * @return string parsed call(s)/read(s)
2262         */
2263        protected function parseMethodCall($output, $methodCall, $curBlock, &$pointer)
2264        {
2265                $ptr = 0;
2266                $len = strlen($methodCall);
2267
2268                while ($ptr < $len) {
2269                        if (strpos($methodCall, '->', $ptr) === $ptr) {
2270                                $ptr += 2;
2271                        }
2272
2273                        if (in_array($methodCall[$ptr], array(';', ',', '/', ' ', "\t", "\r", "\n", ')', '+', '*', '%', '=', '-', '|')) || substr($methodCall, $ptr, strlen($this->rd)) === $this->rd) {
2274                                // break char found
2275                                break;
2276                        }
2277
2278                        if(!preg_match('/^([a-z0-9_]+)(\(.*?\))?/i', substr($methodCall, $ptr), $methMatch)) {
2279                                throw new Dwoo_Compilation_Exception($this, 'Invalid method name : '.substr($methodCall, $ptr, 20));
2280                        }
2281
2282                        if (empty($methMatch[2])) {
2283                                // property
2284                                if ($curBlock === 'root') {
2285                                        $output .= '->'.$methMatch[1];
2286                                } else {
2287                                        $output = '(($tmp = '.$output.') ? $tmp->'.$methMatch[1].' : null)';
2288                                }
2289                                $ptr += strlen($methMatch[1]);
2290                        } else {
2291                                // method
2292                                if (substr($methMatch[2], 0, 2) === '()') {
2293                                        $parsedCall = $methMatch[1].'()';
2294                                        $ptr += strlen($methMatch[1]) + 2;
2295                                } else {
2296                                        $parsedCall = $this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr);
2297                                }
2298                                if ($this->securityPolicy !== null) {
2299                                        $argPos = strpos($parsedCall, '(');
2300                                        $method = strtolower(substr($parsedCall, 0, $argPos));
2301                                        $args = substr($parsedCall, $argPos);
2302                                        if ($curBlock === 'root') {
2303                                                $output = '$this->getSecurityPolicy()->callMethod($this, '.$output.', '.var_export($method, true).', array'.$args.')';
2304                                        } else {
2305                                                $output = '(($tmp = '.$output.') ? $this->getSecurityPolicy()->callMethod($this, $tmp, '.var_export($method, true).', array'.$args.') : null)';
2306                                        }
2307                                } else {
2308                                        if ($curBlock === 'root') {
2309                                                $output .= '->'.$parsedCall;
2310                                        } else {
2311                                                $output = '(($tmp = '.$output.') ? $tmp->'.$parsedCall.' : null)';
2312                                        }
2313                                }
2314                        }
2315                }
2316
2317                $pointer += $ptr;
2318                return $output;
2319        }
2320
2321        /**
2322         * parses a constant variable (a variable that doesn't contain another variable) and preprocesses it to save runtime processing time
2323         *
2324         * @param string $key the variable to parse
2325         * @param string $curBlock the current parser-block being processed
2326         * @return string parsed variable
2327         */
2328        protected function parseVarKey($key, $curBlock)
2329        {
2330                if ($key === '') {
2331                        return '$this->scope';
2332                }
2333                if (substr($key, 0, 1) === '.') {
2334                        $key = 'dwoo'.$key;
2335                }
2336                if (preg_match('#dwoo\.(get|post|server|cookies|session|env|request)((?:\.[a-z0-9_-]+)+)#i', $key, $m)) {
2337                        $global = strtoupper($m[1]);
2338                        if ($global === 'COOKIES') {
2339                                $global = 'COOKIE';
2340                        }
2341                        $key = '$_'.$global;
2342                        foreach (explode('.', ltrim($m[2], '.')) as $part)
2343                                $key .= '['.var_export($part, true).']';
2344                        if ($curBlock === 'root') {
2345                                $output = $key;
2346                        } else {
2347                                $output = '(isset('.$key.')?'.$key.':null)';
2348                        }
2349                } elseif (preg_match('#dwoo\.const\.([a-z0-9_:]+)#i', $key, $m)) {
2350                        return $this->parseConstKey($m[1], $curBlock);
2351                } elseif ($this->scope !== null) {
2352                        if (strstr($key, '.') === false && strstr($key, '[') === false && strstr($key, '->') === false) {
2353                                if ($key === 'dwoo') {
2354                                        $output = '$this->globals';
2355                                } elseif ($key === '_root' || $key === '__') {
2356                                        $output = '$this->data';
2357                                } elseif ($key === '_parent' || $key === '_') {
2358                                        $output = '$this->readParentVar(1)';
2359                                } elseif ($key === '_key') {
2360                                        $output = '$tmp_key';
2361                                } else {
2362                                        if ($curBlock === 'root') {
2363                                                $output = '$this->scope["'.$key.'"]';
2364                                        } else {
2365                                                $output = '(isset($this->scope["'.$key.'"]) ? $this->scope["'.$key.'"] : null)';
2366                                        }
2367                                }
2368                        } else {
2369                                preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+|(\\\?[\'"])[^\3]*?\3)\]?#i', $key, $m);
2370
2371                                $i = $m[2][0];
2372                                if ($i === '_parent' || $i === '_') {
2373                                        $parentCnt = 0;
2374
2375                                        while (true) {
2376                                                $parentCnt++;
2377                                                array_shift($m[2]);
2378                                                array_shift($m[1]);
2379                                                if (current($m[2]) === '_parent') {
2380                                                        continue;
2381                                                }
2382                                                break;
2383                                        }
2384
2385                                        $output = '$this->readParentVar('.$parentCnt.')';
2386                                } else {
2387                                        if ($i === 'dwoo') {
2388                                                $output = '$this->globals';
2389                                                array_shift($m[2]);
2390                                                array_shift($m[1]);
2391                                        } elseif ($i === '_root' || $i === '__') {
2392                                                $output = '$this->data';
2393                                                array_shift($m[2]);
2394                                                array_shift($m[1]);
2395                                        } elseif ($i === '_key') {
2396                                                $output = '$tmp_key';
2397                                        } else {
2398                                                $output = '$this->scope';
2399                                        }
2400
2401                                        while (count($m[1]) && $m[1][0] !== '->') {
2402                                                $m[2][0] = preg_replace('/(^\\\([\'"])|\\\([\'"])$)/x', '$2$3', $m[2][0]);
2403                                                if(substr($m[2][0], 0, 1) == '"' || substr($m[2][0], 0, 1) == "'") {
2404                                                        $output .= '['.$m[2][0].']';
2405                                                } else {
2406                                                        $output .= '["'.$m[2][0].'"]';
2407                                                }
2408                                                array_shift($m[2]);
2409                                                array_shift($m[1]);
2410                                        }
2411
2412                                        if ($curBlock !== 'root') {
2413                                                $output = '(isset('.$output.') ? '.$output.':null)';
2414                                        }
2415                                }
2416
2417                                if (count($m[2])) {
2418                                        unset($m[0]);
2419                                        $output = '$this->readVarInto('.str_replace("\n", '', var_export($m, true)).', '.$output.', '.($curBlock == 'root' ? 'false': 'true').')';
2420                                }
2421                        }
2422                } else {
2423                        preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+)\]?#i', $key, $m);
2424                        unset($m[0]);
2425                        $output = '$this->readVar('.str_replace("\n", '', var_export($m, true)).')';
2426                }
2427
2428                return $output;
2429        }
2430
2431        /**
2432         * flattens a variable tree, this helps in parsing very complex variables such as $var.foo[$foo.bar->baz].baz,
2433         * it computes the contents of the brackets first and works out from there
2434         *
2435         * @param array $tree the variable tree parsed by he parseVar() method that must be flattened
2436         * @param bool $recursed leave that to false by default, it is only for internal use
2437         * @return string flattened tree
2438         */
2439        protected function flattenVarTree(array $tree, $recursed=false)
2440        {
2441                $out = $recursed ?  '".$this->readVarInto(' : '';
2442                foreach ($tree as $bit) {
2443                        if (is_array($bit)) {
2444                                $out.='.'.$this->flattenVarTree($bit, false);
2445                        } else {
2446                                $key = str_replace('"', '\\"', $bit);
2447
2448                                if (substr($key, 0, 1)==='$') {
2449                                        $out .= '".'.$this->parseVar($key, 0, strlen($key), false, 'variable').'."';
2450                                } else {
2451                                        $cnt = substr_count($key, '$');
2452
2453                                        if ($this->debug) echo 'PARSING SUBVARS IN : '.$key.'<br>';
2454                                        if ($cnt > 0) {
2455                                                while (--$cnt >= 0) {
2456                                                        if (isset($last)) {
2457                                                                $last = strrpos($key, '$', - (strlen($key) - $last + 1));
2458                                                        } else {
2459                                                                $last = strrpos($key, '$');
2460                                                        }
2461                                                        preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*'.
2462                                                                          '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', substr($key, $last), $submatch);
2463
2464                                                        $len = strlen($submatch[0]);
2465                                                        $key = substr_replace(
2466                                                                $key,
2467                                                                preg_replace_callback(
2468                                                                        '#(\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)'.
2469                                                                        '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i',
2470                                                                        array($this, 'replaceVarKeyHelper'), substr($key, $last, $len)
2471                                                                ),
2472                                                                $last,
2473                                                                $len
2474                                                        );
2475                                                        if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT DONE : '.$key.'<br>';
2476                                                }
2477                                                unset($last);
2478
2479                                                $out .= $key;
2480                                        } else {
2481                                                $out .= $key;
2482                                        }
2483                                }
2484                        }
2485                }
2486                $out .= $recursed ? ', true)."' : '';
2487                return $out;
2488        }
2489
2490        /**
2491         * helper function that parses a variable
2492         *
2493         * @param array $match the matched variable, array(1=>"string match")
2494         * @return string parsed variable
2495         */
2496        protected function replaceVarKeyHelper($match)
2497        {
2498                return '".'.$this->parseVar($match[0], 0, strlen($match[0]), false, 'variable').'."';
2499        }
2500
2501        /**
2502         * parses various constants, operators or non-quoted strings
2503         *
2504         * @param string $in the string within which we must parse something
2505         * @param int $from the starting offset of the parsed area
2506         * @param int $to the ending offset of the parsed area
2507         * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
2508         * @param string $curBlock the current parser-block being processed
2509         * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
2510         * @return string parsed values
2511         */
2512        protected function parseOthers($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
2513        {
2514                $first = $in[$from];
2515                $substr = substr($in, $from, $to-$from);
2516
2517                $end = strlen($substr);
2518
2519                if ($curBlock === 'condition') {
2520                        $breakChars = array('(', ')', ' ', '||', '&&', '|', '&', '>=', '<=', '===', '==', '=', '!==', '!=', '<<', '<', '>>', '>', '^', '~', ',', '+', '-', '*', '/', '%', '!', '?', ':', $this->rd, ';');
2521                } elseif ($curBlock === 'modifier') {
2522                        $breakChars = array(' ', ',', ')', ':', '|', "\r", "\n", "\t", ";", $this->rd);
2523                } elseif ($curBlock === 'expression') {
2524                        $breakChars = array('/', '%', '+', '-', '*', ' ', ',', ')', "\r", "\n", "\t", ";", $this->rd);
2525                } else {
2526                        $breakChars = array(' ', ',', ')', "\r", "\n", "\t", ";", $this->rd);
2527                }
2528
2529                $breaker = false;
2530                while (list($k,$char) = each($breakChars)) {
2531                        $test = strpos($substr, $char);
2532                        if ($test !== false && $test < $end) {
2533                                $end = $test;
2534                                $breaker = $k;
2535                        }
2536                }
2537
2538                if ($curBlock === 'condition') {
2539                        if ($end === 0 && $breaker !== false) {
2540                                $end = strlen($breakChars[$breaker]);
2541                        }
2542                }
2543
2544                if ($end !== false) {
2545                        $substr = substr($substr, 0, $end);
2546                }
2547
2548                if ($pointer !== null) {
2549                        $pointer += strlen($substr);
2550                }
2551
2552                $src = $substr;
2553                $substr = trim($substr);
2554
2555                if (strtolower($substr) === 'false' || strtolower($substr) === 'no' || strtolower($substr) === 'off') {
2556                        if ($this->debug) echo 'BOOLEAN(FALSE) PARSED<br />';
2557                        $substr = 'false';
2558                        $type = self::T_BOOL;
2559                } elseif (strtolower($substr) === 'true' || strtolower($substr) === 'yes' || strtolower($substr) === 'on') {
2560                        if ($this->debug) echo 'BOOLEAN(TRUE) PARSED<br />';
2561                        $substr = 'true';
2562                        $type = self::T_BOOL;
2563                } elseif ($substr === 'null' || $substr === 'NULL') {
2564                        if ($this->debug) echo 'NULL PARSED<br />';
2565                        $substr = 'null';
2566                        $type = self::T_NULL;
2567                } elseif (is_numeric($substr)) {
2568                        $substr = (float) $substr;
2569                        if ((int) $substr == $substr) {
2570                                $substr = (int) $substr;
2571                        }
2572                        $type = self::T_NUMERIC;
2573                        if ($this->debug) echo 'NUMBER ('.$substr.') PARSED<br />';
2574                } elseif (preg_match('{^-?(\d+|\d*(\.\d+))\s*([/*%+-]\s*-?(\d+|\d*(\.\d+)))+$}', $substr)) {
2575                        if ($this->debug) echo 'SIMPLE MATH PARSED<br />';
2576                        $type = self::T_MATH;
2577                        $substr = '('.$substr.')';
2578                } elseif ($curBlock === 'condition' && array_search($substr, $breakChars, true) !== false) {
2579                        if ($this->debug) echo 'BREAKCHAR ('.$substr.') PARSED<br />';
2580                        $type = self::T_BREAKCHAR;
2581                        //$substr = '"'.$substr.'"';
2582                } else {
2583                        $substr = $this->replaceStringVars('\''.str_replace('\'', '\\\'', $substr).'\'', '\'', $curBlock);
2584                        $type = self::T_UNQUOTED_STRING;
2585                        if ($this->debug) echo 'BLABBER ('.$substr.') CASTED AS STRING<br />';
2586                }
2587
2588                if (is_array($parsingParams)) {
2589                        $parsingParams[] = array($substr, $src, $type);
2590                        return $parsingParams;
2591                } elseif ($curBlock === 'namedparam') {
2592                        return array($substr, $src, $type);
2593                } elseif ($curBlock === 'expression') {
2594                        return $substr;
2595                } else {
2596                        throw new Exception('Something went wrong');
2597                }
2598        }
2599
2600        /**
2601         * replaces variables within a parsed string
2602         *
2603         * @param string $string the parsed string
2604         * @param string $first the first character parsed in the string, which is the string delimiter (' or ")
2605         * @param string $curBlock the current parser-block being processed
2606         * @return string the original string with variables replaced
2607         */
2608        protected function replaceStringVars($string, $first, $curBlock='')
2609        {
2610                $pos = 0;
2611                if ($this->debug) echo 'STRING VAR REPLACEMENT : '.$string.'<br>';
2612                // replace vars
2613                while (($pos = strpos($string, '$', $pos)) !== false) {
2614                        $prev = substr($string, $pos-1, 1);
2615                        if ($prev === '\\') {
2616                                $pos++;
2617                                continue;
2618                        }
2619
2620                        $var = $this->parse($string, $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string':'string')));
2621                        $len = $var[0];
2622                        $var = $this->parse(str_replace('\\'.$first, $first, $string), $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string':'string')));
2623
2624                        if ($prev === '`' && substr($string, $pos+$len, 1) === '`') {
2625                                $string = substr_replace($string, $first.'.'.$var[1].'.'.$first, $pos-1, $len+2);
2626                        } else {
2627                                $string = substr_replace($string, $first.'.'.$var[1].'.'.$first, $pos, $len);
2628                        }
2629                        $pos += strlen($var[1]) + 2;
2630                        if ($this->debug) echo 'STRING VAR REPLACEMENT DONE : '.$string.'<br>';
2631                }
2632
2633                // handle modifiers
2634                // TODO Obsolete?
2635                $string = preg_replace_callback('#("|\')\.(.+?)\.\1((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\4|:[^`]*))*))+)#i', array($this, 'replaceModifiers'), $string);
2636
2637                // replace escaped dollar operators by unescaped ones if required
2638                if ($first==="'") {
2639                        $string = str_replace('\\$', '$', $string);
2640                }
2641
2642                return $string;
2643        }
2644
2645        /**
2646         * replaces the modifiers applied to a string or a variable
2647         *
2648         * @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")
2649         * @param string $curBlock the current parser-block being processed
2650         * @return string the input enclosed with various function calls according to the modifiers found
2651         */
2652        protected function replaceModifiers(array $m, $curBlock = null, &$pointer = null)
2653        {
2654                if ($this->debug) echo 'PARSING MODIFIERS : '.$m[3].'<br />';
2655
2656                if ($pointer !== null) {
2657                        $pointer += strlen($m[3]);
2658                }
2659                // remove first pipe
2660                $cmdstrsrc = substr($m[3], 1);
2661                // remove last quote if present
2662                if (substr($cmdstrsrc, -1, 1) === $m[1]) {
2663                        $cmdstrsrc = substr($cmdstrsrc, 0, -1);
2664                        $add = $m[1];
2665                }
2666
2667                $output = $m[2];
2668
2669                $continue = true;
2670                while (strlen($cmdstrsrc) > 0 && $continue) {
2671                        if ($cmdstrsrc[0] === '|') {
2672                                $cmdstrsrc = substr($cmdstrsrc, 1);
2673                                continue;
2674                        }
2675                        if ($cmdstrsrc[0] === ' ' || $cmdstrsrc[0] === ';' || substr($cmdstrsrc, 0, strlen($this->rd)) === $this->rd) {
2676                                if ($this->debug) echo 'MODIFIER PARSING ENDED, RIGHT DELIMITER or ";" FOUND<br/>';
2677                                $continue = false;
2678                                if ($pointer !== null) {
2679                                        $pointer -= strlen($cmdstrsrc);
2680                                }
2681                                break;
2682                        }
2683                        $cmdstr = $cmdstrsrc;
2684                        $paramsep = ':';
2685                        if (!preg_match('/^(@{0,2}[a-z_][a-z0-9_]*)(:)?/i', $cmdstr, $match)) {
2686                                throw new Dwoo_Compilation_Exception($this, 'Invalid modifier name, started with : '.substr($cmdstr, 0, 10));
2687                        }
2688                        $paramspos = !empty($match[2]) ? strlen($match[1]) : false;
2689                        $func = $match[1];
2690
2691                        $state = 0;
2692                        if ($paramspos === false) {
2693                                $cmdstrsrc = substr($cmdstrsrc, strlen($func));
2694                                $params = array();
2695                                if ($this->debug) echo 'MODIFIER ('.$func.') CALLED WITH NO PARAMS<br/>';
2696                        } else {
2697                                $paramstr = substr($cmdstr, $paramspos+1);
2698                                if (substr($paramstr, -1, 1) === $paramsep) {
2699                                        $paramstr = substr($paramstr, 0, -1);
2700                                }
2701
2702                                $ptr = 0;
2703                                $params = array();
2704                                while ($ptr < strlen($paramstr)) {
2705                                        if ($this->debug) echo 'MODIFIER ('.$func.') START PARAM PARSING WITH POINTER AT '.$ptr.'<br/>';
2706                                        if ($this->debug) echo $paramstr.'--'.$ptr.'--'.strlen($paramstr).'--modifier<br/>';
2707                                        $params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'modifier', $ptr);
2708                                        if ($this->debug) echo 'PARAM PARSED, POINTER AT '.$ptr.'<br/>';
2709
2710                                        if ($ptr >= strlen($paramstr)) {
2711                                                if ($this->debug) echo 'PARAM PARSING ENDED, PARAM STRING CONSUMED<br/>';
2712                                                break;
2713                                        }
2714
2715                                        if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === '|' || $paramstr[$ptr] === ';' || substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
2716                                                if ($this->debug) echo 'PARAM PARSING ENDED, " ", "|", RIGHT DELIMITER or ";" FOUND, POINTER AT '.$ptr.'<br/>';
2717                                                if ($paramstr[$ptr] !== '|') {
2718                                                        $continue = false;
2719                                                        if ($pointer !== null) {
2720                                                                $pointer -= strlen($paramstr) - $ptr;
2721                                                        }
2722                                                }
2723                                                $ptr++;
2724                                                break;
2725                                        }
2726                                        if ($ptr < strlen($paramstr) && $paramstr[$ptr] === ':') {
2727                                                $ptr++;
2728                                        }
2729                                }
2730                                $cmdstrsrc = substr($cmdstrsrc, strlen($func)+1+$ptr);
2731                                $paramstr = substr($paramstr, 0, $ptr);
2732                                foreach ($params as $k=>$p) {
2733                                        if (is_array($p) && is_array($p[1])) {
2734                                                $state |= 2;
2735                                        } else {
2736                                                if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) {
2737                                                        $params[$k] = array($m[2], array('true', 'true'));
2738                                                } else {
2739                                                        if ($state & 2) {
2740                                                                throw new Dwoo_Compilation_Exception($this, 'You can not use an unnamed parameter after a named one');
2741                                                        }
2742                                                        $state |= 1;
2743                                                }
2744                                        }
2745                                }
2746                        }
2747
2748                        // check if we must use array_map with this plugin or not
2749                        $mapped = false;
2750                        if (substr($func, 0, 1) === '@') {
2751                                $func = substr($func, 1);
2752                                $mapped = true;
2753                        }
2754
2755                        $pluginType = $this->getPluginType($func);
2756
2757                        if ($state & 2) {
2758                                array_unshift($params, array('value', array($output, $output)));
2759                        } else {
2760                                array_unshift($params, array($output, $output));
2761                        }
2762
2763                        if ($pluginType & Dwoo_Core::NATIVE_PLUGIN) {
2764                                $params = $this->mapParams($params, null, $state);
2765
2766                                $params = $params['*'][0];
2767
2768                                $params = self::implode_r($params);
2769
2770                                if ($mapped) {
2771                                        $output = '$this->arrayMap(\''.$func.'\', array('.$params.'))';
2772                                } else {
2773                                        $output = $func.'('.$params.')';
2774                                }
2775                        } elseif ($pluginType & Dwoo_Core::PROXY_PLUGIN) {
2776                                $params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
2777                                foreach ($params as &$p)
2778                                        $p = $p[0];
2779                                $output = call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params);
2780                        } elseif ($pluginType & Dwoo_Core::SMARTY_MODIFIER) {
2781                                $params = $this->mapParams($params, null, $state);
2782                                $params = $params['*'][0];
2783
2784                                $params = self::implode_r($params);
2785
2786                                if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
2787                                        $callback = $this->customPlugins[$func]['callback'];
2788                                        if (is_array($callback)) {
2789                                                if (is_object($callback[0])) {
2790                                                        $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array('.$params.'))';
2791                                                } else {
2792                                                        $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array(\''.$callback[0].'\', \''.$callback[1].'\'), array('.$params.'))';
2793                                                }
2794                                        } elseif ($mapped) {
2795                                                $output = '$this->arrayMap(\''.$callback.'\', array('.$params.'))';
2796                                        } else {
2797                                                $output = $callback.'('.$params.')';
2798                                        }
2799                                } elseif ($mapped) {
2800                                        $output = '$this->arrayMap(\'smarty_modifier_'.$func.'\', array('.$params.'))';
2801                                } else {
2802                                        $output = 'smarty_modifier_'.$func.'('.$params.')';
2803                                }
2804                        } else {
2805                                if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
2806                                        $callback = $this->customPlugins[$func]['callback'];
2807                                        $pluginName = $callback;
2808                                } else {
2809                                        $pluginName = 'Dwoo_Plugin_'.$func;
2810
2811                                        if ($pluginType & Dwoo_Core::CLASS_PLUGIN) {
2812                                                $callback = array($pluginName, ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? 'compile' : 'process');
2813                                        } else {
2814                                                $callback = $pluginName . (($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? '_compile' : '');
2815                                        }
2816                                }
2817
2818                                $params = $this->mapParams($params, $callback, $state);
2819
2820                                foreach ($params as &$p)
2821                                        $p = $p[0];
2822
2823                                if ($pluginType & Dwoo_Core::FUNC_PLUGIN) {
2824                                        if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) {
2825                                                if ($mapped) {
2826                                                        throw new Dwoo_Compilation_Exception($this, 'The @ operator can not be used on compiled plugins.');
2827                                                }
2828                                                if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
2829                                                        $funcCompiler = $this->customPlugins[$func]['callback'];
2830                                                } else {
2831                                                        $funcCompiler = 'Dwoo_Plugin_'.$func.'_compile';
2832                                                }
2833                                                array_unshift($params, $this);
2834                                                $output = call_user_func_array($funcCompiler, $params);
2835                                        } else {
2836                                                array_unshift($params, '$this');
2837
2838                                                $params = self::implode_r($params);
2839                                                if ($mapped) {
2840                                                        $output = '$this->arrayMap(\''.$pluginName.'\', array('.$params.'))';
2841                                                } else {
2842                                                        $output = $pluginName.'('.$params.')';
2843                                                }
2844                                        }
2845                                } else {
2846                                        if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) {
2847                                                if ($mapped) {
2848                                                        throw new Dwoo_Compilation_Exception($this, 'The @ operator can not be used on compiled plugins.');
2849                                                }
2850                                                if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
2851                                                        $callback = $this->customPlugins[$func]['callback'];
2852                                                        if (!is_array($callback)) {
2853                                                                if (!method_exists($callback, 'compile')) {
2854                                                                        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');
2855                                                                }
2856                                                                if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
2857                                                                        $funcCompiler = array($callback, 'compile');
2858                                                                } else {
2859                                                                        $funcCompiler = array(new $callback, 'compile');
2860                                                                }
2861                                                        } else {
2862                                                                $funcCompiler = $callback;
2863                                                        }
2864                                                } else {
2865                                                        $funcCompiler = array('Dwoo_Plugin_'.$func, 'compile');
2866                                                        array_unshift($params, $this);
2867                                                }
2868                                                $output = call_user_func_array($funcCompiler, $params);
2869                                        } else {
2870                                                $params = self::implode_r($params);
2871
2872                                                if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
2873                                                        if (is_object($callback[0])) {
2874                                                                $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array('.$params.'))';
2875                                                        } else {
2876                                                                $output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array(\''.$callback[0].'\', \''.$callback[1].'\'), array('.$params.'))';
2877                                                        }
2878                                                } elseif ($mapped) {
2879                                                        $output = '$this->arrayMap(array($this->getObjectPlugin(\'Dwoo_Plugin_'.$func.'\'), \'process\'), array('.$params.'))';
2880                                                } else {
2881                                                        $output = '$this->classCall(\''.$func.'\', array('.$params.'))';
2882                                                }
2883                                        }
2884                                }
2885                        }
2886                }
2887
2888                if ($curBlock === 'var' || $m[1] === null) {
2889                        return $output;
2890                } elseif ($curBlock === 'string' || $curBlock === 'root') {
2891                        return $m[1].'.'.$output.'.'.$m[1].(isset($add)?$add:null);
2892                }
2893        }
2894
2895        /**
2896         * recursively implodes an array in a similar manner as var_export() does but with some tweaks
2897         * to handle pre-compiled values and the fact that we do not need to enclose everything with
2898         * "array" and do not require top-level keys to be displayed
2899         *
2900         * @param array $params the array to implode
2901         * @param bool $recursiveCall if set to true, the function outputs key names for the top level
2902         * @return string the imploded array
2903         */
2904        public static function implode_r(array $params, $recursiveCall = false)
2905        {
2906                $out = '';
2907                foreach ($params as $k=>$p) {
2908                        if (is_array($p)) {
2909                                $out2 = 'array(';
2910                                foreach ($p as $k2=>$v)
2911                                        $out2 .= var_export($k2, true).' => '.(is_array($v) ? 'array('.self::implode_r($v, true).')' : $v).', ';
2912                                $p = rtrim($out2, ', ').')';
2913                        }
2914                        if ($recursiveCall) {
2915                                $out .= var_export($k, true).' => '.$p.', ';
2916                        } else {
2917                                $out .= $p.', ';
2918                        }
2919                }
2920                return rtrim($out, ', ');
2921        }
2922
2923        /**
2924         * returns the plugin type of a plugin and adds it to the used plugins array if required
2925         *
2926         * @param string $name plugin name, as found in the template
2927         * @return int type as a multi bit flag composed of the Dwoo plugin types constants
2928         */
2929        protected function getPluginType($name)
2930        {
2931                $pluginType = -1;
2932
2933                if (($this->securityPolicy === null && (function_exists($name) || strtolower($name) === 'isset' || strtolower($name) === 'empty')) ||
2934                        ($this->securityPolicy !== null && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) !== false)) {
2935                        $phpFunc = true;
2936                } elseif ($this->securityPolicy !== null && function_exists($name) && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) === false) {
2937                        throw new Dwoo_Security_Exception('Call to a disallowed php function : '.$name);
2938                }
2939
2940                while ($pluginType <= 0) {
2941                        if (isset($this->templatePlugins[$name])) {
2942                                $pluginType = Dwoo_Core::TEMPLATE_PLUGIN | Dwoo_Core::COMPILABLE_PLUGIN;
2943                        } elseif (isset($this->customPlugins[$name])) {
2944                                $pluginType = $this->customPlugins[$name]['type'] | Dwoo_Core::CUSTOM_PLUGIN;
2945                        } elseif (class_exists('Dwoo_Plugin_'.$name, false) !== false) {
2946                                if (is_subclass_of('Dwoo_Plugin_'.$name, 'Dwoo_Block_Plugin')) {
2947                                        $pluginType = Dwoo_Core::BLOCK_PLUGIN;
2948                                } else {
2949                                        $pluginType = Dwoo_Core::CLASS_PLUGIN;
2950                                }
2951                                $interfaces = class_implements('Dwoo_Plugin_'.$name, false);
2952                                if (in_array('Dwoo_ICompilable', $interfaces) !== false || in_array('Dwoo_ICompilable_Block', $interfaces) !== false) {
2953                                        $pluginType |= Dwoo_Core::COMPILABLE_PLUGIN;
2954                                }
2955                        } elseif (function_exists('Dwoo_Plugin_'.$name) !== false) {
2956                                $pluginType = Dwoo_Core::FUNC_PLUGIN;
2957                        } elseif (function_exists('Dwoo_Plugin_'.$name.'_compile')) {
2958                                $pluginType = Dwoo_Core::FUNC_PLUGIN | Dwoo_Core::COMPILABLE_PLUGIN;
2959                        } elseif (function_exists('smarty_modifier_'.$name) !== false) {
2960                                $pluginType = Dwoo_Core::SMARTY_MODIFIER;
2961                        } elseif (function_exists('smarty_function_'.$name) !== false) {
2962                                $pluginType = Dwoo_Core::SMARTY_FUNCTION;
2963                        } elseif (function_exists('smarty_block_'.$name) !== false) {
2964                                $pluginType = Dwoo_Core::SMARTY_BLOCK;
2965                        } else {
2966                                if ($pluginType===-1) {
2967                                        try {
2968                                                $this->dwoo->getLoader()->loadPlugin($name, isset($phpFunc)===false);
2969                                        } catch (Exception $e) {
2970                                                if (isset($phpFunc)) {
2971                                                        $pluginType = Dwoo_Core::NATIVE_PLUGIN;
2972                                                } elseif (is_object($this->dwoo->getPluginProxy()) && $this->dwoo->getPluginProxy()->handles($name)) {
2973                                                        $pluginType = Dwoo_Core::PROXY_PLUGIN;
2974                                                        break;
2975                                                } else {
2976                                                        throw $e;
2977                                                }
2978                                        }
2979                                } else {
2980                                        throw new Dwoo_Exception('Plugin "'.$name.'" could not be found');
2981                                }
2982                                $pluginType++;
2983                        }
2984                }
2985
2986                if (($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) === 0 && ($pluginType & Dwoo_Core::NATIVE_PLUGIN) === 0 && ($pluginType & Dwoo_Core::PROXY_PLUGIN) === 0) {
2987                        $this->addUsedPlugin($name, $pluginType);
2988                }
2989
2990                return $pluginType;
2991        }
2992
2993        /**
2994         * allows a plugin to load another one at compile time, this will also mark
2995         * it as used by this template so it will be loaded at runtime (which can be
2996         * useful for compiled plugins that rely on another plugin when their compiled
2997         * code runs)
2998         *
2999         * @param string $name the plugin name
3000         */
3001        public function loadPlugin($name) {
3002                $this->getPluginType($name);
3003        }
3004
3005        /**
3006         * runs htmlentities over the matched <?php ?> blocks when the security policy enforces that
3007         *
3008         * @param array $match matched php block
3009         * @return string the htmlentities-converted string
3010         */
3011        protected function phpTagEncodingHelper($match)
3012        {
3013                return htmlspecialchars($match[0]);
3014        }
3015
3016        /**
3017         * maps the parameters received from the template onto the parameters required by the given callback
3018         *
3019         * @param array $params the array of parameters
3020         * @param callback $callback the function or method to reflect on to find out the required parameters
3021         * @param int $callType the type of call in the template, 0 = no params, 1 = php-style call, 2 = named parameters call
3022         * @param array $map the parameter map to use, if not provided it will be built from the callback
3023         * @return array parameters sorted in the correct order with missing optional parameters filled
3024         */
3025        protected function mapParams(array $params, $callback, $callType=2, $map = null)
3026        {
3027                if (!$map) {
3028                        $map = $this->getParamMap($callback);
3029                }
3030
3031                $paramlist = array();
3032
3033                // transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
3034                $ps = array();
3035                foreach ($params as $p) {
3036                        if (is_array($p[1])) {
3037                                $ps[$p[0]] = $p[1];
3038                        } else {
3039                                $ps[] = $p;
3040                        }
3041                }
3042
3043                // loops over the param map and assigns values from the template or default value for unset optional params
3044                while (list($k,$v) = each($map)) {
3045                        if ($v[0] === '*') {
3046                                // "rest" array parameter, fill every remaining params in it and then break
3047                                if (count($ps) === 0) {
3048                                        if ($v[1]===false) {
3049                                                throw new Dwoo_Compilation_Exception($this, 'Rest argument missing for '.str_replace(array('Dwoo_Plugin_', '_compile'), '', (is_array($callback) ? $callback[0] : $callback)));
3050                                        } else {
3051                                                break;
3052                                        }
3053                                }
3054                                $tmp = array();
3055                                $tmp2 = array();
3056                                $tmp3 = array();
3057                                foreach ($ps as $i=>$p) {
3058                                        $tmp[$i] = $p[0];
3059                                        $tmp2[$i] = $p[1];
3060                                        $tmp3[$i] = isset($p[2]) ? $p[2] : 0;
3061                                        unset($ps[$i]);
3062                                }
3063                                $paramlist[$v[0]] = array($tmp, $tmp2, $tmp3);
3064                                unset($tmp, $tmp2, $i, $p);
3065                                break;
3066                        } elseif (isset($ps[$v[0]])) {
3067                                // parameter is defined as named param
3068                                $paramlist[$v[0]] = $ps[$v[0]];
3069                                unset($ps[$v[0]]);
3070                        } elseif (isset($ps[$k])) {
3071                                // parameter is defined as ordered param
3072                                $paramlist[$v[0]] = $ps[$k];
3073                                unset($ps[$k]);
3074                        } elseif ($v[1]===false) {
3075                                // parameter is not defined and not optional, throw error
3076                                if (is_array($callback)) {
3077                                        if (is_object($callback[0])) {
3078                                                $name = get_class($callback[0]) . '::' . $callback[1];
3079                                        } else {
3080                                                $name = $callback[0];
3081                                        }
3082                                } else {
3083                                        $name = $callback;
3084                                }
3085
3086                                throw new Dwoo_Compilation_Exception($this, 'Argument '.$k.'/'.$v[0].' missing for '.str_replace(array('Dwoo_Plugin_', '_compile'), '', $name));
3087                        } elseif ($v[2]===null) {
3088                                // enforce lowercased null if default value is null (php outputs NULL with var export)
3089                                $paramlist[$v[0]] = array('null', null, self::T_NULL);
3090                        } else {
3091                                // outputs default value with var_export
3092                                $paramlist[$v[0]] = array(var_export($v[2], true), $v[2]);
3093                        }
3094                }
3095
3096                if (count($ps)) {
3097                        foreach ($ps as $i=>$p) {
3098                                array_push($paramlist, $p);
3099                        }
3100                }
3101
3102                return $paramlist;
3103        }
3104
3105        /**
3106         * 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 "*"
3107         *
3108         * @param callback $callback the function/method to reflect on
3109         * @return array processed parameter map
3110         */
3111        protected function getParamMap($callback)
3112        {
3113                if (is_null($callback)) {
3114                        return array(array('*', true));
3115                }
3116                if (is_array($callback)) {
3117                        $ref = new ReflectionMethod($callback[0], $callback[1]);
3118                } else {
3119                        $ref = new ReflectionFunction($callback);
3120                }
3121
3122                $out = array();
3123                foreach ($ref->getParameters() as $param) {
3124                        if (($class = $param->getClass()) !== null && ($class->name === 'Dwoo' || $class->name === 'Dwoo_Core')) {
3125                                continue;
3126                        }
3127                        if (($class = $param->getClass()) !== null && $class->name === 'Dwoo_Compiler') {
3128                                continue;
3129                        }
3130                        if ($param->getName() === 'rest' && $param->isArray() === true) {
3131                                $out[] = array('*', $param->isOptional(), null);
3132                                continue;
3133                        }
3134                        $out[] = array($param->getName(), $param->isOptional(), $param->isOptional() ? $param->getDefaultValue() : null);
3135                }
3136
3137                return $out;
3138        }
3139
3140        /**
3141         * returns a default instance of this compiler, used by default by all Dwoo templates that do not have a
3142         * specific compiler assigned and when you do not override the default compiler factory function
3143         *
3144         * @see Dwoo_Core::setDefaultCompilerFactory()
3145         * @return Dwoo_Compiler
3146         */
3147        public static function compilerFactory()
3148        {
3149                if (self::$instance === null) {
3150                        new self;
3151                }
3152                return self::$instance;
3153        }
3154}
Note: See TracBrowser for help on using the repository browser.