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.
30 * Template compiling class
33 class Smarty_Compiler extends Smarty {
39 var $_folded_blocks = array(); // keeps folded template blocks
40 var $_current_file = null; // the current template being compiled
41 var $_current_line_no = 1; // line number for error messages
42 var $_capture_stack = array(); // keeps track of nested capture buffers
43 var $_plugin_info = array(); // keeps track of plugins to load
44 var $_init_smarty_vars = false;
45 var $_permitted_tokens = array('true','false','yes','no','on','off','null');
46 var $_db_qstr_regexp = null; // regexps are setup in the constructor
47 var $_si_qstr_regexp = null;
48 var $_qstr_regexp = null;
49 var $_func_regexp = null;
50 var $_reg_obj_regexp = null;
51 var $_var_bracket_regexp = null;
52 var $_num_const_regexp = null;
53 var $_dvar_guts_regexp = null;
54 var $_dvar_regexp = null;
55 var $_cvar_regexp = null;
56 var $_svar_regexp = null;
57 var $_avar_regexp = null;
58 var $_mod_regexp = null;
59 var $_var_regexp = null;
60 var $_parenth_param_regexp = null;
61 var $_func_call_regexp = null;
62 var $_obj_ext_regexp = null;
63 var $_obj_start_regexp = null;
64 var $_obj_params_regexp = null;
65 var $_obj_call_regexp = null;
66 var $_cacheable_state = 0;
67 var $_cache_attrs_count = 0;
68 var $_nocache_count = 0;
69 var $_cache_serial = null;
70 var $_cache_include = null;
72 var $_strip_depth = 0;
73 var $_additional_newline = "\n";
77 * The class constructor.
79 function Smarty_Compiler()
81 // matches double quoted strings:
84 $this->_db_qstr_regexp = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
86 // matches single quoted strings:
89 $this->_si_qstr_regexp = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
91 // matches single or double quoted strings
92 $this->_qstr_regexp = '(?:' . $this->_db_qstr_regexp . '|' . $this->_si_qstr_regexp . ')';
94 // matches bracket portion of vars
98 $this->_var_bracket_regexp = '\[\$?[\w\.]+\]';
100 // matches numerical constants
104 $this->_num_const_regexp = '(?:\-?\d+(?:\.\d+)?)';
106 // matches $ vars (not objects):
113 // $foo[5].bar[$foobar][4]
114 $this->_dvar_math_regexp = '(?:[\+\*\/\%]|(?:-(?!>)))';
115 $this->_dvar_math_var_regexp = '[\$\w\.\+\-\*\/\%\d\>\[\]]';
116 $this->_dvar_guts_regexp = '\w+(?:' . $this->_var_bracket_regexp
117 . ')*(?:\.\$?\w+(?:' . $this->_var_bracket_regexp . ')*)*(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?';
118 $this->_dvar_regexp = '\$' . $this->_dvar_guts_regexp;
120 // matches config vars:
123 $this->_cvar_regexp = '\#\w+\#';
125 // matches section vars:
127 $this->_svar_regexp = '\%\w+\.\w+\%';
129 // matches all valid variables (no quotes, no modifiers)
130 $this->_avar_regexp = '(?:' . $this->_dvar_regexp . '|'
131 . $this->_cvar_regexp . '|' . $this->_svar_regexp . ')';
133 // matches valid variable syntax:
140 $this->_var_regexp = '(?:' . $this->_avar_regexp . '|' . $this->_qstr_regexp . ')';
142 // matches valid object call (one level of object nesting allowed in parameters):
146 // $foo->bar($foo, $bar, "text")
147 // $foo->bar($foo, "foo")
149 // $foo->bar->foo->bar()
150 // $foo->bar($foo->bar)
151 // $foo->bar($foo->bar())
152 // $foo->bar($foo->bar($blah,$foo,44,"foo",$foo[0].bar))
153 $this->_obj_ext_regexp = '\->(?:\$?' . $this->_dvar_guts_regexp . ')';
154 $this->_obj_restricted_param_regexp = '(?:'
155 . '(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')(?:' . $this->_obj_ext_regexp . '(?:\((?:(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')'
156 . '(?:\s*,\s*(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . '))*)?\))?)*)';
157 $this->_obj_single_param_regexp = '(?:\w+|' . $this->_obj_restricted_param_regexp . '(?:\s*,\s*(?:(?:\w+|'
158 . $this->_var_regexp . $this->_obj_restricted_param_regexp . ')))*)';
159 $this->_obj_params_regexp = '\((?:' . $this->_obj_single_param_regexp
160 . '(?:\s*,\s*' . $this->_obj_single_param_regexp . ')*)?\)';
161 $this->_obj_start_regexp = '(?:' . $this->_dvar_regexp . '(?:' . $this->_obj_ext_regexp . ')+)';
162 $this->_obj_call_regexp = '(?:' . $this->_obj_start_regexp . '(?:' . $this->_obj_params_regexp . ')?(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?)';
164 // matches valid modifier syntax:
169 // |foo:"bar":$foobar
172 $this->_mod_regexp = '(?:\|@?\w+(?::(?:\w+|' . $this->_num_const_regexp . '|'
173 . $this->_obj_call_regexp . '|' . $this->_avar_regexp . '|' . $this->_qstr_regexp .'))*)';
175 // matches valid function name:
178 $this->_func_regexp = '[a-zA-Z_]\w*';
180 // matches valid registered object:
182 $this->_reg_obj_regexp = '[a-zA-Z_]\w*->[a-zA-Z_]\w*';
184 // matches valid parameter values:
193 $this->_param_regexp = '(?:\s*(?:' . $this->_obj_call_regexp . '|'
194 . $this->_var_regexp . '|' . $this->_num_const_regexp . '|\w+)(?>' . $this->_mod_regexp . '*)\s*)';
196 // matches valid parenthesised function parameters:
199 // $foo, $bar, "text"
200 // $foo|bar, "foo"|bar, $foo->bar($foo)|bar
201 $this->_parenth_param_regexp = '(?:\((?:\w+|'
202 . $this->_param_regexp . '(?:\s*,\s*(?:(?:\w+|'
203 . $this->_param_regexp . ')))*)?\))';
205 // matches valid function call:
208 // _foo_bar($foo,"bar")
209 // foo123($foo,$foo->bar(),"foo")
210 $this->_func_call_regexp = '(?:' . $this->_func_regexp . '\s*(?:'
211 . $this->_parenth_param_regexp . '))';
217 * sets $compiled_content to the compiled source
218 * @param string $resource_name
219 * @param string $source_content
220 * @param string $compiled_content
223 function _compile_file($resource_name, $source_content, &$compiled_content)
226 if ($this->security) {
227 // do not allow php syntax to be executed unless specified
228 if ($this->php_handling == SMARTY_PHP_ALLOW &&
229 !$this->security_settings['PHP_HANDLING']) {
230 $this->php_handling = SMARTY_PHP_PASSTHRU;
234 $this->_load_filters();
236 $this->_current_file = $resource_name;
237 $this->_current_line_no = 1;
238 $ldq = preg_quote($this->left_delimiter, '~');
239 $rdq = preg_quote($this->right_delimiter, '~');
241 // run template source through prefilter functions
242 if (count($this->_plugins['prefilter']) > 0) {
243 foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
244 if ($prefilter === false) continue;
245 if ($prefilter[3] || is_callable($prefilter[0])) {
246 $source_content = call_user_func_array($prefilter[0],
247 array($source_content, &$this));
248 $this->_plugins['prefilter'][$filter_name][3] = true;
250 $this->_trigger_fatal_error("[plugin] prefilter '$filter_name' is not implemented");
255 /* fetch all special blocks */
256 $search = "~{$ldq}\*(.*?)\*{$rdq}|{$ldq}\s*literal\s*{$rdq}(.*?){$ldq}\s*/literal\s*{$rdq}|{$ldq}\s*php\s*{$rdq}(.*?){$ldq}\s*/php\s*{$rdq}~s";
258 preg_match_all($search, $source_content, $match, PREG_SET_ORDER);
259 $this->_folded_blocks = $match;
260 reset($this->_folded_blocks);
262 /* replace special blocks by "{php}" */
263 $source_content = preg_replace($search.'e', "'"
264 . $this->_quote_replace($this->left_delimiter) . 'php'
265 . "' . str_repeat(\"\n\", substr_count('\\0', \"\n\")) .'"
266 . $this->_quote_replace($this->right_delimiter)
270 /* Gather all template tags. */
271 preg_match_all("~{$ldq}\s*(.*?)\s*{$rdq}~s", $source_content, $_match);
272 $template_tags = $_match[1];
273 /* Split content by template tags to obtain non-template content. */
274 $text_blocks = preg_split("~{$ldq}.*?{$rdq}~s", $source_content);
276 /* loop through text blocks */
277 for ($curr_tb = 0, $for_max = count($text_blocks); $curr_tb < $for_max; $curr_tb++) {
278 /* match anything resembling php tags */
279 if (preg_match_all('~(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?php[\"\']?)~is', $text_blocks[$curr_tb], $sp_match)) {
280 /* replace tags with placeholders to prevent recursive replacements */
281 $sp_match[1] = array_unique($sp_match[1]);
282 usort($sp_match[1], '_smarty_sort_length');
283 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) {
284 $text_blocks[$curr_tb] = str_replace($sp_match[1][$curr_sp],'%%%SMARTYSP'.$curr_sp.'%%%',$text_blocks[$curr_tb]);
286 /* process each one */
287 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) {
288 if ($this->php_handling == SMARTY_PHP_PASSTHRU) {
289 /* echo php contents */
290 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '<?php echo \''.str_replace("'", "\'", $sp_match[1][$curr_sp]).'\'; ?>'."\n", $text_blocks[$curr_tb]);
291 } else if ($this->php_handling == SMARTY_PHP_QUOTE) {
293 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', htmlspecialchars($sp_match[1][$curr_sp]), $text_blocks[$curr_tb]);
294 } else if ($this->php_handling == SMARTY_PHP_REMOVE) {
295 /* remove php tags */
296 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '', $text_blocks[$curr_tb]);
298 /* SMARTY_PHP_ALLOW, but echo non php starting tags */
299 $sp_match[1][$curr_sp] = preg_replace('~(<\?(?!php|=|$))~i', '<?php echo \'\\1\'?>'."\n", $sp_match[1][$curr_sp]);
300 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', $sp_match[1][$curr_sp], $text_blocks[$curr_tb]);
306 /* Compile the template tags into PHP code. */
307 $compiled_tags = array();
308 for ($i = 0, $for_max = count($template_tags); $i < $for_max; $i++) {
309 $this->_current_line_no += substr_count($text_blocks[$i], "\n");
310 $compiled_tags[] = $this->_compile_tag($template_tags[$i]);
311 $this->_current_line_no += substr_count($template_tags[$i], "\n");
313 if (count($this->_tag_stack)>0) {
314 list($_open_tag, $_line_no) = end($this->_tag_stack);
315 $this->_syntax_error("unclosed tag \{$_open_tag} (opened line $_line_no).", E_USER_ERROR, __FILE__, __LINE__);
319 /* Reformat $text_blocks between 'strip' and '/strip' tags,
320 removing spaces, tabs and newlines. */
322 for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) {
323 if ($compiled_tags[$i] == '{strip}') {
324 $compiled_tags[$i] = '';
326 /* remove leading whitespaces */
327 $text_blocks[$i + 1] = ltrim($text_blocks[$i + 1]);
330 /* strip all $text_blocks before the next '/strip' */
331 for ($j = $i + 1; $j < $for_max; $j++) {
332 /* remove leading and trailing whitespaces of each line */
333 $text_blocks[$j] = preg_replace('![\t ]*[\r\n]+[\t ]*!', '', $text_blocks[$j]);
334 if ($compiled_tags[$j] == '{/strip}') {
335 /* remove trailing whitespaces from the last text_block */
336 $text_blocks[$j] = rtrim($text_blocks[$j]);
338 $text_blocks[$j] = "<?php echo '" . strtr($text_blocks[$j], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>";
339 if ($compiled_tags[$j] == '{/strip}') {
340 $compiled_tags[$j] = "\n"; /* slurped by php, but necessary
341 if a newline is following the closing strip-tag */
349 $compiled_content = '';
351 /* Interleave the compiled contents and text blocks to get the final result. */
352 for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) {
353 if ($compiled_tags[$i] == '') {
354 // tag result empty, remove first newline from following text block
355 $text_blocks[$i+1] = preg_replace('~^(\r\n|\r|\n)~', '', $text_blocks[$i+1]);
357 $compiled_content .= $text_blocks[$i].$compiled_tags[$i];
359 $compiled_content .= $text_blocks[$i];
361 // remove \n from the end of the file, if any
362 if (strlen($compiled_content) && (substr($compiled_content, -1) == "\n") ) {
363 $compiled_content = substr($compiled_content, 0, -1);
366 if (!empty($this->_cache_serial)) {
367 $compiled_content = "<?php \$this->_cache_serials['".$this->_cache_include."'] = '".$this->_cache_serial."'; ?>" . $compiled_content;
370 // remove unnecessary close/open tags
371 $compiled_content = preg_replace('~\?>\n?<\?php~', '', $compiled_content);
373 // run compiled template through postfilter functions
374 if (count($this->_plugins['postfilter']) > 0) {
375 foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
376 if ($postfilter === false) continue;
377 if ($postfilter[3] || is_callable($postfilter[0])) {
378 $compiled_content = call_user_func_array($postfilter[0],
379 array($compiled_content, &$this));
380 $this->_plugins['postfilter'][$filter_name][3] = true;
382 $this->_trigger_fatal_error("Smarty plugin error: postfilter '$filter_name' is not implemented");
387 // put header at the top of the compiled template
388 $template_header = "<?php /* Smarty version ".$this->_version.", created on ".strftime("%Y-%m-%d %H:%M:%S")."\n";
389 $template_header .= " compiled from ".strtr(urlencode($resource_name), array('%2F'=>'/', '%3A'=>':'))." */ ?>\n";
391 /* Emit code to load needed plugins. */
392 $this->_plugins_code = '';
393 if (count($this->_plugin_info)) {
394 $_plugins_params = "array('plugins' => array(";
395 foreach ($this->_plugin_info as $plugin_type => $plugins) {
396 foreach ($plugins as $plugin_name => $plugin_info) {
397 $_plugins_params .= "array('$plugin_type', '$plugin_name', '" . strtr($plugin_info[0], array("'" => "\\'", "\\" => "\\\\")) . "', $plugin_info[1], ";
398 $_plugins_params .= $plugin_info[2] ? 'true),' : 'false),';
401 $_plugins_params .= '))';
402 $plugins_code = "<?php require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');\nsmarty_core_load_plugins($_plugins_params, \$this); ?>\n";
403 $template_header .= $plugins_code;
404 $this->_plugin_info = array();
405 $this->_plugins_code = $plugins_code;
408 if ($this->_init_smarty_vars) {
409 $template_header .= "<?php require_once(SMARTY_CORE_DIR . 'core.assign_smarty_interface.php');\nsmarty_core_assign_smarty_interface(null, \$this); ?>\n";
410 $this->_init_smarty_vars = false;
413 $compiled_content = $template_header . $compiled_content;
418 * Compile a template tag
420 * @param string $template_tag
423 function _compile_tag($template_tag)
425 /* Matched comment. */
426 if (substr($template_tag, 0, 1) == '*' && substr($template_tag, -1) == '*')
429 /* Split tag into two three parts: command, command modifiers and the arguments. */
430 if(! preg_match('~^(?:(' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp
431 . '|\/?' . $this->_reg_obj_regexp . '|\/?' . $this->_func_regexp . ')(' . $this->_mod_regexp . '*))
433 ~xs', $template_tag, $match)) {
434 $this->_syntax_error("unrecognized tag: $template_tag", E_USER_ERROR, __FILE__, __LINE__);
437 $tag_command = $match[1];
438 $tag_modifier = isset($match[2]) ? $match[2] : null;
439 $tag_args = isset($match[3]) ? $match[3] : null;
441 if (preg_match('~^' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '$~', $tag_command)) {
442 /* tag name is a variable or object */
443 $_return = $this->_parse_var_props($tag_command . $tag_modifier);
444 return "<?php echo $_return; ?>" . $this->_additional_newline;
447 /* If the tag name is a registered object, we process it. */
448 if (preg_match('~^\/?' . $this->_reg_obj_regexp . '$~', $tag_command)) {
449 return $this->_compile_registered_object_tag($tag_command, $this->_parse_attrs($tag_args), $tag_modifier);
452 switch ($tag_command) {
454 return $this->_compile_include_tag($tag_args);
457 return $this->_compile_include_php_tag($tag_args);
460 $this->_push_tag('if');
461 return $this->_compile_if_tag($tag_args);
464 list($_open_tag) = end($this->_tag_stack);
465 if ($_open_tag != 'if' && $_open_tag != 'elseif')
466 $this->_syntax_error('unexpected {else}', E_USER_ERROR, __FILE__, __LINE__);
468 $this->_push_tag('else');
469 return '<?php else: ?>';
472 list($_open_tag) = end($this->_tag_stack);
473 if ($_open_tag != 'if' && $_open_tag != 'elseif')
474 $this->_syntax_error('unexpected {elseif}', E_USER_ERROR, __FILE__, __LINE__);
475 if ($_open_tag == 'if')
476 $this->_push_tag('elseif');
477 return $this->_compile_if_tag($tag_args, true);
480 $this->_pop_tag('if');
481 return '<?php endif; ?>';
484 return $this->_compile_capture_tag(true, $tag_args);
487 return $this->_compile_capture_tag(false);
490 return $this->left_delimiter;
493 return $this->right_delimiter;
496 $this->_push_tag('section');
497 return $this->_compile_section_start($tag_args);
500 $this->_push_tag('sectionelse');
501 return "<?php endfor; else: ?>";
505 $_open_tag = $this->_pop_tag('section');
506 if ($_open_tag == 'sectionelse')
507 return "<?php endif; ?>";
509 return "<?php endfor; endif; ?>";
512 $this->_push_tag('foreach');
513 return $this->_compile_foreach_start($tag_args);
517 $this->_push_tag('foreachelse');
518 return "<?php endforeach; else: ?>";
521 $_open_tag = $this->_pop_tag('foreach');
522 if ($_open_tag == 'foreachelse')
523 return "<?php endif; unset(\$_from); ?>";
525 return "<?php endforeach; endif; unset(\$_from); ?>";
530 if (substr($tag_command, 0, 1)=='/') {
531 $this->_pop_tag('strip');
532 if (--$this->_strip_depth==0) { /* outermost closing {/strip} */
533 $this->_additional_newline = "\n";
534 return '{' . $tag_command . '}';
537 $this->_push_tag('strip');
538 if ($this->_strip_depth++==0) { /* outermost opening {strip} */
539 $this->_additional_newline = "";
540 return '{' . $tag_command . '}';
546 /* handle folded tags replaced by {php} */
547 list(, $block) = each($this->_folded_blocks);
548 $this->_current_line_no += substr_count($block[0], "\n");
549 /* the number of matched elements in the regexp in _compile_file()
550 determins the type of folded tag that was found */
551 switch (count($block)) {
552 case 2: /* comment */
555 case 3: /* literal */
556 return "<?php echo '" . strtr($block[2], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>" . $this->_additional_newline;
559 if ($this->security && !$this->security_settings['PHP_TAGS']) {
560 $this->_syntax_error("(secure mode) php tags not permitted", E_USER_WARNING, __FILE__, __LINE__);
563 return '<?php ' . $block[3] .' ?>';
568 return $this->_compile_insert_tag($tag_args);
571 if ($this->_compile_compiler_tag($tag_command, $tag_args, $output)) {
573 } else if ($this->_compile_block_tag($tag_command, $tag_args, $tag_modifier, $output)) {
575 } else if ($this->_compile_custom_tag($tag_command, $tag_args, $tag_modifier, $output)) {
578 $this->_syntax_error("unrecognized tag '$tag_command'", E_USER_ERROR, __FILE__, __LINE__);
586 * compile the custom compiler tag
588 * sets $output to the compiled custom compiler tag
589 * @param string $tag_command
590 * @param string $tag_args
591 * @param string $output
594 function _compile_compiler_tag($tag_command, $tag_args, &$output)
597 $have_function = true;
600 * First we check if the compiler function has already been registered
601 * or loaded from a plugin file.
603 if (isset($this->_plugins['compiler'][$tag_command])) {
605 $plugin_func = $this->_plugins['compiler'][$tag_command][0];
606 if (!is_callable($plugin_func)) {
607 $message = "compiler function '$tag_command' is not implemented";
608 $have_function = false;
612 * Otherwise we need to load plugin file and look for the function
615 else if ($plugin_file = $this->_get_plugin_filepath('compiler', $tag_command)) {
618 include_once $plugin_file;
620 $plugin_func = 'smarty_compiler_' . $tag_command;
621 if (!is_callable($plugin_func)) {
622 $message = "plugin function $plugin_func() not found in $plugin_file\n";
623 $have_function = false;
625 $this->_plugins['compiler'][$tag_command] = array($plugin_func, null, null, null, true);
630 * True return value means that we either found a plugin or a
631 * dynamically registered function. False means that we didn't and the
632 * compiler should now emit code to load custom function plugin for this
636 if ($have_function) {
637 $output = call_user_func_array($plugin_func, array($tag_args, &$this));
639 $output = '<?php ' . $this->_push_cacheable_state('compiler', $tag_command)
641 . $this->_pop_cacheable_state('compiler', $tag_command) . ' ?>';
644 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
654 * compile block function tag
656 * sets $output to compiled block function tag
657 * @param string $tag_command
658 * @param string $tag_args
659 * @param string $tag_modifier
660 * @param string $output
663 function _compile_block_tag($tag_command, $tag_args, $tag_modifier, &$output)
665 if (substr($tag_command, 0, 1) == '/') {
667 $tag_command = substr($tag_command, 1);
672 $have_function = true;
675 * First we check if the block function has already been registered
676 * or loaded from a plugin file.
678 if (isset($this->_plugins['block'][$tag_command])) {
680 $plugin_func = $this->_plugins['block'][$tag_command][0];
681 if (!is_callable($plugin_func)) {
682 $message = "block function '$tag_command' is not implemented";
683 $have_function = false;
687 * Otherwise we need to load plugin file and look for the function
690 else if ($plugin_file = $this->_get_plugin_filepath('block', $tag_command)) {
693 include_once $plugin_file;
695 $plugin_func = 'smarty_block_' . $tag_command;
696 if (!function_exists($plugin_func)) {
697 $message = "plugin function $plugin_func() not found in $plugin_file\n";
698 $have_function = false;
700 $this->_plugins['block'][$tag_command] = array($plugin_func, null, null, null, true);
707 } else if (!$have_function) {
708 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
713 * Even though we've located the plugin function, compilation
714 * happens only once, so the plugin will still need to be loaded
715 * at runtime for future requests.
717 $this->_add_plugin('block', $tag_command);
720 $this->_push_tag($tag_command);
722 $this->_pop_tag($tag_command);
725 $output = '<?php ' . $this->_push_cacheable_state('block', $tag_command);
726 $attrs = $this->_parse_attrs($tag_args);
727 $arg_list = $this->_compile_arg_list('block', $tag_command, $attrs, $_cache_attrs='');
728 $output .= "$_cache_attrs\$this->_tag_stack[] = array('$tag_command', array(".implode(',', $arg_list).')); ';
729 $output .= $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], null, $this, $_block_repeat=true);';
730 $output .= 'while ($_block_repeat) { ob_start(); ?>';
732 $output = '<?php $_block_content = ob_get_contents(); ob_end_clean(); ';
733 $_out_tag_text = $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], $_block_content, $this, $_block_repeat=false)';
734 if ($tag_modifier != '') {
735 $this->_parse_modifiers($_out_tag_text, $tag_modifier);
737 $output .= 'echo '.$_out_tag_text.'; } ';
738 $output .= " array_pop(\$this->_tag_stack); " . $this->_pop_cacheable_state('block', $tag_command) . '?>';
746 * compile custom function tag
748 * @param string $tag_command
749 * @param string $tag_args
750 * @param string $tag_modifier
753 function _compile_custom_tag($tag_command, $tag_args, $tag_modifier, &$output)
756 $have_function = true;
759 * First we check if the custom function has already been registered
760 * or loaded from a plugin file.
762 if (isset($this->_plugins['function'][$tag_command])) {
764 $plugin_func = $this->_plugins['function'][$tag_command][0];
765 if (!is_callable($plugin_func)) {
766 $message = "custom function '$tag_command' is not implemented";
767 $have_function = false;
771 * Otherwise we need to load plugin file and look for the function
774 else if ($plugin_file = $this->_get_plugin_filepath('function', $tag_command)) {
777 include_once $plugin_file;
779 $plugin_func = 'smarty_function_' . $tag_command;
780 if (!function_exists($plugin_func)) {
781 $message = "plugin function $plugin_func() not found in $plugin_file\n";
782 $have_function = false;
784 $this->_plugins['function'][$tag_command] = array($plugin_func, null, null, null, true);
791 } else if (!$have_function) {
792 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
796 /* declare plugin to be loaded on display of the template that
797 we compile right now */
798 $this->_add_plugin('function', $tag_command);
800 $_cacheable_state = $this->_push_cacheable_state('function', $tag_command);
801 $attrs = $this->_parse_attrs($tag_args);
802 $arg_list = $this->_compile_arg_list('function', $tag_command, $attrs, $_cache_attrs='');
804 $output = $this->_compile_plugin_call('function', $tag_command).'(array('.implode(',', $arg_list)."), \$this)";
805 if($tag_modifier != '') {
806 $this->_parse_modifiers($output, $tag_modifier);
810 $output = '<?php ' . $_cacheable_state . $_cache_attrs . 'echo ' . $output . ';'
811 . $this->_pop_cacheable_state('function', $tag_command) . "?>" . $this->_additional_newline;
818 * compile a registered object tag
820 * @param string $tag_command
821 * @param array $attrs
822 * @param string $tag_modifier
825 function _compile_registered_object_tag($tag_command, $attrs, $tag_modifier)
827 if (substr($tag_command, 0, 1) == '/') {
829 $tag_command = substr($tag_command, 1);
834 list($object, $obj_comp) = explode('->', $tag_command);
838 $_assign_var = false;
839 foreach ($attrs as $arg_name => $arg_value) {
840 if($arg_name == 'assign') {
841 $_assign_var = $arg_value;
842 unset($attrs['assign']);
845 if (is_bool($arg_value))
846 $arg_value = $arg_value ? 'true' : 'false';
847 $arg_list[] = "'$arg_name' => $arg_value";
851 if($this->_reg_objects[$object][2]) {
852 // smarty object argument format
853 $args = "array(".implode(',', (array)$arg_list)."), \$this";
855 // traditional argument format
856 $args = implode(',', array_values($attrs));
865 if(!is_object($this->_reg_objects[$object][0])) {
866 $this->_trigger_fatal_error("registered '$object' is not an object" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
867 } elseif(!empty($this->_reg_objects[$object][1]) && !in_array($obj_comp, $this->_reg_objects[$object][1])) {
868 $this->_trigger_fatal_error("'$obj_comp' is not a registered component of object '$object'", $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
869 } elseif(method_exists($this->_reg_objects[$object][0], $obj_comp)) {
871 if(in_array($obj_comp, $this->_reg_objects[$object][3])) {
874 $prefix = "\$this->_tag_stack[] = array('$obj_comp', $args); ";
875 $prefix .= "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], null, \$this, \$_block_repeat=true); ";
876 $prefix .= "while (\$_block_repeat) { ob_start();";
880 $prefix = "\$_obj_block_content = ob_get_contents(); ob_end_clean(); ";
881 $return = "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], \$_obj_block_content, \$this, \$_block_repeat=false)";
882 $postfix = "} array_pop(\$this->_tag_stack);";
886 $return = "\$this->_reg_objects['$object'][0]->$obj_comp($args)";
890 $return = "\$this->_reg_objects['$object'][0]->$obj_comp";
893 if($return != null) {
894 if($tag_modifier != '') {
895 $this->_parse_modifiers($return, $tag_modifier);
898 if(!empty($_assign_var)) {
899 $output = "\$this->assign('" . $this->_dequote($_assign_var) ."', $return);";
901 $output = 'echo ' . $return . ';';
902 $newline = $this->_additional_newline;
908 return '<?php ' . $prefix . $output . $postfix . "?>" . $newline;
912 * Compile {insert ...} tag
914 * @param string $tag_args
917 function _compile_insert_tag($tag_args)
919 $attrs = $this->_parse_attrs($tag_args);
920 $name = $this->_dequote($attrs['name']);
923 $this->_syntax_error("missing insert name", E_USER_ERROR, __FILE__, __LINE__);
926 if (!empty($attrs['script'])) {
927 $delayed_loading = true;
929 $delayed_loading = false;
932 foreach ($attrs as $arg_name => $arg_value) {
933 if (is_bool($arg_value))
934 $arg_value = $arg_value ? 'true' : 'false';
935 $arg_list[] = "'$arg_name' => $arg_value";
938 $this->_add_plugin('insert', $name, $delayed_loading);
940 $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))";
942 return "<?php require_once(SMARTY_CORE_DIR . 'core.run_insert_handler.php');\necho smarty_core_run_insert_handler($_params, \$this); ?>" . $this->_additional_newline;
946 * Compile {include ...} tag
948 * @param string $tag_args
951 function _compile_include_tag($tag_args)
953 $attrs = $this->_parse_attrs($tag_args);
956 if (empty($attrs['file'])) {
957 $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR, __FILE__, __LINE__);
960 $theme_template = 'false';
961 foreach ($attrs as $arg_name => $arg_value) {
962 if ($arg_name == 'file') {
963 $include_file = $arg_value;
965 } else if ($arg_name == 'assign') {
966 $assign_var = $arg_value;
968 } else if ($arg_name == 'theme_template') {
969 $theme_template = $arg_value;
972 if (is_bool($arg_value))
973 $arg_value = $arg_value ? 'true' : 'false';
974 $arg_list[] = "'$arg_name' => $arg_value";
977 if ( $theme_template == 'true' )
978 $include_file = '"'.SugarThemeRegistry::current()->getTemplate(str_replace(array('"',"'"),'',$include_file)).'"';
982 if (isset($assign_var)) {
983 $output .= "ob_start();\n";
987 "\$_smarty_tpl_vars = \$this->_tpl_vars;\n";
990 $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))";
991 $output .= "\$this->_smarty_include($_params);\n" .
992 "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" .
993 "unset(\$_smarty_tpl_vars);\n";
995 if (isset($assign_var)) {
996 $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n";
1006 * Compile {include ...} tag
1008 * @param string $tag_args
1011 function _compile_include_php_tag($tag_args)
1013 $attrs = $this->_parse_attrs($tag_args);
1015 if (empty($attrs['file'])) {
1016 $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR, __FILE__, __LINE__);
1019 $assign_var = (empty($attrs['assign'])) ? '' : $this->_dequote($attrs['assign']);
1020 $once_var = (empty($attrs['once']) || $attrs['once']=='false') ? 'false' : 'true';
1022 $arg_list = array();
1023 foreach($attrs as $arg_name => $arg_value) {
1024 if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') {
1025 if(is_bool($arg_value))
1026 $arg_value = $arg_value ? 'true' : 'false';
1027 $arg_list[] = "'$arg_name' => $arg_value";
1031 $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', $arg_list)."))";
1033 return "<?php require_once(SMARTY_CORE_DIR . 'core.smarty_include_php.php');\nsmarty_core_smarty_include_php($_params, \$this); ?>" . $this->_additional_newline;
1038 * Compile {section ...} tag
1040 * @param string $tag_args
1043 function _compile_section_start($tag_args)
1045 $attrs = $this->_parse_attrs($tag_args);
1046 $arg_list = array();
1049 $section_name = $attrs['name'];
1050 if (empty($section_name)) {
1051 $this->_syntax_error("missing section name", E_USER_ERROR, __FILE__, __LINE__);
1054 $output .= "unset(\$this->_sections[$section_name]);\n";
1055 $section_props = "\$this->_sections[$section_name]";
1057 foreach ($attrs as $attr_name => $attr_value) {
1058 switch ($attr_name) {
1060 $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n";
1064 if (is_bool($attr_value))
1065 $show_attr_value = $attr_value ? 'true' : 'false';
1067 $show_attr_value = "(bool)$attr_value";
1068 $output .= "{$section_props}['show'] = $show_attr_value;\n";
1072 $output .= "{$section_props}['$attr_name'] = $attr_value;\n";
1077 $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n";
1081 $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n";
1085 $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR, __FILE__, __LINE__);
1090 if (!isset($attrs['show']))
1091 $output .= "{$section_props}['show'] = true;\n";
1093 if (!isset($attrs['loop']))
1094 $output .= "{$section_props}['loop'] = 1;\n";
1096 if (!isset($attrs['max']))
1097 $output .= "{$section_props}['max'] = {$section_props}['loop'];\n";
1099 $output .= "if ({$section_props}['max'] < 0)\n" .
1100 " {$section_props}['max'] = {$section_props}['loop'];\n";
1102 if (!isset($attrs['step']))
1103 $output .= "{$section_props}['step'] = 1;\n";
1105 if (!isset($attrs['start']))
1106 $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n";
1108 $output .= "if ({$section_props}['start'] < 0)\n" .
1109 " {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" .
1111 " {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n";
1114 $output .= "if ({$section_props}['show']) {\n";
1115 if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) {
1116 $output .= " {$section_props}['total'] = {$section_props}['loop'];\n";
1118 $output .= " {$section_props}['total'] = min(ceil(({$section_props}['step'] > 0 ? {$section_props}['loop'] - {$section_props}['start'] : {$section_props}['start']+1)/abs({$section_props}['step'])), {$section_props}['max']);\n";
1120 $output .= " if ({$section_props}['total'] == 0)\n" .
1121 " {$section_props}['show'] = false;\n" .
1123 " {$section_props}['total'] = 0;\n";
1125 $output .= "if ({$section_props}['show']):\n";
1127 for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1;
1128 {$section_props}['iteration'] <= {$section_props}['total'];
1129 {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n";
1130 $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n";
1131 $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n";
1132 $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n";
1133 $output .= "{$section_props}['first'] = ({$section_props}['iteration'] == 1);\n";
1134 $output .= "{$section_props}['last'] = ({$section_props}['iteration'] == {$section_props}['total']);\n";
1143 * Compile {foreach ...} tag.
1145 * @param string $tag_args
1148 function _compile_foreach_start($tag_args)
1150 $attrs = $this->_parse_attrs($tag_args);
1151 $arg_list = array();
1153 if (empty($attrs['from'])) {
1154 return $this->_syntax_error("foreach: missing 'from' attribute", E_USER_ERROR, __FILE__, __LINE__);
1156 $from = $attrs['from'];
1158 if (empty($attrs['item'])) {
1159 return $this->_syntax_error("foreach: missing 'item' attribute", E_USER_ERROR, __FILE__, __LINE__);
1161 $item = $this->_dequote($attrs['item']);
1162 if (!preg_match('~^\w+$~', $item)) {
1163 return $this->_syntax_error("'foreach: item' must be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1166 if (isset($attrs['key'])) {
1167 $key = $this->_dequote($attrs['key']);
1168 if (!preg_match('~^\w+$~', $key)) {
1169 return $this->_syntax_error("foreach: 'key' must to be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1171 $key_part = "\$this->_tpl_vars['$key'] => ";
1177 if (isset($attrs['name'])) {
1178 $name = $attrs['name'];
1184 $output .= "\$_from = $from; if (!is_array(\$_from) && !is_object(\$_from)) { settype(\$_from, 'array'); }";
1186 $foreach_props = "\$this->_foreach[$name]";
1187 $output .= "{$foreach_props} = array('total' => count(\$_from), 'iteration' => 0);\n";
1188 $output .= "if ({$foreach_props}['total'] > 0):\n";
1189 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1190 $output .= " {$foreach_props}['iteration']++;\n";
1192 $output .= "if (count(\$_from)):\n";
1193 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1202 * Compile {capture} .. {/capture} tags
1204 * @param boolean $start true if this is the {capture} tag
1205 * @param string $tag_args
1209 function _compile_capture_tag($start, $tag_args = '')
1211 $attrs = $this->_parse_attrs($tag_args);
1214 if (isset($attrs['name']))
1215 $buffer = $attrs['name'];
1217 $buffer = "'default'";
1219 if (isset($attrs['assign']))
1220 $assign = $attrs['assign'];
1223 $output = "<?php ob_start(); ?>";
1224 $this->_capture_stack[] = array($buffer, $assign);
1226 list($buffer, $assign) = array_pop($this->_capture_stack);
1227 $output = "<?php \$this->_smarty_vars['capture'][$buffer] = ob_get_contents(); ";
1228 if (isset($assign)) {
1229 $output .= " \$this->assign($assign, ob_get_contents());";
1231 $output .= "ob_end_clean(); ?>";
1238 * Compile {if ...} tag
1240 * @param string $tag_args
1241 * @param boolean $elseif if true, uses elseif instead of if
1244 function _compile_if_tag($tag_args, $elseif = false)
1247 /* Tokenize args for 'if' tag. */
1248 preg_match_all('~(?>
1249 ' . $this->_obj_call_regexp . '(?:' . $this->_mod_regexp . '*)? | # valid object call
1250 ' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)? | # var or quoted string
1251 \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@ | # valid non-word token
1252 \b\w+\b | # valid word token
1254 )~x', $tag_args, $match);
1256 $tokens = $match[0];
1258 if(empty($tokens)) {
1259 $_error_msg .= $elseif ? "'elseif'" : "'if'";
1260 $_error_msg .= ' statement requires arguments';
1261 $this->_syntax_error($_error_msg, E_USER_ERROR, __FILE__, __LINE__);
1265 // make sure we have balanced parenthesis
1266 $token_count = array_count_values($tokens);
1267 if(isset($token_count['(']) && $token_count['('] != $token_count[')']) {
1268 $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR, __FILE__, __LINE__);
1271 $is_arg_stack = array();
1273 for ($i = 0; $i < count($tokens); $i++) {
1275 $token = &$tokens[$i];
1277 switch (strtolower($token)) {
1350 array_push($is_arg_stack, $i);
1354 /* If last token was a ')', we operate on the parenthesized
1355 expression. The start of the expression is on the stack.
1356 Otherwise, we operate on the last encountered token. */
1357 if ($tokens[$i-1] == ')')
1358 $is_arg_start = array_pop($is_arg_stack);
1360 $is_arg_start = $i-1;
1361 /* Construct the argument for 'is' expression, so it knows
1362 what to operate on. */
1363 $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
1365 /* Pass all tokens from next one until the end to the
1366 'is' expression parsing function. The function will
1367 return modified tokens, where the first one is the result
1368 of the 'is' expression and the rest are the tokens it
1370 $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));
1372 /* Replace the old tokens with the new ones. */
1373 array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);
1375 /* Adjust argument start so that it won't change from the
1376 current position for the next iteration. */
1381 if(preg_match('~^' . $this->_func_regexp . '$~', $token) ) {
1383 if($this->security &&
1384 !in_array($token, $this->security_settings['IF_FUNCS'])) {
1385 $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);
1387 } elseif(preg_match('~^' . $this->_var_regexp . '$~', $token) && isset($tokens[$i+1]) && $tokens[$i+1] == '(') {
1388 // variable function call
1389 $this->_syntax_error("variable function call '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);
1390 } elseif(preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$~', $token)) {
1391 // object or variable
1392 $token = $this->_parse_var_props($token);
1393 } elseif(is_numeric($token)) {
1396 $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__);
1403 return '<?php elseif ('.implode(' ', $tokens).'): ?>';
1405 return '<?php if ('.implode(' ', $tokens).'): ?>';
1409 function _compile_arg_list($type, $name, $attrs, &$cache_code) {
1410 $arg_list = array();
1412 if (isset($type) && isset($name)
1413 && isset($this->_plugins[$type])
1414 && isset($this->_plugins[$type][$name])
1415 && empty($this->_plugins[$type][$name][4])
1416 && is_array($this->_plugins[$type][$name][5])
1418 /* we have a list of parameters that should be cached */
1419 $_cache_attrs = $this->_plugins[$type][$name][5];
1420 $_count = $this->_cache_attrs_count++;
1421 $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');";
1424 /* no parameters are cached */
1425 $_cache_attrs = null;
1428 foreach ($attrs as $arg_name => $arg_value) {
1429 if (is_bool($arg_value))
1430 $arg_value = $arg_value ? 'true' : 'false';
1431 if (is_null($arg_value))
1432 $arg_value = 'null';
1433 if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) {
1434 $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)";
1436 $arg_list[] = "'$arg_name' => $arg_value";
1443 * Parse is expression
1445 * @param string $is_arg
1446 * @param array $tokens
1449 function _parse_is_expr($is_arg, $tokens)
1452 $negate_expr = false;
1454 if (($first_token = array_shift($tokens)) == 'not') {
1455 $negate_expr = true;
1456 $expr_type = array_shift($tokens);
1458 $expr_type = $first_token;
1460 switch ($expr_type) {
1462 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1464 $expr_arg = $tokens[$expr_end++];
1465 $expr = "!(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1467 $expr = "!(1 & $is_arg)";
1471 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1473 $expr_arg = $tokens[$expr_end++];
1474 $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1476 $expr = "(1 & $is_arg)";
1480 if (@$tokens[$expr_end] == 'by') {
1482 $expr_arg = $tokens[$expr_end++];
1483 $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")";
1485 $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__);
1490 $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__);
1498 array_splice($tokens, 0, $expr_end, $expr);
1505 * Parse attribute string
1507 * @param string $tag_args
1510 function _parse_attrs($tag_args)
1513 /* Tokenize tag attributes. */
1514 preg_match_all('~(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+)
1517 ~x', $tag_args, $match);
1518 $tokens = $match[0];
1522 0 - expecting attribute name
1524 2 - expecting attribute value (not '=') */
1527 foreach ($tokens as $token) {
1530 /* If the token is a valid identifier, we set attribute name
1531 and go to state 1. */
1532 if (preg_match('~^\w+$~', $token)) {
1533 $attr_name = $token;
1536 $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__);
1540 /* If the token is '=', then we go to state 2. */
1541 if ($token == '=') {
1544 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1548 /* If token is not '=', we set the attribute value and go to
1550 if ($token != '=') {
1551 /* We booleanize the token if it's a non-quoted possible
1553 if (preg_match('~^(on|yes|true)$~', $token)) {
1555 } else if (preg_match('~^(off|no|false)$~', $token)) {
1557 } else if ($token == 'null') {
1559 } else if (preg_match('~^' . $this->_num_const_regexp . '|0[xX][0-9a-fA-F]+$~', $token)) {
1560 /* treat integer literally */
1561 } else if (!preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$~', $token)) {
1562 /* treat as a string, double-quote it escaping quotes */
1563 $token = '"'.addslashes($token).'"';
1566 $attrs[$attr_name] = $token;
1569 $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__);
1572 $last_token = $token;
1577 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1579 $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__);
1583 $this->_parse_vars_props($attrs);
1589 * compile multiple variables and section properties tokens into
1592 * @param array $tokens
1594 function _parse_vars_props(&$tokens)
1596 foreach($tokens as $key => $val) {
1597 $tokens[$key] = $this->_parse_var_props($val);
1602 * compile single variable and section properties token into
1605 * @param string $val
1606 * @param string $tag_attrs
1609 function _parse_var_props($val)
1613 if(preg_match('~^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$~', $val, $match)) {
1614 // $ variable or object
1615 $return = $this->_parse_var($match[1]);
1616 $modifiers = $match[2];
1617 if (!empty($this->default_modifiers) && !preg_match('~(^|\|)smarty:nodefaults($|\|)~',$modifiers)) {
1618 $_default_mod_string = implode('|',(array)$this->default_modifiers);
1619 $modifiers = empty($modifiers) ? $_default_mod_string : $_default_mod_string . '|' . $modifiers;
1621 $this->_parse_modifiers($return, $modifiers);
1623 } elseif (preg_match('~^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1624 // double quoted text
1625 preg_match('~^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1626 $return = $this->_expand_quoted_text($match[1]);
1627 if($match[2] != '') {
1628 $this->_parse_modifiers($return, $match[2]);
1632 elseif(preg_match('~^' . $this->_num_const_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1633 // numerical constant
1634 preg_match('~^(' . $this->_num_const_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1635 if($match[2] != '') {
1636 $this->_parse_modifiers($match[1], $match[2]);
1640 elseif(preg_match('~^' . $this->_si_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1641 // single quoted text
1642 preg_match('~^(' . $this->_si_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1643 if($match[2] != '') {
1644 $this->_parse_modifiers($match[1], $match[2]);
1648 elseif(preg_match('~^' . $this->_cvar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1650 return $this->_parse_conf_var($val);
1652 elseif(preg_match('~^' . $this->_svar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1654 return $this->_parse_section_prop($val);
1656 elseif(!in_array($val, $this->_permitted_tokens) && !is_numeric($val)) {
1658 return $this->_expand_quoted_text('"' . strtr($val, array('\\' => '\\\\', '"' => '\\"')) .'"');
1664 * expand quoted text with embedded variables
1666 * @param string $var_expr
1669 function _expand_quoted_text($var_expr)
1671 // if contains unescaped $, expand it
1672 if(preg_match_all('~(?:\`(?<!\\\\)\$' . $this->_dvar_guts_regexp . '(?:' . $this->_obj_ext_regexp . ')*\`)|(?:(?<!\\\\)\$\w+(\[[a-zA-Z0-9]+\])*)~', $var_expr, $_match)) {
1673 $_match = $_match[0];
1676 foreach($_match as $_var) {
1677 $var_expr = str_replace ($_var, '".(' . $this->_parse_var(str_replace('`','',$_var)) . ')."', $var_expr);
1679 $_return = preg_replace('~\.""|(?<!\\\\)""\.~', '', $var_expr);
1681 $_return = $var_expr;
1683 // replace double quoted literal string with single quotes
1684 $_return = preg_replace('~^"([\s\w]+)"$~',"'\\1'",$_return);
1689 * parse variable expression into PHP code
1691 * @param string $var_expr
1692 * @param string $output
1695 function _parse_var($var_expr)
1698 $_math_vars = preg_split('~('.$this->_dvar_math_regexp.'|'.$this->_qstr_regexp.')~', $var_expr, -1, PREG_SPLIT_DELIM_CAPTURE);
1700 if(count($_math_vars) > 1) {
1702 $_complete_var = "";
1704 // simple check if there is any math, to stop recursion (due to modifiers with "xx % yy" as parameter)
1705 foreach($_math_vars as $_k => $_math_var) {
1706 $_math_var = $_math_vars[$_k];
1708 if(!empty($_math_var) || is_numeric($_math_var)) {
1709 // hit a math operator, so process the stuff which came before it
1710 if(preg_match('~^' . $this->_dvar_math_regexp . '$~', $_math_var)) {
1712 if(!empty($_complete_var) || is_numeric($_complete_var)) {
1713 $_output .= $this->_parse_var($_complete_var);
1716 // just output the math operator to php
1717 $_output .= $_math_var;
1719 if(empty($_first_var))
1720 $_first_var = $_complete_var;
1722 $_complete_var = "";
1724 $_complete_var .= $_math_var;
1729 if(!empty($_complete_var) || is_numeric($_complete_var))
1730 $_output .= $this->_parse_var($_complete_var);
1732 // get the modifiers working (only the last var from math + modifier is left)
1733 $var_expr = $_complete_var;
1737 // prevent cutting of first digit in the number (we _definitly_ got a number if the first char is a digit)
1738 if(is_numeric(substr($var_expr, 0, 1)))
1739 $_var_ref = $var_expr;
1741 $_var_ref = substr($var_expr, 1);
1745 // get [foo] and .foo and ->foo and (...) pieces
1746 preg_match_all('~(?:^\w+)|' . $this->_obj_params_regexp . '|(?:' . $this->_var_bracket_regexp . ')|->\$?\w+|\.\$?\w+|\S+~', $_var_ref, $match);
1748 $_indexes = $match[0];
1749 $_var_name = array_shift($_indexes);
1751 /* Handle $smarty.* variable references as a special case. */
1752 if ($_var_name == 'smarty') {
1754 * If the reference could be compiled, use the compiled output;
1755 * otherwise, fall back on the $smarty variable generated at
1758 if (($smarty_ref = $this->_compile_smarty_ref($_indexes)) !== null) {
1759 $_output = $smarty_ref;
1761 $_var_name = substr(array_shift($_indexes), 1);
1762 $_output = "\$this->_smarty_vars['$_var_name']";
1764 } elseif(is_numeric($_var_name) && is_numeric(substr($var_expr, 0, 1))) {
1765 // because . is the operator for accessing arrays thru inidizes we need to put it together again for floating point numbers
1766 if(count($_indexes) > 0)
1768 $_var_name .= implode("", $_indexes);
1769 $_indexes = array();
1771 $_output = $_var_name;
1773 $_output = "\$this->_tpl_vars['$_var_name']";
1776 foreach ($_indexes as $_index) {
1777 if (substr($_index, 0, 1) == '[') {
1778 $_index = substr($_index, 1, -1);
1779 if (is_numeric($_index)) {
1780 $_output .= "[$_index]";
1781 } elseif (substr($_index, 0, 1) == '$') {
1782 if (strpos($_index, '.') !== false) {
1783 $_output .= '[' . $this->_parse_var($_index) . ']';
1785 $_output .= "[\$this->_tpl_vars['" . substr($_index, 1) . "']]";
1788 $_var_parts = explode('.', $_index);
1789 $_var_section = $_var_parts[0];
1790 $_var_section_prop = isset($_var_parts[1]) ? $_var_parts[1] : 'index';
1791 $_output .= "[\$this->_sections['$_var_section']['$_var_section_prop']]";
1793 } else if (substr($_index, 0, 1) == '.') {
1794 if (substr($_index, 1, 1) == '$')
1795 $_output .= "[\$this->_tpl_vars['" . substr($_index, 2) . "']]";
1797 $_output .= "['" . substr($_index, 1) . "']";
1798 } else if (substr($_index,0,2) == '->') {
1799 if(substr($_index,2,2) == '__') {
1800 $this->_syntax_error('call to internal object members is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1801 } elseif($this->security && substr($_index, 2, 1) == '_') {
1802 $this->_syntax_error('(secure) call to private object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1803 } elseif (substr($_index, 2, 1) == '$') {
1804 if ($this->security) {
1805 $this->_syntax_error('(secure) call to dynamic object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1807 $_output .= '->{(($_var=$this->_tpl_vars[\''.substr($_index,3).'\']) && substr($_var,0,2)!=\'__\') ? $_var : $this->trigger_error("cannot access property \\"$_var\\"")}';
1810 $_output .= $_index;
1812 } elseif (substr($_index, 0, 1) == '(') {
1813 $_index = $this->_parse_parenth_args($_index);
1814 $_output .= $_index;
1816 $_output .= $_index;
1825 * parse arguments in function call parenthesis
1827 * @param string $parenth_args
1830 function _parse_parenth_args($parenth_args)
1832 preg_match_all('~' . $this->_param_regexp . '~',$parenth_args, $match);
1833 $orig_vals = $match = $match[0];
1834 $this->_parse_vars_props($match);
1836 for ($i = 0, $count = count($match); $i < $count; $i++) {
1837 $replace[$orig_vals[$i]] = $match[$i];
1839 return strtr($parenth_args, $replace);
1843 * parse configuration variable expression into PHP code
1845 * @param string $conf_var_expr
1847 function _parse_conf_var($conf_var_expr)
1849 $parts = explode('|', $conf_var_expr, 2);
1850 $var_ref = $parts[0];
1851 $modifiers = isset($parts[1]) ? $parts[1] : '';
1853 $var_name = substr($var_ref, 1, -1);
1855 $output = "\$this->_config[0]['vars']['$var_name']";
1857 $this->_parse_modifiers($output, $modifiers);
1863 * parse section property expression into PHP code
1865 * @param string $section_prop_expr
1868 function _parse_section_prop($section_prop_expr)
1870 $parts = explode('|', $section_prop_expr, 2);
1871 $var_ref = $parts[0];
1872 $modifiers = isset($parts[1]) ? $parts[1] : '';
1874 preg_match('!%(\w+)\.(\w+)%!', $var_ref, $match);
1875 $section_name = $match[1];
1876 $prop_name = $match[2];
1878 $output = "\$this->_sections['$section_name']['$prop_name']";
1880 $this->_parse_modifiers($output, $modifiers);
1887 * parse modifier chain into PHP code
1889 * sets $output to parsed modified chain
1890 * @param string $output
1891 * @param string $modifier_string
1893 function _parse_modifiers(&$output, $modifier_string)
1895 preg_match_all('~\|(@?\w+)((?>:(?:'. $this->_qstr_regexp . '|[^|]+))*)~', '|' . $modifier_string, $_match);
1896 list(, $_modifiers, $modifier_arg_strings) = $_match;
1898 for ($_i = 0, $_for_max = count($_modifiers); $_i < $_for_max; $_i++) {
1899 $_modifier_name = $_modifiers[$_i];
1901 if($_modifier_name == 'smarty') {
1902 // skip smarty modifier
1906 preg_match_all('~:(' . $this->_qstr_regexp . '|[^:]+)~', $modifier_arg_strings[$_i], $_match);
1907 $_modifier_args = $_match[1];
1909 if (substr($_modifier_name, 0, 1) == '@') {
1910 $_map_array = false;
1911 $_modifier_name = substr($_modifier_name, 1);
1916 if (empty($this->_plugins['modifier'][$_modifier_name])
1917 && !$this->_get_plugin_filepath('modifier', $_modifier_name)
1918 && function_exists($_modifier_name)) {
1919 if ($this->security && !in_array($_modifier_name, $this->security_settings['MODIFIER_FUNCS'])) {
1920 $this->_trigger_fatal_error("[plugin] (secure mode) modifier '$_modifier_name' is not allowed" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
1922 $this->_plugins['modifier'][$_modifier_name] = array($_modifier_name, null, null, false);
1925 $this->_add_plugin('modifier', $_modifier_name);
1927 $this->_parse_vars_props($_modifier_args);
1929 if($_modifier_name == 'default') {
1930 // supress notifications of default modifier vars and args
1931 if(substr($output, 0, 1) == '$') {
1932 $output = '@' . $output;
1934 if(isset($_modifier_args[0]) && substr($_modifier_args[0], 0, 1) == '$') {
1935 $_modifier_args[0] = '@' . $_modifier_args[0];
1938 if (count($_modifier_args) > 0)
1939 $_modifier_args = ', '.implode(', ', $_modifier_args);
1941 $_modifier_args = '';
1944 $output = "((is_array(\$_tmp=$output)) ? \$this->_run_mod_handler('$_modifier_name', true, \$_tmp$_modifier_args) : " . $this->_compile_plugin_call('modifier', $_modifier_name) . "(\$_tmp$_modifier_args))";
1948 $output = $this->_compile_plugin_call('modifier', $_modifier_name)."($output$_modifier_args)";
1958 * @param string $type
1959 * @param string $name
1960 * @param boolean? $delayed_loading
1962 function _add_plugin($type, $name, $delayed_loading = null)
1964 if (!isset($this->_plugin_info[$type])) {
1965 $this->_plugin_info[$type] = array();
1967 if (!isset($this->_plugin_info[$type][$name])) {
1968 $this->_plugin_info[$type][$name] = array($this->_current_file,
1969 $this->_current_line_no,
1976 * Compiles references of type $smarty.foo
1978 * @param string $indexes
1981 function _compile_smarty_ref(&$indexes)
1983 /* Extract the reference name. */
1984 $_ref = substr($indexes[0], 1);
1985 foreach($indexes as $_index_no=>$_index) {
1986 if (substr($_index, 0, 1) != '.' && $_index_no<2 || !preg_match('~^(\.|\[|->)~', $_index)) {
1987 $this->_syntax_error('$smarty' . implode('', array_slice($indexes, 0, 2)) . ' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
1993 $compiled_ref = 'time()';
1998 array_shift($indexes);
1999 $_var = $this->_parse_var_props(substr($indexes[0], 1));
2000 $_propname = substr($indexes[1], 1);
2002 switch ($_propname) {
2004 array_shift($indexes);
2005 $compiled_ref = "(\$this->_foreach[$_var]['iteration']-1)";
2009 array_shift($indexes);
2010 $compiled_ref = "(\$this->_foreach[$_var]['iteration'] <= 1)";
2014 array_shift($indexes);
2015 $compiled_ref = "(\$this->_foreach[$_var]['iteration'] == \$this->_foreach[$_var]['total'])";
2019 array_shift($indexes);
2020 $compiled_ref = "(\$this->_foreach[$_var]['total'] > 0)";
2025 $compiled_ref = "\$this->_foreach[$_var]";
2030 array_shift($indexes);
2031 $_var = $this->_parse_var_props(substr($indexes[0], 1));
2032 $compiled_ref = "\$this->_sections[$_var]";
2036 $compiled_ref = ($this->request_use_auto_globals) ? '$_GET' : "\$GLOBALS['HTTP_GET_VARS']";
2040 $compiled_ref = ($this->request_use_auto_globals) ? '$_POST' : "\$GLOBALS['HTTP_POST_VARS']";
2044 $compiled_ref = ($this->request_use_auto_globals) ? '$_COOKIE' : "\$GLOBALS['HTTP_COOKIE_VARS']";
2048 $compiled_ref = ($this->request_use_auto_globals) ? '$_ENV' : "\$GLOBALS['HTTP_ENV_VARS']";
2052 $compiled_ref = ($this->request_use_auto_globals) ? '$_SERVER' : "\$GLOBALS['HTTP_SERVER_VARS']";
2056 $compiled_ref = ($this->request_use_auto_globals) ? '$_SESSION' : "\$GLOBALS['HTTP_SESSION_VARS']";
2060 * These cases are handled either at run-time or elsewhere in the
2064 if ($this->request_use_auto_globals) {
2065 $compiled_ref = '$_REQUEST';
2068 $this->_init_smarty_vars = true;
2076 $compiled_ref = "'$this->_current_file'";
2081 $compiled_ref = "'$this->_version'";
2086 if ($this->security && !$this->security_settings['ALLOW_CONSTANTS']) {
2087 $this->_syntax_error("(secure mode) constants not permitted",
2088 E_USER_WARNING, __FILE__, __LINE__);
2091 array_shift($indexes);
2092 if (preg_match('!^\.\w+$!', $indexes[0])) {
2093 $compiled_ref = '@' . substr($indexes[0], 1);
2095 $_val = $this->_parse_var_props(substr($indexes[0], 1));
2096 $compiled_ref = '@constant(' . $_val . ')';
2102 $compiled_ref = "\$this->_config[0]['vars']";
2107 $compiled_ref = "'$this->left_delimiter'";
2111 $compiled_ref = "'$this->right_delimiter'";
2115 $this->_syntax_error('$smarty.' . $_ref . ' is an unknown reference', E_USER_ERROR, __FILE__, __LINE__);
2119 if (isset($_max_index) && count($indexes) > $_max_index) {
2120 $this->_syntax_error('$smarty' . implode('', $indexes) .' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
2123 array_shift($indexes);
2124 return $compiled_ref;
2128 * compiles call to plugin of type $type with name $name
2129 * returns a string containing the function-name or method call
2130 * without the paramter-list that would have follow to make the
2131 * call valid php-syntax
2133 * @param string $type
2134 * @param string $name
2137 function _compile_plugin_call($type, $name) {
2138 if (isset($this->_plugins[$type][$name])) {
2140 if (is_array($this->_plugins[$type][$name][0])) {
2141 return ((is_object($this->_plugins[$type][$name][0][0])) ?
2142 "\$this->_plugins['$type']['$name'][0][0]->" /* method callback */
2143 : (string)($this->_plugins[$type][$name][0][0]).'::' /* class callback */
2144 ). $this->_plugins[$type][$name][0][1];
2147 /* function callback */
2148 return $this->_plugins[$type][$name][0];
2152 /* plugin not loaded -> auto-loadable-plugin */
2153 return 'smarty_'.$type.'_'.$name;
2159 * load pre- and post-filters
2161 function _load_filters()
2163 if(!function_exists('smarty_core_load_plugins') && (count($this->_plugins['prefilter']) > 0 || count($this->_plugins['postfilter']) > 0)) {
2164 require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
2166 if (count($this->_plugins['prefilter']) > 0) {
2167 foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
2168 if ($prefilter === false) {
2169 unset($this->_plugins['prefilter'][$filter_name]);
2170 $_params = array('plugins' => array(array('prefilter', $filter_name, null, null, false)));
2171 smarty_core_load_plugins($_params, $this);
2175 if (count($this->_plugins['postfilter']) > 0) {
2176 foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
2177 if ($postfilter === false) {
2178 unset($this->_plugins['postfilter'][$filter_name]);
2179 $_params = array('plugins' => array(array('postfilter', $filter_name, null, null, false)));
2180 smarty_core_load_plugins($_params, $this);
2188 * Quote subpattern references
2190 * @param string $string
2193 function _quote_replace($string)
2195 return strtr($string, array('\\' => '\\\\', '$' => '\\$'));
2199 * display Smarty syntax error
2201 * @param string $error_msg
2202 * @param integer $error_type
2203 * @param string $file
2204 * @param integer $line
2206 function _syntax_error($error_msg, $error_type = E_USER_ERROR, $file=null, $line=null)
2208 $this->_trigger_fatal_error("syntax error: $error_msg", $this->_current_file, $this->_current_line_no, $file, $line, $error_type);
2213 * check if the compilation changes from cacheable to
2214 * non-cacheable state with the beginning of the current
2215 * plugin. return php-code to reflect the transition.
2218 function _push_cacheable_state($type, $name) {
2219 $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2221 || 0<$this->_cacheable_state++) return '';
2222 if (!isset($this->_cache_serial)) $this->_cache_serial = md5(uniqid('Smarty'));
2223 $_ret = 'if ($this->caching && !$this->_cache_including) { echo \'{nocache:'
2224 . $this->_cache_serial . '#' . $this->_nocache_count
2231 * check if the compilation changes from non-cacheable to
2232 * cacheable state with the end of the current plugin return
2233 * php-code to reflect the transition.
2236 function _pop_cacheable_state($type, $name) {
2237 $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2239 || --$this->_cacheable_state>0) return '';
2240 return 'if ($this->caching && !$this->_cache_including) { echo \'{/nocache:'
2241 . $this->_cache_serial . '#' . ($this->_nocache_count++)
2247 * push opening tag-name, file-name and line-number on the tag-stack
2248 * @param string the opening tag's name
2250 function _push_tag($open_tag)
2252 array_push($this->_tag_stack, array($open_tag, $this->_current_line_no));
2256 * pop closing tag-name
2257 * raise an error if this stack-top doesn't match with the closing tag
2258 * @param string the closing tag's name
2259 * @return string the opening tag's name
2261 function _pop_tag($close_tag)
2264 if (count($this->_tag_stack)>0) {
2265 list($_open_tag, $_line_no) = array_pop($this->_tag_stack);
2266 if ($close_tag == $_open_tag) {
2269 if ($close_tag == 'if' && ($_open_tag == 'else' || $_open_tag == 'elseif' )) {
2270 return $this->_pop_tag($close_tag);
2272 if ($close_tag == 'section' && $_open_tag == 'sectionelse') {
2273 $this->_pop_tag($close_tag);
2276 if ($close_tag == 'foreach' && $_open_tag == 'foreachelse') {
2277 $this->_pop_tag($close_tag);
2280 if ($_open_tag == 'else' || $_open_tag == 'elseif') {
2282 } elseif ($_open_tag == 'sectionelse') {
2283 $_open_tag = 'section';
2284 } elseif ($_open_tag == 'foreachelse') {
2285 $_open_tag = 'foreach';
2287 $message = " expected {/$_open_tag} (opened line $_line_no).";
2289 $this->_syntax_error("mismatched tag {/$close_tag}.$message",
2290 E_USER_ERROR, __FILE__, __LINE__);
2296 * compare to values by their string length
2303 function _smarty_sort_length($a, $b)
2308 if(strlen($a) == strlen($b))
2309 return ($a > $b) ? -1 : 1;
2311 return (strlen($a) > strlen($b)) ? -1 : 1;