Code

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