Code

Updated regex
[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.14
25  * @copyright 2001-2005 New Digital Group, Inc.
26  * @package Smarty
27  */
29 /* $Id: Smarty_Compiler.class.php,v 1.381 2006/05/25 14:46:18 boots 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             $this->_syntax_error("missing insert name", E_USER_ERROR, __FILE__, __LINE__);
931         }
933         if (!empty($attrs['script'])) {
934             $delayed_loading = true;
935         } else {
936             $delayed_loading = false;
937         }
939         foreach ($attrs as $arg_name => $arg_value) {
940             if (is_bool($arg_value))
941                 $arg_value = $arg_value ? 'true' : 'false';
942             $arg_list[] = "'$arg_name' => $arg_value";
943         }
945         $this->_add_plugin('insert', $name, $delayed_loading);
947         $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))";
949         return "<?php require_once(SMARTY_CORE_DIR . 'core.run_insert_handler.php');\necho smarty_core_run_insert_handler($_params, \$this); ?>" . $this->_additional_newline;
950     }
952     /**
953      * Compile {include ...} tag
954      *
955      * @param string $tag_args
956      * @return string
957      */
958     function _compile_include_tag($tag_args)
959     {
960         $attrs = $this->_parse_attrs($tag_args);
961         $arg_list = array();
963         if (empty($attrs['file'])) {
964             $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR, __FILE__, __LINE__);
965         }
967         foreach ($attrs as $arg_name => $arg_value) {
968             if ($arg_name == 'file') {
969                 $include_file = $arg_value;
970                 continue;
971             } else if ($arg_name == 'assign') {
972                 $assign_var = $arg_value;
973                 continue;
974             }
975             if (is_bool($arg_value))
976                 $arg_value = $arg_value ? 'true' : 'false';
977             $arg_list[] = "'$arg_name' => $arg_value";
978         }
980         $output = '<?php ';
982         if (isset($assign_var)) {
983             $output .= "ob_start();\n";
984         }
986         $output .=
987             "\$_smarty_tpl_vars = \$this->_tpl_vars;\n";
990         $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))";
991         $output .= "\$this->_smarty_include($_params);\n" .
992         "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" .
993         "unset(\$_smarty_tpl_vars);\n";
995         if (isset($assign_var)) {
996             $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n";
997         }
999         $output .= ' ?>';
1001         return $output;
1003     }
1005     /**
1006      * Compile {include ...} tag
1007      *
1008      * @param string $tag_args
1009      * @return string
1010      */
1011     function _compile_include_php_tag($tag_args)
1012     {
1013         $attrs = $this->_parse_attrs($tag_args);
1015         if (empty($attrs['file'])) {
1016             $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR, __FILE__, __LINE__);
1017         }
1019         $assign_var = (empty($attrs['assign'])) ? '' : $this->_dequote($attrs['assign']);
1020         $once_var = (empty($attrs['once']) || $attrs['once']=='false') ? 'false' : 'true';
1022         $arg_list = array();
1023         foreach($attrs as $arg_name => $arg_value) {
1024             if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') {
1025                 if(is_bool($arg_value))
1026                     $arg_value = $arg_value ? 'true' : 'false';
1027                 $arg_list[] = "'$arg_name' => $arg_value";
1028             }
1029         }
1031         $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', $arg_list)."))";
1033         return "<?php require_once(SMARTY_CORE_DIR . 'core.smarty_include_php.php');\nsmarty_core_smarty_include_php($_params, \$this); ?>" . $this->_additional_newline;
1034     }
1037     /**
1038      * Compile {section ...} tag
1039      *
1040      * @param string $tag_args
1041      * @return string
1042      */
1043     function _compile_section_start($tag_args)
1044     {
1045         $attrs = $this->_parse_attrs($tag_args);
1046         $arg_list = array();
1048         $output = '<?php ';
1049         $section_name = $attrs['name'];
1050         if (empty($section_name)) {
1051             $this->_syntax_error("missing section name", E_USER_ERROR, __FILE__, __LINE__);
1052         }
1054         $output .= "unset(\$this->_sections[$section_name]);\n";
1055         $section_props = "\$this->_sections[$section_name]";
1057         foreach ($attrs as $attr_name => $attr_value) {
1058             switch ($attr_name) {
1059                 case 'loop':
1060                     $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n";
1061                     break;
1063                 case 'show':
1064                     if (is_bool($attr_value))
1065                         $show_attr_value = $attr_value ? 'true' : 'false';
1066                     else
1067                         $show_attr_value = "(bool)$attr_value";
1068                     $output .= "{$section_props}['show'] = $show_attr_value;\n";
1069                     break;
1071                 case 'name':
1072                     $output .= "{$section_props}['$attr_name'] = $attr_value;\n";
1073                     break;
1075                 case 'max':
1076                 case 'start':
1077                     $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n";
1078                     break;
1080                 case 'step':
1081                     $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n";
1082                     break;
1084                 default:
1085                     $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR, __FILE__, __LINE__);
1086                     break;
1087             }
1088         }
1090         if (!isset($attrs['show']))
1091             $output .= "{$section_props}['show'] = true;\n";
1093         if (!isset($attrs['loop']))
1094             $output .= "{$section_props}['loop'] = 1;\n";
1096         if (!isset($attrs['max']))
1097             $output .= "{$section_props}['max'] = {$section_props}['loop'];\n";
1098         else
1099             $output .= "if ({$section_props}['max'] < 0)\n" .
1100                        "    {$section_props}['max'] = {$section_props}['loop'];\n";
1102         if (!isset($attrs['step']))
1103             $output .= "{$section_props}['step'] = 1;\n";
1105         if (!isset($attrs['start']))
1106             $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n";
1107         else {
1108             $output .= "if ({$section_props}['start'] < 0)\n" .
1109                        "    {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" .
1110                        "else\n" .
1111                        "    {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n";
1112         }
1114         $output .= "if ({$section_props}['show']) {\n";
1115         if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) {
1116             $output .= "    {$section_props}['total'] = {$section_props}['loop'];\n";
1117         } else {
1118             $output .= "    {$section_props}['total'] = min(ceil(({$section_props}['step'] > 0 ? {$section_props}['loop'] - {$section_props}['start'] : {$section_props}['start']+1)/abs({$section_props}['step'])), {$section_props}['max']);\n";
1119         }
1120         $output .= "    if ({$section_props}['total'] == 0)\n" .
1121                    "        {$section_props}['show'] = false;\n" .
1122                    "} else\n" .
1123                    "    {$section_props}['total'] = 0;\n";
1125         $output .= "if ({$section_props}['show']):\n";
1126         $output .= "
1127             for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1;
1128                  {$section_props}['iteration'] <= {$section_props}['total'];
1129                  {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n";
1130         $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n";
1131         $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n";
1132         $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n";
1133         $output .= "{$section_props}['first']      = ({$section_props}['iteration'] == 1);\n";
1134         $output .= "{$section_props}['last']       = ({$section_props}['iteration'] == {$section_props}['total']);\n";
1136         $output .= "?>";
1138         return $output;
1139     }
1142     /**
1143      * Compile {foreach ...} tag.
1144      *
1145      * @param string $tag_args
1146      * @return string
1147      */
1148     function _compile_foreach_start($tag_args)
1149     {
1150         $attrs = $this->_parse_attrs($tag_args);
1151         $arg_list = array();
1153         if (empty($attrs['from'])) {
1154             return $this->_syntax_error("foreach: missing 'from' attribute", E_USER_ERROR, __FILE__, __LINE__);
1155         }
1156         $from = $attrs['from'];
1158         if (empty($attrs['item'])) {
1159             return $this->_syntax_error("foreach: missing 'item' attribute", E_USER_ERROR, __FILE__, __LINE__);
1160         }
1161         $item = $this->_dequote($attrs['item']);
1162         if (!preg_match('~^\w+$~', $item)) {
1163             return $this->_syntax_error("'foreach: item' must be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1164         }
1166         if (isset($attrs['key'])) {
1167             $key  = $this->_dequote($attrs['key']);
1168             if (!preg_match('~^\w+$~', $key)) {
1169                 return $this->_syntax_error("foreach: 'key' must to be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1170             }
1171             $key_part = "\$this->_tpl_vars['$key'] => ";
1172         } else {
1173             $key = null;
1174             $key_part = '';
1175         }
1177         if (isset($attrs['name'])) {
1178             $name = $attrs['name'];
1179         } else {
1180             $name = null;
1181         }
1183         $output = '<?php ';
1184         $output .= "\$_from = $from; if (!is_array(\$_from) && !is_object(\$_from)) { settype(\$_from, 'array'); }";
1185         if (isset($name)) {
1186             $foreach_props = "\$this->_foreach[$name]";
1187             $output .= "{$foreach_props} = array('total' => count(\$_from), 'iteration' => 0);\n";
1188             $output .= "if ({$foreach_props}['total'] > 0):\n";
1189             $output .= "    foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1190             $output .= "        {$foreach_props}['iteration']++;\n";
1191         } else {
1192             $output .= "if (count(\$_from)):\n";
1193             $output .= "    foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1194         }
1195         $output .= '?>';
1197         return $output;
1198     }
1201     /**
1202      * Compile {capture} .. {/capture} tags
1203      *
1204      * @param boolean $start true if this is the {capture} tag
1205      * @param string $tag_args
1206      * @return string
1207      */
1209     function _compile_capture_tag($start, $tag_args = '')
1210     {
1211         $attrs = $this->_parse_attrs($tag_args);
1213         if ($start) {
1214             if (isset($attrs['name']))
1215                 $buffer = $attrs['name'];
1216             else
1217                 $buffer = "'default'";
1219             if (isset($attrs['assign']))
1220                 $assign = $attrs['assign'];
1221             else
1222                 $assign = null;
1223             $output = "<?php ob_start(); ?>";
1224             $this->_capture_stack[] = array($buffer, $assign);
1225         } else {
1226             list($buffer, $assign) = array_pop($this->_capture_stack);
1227             $output = "<?php \$this->_smarty_vars['capture'][$buffer] = ob_get_contents(); ";
1228             if (isset($assign)) {
1229                 $output .= " \$this->assign($assign, ob_get_contents());";
1230             }
1231             $output .= "ob_end_clean(); ?>";
1232         }
1234         return $output;
1235     }
1237     /**
1238      * Compile {if ...} tag
1239      *
1240      * @param string $tag_args
1241      * @param boolean $elseif if true, uses elseif instead of if
1242      * @return string
1243      */
1244     function _compile_if_tag($tag_args, $elseif = false)
1245     {
1247         /* Tokenize args for 'if' tag. */
1248         preg_match_all('~(?>
1249                 ' . $this->_obj_call_regexp . '(?:' . $this->_mod_regexp . '*)? | # valid object call
1250                 ' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)?    | # var or quoted string
1251                 \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@    | # valid non-word token
1252                 \b\w+\b                                                        | # valid word token
1253                 \S+                                                           # anything else
1254                 )~x', $tag_args, $match);
1256         $tokens = $match[0];
1258         if(empty($tokens)) {
1259             $_error_msg = $elseif ? "'elseif'" : "'if'";
1260             $_error_msg .= ' statement requires arguments'; 
1261             $this->_syntax_error($_error_msg, E_USER_ERROR, __FILE__, __LINE__);
1262         }
1263             
1264                 
1265         // make sure we have balanced parenthesis
1266         $token_count = array_count_values($tokens);
1267         if(isset($token_count['(']) && $token_count['('] != $token_count[')']) {
1268             $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR, __FILE__, __LINE__);
1269         }
1271         $is_arg_stack = array();
1273         for ($i = 0; $i < count($tokens); $i++) {
1275             $token = &$tokens[$i];
1277             switch (strtolower($token)) {
1278                 case '!':
1279                 case '%':
1280                 case '!==':
1281                 case '==':
1282                 case '===':
1283                 case '>':
1284                 case '<':
1285                 case '!=':
1286                 case '<>':
1287                 case '<<':
1288                 case '>>':
1289                 case '<=':
1290                 case '>=':
1291                 case '&&':
1292                 case '||':
1293                 case '|':
1294                 case '^':
1295                 case '&':
1296                 case '~':
1297                 case ')':
1298                 case ',':
1299                 case '+':
1300                 case '-':
1301                 case '*':
1302                 case '/':
1303                 case '@':
1304                     break;
1306                 case 'eq':
1307                     $token = '==';
1308                     break;
1310                 case 'ne':
1311                 case 'neq':
1312                     $token = '!=';
1313                     break;
1315                 case 'lt':
1316                     $token = '<';
1317                     break;
1319                 case 'le':
1320                 case 'lte':
1321                     $token = '<=';
1322                     break;
1324                 case 'gt':
1325                     $token = '>';
1326                     break;
1328                 case 'ge':
1329                 case 'gte':
1330                     $token = '>=';
1331                     break;
1333                 case 'and':
1334                     $token = '&&';
1335                     break;
1337                 case 'or':
1338                     $token = '||';
1339                     break;
1341                 case 'not':
1342                     $token = '!';
1343                     break;
1345                 case 'mod':
1346                     $token = '%';
1347                     break;
1349                 case '(':
1350                     array_push($is_arg_stack, $i);
1351                     break;
1353                 case 'is':
1354                     /* If last token was a ')', we operate on the parenthesized
1355                        expression. The start of the expression is on the stack.
1356                        Otherwise, we operate on the last encountered token. */
1357                     if ($tokens[$i-1] == ')')
1358                         $is_arg_start = array_pop($is_arg_stack);
1359                     else
1360                         $is_arg_start = $i-1;
1361                     /* Construct the argument for 'is' expression, so it knows
1362                        what to operate on. */
1363                     $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
1365                     /* Pass all tokens from next one until the end to the
1366                        'is' expression parsing function. The function will
1367                        return modified tokens, where the first one is the result
1368                        of the 'is' expression and the rest are the tokens it
1369                        didn't touch. */
1370                     $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));
1372                     /* Replace the old tokens with the new ones. */
1373                     array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);
1375                     /* Adjust argument start so that it won't change from the
1376                        current position for the next iteration. */
1377                     $i = $is_arg_start;
1378                     break;
1380                 default:
1381                     if(preg_match('~^' . $this->_func_regexp . '$~', $token) ) {
1382                             // function call
1383                             if($this->security &&
1384                                !in_array($token, $this->security_settings['IF_FUNCS'])) {
1385                                 $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);
1386                             }
1387                     } elseif(preg_match('~^' . $this->_var_regexp . '$~', $token) && (strpos('+-*/^%&|', substr($token, -1)) === false) && isset($tokens[$i+1]) && $tokens[$i+1] == '(') {
1388                         // variable function call
1389                         $this->_syntax_error("variable function call '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);                      
1390                     } elseif(preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$~', $token)) {
1391                         // object or variable
1392                         $token = $this->_parse_var_props($token);
1393                     } elseif(is_numeric($token)) {
1394                         // number, skip it
1395                     } else {
1396                         $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__);
1397                     }
1398                     break;
1399             }
1400         }
1402         if ($elseif)
1403             return '<?php elseif ('.implode(' ', $tokens).'): ?>';
1404         else
1405             return '<?php if ('.implode(' ', $tokens).'): ?>';
1406     }
1409     function _compile_arg_list($type, $name, $attrs, &$cache_code) {
1410         $arg_list = array();
1412         if (isset($type) && isset($name)
1413             && isset($this->_plugins[$type])
1414             && isset($this->_plugins[$type][$name])
1415             && empty($this->_plugins[$type][$name][4])
1416             && is_array($this->_plugins[$type][$name][5])
1417             ) {
1418             /* we have a list of parameters that should be cached */
1419             $_cache_attrs = $this->_plugins[$type][$name][5];
1420             $_count = $this->_cache_attrs_count++;
1421             $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');";
1423         } else {
1424             /* no parameters are cached */
1425             $_cache_attrs = null;
1426         }
1428         foreach ($attrs as $arg_name => $arg_value) {
1429             if (is_bool($arg_value))
1430                 $arg_value = $arg_value ? 'true' : 'false';
1431             if (is_null($arg_value))
1432                 $arg_value = 'null';
1433             if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) {
1434                 $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)";
1435             } else {
1436                 $arg_list[] = "'$arg_name' => $arg_value";
1437             }
1438         }
1439         return $arg_list;
1440     }
1442     /**
1443      * Parse is expression
1444      *
1445      * @param string $is_arg
1446      * @param array $tokens
1447      * @return array
1448      */
1449     function _parse_is_expr($is_arg, $tokens)
1450     {
1451         $expr_end = 0;
1452         $negate_expr = false;
1454         if (($first_token = array_shift($tokens)) == 'not') {
1455             $negate_expr = true;
1456             $expr_type = array_shift($tokens);
1457         } else
1458             $expr_type = $first_token;
1460         switch ($expr_type) {
1461             case 'even':
1462                 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1463                     $expr_end++;
1464                     $expr_arg = $tokens[$expr_end++];
1465                     $expr = "!(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1466                 } else
1467                     $expr = "!(1 & $is_arg)";
1468                 break;
1470             case 'odd':
1471                 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1472                     $expr_end++;
1473                     $expr_arg = $tokens[$expr_end++];
1474                     $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1475                 } else
1476                     $expr = "(1 & $is_arg)";
1477                 break;
1479             case 'div':
1480                 if (@$tokens[$expr_end] == 'by') {
1481                     $expr_end++;
1482                     $expr_arg = $tokens[$expr_end++];
1483                     $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")";
1484                 } else {
1485                     $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__);
1486                 }
1487                 break;
1489             default:
1490                 $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__);
1491                 break;
1492         }
1494         if ($negate_expr) {
1495             $expr = "!($expr)";
1496         }
1498         array_splice($tokens, 0, $expr_end, $expr);
1500         return $tokens;
1501     }
1504     /**
1505      * Parse attribute string
1506      *
1507      * @param string $tag_args
1508      * @return array
1509      */
1510     function _parse_attrs($tag_args)
1511     {
1513         /* Tokenize tag attributes. */
1514         preg_match_all('~(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+)
1515                          )+ |
1516                          [=]
1517                         ~x', $tag_args, $match);
1518         $tokens       = $match[0];
1520         $attrs = array();
1521         /* Parse state:
1522             0 - expecting attribute name
1523             1 - expecting '='
1524             2 - expecting attribute value (not '=') */
1525         $state = 0;
1527         foreach ($tokens as $token) {
1528             switch ($state) {
1529                 case 0:
1530                     /* If the token is a valid identifier, we set attribute name
1531                        and go to state 1. */
1532                     if (preg_match('~^\w+$~', $token)) {
1533                         $attr_name = $token;
1534                         $state = 1;
1535                     } else
1536                         $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__);
1537                     break;
1539                 case 1:
1540                     /* If the token is '=', then we go to state 2. */
1541                     if ($token == '=') {
1542                         $state = 2;
1543                     } else
1544                         $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1545                     break;
1547                 case 2:
1548                     /* If token is not '=', we set the attribute value and go to
1549                        state 0. */
1550                     if ($token != '=') {
1551                         /* We booleanize the token if it's a non-quoted possible
1552                            boolean value. */
1553                         if (preg_match('~^(on|yes|true)$~', $token)) {
1554                             $token = 'true';
1555                         } else if (preg_match('~^(off|no|false)$~', $token)) {
1556                             $token = 'false';
1557                         } else if ($token == 'null') {
1558                             $token = 'null';
1559                         } else if (preg_match('~^' . $this->_num_const_regexp . '|0[xX][0-9a-fA-F]+$~', $token)) {
1560                             /* treat integer literally */
1561                         } else if (!preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$~', $token)) {
1562                             /* treat as a string, double-quote it escaping quotes */
1563                             $token = '"'.addslashes($token).'"';
1564                         }
1566                         $attrs[$attr_name] = $token;
1567                         $state = 0;
1568                     } else
1569                         $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__);
1570                     break;
1571             }
1572             $last_token = $token;
1573         }
1575         if($state != 0) {
1576             if($state == 1) {
1577                 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1578             } else {
1579                 $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__);
1580             }
1581         }
1583         $this->_parse_vars_props($attrs);
1585         return $attrs;
1586     }
1588     /**
1589      * compile multiple variables and section properties tokens into
1590      * PHP code
1591      *
1592      * @param array $tokens
1593      */
1594     function _parse_vars_props(&$tokens)
1595     {
1596         foreach($tokens as $key => $val) {
1597             $tokens[$key] = $this->_parse_var_props($val);
1598         }
1599     }
1601     /**
1602      * compile single variable and section properties token into
1603      * PHP code
1604      *
1605      * @param string $val
1606      * @param string $tag_attrs
1607      * @return string
1608      */
1609     function _parse_var_props($val)
1610     {
1611         $val = trim($val);
1613         if(preg_match('~^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$~', $val, $match)) {
1614             // $ variable or object
1615             $return = $this->_parse_var($match[1]);
1616             $modifiers = $match[2];
1617             if (!empty($this->default_modifiers) && !preg_match('~(^|\|)smarty:nodefaults($|\|)~',$modifiers)) {
1618                 $_default_mod_string = implode('|',(array)$this->default_modifiers);
1619                 $modifiers = empty($modifiers) ? $_default_mod_string : $_default_mod_string . '|' . $modifiers;
1620             }
1621             $this->_parse_modifiers($return, $modifiers);
1622             return $return;
1623         } elseif (preg_match('~^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1624                 // double quoted text
1625                 preg_match('~^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1626                 $return = $this->_expand_quoted_text($match[1]);
1627                 if($match[2] != '') {
1628                     $this->_parse_modifiers($return, $match[2]);
1629                 }
1630                 return $return;
1631             }
1632         elseif(preg_match('~^' . $this->_num_const_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1633                 // numerical constant
1634                 preg_match('~^(' . $this->_num_const_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1635                 if($match[2] != '') {
1636                     $this->_parse_modifiers($match[1], $match[2]);
1637                     return $match[1];
1638                 }
1639             }
1640         elseif(preg_match('~^' . $this->_si_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1641                 // single quoted text
1642                 preg_match('~^(' . $this->_si_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1643                 if($match[2] != '') {
1644                     $this->_parse_modifiers($match[1], $match[2]);
1645                     return $match[1];
1646                 }
1647             }
1648         elseif(preg_match('~^' . $this->_cvar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1649                 // config var
1650                 return $this->_parse_conf_var($val);
1651             }
1652         elseif(preg_match('~^' . $this->_svar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1653                 // section var
1654                 return $this->_parse_section_prop($val);
1655             }
1656         elseif(!in_array($val, $this->_permitted_tokens) && !is_numeric($val)) {
1657             // literal string
1658             return $this->_expand_quoted_text('"' . strtr($val, array('\\' => '\\\\', '"' => '\\"')) .'"');
1659         }
1660         return $val;
1661     }
1663     /**
1664      * expand quoted text with embedded variables
1665      *
1666      * @param string $var_expr
1667      * @return string
1668      */
1669     function _expand_quoted_text($var_expr)
1670     {
1671         // if contains unescaped $, expand it
1672         if(preg_match_all('~(?:\`(?<!\\\\)\$' . $this->_dvar_guts_regexp . '(?:' . $this->_obj_ext_regexp . ')*\`)|(?:(?<!\\\\)\$\w+(\[[a-zA-Z0-9]+\])*)~', $var_expr, $_match)) {
1673             $_match = $_match[0];
1674             rsort($_match);
1675             reset($_match);
1676             foreach($_match as $_var) {
1677                 $var_expr = str_replace ($_var, '".(' . $this->_parse_var(str_replace('`','',$_var)) . ')."', $var_expr);
1678             }
1679             $_return = preg_replace('~\.""|(?<!\\\\)""\.~', '', $var_expr);
1680         } else {
1681             $_return = $var_expr;
1682         }
1683         // replace double quoted literal string with single quotes
1684         $_return = preg_replace('~^"([\s\w]+)"$~',"'\\1'",$_return);
1685         return $_return;
1686     }
1688     /**
1689      * parse variable expression into PHP code
1690      *
1691      * @param string $var_expr
1692      * @param string $output
1693      * @return string
1694      */
1695     function _parse_var($var_expr)
1696     {
1697         $_has_math = false;
1698         $_math_vars = preg_split('~('.$this->_dvar_math_regexp.'|'.$this->_qstr_regexp.')~', $var_expr, -1, PREG_SPLIT_DELIM_CAPTURE);
1700         if(count($_math_vars) > 1) {
1701             $_first_var = "";
1702             $_complete_var = "";
1703             $_output = "";
1704             // simple check if there is any math, to stop recursion (due to modifiers with "xx % yy" as parameter)
1705             foreach($_math_vars as $_k => $_math_var) {
1706                 $_math_var = $_math_vars[$_k];
1708                 if(!empty($_math_var) || is_numeric($_math_var)) {
1709                     // hit a math operator, so process the stuff which came before it
1710                     if(preg_match('~^' . $this->_dvar_math_regexp . '$~', $_math_var)) {
1711                         $_has_math = true;
1712                         if(!empty($_complete_var) || is_numeric($_complete_var)) {
1713                             $_output .= $this->_parse_var($_complete_var);
1714                         }
1716                         // just output the math operator to php
1717                         $_output .= $_math_var;
1719                         if(empty($_first_var))
1720                             $_first_var = $_complete_var;
1722                         $_complete_var = "";
1723                     } else {
1724                         $_complete_var .= $_math_var;
1725                     }
1726                 }
1727             }
1728             if($_has_math) {
1729                 if(!empty($_complete_var) || is_numeric($_complete_var))
1730                     $_output .= $this->_parse_var($_complete_var);
1732                 // get the modifiers working (only the last var from math + modifier is left)
1733                 $var_expr = $_complete_var;
1734             }
1735         }
1737         // prevent cutting of first digit in the number (we _definitly_ got a number if the first char is a digit)
1738         if(is_numeric(substr($var_expr, 0, 1)))
1739             $_var_ref = $var_expr;
1740         else
1741             $_var_ref = substr($var_expr, 1);
1742         
1743         if(!$_has_math) {
1744             
1745             // get [foo] and .foo and ->foo and (...) pieces
1746             preg_match_all('~(?:^\w+)|' . $this->_obj_params_regexp . '|(?:' . $this->_var_bracket_regexp . ')|->\$?\w+|\.\$?\w+|\S+~', $_var_ref, $match);
1747                         
1748             $_indexes = $match[0];
1749             $_var_name = array_shift($_indexes);
1751             /* Handle $smarty.* variable references as a special case. */
1752             if ($_var_name == 'smarty') {
1753                 /*
1754                  * If the reference could be compiled, use the compiled output;
1755                  * otherwise, fall back on the $smarty variable generated at
1756                  * run-time.
1757                  */
1758                 if (($smarty_ref = $this->_compile_smarty_ref($_indexes)) !== null) {
1759                     $_output = $smarty_ref;
1760                 } else {
1761                     $_var_name = substr(array_shift($_indexes), 1);
1762                     $_output = "\$this->_smarty_vars['$_var_name']";
1763                 }
1764             } elseif(is_numeric($_var_name) && is_numeric(substr($var_expr, 0, 1))) {
1765                 // because . is the operator for accessing arrays thru inidizes we need to put it together again for floating point numbers
1766                 if(count($_indexes) > 0)
1767                 {
1768                     $_var_name .= implode("", $_indexes);
1769                     $_indexes = array();
1770                 }
1771                 $_output = $_var_name;
1772             } else {
1773                 $_output = "\$this->_tpl_vars['$_var_name']";
1774             }
1776             foreach ($_indexes as $_index) {
1777                 if (substr($_index, 0, 1) == '[') {
1778                     $_index = substr($_index, 1, -1);
1779                     if (is_numeric($_index)) {
1780                         $_output .= "[$_index]";
1781                     } elseif (substr($_index, 0, 1) == '$') {
1782                         if (strpos($_index, '.') !== false) {
1783                             $_output .= '[' . $this->_parse_var($_index) . ']';
1784                         } else {
1785                             $_output .= "[\$this->_tpl_vars['" . substr($_index, 1) . "']]";
1786                         }
1787                     } else {
1788                         $_var_parts = explode('.', $_index);
1789                         $_var_section = $_var_parts[0];
1790                         $_var_section_prop = isset($_var_parts[1]) ? $_var_parts[1] : 'index';
1791                         $_output .= "[\$this->_sections['$_var_section']['$_var_section_prop']]";
1792                     }
1793                 } else if (substr($_index, 0, 1) == '.') {
1794                     if (substr($_index, 1, 1) == '$')
1795                         $_output .= "[\$this->_tpl_vars['" . substr($_index, 2) . "']]";
1796                     else
1797                         $_output .= "['" . substr($_index, 1) . "']";
1798                 } else if (substr($_index,0,2) == '->') {
1799                     if(substr($_index,2,2) == '__') {
1800                         $this->_syntax_error('call to internal object members is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1801                     } elseif($this->security && substr($_index, 2, 1) == '_') {
1802                         $this->_syntax_error('(secure) call to private object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1803                     } elseif (substr($_index, 2, 1) == '$') {
1804                         if ($this->security) {
1805                             $this->_syntax_error('(secure) call to dynamic object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1806                         } else {
1807                             $_output .= '->{(($_var=$this->_tpl_vars[\''.substr($_index,3).'\']) && substr($_var,0,2)!=\'__\') ? $_var : $this->trigger_error("cannot access property \\"$_var\\"")}';
1808                         }
1809                     } else {
1810                         $_output .= $_index;
1811                     }
1812                 } elseif (substr($_index, 0, 1) == '(') {
1813                     $_index = $this->_parse_parenth_args($_index);
1814                     $_output .= $_index;
1815                 } else {
1816                     $_output .= $_index;
1817                 }
1818             }
1819         }
1821         return $_output;
1822     }
1824     /**
1825      * parse arguments in function call parenthesis
1826      *
1827      * @param string $parenth_args
1828      * @return string
1829      */
1830     function _parse_parenth_args($parenth_args)
1831     {
1832         preg_match_all('~' . $this->_param_regexp . '~',$parenth_args, $match);
1833         $orig_vals = $match = $match[0];
1834         $this->_parse_vars_props($match);
1835         $replace = array();
1836         for ($i = 0, $count = count($match); $i < $count; $i++) {
1837             $replace[$orig_vals[$i]] = $match[$i];
1838         }
1839         return strtr($parenth_args, $replace);
1840     }
1842     /**
1843      * parse configuration variable expression into PHP code
1844      *
1845      * @param string $conf_var_expr
1846      */
1847     function _parse_conf_var($conf_var_expr)
1848     {
1849         $parts = explode('|', $conf_var_expr, 2);
1850         $var_ref = $parts[0];
1851         $modifiers = isset($parts[1]) ? $parts[1] : '';
1853         $var_name = substr($var_ref, 1, -1);
1855         $output = "\$this->_config[0]['vars']['$var_name']";
1857         $this->_parse_modifiers($output, $modifiers);
1859         return $output;
1860     }
1862     /**
1863      * parse section property expression into PHP code
1864      *
1865      * @param string $section_prop_expr
1866      * @return string
1867      */
1868     function _parse_section_prop($section_prop_expr)
1869     {
1870         $parts = explode('|', $section_prop_expr, 2);
1871         $var_ref = $parts[0];
1872         $modifiers = isset($parts[1]) ? $parts[1] : '';
1874         preg_match('!%(\w+)\.(\w+)%!', $var_ref, $match);
1875         $section_name = $match[1];
1876         $prop_name = $match[2];
1878         $output = "\$this->_sections['$section_name']['$prop_name']";
1880         $this->_parse_modifiers($output, $modifiers);
1882         return $output;
1883     }
1886     /**
1887      * parse modifier chain into PHP code
1888      *
1889      * sets $output to parsed modified chain
1890      * @param string $output
1891      * @param string $modifier_string
1892      */
1893     function _parse_modifiers(&$output, $modifier_string)
1894     {
1895         preg_match_all('~\|(@?\w+)((?>:(?:'. $this->_qstr_regexp . '|[^|]+))*)~', '|' . $modifier_string, $_match);
1896         list(, $_modifiers, $modifier_arg_strings) = $_match;
1898         for ($_i = 0, $_for_max = count($_modifiers); $_i < $_for_max; $_i++) {
1899             $_modifier_name = $_modifiers[$_i];
1901             if($_modifier_name == 'smarty') {
1902                 // skip smarty modifier
1903                 continue;
1904             }
1906             preg_match_all('~:(' . $this->_qstr_regexp . '|[^:]+)~', $modifier_arg_strings[$_i], $_match);
1907             $_modifier_args = $_match[1];
1909             if (substr($_modifier_name, 0, 1) == '@') {
1910                 $_map_array = false;
1911                 $_modifier_name = substr($_modifier_name, 1);
1912             } else {
1913                 $_map_array = true;
1914             }
1916             if (empty($this->_plugins['modifier'][$_modifier_name])
1917                 && !$this->_get_plugin_filepath('modifier', $_modifier_name)
1918                 && function_exists($_modifier_name)) {
1919                 if ($this->security && !in_array($_modifier_name, $this->security_settings['MODIFIER_FUNCS'])) {
1920                     $this->_trigger_fatal_error("[plugin] (secure mode) modifier '$_modifier_name' is not allowed" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
1921                 } else {
1922                     $this->_plugins['modifier'][$_modifier_name] = array($_modifier_name,  null, null, false);
1923                 }
1924             }
1925             $this->_add_plugin('modifier', $_modifier_name);
1927             $this->_parse_vars_props($_modifier_args);
1929             if($_modifier_name == 'default') {
1930                 // supress notifications of default modifier vars and args
1931                 if(substr($output, 0, 1) == '$') {
1932                     $output = '@' . $output;
1933                 }
1934                 if(isset($_modifier_args[0]) && substr($_modifier_args[0], 0, 1) == '$') {
1935                     $_modifier_args[0] = '@' . $_modifier_args[0];
1936                 }
1937             }
1938             if (count($_modifier_args) > 0)
1939                 $_modifier_args = ', '.implode(', ', $_modifier_args);
1940             else
1941                 $_modifier_args = '';
1943             if ($_map_array) {
1944                 $output = "((is_array(\$_tmp=$output)) ? \$this->_run_mod_handler('$_modifier_name', true, \$_tmp$_modifier_args) : " . $this->_compile_plugin_call('modifier', $_modifier_name) . "(\$_tmp$_modifier_args))";
1946             } else {
1948                 $output = $this->_compile_plugin_call('modifier', $_modifier_name)."($output$_modifier_args)";
1950             }
1951         }
1952     }
1955     /**
1956      * add plugin
1957      *
1958      * @param string $type
1959      * @param string $name
1960      * @param boolean? $delayed_loading
1961      */
1962     function _add_plugin($type, $name, $delayed_loading = null)
1963     {
1964         if (!isset($this->_plugin_info[$type])) {
1965             $this->_plugin_info[$type] = array();
1966         }
1967         if (!isset($this->_plugin_info[$type][$name])) {
1968             $this->_plugin_info[$type][$name] = array($this->_current_file,
1969                                                       $this->_current_line_no,
1970                                                       $delayed_loading);
1971         }
1972     }
1975     /**
1976      * Compiles references of type $smarty.foo
1977      *
1978      * @param string $indexes
1979      * @return string
1980      */
1981     function _compile_smarty_ref(&$indexes)
1982     {
1983         /* Extract the reference name. */
1984         $_ref = substr($indexes[0], 1);
1985         foreach($indexes as $_index_no=>$_index) {
1986             if (substr($_index, 0, 1) != '.' && $_index_no<2 || !preg_match('~^(\.|\[|->)~', $_index)) {
1987                 $this->_syntax_error('$smarty' . implode('', array_slice($indexes, 0, 2)) . ' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
1988             }
1989         }
1991         switch ($_ref) {
1992             case 'now':
1993                 $compiled_ref = 'time()';
1994                 $_max_index = 1;
1995                 break;
1997             case 'foreach':
1998                 array_shift($indexes);
1999                 $_var = $this->_parse_var_props(substr($indexes[0], 1));
2000                 $_propname = substr($indexes[1], 1);
2001                 $_max_index = 1;
2002                 switch ($_propname) {
2003                     case 'index':
2004                         array_shift($indexes);
2005                         $compiled_ref = "(\$this->_foreach[$_var]['iteration']-1)";
2006                         break;
2007                         
2008                     case 'first':
2009                         array_shift($indexes);
2010                         $compiled_ref = "(\$this->_foreach[$_var]['iteration'] <= 1)";
2011                         break;
2013                     case 'last':
2014                         array_shift($indexes);
2015                         $compiled_ref = "(\$this->_foreach[$_var]['iteration'] == \$this->_foreach[$_var]['total'])";
2016                         break;
2017                         
2018                     case 'show':
2019                         array_shift($indexes);
2020                         $compiled_ref = "(\$this->_foreach[$_var]['total'] > 0)";
2021                         break;
2022                         
2023                     default:
2024                         unset($_max_index);
2025                         $compiled_ref = "\$this->_foreach[$_var]";
2026                 }
2027                 break;
2029             case 'section':
2030                 array_shift($indexes);
2031                 $_var = $this->_parse_var_props(substr($indexes[0], 1));
2032                 $compiled_ref = "\$this->_sections[$_var]";
2033                 break;
2035             case 'get':
2036                 $compiled_ref = ($this->request_use_auto_globals) ? '$_GET' : "\$GLOBALS['HTTP_GET_VARS']";
2037                 break;
2039             case 'post':
2040                 $compiled_ref = ($this->request_use_auto_globals) ? '$_POST' : "\$GLOBALS['HTTP_POST_VARS']";
2041                 break;
2043             case 'cookies':
2044                 $compiled_ref = ($this->request_use_auto_globals) ? '$_COOKIE' : "\$GLOBALS['HTTP_COOKIE_VARS']";
2045                 break;
2047             case 'env':
2048                 $compiled_ref = ($this->request_use_auto_globals) ? '$_ENV' : "\$GLOBALS['HTTP_ENV_VARS']";
2049                 break;
2051             case 'server':
2052                 $compiled_ref = ($this->request_use_auto_globals) ? '$_SERVER' : "\$GLOBALS['HTTP_SERVER_VARS']";
2053                 break;
2055             case 'session':
2056                 $compiled_ref = ($this->request_use_auto_globals) ? '$_SESSION' : "\$GLOBALS['HTTP_SESSION_VARS']";
2057                 break;
2059             /*
2060              * These cases are handled either at run-time or elsewhere in the
2061              * compiler.
2062              */
2063             case 'request':
2064                 if ($this->request_use_auto_globals) {
2065                     $compiled_ref = '$_REQUEST';
2066                     break;
2067                 } else {
2068                     $this->_init_smarty_vars = true;
2069                 }
2070                 return null;
2072             case 'capture':
2073                 return null;
2075             case 'template':
2076                 $compiled_ref = "'$this->_current_file'";
2077                 $_max_index = 1;
2078                 break;
2080             case 'version':
2081                 $compiled_ref = "'$this->_version'";
2082                 $_max_index = 1;
2083                 break;
2085             case 'const':
2086                 if ($this->security && !$this->security_settings['ALLOW_CONSTANTS']) {
2087                     $this->_syntax_error("(secure mode) constants not permitted",
2088                                          E_USER_WARNING, __FILE__, __LINE__);
2089                     return;
2090                 }
2091                 array_shift($indexes);
2092                 if (preg_match('!^\.\w+$!', $indexes[0])) {
2093                     $compiled_ref = '@' . substr($indexes[0], 1);
2094                 } else {
2095                     $_val = $this->_parse_var_props(substr($indexes[0], 1));
2096                     $compiled_ref = '@constant(' . $_val . ')';
2097                 }
2098                 $_max_index = 1;
2099                 break;
2101             case 'config':
2102                 $compiled_ref = "\$this->_config[0]['vars']";
2103                 $_max_index = 3;
2104                 break;
2106             case 'ldelim':
2107                 $compiled_ref = "'$this->left_delimiter'";
2108                 break;
2110             case 'rdelim':
2111                 $compiled_ref = "'$this->right_delimiter'";
2112                 break;
2113                 
2114             default:
2115                 $this->_syntax_error('$smarty.' . $_ref . ' is an unknown reference', E_USER_ERROR, __FILE__, __LINE__);
2116                 break;
2117         }
2119         if (isset($_max_index) && count($indexes) > $_max_index) {
2120             $this->_syntax_error('$smarty' . implode('', $indexes) .' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
2121         }
2123         array_shift($indexes);
2124         return $compiled_ref;
2125     }
2127     /**
2128      * compiles call to plugin of type $type with name $name
2129      * returns a string containing the function-name or method call
2130      * without the paramter-list that would have follow to make the
2131      * call valid php-syntax
2132      *
2133      * @param string $type
2134      * @param string $name
2135      * @return string
2136      */
2137     function _compile_plugin_call($type, $name) {
2138         if (isset($this->_plugins[$type][$name])) {
2139             /* plugin loaded */
2140             if (is_array($this->_plugins[$type][$name][0])) {
2141                 return ((is_object($this->_plugins[$type][$name][0][0])) ?
2142                         "\$this->_plugins['$type']['$name'][0][0]->"    /* method callback */
2143                         : (string)($this->_plugins[$type][$name][0][0]).'::'    /* class callback */
2144                        ). $this->_plugins[$type][$name][0][1];
2146             } else {
2147                 /* function callback */
2148                 return $this->_plugins[$type][$name][0];
2150             }
2151         } else {
2152             /* plugin not loaded -> auto-loadable-plugin */
2153             return 'smarty_'.$type.'_'.$name;
2155         }
2156     }
2158     /**
2159      * load pre- and post-filters
2160      */
2161     function _load_filters()
2162     {
2163         if (count($this->_plugins['prefilter']) > 0) {
2164             foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
2165                 if ($prefilter === false) {
2166                     unset($this->_plugins['prefilter'][$filter_name]);
2167                     $_params = array('plugins' => array(array('prefilter', $filter_name, null, null, false)));
2168                     require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
2169                     smarty_core_load_plugins($_params, $this);
2170                 }
2171             }
2172         }
2173         if (count($this->_plugins['postfilter']) > 0) {
2174             foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
2175                 if ($postfilter === false) {
2176                     unset($this->_plugins['postfilter'][$filter_name]);
2177                     $_params = array('plugins' => array(array('postfilter', $filter_name, null, null, false)));
2178                     require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
2179                     smarty_core_load_plugins($_params, $this);
2180                 }
2181             }
2182         }
2183     }
2186     /**
2187      * Quote subpattern references
2188      *
2189      * @param string $string
2190      * @return string
2191      */
2192     function _quote_replace($string)
2193     {
2194         return strtr($string, array('\\' => '\\\\', '$' => '\\$'));
2195     }
2197     /**
2198      * display Smarty syntax error
2199      *
2200      * @param string $error_msg
2201      * @param integer $error_type
2202      * @param string $file
2203      * @param integer $line
2204      */
2205     function _syntax_error($error_msg, $error_type = E_USER_ERROR, $file=null, $line=null)
2206     {
2207         $this->_trigger_fatal_error("syntax error: $error_msg", $this->_current_file, $this->_current_line_no, $file, $line, $error_type);
2208     }
2211     /**
2212      * check if the compilation changes from cacheable to
2213      * non-cacheable state with the beginning of the current
2214      * plugin. return php-code to reflect the transition.
2215      * @return string
2216      */
2217     function _push_cacheable_state($type, $name) {
2218         $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2219         if ($_cacheable
2220             || 0<$this->_cacheable_state++) return '';
2221         if (!isset($this->_cache_serial)) $this->_cache_serial = md5(uniqid('Smarty'));
2222         $_ret = 'if ($this->caching && !$this->_cache_including) { echo \'{nocache:'
2223             . $this->_cache_serial . '#' . $this->_nocache_count
2224             . '}\'; };';
2225         return $_ret;
2226     }
2229     /**
2230      * check if the compilation changes from non-cacheable to
2231      * cacheable state with the end of the current plugin return
2232      * php-code to reflect the transition.
2233      * @return string
2234      */
2235     function _pop_cacheable_state($type, $name) {
2236         $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2237         if ($_cacheable
2238             || --$this->_cacheable_state>0) return '';
2239         return 'if ($this->caching && !$this->_cache_including) { echo \'{/nocache:'
2240             . $this->_cache_serial . '#' . ($this->_nocache_count++)
2241             . '}\'; };';
2242     }
2245     /**
2246      * push opening tag-name, file-name and line-number on the tag-stack
2247      * @param string the opening tag's name
2248      */
2249     function _push_tag($open_tag)
2250     {
2251         array_push($this->_tag_stack, array($open_tag, $this->_current_line_no));
2252     }
2254     /**
2255      * pop closing tag-name
2256      * raise an error if this stack-top doesn't match with the closing tag
2257      * @param string the closing tag's name
2258      * @return string the opening tag's name
2259      */
2260     function _pop_tag($close_tag)
2261     {
2262         $message = '';
2263         if (count($this->_tag_stack)>0) {
2264             list($_open_tag, $_line_no) = array_pop($this->_tag_stack);
2265             if ($close_tag == $_open_tag) {
2266                 return $_open_tag;
2267             }
2268             if ($close_tag == 'if' && ($_open_tag == 'else' || $_open_tag == 'elseif' )) {
2269                 return $this->_pop_tag($close_tag);
2270             }
2271             if ($close_tag == 'section' && $_open_tag == 'sectionelse') {
2272                 $this->_pop_tag($close_tag);
2273                 return $_open_tag;
2274             }
2275             if ($close_tag == 'foreach' && $_open_tag == 'foreachelse') {
2276                 $this->_pop_tag($close_tag);
2277                 return $_open_tag;
2278             }
2279             if ($_open_tag == 'else' || $_open_tag == 'elseif') {
2280                 $_open_tag = 'if';
2281             } elseif ($_open_tag == 'sectionelse') {
2282                 $_open_tag = 'section';
2283             } elseif ($_open_tag == 'foreachelse') {
2284                 $_open_tag = 'foreach';
2285             }
2286             $message = " expected {/$_open_tag} (opened line $_line_no).";
2287         }
2288         $this->_syntax_error("mismatched tag {/$close_tag}.$message",
2289                              E_USER_ERROR, __FILE__, __LINE__);
2290     }
2294 /**
2295  * compare to values by their string length
2296  *
2297  * @access private
2298  * @param string $a
2299  * @param string $b
2300  * @return 0|-1|1
2301  */
2302 function _smarty_sort_length($a, $b)
2304     if($a == $b)
2305         return 0;
2307     if(strlen($a) == strlen($b))
2308         return ($a > $b) ? -1 : 1;
2310     return (strlen($a) > strlen($b)) ? -1 : 1;
2314 /* vim: set et: */
2316 ?>