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}
3155