source: branches/comu/data/module/Smarty/libs/Smarty_Compiler.class.php @ 2

Revision 2, 89.2 KB checked in by root, 17 years ago (diff)

new import

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