source: trunk/include/smarty/libs/Smarty_Compiler.class.php @ 2216

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