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