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