root/tags/1.3.3/A2BCustomer_UI/lib/Smarty/Smarty_Compiler.class.php

Revision 720, 89.3 kB (checked in by areski, 2 years ago)

NEW TAG : Version 1.3.3

Line 
1 <?php
2
3 /**
4  * Project:     Smarty: the PHP compiling template engine
5  * File:        Smarty_Compiler.class.php
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * @link http://smarty.php.net/
22  * @author Monte Ohrt <monte at ohrt dot com>
23  * @author Andrei Zmievski <andrei@php.net>
24  * @version 2.6.13
25  * @copyright 2001-2005 New Digital Group, Inc.
26  * @package Smarty
27  */
28
29 /* $Id: Smarty_Compiler.class.php,v 1.378 2006/01/29 18:11:22 messju Exp $ */
30
31 /**
32  * Template compiling class
33  * @package Smarty
34  */
35 class Smarty_Compiler extends Smarty {
36
37     // internal vars
38     /**#@+
39      * @access private
40      */
41     var $_folded_blocks         =   array();    // keeps folded template blocks
42     var $_current_file          =   null;       // the current template being compiled
43     var $_current_line_no       =   1;          // line number for error messages
44     var $_capture_stack         =   array();    // keeps track of nested capture buffers
45     var $_plugin_info           =   array();    // keeps track of plugins to load
46     var $_init_smarty_vars      =   false;
47     var $_permitted_tokens      =   array('true','false','yes','no','on','off','null');
48     var $_db_qstr_regexp        =   null;        // regexps are setup in the constructor
49     var $_si_qstr_regexp        =   null;
50     var $_qstr_regexp           =   null;
51     var $_func_regexp           =   null;
52     var $_reg_obj_regexp        =   null;
53     var $_var_bracket_regexp    =   null;
54     var $_num_const_regexp      =   null;
55     var $_dvar_guts_regexp      =   null;
56     var $_dvar_regexp           =   null;
57     var $_cvar_regexp           =   null;
58     var $_svar_regexp           =   null;
59     var $_avar_regexp           =   null;
60     var $_mod_regexp            =   null;
61     var $_var_regexp            =   null;
62     var $_parenth_param_regexp  =   null;
63     var $_func_call_regexp      =   null;
64     var $_obj_ext_regexp        =   null;
65     var $_obj_start_regexp      =   null;
66     var $_obj_params_regexp     =   null;
67     var $_obj_call_regexp       =   null;
68     var $_cacheable_state       =   0;
69     var $_cache_attrs_count     =   0;
70     var $_nocache_count         =   0;
71     var $_cache_serial          =   null;
72     var $_cache_include         =   null;
73
74     var $_strip_depth           =   0;
75     var $_additional_newline    =   "\n";
76
77     /**#@-*/
78     /**
79      * The class constructor.
80      */
81     function Smarty_Compiler()
82     {
83         // matches double quoted strings:
84         // "foobar"
85         // "foo\"bar"
86         $this->_db_qstr_regexp = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
87
88         // matches single quoted strings:
89         // 'foobar'
90         // 'foo\'bar'
91         $this->_si_qstr_regexp = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
92
93         // matches single or double quoted strings
94         $this->_qstr_regexp = '(?:' . $this->_db_qstr_regexp . '|' . $this->_si_qstr_regexp . ')';
95
96         // matches bracket portion of vars
97         // [0]
98         // [foo]
99         // [$bar]
100         $this->_var_bracket_regexp = '\[\$?[\w\.]+\]';
101
102         // matches numerical constants
103         // 30
104         // -12
105         // 13.22
106         $this->_num_const_regexp = '(?:\-?\d+(?:\.\d+)?)';
107
108         // matches $ vars (not objects):
109         // $foo
110         // $foo.bar
111         // $foo.bar.foobar
112         // $foo[0]
113         // $foo[$bar]
114         // $foo[5][blah]
115         // $foo[5].bar[$foobar][4]
116         $this->_dvar_math_regexp = '(?:[\+\*\/\%]|(?:-(?!>)))';
117         $this->_dvar_math_var_regexp = '[\$\w\.\+\-\*\/\%\d\>\[\]]';
118         $this->_dvar_guts_regexp = '\w+(?:' . $this->_var_bracket_regexp
119                 . ')*(?:\.\$?\w+(?:' . $this->_var_bracket_regexp . ')*)*(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?';
120         $this->_dvar_regexp = '\$' . $this->_dvar_guts_regexp;
121
122         // matches config vars:
123         // #foo#
124         // #foobar123_foo#
125         $this->_cvar_regexp = '\#\w+\#';
126
127         // matches section vars:
128         // %foo.bar%
129         $this->_svar_regexp = '\%\w+\.\w+\%';
130
131         // matches all valid variables (no quotes, no modifiers)
132         $this->_avar_regexp = '(?:' . $this->_dvar_regexp . '|'
133            . $this->_cvar_regexp . '|' . $this->_svar_regexp . ')';
134
135         // matches valid variable syntax:
136         // $foo
137         // $foo
138         // #foo#
139         // #foo#
140         // "text"
141         // "text"
142         $this->_var_regexp = '(?:' . $this->_avar_regexp . '|' . $this->_qstr_regexp . ')';
143
144         // matches valid object call (one level of object nesting allowed in parameters):
145         // $foo->bar
146         // $foo->bar()
147         // $foo->bar("text")
148         // $foo->bar($foo, $bar, "text")
149         // $foo->bar($foo, "foo")
150         // $foo->bar->foo()
151         // $foo->bar->foo->bar()
152         // $foo->bar($foo->bar)
153         // $foo->bar($foo->bar())
154         // $foo->bar($foo->bar($blah,$foo,44,"foo",$foo[0].bar))
155         $this->_obj_ext_regexp = '\->(?:\$?' . $this->_dvar_guts_regexp . ')';
156         $this->_obj_restricted_param_regexp = '(?:'
157                 . '(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')(?:' . $this->_obj_ext_regexp . '(?:\((?:(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')'
158                 . '(?:\s*,\s*(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . '))*)?\))?)*)';
159         $this->_obj_single_param_regexp = '(?:\w+|' . $this->_obj_restricted_param_regexp . '(?:\s*,\s*(?:(?:\w+|'
160                 . $this->_var_regexp . $this->_obj_restricted_param_regexp . ')))*)';
161         $this->_obj_params_regexp = '\((?:' . $this->_obj_single_param_regexp
162                 . '(?:\s*,\s*' . $this->_obj_single_param_regexp . ')*)?\)';
163         $this->_obj_start_regexp = '(?:' . $this->_dvar_regexp . '(?:' . $this->_obj_ext_regexp . ')+)';
164         $this->_obj_call_regexp = '(?:' . $this->_obj_start_regexp . '(?:' . $this->_obj_params_regexp . ')?(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?)';
165         
166         // matches valid modifier syntax:
167         // |foo
168         // |@foo
169         // |foo:"bar"
170         // |foo:$bar
171         // |foo:"bar":$foobar
172         // |foo|bar
173         // |foo:$foo->bar
174         $this->_mod_regexp = '(?:\|@?\w+(?::(?:\w+|' . $this->_num_const_regexp . '|'
175            . $this->_obj_call_regexp . '|' . $this->_avar_regexp . '|' . $this->_qstr_regexp .'))*)';
176
177         // matches valid function name:
178         // foo123
179         // _foo_bar
180         $this->_func_regexp = '[a-zA-Z_]\w*';
181
182         // matches valid registered object:
183         // foo->bar
184         $this->_reg_obj_regexp = '[a-zA-Z_]\w*->[a-zA-Z_]\w*';
185
186         // matches valid parameter values:
187         // true
188         // $foo
189         // $foo|bar
190         // #foo#
191         // #foo#|bar
192         // "text"
193         // "text"|bar
194         // $foo->bar
195         $this->_param_regexp = '(?:\s*(?:' . $this->_obj_call_regexp . '|'
196            . $this->_var_regexp . '|' . $this->_num_const_regexp  . '|\w+)(?>' . $this->_mod_regexp . '*)\s*)';
197
198         // matches valid parenthesised function parameters:
199         //
200         // "text"
201         //    $foo, $bar, "text"
202         // $foo|bar, "foo"|bar, $foo->bar($foo)|bar
203         $this->_parenth_param_regexp = '(?:\((?:\w+|'
204                 . $this->_param_regexp . '(?:\s*,\s*(?:(?:\w+|'
205                 . $this->_param_regexp . ')))*)?\))';
206
207         // matches valid function call:
208         // foo()
209         // foo_bar($foo)
210         // _foo_bar($foo,"bar")
211         // foo123($foo,$foo->bar(),"foo")
212         $this->_func_call_regexp = '(?:' . $this->_func_regexp . '\s*(?:'
213            . $this->_parenth_param_regexp . '))';
214     }
215
216     /**
217      * compile a resource
218      *
219      * sets $compiled_content to the compiled source
220      * @param string $resource_name
221      * @param string $source_content
222      * @param string $compiled_content
223      * @return true
224      */
225     function _compile_file($resource_name, $source_content, &$compiled_content)
226     {
227
228         if ($this->security) {
229             // do not allow php syntax to be executed unless specified
230             if ($this->php_handling == SMARTY_PHP_ALLOW &&
231                 !$this->security_settings['PHP_HANDLING']) {
232                 $this->php_handling = SMARTY_PHP_PASSTHRU;
233             }
234         }
235
236         $this->_load_filters();
237
238         $this->_current_file = $resource_name;
239         $this->_current_line_no = 1;
240         $ldq = preg_quote($this->left_delimiter, '~');
241         $rdq = preg_quote($this->right_delimiter, '~');
242
243         // run template source through prefilter functions
244         if (count($this->_plugins['prefilter']) > 0) {
245             foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
246                 if ($prefilter === false) continue;
247                 if ($prefilter[3] || is_callable($prefilter[0])) {
248                     $source_content = call_user_func_array($prefilter[0],
249                                                             array($source_content, &$this));
250                     $this->_plugins['prefilter'][$filter_name][3] = true;
251                 } else {
252                     $this->_trigger_fatal_error("[plugin] prefilter '$filter_name' is not implemented");
253                 }
254             }
255         }
256
257         /* fetch all special blocks */
258         $search = "~{$ldq}\*(.*?)\*{$rdq}|{$ldq}\s*literal\s*{$rdq}(.*?){$ldq}\s*/literal\s*{$rdq}|{$ldq}\s*php\s*{$rdq}(.*?){$ldq}\s*/php\s*{$rdq}~s";
259
260         preg_match_all($search, $source_content, $matchPREG_SET_ORDER);
261         $this->_folded_blocks = $match;
262         reset($this->_folded_blocks);
263
264         /* replace special blocks by "{php}" */
265         $source_content = preg_replace($search.'e', "'"
266                                        . $this->_quote_replace($this->left_delimiter) . 'php'
267                                        . "' . str_repeat(\"\n\", substr_count('\\0', \"\n\")) .'"
268                                        . $this->_quote_replace($this->right_delimiter)
269                                        . "'"
270                                        , $source_content);
271
272         /* Gather all template tags. */
273         preg_match_all("~{$ldq}\s*(.*?)\s*{$rdq}~s", $source_content, $_match);
274         $template_tags = $_match[1];
275         /* Split content by template tags to obtain non-template content. */
276         $text_blocks = preg_split("~{$ldq}.*?{$rdq}~s", $source_content);
277
278         /* loop through text blocks */
279         for ($curr_tb = 0, $for_max = count($text_blocks); $curr_tb < $for_max; $curr_tb++) {
280             /* match anything resembling php tags */
281             if (preg_match_all('~(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?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         }
307
308         /* Compile the template tags into PHP code. */
309         $compiled_tags = array();
310         for ($i = 0, $for_max = count($template_tags); $i < $for_max; $i++) {
311             $this->_current_line_no += substr_count($text_blocks[$i], "\n");
312             $compiled_tags[] = $this->_compile_tag($template_tags[$i]);
313             $this->_current_line_no += substr_count($template_tags[$i], "\n");
314         }
315         if (count($this->_tag_stack)>0) {
316             list($_open_tag, $_line_no) = end($this->_tag_stack);
317             $this->_syntax_error("unclosed tag \{$_open_tag} (opened line $_line_no).", E_USER_ERROR, __FILE__, __LINE__);
318             return;
319         }
320
321         /* Reformat $text_blocks between 'strip' and '/strip' tags,
322            removing spaces, tabs and newlines. */
323         $strip = false;
324         for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) {
325             if ($compiled_tags[$i] == '{strip}') {
326                 $compiled_tags[$i] = '';
327                 $strip = true;
328                 /* remove leading whitespaces */
329                 $text_blocks[$i + 1] = ltrim($text_blocks[$i + 1]);
330             }
331             if ($strip) {
332                 /* strip all $text_blocks before the next '/strip' */
333                 for ($j = $i + 1; $j < $for_max; $j++) {
334                     /* remove leading and trailing whitespaces of each line */
335                     $text_blocks[$j] = preg_replace('![\t ]*[\r\n]+[\t ]*!', '', $text_blocks[$j]);
336                     if ($compiled_tags[$j] == '{/strip}') {                       
337                         /* remove trailing whitespaces from the last text_block */
338                         $text_blocks[$j] = rtrim($text_blocks[$j]);
339                     }
340                     $text_blocks[$j] = "<?php echo '" . strtr($text_blocks[$j], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>";
341                     if ($compiled_tags[$j] == '{/strip}') {
342                         $compiled_tags[$j] = "\n"; /* slurped by php, but necessary
343                                     if a newline is following the closing strip-tag */
344                         $strip = false;
345                         $i = $j;
346                         break;
347                     }
348                 }
349             }
350         }
351         $compiled_content = '';
352
353         /* 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];
362
363         // remove \n from the end of the file, if any
364         if (strlen($compiled_content) && (substr($compiled_content, -1) == "\n") ) {
365             $compiled_content = substr($compiled_content, 0, -1);
366         }
367
368         if (!empty($this->_cache_serial)) {
369             $compiled_content = "<?php \$this->_cache_serials['".$this->_cache_include."'] = '".$this->_cache_serial."'; ?>" . $compiled_content;
370         }
371
372         // remove unnecessary close/open tags
373         $compiled_content = preg_replace('~\?>\n?<\?php~', '', $compiled_content);
374
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         }
388
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";
392
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         }
409
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         }
414
415         $compiled_content = $template_header . $compiled_content;
416         return true;
417     }
418
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 (substr($template_tag, 0, 1) == '*' && substr($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;
442
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         }
448
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         }
453
454         switch ($tag_command) {
455             case 'include':
456                 return $this->_compile_include_tag($tag_args);
457
458             case 'include_php':
459                 return $this->_compile_include_php_tag($tag_args);
460
461             case 'if':
462                 $this->_push_tag('if');
463                 return $this->_compile_if_tag($tag_args);
464
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: ?>';
472
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);
480
481             case '/if':
482                 $this->_pop_tag('if');
483                 return '<?php endif; ?>';
484
485             case 'capture':
486                 return $this->_compile_capture_tag(true, $tag_args);
487
488             case '/capture':
489                 return $this->_compile_capture_tag(false);
490
491             case 'ldelim':
492                 return $this->left_delimiter;
493
494             case 'rdelim':
495                 return $this->right_delimiter;
496
497             case 'section':
498                 $this->_push_tag('section');
499                 return $this->_compile_section_start($tag_args);
500
501             case 'sectionelse':
502                 $this->_push_tag('sectionelse');
503                 return "<?php endfor; else: ?>";
504                 break;
505
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; ?>";
512
513             case 'foreach':
514                 $this->_push_tag('foreach');
515                 return $this->_compile_foreach_start($tag_args);
516                 break;
517
518             case 'foreachelse':
519                 $this->_push_tag('foreachelse');
520                 return "<?php endforeach; else: ?>";
521
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;
529
530             case 'strip':
531             case '/strip':
532                 if (substr($tag_command, 0, 1)=='/') {
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 '';
546
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 '';
556
557                     case 3: /* literal */
558                         return "<?php echo '" . strtr($block[2], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>" . $this->_additional_newline;
559
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;
568
569             case 'insert':
570                 return $this->_compile_insert_tag($tag_args);
571
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                 }
582
583         }
584     }
585
586
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;
600
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;
619
620             include_once $plugin_file;
621
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         }
630
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     }
653
654
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 (substr($tag_command, 0, 1) == '/') {
668             $start_tag = false;
669             $tag_command = substr($tag_command, 1);
670         } else
671             $start_tag = true;
672
673         $found = false;
674         $have_function = true;
675
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;
694
695             include_once $plugin_file;
696
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);
703
704             }
705         }
706
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         }
713
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);
720
721         if ($start_tag)
722             $this->_push_tag($tag_command);
723         else
724             $this->_pop_tag($tag_command);
725
726         if ($start_tag) {
727             $output = '<?php ' . $this->_push_cacheable_state('block', $tag_command);
728             $attrs = $this->_parse_attrs($tag_args);
729             $_cache_attrs='';
730             $arg_list = $this->_compile_arg_list('block', $tag_command, $attrs, $_cache_attrs);
731             $output .= "$_cache_attrs\$this->_tag_stack[] = array('$tag_command', array(".implode(',', $arg_list).')); ';
732             $output .= '$_block_repeat=true;' . $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], null, $this, $_block_repeat);';
733             $output .= 'while ($_block_repeat) { ob_start(); ?>';
734         } else {
735             $output = '<?php $_block_content = ob_get_contents(); ob_end_clean(); ';
736             $_out_tag_text = $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], $_block_content, $this, $_block_repeat)';
737             if ($tag_modifier != '') {
738                 $this->_parse_modifiers($_out_tag_text, $tag_modifier);
739             }
740             $output .= '$_block_repeat=false;echo ' . $_out_tag_text . '; } ';
741             $output .= " array_pop(\$this->_tag_stack); " . $this->_pop_cacheable_state('block', $tag_command) . '?>';
742         }
743
744         return true;
745     }
746
747
748     /**
749      * compile custom function tag
750      *
751      * @param string $tag_command
752      * @param string $tag_args
753      * @param string $tag_modifier
754      * @return string
755      */
756     function _compile_custom_tag($tag_command, $tag_args, $tag_modifier, &$output)
757     {
758         $found = false;
759         $have_function = true;
760
761         /*
762          * First we check if the custom function has already been registered
763          * or loaded from a plugin file.
764          */
765         if (isset($this->_plugins['function'][$tag_command])) {
766             $found = true;
767             $plugin_func = $this->_plugins['function'][$tag_command][0];
768             if (!is_callable($plugin_func)) {
769                 $message = "custom function '$tag_command' is not implemented";
770                 $have_function = false;
771             }
772         }
773         /*
774          * Otherwise we need to load plugin file and look for the function
775          * inside it.
776          */
777         else if ($plugin_file = $this->_get_plugin_filepath('function', $tag_command)) {
778             $found = true;
779
780             include_once $plugin_file;
781
782             $plugin_func = 'smarty_function_' . $tag_command;
783             if (!function_exists($plugin_func)) {
784                 $message = "plugin function $plugin_func() not found in $plugin_file\n";
785                 $have_function = false;
786             } else {
787                 $this->_plugins['function'][$tag_command] = array($plugin_func, null, null, null, true);
788
789             }
790         }
791
792         if (!$found) {
793             return false;
794         } else if (!$have_function) {
795             $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
796             return true;
797         }
798
799         /* declare plugin to be loaded on display of the template that
800            we compile right now */
801         $this->_add_plugin('function', $tag_command);
802
803         $_cacheable_state = $this->_push_cacheable_state('function', $tag_command);
804         $attrs = $this->_parse_attrs($tag_args);
805         $_cache_attrs = '';
806         $arg_list = $this->_compile_arg_list('function', $tag_command, $attrs, $_cache_attrs);
807
808         $output = $this->_compile_plugin_call('function', $tag_command).'(array('.implode(',', $arg_list)."), \$this)";
809         if($tag_modifier != '') {
810             $this->_parse_modifiers($output, $tag_modifier);
811         }
812
813         if($output != '') {
814             $output '<?php ' . $_cacheable_state . $_cache_attrs . 'echo ' . $output . ';'
815                 . $this->_pop_cacheable_state('function', $tag_command) . "?>" . $this->_additional_newline;
816         }
817
818         return true;
819     }
820
821     /**
822      * compile a registered object tag
823      *
824      * @param string $tag_command
825      * @param array $attrs
826      * @param string $tag_modifier
827      * @return string
828      */
829     function _compile_registered_object_tag($tag_command, $attrs, $tag_modifier)
830     {
831         if (substr($tag_command, 0, 1) == '/') {
832             $start_tag = false;
833             $tag_command = substr($tag_command, 1);
834         } else {
835             $start_tag = true;
836         }
837
838         list($object, $obj_comp) = explode('->', $tag_command);
839
840         $arg_list = array();
841         if(count($attrs)) {
842             $_assign_var = false;
843             foreach ($attrs as $arg_name => $arg_value) {
844                 if($arg_name == 'assign') {
845                     $_assign_var = $arg_value;
846                     unset($attrs['assign']);
847                     continue;
848                 }
849                 if (is_bool($arg_value))
850                     $arg_value = $arg_value ? 'true' : 'false';
851                 $arg_list[] = "'$arg_name' => $arg_value";
852             }
853         }
854
855         if($this->_reg_objects[$object][2]) {
856             // smarty object argument format
857             $args = "array(".implode(',', (array)$arg_list)."), \$this";
858         } else {
859             // traditional argument format
860             $args = implode(',', array_values($attrs));
861             if (empty($args)) {
862                 $args = 'null';
863             }
864         }
865
866         $prefix = '';
867         $postfix = '';
868         $newline = '';
869         if(!is_object($this->_reg_objects[$object][0])) {
870             $this->_trigger_fatal_error("registered '$object' is not an object" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
871         } elseif(!empty($this->_reg_objects[$object][1]) && !in_array($obj_comp, $this->_reg_objects[$object][1])) {
872             $this->_trigger_fatal_error("'$obj_comp' is not a registered component of object '$object'", $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
873         } elseif(method_exists($this->_reg_objects[$object][0], $obj_comp)) {
874             // method
875             if(in_array($obj_comp, $this->_reg_objects[$object][3])) {
876                 // block method
877                 if ($start_tag) {
878                     $prefix = "\$this->_tag_stack[] = array('$obj_comp', $args); ";
879                     $prefix .= "\$_block_repeat=true; \$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], null, \$this, \$_block_repeat); ";
880                     $prefix .= "while (\$_block_repeat) { ob_start();";
881                     $return = null;
882                     $postfix = '';
883             } else {
884                     $prefix = "\$_obj_block_content = ob_get_contents(); ob_end_clean(); ";
885                     $return = "\$_block_repeat=false; \$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], \$_obj_block_content, \$this, \$_block_repeat)";
886                     $postfix = "} array_pop(\$this->_tag_stack);";
887                 }
888             } else {
889                 // non-block method
890                 $return = "\$this->_reg_objects['$object'][0]->$obj_comp($args)";
891             }
892         } else {
893             // property
894             $return = "\$this->_reg_objects['$object'][0]->$obj_comp";
895         }
896
897         if($return != null) {
898             if($tag_modifier != '') {
899                 $this->_parse_modifiers($return, $tag_modifier);
900             }
901
902             if(!empty($_assign_var)) {
903                 $output = "\$this->assign('" . $this->_dequote($_assign_var) ."',  $return);";
904             } else {
905                 $output = 'echo ' . $return . ';';
906                 $newline = $this->_additional_newline;
907             }
908         } else {
909             $output = '';
910         }
911
912         return '<?php ' . $prefix . $output . $postfix . "?>" . $newline;
913     }
914
915     /**
916      * Compile {insert ...} tag
917      *
918      * @param string $tag_args
919      * @return string
920      */
921     function _compile_insert_tag($tag_args)
922     {
923         $attrs = $this->_parse_attrs($tag_args);
924         $name = $this->_dequote($attrs['name']);
925
926         if (empty($name)) {
927             $this->_syntax_error("missing insert name", E_USER_ERROR, __FILE__, __LINE__);
928         }
929
930         if (!empty($attrs['script'])) {
931             $delayed_loading = true;
932         } else {
933             $delayed_loading = false;
934         }
935
936         foreach ($attrs as $arg_name => $arg_value) {
937             if (is_bool($arg_value))
938                 $arg_value = $arg_value ? 'true' : 'false';
939             $arg_list[] = "'$arg_name' => $arg_value";
940         }
941
942         $this->_add_plugin('insert', $name, $delayed_loading);
943
944         $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))";
945
946         return "<?php require_once(SMARTY_CORE_DIR . 'core.run_insert_handler.php');\necho smarty_core_run_insert_handler($_params, \$this); ?>" . $this->_additional_newline;
947     }
948
949     /**
950      * Compile {include ...} tag
951      *
952      * @param string $tag_args
953      * @return string
954      */
955     function _compile_include_tag($tag_args)
956     {
957         $attrs = $this->_parse_attrs($tag_args);
958         $arg_list = array();
959
960         if (empty($attrs['file'])) {
961             $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR, __FILE__, __LINE__);
962         }
963
964         foreach ($attrs as $arg_name => $arg_value) {
965             if ($arg_name == 'file') {
966                 $include_file = $arg_value;
967                 continue;
968             } else if ($arg_name == 'assign') {
969                 $assign_var = $arg_value;
970                 continue;
971             }
972             if (is_bool($arg_value))
973                 $arg_value = $arg_value ? 'true' : 'false';
974             $arg_list[] = "'$arg_name' => $arg_value";
975         }
976
977         $output = '<?php ';
978
979         if (isset($assign_var)) {
980             $output .= "ob_start();\n";
981         }
982
983         $output .=
984             "\$_smarty_tpl_vars = \$this->_tpl_vars;\n";
985
986
987         $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))";
988         $output .= "\$this->_smarty_include($_params);\n" .
989         "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" .
990         "unset(\$_smarty_tpl_vars);\n";
991
992         if (isset($assign_var)) {
993             $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n";
994         }
995
996         $output .= ' ?>';
997
998         return $output;
999
1000     }
1001
1002     /**
1003      * Compile {include ...} tag
1004      *
1005      * @param string $tag_args
1006      * @return string
1007      */
1008     function _compile_include_php_tag($tag_args)
1009     {
1010         $attrs = $this->_parse_attrs($tag_args);
1011
1012         if (empty($attrs['file'])) {
1013             $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR, __FILE__, __LINE__);
1014         }
1015
1016         $assign_var = (empty($attrs['assign'])) ? '' : $this->_dequote($attrs['assign']);
1017         $once_var = (empty($attrs['once']) || $attrs['once']=='false') ? 'false' : 'true';
1018
1019         $arg_list = array();
1020         foreach($attrs as $arg_name => $arg_value) {
1021             if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') {
1022                 if(is_bool($arg_value))
1023                     $arg_value = $arg_value ? 'true' : 'false';
1024                 $arg_list[] = "'$arg_name' => $arg_value";
1025             }
1026         }
1027
1028         $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', $arg_list)."))";
1029
1030         return "<?php require_once(SMARTY_CORE_DIR . 'core.smarty_include_php.php');\nsmarty_core_smarty_include_php($_params, \$this); ?>" . $this->_additional_newline;
1031     }
1032
1033
1034     /**
1035      * Compile {section ...} tag
1036      *
1037      * @param string $tag_args
1038      * @return string
1039      */
1040     function _compile_section_start($tag_args)
1041     {
1042         $attrs = $this->_parse_attrs($tag_args);
1043         $arg_list = array();
1044
1045         $output = '<?php ';
1046         $section_name = $attrs['name'];
1047         if (empty($section_name)) {
1048             $this->_syntax_error("missing section name", E_USER_ERROR, __FILE__, __LINE__);
1049         }
1050
1051         $output .= "unset(\$this->_sections[$section_name]);\n";
1052         $section_props = "\$this->_sections[$section_name]";
1053
1054         foreach ($attrs as $attr_name => $attr_value) {
1055             switch ($attr_name) {
1056                 case 'loop':
1057                     $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n";
1058                     break;
1059
1060                 case 'show':
1061                     if (is_bool($attr_value))
1062                         $show_attr_value = $attr_value ? 'true' : 'false';
1063                     else
1064                         $show_attr_value = "(bool)$attr_value";
1065                     $output .= "{$section_props}['show'] = $show_attr_value;\n";
1066                     break;
1067
1068                 case 'name':
1069                     $output .= "{$section_props}['$attr_name'] = $attr_value;\n";
1070                     break;
1071
1072                 case 'max':
1073                 case 'start':
1074                     $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n";
1075                     break;
1076
1077                 case 'step':
1078                     $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n";
1079                     break;
1080
1081                 default:
1082                     $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR, __FILE__, __LINE__);
1083                     break;
1084             }
1085         }
1086
1087         if (!isset($attrs['show']))
1088             $output .= "{$section_props}['show'] = true;\n";
1089
1090         if (!isset($attrs['loop']))
1091             $output .= "{$section_props}['loop'] = 1;\n";
1092
1093         if (!isset($attrs['max']))
1094             $output .= "{$section_props}['max'] = {$section_props}['loop'];\n";
1095         else
1096             $output .= "if ({$section_props}['max'] < 0)\n" .
1097                        "    {$section_props}['max'] = {$section_props}['loop'];\n";
1098
1099         if (!isset($attrs['step']))
1100             $output .= "{$section_props}['step'] = 1;\n";
1101
1102         if (!isset($attrs['start']))
1103             $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n";
1104         else {
1105             $output .= "if ({$section_props}['start'] < 0)\n" .
1106                        "    {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" .
1107                        "else\n" .
1108                        "    {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n";
1109         }
1110
1111         $output .= "if ({$section_props}['show']) {\n";
1112         if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) {
1113             $output .= "    {$section_props}['total'] = {$section_props}['loop'];\n";
1114         } else {
1115             $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";
1116         }
1117         $output .= "    if ({$section_props}['total'] == 0)\n" .
1118                    "        {$section_props}['show'] = false;\n" .
1119                    "} else\n" .
1120                    "    {$section_props}['total'] = 0;\n";
1121
1122         $output .= "if ({$section_props}['show']):\n";
1123         $output .= "
1124             for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1;
1125                  {$section_props}['iteration'] <= {$section_props}['total'];
1126                  {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n";
1127         $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n";
1128         $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n";
1129         $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n";
1130         $output .= "{$section_props}['first']      = ({$section_props}['iteration'] == 1);\n";
1131         $output .= "{$section_props}['last']       = ({$section_props}['iteration'] == {$section_props}['total']);\n";
1132
1133         $output .= "?>";
1134
1135         return $output;
1136     }
1137
1138
1139     /**
1140      * Compile {foreach ...} tag.
1141      *
1142      * @param string $tag_args
1143      * @return string
1144      */
1145     function _compile_foreach_start($tag_args)
1146     {
1147         $attrs = $this->_parse_attrs($tag_args);
1148         $arg_list = array();
1149
1150         if (empty($attrs['from'])) {
1151             return $this->_syntax_error("foreach: missing 'from' attribute", E_USER_ERROR, __FILE__, __LINE__);
1152         }
1153         $from = $attrs['from'];
1154
1155         if (empty($attrs['item'])) {
1156             return $this->_syntax_error("foreach: missing 'item' attribute", E_USER_ERROR, __FILE__, __LINE__);
1157         }
1158         $item = $this->_dequote($attrs['item']);
1159         if (!preg_match('~^\w+$~', $item)) {
1160             return $this->_syntax_error("'foreach: item' must be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1161         }
1162
1163         if (isset($attrs['key'])) {
1164             $key  = $this->_dequote($attrs['key']);
1165             if (!preg_match('~^\w+$~', $key)) {
1166                 return $this->_syntax_error("foreach: 'key' must to be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1167             }
1168             $key_part = "\$this->_tpl_vars['$key'] => ";
1169         } else {
1170             $key = null;
1171             $key_part = '';
1172         }
1173
1174         if (isset($attrs['name'])) {
1175             $name = $attrs['name'];
1176         } else {
1177             $name = null;
1178         }
1179
1180         $output = '<?php ';
1181         $output .= "\$_from = $from; if (!is_array(\$_from) && !is_object(\$_from)) { settype(\$_from, 'array'); }";
1182         if (isset($name)) {
1183             $foreach_props = "\$this->_foreach[$name]";
1184             $output .= "{$foreach_props} = array('total' => count(\$_from), 'iteration' => 0);\n";
1185             $output .= "if ({$foreach_props}['total'] > 0):\n";
1186             $output .= "    foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1187             $output .= "        {$foreach_props}['iteration']++;\n";
1188         } else {
1189             $output .= "if (count(\$_from)):\n";
1190             $output .= "    foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1191         }
1192         $output .= '?>';
1193
1194         return $output;
1195     }
1196
1197
1198     /**
1199      * Compile {capture} .. {/capture} tags
1200      *
1201      * @param boolean $start true if this is the {capture} tag
1202      * @param string $tag_args
1203      * @return string
1204      */
1205
1206     function _compile_capture_tag($start, $tag_args = '')
1207     {
1208         $attrs = $this->_parse_attrs($tag_args);
1209
1210         if ($start) {
1211             if (isset($attrs['name']))
1212                 $buffer = $attrs['name'];
1213             else
1214                 $buffer = "'default'";
1215
1216             if (isset($attrs['assign']))
1217                 $assign = $attrs['assign'];
1218             else
1219                 $assign = null;
1220             $output = "<?php ob_start(); ?>";
1221             $this->_capture_stack[] = array($buffer, $assign);
1222         } else {
1223             list($buffer, $assign) = array_pop($this->_capture_stack);
1224             $output = "<?php \$this->_smarty_vars['capture'][$buffer] = ob_get_contents(); ";
1225             if (isset($assign)) {
1226                 $output .= " \$this->assign($assign, ob_get_contents());";
1227             }
1228             $output .= "ob_end_clean(); ?>";
1229         }
1230
1231         return $output;
1232     }
1233
1234     /**
1235      * Compile {if ...} tag
1236      *
1237      * @param string $tag_args
1238      * @param boolean $elseif if true, uses elseif instead of if
1239      * @return string
1240      */
1241     function _compile_if_tag($tag_args, $elseif = false)
1242     {
1243
1244         /* Tokenize args for 'if' tag. */
1245         preg_match_all('~(?>
1246                 ' . $this->_obj_call_regexp . '(?:' . $this->_mod_regexp . '*)? | # valid object call
1247                 ' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)?    | # var or quoted string
1248                 \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@    | # valid non-word token
1249                 \b\w+\b                                                        | # valid word token
1250                 \S+                                                           # anything else
1251                 )~x', $tag_args, $match);
1252
1253         $tokens = $match[0];
1254
1255         if(empty($tokens)) {
1256             $_error_msg = $elseif ? "'elseif'" : "'if'";
1257             $_error_msg .= ' statement requires arguments';
1258             $this->_syntax_error($_error_msg, E_USER_ERROR, __FILE__, __LINE__);
1259         }
1260             
1261                 
1262         // make sure we have balanced parenthesis
1263         $token_count = array_count_values($tokens);
1264         if(isset($token_count['(']) && $token_count['('] != $token_count[')']) {
1265             $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR, __FILE__, __LINE__);
1266         }
1267
1268         $is_arg_stack = array();
1269
1270         for ($i = 0; $i < count($tokens); $i++) {
1271
1272             $token = &$tokens[$i];
1273
1274             switch (strtolower($token)) {
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                 case '&':
1293                 case '~':
1294                 case ')':
1295                 case ',':
1296                 case '+':
1297                 case '-':
1298                 case '*':
1299                 case '/':
1300                 case '@':
1301                     break;
1302
1303                 case 'eq':
1304                     $token = '==';
1305                     break;
1306
1307                 case 'ne':
1308                 case 'neq':
1309                     $token = '!=';
1310                     break;
1311
1312                 case 'lt':
1313                     $token = '<';
1314                     break;
1315
1316                 case 'le':
1317                 case 'lte':
1318                     $token = '<=';
1319                     break;
1320
1321                 case 'gt':
1322                     $token = '>';
1323                     break;
1324
1325                 case 'ge':
1326                 case 'gte':
1327                     $token = '>=';
1328                     break;
1329
1330                 case 'and':
1331                     $token = '&&';
1332                     break;
1333
1334                 case 'or':
1335                     $token = '||';
1336                     break;
1337
1338                 case 'not':
1339                     $token = '!';
1340                     break;
1341
1342                 case 'mod':
1343                     $token = '%';
1344                     break;
1345
1346                 case '(':
1347                     array_push($is_arg_stack, $i);
1348                     break;
1349
1350                 case 'is':
1351                     /* If last token was a ')', we operate on the parenthesized
1352                        expression. The start of the expression is on the stack.
1353                        Otherwise, we operate on the last encountered token. */
1354                     if ($tokens[$i-1] == ')')
1355                         $is_arg_start = array_pop($is_arg_stack);
1356                     else
1357                         $is_arg_start = $i-1;
1358                     /* Construct the argument for 'is' expression, so it knows
1359                        what to operate on. */
1360                     $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
1361
1362                     /* Pass all tokens from next one until the end to the
1363                        'is' expression parsing function. The function will
1364                        return modified tokens, where the first one is the result
1365                        of the 'is' expression and the rest are the tokens it
1366                        didn't touch. */
1367                     $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));
1368
1369                     /* Replace the old tokens with the new ones. */
1370                     array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);
1371
1372                     /* Adjust argument start so that it won't change from the
1373                        current position for the next iteration. */
1374                     $i = $is_arg_start;
1375                     break;
1376
1377                 default:
1378                     if(preg_match('~^' . $this->_func_regexp . '$~', $token) ) {
1379                             // function call
1380                             if($this->security &&
1381                                !in_array($token, $this->security_settings['IF_FUNCS'])) {
1382                                 $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);
1383                             }
1384                     } elseif(preg_match('~^' . $this->_var_regexp . '$~', $token) && (strpos('+-*/^%&|', substr($token, -1)) === false) && isset($tokens[$i+1]) && $tokens[$i+1] == '(') {
1385                         // variable function call
1386                         $this->_syntax_error("variable function call '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);                     
1387                     } elseif(preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$~', $token)) {
1388                         // object or variable
1389                         $token = $this->_parse_var_props($token);
1390                     } elseif(is_numeric($token)) {
1391                         // number, skip it
1392                     } else {
1393                         $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__);
1394                     }
1395                     break;
1396             }
1397         }
1398
1399         if ($elseif)
1400             return '<?php elseif ('.implode(' ', $tokens).'): ?>';
1401         else
1402             return '<?php if ('.implode(' ', $tokens).'): ?>';
1403     }
1404
1405
1406     function _compile_arg_list($type, $name, $attrs, &$cache_code) {
1407         $arg_list = array();
1408
1409         if (isset($type) && isset($name)
1410             && isset($this->_plugins[$type])
1411             && isset($this->_plugins[$type][$name])
1412             && empty($this->_plugins[$type][$name][4])
1413             && is_array($this->_plugins[$type][$name][5])
1414             ) {
1415             /* we have a list of parameters that should be cached */
1416             $_cache_attrs = $this->_plugins[$type][$name][5];
1417             $_count = $this->_cache_attrs_count++;
1418             $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');";
1419
1420         } else {
1421             /* no parameters are cached */
1422             $_cache_attrs = null;
1423         }
1424
1425         foreach ($attrs as $arg_name => $arg_value) {
1426             if (is_bool($arg_value))
1427                 $arg_value = $arg_value ? 'true' : 'false';
1428             if (is_null($arg_value))
1429                 $arg_value = 'null';
1430             if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) {
1431                 $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)";
1432             } else {
1433                 $arg_list[] = "'$arg_name' => $arg_value";
1434             }
1435         }
1436         return $arg_list;
1437     }
1438
1439     /**
1440      * Parse is expression
1441      *
1442      * @param string $is_arg
1443      * @param array $tokens
1444      * @return array
1445      */
1446     function _parse_is_expr($is_arg, $tokens)
1447     {
1448         $expr_end = 0;
1449         $negate_expr = false;
1450
1451         if (($first_token = array_shift($tokens)) == 'not') {
1452             $negate_expr = true;
1453             $expr_type = array_shift($tokens);
1454         } else
1455             $expr_type = $first_token;
1456
1457         switch ($expr_type) {
1458             case 'even':
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;
1466
1467             case 'odd':
1468                 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1469                     $expr_end++;
1470                     $expr_arg = $tokens[$expr_end++];
1471                     $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1472                 } else
1473                     $expr = "(1 & $is_arg)";
1474                 break;
1475
1476             case 'div':
1477                 if (@$tokens[$expr_end] == 'by') {
1478                     $expr_end++;
1479                     $expr_arg = $tokens[$expr_end++];
1480                     $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")";
1481                 } else {
1482                     $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__);
1483                 }
1484                 break;
1485
1486             default:
1487                 $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__);
1488                 break;
1489         }
1490
1491         if ($negate_expr) {
1492             $expr = "!($expr)";
1493         }
1494
1495         array_splice($tokens, 0, $expr_end, $expr);
1496
1497         return $tokens;
1498     }
1499
1500
1501     /**
1502      * Parse attribute string
1503      *
1504      * @param string $tag_args
1505      * @return array
1506      */
1507     function _parse_attrs($tag_args)
1508     {
1509
1510         /* Tokenize tag attributes. */
1511         preg_match_all('~(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+)
1512                          )+ |
1513                          [=]
1514                         ~x', $tag_args, $match);
1515         $tokens       = $match[0];
1516
1517         $attrs = array();
1518         /* Parse state:
1519             0 - expecting attribute name
1520             1 - expecting '='
1521             2 - expecting attribute value (not '=') */
1522         $state = 0;
1523
1524         foreach ($tokens as $token) {
1525             switch ($state) {
1526                 case 0:
1527                     /* If the token is a valid identifier, we set attribute name
1528                        and go to state 1. */
1529                     if (preg_match('~^\w+$~', $token)) {
1530                         $attr_name = $token;
1531                         $state = 1;
1532                     } else
1533                         $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__);
1534                     break;
1535
1536                 case 1:
1537                     /* If the token is '=', then we go to state 2. */
1538                     if ($token == '=') {
1539                         $state = 2;
1540                     } else
1541                         $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1542                     break;
1543
1544                 case 2:
1545                     /* If token is not '=', we set the attribute value and go to
1546                        state 0. */
1547                     if ($token != '=') {
1548                         /* We booleanize the token if it's a non-quoted possible
1549                            boolean value. */
1550                         if (preg_match('~^(on|yes|true)$~', $token)) {
1551                             $token = 'true';
1552                         } else if (preg_match('~^(off|no|false)$~', $token)) {
1553                             $token = 'false';
1554                         } else if ($token == 'null') {
1555                             $token = 'null';
1556                         } else if (preg_match('~^' . $this->_num_const_regexp . '|0[xX][0-9a-fA-F]+$~', $token)) {
1557                             /* treat integer literally */
1558                         } else if (!preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$~', $token)) {
1559                             /* treat as a string, double-quote it escaping quotes */
1560                             $token = '"'.addslashes($token).'"';
1561                         }
1562
1563                         $attrs[$attr_name] = $token;
1564                         $state = 0;
1565                     } else
1566                         $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__);
1567                     break;
1568             }
1569             $last_token = $token;
1570         }
1571
1572         if($state != 0) {
1573             if($state == 1) {
1574                 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1575             } else {
1576                 $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__);
1577             }
1578         }
1579
1580         $this->_parse_vars_props($attrs);
1581
1582         return $attrs;
1583     }
1584
1585     /**
1586      * compile multiple variables and section properties tokens into
1587      * PHP code
1588      *
1589      * @param array $tokens
1590      */
1591     function _parse_vars_props(&$tokens)
1592     {
1593         foreach($tokens as $key => $val) {
1594             $tokens[$key] = $this->_parse_var_props($val);
1595         }
1596     }
1597
1598     /**
1599      * compile single variable and section properties token into
1600      * PHP code
1601      *
1602      * @param string $val
1603      * @param string $tag_attrs
1604      * @return string
1605      */
1606     function _parse_var_props($val)
1607     {
1608         $val = trim($val);
1609
1610         if(preg_match('~^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$~', $val, $match)) {
1611             // $ variable or object
1612             $return = $this->_parse_var($match[1]);
1613             $modifiers = $match[2];
1614             if (!empty($this->default_modifiers) && !preg_match('~(^|\|)smarty:nodefaults($|\|)~',$modifiers)) {
1615                 $_default_mod_string = implode('|',(array)$this->default_modifiers);
1616                 $modifiers = empty($modifiers) ? $_default_mod_string : $_default_mod_string . '|' . $modifiers;
1617             }
1618             $this->_parse_modifiers($return, $modifiers);
1619             return $return;
1620         } elseif (preg_match('~^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1621                 // double quoted text
1622                 preg_match('~^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1623                 $return = $this->_expand_quoted_text($match[1]);
1624                 if($match[2] != '') {
1625                     $this->_parse_modifiers($return, $match[2]);
1626                 }
1627                 return $return;
1628             }
1629         elseif(preg_match('~^' . $this->_num_const_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1630                 // numerical constant
1631                 preg_match('~^(' . $this->_num_const_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1632                 if($match[2] != '') {
1633                     $this->_parse_modifiers($match[1], $match[2]);
1634                     return $match[1];
1635                 }
1636             }
1637         elseif(preg_match('~^' . $this->_si_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1638                 // single quoted text
1639                 preg_match('~^(' . $this->_si_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1640                 if($match[2] != '') {
1641                     $this->_parse_modifiers($match[1], $match[2]);
1642                     return $match[1];
1643                 }
1644             }
1645         elseif(preg_match('~^' . $this->_cvar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1646                 // config var
1647                 return $this->_parse_conf_var($val);
1648             }
1649         elseif(preg_match('~^' . $this->_svar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1650                 // section var
1651                 return $this->_parse_section_prop($val);
1652             }
1653         elseif(!in_array($val, $this->_permitted_tokens) && !is_numeric($val)) {
1654             // literal string
1655             return $this->_expand_quoted_text('"' . strtr($val, array('\\' => '\\\\', '"' => '\\"')) .'"');
1656         }
1657         return $val;
1658     }
1659
1660     /**
1661      * expand quoted text with embedded variables
1662      *
1663      * @param string $var_expr
1664      * @return string
1665      */
1666     function _expand_quoted_text($var_expr)
1667     {
1668         // if contains unescaped $, expand it
1669         if(preg_match_all('~(?:\`(?<!\\\\)\$' . $this->_dvar_guts_regexp . '(?:' . $this->_obj_ext_regexp . ')*\`)|(?:(?<!\\\\)\$\w+(\[[a-zA-Z0-9]+\])*)~', $var_expr, $_match)) {
1670             $_match = $_match[0];
1671             rsort($_match);
1672             reset($_match);
1673             foreach($_match as $_var) {
1674                 $var_expr = str_replace ($_var, '".(' . $this->_parse_var(str_replace('`','',$_var)) . ')."', $var_expr);
1675             }
1676             $_return = preg_replace('~\.""|(?<!\\\\)""\.~', '', $var_expr);
1677         } else {
1678             $_return = $var_expr;
1679         }
1680         // replace double quoted literal string with single quotes
1681         $_return = preg_replace('~^"([\s\w]+)"$~',"'\\1'",$_return);
1682         return $_return;
1683     }
1684
1685     /**
1686      * parse variable expression into PHP code
1687      *
1688      * @param string $var_expr
1689      * @param string $output
1690      * @return string
1691      */
1692     function _parse_var($var_expr)
1693     {
1694         $_has_math = false;
1695         $_math_vars = preg_split('~('.$this->_dvar_math_regexp.'|'.$this->_qstr_regexp.')~', $var_expr, -1, PREG_SPLIT_DELIM_CAPTURE);
1696
1697         if(count($_math_vars) > 1) {
1698             $_first_var = "";
1699             $_complete_var = "";
1700             $_output = "";
1701             // simple check if there is any math, to stop recursion (due to modifiers with "xx % yy" as parameter)
1702             foreach($_math_vars as $_k => $_math_var) {
1703                 $_math_var = $_math_vars[$_k];
1704
1705                 if(!empty($_math_var) || is_numeric($_math_var)) {
1706                     // hit a math operator, so process the stuff which came before it
1707                     if(preg_match('~^' . $this->_dvar_math_regexp . '$~', $_math_var)) {
1708                         $_has_math = true;
1709                         if(!empty($_complete_var) || is_numeric($_complete_var)) {
1710                             $_output .= $this->_parse_var($_complete_var);
1711                         }
1712
1713                         // just output the math operator to php
1714                         $_output .= $_math_var;
1715
1716                         if(empty($_first_var))
1717                             $_first_var = $_complete_var;
1718
1719                         $_complete_var = "";
1720                     } else {
1721                         $_complete_var .= $_math_var;
1722                     }
1723                 }
1724             }
1725             if($_has_math) {
1726                 if(!empty($_complete_var) || is_numeric($_complete_var))
1727                     $_output .= $this->_parse_var($_complete_var);
1728
1729                 // get the modifiers working (only the last var from math + modifier is left)
1730                 $var_expr = $_complete_var;
1731             }
1732         }
1733
1734         // prevent cutting of first digit in the number (we _definitly_ got a number if the first char is a digit)
1735         if(is_numeric(substr($var_expr, 0, 1)))
1736             $_var_ref = $var_expr;
1737         else
1738             $_var_ref = substr($var_expr, 1);
1739         
1740         if(!$_has_math) {
1741             
1742             // get [foo] and .foo and ->foo and (...) pieces
1743             preg_match_all('~(?:^\w+)|' . $this->_obj_params_regexp . '|(?:' . $this->_var_bracket_regexp . ')|->\$?\w+|\.\$?\w+|\S+~', $_var_ref, $match);
1744                         
1745             $_indexes = $match[0];
1746             $_var_name = array_shift($_indexes);
1747
1748             /* Handle $smarty.* variable references as a special case. */
1749             if ($_var_name == 'smarty') {
1750                 /*
1751                  * If the reference could be compiled, use the compiled output;
1752                  * otherwise, fall back on the $smarty variable generated at
1753                  * run-time.
1754                  */
1755                 if (($smarty_ref = $this->_compile_smarty_ref($_indexes)) !== null) {
1756                     $_output = $smarty_ref;
1757                 } else {
1758                     $_var_name = substr(array_shift($_indexes), 1);
1759                     $_output = "\$this->_smarty_vars['$_var_name']";
1760                 }
1761             } elseif(is_numeric($_var_name) && is_numeric(substr($var_expr, 0, 1))) {
1762                 // because . is the operator for accessing arrays thru inidizes we need to put it together again for floating point numbers
1763                 if(count($_indexes) > 0)
1764                 {
1765                     $_var_name .= implode("", $_indexes);
1766                     $_indexes = array();
1767                 }
1768                 $_output = $_var_name;
1769             } else {
1770                 $_output = "\$this->_tpl_vars['$_var_name']";
1771             }
1772
1773             foreach ($_indexes as $_index) {
1774                 if (substr($_index, 0, 1) == '[') {
1775                     $_index = substr($_index, 1, -1);
1776                     if (is_numeric($_index)) {
1777                         $_output .= "[$_index]";
1778                     } elseif (substr($_index, 0, 1) == '$') {
1779                         if (strpos($_index, '.') !== false) {
1780                             $_output .= '[' . $this->_parse_var($_index) . ']';
1781                         } else {
1782                             $_output .= "[\$this->_tpl_vars['" . substr($_index, 1) . "']]";
1783                         }
1784          &n