root/dev/common/smarty/Smarty_Compiler.class.php @ 195

Revision 86, 89.4 KB (checked in by exi, 16 years ago)

Added Smarty files and some basic templates for index.php and killlists.

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