4 * Project: Smarty: the PHP compiling template engine
5 * File: Smarty_Compiler.class.php
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.
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.
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
21 * @link http://smarty.php.net/
22 * @author Monte Ohrt <monte at ohrt dot com>
23 * @author Andrei Zmievski <andrei@php.net>
25 * @copyright 2001-2005 New Digital Group, Inc.
32 * Template compiling class
35 class Smarty_Compiler extends Smarty {
41 var $_folded_blocks = array(); // keeps folded template blocks
42 var $_current_file = null; // the current template being compiled
43 var $_current_line_no = 1; // line number for error messages
44 var $_capture_stack = array(); // keeps track of nested capture buffers
45 var $_plugin_info = array(); // keeps track of plugins to load
46 var $_init_smarty_vars = false;
47 var $_permitted_tokens = array('true','false','yes','no','on','off','null');
48 var $_db_qstr_regexp = null; // regexps are setup in the constructor
49 var $_si_qstr_regexp = null;
50 var $_qstr_regexp = null;
51 var $_func_regexp = null;
52 var $_reg_obj_regexp = null;
53 var $_var_bracket_regexp = null;
54 var $_num_const_regexp = null;
55 var $_dvar_guts_regexp = null;
56 var $_dvar_regexp = null;
57 var $_cvar_regexp = null;
58 var $_svar_regexp = null;
59 var $_avar_regexp = null;
60 var $_mod_regexp = null;
61 var $_var_regexp = null;
62 var $_parenth_param_regexp = null;
63 var $_func_call_regexp = null;
64 var $_obj_ext_regexp = null;
65 var $_obj_start_regexp = null;
66 var $_obj_params_regexp = null;
67 var $_obj_call_regexp = null;
68 var $_cacheable_state = 0;
69 var $_cache_attrs_count = 0;
70 var $_nocache_count = 0;
71 var $_cache_serial = null;
72 var $_cache_include = null;
74 var $_strip_depth = 0;
75 var $_additional_newline = "\n";
79 * The class constructor.
81 function Smarty_Compiler()
83 // matches double quoted strings:
86 $this->_db_qstr_regexp = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
88 // matches single quoted strings:
91 $this->_si_qstr_regexp = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
93 // matches single or double quoted strings
94 $this->_qstr_regexp = '(?:' . $this->_db_qstr_regexp . '|' . $this->_si_qstr_regexp . ')';
96 // matches bracket portion of vars
100 $this->_var_bracket_regexp = '\[\$?[\w\.]+\]';
102 // matches numerical constants
106 $this->_num_const_regexp = '(?:\-?\d+(?:\.\d+)?)';
108 // matches $ vars (not objects):
115 // $foo[5].bar[$foobar][4]
116 $this->_dvar_math_regexp = '(?:[\+\*\/\%]|(?:-(?!>)))';
117 $this->_dvar_math_var_regexp = '[\$\w\.\+\-\*\/\%\d\>\[\]]';
118 $this->_dvar_guts_regexp = '\w+(?:' . $this->_var_bracket_regexp
119 . ')*(?:\.\$?\w+(?:' . $this->_var_bracket_regexp . ')*)*(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?';
120 $this->_dvar_regexp = '\$' . $this->_dvar_guts_regexp;
122 // matches config vars:
125 $this->_cvar_regexp = '\#\w+\#';
127 // matches section vars:
129 $this->_svar_regexp = '\%\w+\.\w+\%';
131 // matches all valid variables (no quotes, no modifiers)
132 $this->_avar_regexp = '(?:' . $this->_dvar_regexp . '|'
133 . $this->_cvar_regexp . '|' . $this->_svar_regexp . ')';
135 // matches valid variable syntax:
142 $this->_var_regexp = '(?:' . $this->_avar_regexp . '|' . $this->_qstr_regexp . ')';
144 // matches valid object call (one level of object nesting allowed in parameters):
148 // $foo->bar($foo, $bar, "text")
149 // $foo->bar($foo, "foo")
151 // $foo->bar->foo->bar()
152 // $foo->bar($foo->bar)
153 // $foo->bar($foo->bar())
154 // $foo->bar($foo->bar($blah,$foo,44,"foo",$foo[0].bar))
155 $this->_obj_ext_regexp = '\->(?:\$?' . $this->_dvar_guts_regexp . ')';
156 $this->_obj_restricted_param_regexp = '(?:'
157 . '(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')(?:' . $this->_obj_ext_regexp . '(?:\((?:(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')'
158 . '(?:\s*,\s*(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . '))*)?\))?)*)';
159 $this->_obj_single_param_regexp = '(?:\w+|' . $this->_obj_restricted_param_regexp . '(?:\s*,\s*(?:(?:\w+|'
160 . $this->_var_regexp . $this->_obj_restricted_param_regexp . ')))*)';
161 $this->_obj_params_regexp = '\((?:' . $this->_obj_single_param_regexp
162 . '(?:\s*,\s*' . $this->_obj_single_param_regexp . ')*)?\)';
163 $this->_obj_start_regexp = '(?:' . $this->_dvar_regexp . '(?:' . $this->_obj_ext_regexp . ')+)';
164 $this->_obj_call_regexp = '(?:' . $this->_obj_start_regexp . '(?:' . $this->_obj_params_regexp . ')?(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?)';
166 // matches valid modifier syntax:
171 // |foo:"bar":$foobar
174 $this->_mod_regexp = '(?:\|@?\w+(?::(?:\w+|' . $this->_num_const_regexp . '|'
175 . $this->_obj_call_regexp . '|' . $this->_avar_regexp . '|' . $this->_qstr_regexp .'))*)';
177 // matches valid function name:
180 $this->_func_regexp = '[a-zA-Z_]\w*';
182 // matches valid registered object:
184 $this->_reg_obj_regexp = '[a-zA-Z_]\w*->[a-zA-Z_]\w*';
186 // matches valid parameter values:
195 $this->_param_regexp = '(?:\s*(?:' . $this->_obj_call_regexp . '|'
196 . $this->_var_regexp . '|' . $this->_num_const_regexp . '|\w+)(?>' . $this->_mod_regexp . '*)\s*)';
198 // matches valid parenthesised function parameters:
201 // $foo, $bar, "text"
202 // $foo|bar, "foo"|bar, $foo->bar($foo)|bar
203 $this->_parenth_param_regexp = '(?:\((?:\w+|'
204 . $this->_param_regexp . '(?:\s*,\s*(?:(?:\w+|'
205 . $this->_param_regexp . ')))*)?\))';
207 // matches valid function call:
210 // _foo_bar($foo,"bar")
211 // foo123($foo,$foo->bar(),"foo")
212 $this->_func_call_regexp = '(?:' . $this->_func_regexp . '\s*(?:'
213 . $this->_parenth_param_regexp . '))';
219 * sets $compiled_content to the compiled source
220 * @param string $resource_name
221 * @param string $source_content
222 * @param string $compiled_content
225 function _compile_file($resource_name, $source_content, &$compiled_content)
228 if ($this->security) {
229 // do not allow php syntax to be executed unless specified
230 if ($this->php_handling == SMARTY_PHP_ALLOW &&
231 !$this->security_settings['PHP_HANDLING']) {
232 $this->php_handling = SMARTY_PHP_PASSTHRU;
236 $this->_load_filters();
238 $this->_current_file = $resource_name;
239 $this->_current_line_no = 1;
240 $ldq = preg_quote($this->left_delimiter, '~');
241 $rdq = preg_quote($this->right_delimiter, '~');
243 // run template source through prefilter functions
244 if (count($this->_plugins['prefilter']) > 0) {
245 foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
246 if ($prefilter === false) continue;
247 if ($prefilter[3] || is_callable($prefilter[0])) {
248 $source_content = call_user_func_array($prefilter[0],
249 array($source_content, &$this));
250 $this->_plugins['prefilter'][$filter_name][3] = true;
252 $this->_trigger_fatal_error("[plugin] prefilter '$filter_name' is not implemented");
257 /* fetch all special blocks */
258 $search = "~{$ldq}\*(.*?)\*{$rdq}|{$ldq}\s*literal\s*{$rdq}(.*?){$ldq}\s*/literal\s*{$rdq}|{$ldq}\s*php\s*{$rdq}(.*?){$ldq}\s*/php\s*{$rdq}~s";
260 preg_match_all($search, $source_content, $match, PREG_SET_ORDER);
261 $this->_folded_blocks = $match;
262 reset($this->_folded_blocks);
264 /* replace special blocks by "{php}" */
265 $source_content = preg_replace($search.'e', "'"
266 . $this->_quote_replace($this->left_delimiter) . 'php'
267 . "' . str_repeat(\"\n\", substr_count('\\0', \"\n\")) .'"
268 . $this->_quote_replace($this->right_delimiter)
272 /* Gather all template tags. */
273 preg_match_all("~{$ldq}\s*(.*?)\s*{$rdq}~s", $source_content, $_match);
274 $template_tags = $_match[1];
275 /* Split content by template tags to obtain non-template content. */
276 $text_blocks = preg_split("~{$ldq}.*?{$rdq}~s", $source_content);
278 /* loop through text blocks */
279 for ($curr_tb = 0, $for_max = count($text_blocks); $curr_tb < $for_max; $curr_tb++) {
280 /* match anything resembling php tags */
281 if (preg_match_all('~(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?php[\"\']?)~is', $text_blocks[$curr_tb], $sp_match)) {
282 /* replace tags with placeholders to prevent recursive replacements */
283 $sp_match[1] = array_unique($sp_match[1]);
284 usort($sp_match[1], '_smarty_sort_length');
285 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) {
286 $text_blocks[$curr_tb] = str_replace($sp_match[1][$curr_sp],'%%%SMARTYSP'.$curr_sp.'%%%',$text_blocks[$curr_tb]);
288 /* process each one */
289 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) {
290 if ($this->php_handling == SMARTY_PHP_PASSTHRU) {
291 /* echo php contents */
292 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '<?php echo \''.str_replace("'", "\'", $sp_match[1][$curr_sp]).'\'; ?>'."\n", $text_blocks[$curr_tb]);
293 } else if ($this->php_handling == SMARTY_PHP_QUOTE) {
295 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', htmlspecialchars($sp_match[1][$curr_sp]), $text_blocks[$curr_tb]);
296 } else if ($this->php_handling == SMARTY_PHP_REMOVE) {
297 /* remove php tags */
298 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '', $text_blocks[$curr_tb]);
300 /* SMARTY_PHP_ALLOW, but echo non php starting tags */
301 $sp_match[1][$curr_sp] = preg_replace('~(<\?(?!php|=|$))~i', '<?php echo \'\\1\'?>'."\n", $sp_match[1][$curr_sp]);
302 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', $sp_match[1][$curr_sp], $text_blocks[$curr_tb]);
308 /* Compile the template tags into PHP code. */
309 $compiled_tags = array();
310 for ($i = 0, $for_max = count($template_tags); $i < $for_max; $i++) {
311 $this->_current_line_no += substr_count($text_blocks[$i], "\n");
312 $compiled_tags[] = $this->_compile_tag($template_tags[$i]);
313 $this->_current_line_no += substr_count($template_tags[$i], "\n");
315 if (count($this->_tag_stack)>0) {
316 list($_open_tag, $_line_no) = end($this->_tag_stack);
317 $this->_syntax_error("unclosed tag \{$_open_tag} (opened line $_line_no).", E_USER_ERROR, __FILE__, __LINE__);
321 /* Reformat $text_blocks between 'strip' and '/strip' tags,
322 removing spaces, tabs and newlines. */
324 for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) {
325 if ($compiled_tags[$i] == '{strip}') {
326 $compiled_tags[$i] = '';
328 /* remove leading whitespaces */
329 $text_blocks[$i + 1] = ltrim($text_blocks[$i + 1]);
332 /* strip all $text_blocks before the next '/strip' */
333 for ($j = $i + 1; $j < $for_max; $j++) {
334 /* remove leading and trailing whitespaces of each line */
335 $text_blocks[$j] = preg_replace('![\t ]*[\r\n]+[\t ]*!', '', $text_blocks[$j]);
336 if ($compiled_tags[$j] == '{/strip}') {
337 /* remove trailing whitespaces from the last text_block */
338 $text_blocks[$j] = rtrim($text_blocks[$j]);
340 $text_blocks[$j] = "<?php echo '" . strtr($text_blocks[$j], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>";
341 if ($compiled_tags[$j] == '{/strip}') {
342 $compiled_tags[$j] = "\n"; /* slurped by php, but necessary
343 if a newline is following the closing strip-tag */
351 $compiled_content = '';
353 /* Interleave the compiled contents and text blocks to get the final result. */
354 for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) {
355 if ($compiled_tags[$i] == '') {
356 // tag result empty, remove first newline from following text block
357 $text_blocks[$i+1] = preg_replace('~^(\r\n|\r|\n)~', '', $text_blocks[$i+1]);
359 $compiled_content .= $text_blocks[$i].$compiled_tags[$i];
361 $compiled_content .= $text_blocks[$i];
363 // remove \n from the end of the file, if any
364 if (strlen($compiled_content) && (substr($compiled_content, -1) == "\n") ) {
365 $compiled_content = substr($compiled_content, 0, -1);
368 if (!empty($this->_cache_serial)) {
369 $compiled_content = "<?php \$this->_cache_serials['".$this->_cache_include."'] = '".$this->_cache_serial."'; ?>" . $compiled_content;
372 // remove unnecessary close/open tags
373 $compiled_content = preg_replace('~\?>\n?<\?php~', '', $compiled_content);
375 // run compiled template through postfilter functions
376 if (count($this->_plugins['postfilter']) > 0) {
377 foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
378 if ($postfilter === false) continue;
379 if ($postfilter[3] || is_callable($postfilter[0])) {
380 $compiled_content = call_user_func_array($postfilter[0],
381 array($compiled_content, &$this));
382 $this->_plugins['postfilter'][$filter_name][3] = true;
384 $this->_trigger_fatal_error("Smarty plugin error: postfilter '$filter_name' is not implemented");
389 // put header at the top of the compiled template
390 $template_header = "<?php /* Smarty version ".$this->_version.", created on ".strftime("%Y-%m-%d %H:%M:%S")."\n";
391 $template_header .= " compiled from ".strtr(urlencode($resource_name), array('%2F'=>'/', '%3A'=>':'))." */ ?>\n";
393 /* Emit code to load needed plugins. */
394 $this->_plugins_code = '';
395 if (count($this->_plugin_info)) {
396 $_plugins_params = "array('plugins' => array(";
397 foreach ($this->_plugin_info as $plugin_type => $plugins) {
398 foreach ($plugins as $plugin_name => $plugin_info) {
399 $_plugins_params .= "array('$plugin_type', '$plugin_name', '" . strtr($plugin_info[0], array("'" => "\\'", "\\" => "\\\\")) . "', $plugin_info[1], ";
400 $_plugins_params .= $plugin_info[2] ? 'true),' : 'false),';
403 $_plugins_params .= '))';
404 $plugins_code = "<?php require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');\nsmarty_core_load_plugins($_plugins_params, \$this); ?>\n";
405 $template_header .= $plugins_code;
406 $this->_plugin_info = array();
407 $this->_plugins_code = $plugins_code;
410 if ($this->_init_smarty_vars) {
411 $template_header .= "<?php require_once(SMARTY_CORE_DIR . 'core.assign_smarty_interface.php');\nsmarty_core_assign_smarty_interface(null, \$this); ?>\n";
412 $this->_init_smarty_vars = false;
415 $compiled_content = $template_header . $compiled_content;
420 * Compile a template tag
422 * @param string $template_tag
425 function _compile_tag($template_tag)
427 /* Matched comment. */
428 if (substr($template_tag, 0, 1) == '*' && substr($template_tag, -1) == '*')
431 /* Split tag into two three parts: command, command modifiers and the arguments. */
432 if(! preg_match('~^(?:(' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp
433 . '|\/?' . $this->_reg_obj_regexp . '|\/?' . $this->_func_regexp . ')(' . $this->_mod_regexp . '*))
435 ~xs', $template_tag, $match)) {
436 $this->_syntax_error("unrecognized tag: $template_tag", E_USER_ERROR, __FILE__, __LINE__);
439 $tag_command = $match[1];
440 $tag_modifier = isset($match[2]) ? $match[2] : null;
441 $tag_args = isset($match[3]) ? $match[3] : null;
443 if (preg_match('~^' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '$~', $tag_command)) {
444 /* tag name is a variable or object */
445 $_return = $this->_parse_var_props($tag_command . $tag_modifier);
446 return "<?php echo $_return; ?>" . $this->_additional_newline;
449 /* If the tag name is a registered object, we process it. */
450 if (preg_match('~^\/?' . $this->_reg_obj_regexp . '$~', $tag_command)) {
451 return $this->_compile_registered_object_tag($tag_command, $this->_parse_attrs($tag_args), $tag_modifier);
454 switch ($tag_command) {
456 return $this->_compile_include_tag($tag_args);
459 return $this->_compile_include_php_tag($tag_args);
462 $this->_push_tag('if');
463 return $this->_compile_if_tag($tag_args);
466 list($_open_tag) = end($this->_tag_stack);
467 if ($_open_tag != 'if' && $_open_tag != 'elseif')
468 $this->_syntax_error('unexpected {else}', E_USER_ERROR, __FILE__, __LINE__);
470 $this->_push_tag('else');
471 return '<?php else: ?>';
474 list($_open_tag) = end($this->_tag_stack);
475 if ($_open_tag != 'if' && $_open_tag != 'elseif')
476 $this->_syntax_error('unexpected {elseif}', E_USER_ERROR, __FILE__, __LINE__);
477 if ($_open_tag == 'if')
478 $this->_push_tag('elseif');
479 return $this->_compile_if_tag($tag_args, true);
482 $this->_pop_tag('if');
483 return '<?php endif; ?>';
486 return $this->_compile_capture_tag(true, $tag_args);
489 return $this->_compile_capture_tag(false);
492 return $this->left_delimiter;
495 return $this->right_delimiter;
498 $this->_push_tag('section');
499 return $this->_compile_section_start($tag_args);
502 $this->_push_tag('sectionelse');
503 return "<?php endfor; else: ?>";
507 $_open_tag = $this->_pop_tag('section');
508 if ($_open_tag == 'sectionelse')
509 return "<?php endif; ?>";
511 return "<?php endfor; endif; ?>";
514 $this->_push_tag('foreach');
515 return $this->_compile_foreach_start($tag_args);
519 $this->_push_tag('foreachelse');
520 return "<?php endforeach; else: ?>";
523 $_open_tag = $this->_pop_tag('foreach');
524 if ($_open_tag == 'foreachelse')
525 return "<?php endif; unset(\$_from); ?>";
527 return "<?php endforeach; endif; unset(\$_from); ?>";
532 if (substr($tag_command, 0, 1)=='/') {
533 $this->_pop_tag('strip');
534 if (--$this->_strip_depth==0) { /* outermost closing {/strip} */
535 $this->_additional_newline = "\n";
536 return '{' . $tag_command . '}';
539 $this->_push_tag('strip');
540 if ($this->_strip_depth++==0) { /* outermost opening {strip} */
541 $this->_additional_newline = "";
542 return '{' . $tag_command . '}';
548 /* handle folded tags replaced by {php} */
549 list(, $block) = each($this->_folded_blocks);
550 $this->_current_line_no += substr_count($block[0], "\n");
551 /* the number of matched elements in the regexp in _compile_file()
552 determins the type of folded tag that was found */
553 switch (count($block)) {
554 case 2: /* comment */
557 case 3: /* literal */
558 return "<?php echo '" . strtr($block[2], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>" . $this->_additional_newline;
561 if ($this->security && !$this->security_settings['PHP_TAGS']) {
562 $this->_syntax_error("(secure mode) php tags not permitted", E_USER_WARNING, __FILE__, __LINE__);
565 return '<?php ' . $block[3] .' ?>';
570 return $this->_compile_insert_tag($tag_args);
573 if ($this->_compile_compiler_tag($tag_command, $tag_args, $output)) {
575 } else if ($this->_compile_block_tag($tag_command, $tag_args, $tag_modifier, $output)) {
577 } else if ($this->_compile_custom_tag($tag_command, $tag_args, $tag_modifier, $output)) {
580 $this->_syntax_error("unrecognized tag '$tag_command'", E_USER_ERROR, __FILE__, __LINE__);
588 * compile the custom compiler tag
590 * sets $output to the compiled custom compiler tag
591 * @param string $tag_command
592 * @param string $tag_args
593 * @param string $output
596 function _compile_compiler_tag($tag_command, $tag_args, &$output)
599 $have_function = true;
602 * First we check if the compiler function has already been registered
603 * or loaded from a plugin file.
605 if (isset($this->_plugins['compiler'][$tag_command])) {
607 $plugin_func = $this->_plugins['compiler'][$tag_command][0];
608 if (!is_callable($plugin_func)) {
609 $message = "compiler function '$tag_command' is not implemented";
610 $have_function = false;
614 * Otherwise we need to load plugin file and look for the function
617 else if ($plugin_file = $this->_get_plugin_filepath('compiler', $tag_command)) {
620 include_once $plugin_file;
622 $plugin_func = 'smarty_compiler_' . $tag_command;
623 if (!is_callable($plugin_func)) {
624 $message = "plugin function $plugin_func() not found in $plugin_file\n";
625 $have_function = false;
627 $this->_plugins['compiler'][$tag_command] = array($plugin_func, null, null, null, true);
632 * True return value means that we either found a plugin or a
633 * dynamically registered function. False means that we didn't and the
634 * compiler should now emit code to load custom function plugin for this
638 if ($have_function) {
639 $output = call_user_func_array($plugin_func, array($tag_args, &$this));
641 $output = '<?php ' . $this->_push_cacheable_state('compiler', $tag_command)
643 . $this->_pop_cacheable_state('compiler', $tag_command) . ' ?>';
646 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
656 * compile block function tag
658 * sets $output to compiled block function tag
659 * @param string $tag_command
660 * @param string $tag_args
661 * @param string $tag_modifier
662 * @param string $output
665 function _compile_block_tag($tag_command, $tag_args, $tag_modifier, &$output)
667 if (substr($tag_command, 0, 1) == '/') {
669 $tag_command = substr($tag_command, 1);
674 $have_function = true;
677 * First we check if the block function has already been registered
678 * or loaded from a plugin file.
680 if (isset($this->_plugins['block'][$tag_command])) {
682 $plugin_func = $this->_plugins['block'][$tag_command][0];
683 if (!is_callable($plugin_func)) {
684 $message = "block function '$tag_command' is not implemented";
685 $have_function = false;
689 * Otherwise we need to load plugin file and look for the function
692 else if ($plugin_file = $this->_get_plugin_filepath('block', $tag_command)) {
695 include_once $plugin_file;
697 $plugin_func = 'smarty_block_' . $tag_command;
698 if (!function_exists($plugin_func)) {
699 $message = "plugin function $plugin_func() not found in $plugin_file\n";
700 $have_function = false;
702 $this->_plugins['block'][$tag_command] = array($plugin_func, null, null, null, true);
709 } else if (!$have_function) {
710 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
715 * Even though we've located the plugin function, compilation
716 * happens only once, so the plugin will still need to be loaded
717 * at runtime for future requests.
719 $this->_add_plugin('block', $tag_command);
722 $this->_push_tag($tag_command);
724 $this->_pop_tag($tag_command);
727 $output = '<?php ' . $this->_push_cacheable_state('block', $tag_command);
728 $attrs = $this->_parse_attrs($tag_args);
729 $arg_list = $this->_compile_arg_list('block', $tag_command, $attrs, $_cache_attrs='');
730 $output .= "$_cache_attrs\$this->_tag_stack[] = array('$tag_command', array(".implode(',', $arg_list).')); ';
731 $output .= $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], null, $this, $_block_repeat=true);';
732 $output .= 'while ($_block_repeat) { ob_start(); ?>';
734 $output = '<?php $_block_content = ob_get_contents(); ob_end_clean(); ';
735 $_out_tag_text = $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], $_block_content, $this, $_block_repeat=false)';
736 if ($tag_modifier != '') {
737 $this->_parse_modifiers($_out_tag_text, $tag_modifier);
739 $output .= 'echo '.$_out_tag_text.'; } ';
740 $output .= " array_pop(\$this->_tag_stack); " . $this->_pop_cacheable_state('block', $tag_command) . '?>';
748 * compile custom function tag
750 * @param string $tag_command
751 * @param string $tag_args
752 * @param string $tag_modifier
755 function _compile_custom_tag($tag_command, $tag_args, $tag_modifier, &$output)
758 $have_function = true;
761 * First we check if the custom function has already been registered
762 * or loaded from a plugin file.
764 if (isset($this->_plugins['function'][$tag_command])) {
766 $plugin_func = $this->_plugins['function'][$tag_command][0];
767 if (!is_callable($plugin_func)) {
768 $message = "custom function '$tag_command' is not implemented";
769 $have_function = false;
773 * Otherwise we need to load plugin file and look for the function
776 else if ($plugin_file = $this->_get_plugin_filepath('function', $tag_command)) {
779 include_once $plugin_file;
781 $plugin_func = 'smarty_function_' . $tag_command;
782 if (!function_exists($plugin_func)) {
783 $message = "plugin function $plugin_func() not found in $plugin_file\n";
784 $have_function = false;
786 $this->_plugins['function'][$tag_command] = array($plugin_func, null, null, null, true);
793 } else if (!$have_function) {
794 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
798 /* declare plugin to be loaded on display of the template that
799 we compile right now */
800 $this->_add_plugin('function', $tag_command);
802 $_cacheable_state = $this->_push_cacheable_state('function', $tag_command);
803 $attrs = $this->_parse_attrs($tag_args);
804 $arg_list = $this->_compile_arg_list('function', $tag_command, $attrs, $_cache_attrs='');
806 $output = $this->_compile_plugin_call('function', $tag_command).'(array('.implode(',', $arg_list)."), \$this)";
807 if($tag_modifier != '') {
808 $this->_parse_modifiers($output, $tag_modifier);
812 $output = '<?php ' . $_cacheable_state . $_cache_attrs . 'echo ' . $output . ';'
813 . $this->_pop_cacheable_state('function', $tag_command) . "?>" . $this->_additional_newline;
820 * compile a registered object tag
822 * @param string $tag_command
823 * @param array $attrs
824 * @param string $tag_modifier
827 function _compile_registered_object_tag($tag_command, $attrs, $tag_modifier)
829 if (substr($tag_command, 0, 1) == '/') {
831 $tag_command = substr($tag_command, 1);
836 list($object, $obj_comp) = explode('->', $tag_command);
840 $_assign_var = false;
841 foreach ($attrs as $arg_name => $arg_value) {
842 if($arg_name == 'assign') {
843 $_assign_var = $arg_value;
844 unset($attrs['assign']);
847 if (is_bool($arg_value))
848 $arg_value = $arg_value ? 'true' : 'false';
849 $arg_list[] = "'$arg_name' => $arg_value";
853 if($this->_reg_objects[$object][2]) {
854 // smarty object argument format
855 $args = "array(".implode(',', (array)$arg_list)."), \$this";
857 // traditional argument format
858 $args = implode(',', array_values($attrs));
867 if(!is_object($this->_reg_objects[$object][0])) {
868 $this->_trigger_fatal_error("registered '$object' is not an object" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
869 } elseif(!empty($this->_reg_objects[$object][1]) && !in_array($obj_comp, $this->_reg_objects[$object][1])) {
870 $this->_trigger_fatal_error("'$obj_comp' is not a registered component of object '$object'", $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
871 } elseif(method_exists($this->_reg_objects[$object][0], $obj_comp)) {
873 if(in_array($obj_comp, $this->_reg_objects[$object][3])) {
876 $prefix = "\$this->_tag_stack[] = array('$obj_comp', $args); ";
877 $prefix .= "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], null, \$this, \$_block_repeat=true); ";
878 $prefix .= "while (\$_block_repeat) { ob_start();";
882 $prefix = "\$_obj_block_content = ob_get_contents(); ob_end_clean(); ";
883 $return = "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], \$_obj_block_content, \$this, \$_block_repeat=false)";
884 $postfix = "} array_pop(\$this->_tag_stack);";
888 $return = "\$this->_reg_objects['$object'][0]->$obj_comp($args)";
892 $return = "\$this->_reg_objects['$object'][0]->$obj_comp";
895 if($return != null) {
896 if($tag_modifier != '') {
897 $this->_parse_modifiers($return, $tag_modifier);
900 if(!empty($_assign_var)) {
901 $output = "\$this->assign('" . $this->_dequote($_assign_var) ."', $return);";
903 $output = 'echo ' . $return . ';';
904 $newline = $this->_additional_newline;
910 return '<?php ' . $prefix . $output . $postfix . "?>" . $newline;
914 * Compile {insert ...} tag
916 * @param string $tag_args
919 function _compile_insert_tag($tag_args)
921 $attrs = $this->_parse_attrs($tag_args);
922 $name = $this->_dequote($attrs['name']);
925 $this->_syntax_error("missing insert name", E_USER_ERROR, __FILE__, __LINE__);
928 if (!empty($attrs['script'])) {
929 $delayed_loading = true;
931 $delayed_loading = false;
934 foreach ($attrs as $arg_name => $arg_value) {
935 if (is_bool($arg_value))
936 $arg_value = $arg_value ? 'true' : 'false';
937 $arg_list[] = "'$arg_name' => $arg_value";
940 $this->_add_plugin('insert', $name, $delayed_loading);
942 $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))";
944 return "<?php require_once(SMARTY_CORE_DIR . 'core.run_insert_handler.php');\necho smarty_core_run_insert_handler($_params, \$this); ?>" . $this->_additional_newline;
948 * Compile {include ...} tag
950 * @param string $tag_args
953 function _compile_include_tag($tag_args)
955 $attrs = $this->_parse_attrs($tag_args);
958 if (empty($attrs['file'])) {
959 $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR, __FILE__, __LINE__);
962 $theme_template = 'false';
963 foreach ($attrs as $arg_name => $arg_value) {
964 if ($arg_name == 'file') {
965 $include_file = $arg_value;
967 } else if ($arg_name == 'assign') {
968 $assign_var = $arg_value;
970 } else if ($arg_name == 'theme_template') {
971 $theme_template = $arg_value;
974 if (is_bool($arg_value))
975 $arg_value = $arg_value ? 'true' : 'false';
976 $arg_list[] = "'$arg_name' => $arg_value";
979 if ( $theme_template == 'true' )
980 $include_file = '"'.SugarThemeRegistry::current()->getTemplate(str_replace(array('"',"'"),'',$include_file)).'"';
984 if (isset($assign_var)) {
985 $output .= "ob_start();\n";
989 "\$_smarty_tpl_vars = \$this->_tpl_vars;\n";
992 $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))";
993 $output .= "\$this->_smarty_include($_params);\n" .
994 "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" .
995 "unset(\$_smarty_tpl_vars);\n";
997 if (isset($assign_var)) {
998 $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n";
1008 * Compile {include ...} tag
1010 * @param string $tag_args
1013 function _compile_include_php_tag($tag_args)
1015 $attrs = $this->_parse_attrs($tag_args);
1017 if (empty($attrs['file'])) {
1018 $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR, __FILE__, __LINE__);
1021 $assign_var = (empty($attrs['assign'])) ? '' : $this->_dequote($attrs['assign']);
1022 $once_var = (empty($attrs['once']) || $attrs['once']=='false') ? 'false' : 'true';
1024 $arg_list = array();
1025 foreach($attrs as $arg_name => $arg_value) {
1026 if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') {
1027 if(is_bool($arg_value))
1028 $arg_value = $arg_value ? 'true' : 'false';
1029 $arg_list[] = "'$arg_name' => $arg_value";
1033 $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', $arg_list)."))";
1035 return "<?php require_once(SMARTY_CORE_DIR . 'core.smarty_include_php.php');\nsmarty_core_smarty_include_php($_params, \$this); ?>" . $this->_additional_newline;
1040 * Compile {section ...} tag
1042 * @param string $tag_args
1045 function _compile_section_start($tag_args)
1047 $attrs = $this->_parse_attrs($tag_args);
1048 $arg_list = array();
1051 $section_name = $attrs['name'];
1052 if (empty($section_name)) {
1053 $this->_syntax_error("missing section name", E_USER_ERROR, __FILE__, __LINE__);
1056 $output .= "unset(\$this->_sections[$section_name]);\n";
1057 $section_props = "\$this->_sections[$section_name]";
1059 foreach ($attrs as $attr_name => $attr_value) {
1060 switch ($attr_name) {
1062 $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n";
1066 if (is_bool($attr_value))
1067 $show_attr_value = $attr_value ? 'true' : 'false';
1069 $show_attr_value = "(bool)$attr_value";
1070 $output .= "{$section_props}['show'] = $show_attr_value;\n";
1074 $output .= "{$section_props}['$attr_name'] = $attr_value;\n";
1079 $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n";
1083 $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n";
1087 $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR, __FILE__, __LINE__);
1092 if (!isset($attrs['show']))
1093 $output .= "{$section_props}['show'] = true;\n";
1095 if (!isset($attrs['loop']))
1096 $output .= "{$section_props}['loop'] = 1;\n";
1098 if (!isset($attrs['max']))
1099 $output .= "{$section_props}['max'] = {$section_props}['loop'];\n";
1101 $output .= "if ({$section_props}['max'] < 0)\n" .
1102 " {$section_props}['max'] = {$section_props}['loop'];\n";
1104 if (!isset($attrs['step']))
1105 $output .= "{$section_props}['step'] = 1;\n";
1107 if (!isset($attrs['start']))
1108 $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n";
1110 $output .= "if ({$section_props}['start'] < 0)\n" .
1111 " {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" .
1113 " {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n";
1116 $output .= "if ({$section_props}['show']) {\n";
1117 if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) {
1118 $output .= " {$section_props}['total'] = {$section_props}['loop'];\n";
1120 $output .= " {$section_props}['total'] = min(ceil(({$section_props}['step'] > 0 ? {$section_props}['loop'] - {$section_props}['start'] : {$section_props}['start']+1)/abs({$section_props}['step'])), {$section_props}['max']);\n";
1122 $output .= " if ({$section_props}['total'] == 0)\n" .
1123 " {$section_props}['show'] = false;\n" .
1125 " {$section_props}['total'] = 0;\n";
1127 $output .= "if ({$section_props}['show']):\n";
1129 for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1;
1130 {$section_props}['iteration'] <= {$section_props}['total'];
1131 {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n";
1132 $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n";
1133 $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n";
1134 $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n";
1135 $output .= "{$section_props}['first'] = ({$section_props}['iteration'] == 1);\n";
1136 $output .= "{$section_props}['last'] = ({$section_props}['iteration'] == {$section_props}['total']);\n";
1145 * Compile {foreach ...} tag.
1147 * @param string $tag_args
1150 function _compile_foreach_start($tag_args)
1152 $attrs = $this->_parse_attrs($tag_args);
1153 $arg_list = array();
1155 if (empty($attrs['from'])) {
1156 return $this->_syntax_error("foreach: missing 'from' attribute", E_USER_ERROR, __FILE__, __LINE__);
1158 $from = $attrs['from'];
1160 if (empty($attrs['item'])) {
1161 return $this->_syntax_error("foreach: missing 'item' attribute", E_USER_ERROR, __FILE__, __LINE__);
1163 $item = $this->_dequote($attrs['item']);
1164 if (!preg_match('~^\w+$~', $item)) {
1165 return $this->_syntax_error("'foreach: item' must be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1168 if (isset($attrs['key'])) {
1169 $key = $this->_dequote($attrs['key']);
1170 if (!preg_match('~^\w+$~', $key)) {
1171 return $this->_syntax_error("foreach: 'key' must to be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1173 $key_part = "\$this->_tpl_vars['$key'] => ";
1179 if (isset($attrs['name'])) {
1180 $name = $attrs['name'];
1186 $output .= "\$_from = $from; if (!is_array(\$_from) && !is_object(\$_from)) { settype(\$_from, 'array'); }";
1188 $foreach_props = "\$this->_foreach[$name]";
1189 $output .= "{$foreach_props} = array('total' => count(\$_from), 'iteration' => 0);\n";
1190 $output .= "if ({$foreach_props}['total'] > 0):\n";
1191 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1192 $output .= " {$foreach_props}['iteration']++;\n";
1194 $output .= "if (count(\$_from)):\n";
1195 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1204 * Compile {capture} .. {/capture} tags
1206 * @param boolean $start true if this is the {capture} tag
1207 * @param string $tag_args
1211 function _compile_capture_tag($start, $tag_args = '')
1213 $attrs = $this->_parse_attrs($tag_args);
1216 if (isset($attrs['name']))
1217 $buffer = $attrs['name'];
1219 $buffer = "'default'";
1221 if (isset($attrs['assign']))
1222 $assign = $attrs['assign'];
1225 $output = "<?php ob_start(); ?>";
1226 $this->_capture_stack[] = array($buffer, $assign);
1228 list($buffer, $assign) = array_pop($this->_capture_stack);
1229 $output = "<?php \$this->_smarty_vars['capture'][$buffer] = ob_get_contents(); ";
1230 if (isset($assign)) {
1231 $output .= " \$this->assign($assign, ob_get_contents());";
1233 $output .= "ob_end_clean(); ?>";
1240 * Compile {if ...} tag
1242 * @param string $tag_args
1243 * @param boolean $elseif if true, uses elseif instead of if
1246 function _compile_if_tag($tag_args, $elseif = false)
1249 /* Tokenize args for 'if' tag. */
1250 preg_match_all('~(?>
1251 ' . $this->_obj_call_regexp . '(?:' . $this->_mod_regexp . '*)? | # valid object call
1252 ' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)? | # var or quoted string
1253 \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@ | # valid non-word token
1254 \b\w+\b | # valid word token
1256 )~x', $tag_args, $match);
1258 $tokens = $match[0];
1260 if(empty($tokens)) {
1261 $_error_msg .= $elseif ? "'elseif'" : "'if'";
1262 $_error_msg .= ' statement requires arguments';
1263 $this->_syntax_error($_error_msg, E_USER_ERROR, __FILE__, __LINE__);
1267 // make sure we have balanced parenthesis
1268 $token_count = array_count_values($tokens);
1269 if(isset($token_count['(']) && $token_count['('] != $token_count[')']) {
1270 $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR, __FILE__, __LINE__);
1273 $is_arg_stack = array();
1275 for ($i = 0; $i < count($tokens); $i++) {
1277 $token = &$tokens[$i];
1279 switch (strtolower($token)) {
1352 array_push($is_arg_stack, $i);
1356 /* If last token was a ')', we operate on the parenthesized
1357 expression. The start of the expression is on the stack.
1358 Otherwise, we operate on the last encountered token. */
1359 if ($tokens[$i-1] == ')')
1360 $is_arg_start = array_pop($is_arg_stack);
1362 $is_arg_start = $i-1;
1363 /* Construct the argument for 'is' expression, so it knows
1364 what to operate on. */
1365 $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
1367 /* Pass all tokens from next one until the end to the
1368 'is' expression parsing function. The function will
1369 return modified tokens, where the first one is the result
1370 of the 'is' expression and the rest are the tokens it
1372 $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));
1374 /* Replace the old tokens with the new ones. */
1375 array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);
1377 /* Adjust argument start so that it won't change from the
1378 current position for the next iteration. */
1383 if(preg_match('~^' . $this->_func_regexp . '$~', $token) ) {
1385 if($this->security &&
1386 !in_array($token, $this->security_settings['IF_FUNCS'])) {
1387 $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);
1389 } elseif(preg_match('~^' . $this->_var_regexp . '$~', $token) && isset($tokens[$i+1]) && $tokens[$i+1] == '(') {
1390 // variable function call
1391 $this->_syntax_error("variable function call '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);
1392 } elseif(preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$~', $token)) {
1393 // object or variable
1394 $token = $this->_parse_var_props($token);
1395 } elseif(is_numeric($token)) {
1398 $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__);
1405 return '<?php elseif ('.implode(' ', $tokens).'): ?>';
1407 return '<?php if ('.implode(' ', $tokens).'): ?>';
1411 function _compile_arg_list($type, $name, $attrs, &$cache_code) {
1412 $arg_list = array();
1414 if (isset($type) && isset($name)
1415 && isset($this->_plugins[$type])
1416 && isset($this->_plugins[$type][$name])
1417 && empty($this->_plugins[$type][$name][4])
1418 && is_array($this->_plugins[$type][$name][5])
1420 /* we have a list of parameters that should be cached */
1421 $_cache_attrs = $this->_plugins[$type][$name][5];
1422 $_count = $this->_cache_attrs_count++;
1423 $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');";
1426 /* no parameters are cached */
1427 $_cache_attrs = null;
1430 foreach ($attrs as $arg_name => $arg_value) {
1431 if (is_bool($arg_value))
1432 $arg_value = $arg_value ? 'true' : 'false';
1433 if (is_null($arg_value))
1434 $arg_value = 'null';
1435 if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) {
1436 $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)";
1438 $arg_list[] = "'$arg_name' => $arg_value";
1445 * Parse is expression
1447 * @param string $is_arg
1448 * @param array $tokens
1451 function _parse_is_expr($is_arg, $tokens)
1454 $negate_expr = false;
1456 if (($first_token = array_shift($tokens)) == 'not') {
1457 $negate_expr = true;
1458 $expr_type = array_shift($tokens);
1460 $expr_type = $first_token;
1462 switch ($expr_type) {
1464 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1466 $expr_arg = $tokens[$expr_end++];
1467 $expr = "!(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1469 $expr = "!(1 & $is_arg)";
1473 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1475 $expr_arg = $tokens[$expr_end++];
1476 $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1478 $expr = "(1 & $is_arg)";
1482 if (@$tokens[$expr_end] == 'by') {
1484 $expr_arg = $tokens[$expr_end++];
1485 $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")";
1487 $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__);
1492 $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__);
1500 array_splice($tokens, 0, $expr_end, $expr);
1507 * Parse attribute string
1509 * @param string $tag_args
1512 function _parse_attrs($tag_args)
1515 /* Tokenize tag attributes. */
1516 preg_match_all('~(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+)
1519 ~x', $tag_args, $match);
1520 $tokens = $match[0];
1524 0 - expecting attribute name
1526 2 - expecting attribute value (not '=') */
1529 foreach ($tokens as $token) {
1532 /* If the token is a valid identifier, we set attribute name
1533 and go to state 1. */
1534 if (preg_match('~^\w+$~', $token)) {
1535 $attr_name = $token;
1538 $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__);
1542 /* If the token is '=', then we go to state 2. */
1543 if ($token == '=') {
1546 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1550 /* If token is not '=', we set the attribute value and go to
1552 if ($token != '=') {
1553 /* We booleanize the token if it's a non-quoted possible
1555 if (preg_match('~^(on|yes|true)$~', $token)) {
1557 } else if (preg_match('~^(off|no|false)$~', $token)) {
1559 } else if ($token == 'null') {
1561 } else if (preg_match('~^' . $this->_num_const_regexp . '|0[xX][0-9a-fA-F]+$~', $token)) {
1562 /* treat integer literally */
1563 } else if (!preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$~', $token)) {
1564 /* treat as a string, double-quote it escaping quotes */
1565 $token = '"'.addslashes($token).'"';
1568 $attrs[$attr_name] = $token;
1571 $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__);
1574 $last_token = $token;
1579 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1581 $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__);
1585 $this->_parse_vars_props($attrs);
1591 * compile multiple variables and section properties tokens into
1594 * @param array $tokens
1596 function _parse_vars_props(&$tokens)
1598 foreach($tokens as $key => $val) {
1599 $tokens[$key] = $this->_parse_var_props($val);
1604 * compile single variable and section properties token into
1607 * @param string $val
1608 * @param string $tag_attrs
1611 function _parse_var_props($val)
1615 if(preg_match('~^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$~', $val, $match)) {
1616 // $ variable or object
1617 $return = $this->_parse_var($match[1]);
1618 $modifiers = $match[2];
1619 if (!empty($this->default_modifiers) && !preg_match('~(^|\|)smarty:nodefaults($|\|)~',$modifiers)) {
1620 $_default_mod_string = implode('|',(array)$this->default_modifiers);
1621 $modifiers = empty($modifiers) ? $_default_mod_string : $_default_mod_string . '|' . $modifiers;
1623 $this->_parse_modifiers($return, $modifiers);
1625 } elseif (preg_match('~^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1626 // double quoted text
1627 preg_match('~^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1628 $return = $this->_expand_quoted_text($match[1]);
1629 if($match[2] != '') {
1630 $this->_parse_modifiers($return, $match[2]);
1634 elseif(preg_match('~^' . $this->_num_const_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1635 // numerical constant
1636 preg_match('~^(' . $this->_num_const_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1637 if($match[2] != '') {
1638 $this->_parse_modifiers($match[1], $match[2]);
1642 elseif(preg_match('~^' . $this->_si_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1643 // single quoted text
1644 preg_match('~^(' . $this->_si_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1645 if($match[2] != '') {
1646 $this->_parse_modifiers($match[1], $match[2]);
1650 elseif(preg_match('~^' . $this->_cvar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1652 return $this->_parse_conf_var($val);
1654 elseif(preg_match('~^' . $this->_svar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1656 return $this->_parse_section_prop($val);
1658 elseif(!in_array($val, $this->_permitted_tokens) && !is_numeric($val)) {
1660 return $this->_expand_quoted_text('"' . strtr($val, array('\\' => '\\\\', '"' => '\\"')) .'"');
1666 * expand quoted text with embedded variables
1668 * @param string $var_expr
1671 function _expand_quoted_text($var_expr)
1673 // if contains unescaped $, expand it
1674 if(preg_match_all('~(?:\`(?<!\\\\)\$' . $this->_dvar_guts_regexp . '(?:' . $this->_obj_ext_regexp . ')*\`)|(?:(?<!\\\\)\$\w+(\[[a-zA-Z0-9]+\])*)~', $var_expr, $_match)) {
1675 $_match = $_match[0];
1678 foreach($_match as $_var) {
1679 $var_expr = str_replace ($_var, '".(' . $this->_parse_var(str_replace('`','',$_var)) . ')."', $var_expr);
1681 $_return = preg_replace('~\.""|(?<!\\\\)""\.~', '', $var_expr);
1683 $_return = $var_expr;
1685 // replace double quoted literal string with single quotes
1686 $_return = preg_replace('~^"([\s\w]+)"$~',"'\\1'",$_return);
1691 * parse variable expression into PHP code
1693 * @param string $var_expr
1694 * @param string $output
1697 function _parse_var($var_expr)
1700 $_math_vars = preg_split('~('.$this->_dvar_math_regexp.'|'.$this->_qstr_regexp.')~', $var_expr, -1, PREG_SPLIT_DELIM_CAPTURE);
1702 if(count($_math_vars) > 1) {
1704 $_complete_var = "";
1706 // simple check if there is any math, to stop recursion (due to modifiers with "xx % yy" as parameter)
1707 foreach($_math_vars as $_k => $_math_var) {
1708 $_math_var = $_math_vars[$_k];
1710 if(!empty($_math_var) || is_numeric($_math_var)) {
1711 // hit a math operator, so process the stuff which came before it
1712 if(preg_match('~^' . $this->_dvar_math_regexp . '$~', $_math_var)) {
1714 if(!empty($_complete_var) || is_numeric($_complete_var)) {
1715 $_output .= $this->_parse_var($_complete_var);
1718 // just output the math operator to php
1719 $_output .= $_math_var;
1721 if(empty($_first_var))
1722 $_first_var = $_complete_var;
1724 $_complete_var = "";
1726 $_complete_var .= $_math_var;
1731 if(!empty($_complete_var) || is_numeric($_complete_var))
1732 $_output .= $this->_parse_var($_complete_var);
1734 // get the modifiers working (only the last var from math + modifier is left)
1735 $var_expr = $_complete_var;
1739 // prevent cutting of first digit in the number (we _definitly_ got a number if the first char is a digit)
1740 if(is_numeric(substr($var_expr, 0, 1)))
1741 $_var_ref = $var_expr;
1743 $_var_ref = substr($var_expr, 1);
1747 // get [foo] and .foo and ->foo and (...) pieces
1748 preg_match_all('~(?:^\w+)|' . $this->_obj_params_regexp . '|(?:' . $this->_var_bracket_regexp . ')|->\$?\w+|\.\$?\w+|\S+~', $_var_ref, $match);
1750 $_indexes = $match[0];
1751 $_var_name = array_shift($_indexes);
1753 /* Handle $smarty.* variable references as a special case. */
1754 if ($_var_name == 'smarty') {
1756 * If the reference could be compiled, use the compiled output;
1757 * otherwise, fall back on the $smarty variable generated at
1760 if (($smarty_ref = $this->_compile_smarty_ref($_indexes)) !== null) {
1761 $_output = $smarty_ref;
1763 $_var_name = substr(array_shift($_indexes), 1);
1764 $_output = "\$this->_smarty_vars['$_var_name']";
1766 } elseif(is_numeric($_var_name) && is_numeric(substr($var_expr, 0, 1))) {
1767 // because . is the operator for accessing arrays thru inidizes we need to put it together again for floating point numbers
1768 if(count($_indexes) > 0)
1770 $_var_name .= implode("", $_indexes);
1771 $_indexes = array();
1773 $_output = $_var_name;
1775 $_output = "\$this->_tpl_vars['$_var_name']";
1778 foreach ($_indexes as $_index) {
1779 if (substr($_index, 0, 1) == '[') {
1780 $_index = substr($_index, 1, -1);
1781 if (is_numeric($_index)) {
1782 $_output .= "[$_index]";
1783 } elseif (substr($_index, 0, 1) == '$') {
1784 if (strpos($_index, '.') !== false) {
1785 $_output .= '[' . $this->_parse_var($_index) . ']';
1787 $_output .= "[\$this->_tpl_vars['" . substr($_index, 1) . "']]";
1790 $_var_parts = explode('.', $_index);
1791 $_var_section = $_var_parts[0];
1792 $_var_section_prop = isset($_var_parts[1]) ? $_var_parts[1] : 'index';
1793 $_output .= "[\$this->_sections['$_var_section']['$_var_section_prop']]";
1795 } else if (substr($_index, 0, 1) == '.') {
1796 if (substr($_index, 1, 1) == '$')
1797 $_output .= "[\$this->_tpl_vars['" . substr($_index, 2) . "']]";
1799 $_output .= "['" . substr($_index, 1) . "']";
1800 } else if (substr($_index,0,2) == '->') {
1801 if(substr($_index,2,2) == '__') {
1802 $this->_syntax_error('call to internal object members is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1803 } elseif($this->security && substr($_index, 2, 1) == '_') {
1804 $this->_syntax_error('(secure) call to private object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1805 } elseif (substr($_index, 2, 1) == '$') {
1806 if ($this->security) {
1807 $this->_syntax_error('(secure) call to dynamic object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1809 $_output .= '->{(($_var=$this->_tpl_vars[\''.substr($_index,3).'\']) && substr($_var,0,2)!=\'__\') ? $_var : $this->trigger_error("cannot access property \\"$_var\\"")}';
1812 $_output .= $_index;
1814 } elseif (substr($_index, 0, 1) == '(') {
1815 $_index = $this->_parse_parenth_args($_index);
1816 $_output .= $_index;
1818 $_output .= $_index;
1827 * parse arguments in function call parenthesis
1829 * @param string $parenth_args
1832 function _parse_parenth_args($parenth_args)
1834 preg_match_all('~' . $this->_param_regexp . '~',$parenth_args, $match);
1835 $orig_vals = $match = $match[0];
1836 $this->_parse_vars_props($match);
1838 for ($i = 0, $count = count($match); $i < $count; $i++) {
1839 $replace[$orig_vals[$i]] = $match[$i];
1841 return strtr($parenth_args, $replace);
1845 * parse configuration variable expression into PHP code
1847 * @param string $conf_var_expr
1849 function _parse_conf_var($conf_var_expr)
1851 $parts = explode('|', $conf_var_expr, 2);
1852 $var_ref = $parts[0];
1853 $modifiers = isset($parts[1]) ? $parts[1] : '';
1855 $var_name = substr($var_ref, 1, -1);
1857 $output = "\$this->_config[0]['vars']['$var_name']";
1859 $this->_parse_modifiers($output, $modifiers);
1865 * parse section property expression into PHP code
1867 * @param string $section_prop_expr
1870 function _parse_section_prop($section_prop_expr)
1872 $parts = explode('|', $section_prop_expr, 2);
1873 $var_ref = $parts[0];
1874 $modifiers = isset($parts[1]) ? $parts[1] : '';
1876 preg_match('!%(\w+)\.(\w+)%!', $var_ref, $match);
1877 $section_name = $match[1];
1878 $prop_name = $match[2];
1880 $output = "\$this->_sections['$section_name']['$prop_name']";
1882 $this->_parse_modifiers($output, $modifiers);
1889 * parse modifier chain into PHP code
1891 * sets $output to parsed modified chain
1892 * @param string $output
1893 * @param string $modifier_string
1895 function _parse_modifiers(&$output, $modifier_string)
1897 preg_match_all('~\|(@?\w+)((?>:(?:'. $this->_qstr_regexp . '|[^|]+))*)~', '|' . $modifier_string, $_match);
1898 list(, $_modifiers, $modifier_arg_strings) = $_match;
1900 for ($_i = 0, $_for_max = count($_modifiers); $_i < $_for_max; $_i++) {
1901 $_modifier_name = $_modifiers[$_i];
1903 if($_modifier_name == 'smarty') {
1904 // skip smarty modifier
1908 preg_match_all('~:(' . $this->_qstr_regexp . '|[^:]+)~', $modifier_arg_strings[$_i], $_match);
1909 $_modifier_args = $_match[1];
1911 if (substr($_modifier_name, 0, 1) == '@') {
1912 $_map_array = false;
1913 $_modifier_name = substr($_modifier_name, 1);
1918 if (empty($this->_plugins['modifier'][$_modifier_name])
1919 && !$this->_get_plugin_filepath('modifier', $_modifier_name)
1920 && function_exists($_modifier_name)) {
1921 if ($this->security && !in_array($_modifier_name, $this->security_settings['MODIFIER_FUNCS'])) {
1922 $this->_trigger_fatal_error("[plugin] (secure mode) modifier '$_modifier_name' is not allowed" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
1924 $this->_plugins['modifier'][$_modifier_name] = array($_modifier_name, null, null, false);
1927 $this->_add_plugin('modifier', $_modifier_name);
1929 $this->_parse_vars_props($_modifier_args);
1931 if($_modifier_name == 'default') {
1932 // supress notifications of default modifier vars and args
1933 if(substr($output, 0, 1) == '$') {
1934 $output = '@' . $output;
1936 if(isset($_modifier_args[0]) && substr($_modifier_args[0], 0, 1) == '$') {
1937 $_modifier_args[0] = '@' . $_modifier_args[0];
1940 if (count($_modifier_args) > 0)
1941 $_modifier_args = ', '.implode(', ', $_modifier_args);
1943 $_modifier_args = '';
1946 $output = "((is_array(\$_tmp=$output)) ? \$this->_run_mod_handler('$_modifier_name', true, \$_tmp$_modifier_args) : " . $this->_compile_plugin_call('modifier', $_modifier_name) . "(\$_tmp$_modifier_args))";
1950 $output = $this->_compile_plugin_call('modifier', $_modifier_name)."($output$_modifier_args)";
1960 * @param string $type
1961 * @param string $name
1962 * @param boolean? $delayed_loading
1964 function _add_plugin($type, $name, $delayed_loading = null)
1966 if (!isset($this->_plugin_info[$type])) {
1967 $this->_plugin_info[$type] = array();
1969 if (!isset($this->_plugin_info[$type][$name])) {
1970 $this->_plugin_info[$type][$name] = array($this->_current_file,
1971 $this->_current_line_no,
1978 * Compiles references of type $smarty.foo
1980 * @param string $indexes
1983 function _compile_smarty_ref(&$indexes)
1985 /* Extract the reference name. */
1986 $_ref = substr($indexes[0], 1);
1987 foreach($indexes as $_index_no=>$_index) {
1988 if (substr($_index, 0, 1) != '.' && $_index_no<2 || !preg_match('~^(\.|\[|->)~', $_index)) {
1989 $this->_syntax_error('$smarty' . implode('', array_slice($indexes, 0, 2)) . ' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
1995 $compiled_ref = 'time()';
2000 array_shift($indexes);
2001 $_var = $this->_parse_var_props(substr($indexes[0], 1));
2002 $_propname = substr($indexes[1], 1);
2004 switch ($_propname) {
2006 array_shift($indexes);
2007 $compiled_ref = "(\$this->_foreach[$_var]['iteration']-1)";
2011 array_shift($indexes);
2012 $compiled_ref = "(\$this->_foreach[$_var]['iteration'] <= 1)";
2016 array_shift($indexes);
2017 $compiled_ref = "(\$this->_foreach[$_var]['iteration'] == \$this->_foreach[$_var]['total'])";
2021 array_shift($indexes);
2022 $compiled_ref = "(\$this->_foreach[$_var]['total'] > 0)";
2027 $compiled_ref = "\$this->_foreach[$_var]";
2032 array_shift($indexes);
2033 $_var = $this->_parse_var_props(substr($indexes[0], 1));
2034 $compiled_ref = "\$this->_sections[$_var]";
2038 $compiled_ref = ($this->request_use_auto_globals) ? '$_GET' : "\$GLOBALS['HTTP_GET_VARS']";
2042 $compiled_ref = ($this->request_use_auto_globals) ? '$_POST' : "\$GLOBALS['HTTP_POST_VARS']";
2046 $compiled_ref = ($this->request_use_auto_globals) ? '$_COOKIE' : "\$GLOBALS['HTTP_COOKIE_VARS']";
2050 $compiled_ref = ($this->request_use_auto_globals) ? '$_ENV' : "\$GLOBALS['HTTP_ENV_VARS']";
2054 $compiled_ref = ($this->request_use_auto_globals) ? '$_SERVER' : "\$GLOBALS['HTTP_SERVER_VARS']";
2058 $compiled_ref = ($this->request_use_auto_globals) ? '$_SESSION' : "\$GLOBALS['HTTP_SESSION_VARS']";
2062 * These cases are handled either at run-time or elsewhere in the
2066 if ($this->request_use_auto_globals) {
2067 $compiled_ref = '$_REQUEST';
2070 $this->_init_smarty_vars = true;
2078 $compiled_ref = "'$this->_current_file'";
2083 $compiled_ref = "'$this->_version'";
2088 if ($this->security && !$this->security_settings['ALLOW_CONSTANTS']) {
2089 $this->_syntax_error("(secure mode) constants not permitted",
2090 E_USER_WARNING, __FILE__, __LINE__);
2093 array_shift($indexes);
2094 if (preg_match('!^\.\w+$!', $indexes[0])) {
2095 $compiled_ref = '@' . substr($indexes[0], 1);
2097 $_val = $this->_parse_var_props(substr($indexes[0], 1));
2098 $compiled_ref = '@constant(' . $_val . ')';
2104 $compiled_ref = "\$this->_config[0]['vars']";
2109 $compiled_ref = "'$this->left_delimiter'";
2113 $compiled_ref = "'$this->right_delimiter'";
2117 $this->_syntax_error('$smarty.' . $_ref . ' is an unknown reference', E_USER_ERROR, __FILE__, __LINE__);
2121 if (isset($_max_index) && count($indexes) > $_max_index) {
2122 $this->_syntax_error('$smarty' . implode('', $indexes) .' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
2125 array_shift($indexes);
2126 return $compiled_ref;
2130 * compiles call to plugin of type $type with name $name
2131 * returns a string containing the function-name or method call
2132 * without the paramter-list that would have follow to make the
2133 * call valid php-syntax
2135 * @param string $type
2136 * @param string $name
2139 function _compile_plugin_call($type, $name) {
2140 if (isset($this->_plugins[$type][$name])) {
2142 if (is_array($this->_plugins[$type][$name][0])) {
2143 return ((is_object($this->_plugins[$type][$name][0][0])) ?
2144 "\$this->_plugins['$type']['$name'][0][0]->" /* method callback */
2145 : (string)($this->_plugins[$type][$name][0][0]).'::' /* class callback */
2146 ). $this->_plugins[$type][$name][0][1];
2149 /* function callback */
2150 return $this->_plugins[$type][$name][0];
2154 /* plugin not loaded -> auto-loadable-plugin */
2155 return 'smarty_'.$type.'_'.$name;
2161 * load pre- and post-filters
2163 function _load_filters()
2165 if (count($this->_plugins['prefilter']) > 0) {
2166 foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
2167 if ($prefilter === false) {
2168 unset($this->_plugins['prefilter'][$filter_name]);
2169 $_params = array('plugins' => array(array('prefilter', $filter_name, null, null, false)));
2170 require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
2171 smarty_core_load_plugins($_params, $this);
2175 if (count($this->_plugins['postfilter']) > 0) {
2176 foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
2177 if ($postfilter === false) {
2178 unset($this->_plugins['postfilter'][$filter_name]);
2179 $_params = array('plugins' => array(array('postfilter', $filter_name, null, null, false)));
2180 require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
2181 smarty_core_load_plugins($_params, $this);
2189 * Quote subpattern references
2191 * @param string $string
2194 function _quote_replace($string)
2196 return strtr($string, array('\\' => '\\\\', '$' => '\\$'));
2200 * display Smarty syntax error
2202 * @param string $error_msg
2203 * @param integer $error_type
2204 * @param string $file
2205 * @param integer $line
2207 function _syntax_error($error_msg, $error_type = E_USER_ERROR, $file=null, $line=null)
2209 $this->_trigger_fatal_error("syntax error: $error_msg", $this->_current_file, $this->_current_line_no, $file, $line, $error_type);
2214 * check if the compilation changes from cacheable to
2215 * non-cacheable state with the beginning of the current
2216 * plugin. return php-code to reflect the transition.
2219 function _push_cacheable_state($type, $name) {
2220 $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2222 || 0<$this->_cacheable_state++) return '';
2223 if (!isset($this->_cache_serial)) $this->_cache_serial = md5(uniqid('Smarty'));
2224 $_ret = 'if ($this->caching && !$this->_cache_including) { echo \'{nocache:'
2225 . $this->_cache_serial . '#' . $this->_nocache_count
2232 * check if the compilation changes from non-cacheable to
2233 * cacheable state with the end of the current plugin return
2234 * php-code to reflect the transition.
2237 function _pop_cacheable_state($type, $name) {
2238 $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2240 || --$this->_cacheable_state>0) return '';
2241 return 'if ($this->caching && !$this->_cache_including) { echo \'{/nocache:'
2242 . $this->_cache_serial . '#' . ($this->_nocache_count++)
2248 * push opening tag-name, file-name and line-number on the tag-stack
2249 * @param string the opening tag's name
2251 function _push_tag($open_tag)
2253 array_push($this->_tag_stack, array($open_tag, $this->_current_line_no));
2257 * pop closing tag-name
2258 * raise an error if this stack-top doesn't match with the closing tag
2259 * @param string the closing tag's name
2260 * @return string the opening tag's name
2262 function _pop_tag($close_tag)
2265 if (count($this->_tag_stack)>0) {
2266 list($_open_tag, $_line_no) = array_pop($this->_tag_stack);
2267 if ($close_tag == $_open_tag) {
2270 if ($close_tag == 'if' && ($_open_tag == 'else' || $_open_tag == 'elseif' )) {
2271 return $this->_pop_tag($close_tag);
2273 if ($close_tag == 'section' && $_open_tag == 'sectionelse') {
2274 $this->_pop_tag($close_tag);
2277 if ($close_tag == 'foreach' && $_open_tag == 'foreachelse') {
2278 $this->_pop_tag($close_tag);
2281 if ($_open_tag == 'else' || $_open_tag == 'elseif') {
2283 } elseif ($_open_tag == 'sectionelse') {
2284 $_open_tag = 'section';
2285 } elseif ($_open_tag == 'foreachelse') {
2286 $_open_tag = 'foreach';
2288 $message = " expected {/$_open_tag} (opened line $_line_no).";
2290 $this->_syntax_error("mismatched tag {/$close_tag}.$message",
2291 E_USER_ERROR, __FILE__, __LINE__);
2297 * compare to values by their string length
2304 function _smarty_sort_length($a, $b)
2309 if(strlen($a) == strlen($b))
2310 return ($a > $b) ? -1 : 1;
2312 return (strlen($a) > strlen($b)) ? -1 : 1;