]> git.sur5r.net Git - contagged/blob - smarty/Smarty_Compiler.class.php
cbcbddb1bd65701972b563de0241ff9d820cb3fa
[contagged] / smarty / Smarty_Compiler.class.php
1 <?php
2
3 /**
4  * Project:     Smarty: the PHP compiling template engine
5  * File:        Smarty_Compiler.class.php
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * @link http://smarty.php.net/
22  * @author Monte Ohrt <monte at ohrt dot com>
23  * @author Andrei Zmievski <andrei@php.net>
24  * @version 2.6.14
25  * @copyright 2001-2005 New Digital Group, Inc.
26  * @package Smarty
27  */
28
29 /* $Id: Smarty_Compiler.class.php,v 1.381 2006/05/25 14:46:18 boots Exp $ */
30
31 /**
32  * Template compiling class
33  * @package Smarty
34  */
35 class Smarty_Compiler extends Smarty {
36
37     // internal vars
38     /**#@+
39      * @access private
40      */
41     var $_folded_blocks         =   array();    // keeps folded template blocks
42     var $_current_file          =   null;       // the current template being compiled
43     var $_current_line_no       =   1;          // line number for error messages
44     var $_capture_stack         =   array();    // keeps track of nested capture buffers
45     var $_plugin_info           =   array();    // keeps track of plugins to load
46     var $_init_smarty_vars      =   false;
47     var $_permitted_tokens      =   array('true','false','yes','no','on','off','null');
48     var $_db_qstr_regexp        =   null;        // regexps are setup in the constructor
49     var $_si_qstr_regexp        =   null;
50     var $_qstr_regexp           =   null;
51     var $_func_regexp           =   null;
52     var $_reg_obj_regexp        =   null;
53     var $_var_bracket_regexp    =   null;
54     var $_num_const_regexp      =   null;
55     var $_dvar_guts_regexp      =   null;
56     var $_dvar_regexp           =   null;
57     var $_cvar_regexp           =   null;
58     var $_svar_regexp           =   null;
59     var $_avar_regexp           =   null;
60     var $_mod_regexp            =   null;
61     var $_var_regexp            =   null;
62     var $_parenth_param_regexp  =   null;
63     var $_func_call_regexp      =   null;
64     var $_obj_ext_regexp        =   null;
65     var $_obj_start_regexp      =   null;
66     var $_obj_params_regexp     =   null;
67     var $_obj_call_regexp       =   null;
68     var $_cacheable_state       =   0;
69     var $_cache_attrs_count     =   0;
70     var $_nocache_count         =   0;
71     var $_cache_serial          =   null;
72     var $_cache_include         =   null;
73
74     var $_strip_depth           =   0;
75     var $_additional_newline    =   "\n";
76
77     /**#@-*/
78     /**
79      * The class constructor.
80      */
81     function Smarty_Compiler()
82     {
83         // matches double quoted strings:
84         // "foobar"
85         // "foo\"bar"
86         $this->_db_qstr_regexp = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
87
88         // matches single quoted strings:
89         // 'foobar'
90         // 'foo\'bar'
91         $this->_si_qstr_regexp = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
92
93         // matches single or double quoted strings
94         $this->_qstr_regexp = '(?:' . $this->_db_qstr_regexp . '|' . $this->_si_qstr_regexp . ')';
95
96         // matches bracket portion of vars
97         // [0]
98         // [foo]
99         // [$bar]
100         $this->_var_bracket_regexp = '\[\$?[\w\.]+\]';
101
102         // matches numerical constants
103         // 30
104         // -12
105         // 13.22
106         $this->_num_const_regexp = '(?:\-?\d+(?:\.\d+)?)';
107
108         // matches $ vars (not objects):
109         // $foo
110         // $foo.bar
111         // $foo.bar.foobar
112         // $foo[0]
113         // $foo[$bar]
114         // $foo[5][blah]
115         // $foo[5].bar[$foobar][4]
116         $this->_dvar_math_regexp = '(?:[\+\*\/\%]|(?:-(?!>)))';
117         $this->_dvar_math_var_regexp = '[\$\w\.\+\-\*\/\%\d\>\[\]]';
118         $this->_dvar_guts_regexp = '\w+(?:' . $this->_var_bracket_regexp
119                 . ')*(?:\.\$?\w+(?:' . $this->_var_bracket_regexp . ')*)*(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?';
120         $this->_dvar_regexp = '\$' . $this->_dvar_guts_regexp;
121
122         // matches config vars:
123         // #foo#
124         // #foobar123_foo#
125         $this->_cvar_regexp = '\#\w+\#';
126
127         // matches section vars:
128         // %foo.bar%
129         $this->_svar_regexp = '\%\w+\.\w+\%';
130
131         // matches all valid variables (no quotes, no modifiers)
132         $this->_avar_regexp = '(?:' . $this->_dvar_regexp . '|'
133            . $this->_cvar_regexp . '|' . $this->_svar_regexp . ')';
134
135         // matches valid variable syntax:
136         // $foo
137         // $foo
138         // #foo#
139         // #foo#
140         // "text"
141         // "text"
142         $this->_var_regexp = '(?:' . $this->_avar_regexp . '|' . $this->_qstr_regexp . ')';
143
144         // matches valid object call (one level of object nesting allowed in parameters):
145         // $foo->bar
146         // $foo->bar()
147         // $foo->bar("text")
148         // $foo->bar($foo, $bar, "text")
149         // $foo->bar($foo, "foo")
150         // $foo->bar->foo()
151         // $foo->bar->foo->bar()
152         // $foo->bar($foo->bar)
153         // $foo->bar($foo->bar())
154         // $foo->bar($foo->bar($blah,$foo,44,"foo",$foo[0].bar))
155         $this->_obj_ext_regexp = '\->(?:\$?' . $this->_dvar_guts_regexp . ')';
156         $this->_obj_restricted_param_regexp = '(?:'
157                 . '(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')(?:' . $this->_obj_ext_regexp . '(?:\((?:(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')'
158                 . '(?:\s*,\s*(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . '))*)?\))?)*)';
159         $this->_obj_single_param_regexp = '(?:\w+|' . $this->_obj_restricted_param_regexp . '(?:\s*,\s*(?:(?:\w+|'
160                 . $this->_var_regexp . $this->_obj_restricted_param_regexp . ')))*)';
161         $this->_obj_params_regexp = '\((?:' . $this->_obj_single_param_regexp
162                 . '(?:\s*,\s*' . $this->_obj_single_param_regexp . ')*)?\)';
163         $this->_obj_start_regexp = '(?:' . $this->_dvar_regexp . '(?:' . $this->_obj_ext_regexp . ')+)';
164         $this->_obj_call_regexp = '(?:' . $this->_obj_start_regexp . '(?:' . $this->_obj_params_regexp . ')?(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?)';
165         
166         // matches valid modifier syntax:
167         // |foo
168         // |@foo
169         // |foo:"bar"
170         // |foo:$bar
171         // |foo:"bar":$foobar
172         // |foo|bar
173         // |foo:$foo->bar
174         $this->_mod_regexp = '(?:\|@?\w+(?::(?:\w+|' . $this->_num_const_regexp . '|'
175            . $this->_obj_call_regexp . '|' . $this->_avar_regexp . '|' . $this->_qstr_regexp .'))*)';
176
177         // matches valid function name:
178         // foo123
179         // _foo_bar
180         $this->_func_regexp = '[a-zA-Z_]\w*';
181
182         // matches valid registered object:
183         // foo->bar
184         $this->_reg_obj_regexp = '[a-zA-Z_]\w*->[a-zA-Z_]\w*';
185
186         // matches valid parameter values:
187         // true
188         // $foo
189         // $foo|bar
190         // #foo#
191         // #foo#|bar
192         // "text"
193         // "text"|bar
194         // $foo->bar
195         $this->_param_regexp = '(?:\s*(?:' . $this->_obj_call_regexp . '|'
196            . $this->_var_regexp . '|' . $this->_num_const_regexp  . '|\w+)(?>' . $this->_mod_regexp . '*)\s*)';
197
198         // matches valid parenthesised function parameters:
199         //
200         // "text"
201         //    $foo, $bar, "text"
202         // $foo|bar, "foo"|bar, $foo->bar($foo)|bar
203         $this->_parenth_param_regexp = '(?:\((?:\w+|'
204                 . $this->_param_regexp . '(?:\s*,\s*(?:(?:\w+|'
205                 . $this->_param_regexp . ')))*)?\))';
206
207         // matches valid function call:
208         // foo()
209         // foo_bar($foo)
210         // _foo_bar($foo,"bar")
211         // foo123($foo,$foo->bar(),"foo")
212         $this->_func_call_regexp = '(?:' . $this->_func_regexp . '\s*(?:'
213            . $this->_parenth_param_regexp . '))';
214     }
215
216     /**
217      * compile a resource
218      *
219      * sets $compiled_content to the compiled source
220      * @param string $resource_name
221      * @param string $source_content
222      * @param string $compiled_content
223      * @return true
224      */
225     function _compile_file($resource_name, $source_content, &$compiled_content)
226     {
227
228         if ($this->security) {
229             // do not allow php syntax to be executed unless specified
230             if ($this->php_handling == SMARTY_PHP_ALLOW &&
231                 !$this->security_settings['PHP_HANDLING']) {
232                 $this->php_handling = SMARTY_PHP_PASSTHRU;
233             }
234         }
235
236         $this->_load_filters();
237
238         $this->_current_file = $resource_name;
239         $this->_current_line_no = 1;
240         $ldq = preg_quote($this->left_delimiter, '~');
241         $rdq = preg_quote($this->right_delimiter, '~');
242
243         /* un-hide hidden xml open tags  */
244         $source_content = preg_replace("~<({$ldq}(.*?){$rdq})[?]~s", '< \\1', $source_content);
245
246         // run template source through prefilter functions
247         if (count($this->_plugins['prefilter']) > 0) {
248             foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
249                 if ($prefilter === false) continue;
250                 if ($prefilter[3] || is_callable($prefilter[0])) {
251                     $source_content = call_user_func_array($prefilter[0],
252                                                             array($source_content, &$this));
253                     $this->_plugins['prefilter'][$filter_name][3] = true;
254                 } else {
255                     $this->_trigger_fatal_error("[plugin] prefilter '$filter_name' is not implemented");
256                 }
257             }
258         }
259
260         /* fetch all special blocks */
261         $search = "~{$ldq}\*(.*?)\*{$rdq}|{$ldq}\s*literal\s*{$rdq}(.*?){$ldq}\s*/literal\s*{$rdq}|{$ldq}\s*php\s*{$rdq}(.*?){$ldq}\s*/php\s*{$rdq}~s";
262
263         preg_match_all($search, $source_content, $match,  PREG_SET_ORDER);
264         $this->_folded_blocks = $match;
265         reset($this->_folded_blocks);
266
267         /* replace special blocks by "{php}" */
268         $source_content = preg_replace($search.'e', "'"
269                                        . $this->_quote_replace($this->left_delimiter) . 'php'
270                                        . "' . str_repeat(\"\n\", substr_count('\\0', \"\n\")) .'"
271                                        . $this->_quote_replace($this->right_delimiter)
272                                        . "'"
273                                        , $source_content);
274
275         /* Gather all template tags. */
276         preg_match_all("~{$ldq}\s*(.*?)\s*{$rdq}~s", $source_content, $_match);
277         $template_tags = $_match[1];
278         /* Split content by template tags to obtain non-template content. */
279         $text_blocks = preg_split("~{$ldq}.*?{$rdq}~s", $source_content);
280
281         /* loop through text blocks */
282         for ($curr_tb = 0, $for_max = count($text_blocks); $curr_tb < $for_max; $curr_tb++) {
283             /* match anything resembling php tags */
284             if (preg_match_all('~(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?php[\"\']?)~is', $text_blocks[$curr_tb], $sp_match)) {
285                 /* replace tags with placeholders to prevent recursive replacements */
286                 $sp_match[1] = array_unique($sp_match[1]);
287                 usort($sp_match[1], '_smarty_sort_length');
288                 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) {
289                     $text_blocks[$curr_tb] = str_replace($sp_match[1][$curr_sp],'%%%SMARTYSP'.$curr_sp.'%%%',$text_blocks[$curr_tb]);
290                 }
291                 /* process each one */
292                 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) {
293                     if ($this->php_handling == SMARTY_PHP_PASSTHRU) {
294                         /* echo php contents */
295                         $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '<?php echo \''.str_replace("'", "\'", $sp_match[1][$curr_sp]).'\'; ?>'."\n", $text_blocks[$curr_tb]);
296                     } else if ($this->php_handling == SMARTY_PHP_QUOTE) {
297                         /* quote php tags */
298                         $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', htmlspecialchars($sp_match[1][$curr_sp]), $text_blocks[$curr_tb]);
299                     } else if ($this->php_handling == SMARTY_PHP_REMOVE) {
300                         /* remove php tags */
301                         $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '', $text_blocks[$curr_tb]);
302                     } else {
303                         /* SMARTY_PHP_ALLOW, but echo non php starting tags */
304                         $sp_match[1][$curr_sp] = preg_replace('~(<\?(?!php|=|$))~i', '<?php echo \'\\1\'?>'."\n", $sp_match[1][$curr_sp]);
305                         $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', $sp_match[1][$curr_sp], $text_blocks[$curr_tb]);
306                     }
307                 }
308             }
309         }
310
311         /* Compile the template tags into PHP code. */
312         $compiled_tags = array();
313         for ($i = 0, $for_max = count($template_tags); $i < $for_max; $i++) {
314             $this->_current_line_no += substr_count($text_blocks[$i], "\n");
315             $compiled_tags[] = $this->_compile_tag($template_tags[$i]);
316             $this->_current_line_no += substr_count($template_tags[$i], "\n");
317         }
318         if (count($this->_tag_stack)>0) {
319             list($_open_tag, $_line_no) = end($this->_tag_stack);
320             $this->_syntax_error("unclosed tag \{$_open_tag} (opened line $_line_no).", E_USER_ERROR, __FILE__, __LINE__);
321             return;
322         }
323
324         /* Reformat $text_blocks between 'strip' and '/strip' tags,
325            removing spaces, tabs and newlines. */
326         $strip = false;
327         for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) {
328             if ($compiled_tags[$i] == '{strip}') {
329                 $compiled_tags[$i] = '';
330                 $strip = true;
331                 /* remove leading whitespaces */
332                 $text_blocks[$i + 1] = ltrim($text_blocks[$i + 1]);
333             }
334             if ($strip) {
335                 /* strip all $text_blocks before the next '/strip' */
336                 for ($j = $i + 1; $j < $for_max; $j++) {
337                     /* remove leading and trailing whitespaces of each line */
338                     $text_blocks[$j] = preg_replace('![\t ]*[\r\n]+[\t ]*!', '', $text_blocks[$j]);
339                     if ($compiled_tags[$j] == '{/strip}') {                       
340                         /* remove trailing whitespaces from the last text_block */
341                         $text_blocks[$j] = rtrim($text_blocks[$j]);
342                     }
343                     $text_blocks[$j] = "<?php echo '" . strtr($text_blocks[$j], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>";
344                     if ($compiled_tags[$j] == '{/strip}') {
345                         $compiled_tags[$j] = "\n"; /* slurped by php, but necessary
346                                     if a newline is following the closing strip-tag */
347                         $strip = false;
348                         $i = $j;
349                         break;
350                     }
351                 }
352             }
353         }
354         $compiled_content = '';
355
356         /* Interleave the compiled contents and text blocks to get the final result. */
357         for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) {
358             if ($compiled_tags[$i] == '') {
359                 // tag result empty, remove first newline from following text block
360                 $text_blocks[$i+1] = preg_replace('~^(\r\n|\r|\n)~', '', $text_blocks[$i+1]);
361             }
362             $compiled_content .= $text_blocks[$i].$compiled_tags[$i];
363         }
364         $compiled_content .= $text_blocks[$i];
365
366         // remove \n from the end of the file, if any
367         if (strlen($compiled_content) && (substr($compiled_content, -1) == "\n") ) {
368             $compiled_content = substr($compiled_content, 0, -1);
369         }
370
371         if (!empty($this->_cache_serial)) {
372             $compiled_content = "<?php \$this->_cache_serials['".$this->_cache_include."'] = '".$this->_cache_serial."'; ?>" . $compiled_content;
373         }
374
375         // remove unnecessary close/open tags
376         $compiled_content = preg_replace('~\?>\n?<\?php~', '', $compiled_content);
377
378         // run compiled template through postfilter functions
379         if (count($this->_plugins['postfilter']) > 0) {
380             foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
381                 if ($postfilter === false) continue;
382                 if ($postfilter[3] || is_callable($postfilter[0])) {
383                     $compiled_content = call_user_func_array($postfilter[0],
384                                                               array($compiled_content, &$this));
385                     $this->_plugins['postfilter'][$filter_name][3] = true;
386                 } else {
387                     $this->_trigger_fatal_error("Smarty plugin error: postfilter '$filter_name' is not implemented");
388                 }
389             }
390         }
391
392         // put header at the top of the compiled template
393         $template_header = "<?php /* Smarty version ".$this->_version.", created on ".strftime("%Y-%m-%d %H:%M:%S")."\n";
394         $template_header .= "         compiled from ".strtr(urlencode($resource_name), array('%2F'=>'/', '%3A'=>':'))." */ ?>\n";
395
396         /* Emit code to load needed plugins. */
397         $this->_plugins_code = '';
398         if (count($this->_plugin_info)) {
399             $_plugins_params = "array('plugins' => array(";
400             foreach ($this->_plugin_info as $plugin_type => $plugins) {
401                 foreach ($plugins as $plugin_name => $plugin_info) {
402                     $_plugins_params .= "array('$plugin_type', '$plugin_name', '" . strtr($plugin_info[0], array("'" => "\\'", "\\" => "\\\\")) . "', $plugin_info[1], ";
403                     $_plugins_params .= $plugin_info[2] ? 'true),' : 'false),';
404                 }
405             }
406             $_plugins_params .= '))';
407             $plugins_code = "<?php require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');\nsmarty_core_load_plugins($_plugins_params, \$this); ?>\n";
408             $template_header .= $plugins_code;
409             $this->_plugin_info = array();
410             $this->_plugins_code = $plugins_code;
411         }
412
413         if ($this->_init_smarty_vars) {
414             $template_header .= "<?php require_once(SMARTY_CORE_DIR . 'core.assign_smarty_interface.php');\nsmarty_core_assign_smarty_interface(null, \$this); ?>\n";
415             $this->_init_smarty_vars = false;
416         }
417
418         $compiled_content = $template_header . $compiled_content;
419         return true;
420     }
421
422     /**
423      * Compile a template tag
424      *
425      * @param string $template_tag
426      * @return string
427      */
428     function _compile_tag($template_tag)
429     {
430         /* Matched comment. */
431         if (substr($template_tag, 0, 1) == '*' && substr($template_tag, -1) == '*')
432             return '';
433         
434         /* Split tag into two three parts: command, command modifiers and the arguments. */
435         if(! preg_match('~^(?:(' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp
436                 . '|\/?' . $this->_reg_obj_regexp . '|\/?' . $this->_func_regexp . ')(' . $this->_mod_regexp . '*))
437                       (?:\s+(.*))?$
438                     ~xs', $template_tag, $match)) {
439             $this->_syntax_error("unrecognized tag: $template_tag", E_USER_ERROR, __FILE__, __LINE__);
440         }
441         
442         $tag_command = $match[1];
443         $tag_modifier = isset($match[2]) ? $match[2] : null;
444         $tag_args = isset($match[3]) ? $match[3] : null;
445
446         if (preg_match('~^' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '$~', $tag_command)) {
447             /* tag name is a variable or object */
448             $_return = $this->_parse_var_props($tag_command . $tag_modifier);
449             return "<?php echo $_return; ?>" . $this->_additional_newline;
450         }
451
452         /* If the tag name is a registered object, we process it. */
453         if (preg_match('~^\/?' . $this->_reg_obj_regexp . '$~', $tag_command)) {
454             return $this->_compile_registered_object_tag($tag_command, $this->_parse_attrs($tag_args), $tag_modifier);
455         }
456
457         switch ($tag_command) {
458             case 'include':
459                 return $this->_compile_include_tag($tag_args);
460
461             case 'include_php':
462                 return $this->_compile_include_php_tag($tag_args);
463
464             case 'if':
465                 $this->_push_tag('if');
466                 return $this->_compile_if_tag($tag_args);
467
468             case 'else':
469                 list($_open_tag) = end($this->_tag_stack);
470                 if ($_open_tag != 'if' && $_open_tag != 'elseif')
471                     $this->_syntax_error('unexpected {else}', E_USER_ERROR, __FILE__, __LINE__);
472                 else
473                     $this->_push_tag('else');
474                 return '<?php else: ?>';
475
476             case 'elseif':
477                 list($_open_tag) = end($this->_tag_stack);
478                 if ($_open_tag != 'if' && $_open_tag != 'elseif')
479                     $this->_syntax_error('unexpected {elseif}', E_USER_ERROR, __FILE__, __LINE__);
480                 if ($_open_tag == 'if')
481                     $this->_push_tag('elseif');
482                 return $this->_compile_if_tag($tag_args, true);
483
484             case '/if':
485                 $this->_pop_tag('if');
486                 return '<?php endif; ?>';
487
488             case 'capture':
489                 return $this->_compile_capture_tag(true, $tag_args);
490
491             case '/capture':
492                 return $this->_compile_capture_tag(false);
493
494             case 'ldelim':
495                 return $this->left_delimiter;
496
497             case 'rdelim':
498                 return $this->right_delimiter;
499
500             case 'section':
501                 $this->_push_tag('section');
502                 return $this->_compile_section_start($tag_args);
503
504             case 'sectionelse':
505                 $this->_push_tag('sectionelse');
506                 return "<?php endfor; else: ?>";
507                 break;
508
509             case '/section':
510                 $_open_tag = $this->_pop_tag('section');
511                 if ($_open_tag == 'sectionelse')
512                     return "<?php endif; ?>";
513                 else
514                     return "<?php endfor; endif; ?>";
515
516             case 'foreach':
517                 $this->_push_tag('foreach');
518                 return $this->_compile_foreach_start($tag_args);
519                 break;
520
521             case 'foreachelse':
522                 $this->_push_tag('foreachelse');
523                 return "<?php endforeach; else: ?>";
524
525             case '/foreach':
526                 $_open_tag = $this->_pop_tag('foreach');
527                 if ($_open_tag == 'foreachelse')
528                     return "<?php endif; unset(\$_from); ?>";
529                 else
530                     return "<?php endforeach; endif; unset(\$_from); ?>";
531                 break;
532
533             case 'strip':
534             case '/strip':
535                 if (substr($tag_command, 0, 1)=='/') {
536                     $this->_pop_tag('strip');
537                     if (--$this->_strip_depth==0) { /* outermost closing {/strip} */
538                         $this->_additional_newline = "\n";
539                         return '{' . $tag_command . '}';
540                     }
541                 } else {
542                     $this->_push_tag('strip');
543                     if ($this->_strip_depth++==0) { /* outermost opening {strip} */
544                         $this->_additional_newline = "";
545                         return '{' . $tag_command . '}';
546                     }
547                 }
548                 return '';
549
550             case 'php':
551                 /* handle folded tags replaced by {php} */
552                 list(, $block) = each($this->_folded_blocks);
553                 $this->_current_line_no += substr_count($block[0], "\n");
554                 /* the number of matched elements in the regexp in _compile_file()
555                    determins the type of folded tag that was found */
556                 switch (count($block)) {
557                     case 2: /* comment */
558                         return '';
559
560                     case 3: /* literal */
561                         return "<?php echo '" . strtr($block[2], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>" . $this->_additional_newline;
562
563                     case 4: /* php */
564                         if ($this->security && !$this->security_settings['PHP_TAGS']) {
565                             $this->_syntax_error("(secure mode) php tags not permitted", E_USER_WARNING, __FILE__, __LINE__);
566                             return;
567                         }
568                         return '<?php ' . $block[3] .' ?>';
569                 }
570                 break;
571
572             case 'insert':
573                 return $this->_compile_insert_tag($tag_args);
574
575             default:
576                 if ($this->_compile_compiler_tag($tag_command, $tag_args, $output)) {
577                     return $output;
578                 } else if ($this->_compile_block_tag($tag_command, $tag_args, $tag_modifier, $output)) {
579                     return $output;
580                 } else if ($this->_compile_custom_tag($tag_command, $tag_args, $tag_modifier, $output)) {
581                     return $output;                    
582                 } else {
583                     $this->_syntax_error("unrecognized tag '$tag_command'", E_USER_ERROR, __FILE__, __LINE__);
584                 }
585
586         }
587     }
588
589
590     /**
591      * compile the custom compiler tag
592      *
593      * sets $output to the compiled custom compiler tag
594      * @param string $tag_command
595      * @param string $tag_args
596      * @param string $output
597      * @return boolean
598      */
599     function _compile_compiler_tag($tag_command, $tag_args, &$output)
600     {
601         $found = false;
602         $have_function = true;
603
604         /*
605          * First we check if the compiler function has already been registered
606          * or loaded from a plugin file.
607          */
608         if (isset($this->_plugins['compiler'][$tag_command])) {
609             $found = true;
610             $plugin_func = $this->_plugins['compiler'][$tag_command][0];
611             if (!is_callable($plugin_func)) {
612                 $message = "compiler function '$tag_command' is not implemented";
613                 $have_function = false;
614             }
615         }
616         /*
617          * Otherwise we need to load plugin file and look for the function
618          * inside it.
619          */
620         else if ($plugin_file = $this->_get_plugin_filepath('compiler', $tag_command)) {
621             $found = true;
622
623             include_once $plugin_file;
624
625             $plugin_func = 'smarty_compiler_' . $tag_command;
626             if (!is_callable($plugin_func)) {
627                 $message = "plugin function $plugin_func() not found in $plugin_file\n";
628                 $have_function = false;
629             } else {
630                 $this->_plugins['compiler'][$tag_command] = array($plugin_func, null, null, null, true);
631             }
632         }
633
634         /*
635          * True return value means that we either found a plugin or a
636          * dynamically registered function. False means that we didn't and the
637          * compiler should now emit code to load custom function plugin for this
638          * tag.
639          */
640         if ($found) {
641             if ($have_function) {
642                 $output = call_user_func_array($plugin_func, array($tag_args, &$this));
643                 if($output != '') {
644                 $output = '<?php ' . $this->_push_cacheable_state('compiler', $tag_command)
645                                    . $output
646                                    . $this->_pop_cacheable_state('compiler', $tag_command) . ' ?>';
647                 }
648             } else {
649                 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
650             }
651             return true;
652         } else {
653             return false;
654         }
655     }
656
657
658     /**
659      * compile block function tag
660      *
661      * sets $output to compiled block function tag
662      * @param string $tag_command
663      * @param string $tag_args
664      * @param string $tag_modifier
665      * @param string $output
666      * @return boolean
667      */
668     function _compile_block_tag($tag_command, $tag_args, $tag_modifier, &$output)
669     {
670         if (substr($tag_command, 0, 1) == '/') {
671             $start_tag = false;
672             $tag_command = substr($tag_command, 1);
673         } else
674             $start_tag = true;
675
676         $found = false;
677         $have_function = true;
678
679         /*
680          * First we check if the block function has already been registered
681          * or loaded from a plugin file.
682          */
683         if (isset($this->_plugins['block'][$tag_command])) {
684             $found = true;
685             $plugin_func = $this->_plugins['block'][$tag_command][0];
686             if (!is_callable($plugin_func)) {
687                 $message = "block function '$tag_command' is not implemented";
688                 $have_function = false;
689             }
690         }
691         /*
692          * Otherwise we need to load plugin file and look for the function
693          * inside it.
694          */
695         else if ($plugin_file = $this->_get_plugin_filepath('block', $tag_command)) {
696             $found = true;
697
698             include_once $plugin_file;
699
700             $plugin_func = 'smarty_block_' . $tag_command;
701             if (!function_exists($plugin_func)) {
702                 $message = "plugin function $plugin_func() not found in $plugin_file\n";
703                 $have_function = false;
704             } else {
705                 $this->_plugins['block'][$tag_command] = array($plugin_func, null, null, null, true);
706
707             }
708         }
709
710         if (!$found) {
711             return false;
712         } else if (!$have_function) {
713             $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
714             return true;
715         }
716
717         /*
718          * Even though we've located the plugin function, compilation
719          * happens only once, so the plugin will still need to be loaded
720          * at runtime for future requests.
721          */
722         $this->_add_plugin('block', $tag_command);
723
724         if ($start_tag)
725             $this->_push_tag($tag_command);
726         else
727             $this->_pop_tag($tag_command);
728
729         if ($start_tag) {
730             $output = '<?php ' . $this->_push_cacheable_state('block', $tag_command);
731             $attrs = $this->_parse_attrs($tag_args);
732             $_cache_attrs='';
733             $arg_list = $this->_compile_arg_list('block', $tag_command, $attrs, $_cache_attrs);
734             $output .= "$_cache_attrs\$this->_tag_stack[] = array('$tag_command', array(".implode(',', $arg_list).')); ';
735             $output .= '$_block_repeat=true;' . $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], null, $this, $_block_repeat);';
736             $output .= 'while ($_block_repeat) { ob_start(); ?>';
737         } else {
738             $output = '<?php $_block_content = ob_get_contents(); ob_end_clean(); ';
739             $_out_tag_text = $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], $_block_content, $this, $_block_repeat)';
740             if ($tag_modifier != '') {
741                 $this->_parse_modifiers($_out_tag_text, $tag_modifier);
742             }
743             $output .= '$_block_repeat=false;echo ' . $_out_tag_text . '; } ';
744             $output .= " array_pop(\$this->_tag_stack); " . $this->_pop_cacheable_state('block', $tag_command) . '?>';
745         }
746
747         return true;
748     }
749
750
751     /**
752      * compile custom function tag
753      *
754      * @param string $tag_command
755      * @param string $tag_args
756      * @param string $tag_modifier
757      * @return string
758      */
759     function _compile_custom_tag($tag_command, $tag_args, $tag_modifier, &$output)
760     {
761         $found = false;
762         $have_function = true;
763
764         /*
765          * First we check if the custom function has already been registered
766          * or loaded from a plugin file.
767          */
768         if (isset($this->_plugins['function'][$tag_command])) {
769             $found = true;
770             $plugin_func = $this->_plugins['function'][$tag_command][0];
771             if (!is_callable($plugin_func)) {
772                 $message = "custom function '$tag_command' is not implemented";
773                 $have_function = false;
774             }
775         }
776         /*
777          * Otherwise we need to load plugin file and look for the function
778          * inside it.
779          */
780         else if ($plugin_file = $this->_get_plugin_filepath('function', $tag_command)) {
781             $found = true;
782
783             include_once $plugin_file;
784
785             $plugin_func = 'smarty_function_' . $tag_command;
786             if (!function_exists($plugin_func)) {
787                 $message = "plugin function $plugin_func() not found in $plugin_file\n";
788                 $have_function = false;
789             } else {
790                 $this->_plugins['function'][$tag_command] = array($plugin_func, null, null, null, true);
791
792             }
793         }
794
795         if (!$found) {
796             return false;
797         } else if (!$have_function) {
798             $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
799             return true;
800         }
801
802         /* declare plugin to be loaded on display of the template that
803            we compile right now */
804         $this->_add_plugin('function', $tag_command);
805
806         $_cacheable_state = $this->_push_cacheable_state('function', $tag_command);
807         $attrs = $this->_parse_attrs($tag_args);
808         $_cache_attrs = '';
809         $arg_list = $this->_compile_arg_list('function', $tag_command, $attrs, $_cache_attrs);
810
811         $output = $this->_compile_plugin_call('function', $tag_command).'(array('.implode(',', $arg_list)."), \$this)";
812         if($tag_modifier != '') {
813             $this->_parse_modifiers($output, $tag_modifier);
814         }
815
816         if($output != '') {
817             $output =  '<?php ' . $_cacheable_state . $_cache_attrs . 'echo ' . $output . ';'
818                 . $this->_pop_cacheable_state('function', $tag_command) . "?>" . $this->_additional_newline;
819         }
820
821         return true;
822     }
823
824     /**
825      * compile a registered object tag
826      *
827      * @param string $tag_command
828      * @param array $attrs
829      * @param string $tag_modifier
830      * @return string
831      */
832     function _compile_registered_object_tag($tag_command, $attrs, $tag_modifier)
833     {
834         if (substr($tag_command, 0, 1) == '/') {
835             $start_tag = false;
836             $tag_command = substr($tag_command, 1);
837         } else {
838             $start_tag = true;
839         }
840
841         list($object, $obj_comp) = explode('->', $tag_command);
842
843         $arg_list = array();
844         if(count($attrs)) {
845             $_assign_var = false;
846             foreach ($attrs as $arg_name => $arg_value) {
847                 if($arg_name == 'assign') {
848                     $_assign_var = $arg_value;
849                     unset($attrs['assign']);
850                     continue;
851                 }
852                 if (is_bool($arg_value))
853                     $arg_value = $arg_value ? 'true' : 'false';
854                 $arg_list[] = "'$arg_name' => $arg_value";
855             }
856         }
857
858         if($this->_reg_objects[$object][2]) {
859             // smarty object argument format
860             $args = "array(".implode(',', (array)$arg_list)."), \$this";
861         } else {
862             // traditional argument format
863             $args = implode(',', array_values($attrs));
864             if (empty($args)) {
865                 $args = 'null';
866             }
867         }
868
869         $prefix = '';
870         $postfix = '';
871         $newline = '';
872         if(!is_object($this->_reg_objects[$object][0])) {
873             $this->_trigger_fatal_error("registered '$object' is not an object" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
874         } elseif(!empty($this->_reg_objects[$object][1]) && !in_array($obj_comp, $this->_reg_objects[$object][1])) {
875             $this->_trigger_fatal_error("'$obj_comp' is not a registered component of object '$object'", $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
876         } elseif(method_exists($this->_reg_objects[$object][0], $obj_comp)) {
877             // method
878             if(in_array($obj_comp, $this->_reg_objects[$object][3])) {
879                 // block method
880                 if ($start_tag) {
881                     $prefix = "\$this->_tag_stack[] = array('$obj_comp', $args); ";
882                     $prefix .= "\$_block_repeat=true; \$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], null, \$this, \$_block_repeat); ";
883                     $prefix .= "while (\$_block_repeat) { ob_start();";
884                     $return = null;
885                     $postfix = '';
886                 } else {
887                     $prefix = "\$_obj_block_content = ob_get_contents(); ob_end_clean(); \$_block_repeat=false;";
888                     $return = "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], \$_obj_block_content, \$this, \$_block_repeat)";
889                     $postfix = "} array_pop(\$this->_tag_stack);";
890                 }
891             } else {
892                 // non-block method
893                 $return = "\$this->_reg_objects['$object'][0]->$obj_comp($args)";
894             }
895         } else {
896             // property
897             $return = "\$this->_reg_objects['$object'][0]->$obj_comp";
898         }
899
900         if($return != null) {
901             if($tag_modifier != '') {
902                 $this->_parse_modifiers($return, $tag_modifier);
903             }
904
905             if(!empty($_assign_var)) {
906                 $output = "\$this->assign('" . $this->_dequote($_assign_var) ."',  $return);";
907             } else {
908                 $output = 'echo ' . $return . ';';
909                 $newline = $this->_additional_newline;
910             }
911         } else {
912             $output = '';
913         }
914
915         return '<?php ' . $prefix . $output . $postfix . "?>" . $newline;
916     }
917
918     /**
919      * Compile {insert ...} tag
920      *
921      * @param string $tag_args
922      * @return string
923      */
924     function _compile_insert_tag($tag_args)
925     {
926         $attrs = $this->_parse_attrs($tag_args);
927         $name = $this->_dequote($attrs['name']);
928
929         if (empty($name)) {
930             $this->_syntax_error("missing insert name", E_USER_ERROR, __FILE__, __LINE__);
931         }
932
933         if (!empty($attrs['script'])) {
934             $delayed_loading = true;
935         } else {
936             $delayed_loading = false;
937         }
938
939         foreach ($attrs as $arg_name => $arg_value) {
940             if (is_bool($arg_value))
941                 $arg_value = $arg_value ? 'true' : 'false';
942             $arg_list[] = "'$arg_name' => $arg_value";
943         }
944
945         $this->_add_plugin('insert', $name, $delayed_loading);
946
947         $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))";
948
949         return "<?php require_once(SMARTY_CORE_DIR . 'core.run_insert_handler.php');\necho smarty_core_run_insert_handler($_params, \$this); ?>" . $this->_additional_newline;
950     }
951
952     /**
953      * Compile {include ...} tag
954      *
955      * @param string $tag_args
956      * @return string
957      */
958     function _compile_include_tag($tag_args)
959     {
960         $attrs = $this->_parse_attrs($tag_args);
961         $arg_list = array();
962
963         if (empty($attrs['file'])) {
964             $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR, __FILE__, __LINE__);
965         }
966
967         foreach ($attrs as $arg_name => $arg_value) {
968             if ($arg_name == 'file') {
969                 $include_file = $arg_value;
970                 continue;
971             } else if ($arg_name == 'assign') {
972                 $assign_var = $arg_value;
973                 continue;
974             }
975             if (is_bool($arg_value))
976                 $arg_value = $arg_value ? 'true' : 'false';
977             $arg_list[] = "'$arg_name' => $arg_value";
978         }
979
980         $output = '<?php ';
981
982         if (isset($assign_var)) {
983             $output .= "ob_start();\n";
984         }
985
986         $output .=
987             "\$_smarty_tpl_vars = \$this->_tpl_vars;\n";
988
989
990         $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))";
991         $output .= "\$this->_smarty_include($_params);\n" .
992         "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" .
993         "unset(\$_smarty_tpl_vars);\n";
994
995         if (isset($assign_var)) {
996             $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n";
997         }
998
999         $output .= ' ?>';
1000
1001         return $output;
1002
1003     }
1004
1005     /**
1006      * Compile {include ...} tag
1007      *
1008      * @param string $tag_args
1009      * @return string
1010      */
1011     function _compile_include_php_tag($tag_args)
1012     {
1013         $attrs = $this->_parse_attrs($tag_args);
1014
1015         if (empty($attrs['file'])) {
1016             $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR, __FILE__, __LINE__);
1017         }
1018
1019         $assign_var = (empty($attrs['assign'])) ? '' : $this->_dequote($attrs['assign']);
1020         $once_var = (empty($attrs['once']) || $attrs['once']=='false') ? 'false' : 'true';
1021
1022         $arg_list = array();
1023         foreach($attrs as $arg_name => $arg_value) {
1024             if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') {
1025                 if(is_bool($arg_value))
1026                     $arg_value = $arg_value ? 'true' : 'false';
1027                 $arg_list[] = "'$arg_name' => $arg_value";
1028             }
1029         }
1030
1031         $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', $arg_list)."))";
1032
1033         return "<?php require_once(SMARTY_CORE_DIR . 'core.smarty_include_php.php');\nsmarty_core_smarty_include_php($_params, \$this); ?>" . $this->_additional_newline;
1034     }
1035
1036
1037     /**
1038      * Compile {section ...} tag
1039      *
1040      * @param string $tag_args
1041      * @return string
1042      */
1043     function _compile_section_start($tag_args)
1044     {
1045         $attrs = $this->_parse_attrs($tag_args);
1046         $arg_list = array();
1047
1048         $output = '<?php ';
1049         $section_name = $attrs['name'];
1050         if (empty($section_name)) {
1051             $this->_syntax_error("missing section name", E_USER_ERROR, __FILE__, __LINE__);
1052         }
1053
1054         $output .= "unset(\$this->_sections[$section_name]);\n";
1055         $section_props = "\$this->_sections[$section_name]";
1056
1057         foreach ($attrs as $attr_name => $attr_value) {
1058             switch ($attr_name) {
1059                 case 'loop':
1060                     $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n";
1061                     break;
1062
1063                 case 'show':
1064                     if (is_bool($attr_value))
1065                         $show_attr_value = $attr_value ? 'true' : 'false';
1066                     else
1067                         $show_attr_value = "(bool)$attr_value";
1068                     $output .= "{$section_props}['show'] = $show_attr_value;\n";
1069                     break;
1070
1071                 case 'name':
1072                     $output .= "{$section_props}['$attr_name'] = $attr_value;\n";
1073                     break;
1074
1075                 case 'max':
1076                 case 'start':
1077                     $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n";
1078                     break;
1079
1080                 case 'step':
1081                     $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n";
1082                     break;
1083
1084                 default:
1085                     $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR, __FILE__, __LINE__);
1086                     break;
1087             }
1088         }
1089
1090         if (!isset($attrs['show']))
1091             $output .= "{$section_props}['show'] = true;\n";
1092
1093         if (!isset($attrs['loop']))
1094             $output .= "{$section_props}['loop'] = 1;\n";
1095
1096         if (!isset($attrs['max']))
1097             $output .= "{$section_props}['max'] = {$section_props}['loop'];\n";
1098         else
1099             $output .= "if ({$section_props}['max'] < 0)\n" .
1100                        "    {$section_props}['max'] = {$section_props}['loop'];\n";
1101
1102         if (!isset($attrs['step']))
1103             $output .= "{$section_props}['step'] = 1;\n";
1104
1105         if (!isset($attrs['start']))
1106             $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n";
1107         else {
1108             $output .= "if ({$section_props}['start'] < 0)\n" .
1109                        "    {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" .
1110                        "else\n" .
1111                        "    {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n";
1112         }
1113
1114         $output .= "if ({$section_props}['show']) {\n";
1115         if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) {
1116             $output .= "    {$section_props}['total'] = {$section_props}['loop'];\n";
1117         } else {
1118             $output .= "    {$section_props}['total'] = min(ceil(({$section_props}['step'] > 0 ? {$section_props}['loop'] - {$section_props}['start'] : {$section_props}['start']+1)/abs({$section_props}['step'])), {$section_props}['max']);\n";
1119         }
1120         $output .= "    if ({$section_props}['total'] == 0)\n" .
1121                    "        {$section_props}['show'] = false;\n" .
1122                    "} else\n" .
1123                    "    {$section_props}['total'] = 0;\n";
1124
1125         $output .= "if ({$section_props}['show']):\n";
1126         $output .= "
1127             for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1;
1128                  {$section_props}['iteration'] <= {$section_props}['total'];
1129                  {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n";
1130         $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n";
1131         $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n";
1132         $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n";
1133         $output .= "{$section_props}['first']      = ({$section_props}['iteration'] == 1);\n";
1134         $output .= "{$section_props}['last']       = ({$section_props}['iteration'] == {$section_props}['total']);\n";
1135
1136         $output .= "?>";
1137
1138         return $output;
1139     }
1140
1141
1142     /**
1143      * Compile {foreach ...} tag.
1144      *
1145      * @param string $tag_args
1146      * @return string
1147      */
1148     function _compile_foreach_start($tag_args)
1149     {
1150         $attrs = $this->_parse_attrs($tag_args);
1151         $arg_list = array();
1152
1153         if (empty($attrs['from'])) {
1154             return $this->_syntax_error("foreach: missing 'from' attribute", E_USER_ERROR, __FILE__, __LINE__);
1155         }
1156         $from = $attrs['from'];
1157
1158         if (empty($attrs['item'])) {
1159             return $this->_syntax_error("foreach: missing 'item' attribute", E_USER_ERROR, __FILE__, __LINE__);
1160         }
1161         $item = $this->_dequote($attrs['item']);
1162         if (!preg_match('~^\w+$~', $item)) {
1163             return $this->_syntax_error("'foreach: item' must be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1164         }
1165
1166         if (isset($attrs['key'])) {
1167             $key  = $this->_dequote($attrs['key']);
1168             if (!preg_match('~^\w+$~', $key)) {
1169                 return $this->_syntax_error("foreach: 'key' must to be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1170             }
1171             $key_part = "\$this->_tpl_vars['$key'] => ";
1172         } else {
1173             $key = null;
1174             $key_part = '';
1175         }
1176
1177         if (isset($attrs['name'])) {
1178             $name = $attrs['name'];
1179         } else {
1180             $name = null;
1181         }
1182
1183         $output = '<?php ';
1184         $output .= "\$_from = $from; if (!is_array(\$_from) && !is_object(\$_from)) { settype(\$_from, 'array'); }";
1185         if (isset($name)) {
1186             $foreach_props = "\$this->_foreach[$name]";
1187             $output .= "{$foreach_props} = array('total' => count(\$_from), 'iteration' => 0);\n";
1188             $output .= "if ({$foreach_props}['total'] > 0):\n";
1189             $output .= "    foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1190             $output .= "        {$foreach_props}['iteration']++;\n";
1191         } else {
1192             $output .= "if (count(\$_from)):\n";
1193             $output .= "    foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1194         }
1195         $output .= '?>';
1196
1197         return $output;
1198     }
1199
1200
1201     /**
1202      * Compile {capture} .. {/capture} tags
1203      *
1204      * @param boolean $start true if this is the {capture} tag
1205      * @param string $tag_args
1206      * @return string
1207      */
1208
1209     function _compile_capture_tag($start, $tag_args = '')
1210     {
1211         $attrs = $this->_parse_attrs($tag_args);
1212
1213         if ($start) {
1214             if (isset($attrs['name']))
1215                 $buffer = $attrs['name'];
1216             else
1217                 $buffer = "'default'";
1218
1219             if (isset($attrs['assign']))
1220                 $assign = $attrs['assign'];
1221             else
1222                 $assign = null;
1223             $output = "<?php ob_start(); ?>";
1224             $this->_capture_stack[] = array($buffer, $assign);
1225         } else {
1226             list($buffer, $assign) = array_pop($this->_capture_stack);
1227             $output = "<?php \$this->_smarty_vars['capture'][$buffer] = ob_get_contents(); ";
1228             if (isset($assign)) {
1229                 $output .= " \$this->assign($assign, ob_get_contents());";
1230             }
1231             $output .= "ob_end_clean(); ?>";
1232         }
1233
1234         return $output;
1235     }
1236
1237     /**
1238      * Compile {if ...} tag
1239      *
1240      * @param string $tag_args
1241      * @param boolean $elseif if true, uses elseif instead of if
1242      * @return string
1243      */
1244     function _compile_if_tag($tag_args, $elseif = false)
1245     {
1246
1247         /* Tokenize args for 'if' tag. */
1248         preg_match_all('~(?>
1249                 ' . $this->_obj_call_regexp . '(?:' . $this->_mod_regexp . '*)? | # valid object call
1250                 ' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)?    | # var or quoted string
1251                 \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@    | # valid non-word token
1252                 \b\w+\b                                                        | # valid word token
1253                 \S+                                                           # anything else
1254                 )~x', $tag_args, $match);
1255
1256         $tokens = $match[0];
1257
1258         if(empty($tokens)) {
1259             $_error_msg = $elseif ? "'elseif'" : "'if'";
1260             $_error_msg .= ' statement requires arguments'; 
1261             $this->_syntax_error($_error_msg, E_USER_ERROR, __FILE__, __LINE__);
1262         }
1263             
1264                 
1265         // make sure we have balanced parenthesis
1266         $token_count = array_count_values($tokens);
1267         if(isset($token_count['(']) && $token_count['('] != $token_count[')']) {
1268             $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR, __FILE__, __LINE__);
1269         }
1270
1271         $is_arg_stack = array();
1272
1273         for ($i = 0; $i < count($tokens); $i++) {
1274
1275             $token = &$tokens[$i];
1276
1277             switch (strtolower($token)) {
1278                 case '!':
1279                 case '%':
1280                 case '!==':
1281                 case '==':
1282                 case '===':
1283                 case '>':
1284                 case '<':
1285                 case '!=':
1286                 case '<>':
1287                 case '<<':
1288                 case '>>':
1289                 case '<=':
1290                 case '>=':
1291                 case '&&':
1292                 case '||':
1293                 case '|':
1294                 case '^':
1295                 case '&':
1296                 case '~':
1297                 case ')':
1298                 case ',':
1299                 case '+':
1300                 case '-':
1301                 case '*':
1302                 case '/':
1303                 case '@':
1304                     break;
1305
1306                 case 'eq':
1307                     $token = '==';
1308                     break;
1309
1310                 case 'ne':
1311                 case 'neq':
1312                     $token = '!=';
1313                     break;
1314
1315                 case 'lt':
1316                     $token = '<';
1317                     break;
1318
1319                 case 'le':
1320                 case 'lte':
1321                     $token = '<=';
1322                     break;
1323
1324                 case 'gt':
1325                     $token = '>';
1326                     break;
1327
1328                 case 'ge':
1329                 case 'gte':
1330                     $token = '>=';
1331                     break;
1332
1333                 case 'and':
1334                     $token = '&&';
1335                     break;
1336
1337                 case 'or':
1338                     $token = '||';
1339                     break;
1340
1341                 case 'not':
1342                     $token = '!';
1343                     break;
1344
1345                 case 'mod':
1346                     $token = '%';
1347                     break;
1348
1349                 case '(':
1350                     array_push($is_arg_stack, $i);
1351                     break;
1352
1353                 case 'is':
1354                     /* If last token was a ')', we operate on the parenthesized
1355                        expression. The start of the expression is on the stack.
1356                        Otherwise, we operate on the last encountered token. */
1357                     if ($tokens[$i-1] == ')')
1358                         $is_arg_start = array_pop($is_arg_stack);
1359                     else
1360                         $is_arg_start = $i-1;
1361                     /* Construct the argument for 'is' expression, so it knows
1362                        what to operate on. */
1363                     $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
1364
1365                     /* Pass all tokens from next one until the end to the
1366                        'is' expression parsing function. The function will
1367                        return modified tokens, where the first one is the result
1368                        of the 'is' expression and the rest are the tokens it
1369                        didn't touch. */
1370                     $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));
1371
1372                     /* Replace the old tokens with the new ones. */
1373                     array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);
1374
1375                     /* Adjust argument start so that it won't change from the
1376                        current position for the next iteration. */
1377                     $i = $is_arg_start;
1378                     break;
1379
1380                 default:
1381                     if(preg_match('~^' . $this->_func_regexp . '$~', $token) ) {
1382                             // function call
1383                             if($this->security &&
1384                                !in_array($token, $this->security_settings['IF_FUNCS'])) {
1385                                 $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);
1386                             }
1387                     } elseif(preg_match('~^' . $this->_var_regexp . '$~', $token) && (strpos('+-*/^%&|', substr($token, -1)) === false) && isset($tokens[$i+1]) && $tokens[$i+1] == '(') {
1388                         // variable function call
1389                         $this->_syntax_error("variable function call '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);                      
1390                     } elseif(preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$~', $token)) {
1391                         // object or variable
1392                         $token = $this->_parse_var_props($token);
1393                     } elseif(is_numeric($token)) {
1394                         // number, skip it
1395                     } else {
1396                         $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__);
1397                     }
1398                     break;
1399             }
1400         }
1401
1402         if ($elseif)
1403             return '<?php elseif ('.implode(' ', $tokens).'): ?>';
1404         else
1405             return '<?php if ('.implode(' ', $tokens).'): ?>';
1406     }
1407
1408
1409     function _compile_arg_list($type, $name, $attrs, &$cache_code) {
1410         $arg_list = array();
1411
1412         if (isset($type) && isset($name)
1413             && isset($this->_plugins[$type])
1414             && isset($this->_plugins[$type][$name])
1415             && empty($this->_plugins[$type][$name][4])
1416             && is_array($this->_plugins[$type][$name][5])
1417             ) {
1418             /* we have a list of parameters that should be cached */
1419             $_cache_attrs = $this->_plugins[$type][$name][5];
1420             $_count = $this->_cache_attrs_count++;
1421             $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');";
1422
1423         } else {
1424             /* no parameters are cached */
1425             $_cache_attrs = null;
1426         }
1427
1428         foreach ($attrs as $arg_name => $arg_value) {
1429             if (is_bool($arg_value))
1430                 $arg_value = $arg_value ? 'true' : 'false';
1431             if (is_null($arg_value))
1432                 $arg_value = 'null';
1433             if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) {
1434                 $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)";
1435             } else {
1436                 $arg_list[] = "'$arg_name' => $arg_value";
1437             }
1438         }
1439         return $arg_list;
1440     }
1441
1442     /**
1443      * Parse is expression
1444      *
1445      * @param string $is_arg
1446      * @param array $tokens
1447      * @return array
1448      */
1449     function _parse_is_expr($is_arg, $tokens)
1450     {
1451         $expr_end = 0;
1452         $negate_expr = false;
1453
1454         if (($first_token = array_shift($tokens)) == 'not') {
1455             $negate_expr = true;
1456             $expr_type = array_shift($tokens);
1457         } else
1458             $expr_type = $first_token;
1459
1460         switch ($expr_type) {
1461             case 'even':
1462                 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1463                     $expr_end++;
1464                     $expr_arg = $tokens[$expr_end++];
1465                     $expr = "!(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1466                 } else
1467                     $expr = "!(1 & $is_arg)";
1468                 break;
1469
1470             case 'odd':
1471                 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1472                     $expr_end++;
1473                     $expr_arg = $tokens[$expr_end++];
1474                     $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1475                 } else
1476                     $expr = "(1 & $is_arg)";
1477                 break;
1478
1479             case 'div':
1480                 if (@$tokens[$expr_end] == 'by') {
1481                     $expr_end++;
1482                     $expr_arg = $tokens[$expr_end++];
1483                     $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")";
1484                 } else {
1485                     $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__);
1486                 }
1487                 break;
1488
1489             default:
1490                 $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__);
1491                 break;
1492         }
1493
1494         if ($negate_expr) {
1495             $expr = "!($expr)";
1496         }
1497
1498         array_splice($tokens, 0, $expr_end, $expr);
1499
1500         return $tokens;
1501     }
1502
1503
1504     /**
1505      * Parse attribute string
1506      *
1507      * @param string $tag_args
1508      * @return array
1509      */
1510     function _parse_attrs($tag_args)
1511     {
1512
1513         /* Tokenize tag attributes. */
1514         preg_match_all('~(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+)
1515                          )+ |
1516                          [=]
1517                         ~x', $tag_args, $match);
1518         $tokens       = $match[0];
1519
1520         $attrs = array();
1521         /* Parse state:
1522             0 - expecting attribute name
1523             1 - expecting '='
1524             2 - expecting attribute value (not '=') */
1525         $state = 0;
1526
1527         foreach ($tokens as $token) {
1528             switch ($state) {
1529                 case 0:
1530                     /* If the token is a valid identifier, we set attribute name
1531                        and go to state 1. */
1532                     if (preg_match('~^\w+$~', $token)) {
1533                         $attr_name = $token;
1534                         $state = 1;
1535                     } else
1536                         $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__);
1537                     break;
1538
1539                 case 1:
1540                     /* If the token is '=', then we go to state 2. */
1541                     if ($token == '=') {
1542                         $state = 2;
1543                     } else
1544                         $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1545                     break;
1546
1547                 case 2:
1548                     /* If token is not '=', we set the attribute value and go to
1549                        state 0. */
1550                     if ($token != '=') {
1551                         /* We booleanize the token if it's a non-quoted possible
1552                            boolean value. */
1553                         if (preg_match('~^(on|yes|true)$~', $token)) {
1554                             $token = 'true';
1555                         } else if (preg_match('~^(off|no|false)$~', $token)) {
1556                             $token = 'false';
1557                         } else if ($token == 'null') {
1558                             $token = 'null';
1559                         } else if (preg_match('~^' . $this->_num_const_regexp . '|0[xX][0-9a-fA-F]+$~', $token)) {
1560                             /* treat integer literally */
1561                         } else if (!preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$~', $token)) {
1562                             /* treat as a string, double-quote it escaping quotes */
1563                             $token = '"'.addslashes($token).'"';
1564                         }
1565
1566                         $attrs[$attr_name] = $token;
1567                         $state = 0;
1568                     } else
1569                         $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__);
1570                     break;
1571             }
1572             $last_token = $token;
1573         }
1574
1575         if($state != 0) {
1576             if($state == 1) {
1577                 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1578             } else {
1579                 $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__);
1580             }
1581         }
1582
1583         $this->_parse_vars_props($attrs);
1584
1585         return $attrs;
1586     }
1587
1588     /**
1589      * compile multiple variables and section properties tokens into
1590      * PHP code
1591      *
1592      * @param array $tokens
1593      */
1594     function _parse_vars_props(&$tokens)
1595     {
1596         foreach($tokens as $key => $val) {
1597             $tokens[$key] = $this->_parse_var_props($val);
1598         }
1599     }
1600
1601     /**
1602      * compile single variable and section properties token into
1603      * PHP code
1604      *
1605      * @param string $val
1606      * @param string $tag_attrs
1607      * @return string
1608      */
1609     function _parse_var_props($val)
1610     {
1611         $val = trim($val);
1612
1613         if(preg_match('~^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$~', $val, $match)) {
1614             // $ variable or object
1615             $return = $this->_parse_var($match[1]);
1616             $modifiers = $match[2];
1617             if (!empty($this->default_modifiers) && !preg_match('~(^|\|)smarty:nodefaults($|\|)~',$modifiers)) {
1618                 $_default_mod_string = implode('|',(array)$this->default_modifiers);
1619                 $modifiers = empty($modifiers) ? $_default_mod_string : $_default_mod_string . '|' . $modifiers;
1620             }
1621             $this->_parse_modifiers($return, $modifiers);
1622             return $return;
1623         } elseif (preg_match('~^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1624                 // double quoted text
1625                 preg_match('~^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1626                 $return = $this->_expand_quoted_text($match[1]);
1627                 if($match[2] != '') {
1628                     $this->_parse_modifiers($return, $match[2]);
1629                 }
1630                 return $return;
1631             }
1632         elseif(preg_match('~^' . $this->_num_const_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1633                 // numerical constant
1634                 preg_match('~^(' . $this->_num_const_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1635                 if($match[2] != '') {
1636                     $this->_parse_modifiers($match[1], $match[2]);
1637                     return $match[1];
1638                 }
1639             }
1640         elseif(preg_match('~^' . $this->_si_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1641                 // single quoted text
1642                 preg_match('~^(' . $this->_si_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1643                 if($match[2] != '') {
1644                     $this->_parse_modifiers($match[1], $match[2]);
1645                     return $match[1];
1646                 }
1647             }
1648         elseif(preg_match('~^' . $this->_cvar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1649                 // config var
1650                 return $this->_parse_conf_var($val);
1651             }
1652         elseif(preg_match('~^' . $this->_svar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1653                 // section var
1654                 return $this->_parse_section_prop($val);
1655             }
1656         elseif(!in_array($val, $this->_permitted_tokens) && !is_numeric($val)) {
1657             // literal string
1658             return $this->_expand_quoted_text('"' . strtr($val, array('\\' => '\\\\', '"' => '\\"')) .'"');
1659         }
1660         return $val;
1661     }
1662
1663     /**
1664      * expand quoted text with embedded variables
1665      *
1666      * @param string $var_expr
1667      * @return string
1668      */
1669     function _expand_quoted_text($var_expr)
1670     {
1671         // if contains unescaped $, expand it
1672         if(preg_match_all('~(?:\`(?<!\\\\)\$' . $this->_dvar_guts_regexp . '(?:' . $this->_obj_ext_regexp . ')*\`)|(?:(?<!\\\\)\$\w+(\[[a-zA-Z0-9]+\])*)~', $var_expr, $_match)) {
1673             $_match = $_match[0];
1674             rsort($_match);
1675             reset($_match);
1676             foreach($_match as $_var) {
1677                 $var_expr = str_replace ($_var, '".(' . $this->_parse_var(str_replace('`','',$_var)) . ')."', $var_expr);
1678             }
1679             $_return = preg_replace('~\.""|(?<!\\\\)""\.~', '', $var_expr);
1680         } else {
1681             $_return = $var_expr;
1682         }
1683         // replace double quoted literal string with single quotes
1684         $_return = preg_replace('~^"([\s\w]+)"$~',"'\\1'",$_return);
1685         return $_return;
1686     }
1687
1688     /**
1689      * parse variable expression into PHP code
1690      *
1691      * @param string $var_expr
1692      * @param string $output
1693      * @return string
1694      */
1695     function _parse_var($var_expr)
1696     {
1697         $_has_math = false;
1698         $_math_vars = preg_split('~('.$this->_dvar_math_regexp.'|'.$this->_qstr_regexp.')~', $var_expr, -1, PREG_SPLIT_DELIM_CAPTURE);
1699
1700         if(count($_math_vars) > 1) {
1701             $_first_var = "";
1702             $_complete_var = "";
1703             $_output = "";
1704             // simple check if there is any math, to stop recursion (due to modifiers with "xx % yy" as parameter)
1705             foreach($_math_vars as $_k => $_math_var) {
1706                 $_math_var = $_math_vars[$_k];
1707
1708                 if(!empty($_math_var) || is_numeric($_math_var)) {
1709                     // hit a math operator, so process the stuff which came before it
1710                     if(preg_match('~^' . $this->_dvar_math_regexp . '$~', $_math_var)) {
1711                         $_has_math = true;
1712                         if(!empty($_complete_var) || is_numeric($_complete_var)) {
1713                             $_output .= $this->_parse_var($_complete_var);
1714                         }
1715
1716                         // just output the math operator to php
1717                         $_output .= $_math_var;
1718
1719                         if(empty($_first_var))
1720                             $_first_var = $_complete_var;
1721
1722                         $_complete_var = "";
1723                     } else {
1724                         $_complete_var .= $_math_var;
1725                     }
1726                 }
1727             }
1728             if($_has_math) {
1729                 if(!empty($_complete_var) || is_numeric($_complete_var))
1730                     $_output .= $this->_parse_var($_complete_var);
1731
1732                 // get the modifiers working (only the last var from math + modifier is left)
1733                 $var_expr = $_complete_var;
1734             }
1735         }
1736
1737         // prevent cutting of first digit in the number (we _definitly_ got a number if the first char is a digit)
1738         if(is_numeric(substr($var_expr, 0, 1)))
1739             $_var_ref = $var_expr;
1740         else
1741             $_var_ref = substr($var_expr, 1);
1742         
1743         if(!$_has_math) {
1744             
1745             // get [foo] and .foo and ->foo and (...) pieces
1746             preg_match_all('~(?:^\w+)|' . $this->_obj_params_regexp . '|(?:' . $this->_var_bracket_regexp . ')|->\$?\w+|\.\$?\w+|\S+~', $_var_ref, $match);
1747                         
1748             $_indexes = $match[0];
1749             $_var_name = array_shift($_indexes);
1750
1751             /* Handle $smarty.* variable references as a special case. */
1752             if ($_var_name == 'smarty') {
1753                 /*
1754                  * If the reference could be compiled, use the compiled output;
1755                  * otherwise, fall back on the $smarty variable generated at
1756                  * run-time.
1757                  */
1758                 if (($smarty_ref = $this->_compile_smarty_ref($_indexes)) !== null) {
1759                     $_output = $smarty_ref;
1760                 } else {
1761                     $_var_name = substr(array_shift($_indexes), 1);
1762                     $_output = "\$this->_smarty_vars['$_var_name']";
1763                 }
1764             } elseif(is_numeric($_var_name) && is_numeric(substr($var_expr, 0, 1))) {
1765                 // because . is the operator for accessing arrays thru inidizes we need to put it together again for floating point numbers
1766                 if(count($_indexes) > 0)
1767                 {
1768                     $_var_name .= implode("", $_indexes);
1769                     $_indexes = array();
1770                 }
1771                 $_output = $_var_name;
1772             } else {
1773                 $_output = "\$this->_tpl_vars['$_var_name']";
1774             }
1775
1776             foreach ($_indexes as $_index) {
1777                 if (substr($_index, 0, 1) == '[') {
1778                     $_index = substr($_index, 1, -1);
1779                     if (is_numeric($_index)) {
1780                         $_output .= "[$_index]";
1781                     } elseif (substr($_index, 0, 1) == '$') {
1782                         if (strpos($_index, '.') !== false) {
1783                             $_output .= '[' . $this->_parse_var($_index) . ']';
1784                         } else {
1785                             $_output .= "[\$this->_tpl_vars['" . substr($_index, 1) . "']]";
1786                         }
1787                     } else {
1788                         $_var_parts = explode('.', $_index);
1789                         $_var_section = $_var_parts[0];
1790                         $_var_section_prop = isset($_var_parts[1]) ? $_var_parts[1] : 'index';
1791                         $_output .= "[\$this->_sections['$_var_section']['$_var_section_prop']]";
1792                     }
1793                 } else if (substr($_index, 0, 1) == '.') {
1794                     if (substr($_index, 1, 1) == '$')
1795                         $_output .= "[\$this->_tpl_vars['" . substr($_index, 2) . "']]";
1796                     else
1797                         $_output .= "['" . substr($_index, 1) . "']";
1798                 } else if (substr($_index,0,2) == '->') {
1799                     if(substr($_index,2,2) == '__') {
1800                         $this->_syntax_error('call to internal object members is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1801                     } elseif($this->security && substr($_index, 2, 1) == '_') {
1802                         $this->_syntax_error('(secure) call to private object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1803                     } elseif (substr($_index, 2, 1) == '$') {
1804                         if ($this->security) {
1805                             $this->_syntax_error('(secure) call to dynamic object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1806                         } else {
1807                             $_output .= '->{(($_var=$this->_tpl_vars[\''.substr($_index,3).'\']) && substr($_var,0,2)!=\'__\') ? $_var : $this->trigger_error("cannot access property \\"$_var\\"")}';
1808                         }
1809                     } else {
1810                         $_output .= $_index;
1811                     }
1812                 } elseif (substr($_index, 0, 1) == '(') {
1813                     $_index = $this->_parse_parenth_args($_index);
1814                     $_output .= $_index;
1815                 } else {
1816                     $_output .= $_index;
1817                 }
1818             }
1819         }
1820
1821         return $_output;
1822     }
1823
1824     /**
1825      * parse arguments in function call parenthesis
1826      *
1827      * @param string $parenth_args
1828      * @return string
1829      */
1830     function _parse_parenth_args($parenth_args)
1831     {
1832         preg_match_all('~' . $this->_param_regexp . '~',$parenth_args, $match);
1833         $orig_vals = $match = $match[0];
1834         $this->_parse_vars_props($match);
1835         $replace = array();
1836         for ($i = 0, $count = count($match); $i < $count; $i++) {
1837             $replace[$orig_vals[$i]] = $match[$i];
1838         }
1839         return strtr($parenth_args, $replace);
1840     }
1841
1842     /**
1843      * parse configuration variable expression into PHP code
1844      *
1845      * @param string $conf_var_expr
1846      */
1847     function _parse_conf_var($conf_var_expr)
1848     {
1849         $parts = explode('|', $conf_var_expr, 2);
1850         $var_ref = $parts[0];
1851         $modifiers = isset($parts[1]) ? $parts[1] : '';
1852
1853         $var_name = substr($var_ref, 1, -1);
1854
1855         $output = "\$this->_config[0]['vars']['$var_name']";
1856
1857         $this->_parse_modifiers($output, $modifiers);
1858
1859         return $output;
1860     }
1861
1862     /**
1863      * parse section property expression into PHP code
1864      *
1865      * @param string $section_prop_expr
1866      * @return string
1867      */
1868     function _parse_section_prop($section_prop_expr)
1869     {
1870         $parts = explode('|', $section_prop_expr, 2);
1871         $var_ref = $parts[0];
1872         $modifiers = isset($parts[1]) ? $parts[1] : '';
1873
1874         preg_match('!%(\w+)\.(\w+)%!', $var_ref, $match);
1875         $section_name = $match[1];
1876         $prop_name = $match[2];
1877
1878         $output = "\$this->_sections['$section_name']['$prop_name']";
1879
1880         $this->_parse_modifiers($output, $modifiers);
1881
1882         return $output;
1883     }
1884
1885
1886     /**
1887      * parse modifier chain into PHP code
1888      *
1889      * sets $output to parsed modified chain
1890      * @param string $output
1891      * @param string $modifier_string
1892      */
1893     function _parse_modifiers(&$output, $modifier_string)
1894     {
1895         preg_match_all('~\|(@?\w+)((?>:(?:'. $this->_qstr_regexp . '|[^|]+))*)~', '|' . $modifier_string, $_match);
1896         list(, $_modifiers, $modifier_arg_strings) = $_match;
1897
1898         for ($_i = 0, $_for_max = count($_modifiers); $_i < $_for_max; $_i++) {
1899             $_modifier_name = $_modifiers[$_i];
1900
1901             if($_modifier_name == 'smarty') {
1902                 // skip smarty modifier
1903                 continue;
1904             }
1905
1906             preg_match_all('~:(' . $this->_qstr_regexp . '|[^:]+)~', $modifier_arg_strings[$_i], $_match);
1907             $_modifier_args = $_match[1];
1908
1909             if (substr($_modifier_name, 0, 1) == '@') {
1910                 $_map_array = false;
1911                 $_modifier_name = substr($_modifier_name, 1);
1912             } else {
1913                 $_map_array = true;
1914             }
1915
1916             if (empty($this->_plugins['modifier'][$_modifier_name])
1917                 && !$this->_get_plugin_filepath('modifier', $_modifier_name)
1918                 && function_exists($_modifier_name)) {
1919                 if ($this->security && !in_array($_modifier_name, $this->security_settings['MODIFIER_FUNCS'])) {
1920                     $this->_trigger_fatal_error("[plugin] (secure mode) modifier '$_modifier_name' is not allowed" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
1921                 } else {
1922                     $this->_plugins['modifier'][$_modifier_name] = array($_modifier_name,  null, null, false);
1923                 }
1924             }
1925             $this->_add_plugin('modifier', $_modifier_name);
1926
1927             $this->_parse_vars_props($_modifier_args);
1928
1929             if($_modifier_name == 'default') {
1930                 // supress notifications of default modifier vars and args
1931                 if(substr($output, 0, 1) == '$') {
1932                     $output = '@' . $output;
1933                 }
1934                 if(isset($_modifier_args[0]) && substr($_modifier_args[0], 0, 1) == '$') {
1935                     $_modifier_args[0] = '@' . $_modifier_args[0];
1936                 }
1937             }
1938             if (count($_modifier_args) > 0)
1939                 $_modifier_args = ', '.implode(', ', $_modifier_args);
1940             else
1941                 $_modifier_args = '';
1942
1943             if ($_map_array) {
1944                 $output = "((is_array(\$_tmp=$output)) ? \$this->_run_mod_handler('$_modifier_name', true, \$_tmp$_modifier_args) : " . $this->_compile_plugin_call('modifier', $_modifier_name) . "(\$_tmp$_modifier_args))";
1945
1946             } else {
1947
1948                 $output = $this->_compile_plugin_call('modifier', $_modifier_name)."($output$_modifier_args)";
1949
1950             }
1951         }
1952     }
1953
1954
1955     /**
1956      * add plugin
1957      *
1958      * @param string $type
1959      * @param string $name
1960      * @param boolean? $delayed_loading
1961      */
1962     function _add_plugin($type, $name, $delayed_loading = null)
1963     {
1964         if (!isset($this->_plugin_info[$type])) {
1965             $this->_plugin_info[$type] = array();
1966         }
1967         if (!isset($this->_plugin_info[$type][$name])) {
1968             $this->_plugin_info[$type][$name] = array($this->_current_file,
1969                                                       $this->_current_line_no,
1970                                                       $delayed_loading);
1971         }
1972     }
1973
1974
1975     /**
1976      * Compiles references of type $smarty.foo
1977      *
1978      * @param string $indexes
1979      * @return string
1980      */
1981     function _compile_smarty_ref(&$indexes)
1982     {
1983         /* Extract the reference name. */
1984         $_ref = substr($indexes[0], 1);
1985         foreach($indexes as $_index_no=>$_index) {
1986             if (substr($_index, 0, 1) != '.' && $_index_no<2 || !preg_match('~^(\.|\[|->)~', $_index)) {
1987                 $this->_syntax_error('$smarty' . implode('', array_slice($indexes, 0, 2)) . ' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
1988             }
1989         }
1990
1991         switch ($_ref) {
1992             case 'now':
1993                 $compiled_ref = 'time()';
1994                 $_max_index = 1;
1995                 break;
1996
1997             case 'foreach':
1998                 array_shift($indexes);
1999                 $_var = $this->_parse_var_props(substr($indexes[0], 1));
2000                 $_propname = substr($indexes[1], 1);
2001                 $_max_index = 1;
2002                 switch ($_propname) {
2003                     case 'index':
2004                         array_shift($indexes);
2005                         $compiled_ref = "(\$this->_foreach[$_var]['iteration']-1)";
2006                         break;
2007                         
2008                     case 'first':
2009                         array_shift($indexes);
2010                         $compiled_ref = "(\$this->_foreach[$_var]['iteration'] <= 1)";
2011                         break;
2012
2013                     case 'last':
2014                         array_shift($indexes);
2015                         $compiled_ref = "(\$this->_foreach[$_var]['iteration'] == \$this->_foreach[$_var]['total'])";
2016                         break;
2017                         
2018                     case 'show':
2019                         array_shift($indexes);
2020                         $compiled_ref = "(\$this->_foreach[$_var]['total'] > 0)";
2021                         break;
2022                         
2023                     default:
2024                         unset($_max_index);
2025                         $compiled_ref = "\$this->_foreach[$_var]";
2026                 }
2027                 break;
2028
2029             case 'section':
2030                 array_shift($indexes);
2031                 $_var = $this->_parse_var_props(substr($indexes[0], 1));
2032                 $compiled_ref = "\$this->_sections[$_var]";
2033                 break;
2034
2035             case 'get':
2036                 $compiled_ref = ($this->request_use_auto_globals) ? '$_GET' : "\$GLOBALS['HTTP_GET_VARS']";
2037                 break;
2038
2039             case 'post':
2040                 $compiled_ref = ($this->request_use_auto_globals) ? '$_POST' : "\$GLOBALS['HTTP_POST_VARS']";
2041                 break;
2042
2043             case 'cookies':
2044                 $compiled_ref = ($this->request_use_auto_globals) ? '$_COOKIE' : "\$GLOBALS['HTTP_COOKIE_VARS']";
2045                 break;
2046
2047             case 'env':
2048                 $compiled_ref = ($this->request_use_auto_globals) ? '$_ENV' : "\$GLOBALS['HTTP_ENV_VARS']";
2049                 break;
2050
2051             case 'server':
2052                 $compiled_ref = ($this->request_use_auto_globals) ? '$_SERVER' : "\$GLOBALS['HTTP_SERVER_VARS']";
2053                 break;
2054
2055             case 'session':
2056                 $compiled_ref = ($this->request_use_auto_globals) ? '$_SESSION' : "\$GLOBALS['HTTP_SESSION_VARS']";
2057                 break;
2058
2059             /*
2060              * These cases are handled either at run-time or elsewhere in the
2061              * compiler.
2062              */
2063             case 'request':
2064                 if ($this->request_use_auto_globals) {
2065                     $compiled_ref = '$_REQUEST';
2066                     break;
2067                 } else {
2068                     $this->_init_smarty_vars = true;
2069                 }
2070                 return null;
2071
2072             case 'capture':
2073                 return null;
2074
2075             case 'template':
2076                 $compiled_ref = "'$this->_current_file'";
2077                 $_max_index = 1;
2078                 break;
2079
2080             case 'version':
2081                 $compiled_ref = "'$this->_version'";
2082                 $_max_index = 1;
2083                 break;
2084
2085             case 'const':
2086                 if ($this->security && !$this->security_settings['ALLOW_CONSTANTS']) {
2087                     $this->_syntax_error("(secure mode) constants not permitted",
2088                                          E_USER_WARNING, __FILE__, __LINE__);
2089                     return;
2090                 }
2091                 array_shift($indexes);
2092                 if (preg_match('!^\.\w+$!', $indexes[0])) {
2093                     $compiled_ref = '@' . substr($indexes[0], 1);
2094                 } else {
2095                     $_val = $this->_parse_var_props(substr($indexes[0], 1));
2096                     $compiled_ref = '@constant(' . $_val . ')';
2097                 }
2098                 $_max_index = 1;
2099                 break;
2100
2101             case 'config':
2102                 $compiled_ref = "\$this->_config[0]['vars']";
2103                 $_max_index = 3;
2104                 break;
2105
2106             case 'ldelim':
2107                 $compiled_ref = "'$this->left_delimiter'";
2108                 break;
2109
2110             case 'rdelim':
2111                 $compiled_ref = "'$this->right_delimiter'";
2112                 break;
2113                 
2114             default:
2115                 $this->_syntax_error('$smarty.' . $_ref . ' is an unknown reference', E_USER_ERROR, __FILE__, __LINE__);
2116                 break;
2117         }
2118
2119         if (isset($_max_index) && count($indexes) > $_max_index) {
2120             $this->_syntax_error('$smarty' . implode('', $indexes) .' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
2121         }
2122
2123         array_shift($indexes);
2124         return $compiled_ref;
2125     }
2126
2127     /**
2128      * compiles call to plugin of type $type with name $name
2129      * returns a string containing the function-name or method call
2130      * without the paramter-list that would have follow to make the
2131      * call valid php-syntax
2132      *
2133      * @param string $type
2134      * @param string $name
2135      * @return string
2136      */
2137     function _compile_plugin_call($type, $name) {
2138         if (isset($this->_plugins[$type][$name])) {
2139             /* plugin loaded */
2140             if (is_array($this->_plugins[$type][$name][0])) {
2141                 return ((is_object($this->_plugins[$type][$name][0][0])) ?
2142                         "\$this->_plugins['$type']['$name'][0][0]->"    /* method callback */
2143                         : (string)($this->_plugins[$type][$name][0][0]).'::'    /* class callback */
2144                        ). $this->_plugins[$type][$name][0][1];
2145
2146             } else {
2147                 /* function callback */
2148                 return $this->_plugins[$type][$name][0];
2149
2150             }
2151         } else {
2152             /* plugin not loaded -> auto-loadable-plugin */
2153             return 'smarty_'.$type.'_'.$name;
2154
2155         }
2156     }
2157
2158     /**
2159      * load pre- and post-filters
2160      */
2161     function _load_filters()
2162     {
2163         if (count($this->_plugins['prefilter']) > 0) {
2164             foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
2165                 if ($prefilter === false) {
2166                     unset($this->_plugins['prefilter'][$filter_name]);
2167                     $_params = array('plugins' => array(array('prefilter', $filter_name, null, null, false)));
2168                     require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
2169                     smarty_core_load_plugins($_params, $this);
2170                 }
2171             }
2172         }
2173         if (count($this->_plugins['postfilter']) > 0) {
2174             foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
2175                 if ($postfilter === false) {
2176                     unset($this->_plugins['postfilter'][$filter_name]);
2177                     $_params = array('plugins' => array(array('postfilter', $filter_name, null, null, false)));
2178                     require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
2179                     smarty_core_load_plugins($_params, $this);
2180                 }
2181             }
2182         }
2183     }
2184
2185
2186     /**
2187      * Quote subpattern references
2188      *
2189      * @param string $string
2190      * @return string
2191      */
2192     function _quote_replace($string)
2193     {
2194         return strtr($string, array('\\' => '\\\\', '$' => '\\$'));
2195     }
2196
2197     /**
2198      * display Smarty syntax error
2199      *
2200      * @param string $error_msg
2201      * @param integer $error_type
2202      * @param string $file
2203      * @param integer $line
2204      */
2205     function _syntax_error($error_msg, $error_type = E_USER_ERROR, $file=null, $line=null)
2206     {
2207         $this->_trigger_fatal_error("syntax error: $error_msg", $this->_current_file, $this->_current_line_no, $file, $line, $error_type);
2208     }
2209
2210
2211     /**
2212      * check if the compilation changes from cacheable to
2213      * non-cacheable state with the beginning of the current
2214      * plugin. return php-code to reflect the transition.
2215      * @return string
2216      */
2217     function _push_cacheable_state($type, $name) {
2218         $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2219         if ($_cacheable
2220             || 0<$this->_cacheable_state++) return '';
2221         if (!isset($this->_cache_serial)) $this->_cache_serial = md5(uniqid('Smarty'));
2222         $_ret = 'if ($this->caching && !$this->_cache_including) { echo \'{nocache:'
2223             . $this->_cache_serial . '#' . $this->_nocache_count
2224             . '}\'; };';
2225         return $_ret;
2226     }
2227
2228
2229     /**
2230      * check if the compilation changes from non-cacheable to
2231      * cacheable state with the end of the current plugin return
2232      * php-code to reflect the transition.
2233      * @return string
2234      */
2235     function _pop_cacheable_state($type, $name) {
2236         $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2237         if ($_cacheable
2238             || --$this->_cacheable_state>0) return '';
2239         return 'if ($this->caching && !$this->_cache_including) { echo \'{/nocache:'
2240             . $this->_cache_serial . '#' . ($this->_nocache_count++)
2241             . '}\'; };';
2242     }
2243
2244
2245     /**
2246      * push opening tag-name, file-name and line-number on the tag-stack
2247      * @param string the opening tag's name
2248      */
2249     function _push_tag($open_tag)
2250     {
2251         array_push($this->_tag_stack, array($open_tag, $this->_current_line_no));
2252     }
2253
2254     /**
2255      * pop closing tag-name
2256      * raise an error if this stack-top doesn't match with the closing tag
2257      * @param string the closing tag's name
2258      * @return string the opening tag's name
2259      */
2260     function _pop_tag($close_tag)
2261     {
2262         $message = '';
2263         if (count($this->_tag_stack)>0) {
2264             list($_open_tag, $_line_no) = array_pop($this->_tag_stack);
2265             if ($close_tag == $_open_tag) {
2266                 return $_open_tag;
2267             }
2268             if ($close_tag == 'if' && ($_open_tag == 'else' || $_open_tag == 'elseif' )) {
2269                 return $this->_pop_tag($close_tag);
2270             }
2271             if ($close_tag == 'section' && $_open_tag == 'sectionelse') {
2272                 $this->_pop_tag($close_tag);
2273                 return $_open_tag;
2274             }
2275             if ($close_tag == 'foreach' && $_open_tag == 'foreachelse') {
2276                 $this->_pop_tag($close_tag);
2277                 return $_open_tag;
2278             }
2279             if ($_open_tag == 'else' || $_open_tag == 'elseif') {
2280                 $_open_tag = 'if';
2281             } elseif ($_open_tag == 'sectionelse') {
2282                 $_open_tag = 'section';
2283             } elseif ($_open_tag == 'foreachelse') {
2284                 $_open_tag = 'foreach';
2285             }
2286             $message = " expected {/$_open_tag} (opened line $_line_no).";
2287         }
2288         $this->_syntax_error("mismatched tag {/$close_tag}.$message",
2289                              E_USER_ERROR, __FILE__, __LINE__);
2290     }
2291
2292 }
2293
2294 /**
2295  * compare to values by their string length
2296  *
2297  * @access private
2298  * @param string $a
2299  * @param string $b
2300  * @return 0|-1|1
2301  */
2302 function _smarty_sort_length($a, $b)
2303 {
2304     if($a == $b)
2305         return 0;
2306
2307     if(strlen($a) == strlen($b))
2308         return ($a > $b) ? -1 : 1;
2309
2310     return (strlen($a) > strlen($b)) ? -1 : 1;
2311 }
2312
2313
2314 /* vim: set et: */
2315
2316 ?>