source: trunk/workflow/inc/smarty/Smarty_Compiler.class.php @ 7684

Revision 7684, 90.2 KB checked in by asaikawa, 11 years ago (diff)

Ticket #3236 - Desfazendo uma alteracao que impactou em uma inconsistencia na compilacao dos templates do smarty

  • Property svn:executable set to *
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 760 2008-04-03 19:15:17Z drovetto $ */
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            $token = &$tokens[$i];
1284
1285            switch (strtolower($token)) {
1286                case '!':
1287                case '%':
1288                case '!==':
1289                case '==':
1290                case '===':
1291                case '>':
1292                case '<':
1293                case '!=':
1294                case '<>':
1295                case '<<':
1296                case '>>':
1297                case '<=':
1298                case '>=':
1299                case '&&':
1300                case '||':
1301                case '|':
1302                case '^':
1303                case '&':
1304                case '~':
1305                case ')':
1306                case ',':
1307                case '+':
1308                case '-':
1309                case '*':
1310                case '/':
1311                case '@':
1312                    break;
1313
1314                case 'eq':
1315                    $token = '==';
1316                    break;
1317
1318                case 'ne':
1319                case 'neq':
1320                    $token = '!=';
1321                    break;
1322
1323                case 'lt':
1324                    $token = '<';
1325                    break;
1326
1327                case 'le':
1328                case 'lte':
1329                    $token = '<=';
1330                    break;
1331
1332                case 'gt':
1333                    $token = '>';
1334                    break;
1335
1336                case 'ge':
1337                case 'gte':
1338                    $token = '>=';
1339                    break;
1340
1341                case 'and':
1342                    $token = '&&';
1343                    break;
1344
1345                case 'or':
1346                    $token = '||';
1347                    break;
1348
1349                case 'not':
1350                    $token = '!';
1351                    break;
1352
1353                case 'mod':
1354                    $token = '%';
1355                    break;
1356
1357                case '(':
1358                    array_push($is_arg_stack, $i);
1359                    break;
1360
1361                case 'is':
1362                    /* If last token was a ')', we operate on the parenthesized
1363                       expression. The start of the expression is on the stack.
1364                       Otherwise, we operate on the last encountered token. */
1365                    if ($tokens[$i-1] == ')')
1366                        $is_arg_start = array_pop($is_arg_stack);
1367                    else
1368                        $is_arg_start = $i-1;
1369                    /* Construct the argument for 'is' expression, so it knows
1370                       what to operate on. */
1371                    $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
1372
1373                    /* Pass all tokens from next one until the end to the
1374                       'is' expression parsing function. The function will
1375                       return modified tokens, where the first one is the result
1376                       of the 'is' expression and the rest are the tokens it
1377                       didn't touch. */
1378                    $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));
1379
1380                    /* Replace the old tokens with the new ones. */
1381                    array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);
1382
1383                    /* Adjust argument start so that it won't change from the
1384                       current position for the next iteration. */
1385                    $i = $is_arg_start;
1386                    break;
1387
1388                default:
1389                    if(preg_match('~^' . $this->_func_regexp . '$~', $token) ) {
1390                            // function call
1391                            if($this->security &&
1392                               !in_array($token, $this->security_settings['IF_FUNCS'])) {
1393                                $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);
1394                            }
1395                    } elseif(preg_match('~^' . $this->_var_regexp . '$~', $token) && (strpos('+-*/^%&|', substr($token, -1)) === false) && isset($tokens[$i+1]) && $tokens[$i+1] == '(') {
1396                        // variable function call
1397                        $this->_syntax_error("variable function call '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);                     
1398                    } elseif(preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$~', $token)) {
1399                        // object or variable
1400                        $token = $this->_parse_var_props($token);
1401                    } elseif(is_numeric($token)) {
1402                        // number, skip it
1403                    } else {
1404                        $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__);
1405                    }
1406                    break;
1407            }
1408        }
1409
1410        if ($elseif)
1411            return '<?php elseif ('.implode(' ', $tokens).'): ?>';
1412        else
1413            return '<?php if ('.implode(' ', $tokens).'): ?>';
1414    }
1415
1416
1417    function _compile_arg_list($type, $name, $attrs, &$cache_code) {
1418        $arg_list = array();
1419
1420        if (isset($type) && isset($name)
1421            && isset($this->_plugins[$type])
1422            && isset($this->_plugins[$type][$name])
1423            && empty($this->_plugins[$type][$name][4])
1424            && is_array($this->_plugins[$type][$name][5])
1425            ) {
1426            /* we have a list of parameters that should be cached */
1427            $_cache_attrs = $this->_plugins[$type][$name][5];
1428            $_count = $this->_cache_attrs_count++;
1429            $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');";
1430
1431        } else {
1432            /* no parameters are cached */
1433            $_cache_attrs = null;
1434        }
1435
1436        foreach ($attrs as $arg_name => $arg_value) {
1437            if (is_bool($arg_value))
1438                $arg_value = $arg_value ? 'true' : 'false';
1439            if (is_null($arg_value))
1440                $arg_value = 'null';
1441            if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) {
1442                $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)";
1443            } else {
1444                $arg_list[] = "'$arg_name' => $arg_value";
1445            }
1446        }
1447        return $arg_list;
1448    }
1449
1450    /**
1451     * Parse is expression
1452     *
1453     * @param string $is_arg
1454     * @param array $tokens
1455     * @return array
1456     */
1457    function _parse_is_expr($is_arg, $tokens)
1458    {
1459        $expr_end = 0;
1460        $negate_expr = false;
1461
1462        if (($first_token = array_shift($tokens)) == 'not') {
1463            $negate_expr = true;
1464            $expr_type = array_shift($tokens);
1465        } else
1466            $expr_type = $first_token;
1467
1468        switch ($expr_type) {
1469            case 'even':
1470                if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1471                    ++$expr_end;
1472                    $expr_arg = $tokens[$expr_end++];
1473                    $expr = "!(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1474                } else
1475                    $expr = "!(1 & $is_arg)";
1476                break;
1477
1478            case 'odd':
1479                if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1480                    ++$expr_end;
1481                    $expr_arg = $tokens[$expr_end++];
1482                    $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1483                } else
1484                    $expr = "(1 & $is_arg)";
1485                break;
1486
1487            case 'div':
1488                if (@$tokens[$expr_end] == 'by') {
1489                    ++$expr_end;
1490                    $expr_arg = $tokens[$expr_end++];
1491                    $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")";
1492                } else {
1493                    $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__);
1494                }
1495                break;
1496
1497            default:
1498                $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__);
1499                break;
1500        }
1501
1502        if ($negate_expr) {
1503            $expr = "!($expr)";
1504        }
1505
1506        array_splice($tokens, 0, $expr_end, $expr);
1507
1508        return $tokens;
1509    }
1510
1511
1512    /**
1513     * Parse attribute string
1514     *
1515     * @param string $tag_args
1516     * @return array
1517     */
1518    function _parse_attrs($tag_args)
1519    {
1520
1521        /* Tokenize tag attributes. */
1522        preg_match_all('~(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+)
1523                         )+ |
1524                         [=]
1525                        ~x', $tag_args, $match);
1526        $tokens       = $match[0];
1527
1528        $attrs = array();
1529        /* Parse state:
1530            0 - expecting attribute name
1531            1 - expecting '='
1532            2 - expecting attribute value (not '=') */
1533        $state = 0;
1534
1535        foreach ($tokens as $token) {
1536            switch ($state) {
1537                case 0:
1538                    /* If the token is a valid identifier, we set attribute name
1539                       and go to state 1. */
1540                    if (preg_match('~^\w+$~', $token)) {
1541                        $attr_name = $token;
1542                        $state = 1;
1543                    } else
1544                        $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__);
1545                    break;
1546
1547                case 1:
1548                    /* If the token is '=', then we go to state 2. */
1549                    if ($token == '=') {
1550                        $state = 2;
1551                    } else
1552                        $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1553                    break;
1554
1555                case 2:
1556                    /* If token is not '=', we set the attribute value and go to
1557                       state 0. */
1558                    if ($token != '=') {
1559                        /* We booleanize the token if it's a non-quoted possible
1560                           boolean value. */
1561                        if (preg_match('~^(on|yes|true)$~', $token)) {
1562                            $token = 'true';
1563                        } else if (preg_match('~^(off|no|false)$~', $token)) {
1564                            $token = 'false';
1565                        } else if ($token == 'null') {
1566                            $token = 'null';
1567                        } else if (preg_match('~^' . $this->_num_const_regexp . '|0[xX][0-9a-fA-F]+$~', $token)) {
1568                            /* treat integer literally */
1569                        } else if (!preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$~', $token)) {
1570                            /* treat as a string, double-quote it escaping quotes */
1571                            $token = '"'.addslashes($token).'"';
1572                        }
1573
1574                        $attrs[$attr_name] = $token;
1575                        $state = 0;
1576                    } else
1577                        $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__);
1578                    break;
1579            }
1580            $last_token = $token;
1581        }
1582
1583        if($state != 0) {
1584            if($state == 1) {
1585                $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1586            } else {
1587                $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__);
1588            }
1589        }
1590
1591        $this->_parse_vars_props($attrs);
1592
1593        return $attrs;
1594    }
1595
1596    /**
1597     * compile multiple variables and section properties tokens into
1598     * PHP code
1599     *
1600     * @param array $tokens
1601     */
1602    function _parse_vars_props(&$tokens)
1603    {
1604        foreach($tokens as $key => $val) {
1605            $tokens[$key] = $this->_parse_var_props($val);
1606        }
1607    }
1608
1609    /**
1610     * compile single variable and section properties token into
1611     * PHP code
1612     *
1613     * @param string $val
1614     * @param string $tag_attrs
1615     * @return string
1616     */
1617    function _parse_var_props($val)
1618    {
1619        $val = trim($val);
1620
1621        if(preg_match('~^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$~', $val, $match)) {
1622            // $ variable or object
1623            $return = $this->_parse_var($match[1]);
1624            $modifiers = $match[2];
1625            if (!empty($this->default_modifiers) && !preg_match('~(^|\|)smarty:nodefaults($|\|)~',$modifiers)) {
1626                $_default_mod_string = implode('|',(array)$this->default_modifiers);
1627                $modifiers = empty($modifiers) ? $_default_mod_string : $_default_mod_string . '|' . $modifiers;
1628            }
1629            $this->_parse_modifiers($return, $modifiers);
1630            return $return;
1631        } elseif (preg_match('~^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1632                // double quoted text
1633                preg_match('~^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1634                $return = $this->_expand_quoted_text($match[1]);
1635                if($match[2] != '') {
1636                    $this->_parse_modifiers($return, $match[2]);
1637                }
1638                return $return;
1639            }
1640        elseif(preg_match('~^' . $this->_num_const_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1641                // numerical constant
1642                preg_match('~^(' . $this->_num_const_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1643                if($match[2] != '') {
1644                    $this->_parse_modifiers($match[1], $match[2]);
1645                    return $match[1];
1646                }
1647            }
1648        elseif(preg_match('~^' . $this->_si_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1649                // single quoted text
1650                preg_match('~^(' . $this->_si_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1651                if($match[2] != '') {
1652                    $this->_parse_modifiers($match[1], $match[2]);
1653                    return $match[1];
1654                }
1655            }
1656        elseif(preg_match('~^' . $this->_cvar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1657                // config var
1658                return $this->_parse_conf_var($val);
1659            }
1660        elseif(preg_match('~^' . $this->_svar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1661                // section var
1662                return $this->_parse_section_prop($val);
1663            }
1664        elseif(!in_array($val, $this->_permitted_tokens) && !is_numeric($val)) {
1665            // literal string
1666            return $this->_expand_quoted_text('"' . strtr($val, array('\\' => '\\\\', '"' => '\\"')) .'"');
1667        }
1668        return $val;
1669    }
1670
1671    /**
1672     * expand quoted text with embedded variables
1673     *
1674     * @param string $var_expr
1675     * @return string
1676     */
1677    function _expand_quoted_text($var_expr)
1678    {
1679        // if contains unescaped $, expand it
1680        if(preg_match_all('~(?:\`(?<!\\\\)\$' . $this->_dvar_guts_regexp . '(?:' . $this->_obj_ext_regexp . ')*\`)|(?:(?<!\\\\)\$\w+(\[[a-zA-Z0-9]+\])*)~', $var_expr, $_match)) {
1681            $_match = $_match[0];
1682            $_replace = array();
1683            foreach($_match as $_var) {
1684                $_replace[$_var] = '".(' . $this->_parse_var(str_replace('`','',$_var)) . ')."';
1685            }
1686            $var_expr = strtr($var_expr, $_replace);
1687            $_return = preg_replace('~\.""|(?<!\\\\)""\.~', '', $var_expr);
1688        } else {
1689            $_return = $var_expr;
1690        }
1691        // replace double quoted literal string with single quotes
1692        $_return = preg_replace('~^"([\s\w]+)"$~',"'\\1'",$_return);
1693        return $_return;
1694    }
1695
1696    /**
1697     * parse variable expression into PHP code
1698     *
1699     * @param string $var_expr
1700     * @param string $output
1701     * @return string
1702     */
1703    function _parse_var($var_expr)
1704    {
1705        $_has_math = false;
1706        $_math_vars = preg_split('~('.$this->_dvar_math_regexp.'|'.$this->_qstr_regexp.')~', $var_expr, -1, PREG_SPLIT_DELIM_CAPTURE);
1707
1708        if(count($_math_vars) > 1) {
1709            $_first_var = "";
1710            $_complete_var = "";
1711            $_output = "";
1712            // simple check if there is any math, to stop recursion (due to modifiers with "xx % yy" as parameter)
1713            foreach($_math_vars as $_k => $_math_var) {
1714                $_math_var = $_math_vars[$_k];
1715
1716                if(!empty($_math_var) || is_numeric($_math_var)) {
1717                    // hit a math operator, so process the stuff which came before it
1718                    if(preg_match('~^' . $this->_dvar_math_regexp . '$~', $_math_var)) {
1719                        $_has_math = true;
1720                        if(!empty($_complete_var) || is_numeric($_complete_var)) {
1721                            $_output .= $this->_parse_var($_complete_var);
1722                        }
1723
1724                        // just output the math operator to php
1725                        $_output .= $_math_var;
1726
1727                        if(empty($_first_var))
1728                            $_first_var = $_complete_var;
1729
1730                        $_complete_var = "";
1731                    } else {
1732                        $_complete_var .= $_math_var;
1733                    }
1734                }
1735            }
1736            if($_has_math) {
1737                if(!empty($_complete_var) || is_numeric($_complete_var))
1738                    $_output .= $this->_parse_var($_complete_var);
1739
1740                // get the modifiers working (only the last var from math + modifier is left)
1741                $var_expr = $_complete_var;
1742            }
1743        }
1744
1745        // prevent cutting of first digit in the number (we _definitly_ got a number if the first char is a digit)
1746        if(is_numeric(substr($var_expr, 0, 1)))
1747            $_var_ref = $var_expr;
1748        else
1749            $_var_ref = substr($var_expr, 1);
1750       
1751        if(!$_has_math) {
1752           
1753            // get [foo] and .foo and ->foo and (...) pieces
1754            preg_match_all('~(?:^\w+)|' . $this->_obj_params_regexp . '|(?:' . $this->_var_bracket_regexp . ')|->\$?\w+|\.\$?\w+|\S+~', $_var_ref, $match);
1755                       
1756            $_indexes = $match[0];
1757            $_var_name = array_shift($_indexes);
1758
1759            /* Handle $smarty.* variable references as a special case. */
1760            if ($_var_name == 'smarty') {
1761                /*
1762                 * If the reference could be compiled, use the compiled output;
1763                 * otherwise, fall back on the $smarty variable generated at
1764                 * run-time.
1765                 */
1766                if (($smarty_ref = $this->_compile_smarty_ref($_indexes)) !== null) {
1767                    $_output = $smarty_ref;
1768                } else {
1769                    $_var_name = substr(array_shift($_indexes), 1);
1770                    $_output = "\$this->_smarty_vars['$_var_name']";
1771                }
1772            } elseif(is_numeric($_var_name) && is_numeric(substr($var_expr, 0, 1))) {
1773                // because . is the operator for accessing arrays thru inidizes we need to put it together again for floating point numbers
1774                if(count($_indexes) > 0)
1775                {
1776                    $_var_name .= implode("", $_indexes);
1777                    $_indexes = array();
1778                }
1779                $_output = $_var_name;
1780            } else {
1781                $_output = "\$this->_tpl_vars['$_var_name']";
1782            }
1783
1784            foreach ($_indexes as $_index) {
1785                if (substr($_index, 0, 1) == '[') {
1786                    $_index = substr($_index, 1, -1);
1787                    if (is_numeric($_index)) {
1788                        $_output .= "[$_index]";
1789                    } elseif (substr($_index, 0, 1) == '$') {
1790                        if (strpos($_index, '.') !== false) {
1791                            $_output .= '[' . $this->_parse_var($_index) . ']';
1792                        } else {
1793                            $_output .= "[\$this->_tpl_vars['" . substr($_index, 1) . "']]";
1794                        }
1795                    } else {
1796                        $_var_parts = explode('.', $_index);
1797                        $_var_section = $_var_parts[0];
1798                        $_var_section_prop = isset($_var_parts[1]) ? $_var_parts[1] : 'index';
1799                        $_output .= "[\$this->_sections['$_var_section']['$_var_section_prop']]";
1800                    }
1801                } else if (substr($_index, 0, 1) == '.') {
1802                    if (substr($_index, 1, 1) == '$')
1803                        $_output .= "[\$this->_tpl_vars['" . substr($_index, 2) . "']]";
1804                    else
1805                        $_output .= "['" . substr($_index, 1) . "']";
1806                } else if (substr($_index,0,2) == '->') {
1807                    if(substr($_index,2,2) == '__') {
1808                        $this->_syntax_error('call to internal object members is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1809                    } elseif($this->security && substr($_index, 2, 1) == '_') {
1810                        $this->_syntax_error('(secure) call to private object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1811                    } elseif (substr($_index, 2, 1) == '$') {
1812                        if ($this->security) {
1813                            $this->_syntax_error('(secure) call to dynamic object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1814                        } else {
1815                            $_output .= '->{(($_var=$this->_tpl_vars[\''.substr($_index,3).'\']) && substr($_var,0,2)!=\'__\') ? $_var : $this->trigger_error("cannot access property \\"$_var\\"")}';
1816                        }
1817                    } else {
1818                        $_output .= $_index;
1819                    }
1820                } elseif (substr($_index, 0, 1) == '(') {
1821                    $_index = $this->_parse_parenth_args($_index);
1822                    $_output .= $_index;
1823                } else {
1824                    $_output .= $_index;
1825                }
1826            }
1827        }
1828
1829        return $_output;
1830    }
1831
1832    /**
1833     * parse arguments in function call parenthesis
1834     *
1835     * @param string $parenth_args
1836     * @return string
1837     */
1838    function _parse_parenth_args($parenth_args)
1839    {
1840        preg_match_all('~' . $this->_param_regexp . '~',$parenth_args, $match);
1841        $orig_vals = $match = $match[0];
1842        $this->_parse_vars_props($match);
1843        $replace = array();
1844        for ($i = 0, $count = count($match); $i < $count; ++$i) {
1845            $replace[$orig_vals[$i]] = $match[$i];
1846        }
1847        return strtr($parenth_args, $replace);
1848    }
1849
1850    /**
1851     * parse configuration variable expression into PHP code
1852     *
1853     * @param string $conf_var_expr
1854     */
1855    function _parse_conf_var($conf_var_expr)
1856    {
1857        $parts = explode('|', $conf_var_expr, 2);
1858        $var_ref = $parts[0];
1859        $modifiers = isset($parts[1]) ? $parts[1] : '';
1860
1861        $var_name = substr($var_ref, 1, -1);
1862
1863        $output = "\$this->_config[0]['vars']['$var_name']";
1864
1865        $this->_parse_modifiers($output, $modifiers);
1866
1867        return $output;
1868    }
1869
1870    /**
1871     * parse section property expression into PHP code
1872     *
1873     * @param string $section_prop_expr
1874     * @return string
1875     */
1876    function _parse_section_prop($section_prop_expr)
1877    {
1878        $parts = explode('|', $section_prop_expr, 2);
1879        $var_ref = $parts[0];
1880        $modifiers = isset($parts[1]) ? $parts[1] : '';
1881
1882        preg_match('!%(\w+)\.(\w+)%!', $var_ref, $match);
1883        $section_name = $match[1];
1884        $prop_name = $match[2];
1885
1886        $output = "\$this->_sections['$section_name']['$prop_name']";
1887
1888        $this->_parse_modifiers($output, $modifiers);
1889
1890        return $output;
1891    }
1892
1893
1894    /**
1895     * parse modifier chain into PHP code
1896     *
1897     * sets $output to parsed modified chain
1898     * @param string $output
1899     * @param string $modifier_string
1900     */
1901    function _parse_modifiers(&$output, $modifier_string)
1902    {
1903        preg_match_all('~\|(@?\w+)((?>:(?:'. $this->_qstr_regexp . '|[^|]+))*)~', '|' . $modifier_string, $_match);
1904        list(, $_modifiers, $modifier_arg_strings) = $_match;
1905
1906        for ($_i = 0, $_for_max = count($_modifiers); $_i < $_for_max; ++$_i) {
1907            $_modifier_name = $_modifiers[$_i];
1908
1909            if($_modifier_name == 'smarty') {
1910                // skip smarty modifier
1911                continue;
1912            }
1913
1914            preg_match_all('~:(' . $this->_qstr_regexp . '|[^:]+)~', $modifier_arg_strings[$_i], $_match);
1915            $_modifier_args = $_match[1];
1916
1917            if (substr($_modifier_name, 0, 1) == '@') {
1918                $_map_array = false;
1919                $_modifier_name = substr($_modifier_name, 1);
1920            } else {
1921                $_map_array = true;
1922            }
1923
1924            if (empty($this->_plugins['modifier'][$_modifier_name])
1925                && !$this->_get_plugin_filepath('modifier', $_modifier_name)
1926                && function_exists($_modifier_name)) {
1927                if ($this->security && !in_array($_modifier_name, $this->security_settings['MODIFIER_FUNCS'])) {
1928                    $this->_trigger_fatal_error("[plugin] (secure mode) modifier '$_modifier_name' is not allowed" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
1929                } else {
1930                    $this->_plugins['modifier'][$_modifier_name] = array($_modifier_name,  null, null, false);
1931                }
1932            }
1933            $this->_add_plugin('modifier', $_modifier_name);
1934
1935            $this->_parse_vars_props($_modifier_args);
1936
1937            if($_modifier_name == 'default') {
1938                // supress notifications of default modifier vars and args
1939                if(substr($output, 0, 1) == '$') {
1940                    $output = '@' . $output;
1941                }
1942                if(isset($_modifier_args[0]) && substr($_modifier_args[0], 0, 1) == '$') {
1943                    $_modifier_args[0] = '@' . $_modifier_args[0];
1944                }
1945            }
1946            if (count($_modifier_args) > 0)
1947                $_modifier_args = ', '.implode(', ', $_modifier_args);
1948            else
1949                $_modifier_args = '';
1950
1951            if ($_map_array) {
1952                $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))";
1953
1954            } else {
1955
1956                $output = $this->_compile_plugin_call('modifier', $_modifier_name)."($output$_modifier_args)";
1957
1958            }
1959        }
1960    }
1961
1962
1963    /**
1964     * add plugin
1965     *
1966     * @param string $type
1967     * @param string $name
1968     * @param boolean? $delayed_loading
1969     */
1970    function _add_plugin($type, $name, $delayed_loading = null)
1971    {
1972        if (!isset($this->_plugin_info[$type])) {
1973            $this->_plugin_info[$type] = array();
1974        }
1975        if (!isset($this->_plugin_info[$type][$name])) {
1976            $this->_plugin_info[$type][$name] = array($this->_current_file,
1977                                                      $this->_current_line_no,
1978                                                      $delayed_loading);
1979        }
1980    }
1981
1982
1983    /**
1984     * Compiles references of type $smarty.foo
1985     *
1986     * @param string $indexes
1987     * @return string
1988     */
1989    function _compile_smarty_ref(&$indexes)
1990    {
1991        /* Extract the reference name. */
1992        $_ref = substr($indexes[0], 1);
1993        foreach($indexes as $_index_no=>$_index) {
1994            if (substr($_index, 0, 1) != '.' && $_index_no<2 || !preg_match('~^(\.|\[|->)~', $_index)) {
1995                $this->_syntax_error('$smarty' . implode('', array_slice($indexes, 0, 2)) . ' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
1996            }
1997        }
1998
1999        switch ($_ref) {
2000            case 'now':
2001                $compiled_ref = 'time()';
2002                $_max_index = 1;
2003                break;
2004
2005            case 'foreach':
2006                array_shift($indexes);
2007                $_var = $this->_parse_var_props(substr($indexes[0], 1));
2008                $_propname = substr($indexes[1], 1);
2009                $_max_index = 1;
2010                switch ($_propname) {
2011                    case 'index':
2012                        array_shift($indexes);
2013                        $compiled_ref = "(\$this->_foreach[$_var]['iteration']-1)";
2014                        break;
2015                       
2016                    case 'first':
2017                        array_shift($indexes);
2018                        $compiled_ref = "(\$this->_foreach[$_var]['iteration'] <= 1)";
2019                        break;
2020
2021                    case 'last':
2022                        array_shift($indexes);
2023                        $compiled_ref = "(\$this->_foreach[$_var]['iteration'] == \$this->_foreach[$_var]['total'])";
2024                        break;
2025                       
2026                    case 'show':
2027                        array_shift($indexes);
2028                        $compiled_ref = "(\$this->_foreach[$_var]['total'] > 0)";
2029                        break;
2030                       
2031                    default:
2032                        unset($_max_index);
2033                        $compiled_ref = "\$this->_foreach[$_var]";
2034                }
2035                break;
2036
2037            case 'section':
2038                array_shift($indexes);
2039                $_var = $this->_parse_var_props(substr($indexes[0], 1));
2040                $compiled_ref = "\$this->_sections[$_var]";
2041                break;
2042
2043            case 'get':
2044                $compiled_ref = ($this->request_use_auto_globals) ? '$_GET' : "\$GLOBALS['HTTP_GET_VARS']";
2045                break;
2046
2047            case 'post':
2048                $compiled_ref = ($this->request_use_auto_globals) ? '$_POST' : "\$GLOBALS['HTTP_POST_VARS']";
2049                break;
2050
2051            case 'cookies':
2052                $compiled_ref = ($this->request_use_auto_globals) ? '$_COOKIE' : "\$GLOBALS['HTTP_COOKIE_VARS']";
2053                break;
2054
2055            case 'env':
2056                $compiled_ref = ($this->request_use_auto_globals) ? '$_ENV' : "\$GLOBALS['HTTP_ENV_VARS']";
2057                break;
2058
2059            case 'server':
2060                $compiled_ref = ($this->request_use_auto_globals) ? '$_SERVER' : "\$GLOBALS['HTTP_SERVER_VARS']";
2061                break;
2062
2063            case 'session':
2064                $compiled_ref = ($this->request_use_auto_globals) ? '$_SESSION' : "\$GLOBALS['HTTP_SESSION_VARS']";
2065                break;
2066
2067            /*
2068             * These cases are handled either at run-time or elsewhere in the
2069             * compiler.
2070             */
2071            case 'request':
2072                if ($this->request_use_auto_globals) {
2073                    $compiled_ref = '$_REQUEST';
2074                    break;
2075                } else {
2076                    $this->_init_smarty_vars = true;
2077                }
2078                return null;
2079
2080            case 'capture':
2081                return null;
2082
2083            case 'template':
2084                $compiled_ref = "'$this->_current_file'";
2085                $_max_index = 1;
2086                break;
2087
2088            case 'version':
2089                $compiled_ref = "'$this->_version'";
2090                $_max_index = 1;
2091                break;
2092
2093            case 'const':
2094                if ($this->security && !$this->security_settings['ALLOW_CONSTANTS']) {
2095                    $this->_syntax_error("(secure mode) constants not permitted",
2096                                         E_USER_WARNING, __FILE__, __LINE__);
2097                    return;
2098                }
2099                array_shift($indexes);
2100                if (preg_match('!^\.\w+$!', $indexes[0])) {
2101                    $compiled_ref = '@' . substr($indexes[0], 1);
2102                } else {
2103                    $_val = $this->_parse_var_props(substr($indexes[0], 1));
2104                    $compiled_ref = '@constant(' . $_val . ')';
2105                }
2106                $_max_index = 1;
2107                break;
2108
2109            case 'config':
2110                $compiled_ref = "\$this->_config[0]['vars']";
2111                $_max_index = 3;
2112                break;
2113
2114            case 'ldelim':
2115                $compiled_ref = "'$this->left_delimiter'";
2116                break;
2117
2118            case 'rdelim':
2119                $compiled_ref = "'$this->right_delimiter'";
2120                break;
2121               
2122            default:
2123                $this->_syntax_error('$smarty.' . $_ref . ' is an unknown reference', E_USER_ERROR, __FILE__, __LINE__);
2124                break;
2125        }
2126
2127        if (isset($_max_index) && count($indexes) > $_max_index) {
2128            $this->_syntax_error('$smarty' . implode('', $indexes) .' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
2129        }
2130
2131        array_shift($indexes);
2132        return $compiled_ref;
2133    }
2134
2135    /**
2136     * compiles call to plugin of type $type with name $name
2137     * returns a string containing the function-name or method call
2138     * without the paramter-list that would have follow to make the
2139     * call valid php-syntax
2140     *
2141     * @param string $type
2142     * @param string $name
2143     * @return string
2144     */
2145    function _compile_plugin_call($type, $name) {
2146        if (isset($this->_plugins[$type][$name])) {
2147            /* plugin loaded */
2148            if (is_array($this->_plugins[$type][$name][0])) {
2149                return ((is_object($this->_plugins[$type][$name][0][0])) ?
2150                        "\$this->_plugins['$type']['$name'][0][0]->"    /* method callback */
2151                        : (string)($this->_plugins[$type][$name][0][0]).'::'    /* class callback */
2152                       ). $this->_plugins[$type][$name][0][1];
2153
2154            } else {
2155                /* function callback */
2156                return $this->_plugins[$type][$name][0];
2157
2158            }
2159        } else {
2160            /* plugin not loaded -> auto-loadable-plugin */
2161            return 'smarty_'.$type.'_'.$name;
2162
2163        }
2164    }
2165
2166    /**
2167     * load pre- and post-filters
2168     */
2169    function _load_filters()
2170    {
2171        if (count($this->_plugins['prefilter']) > 0) {
2172            foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
2173                if ($prefilter === false) {
2174                    unset($this->_plugins['prefilter'][$filter_name]);
2175                    $_params = array('plugins' => array(array('prefilter', $filter_name, null, null, false)));
2176                    require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
2177                    smarty_core_load_plugins($_params, $this);
2178                }
2179            }
2180        }
2181        if (count($this->_plugins['postfilter']) > 0) {
2182            foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
2183                if ($postfilter === false) {
2184                    unset($this->_plugins['postfilter'][$filter_name]);
2185                    $_params = array('plugins' => array(array('postfilter', $filter_name, null, null, false)));
2186                    require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
2187                    smarty_core_load_plugins($_params, $this);
2188                }
2189            }
2190        }
2191    }
2192
2193
2194    /**
2195     * Quote subpattern references
2196     *
2197     * @param string $string
2198     * @return string
2199     */
2200    function _quote_replace($string)
2201    {
2202        return strtr($string, array('\\' => '\\\\', '$' => '\\$'));
2203    }
2204
2205    /**
2206     * display Smarty syntax error
2207     *
2208     * @param string $error_msg
2209     * @param integer $error_type
2210     * @param string $file
2211     * @param integer $line
2212     */
2213    function _syntax_error($error_msg, $error_type = E_USER_ERROR, $file=null, $line=null)
2214    {
2215        $this->_trigger_fatal_error("syntax error: $error_msg", $this->_current_file, $this->_current_line_no, $file, $line, $error_type);
2216    }
2217
2218
2219    /**
2220     * check if the compilation changes from cacheable to
2221     * non-cacheable state with the beginning of the current
2222     * plugin. return php-code to reflect the transition.
2223     * @return string
2224     */
2225    function _push_cacheable_state($type, $name) {
2226        $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2227        if ($_cacheable
2228            || 0<$this->_cacheable_state++) return '';
2229        if (!isset($this->_cache_serial)) $this->_cache_serial = md5(uniqid('Smarty'));
2230        $_ret = 'if ($this->caching && !$this->_cache_including): echo \'{nocache:'
2231            . $this->_cache_serial . '#' . $this->_nocache_count
2232            . '}\'; endif;';
2233        return $_ret;
2234    }
2235
2236
2237    /**
2238     * check if the compilation changes from non-cacheable to
2239     * cacheable state with the end of the current plugin return
2240     * php-code to reflect the transition.
2241     * @return string
2242     */
2243    function _pop_cacheable_state($type, $name) {
2244        $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2245        if ($_cacheable
2246            || --$this->_cacheable_state>0) return '';
2247        return 'if ($this->caching && !$this->_cache_including): echo \'{/nocache:'
2248            . $this->_cache_serial . '#' . ($this->_nocache_count++)
2249            . '}\'; endif;';
2250    }
2251
2252
2253    /**
2254     * push opening tag-name, file-name and line-number on the tag-stack
2255     * @param string the opening tag's name
2256     */
2257    function _push_tag($open_tag)
2258    {
2259        array_push($this->_tag_stack, array($open_tag, $this->_current_line_no));
2260    }
2261
2262    /**
2263     * pop closing tag-name
2264     * raise an error if this stack-top doesn't match with the closing tag
2265     * @param string the closing tag's name
2266     * @return string the opening tag's name
2267     */
2268    function _pop_tag($close_tag)
2269    {
2270        $message = '';
2271        if (count($this->_tag_stack)>0) {
2272            list($_open_tag, $_line_no) = array_pop($this->_tag_stack);
2273            if ($close_tag == $_open_tag) {
2274                return $_open_tag;
2275            }
2276            if ($close_tag == 'if' && ($_open_tag == 'else' || $_open_tag == 'elseif' )) {
2277                return $this->_pop_tag($close_tag);
2278            }
2279            if ($close_tag == 'section' && $_open_tag == 'sectionelse') {
2280                $this->_pop_tag($close_tag);
2281                return $_open_tag;
2282            }
2283            if ($close_tag == 'foreach' && $_open_tag == 'foreachelse') {
2284                $this->_pop_tag($close_tag);
2285                return $_open_tag;
2286            }
2287            if ($_open_tag == 'else' || $_open_tag == 'elseif') {
2288                $_open_tag = 'if';
2289            } elseif ($_open_tag == 'sectionelse') {
2290                $_open_tag = 'section';
2291            } elseif ($_open_tag == 'foreachelse') {
2292                $_open_tag = 'foreach';
2293            }
2294            $message = " expected {/$_open_tag} (opened line $_line_no).";
2295        }
2296        $this->_syntax_error("mismatched tag {/$close_tag}.$message",
2297                             E_USER_ERROR, __FILE__, __LINE__);
2298    }
2299
2300}
2301
2302/**
2303 * compare to values by their string length
2304 *
2305 * @access private
2306 * @param string $a
2307 * @param string $b
2308 * @return 0|-1|1
2309 */
2310function _smarty_sort_length($a, $b)
2311{
2312    if($a == $b)
2313        return 0;
2314
2315    if(strlen($a) == strlen($b))
2316        return ($a > $b) ? -1 : 1;
2317
2318    return (strlen($a) > strlen($b)) ? -1 : 1;
2319}
2320
2321
2322/* vim: set et: */
2323
2324?>
Note: See TracBrowser for help on using the repository browser.