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