source: branches/2.0/include/smarty/libs/Smarty_Compiler.class.php @ 3001

Last change on this file since 3001 was 3001, checked in by patdenice, 15 years ago

merge -c3000 from trunk to branch 2.0.
Upgrade Smarty to 2.6.22

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