source: branches/version-2_5-dev/data/module/Smarty/libs/Smarty_Compiler.class.php @ 20119

Revision 20119, 92.2 KB checked in by nanasess, 13 years ago (diff)

module 以下は svn:keywords を除外

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