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