Code

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