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 * You may contact the authors of Smarty by e-mail at:
27 * Director of Technology, ispi
28 * 237 S. 70th suite 220
31 * The latest version of Smarty can be obtained from:
32 * http://smarty.php.net/
34 * @link http://smarty.php.net/
35 * @author Monte Ohrt <monte@ispi.net>
36 * @author Andrei Zmievski <andrei@php.net>
38 * @copyright 2001-2003 ispi of Lincoln, Inc.
42 /* $Id: Smarty_Compiler.class.php,v 1.1.1.1 2004/01/13 16:02:46 gohr Exp $ */
45 * Template compiling class
48 class Smarty_Compiler extends Smarty {
54 var $_sectionelse_stack = array(); // keeps track of whether section had 'else' part
55 var $_foreachelse_stack = array(); // keeps track of whether foreach had 'else' part
56 var $_literal_blocks = array(); // keeps literal template blocks
57 var $_php_blocks = array(); // keeps php code blocks
58 var $_current_file = null; // the current template being compiled
59 var $_current_line_no = 1; // line number for error messages
60 var $_capture_stack = array(); // keeps track of nested capture buffers
61 var $_plugin_info = array(); // keeps track of plugins to load
62 var $_init_smarty_vars = false;
63 var $_permitted_tokens = array('true','false','yes','no','on','off','null');
64 var $_db_qstr_regexp = null; // regexps are setup in the constructor
65 var $_si_qstr_regexp = null;
66 var $_qstr_regexp = null;
67 var $_func_regexp = null;
68 var $_var_bracket_regexp = null;
69 var $_dvar_guts_regexp = null;
70 var $_dvar_regexp = null;
71 var $_cvar_regexp = null;
72 var $_svar_regexp = null;
73 var $_avar_regexp = null;
74 var $_mod_regexp = null;
75 var $_var_regexp = null;
76 var $_parenth_param_regexp = null;
77 var $_func_call_regexp = null;
78 var $_obj_ext_regexp = null;
79 var $_obj_start_regexp = null;
80 var $_obj_params_regexp = null;
81 var $_obj_call_regexp = null;
82 var $_cacheable_state = 0;
83 var $_cache_attrs_count = 0;
84 var $_nocache_count = 0;
85 var $_cache_serial = null;
86 var $_cache_include = null;
88 var $_strip_depth = 0;
89 var $_additional_newline = "\n";
93 * The class constructor.
95 function Smarty_Compiler()
97 // matches double quoted strings:
100 $this->_db_qstr_regexp = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
102 // matches single quoted strings:
105 $this->_si_qstr_regexp = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
107 // matches single or double quoted strings
108 $this->_qstr_regexp = '(?:' . $this->_db_qstr_regexp . '|' . $this->_si_qstr_regexp . ')';
110 // matches bracket portion of vars
114 $this->_var_bracket_regexp = '\[\$?[\w\.]+\]';
116 // matches $ vars (not objects):
123 // $foo[5].bar[$foobar][4]
124 $this->_dvar_math_regexp = '[\+\-\*\/\%]';
125 $this->_dvar_math_var_regexp = '[\$\w\.\+\-\*\/\%\d\>\[\]]';
126 $this->_dvar_num_var_regexp = '\-?\d+(?:\.\d+)?' . $this->_dvar_math_var_regexp;
127 $this->_dvar_guts_regexp = '\w+(?:' . $this->_var_bracket_regexp
128 . ')*(?:\.\$?\w+(?:' . $this->_var_bracket_regexp . ')*)*(?:' . $this->_dvar_math_regexp . '(?:\-?\d+(?:\.\d+)?|' . $this->_dvar_math_var_regexp . ')*)?';
129 $this->_dvar_regexp = '\$' . $this->_dvar_guts_regexp;
131 // matches config vars:
134 $this->_cvar_regexp = '\#\w+\#';
136 // matches section vars:
138 $this->_svar_regexp = '\%\w+\.\w+\%';
140 // matches all valid variables (no quotes, no modifiers)
141 $this->_avar_regexp = '(?:' . $this->_dvar_regexp . '|'
142 . $this->_cvar_regexp . '|' . $this->_svar_regexp . ')';
144 // matches valid variable syntax:
151 $this->_var_regexp = '(?:' . $this->_avar_regexp . '|' . $this->_qstr_regexp . ')';
153 // matches valid object call (no objects allowed in parameters):
157 // $foo->bar($foo, $bar, "text")
158 // $foo->bar($foo, "foo")
160 // $foo->bar->foo->bar()
161 $this->_obj_ext_regexp = '\->(?:\$?' . $this->_dvar_guts_regexp . ')';
162 $this->_obj_params_regexp = '\((?:\w+|'
163 . $this->_var_regexp . '(?:\s*,\s*(?:(?:\w+|'
164 . $this->_var_regexp . ')))*)?\)';
165 $this->_obj_start_regexp = '(?:' . $this->_dvar_regexp . '(?:' . $this->_obj_ext_regexp . ')+)';
166 $this->_obj_call_regexp = '(?:' . $this->_obj_start_regexp . '(?:' . $this->_obj_params_regexp . ')?)';
168 // matches valid modifier syntax:
173 // |foo:"bar":$foobar
176 $this->_mod_regexp = '(?:\|@?\w+(?::(?>-?\w+|'
177 . $this->_obj_call_regexp . '|' . $this->_avar_regexp . '|' . $this->_qstr_regexp .'))*)';
179 // matches valid function name:
182 $this->_func_regexp = '[a-zA-Z_]\w*';
184 // matches valid registered object:
186 $this->_reg_obj_regexp = '[a-zA-Z_]\w*->[a-zA-Z_]\w*';
188 // matches valid parameter values:
197 $this->_param_regexp = '(?:\s*(?:' . $this->_obj_call_regexp . '|'
198 . $this->_var_regexp . '|\w+)(?>' . $this->_mod_regexp . '*)\s*)';
200 // matches valid parenthesised function parameters:
203 // $foo, $bar, "text"
204 // $foo|bar, "foo"|bar, $foo->bar($foo)|bar
205 $this->_parenth_param_regexp = '(?:\((?:\w+|'
206 . $this->_param_regexp . '(?:\s*,\s*(?:(?:\w+|'
207 . $this->_param_regexp . ')))*)?\))';
209 // matches valid function call:
212 // _foo_bar($foo,"bar")
213 // foo123($foo,$foo->bar(),"foo")
214 $this->_func_call_regexp = '(?:' . $this->_func_regexp . '\s*(?:'
215 . $this->_parenth_param_regexp . '))';
221 * sets $compiled_content to the compiled source
222 * @param string $resource_name
223 * @param string $source_content
224 * @param string $compiled_content
227 function _compile_file($resource_name, $source_content, &$compiled_content)
230 if ($this->security) {
231 // do not allow php syntax to be executed unless specified
232 if ($this->php_handling == SMARTY_PHP_ALLOW &&
233 !$this->security_settings['PHP_HANDLING']) {
234 $this->php_handling = SMARTY_PHP_PASSTHRU;
238 $this->_load_filters();
240 $this->_current_file = $resource_name;
241 $this->_current_line_no = 1;
242 $ldq = preg_quote($this->left_delimiter, '!');
243 $rdq = preg_quote($this->right_delimiter, '!');
245 // run template source through prefilter functions
246 if (count($this->_plugins['prefilter']) > 0) {
247 foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
248 if ($prefilter === false) continue;
249 if ($prefilter[3] || is_callable($prefilter[0])) {
250 $source_content = call_user_func_array($prefilter[0],
251 array($source_content, &$this));
252 $this->_plugins['prefilter'][$filter_name][3] = true;
254 $this->_trigger_fatal_error("[plugin] prefilter '$filter_name' is not implemented");
259 /* Annihilate the comments. */
260 $source_content = preg_replace("!({$ldq})\*(.*?)\*({$rdq})!se",
261 "'\\1*'.str_repeat(\"\n\", substr_count('\\2', \"\n\")) .'*\\3'",
264 /* Pull out the literal blocks. */
265 preg_match_all("!{$ldq}\s*literal\s*{$rdq}(.*?){$ldq}\s*/literal\s*{$rdq}!s", $source_content, $_match);
266 $this->_literal_blocks = $_match[1];
267 $source_content = preg_replace("!{$ldq}\s*literal\s*{$rdq}(.*?){$ldq}\s*/literal\s*{$rdq}!s",
268 $this->_quote_replace($this->left_delimiter.'literal'.$this->right_delimiter), $source_content);
270 /* Pull out the php code blocks. */
271 preg_match_all("!{$ldq}php{$rdq}(.*?){$ldq}/php{$rdq}!s", $source_content, $_match);
272 $this->_php_blocks = $_match[1];
273 $source_content = preg_replace("!{$ldq}php{$rdq}(.*?){$ldq}/php{$rdq}!s",
274 $this->_quote_replace($this->left_delimiter.'php'.$this->right_delimiter), $source_content);
276 /* Gather all template tags. */
277 preg_match_all("!{$ldq}\s*(.*?)\s*{$rdq}!s", $source_content, $_match);
278 $template_tags = $_match[1];
279 /* Split content by template tags to obtain non-template content. */
280 $text_blocks = preg_split("!{$ldq}.*?{$rdq}!s", $source_content);
282 /* loop through text blocks */
283 for ($curr_tb = 0, $for_max = count($text_blocks); $curr_tb < $for_max; $curr_tb++) {
284 /* match anything resembling php tags */
285 if (preg_match_all('!(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?php[\"\']?)!is', $text_blocks[$curr_tb], $sp_match)) {
286 /* replace tags with placeholders to prevent recursive replacements */
287 $sp_match[1] = array_unique($sp_match[1]);
288 usort($sp_match[1], '_smarty_sort_length');
289 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) {
290 $text_blocks[$curr_tb] = str_replace($sp_match[1][$curr_sp],'%%%SMARTYSP'.$curr_sp.'%%%',$text_blocks[$curr_tb]);
292 /* process each one */
293 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) {
294 if ($this->php_handling == SMARTY_PHP_PASSTHRU) {
295 /* echo php contents */
296 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '<?php echo \''.str_replace("'", "\'", $sp_match[1][$curr_sp]).'\'; ?>'."\n", $text_blocks[$curr_tb]);
297 } else if ($this->php_handling == SMARTY_PHP_QUOTE) {
299 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', htmlspecialchars($sp_match[1][$curr_sp]), $text_blocks[$curr_tb]);
300 } else if ($this->php_handling == SMARTY_PHP_REMOVE) {
301 /* remove php tags */
302 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '', $text_blocks[$curr_tb]);
304 /* SMARTY_PHP_ALLOW, but echo non php starting tags */
305 $sp_match[1][$curr_sp] = preg_replace('%(<\?(?!php|=|$))%i', '<?php echo \'\\1\'?>'."\n", $sp_match[1][$curr_sp]);
306 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', $sp_match[1][$curr_sp], $text_blocks[$curr_tb]);
312 /* Compile the template tags into PHP code. */
313 $compiled_tags = array();
314 for ($i = 0, $for_max = count($template_tags); $i < $for_max; $i++) {
315 $this->_current_line_no += substr_count($text_blocks[$i], "\n");
316 $compiled_tags[] = $this->_compile_tag($template_tags[$i]);
317 $this->_current_line_no += substr_count($template_tags[$i], "\n");
320 $compiled_content = '';
322 /* Interleave the compiled contents and text blocks to get the final result. */
323 for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) {
324 if ($compiled_tags[$i] == '') {
325 // tag result empty, remove first newline from following text block
326 $text_blocks[$i+1] = preg_replace('!^(\r\n|\r|\n)!', '', $text_blocks[$i+1]);
328 $compiled_content .= $text_blocks[$i].$compiled_tags[$i];
330 $compiled_content .= $text_blocks[$i];
332 /* Reformat data between 'strip' and '/strip' tags, removing spaces, tabs and newlines. */
333 if (preg_match_all("!{$ldq}strip{$rdq}.*?{$ldq}/strip{$rdq}!s", $compiled_content, $_match)) {
334 $strip_tags = $_match[0];
335 $strip_tags_modified = preg_replace("!{$ldq}/?strip{$rdq}|[\t ]+$|^[\t ]+!m", '', $strip_tags);
336 $strip_tags_modified = preg_replace('![\r\n]+!m', '', $strip_tags_modified);
337 for ($i = 0, $for_max = count($strip_tags); $i < $for_max; $i++)
338 $compiled_content = preg_replace("!{$ldq}strip{$rdq}.*?{$ldq}/strip{$rdq}!s",
339 $this->_quote_replace($strip_tags_modified[$i]),
340 $compiled_content, 1);
343 // remove \n from the end of the file, if any
344 if (($_len=strlen($compiled_content)) && ($compiled_content{$_len - 1} == "\n" )) {
345 $compiled_content = substr($compiled_content, 0, -1);
348 if (!empty($this->_cache_serial)) {
349 $compiled_content = "<?php \$this->_cache_serials['".$this->_cache_include."'] = '".$this->_cache_serial."'; ?>" . $compiled_content;
352 // remove unnecessary close/open tags
353 $compiled_content = preg_replace('!\?>\n?<\?php!', '', $compiled_content);
355 // run compiled template through postfilter functions
356 if (count($this->_plugins['postfilter']) > 0) {
357 foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
358 if ($postfilter === false) continue;
359 if ($postfilter[3] || is_callable($postfilter[0])) {
360 $compiled_content = call_user_func_array($postfilter[0],
361 array($compiled_content, &$this));
362 $this->_plugins['postfilter'][$filter_name][3] = true;
364 $this->_trigger_fatal_error("Smarty plugin error: postfilter '$filter_name' is not implemented");
369 // put header at the top of the compiled template
370 $template_header = "<?php /* Smarty version ".$this->_version.", created on ".strftime("%Y-%m-%d %H:%M:%S")."\n";
371 $template_header .= " compiled from ".strtr(urlencode($resource_name), array('%2F'=>'/', '%3A'=>':'))." */ ?>\n";
373 /* Emit code to load needed plugins. */
374 $this->_plugins_code = '';
375 if (count($this->_plugin_info)) {
376 $_plugins_params = "array('plugins' => array(";
377 foreach ($this->_plugin_info as $plugin_type => $plugins) {
378 foreach ($plugins as $plugin_name => $plugin_info) {
379 $_plugins_params .= "array('$plugin_type', '$plugin_name', '$plugin_info[0]', $plugin_info[1], ";
380 $_plugins_params .= $plugin_info[2] ? 'true),' : 'false),';
383 $_plugins_params .= '))';
384 $plugins_code = "<?php require_once(SMARTY_DIR . 'core' . DIRECTORY_SEPARATOR . 'core.load_plugins.php');\nsmarty_core_load_plugins($_plugins_params, \$this); ?>\n";
385 $template_header .= $plugins_code;
386 $this->_plugin_info = array();
387 $this->_plugins_code = $plugins_code;
390 if ($this->_init_smarty_vars) {
391 $template_header .= "<?php require_once(SMARTY_DIR . 'core' . DIRECTORY_SEPARATOR . 'core.assign_smarty_interface.php');\nsmarty_core_assign_smarty_interface(null, \$this); ?>\n";
392 $this->_init_smarty_vars = false;
395 $compiled_content = $template_header . $compiled_content;
401 * Compile a template tag
403 * @param string $template_tag
406 function _compile_tag($template_tag)
408 /* Matched comment. */
409 if ($template_tag{0} == '*' && $template_tag{strlen($template_tag) - 1} == '*')
412 /* Split tag into two three parts: command, command modifiers and the arguments. */
413 if(! preg_match('/^(?:(' . $this->_obj_call_regexp . '|' . $this->_var_regexp
414 . '|\/?' . $this->_reg_obj_regexp . '|\/?' . $this->_func_regexp . ')(' . $this->_mod_regexp . '*))
416 /xs', $template_tag, $match)) {
417 $this->_syntax_error("unrecognized tag: $template_tag", E_USER_ERROR, __FILE__, __LINE__);
420 $tag_command = $match[1];
421 $tag_modifier = isset($match[2]) ? $match[2] : null;
422 $tag_args = isset($match[3]) ? $match[3] : null;
424 if (preg_match('!^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '$!', $tag_command)) {
425 /* tag name is a variable or object */
426 $_return = $this->_parse_var_props($tag_command . $tag_modifier, $this->_parse_attrs($tag_args));
427 if(isset($_tag_attrs['assign'])) {
428 return "<?php \$this->assign('" . $this->_dequote($_tag_attrs['assign']) . "', $_return ); ?>\n";
430 return "<?php echo $_return; ?>" . $this->_additional_newline;
434 /* If the tag name is a registered object, we process it. */
435 if (preg_match('!^\/?' . $this->_reg_obj_regexp . '$!', $tag_command)) {
436 return $this->_compile_registered_object_tag($tag_command, $this->_parse_attrs($tag_args), $tag_modifier);
439 switch ($tag_command) {
441 return $this->_compile_include_tag($tag_args);
444 return $this->_compile_include_php_tag($tag_args);
447 return $this->_compile_if_tag($tag_args);
450 return '<?php else: ?>';
453 return $this->_compile_if_tag($tag_args, true);
456 return '<?php endif; ?>';
459 return $this->_compile_capture_tag(true, $tag_args);
462 return $this->_compile_capture_tag(false);
465 return $this->left_delimiter;
468 return $this->right_delimiter;
471 array_push($this->_sectionelse_stack, false);
472 return $this->_compile_section_start($tag_args);
475 $this->_sectionelse_stack[count($this->_sectionelse_stack)-1] = true;
476 return "<?php endfor; else: ?>";
479 if (array_pop($this->_sectionelse_stack))
480 return "<?php endif; ?>";
482 return "<?php endfor; endif; ?>";
485 array_push($this->_foreachelse_stack, false);
486 return $this->_compile_foreach_start($tag_args);
490 $this->_foreachelse_stack[count($this->_foreachelse_stack)-1] = true;
491 return "<?php endforeach; unset(\$_from); else: ?>";
494 if (array_pop($this->_foreachelse_stack))
495 return "<?php endif; ?>";
497 return "<?php endforeach; unset(\$_from); endif; ?>";
501 if ($tag_command{0}=='/') {
502 if (--$this->_strip_depth==0) { /* outermost closing {/strip} */
503 $this->_additional_newline = "\n";
504 return $this->left_delimiter.$tag_command.$this->right_delimiter;
507 if ($this->_strip_depth++==0) { /* outermost opening {strip} */
508 $this->_additional_newline = "";
509 return $this->left_delimiter.$tag_command.$this->right_delimiter;
515 list (,$literal_block) = each($this->_literal_blocks);
516 $this->_current_line_no += substr_count($literal_block, "\n");
517 return "<?php echo '".str_replace("'", "\'", str_replace("\\", "\\\\", $literal_block))."'; ?>" . $this->_additional_newline;
520 if ($this->security && !$this->security_settings['PHP_TAGS']) {
521 $this->_syntax_error("(secure mode) php tags not permitted", E_USER_WARNING, __FILE__, __LINE__);
524 list (,$php_block) = each($this->_php_blocks);
525 $this->_current_line_no += substr_count($php_block, "\n");
526 return '<?php '.$php_block.' ?>';
529 return $this->_compile_insert_tag($tag_args);
532 if ($this->_compile_compiler_tag($tag_command, $tag_args, $output)) {
534 } else if ($this->_compile_block_tag($tag_command, $tag_args, $tag_modifier, $output)) {
537 return $this->_compile_custom_tag($tag_command, $tag_args, $tag_modifier);
544 * compile the custom compiler tag
546 * sets $output to the compiled custom compiler tag
547 * @param string $tag_command
548 * @param string $tag_args
549 * @param string $output
552 function _compile_compiler_tag($tag_command, $tag_args, &$output)
555 $have_function = true;
558 * First we check if the compiler function has already been registered
559 * or loaded from a plugin file.
561 if (isset($this->_plugins['compiler'][$tag_command])) {
563 $plugin_func = $this->_plugins['compiler'][$tag_command][0];
564 if (!is_callable($plugin_func)) {
565 $message = "compiler function '$tag_command' is not implemented";
566 $have_function = false;
570 * Otherwise we need to load plugin file and look for the function
573 else if ($plugin_file = $this->_get_plugin_filepath('compiler', $tag_command)) {
576 include_once $plugin_file;
578 $plugin_func = 'smarty_compiler_' . $tag_command;
579 if (!is_callable($plugin_func)) {
580 $message = "plugin function $plugin_func() not found in $plugin_file\n";
581 $have_function = false;
583 $this->_plugins['compiler'][$tag_command] = array($plugin_func, null, null, null, true);
588 * True return value means that we either found a plugin or a
589 * dynamically registered function. False means that we didn't and the
590 * compiler should now emit code to load custom function plugin for this
594 if ($have_function) {
595 $output = call_user_func_array($plugin_func, array($tag_args, &$this));
597 $output = '<?php ' . $this->_push_cacheable_state('compiler', $tag_command)
599 . $this->_pop_cacheable_state('compiler', $tag_command) . ' ?>';
602 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
612 * compile block function tag
614 * sets $output to compiled block function tag
615 * @param string $tag_command
616 * @param string $tag_args
617 * @param string $tag_modifier
618 * @param string $output
621 function _compile_block_tag($tag_command, $tag_args, $tag_modifier, &$output)
623 if ($tag_command{0} == '/') {
625 $tag_command = substr($tag_command, 1);
630 $have_function = true;
633 * First we check if the block function has already been registered
634 * or loaded from a plugin file.
636 if (isset($this->_plugins['block'][$tag_command])) {
638 $plugin_func = $this->_plugins['block'][$tag_command][0];
639 if (!is_callable($plugin_func)) {
640 $message = "block function '$tag_command' is not implemented";
641 $have_function = false;
645 * Otherwise we need to load plugin file and look for the function
648 else if ($plugin_file = $this->_get_plugin_filepath('block', $tag_command)) {
651 include_once $plugin_file;
653 $plugin_func = 'smarty_block_' . $tag_command;
654 if (!function_exists($plugin_func)) {
655 $message = "plugin function $plugin_func() not found in $plugin_file\n";
656 $have_function = false;
658 $this->_plugins['block'][$tag_command] = array($plugin_func, null, null, null, true);
665 } else if (!$have_function) {
666 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
671 * Even though we've located the plugin function, compilation
672 * happens only once, so the plugin will still need to be loaded
673 * at runtime for future requests.
675 $this->_add_plugin('block', $tag_command);
678 $output = '<?php ' . $this->_push_cacheable_state('block', $tag_command);
679 $attrs = $this->_parse_attrs($tag_args);
680 $arg_list = $this->_compile_arg_list('block', $tag_command, $attrs, $_cache_attrs='');
681 $output .= "$_cache_attrs\$_params = \$this->_tag_stack[] = array('$tag_command', array(".implode(',', $arg_list).')); ';
682 $output .= $this->_compile_plugin_call('block', $tag_command).'($_params[1], null, $this, $_block_repeat=true); unset($_params);';
683 $output .= 'while ($_block_repeat) { ob_start(); ?>';
685 $output = '<?php $this->_block_content = ob_get_contents(); ob_end_clean(); ';
686 $_out_tag_text = $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], $this->_block_content, $this, $_block_repeat=false)';
687 if ($tag_modifier != '') {
688 $this->_parse_modifiers($_out_tag_text, $tag_modifier);
690 $output .= 'echo '.$_out_tag_text.'; } ';
691 $output .= " array_pop(\$this->_tag_stack); " . $this->_pop_cacheable_state('block', $tag_command) . '?>';
699 * compile custom function tag
701 * @param string $tag_command
702 * @param string $tag_args
703 * @param string $tag_modifier
706 function _compile_custom_tag($tag_command, $tag_args, $tag_modifier)
708 $this->_add_plugin('function', $tag_command);
710 $_cacheable_state = $this->_push_cacheable_state('function', $tag_command);
711 $attrs = $this->_parse_attrs($tag_args);
712 $arg_list = $this->_compile_arg_list('function', $tag_command, $attrs, $_cache_attrs='');
714 $_return = $this->_compile_plugin_call('function', $tag_command).'(array('.implode(',', $arg_list)."), \$this)";
715 if($tag_modifier != '') {
716 $this->_parse_modifiers($_return, $tag_modifier);
720 $_return = '<?php ' . $_cacheable_state . $_cache_attrs . 'echo ' . $_return . ';'
721 . $this->_pop_cacheable_state('function', $tag_command) . "?>" . $this->_additional_newline;
728 * compile a registered object tag
730 * @param string $tag_command
731 * @param array $attrs
732 * @param string $tag_modifier
735 function _compile_registered_object_tag($tag_command, $attrs, $tag_modifier)
737 if ($tag_command{0} == '/') {
739 $tag_command = substr($tag_command, 1);
744 list($object, $obj_comp) = explode('->', $tag_command);
748 $_assign_var = false;
749 foreach ($attrs as $arg_name => $arg_value) {
750 if($arg_name == 'assign') {
751 $_assign_var = $arg_value;
752 unset($attrs['assign']);
755 if (is_bool($arg_value))
756 $arg_value = $arg_value ? 'true' : 'false';
757 $arg_list[] = "'$arg_name' => $arg_value";
761 if($this->_reg_objects[$object][2]) {
762 // smarty object argument format
763 $args = "array(".implode(',', (array)$arg_list)."), \$this";
765 // traditional argument format
766 $args = implode(',', array_values($attrs));
775 if(!is_object($this->_reg_objects[$object][0])) {
776 $this->_trigger_fatal_error("registered '$object' is not an object");
777 } elseif(!empty($this->_reg_objects[$object][1]) && !in_array($obj_comp, $this->_reg_objects[$object][1])) {
778 $this->_trigger_fatal_error("'$obj_comp' is not a registered component of object '$object'");
779 } elseif(method_exists($this->_reg_objects[$object][0], $obj_comp)) {
781 if(in_array($obj_comp, $this->_reg_objects[$object][3])) {
784 $prefix = "\$this->_tag_stack[] = array('$obj_comp', $args); ";
785 $prefix .= "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], null, \$this, \$_block_repeat=true); ";
786 $prefix .= "while (\$_block_repeat) { ob_start();";
790 $prefix = "\$this->_obj_block_content = ob_get_contents(); ob_end_clean(); ";
791 $return = "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], \$this->_obj_block_content, \$this, \$_block_repeat=false)";
792 $postfix = "} array_pop(\$this->_tag_stack);";
796 $return = "\$this->_reg_objects['$object'][0]->$obj_comp($args)";
800 $return = "\$this->_reg_objects['$object'][0]->$obj_comp";
803 if($return != null) {
804 if($tag_modifier != '') {
805 $this->_parse_modifiers($return, $tag_modifier);
808 if(!empty($_assign_var)) {
809 $output = "\$this->assign('" . $this->_dequote($_assign_var) ."', $return);";
811 $output = 'echo ' . $return . ';';
812 $newline = $this->_additional_newline;
818 return '<?php ' . $prefix . $output . $postfix . "?>" . $newline;
822 * Compile {insert ...} tag
824 * @param string $tag_args
827 function _compile_insert_tag($tag_args)
829 $attrs = $this->_parse_attrs($tag_args);
830 $name = $this->_dequote($attrs['name']);
833 $this->_syntax_error("missing insert name", E_USER_ERROR, __FILE__, __LINE__);
836 if (!empty($attrs['script'])) {
837 $delayed_loading = true;
839 $delayed_loading = false;
842 foreach ($attrs as $arg_name => $arg_value) {
843 if (is_bool($arg_value))
844 $arg_value = $arg_value ? 'true' : 'false';
845 $arg_list[] = "'$arg_name' => $arg_value";
848 $this->_add_plugin('insert', $name, $delayed_loading);
850 $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))";
852 return "<?php require_once(SMARTY_DIR . 'core' . DIRECTORY_SEPARATOR . 'core.run_insert_handler.php');\necho smarty_core_run_insert_handler($_params, \$this); ?>" . $this->_additional_newline;
856 * Compile {include ...} tag
858 * @param string $tag_args
861 function _compile_include_tag($tag_args)
863 $attrs = $this->_parse_attrs($tag_args);
866 if (empty($attrs['file'])) {
867 $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR, __FILE__, __LINE__);
870 foreach ($attrs as $arg_name => $arg_value) {
871 if ($arg_name == 'file') {
872 $include_file = $arg_value;
874 } else if ($arg_name == 'assign') {
875 $assign_var = $arg_value;
878 if (is_bool($arg_value))
879 $arg_value = $arg_value ? 'true' : 'false';
880 $arg_list[] = "'$arg_name' => $arg_value";
885 if (isset($assign_var)) {
886 $output .= "ob_start();\n";
890 "\$_smarty_tpl_vars = \$this->_tpl_vars;\n";
893 $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))";
894 $output .= "\$this->_smarty_include($_params);\n" .
895 "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" .
896 "unset(\$_smarty_tpl_vars);\n";
898 if (isset($assign_var)) {
899 $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n";
909 * Compile {include ...} tag
911 * @param string $tag_args
914 function _compile_include_php_tag($tag_args)
916 $attrs = $this->_parse_attrs($tag_args);
918 if (empty($attrs['file'])) {
919 $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR, __FILE__, __LINE__);
922 $assign_var = (empty($attrs['assign'])) ? '' : $this->_dequote($attrs['assign']);
923 $once_var = (empty($attrs['once']) || $attrs['once']=='false') ? 'false' : 'true';
925 foreach($attrs as $arg_name => $arg_value) {
926 if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') {
927 if(is_bool($arg_value))
928 $arg_value = $arg_value ? 'true' : 'false';
929 $arg_list[] = "'$arg_name' => $arg_value";
933 $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))";
935 return "<?php require_once(SMARTY_DIR . 'core' . DIRECTORY_SEPARATOR . 'core.smarty_include_php.php');\nsmarty_core_smarty_include_php($_params, \$this); ?>" . $this->_additional_newline;
940 * Compile {section ...} tag
942 * @param string $tag_args
945 function _compile_section_start($tag_args)
947 $attrs = $this->_parse_attrs($tag_args);
951 $section_name = $attrs['name'];
952 if (empty($section_name)) {
953 $this->_syntax_error("missing section name", E_USER_ERROR, __FILE__, __LINE__);
956 $output .= "if (isset(\$this->_sections[$section_name])) unset(\$this->_sections[$section_name]);\n";
957 $section_props = "\$this->_sections[$section_name]";
959 foreach ($attrs as $attr_name => $attr_value) {
960 switch ($attr_name) {
962 $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n";
966 if (is_bool($attr_value))
967 $show_attr_value = $attr_value ? 'true' : 'false';
969 $show_attr_value = "(bool)$attr_value";
970 $output .= "{$section_props}['show'] = $show_attr_value;\n";
974 $output .= "{$section_props}['$attr_name'] = $attr_value;\n";
979 $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n";
983 $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n";
987 $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR, __FILE__, __LINE__);
992 if (!isset($attrs['show']))
993 $output .= "{$section_props}['show'] = true;\n";
995 if (!isset($attrs['loop']))
996 $output .= "{$section_props}['loop'] = 1;\n";
998 if (!isset($attrs['max']))
999 $output .= "{$section_props}['max'] = {$section_props}['loop'];\n";
1001 $output .= "if ({$section_props}['max'] < 0)\n" .
1002 " {$section_props}['max'] = {$section_props}['loop'];\n";
1004 if (!isset($attrs['step']))
1005 $output .= "{$section_props}['step'] = 1;\n";
1007 if (!isset($attrs['start']))
1008 $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n";
1010 $output .= "if ({$section_props}['start'] < 0)\n" .
1011 " {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" .
1013 " {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n";
1016 $output .= "if ({$section_props}['show']) {\n";
1017 if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) {
1018 $output .= " {$section_props}['total'] = {$section_props}['loop'];\n";
1020 $output .= " {$section_props}['total'] = min(ceil(({$section_props}['step'] > 0 ? {$section_props}['loop'] - {$section_props}['start'] : {$section_props}['start']+1)/abs({$section_props}['step'])), {$section_props}['max']);\n";
1022 $output .= " if ({$section_props}['total'] == 0)\n" .
1023 " {$section_props}['show'] = false;\n" .
1025 " {$section_props}['total'] = 0;\n";
1027 $output .= "if ({$section_props}['show']):\n";
1029 for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1;
1030 {$section_props}['iteration'] <= {$section_props}['total'];
1031 {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n";
1032 $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n";
1033 $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n";
1034 $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n";
1035 $output .= "{$section_props}['first'] = ({$section_props}['iteration'] == 1);\n";
1036 $output .= "{$section_props}['last'] = ({$section_props}['iteration'] == {$section_props}['total']);\n";
1045 * Compile {foreach ...} tag.
1047 * @param string $tag_args
1050 function _compile_foreach_start($tag_args)
1052 $attrs = $this->_parse_attrs($tag_args);
1053 $arg_list = array();
1055 if (empty($attrs['from'])) {
1056 $this->_syntax_error("missing 'from' attribute", E_USER_ERROR, __FILE__, __LINE__);
1059 if (empty($attrs['item'])) {
1060 $this->_syntax_error("missing 'item' attribute", E_USER_ERROR, __FILE__, __LINE__);
1063 $from = $attrs['from'];
1064 $item = $this->_dequote($attrs['item']);
1065 if (isset($attrs['name']))
1066 $name = $attrs['name'];
1070 $output .= "if (isset(\$this->_foreach[$name])) unset(\$this->_foreach[$name]);\n";
1071 $foreach_props = "\$this->_foreach[$name]";
1076 foreach ($attrs as $attr_name => $attr_value) {
1077 switch ($attr_name) {
1079 $key = $this->_dequote($attrs['key']);
1080 $key_part = "\$this->_tpl_vars['$key'] => ";
1084 $output .= "{$foreach_props}['$attr_name'] = $attr_value;\n";
1090 $output .= "{$foreach_props}['total'] = count(\$_from = (array)$from);\n";
1091 $output .= "{$foreach_props}['show'] = {$foreach_props}['total'] > 0;\n";
1092 $output .= "if ({$foreach_props}['show']):\n";
1093 $output .= "{$foreach_props}['iteration'] = 0;\n";
1094 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1095 $output .= " {$foreach_props}['iteration']++;\n";
1096 $output .= " {$foreach_props}['first'] = ({$foreach_props}['iteration'] == 1);\n";
1097 $output .= " {$foreach_props}['last'] = ({$foreach_props}['iteration'] == {$foreach_props}['total']);\n";
1099 $output .= "if (count(\$_from = (array)$from)):\n";
1100 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1109 * Compile {capture} .. {/capture} tags
1111 * @param boolean $start true if this is the {capture} tag
1112 * @param string $tag_args
1116 function _compile_capture_tag($start, $tag_args = '')
1118 $attrs = $this->_parse_attrs($tag_args);
1121 if (isset($attrs['name']))
1122 $buffer = $attrs['name'];
1124 $buffer = "'default'";
1126 if (isset($attrs['assign']))
1127 $assign = $attrs['assign'];
1130 $output = "<?php ob_start(); ?>";
1131 $this->_capture_stack[] = array($buffer, $assign);
1133 list($buffer, $assign) = array_pop($this->_capture_stack);
1134 $output = "<?php \$this->_smarty_vars['capture'][$buffer] = ob_get_contents(); ";
1135 if (isset($assign)) {
1136 $output .= " \$this->assign($assign, ob_get_contents());";
1138 $output .= "ob_end_clean(); ?>";
1145 * Compile {if ...} tag
1147 * @param string $tag_args
1148 * @param boolean $elseif if true, uses elseif instead of if
1151 function _compile_if_tag($tag_args, $elseif = false)
1154 /* Tokenize args for 'if' tag. */
1155 preg_match_all('/(?>
1156 ' . $this->_obj_call_regexp . '(?:' . $this->_mod_regexp . '*)? | # valid object call
1157 ' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)? | # var or quoted string
1158 \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@ | # valid non-word token
1159 \b\w+\b | # valid word token
1161 )/x', $tag_args, $match);
1163 $tokens = $match[0];
1165 // make sure we have balanced parenthesis
1166 $token_count = array_count_values($tokens);
1167 if(isset($token_count['(']) && $token_count['('] != $token_count[')']) {
1168 $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR, __FILE__, __LINE__);
1171 $is_arg_stack = array();
1173 for ($i = 0; $i < count($tokens); $i++) {
1175 $token = &$tokens[$i];
1177 switch (strtolower($token)) {
1250 array_push($is_arg_stack, $i);
1254 /* If last token was a ')', we operate on the parenthesized
1255 expression. The start of the expression is on the stack.
1256 Otherwise, we operate on the last encountered token. */
1257 if ($tokens[$i-1] == ')')
1258 $is_arg_start = array_pop($is_arg_stack);
1260 $is_arg_start = $i-1;
1261 /* Construct the argument for 'is' expression, so it knows
1262 what to operate on. */
1263 $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
1265 /* Pass all tokens from next one until the end to the
1266 'is' expression parsing function. The function will
1267 return modified tokens, where the first one is the result
1268 of the 'is' expression and the rest are the tokens it
1270 $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));
1272 /* Replace the old tokens with the new ones. */
1273 array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);
1275 /* Adjust argument start so that it won't change from the
1276 current position for the next iteration. */
1281 if(preg_match('!^' . $this->_func_regexp . '$!', $token) ) {
1283 if($this->security &&
1284 !in_array($token, $this->security_settings['IF_FUNCS'])) {
1285 $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);
1287 } elseif(preg_match('!^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$!', $token)) {
1288 // object or variable
1289 $token = $this->_parse_var_props($token);
1290 } elseif(is_numeric($token)) {
1293 $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__);
1300 return '<?php elseif ('.implode(' ', $tokens).'): ?>';
1302 return '<?php if ('.implode(' ', $tokens).'): ?>';
1306 function _compile_arg_list($type, $name, $attrs, &$cache_code) {
1307 $arg_list = array();
1309 if (isset($type) && isset($name)
1310 && isset($this->_plugins[$type])
1311 && isset($this->_plugins[$type][$name])
1312 && empty($this->_plugins[$type][$name][4])
1313 && is_array($this->_plugins[$type][$name][5])
1315 /* we have a list of parameters that should be cached */
1316 $_cache_attrs = $this->_plugins[$type][$name][5];
1317 $_count = $this->_cache_attrs_count++;
1318 $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');";
1321 /* no parameters are cached */
1322 $_cache_attrs = null;
1325 foreach ($attrs as $arg_name => $arg_value) {
1326 if (is_bool($arg_value))
1327 $arg_value = $arg_value ? 'true' : 'false';
1328 if (is_null($arg_value))
1329 $arg_value = 'null';
1330 if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) {
1331 $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)";
1333 $arg_list[] = "'$arg_name' => $arg_value";
1340 * Parse is expression
1342 * @param string $is_arg
1343 * @param array $tokens
1346 function _parse_is_expr($is_arg, $tokens)
1349 $negate_expr = false;
1351 if (($first_token = array_shift($tokens)) == 'not') {
1352 $negate_expr = true;
1353 $expr_type = array_shift($tokens);
1355 $expr_type = $first_token;
1357 switch ($expr_type) {
1359 if (@$tokens[$expr_end] == 'by') {
1361 $expr_arg = $tokens[$expr_end++];
1362 $expr = "!(($is_arg / $expr_arg) % " . $this->_parse_var_props($expr_arg) . ")";
1364 $expr = "!($is_arg % 2)";
1368 if (@$tokens[$expr_end] == 'by') {
1370 $expr_arg = $tokens[$expr_end++];
1371 $expr = "(($is_arg / $expr_arg) % ". $this->_parse_var_props($expr_arg) . ")";
1373 $expr = "($is_arg % 2)";
1377 if (@$tokens[$expr_end] == 'by') {
1379 $expr_arg = $tokens[$expr_end++];
1380 $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")";
1382 $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__);
1387 $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__);
1395 array_splice($tokens, 0, $expr_end, $expr);
1402 * Parse attribute string
1404 * @param string $tag_args
1407 function _parse_attrs($tag_args)
1410 /* Tokenize tag attributes. */
1411 preg_match_all('/(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+)
1414 /x', $tag_args, $match);
1415 $tokens = $match[0];
1419 0 - expecting attribute name
1421 2 - expecting attribute value (not '=') */
1424 foreach ($tokens as $token) {
1427 /* If the token is a valid identifier, we set attribute name
1428 and go to state 1. */
1429 if (preg_match('!^\w+$!', $token)) {
1430 $attr_name = $token;
1433 $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__);
1437 /* If the token is '=', then we go to state 2. */
1438 if ($token == '=') {
1441 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1445 /* If token is not '=', we set the attribute value and go to
1447 if ($token != '=') {
1448 /* We booleanize the token if it's a non-quoted possible
1450 if (preg_match('!^(on|yes|true)$!', $token)) {
1452 } else if (preg_match('!^(off|no|false)$!', $token)) {
1454 } else if ($token == 'null') {
1456 } else if (preg_match('!^-?([0-9]+|0[xX][0-9a-fA-F]+)$!', $token)) {
1457 /* treat integer literally */
1458 } else if (!preg_match('!^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$!', $token)) {
1459 /* treat as a string, double-quote it escaping quotes */
1460 $token = '"'.addslashes($token).'"';
1463 $attrs[$attr_name] = $token;
1466 $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__);
1469 $last_token = $token;
1474 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1476 $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__);
1480 $this->_parse_vars_props($attrs);
1486 * compile multiple variables and section properties tokens into
1489 * @param array $tokens
1491 function _parse_vars_props(&$tokens)
1493 foreach($tokens as $key => $val) {
1494 $tokens[$key] = $this->_parse_var_props($val);
1499 * compile single variable and section properties token into
1502 * @param string $val
1503 * @param string $tag_attrs
1506 function _parse_var_props($val)
1510 if(preg_match('!^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$!', $val, $match)) {
1511 // $ variable or object
1512 $return = $this->_parse_var($match[1]);
1513 if($match[2] != '') {
1514 $this->_parse_modifiers($return, $match[2]);
1518 elseif(preg_match('!^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$!', $val)) {
1519 // double quoted text
1520 preg_match('!^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$!', $val, $match);
1521 $return = $this->_expand_quoted_text($match[1]);
1522 if($match[2] != '') {
1523 $this->_parse_modifiers($return, $match[2]);
1527 elseif(preg_match('!^' . $this->_si_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$!', $val)) {
1528 // single quoted text
1529 preg_match('!^(' . $this->_si_qstr_regexp . ')('. $this->_mod_regexp . '*)$!', $val, $match);
1530 if($match[2] != '') {
1531 $this->_parse_modifiers($match[1], $match[2]);
1535 elseif(preg_match('!^' . $this->_cvar_regexp . '(?:' . $this->_mod_regexp . '*)$!', $val)) {
1537 return $this->_parse_conf_var($val);
1539 elseif(preg_match('!^' . $this->_svar_regexp . '(?:' . $this->_mod_regexp . '*)$!', $val)) {
1541 return $this->_parse_section_prop($val);
1543 elseif(!in_array($val, $this->_permitted_tokens) && !is_numeric($val)) {
1545 return $this->_expand_quoted_text('"' . $val .'"');
1551 * expand quoted text with embedded variables
1553 * @param string $var_expr
1556 function _expand_quoted_text($var_expr)
1558 // if contains unescaped $, expand it
1559 if(preg_match_all('%(?:\`(?<!\\\\)\$' . $this->_dvar_guts_regexp . '\`)|(?:(?<!\\\\)\$\w+(\[[a-zA-Z0-9]+\])*)%', $var_expr, $_match)) {
1560 $_match = $_match[0];
1563 foreach($_match as $_var) {
1564 $var_expr = str_replace ($_var, '".(' . $this->_parse_var(str_replace('`','',$_var)) . ')."', $var_expr);
1566 $_return = preg_replace('%\.""|(?<!\\\\)""\.%', '', $var_expr);
1568 $_return = $var_expr;
1570 // replace double quoted literal string with single quotes
1571 $_return = preg_replace('!^"([\s\w]+)"$!',"'\\1'",$_return);
1576 * parse variable expression into PHP code
1578 * @param string $var_expr
1579 * @param string $output
1582 function _parse_var($var_expr)
1585 $_math_vars = preg_split('!('.$this->_dvar_math_regexp.'|'.$this->_qstr_regexp.')!', $var_expr, -1, PREG_SPLIT_DELIM_CAPTURE);
1587 if(count($_math_vars) > 1) {
1589 $_complete_var = "";
1590 // simple check if there is any math, to stop recursion (due to modifiers with "xx % yy" as parameter)
1591 foreach($_math_vars as $_k => $_math_var) {
1592 $_math_var = $_math_vars[$_k];
1594 if(!empty($_math_var) || is_numeric($_math_var)) {
1595 // hit a math operator, so process the stuff which came before it
1596 if(preg_match('!^' . $this->_dvar_math_regexp . '$!', $_math_var)) {
1598 if(!empty($_complete_var) || is_numeric($_complete_var)) {
1599 $_output .= $this->_parse_var($_complete_var);
1602 // just output the math operator to php
1603 $_output .= $_math_var;
1605 if(empty($_first_var))
1606 $_first_var = $_complete_var;
1608 $_complete_var = "";
1610 // fetch multiple -> (like $foo->bar->baz ) which wouldn't get fetched else, because it would only get $foo->bar and treat the ->baz as "-" ">baz" then
1611 for($_i = $_k + 1; $_i <= count($_math_vars); $_i += 2) {
1612 // fetch -> because it gets splitted at - and move it back together
1613 if( /* prevent notice */ (isset($_math_vars[$_i]) && isset($_math_vars[$_i+1])) && ($_math_vars[$_i] === '-' && $_math_vars[$_i+1]{0} === '>')) {
1614 $_math_var .= $_math_vars[$_i].$_math_vars[$_i+1];
1615 $_math_vars[$_i] = $_math_vars[$_i+1] = '';
1620 $_complete_var .= $_math_var;
1625 if(!empty($_complete_var) || is_numeric($_complete_var))
1626 $_output .= $this->_parse_var($_complete_var, true);
1628 // get the modifiers working (only the last var from math + modifier is left)
1629 $var_expr = $_complete_var;
1633 // prevent cutting of first digit in the number (we _definitly_ got a number if the first char is a digit)
1634 if(is_numeric($var_expr{0}))
1635 $_var_ref = $var_expr;
1637 $_var_ref = substr($var_expr, 1);
1640 // get [foo] and .foo and ->foo and (...) pieces
1641 preg_match_all('!(?:^\w+)|' . $this->_obj_params_regexp . '|(?:' . $this->_var_bracket_regexp . ')|->\$?\w+|\.\$?\w+|\S+!', $_var_ref, $match);
1643 $_indexes = $match[0];
1644 $_var_name = array_shift($_indexes);
1646 /* Handle $smarty.* variable references as a special case. */
1647 if ($_var_name == 'smarty') {
1649 * If the reference could be compiled, use the compiled output;
1650 * otherwise, fall back on the $smarty variable generated at
1653 if (($smarty_ref = $this->_compile_smarty_ref($_indexes)) !== null) {
1654 $_output = $smarty_ref;
1656 $_var_name = substr(array_shift($_indexes), 1);
1657 $_output = "\$this->_smarty_vars['$_var_name']";
1659 } elseif(is_numeric($_var_name) && is_numeric($var_expr{0})) {
1660 // because . is the operator for accessing arrays thru inidizes we need to put it together again for floating point numbers
1661 if(count($_indexes) > 0)
1663 $_var_name .= implode("", $_indexes);
1664 $_indexes = array();
1666 $_output = $_var_name;
1668 $_output = "\$this->_tpl_vars['$_var_name']";
1671 foreach ($_indexes as $_index) {
1672 if ($_index{0} == '[') {
1673 $_index = substr($_index, 1, -1);
1674 if (is_numeric($_index)) {
1675 $_output .= "[$_index]";
1676 } elseif ($_index{0} == '$') {
1677 if (strpos($_index, '.') !== false) {
1678 $_output .= '[' . $this->_parse_var($_index) . ']';
1680 $_output .= "[\$this->_tpl_vars['" . substr($_index, 1) . "']]";
1683 $_var_parts = explode('.', $_index);
1684 $_var_section = $_var_parts[0];
1685 $_var_section_prop = isset($_var_parts[1]) ? $_var_parts[1] : 'index';
1686 $_output .= "[\$this->_sections['$_var_section']['$_var_section_prop']]";
1688 } else if ($_index{0} == '.') {
1689 if ($_index{1} == '$')
1690 $_output .= "[\$this->_tpl_vars['" . substr($_index, 2) . "']]";
1692 $_output .= "['" . substr($_index, 1) . "']";
1693 } else if (substr($_index,0,2) == '->') {
1694 if(substr($_index,2,2) == '__') {
1695 $this->_syntax_error('call to internal object members is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1696 } elseif($this->security && substr($_index, 2, 1) == '_') {
1697 $this->_syntax_error('(secure) call to private object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1698 } elseif ($_index{2} == '$') {
1699 if ($this->security) {
1700 $this->_syntax_error('(secure) call to dynamic object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1702 $_output .= '->{(($_var=$this->_tpl_vars[\''.substr($_index,3).'\']) && substr($_var,0,2)!=\'__\') ? $_var : $this->trigger_error("cannot access property \\"$_var\\"")}';
1705 $_output .= $_index;
1707 } elseif ($_index{0} == '(') {
1708 $_index = $this->_parse_parenth_args($_index);
1709 $_output .= $_index;
1711 $_output .= $_index;
1720 * parse arguments in function call parenthesis
1722 * @param string $parenth_args
1725 function _parse_parenth_args($parenth_args)
1727 preg_match_all('!' . $this->_param_regexp . '!',$parenth_args, $match);
1731 $orig_vals = $match;
1732 $this->_parse_vars_props($match);
1733 return str_replace($orig_vals, $match, $parenth_args);
1737 * parse configuration variable expression into PHP code
1739 * @param string $conf_var_expr
1741 function _parse_conf_var($conf_var_expr)
1743 $parts = explode('|', $conf_var_expr, 2);
1744 $var_ref = $parts[0];
1745 $modifiers = isset($parts[1]) ? $parts[1] : '';
1747 $var_name = substr($var_ref, 1, -1);
1749 $output = "\$this->_config[0]['vars']['$var_name']";
1751 $this->_parse_modifiers($output, $modifiers);
1757 * parse section property expression into PHP code
1759 * @param string $section_prop_expr
1762 function _parse_section_prop($section_prop_expr)
1764 $parts = explode('|', $section_prop_expr, 2);
1765 $var_ref = $parts[0];
1766 $modifiers = isset($parts[1]) ? $parts[1] : '';
1768 preg_match('!%(\w+)\.(\w+)%!', $var_ref, $match);
1769 $section_name = $match[1];
1770 $prop_name = $match[2];
1772 $output = "\$this->_sections['$section_name']['$prop_name']";
1774 $this->_parse_modifiers($output, $modifiers);
1781 * parse modifier chain into PHP code
1783 * sets $output to parsed modified chain
1784 * @param string $output
1785 * @param string $modifier_string
1787 function _parse_modifiers(&$output, $modifier_string)
1789 preg_match_all('!\|(@?\w+)((?>:(?:'. $this->_qstr_regexp . '|[^|]+))*)!', '|' . $modifier_string, $_match);
1790 list(, $_modifiers, $modifier_arg_strings) = $_match;
1792 for ($_i = 0, $_for_max = count($_modifiers); $_i < $_for_max; $_i++) {
1793 $_modifier_name = $_modifiers[$_i];
1795 if($_modifier_name == 'smarty') {
1796 // skip smarty modifier
1800 preg_match_all('!:(' . $this->_qstr_regexp . '|[^:]+)!', $modifier_arg_strings[$_i], $_match);
1801 $_modifier_args = $_match[1];
1803 if ($_modifier_name{0} == '@') {
1804 $_map_array = false;
1805 $_modifier_name = substr($_modifier_name, 1);
1810 $this->_add_plugin('modifier', $_modifier_name);
1811 if (empty($this->_plugins['modifier'][$_modifier_name])
1812 && !$this->_get_plugin_filepath('modifier', $_modifier_name)
1813 && function_exists($_modifier_name)) {
1814 if ($this->security && !in_array($_modifier_name, $this->security_settings['MODIFIER_FUNCS'])) {
1815 $this->_trigger_fatal_error("[plugin] (secure mode) modifier '$_modifier_name' is not allowed" , $_tpl_file, $_tpl_line, __FILE__, __LINE__);
1817 $this->_plugins['modifier'][$_modifier_name] = array($_modifier_name, null, null, false);
1821 $this->_parse_vars_props($_modifier_args);
1823 if($_modifier_name == 'default') {
1824 // supress notifications of default modifier vars and args
1825 if($output{0} == '$') {
1826 $output = '@' . $output;
1828 if(isset($_modifier_args[0]) && $_modifier_args[0]{0} == '$') {
1829 $_modifier_args[0] = '@' . $_modifier_args[0];
1832 if (count($_modifier_args) > 0)
1833 $_modifier_args = ', '.implode(', ', $_modifier_args);
1835 $_modifier_args = '';
1838 $output = "((is_array(\$_tmp=$output)) ? \$this->_run_mod_handler('$_modifier_name', true, \$_tmp$_modifier_args) : " . $this->_compile_plugin_call('modifier', $_modifier_name) . "(\$_tmp$_modifier_args))";
1842 $output = $this->_compile_plugin_call('modifier', $_modifier_name)."($output$_modifier_args)";
1852 * @param string $type
1853 * @param string $name
1854 * @param boolean? $delayed_loading
1856 function _add_plugin($type, $name, $delayed_loading = null)
1858 if (!isset($this->_plugin_info[$type])) {
1859 $this->_plugin_info[$type] = array();
1861 if (!isset($this->_plugin_info[$type][$name])) {
1862 $this->_plugin_info[$type][$name] = array($this->_current_file,
1863 $this->_current_line_no,
1870 * Compiles references of type $smarty.foo
1872 * @param string $indexes
1875 function _compile_smarty_ref(&$indexes)
1877 /* Extract the reference name. */
1878 $_ref = substr($indexes[0], 1);
1879 foreach($indexes as $_index_no=>$_index) {
1880 if ($_index{0} != '.' && $_index_no<2 || !preg_match('!^(\.|\[|->)!', $_index)) {
1881 $this->_syntax_error('$smarty' . implode('', array_slice($indexes, 0, 2)) . ' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
1887 $compiled_ref = 'time()';
1893 array_shift($indexes);
1894 $_var = $this->_parse_var_props(substr($indexes[0], 1));
1895 if ($_ref == 'foreach')
1896 $compiled_ref = "\$this->_foreach[$_var]";
1898 $compiled_ref = "\$this->_sections[$_var]";
1902 $compiled_ref = ($this->request_use_auto_globals) ? '$_GET' : "\$GLOBALS['HTTP_GET_VARS']";
1906 $compiled_ref = ($this->request_use_auto_globals) ? '$_POST' : "\$GLOBALS['HTTP_POST_VARS']";
1910 $compiled_ref = ($this->request_use_auto_globals) ? '$_COOKIE' : "\$GLOBALS['HTTP_COOKIE_VARS']";
1914 $compiled_ref = ($this->request_use_auto_globals) ? '$_ENV' : "\$GLOBALS['HTTP_ENV_VARS']";
1918 $compiled_ref = ($this->request_use_auto_globals) ? '$_SERVER' : "\$GLOBALS['HTTP_SERVER_VARS']";
1922 $compiled_ref = ($this->request_use_auto_globals) ? '$_SESSION' : "\$GLOBALS['HTTP_SESSION_VARS']";
1926 * These cases are handled either at run-time or elsewhere in the
1930 if ($this->request_use_auto_globals) {
1931 $compiled_ref = '$_REQUEST';
1934 $this->_init_smarty_vars = true;
1942 $compiled_ref = "'$this->_current_file'";
1947 $compiled_ref = "'$this->_version'";
1952 array_shift($indexes);
1953 $_val = $this->_parse_var_props(substr($indexes[0],1));
1954 $compiled_ref = '@constant(' . $_val . ')';
1959 $compiled_ref = "\$this->_config[0]['vars']";
1964 $this->_syntax_error('$smarty.' . $_ref . ' is an unknown reference', E_USER_ERROR, __FILE__, __LINE__);
1968 if (isset($_max_index) && count($indexes) > $_max_index) {
1969 $this->_syntax_error('$smarty' . implode('', $indexes) .' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
1972 array_shift($indexes);
1973 return $compiled_ref;
1977 * compiles call to plugin of type $type with name $name
1978 * returns a string containing the function-name or method call
1979 * without the paramter-list that would have follow to make the
1980 * call valid php-syntax
1982 * @param string $type
1983 * @param string $name
1986 function _compile_plugin_call($type, $name) {
1987 if (isset($this->_plugins[$type][$name])) {
1989 if (is_array($this->_plugins[$type][$name][0])) {
1990 return ((is_object($this->_plugins[$type][$name][0][0])) ?
1991 "\$this->_plugins['$type']['$name'][0][0]->" /* method callback */
1992 : (string)($this->_plugins[$type][$name][0][0]).'::' /* class callback */
1993 ). $this->_plugins[$type][$name][0][1];
1996 /* function callback */
1997 return $this->_plugins[$type][$name][0];
2001 /* plugin not loaded -> auto-loadable-plugin */
2002 return 'smarty_'.$type.'_'.$name;
2008 * load pre- and post-filters
2010 function _load_filters()
2012 if (count($this->_plugins['prefilter']) > 0) {
2013 foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
2014 if ($prefilter === false) {
2015 unset($this->_plugins['prefilter'][$filter_name]);
2016 $_params = array('plugins' => array(array('prefilter', $filter_name, null, null, false)));
2017 require_once(SMARTY_DIR . 'core' . DIRECTORY_SEPARATOR . 'core.load_plugins.php');
2018 smarty_core_load_plugins($_params, $this);
2022 if (count($this->_plugins['postfilter']) > 0) {
2023 foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
2024 if ($postfilter === false) {
2025 unset($this->_plugins['postfilter'][$filter_name]);
2026 $_params = array('plugins' => array(array('postfilter', $filter_name, null, null, false)));
2027 require_once(SMARTY_DIR . 'core' . DIRECTORY_SEPARATOR . 'core.load_plugins.php');
2028 smarty_core_load_plugins($_params, $this);
2036 * Quote subpattern references
2038 * @param string $string
2041 function _quote_replace($string)
2043 return preg_replace('![\\$]\d!', '\\\\\\0', $string);
2047 * display Smarty syntax error
2049 * @param string $error_msg
2050 * @param integer $error_type
2051 * @param string $file
2052 * @param integer $line
2054 function _syntax_error($error_msg, $error_type = E_USER_ERROR, $file=null, $line=null)
2056 if(isset($file) && isset($line)) {
2057 $info = ' ('.basename($file).", line $line)";
2061 trigger_error('Smarty: [in ' . $this->_current_file . ' line ' .
2062 $this->_current_line_no . "]: syntax error: $error_msg$info", $error_type);
2067 * check if the compilation changes from cacheable to
2068 * non-cacheable state with the beginning of the current
2069 * plugin. return php-code to reflect the transition.
2072 function _push_cacheable_state($type, $name) {
2073 $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2075 || 0<$this->_cacheable_state++) return '';
2076 if (!isset($this->_cache_serial)) $this->_cache_serial = md5(uniqid('Smarty'));
2077 $_ret = 'if ($this->caching) { echo \'{nocache:'
2078 . $this->_cache_serial . '#' . $this->_nocache_count
2085 * check if the compilation changes from non-cacheable to
2086 * cacheable state with the end of the current plugin return
2087 * php-code to reflect the transition.
2090 function _pop_cacheable_state($type, $name) {
2091 $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2093 || --$this->_cacheable_state>0) return '';
2094 return 'if ($this->caching) { echo \'{/nocache:'
2095 . $this->_cache_serial . '#' . ($this->_nocache_count++)
2102 * compare to values by their string length
2109 function _smarty_sort_length($a, $b)
2114 if(strlen($a) == strlen($b))
2115 return ($a > $b) ? -1 : 1;
2117 return (strlen($a) > strlen($b)) ? -1 : 1;