Code

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