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@ispi.net>
23 * @author Andrei Zmievski <andrei@php.net>
25 * @copyright 2001-2004 ispi of Lincoln, Inc.
32 * Template compiling class
35 class Smarty_Compiler
extends Smarty
{
41 var $_folded_blocks = array(); // keeps folded template blocks
42 var $_current_file = null; // the current template being compiled
43 var $_current_line_no = 1; // line number for error messages
44 var $_capture_stack = array(); // keeps track of nested capture buffers
45 var $_plugin_info = array(); // keeps track of plugins to load
46 var $_init_smarty_vars = false;
47 var $_permitted_tokens = array('true','false','yes','no','on','off','null');
48 var $_db_qstr_regexp = null; // regexps are setup in the constructor
49 var $_si_qstr_regexp = null;
50 var $_qstr_regexp = null;
51 var $_func_regexp = null;
52 var $_var_bracket_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 $ vars (not objects):
107 // $foo[5].bar[$foobar][4]
108 $this->_dvar_math_regexp
= '[\+\-\*\/\%]';
109 $this->_dvar_math_var_regexp
= '[\$\w\.\+\-\*\/\%\d\>\[\]]';
110 $this->_dvar_num_var_regexp
= '\-?\d+(?:\.\d+)?' . $this->_dvar_math_var_regexp
;
111 $this->_dvar_guts_regexp
= '\w+(?:' . $this->_var_bracket_regexp
112 . ')*(?:\.\$?\w+(?:' . $this->_var_bracket_regexp
. ')*)*(?:' . $this->_dvar_math_regexp
. '(?:\-?\d+(?:\.\d+)?|' . $this->_dvar_math_var_regexp
. ')*)?';
113 $this->_dvar_regexp
= '\$' . $this->_dvar_guts_regexp
;
115 // matches config vars:
118 $this->_cvar_regexp
= '\#\w+\#';
120 // matches section vars:
122 $this->_svar_regexp
= '\%\w+\.\w+\%';
124 // matches all valid variables (no quotes, no modifiers)
125 $this->_avar_regexp
= '(?:' . $this->_dvar_regexp
. '|'
126 . $this->_cvar_regexp
. '|' . $this->_svar_regexp
. ')';
128 // matches valid variable syntax:
135 $this->_var_regexp
= '(?:' . $this->_avar_regexp
. '|' . $this->_qstr_regexp
. ')';
137 // matches valid object call (no objects allowed in parameters):
141 // $foo->bar($foo, $bar, "text")
142 // $foo->bar($foo, "foo")
144 // $foo->bar->foo->bar()
145 $this->_obj_ext_regexp
= '\->(?:\$?' . $this->_dvar_guts_regexp
. ')';
146 $this->_obj_params_regexp
= '\((?:\w+|'
147 . $this->_var_regexp
. '(?:\s*,\s*(?:(?:\w+|'
148 . $this->_var_regexp
. ')))*)?\)';
149 $this->_obj_start_regexp
= '(?:' . $this->_dvar_regexp
. '(?:' . $this->_obj_ext_regexp
. ')+)';
150 $this->_obj_call_regexp
= '(?:' . $this->_obj_start_regexp
. '(?:' . $this->_obj_params_regexp
. ')?)';
152 // matches valid modifier syntax:
157 // |foo:"bar":$foobar
160 $this->_mod_regexp
= '(?:\|@?\w+(?::(?>-?\w+|'
161 . $this->_obj_call_regexp
. '|' . $this->_avar_regexp
. '|' . $this->_qstr_regexp
.'))*)';
163 // matches valid function name:
166 $this->_func_regexp
= '[a-zA-Z_]\w*';
168 // matches valid registered object:
170 $this->_reg_obj_regexp
= '[a-zA-Z_]\w*->[a-zA-Z_]\w*';
172 // matches valid parameter values:
181 $this->_param_regexp
= '(?:\s*(?:' . $this->_obj_call_regexp
. '|'
182 . $this->_var_regexp
. '|\w+)(?>' . $this->_mod_regexp
. '*)\s*)';
184 // matches valid parenthesised function parameters:
187 // $foo, $bar, "text"
188 // $foo|bar, "foo"|bar, $foo->bar($foo)|bar
189 $this->_parenth_param_regexp
= '(?:\((?:\w+|'
190 . $this->_param_regexp
. '(?:\s*,\s*(?:(?:\w+|'
191 . $this->_param_regexp
. ')))*)?\))';
193 // matches valid function call:
196 // _foo_bar($foo,"bar")
197 // foo123($foo,$foo->bar(),"foo")
198 $this->_func_call_regexp
= '(?:' . $this->_func_regexp
. '\s*(?:'
199 . $this->_parenth_param_regexp
. '))';
205 * sets $compiled_content to the compiled source
206 * @param string $resource_name
207 * @param string $source_content
208 * @param string $compiled_content
211 function _compile_file($resource_name, $source_content, &$compiled_content)
214 if ($this->security
) {
215 // do not allow php syntax to be executed unless specified
216 if ($this->php_handling
== SMARTY_PHP_ALLOW
&&
217 !$this->security_settings
['PHP_HANDLING']) {
218 $this->php_handling
= SMARTY_PHP_PASSTHRU
;
222 $this->_load_filters();
224 $this->_current_file
= $resource_name;
225 $this->_current_line_no
= 1;
226 $ldq = preg_quote($this->left_delimiter
, '!');
227 $rdq = preg_quote($this->right_delimiter
, '!');
229 // run template source through prefilter functions
230 if (count($this->_plugins
['prefilter']) > 0) {
231 foreach ($this->_plugins
['prefilter'] as $filter_name => $prefilter) {
232 if ($prefilter === false) continue;
233 if ($prefilter[3] ||
is_callable($prefilter[0])) {
234 $source_content = call_user_func_array($prefilter[0],
235 array($source_content, &$this));
236 $this->_plugins
['prefilter'][$filter_name][3] = true;
238 $this->_trigger_fatal_error("[plugin] prefilter '$filter_name' is not implemented");
243 /* fetch all special blocks */
244 $search = "!{$ldq}\*(.*?)\*{$rdq}|{$ldq}\s*literal\s*{$rdq}(.*?){$ldq}\s*/literal\s*{$rdq}|{$ldq}\s*php\s*{$rdq}(.*?){$ldq}\s*/php\s*{$rdq}!s";
246 preg_match_all($search, $source_content, $match, PREG_SET_ORDER
);
247 $this->_folded_blocks
= $match;
248 reset($this->_folded_blocks
);
250 /* replace special blocks by "{php}" */
251 $source_content = preg_replace($search.'e', "'"
252 . $this->_quote_replace($this->left_delimiter
) . 'php'
253 . "' . str_repeat(\"\n\", substr_count('\\0', \"\n\")) .'"
254 . $this->_quote_replace($this->right_delimiter
)
258 /* Gather all template tags. */
259 preg_match_all("!{$ldq}\s*(.*?)\s*{$rdq}!s", $source_content, $_match);
260 $template_tags = $_match[1];
261 /* Split content by template tags to obtain non-template content. */
262 $text_blocks = preg_split("!{$ldq}.*?{$rdq}!s", $source_content);
264 /* loop through text blocks */
265 for ($curr_tb = 0, $for_max = count($text_blocks); $curr_tb < $for_max; $curr_tb++
) {
266 /* match anything resembling php tags */
267 if (preg_match_all('!(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?php[\"\']?)!is', $text_blocks[$curr_tb], $sp_match)) {
268 /* replace tags with placeholders to prevent recursive replacements */
269 $sp_match[1] = array_unique($sp_match[1]);
270 usort($sp_match[1], '_smarty_sort_length');
271 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++
) {
272 $text_blocks[$curr_tb] = str_replace($sp_match[1][$curr_sp],'%%%SMARTYSP'.$curr_sp.'%%%',$text_blocks[$curr_tb]);
274 /* process each one */
275 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++
) {
276 if ($this->php_handling
== SMARTY_PHP_PASSTHRU
) {
277 /* echo php contents */
278 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '<?php echo \''.str_replace("'", "\'", $sp_match[1][$curr_sp]).'\'; ?>'."\n", $text_blocks[$curr_tb]);
279 } else if ($this->php_handling
== SMARTY_PHP_QUOTE
) {
281 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', htmlspecialchars($sp_match[1][$curr_sp]), $text_blocks[$curr_tb]);
282 } else if ($this->php_handling
== SMARTY_PHP_REMOVE
) {
283 /* remove php tags */
284 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '', $text_blocks[$curr_tb]);
286 /* SMARTY_PHP_ALLOW, but echo non php starting tags */
287 $sp_match[1][$curr_sp] = preg_replace('%(<\?(?!php|=|$))%i', '<?php echo \'\\1\'?>'."\n", $sp_match[1][$curr_sp]);
288 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', $sp_match[1][$curr_sp], $text_blocks[$curr_tb]);
294 /* Compile the template tags into PHP code. */
295 $compiled_tags = array();
296 for ($i = 0, $for_max = count($template_tags); $i < $for_max; $i++
) {
297 $this->_current_line_no +
= substr_count($text_blocks[$i], "\n");
298 $compiled_tags[] = $this->_compile_tag($template_tags[$i]);
299 $this->_current_line_no +
= substr_count($template_tags[$i], "\n");
301 if (count($this->_tag_stack
)>0) {
302 list($_open_tag, $_line_no) = end($this->_tag_stack
);
303 $this->_syntax_error("unclosed tag \{$_open_tag} (opened line $_line_no).", E_USER_ERROR
, __FILE__
, __LINE__
);
307 $compiled_content = '';
309 /* Interleave the compiled contents and text blocks to get the final result. */
310 for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++
) {
311 if ($compiled_tags[$i] == '') {
312 // tag result empty, remove first newline from following text block
313 $text_blocks[$i+
1] = preg_replace('!^(\r\n|\r|\n)!', '', $text_blocks[$i+
1]);
315 $compiled_content .= $text_blocks[$i].$compiled_tags[$i];
317 $compiled_content .= $text_blocks[$i];
319 /* Reformat data between 'strip' and '/strip' tags, removing spaces, tabs and newlines. */
320 if (preg_match_all("!{$ldq}strip{$rdq}.*?{$ldq}/strip{$rdq}!s", $compiled_content, $_match)) {
321 $strip_tags = $_match[0];
322 $strip_tags_modified = preg_replace("!{$ldq}/?strip{$rdq}|[\t ]+$|^[\t ]+!m", '', $strip_tags);
323 $strip_tags_modified = preg_replace('![\r\n]+!m', '', $strip_tags_modified);
324 for ($i = 0, $for_max = count($strip_tags); $i < $for_max; $i++
)
325 $compiled_content = preg_replace("!{$ldq}strip{$rdq}.*?{$ldq}/strip{$rdq}!s",
326 $this->_quote_replace($strip_tags_modified[$i]),
327 $compiled_content, 1);
330 // remove \n from the end of the file, if any
331 if (($_len=strlen($compiled_content)) && ($compiled_content{$_len - 1} == "\n" )) {
332 $compiled_content = substr($compiled_content, 0, -1);
335 if (!empty($this->_cache_serial
)) {
336 $compiled_content = "<?php \$this->_cache_serials['".$this->_cache_include
."'] = '".$this->_cache_serial
."'; ?>" . $compiled_content;
339 // remove unnecessary close/open tags
340 $compiled_content = preg_replace('!\?>\n?<\?php!', '', $compiled_content);
342 // run compiled template through postfilter functions
343 if (count($this->_plugins
['postfilter']) > 0) {
344 foreach ($this->_plugins
['postfilter'] as $filter_name => $postfilter) {
345 if ($postfilter === false) continue;
346 if ($postfilter[3] ||
is_callable($postfilter[0])) {
347 $compiled_content = call_user_func_array($postfilter[0],
348 array($compiled_content, &$this));
349 $this->_plugins
['postfilter'][$filter_name][3] = true;
351 $this->_trigger_fatal_error("Smarty plugin error: postfilter '$filter_name' is not implemented");
356 // put header at the top of the compiled template
357 $template_header = "<?php /* Smarty version ".$this->_version
.", created on ".strftime("%Y-%m-%d %H:%M:%S")."\n";
358 $template_header .= " compiled from ".strtr(urlencode($resource_name), array('%2F'=>'/', '%3A'=>':'))." */ ?>\n";
360 /* Emit code to load needed plugins. */
361 $this->_plugins_code
= '';
362 if (count($this->_plugin_info
)) {
363 $_plugins_params = "array('plugins' => array(";
364 foreach ($this->_plugin_info
as $plugin_type => $plugins) {
365 foreach ($plugins as $plugin_name => $plugin_info) {
366 $_plugins_params .= "array('$plugin_type', '$plugin_name', '$plugin_info[0]', $plugin_info[1], ";
367 $_plugins_params .= $plugin_info[2] ?
'true),' : 'false),';
370 $_plugins_params .= '))';
371 $plugins_code = "<?php require_once(SMARTY_DIR . 'core' . DIRECTORY_SEPARATOR . 'core.load_plugins.php');\nsmarty_core_load_plugins($_plugins_params, \$this); ?>\n";
372 $template_header .= $plugins_code;
373 $this->_plugin_info
= array();
374 $this->_plugins_code
= $plugins_code;
377 if ($this->_init_smarty_vars
) {
378 $template_header .= "<?php require_once(SMARTY_DIR . 'core' . DIRECTORY_SEPARATOR . 'core.assign_smarty_interface.php');\nsmarty_core_assign_smarty_interface(null, \$this); ?>\n";
379 $this->_init_smarty_vars
= false;
382 $compiled_content = $template_header . $compiled_content;
387 * Compile a template tag
389 * @param string $template_tag
392 function _compile_tag($template_tag)
394 /* Matched comment. */
395 if ($template_tag{0} == '*' && $template_tag{strlen($template_tag) - 1} == '*')
398 /* Split tag into two three parts: command, command modifiers and the arguments. */
399 if(! preg_match('/^(?:(' . $this->_obj_call_regexp
. '|' . $this->_var_regexp
400 . '|\/?' . $this->_reg_obj_regexp
. '|\/?' . $this->_func_regexp
. ')(' . $this->_mod_regexp
. '*))
402 /xs', $template_tag, $match)) {
403 $this->_syntax_error("unrecognized tag: $template_tag", E_USER_ERROR
, __FILE__
, __LINE__
);
406 $tag_command = $match[1];
407 $tag_modifier = isset($match[2]) ?
$match[2] : null;
408 $tag_args = isset($match[3]) ?
$match[3] : null;
410 if (preg_match('!^' . $this->_obj_call_regexp
. '|' . $this->_var_regexp
. '$!', $tag_command)) {
411 /* tag name is a variable or object */
412 $_return = $this->_parse_var_props($tag_command . $tag_modifier, $this->_parse_attrs($tag_args));
413 if(isset($_tag_attrs['assign'])) {
414 return "<?php \$this->assign('" . $this->_dequote($_tag_attrs['assign']) . "', $_return ); ?>\n";
416 return "<?php echo $_return; ?>" . $this->_additional_newline
;
420 /* If the tag name is a registered object, we process it. */
421 if (preg_match('!^\/?' . $this->_reg_obj_regexp
. '$!', $tag_command)) {
422 return $this->_compile_registered_object_tag($tag_command, $this->_parse_attrs($tag_args), $tag_modifier);
425 switch ($tag_command) {
427 return $this->_compile_include_tag($tag_args);
430 return $this->_compile_include_php_tag($tag_args);
433 $this->_push_tag('if');
434 return $this->_compile_if_tag($tag_args);
437 list($_open_tag) = end($this->_tag_stack
);
438 if ($_open_tag != 'if' && $_open_tag != 'elseif')
439 $this->_syntax_error('unxepected {else}', E_USER_ERROR
, __FILE__
, __LINE__
);
441 $this->_push_tag('else');
442 return '<?php else: ?>';
445 list($_open_tag) = end($this->_tag_stack
);
446 if ($_open_tag != 'if' && $_open_tag != 'elseif')
447 $this->_syntax_error('unxepected {elseif}', E_USER_ERROR
, __FILE__
, __LINE__
);
448 if ($_open_tag == 'if')
449 $this->_push_tag('elseif');
450 return $this->_compile_if_tag($tag_args, true);
453 $this->_pop_tag('if');
454 return '<?php endif; ?>';
457 return $this->_compile_capture_tag(true, $tag_args);
460 return $this->_compile_capture_tag(false);
463 return $this->left_delimiter
;
466 return $this->right_delimiter
;
469 $this->_push_tag('section');
470 return $this->_compile_section_start($tag_args);
473 $this->_push_tag('sectionelse');
474 return "<?php endfor; else: ?>";
478 $_open_tag = $this->_pop_tag('section');
479 if ($_open_tag == 'sectionelse')
480 return "<?php endif; ?>";
482 return "<?php endfor; endif; ?>";
485 $this->_push_tag('foreach');
486 return $this->_compile_foreach_start($tag_args);
490 $this->_push_tag('foreachelse');
491 return "<?php endforeach; unset(\$_from); else: ?>";
494 $_open_tag = $this->_pop_tag('foreach');
495 if ($_open_tag == 'foreachelse')
496 return "<?php endif; ?>";
498 return "<?php endforeach; unset(\$_from); endif; ?>";
503 if ($tag_command{0}=='/') {
504 $this->_pop_tag('strip');
505 if (--$this->_strip_depth
==0) { /* outermost closing {/strip} */
506 $this->_additional_newline
= "\n";
507 return $this->left_delimiter
.$tag_command.$this->right_delimiter
;
510 $this->_push_tag('strip');
511 if ($this->_strip_depth++
==0) { /* outermost opening {strip} */
512 $this->_additional_newline
= "";
513 return $this->left_delimiter
.$tag_command.$this->right_delimiter
;
519 /* handle folded tags replaced by {php} */
520 list(, $block) = each($this->_folded_blocks
);
521 $this->_current_line_no +
= substr_count($block[0], "\n");
522 /* the number of matched elements in the regexp in _compile_file()
523 determins the type of folded tag that was found */
524 switch (count($block)) {
525 case 2: /* comment */
528 case 3: /* literal */
529 return "<?php echo '" . strtr($block[2], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>" . $this->_additional_newline
;
532 if ($this->security
&& !$this->security_settings
['PHP_TAGS']) {
533 $this->_syntax_error("(secure mode) php tags not permitted", E_USER_WARNING
, __FILE__
, __LINE__
);
536 return '<?php ' . $block[3] .' ?>';
541 return $this->_compile_insert_tag($tag_args);
544 if ($this->_compile_compiler_tag($tag_command, $tag_args, $output)) {
546 } else if ($this->_compile_block_tag($tag_command, $tag_args, $tag_modifier, $output)) {
548 } else if ($this->_compile_custom_tag($tag_command, $tag_args, $tag_modifier, $output)) {
551 $this->_syntax_error("unrecognized tag '$tag_command'", E_USER_ERROR
, __FILE__
, __LINE__
);
559 * compile the custom compiler tag
561 * sets $output to the compiled custom compiler tag
562 * @param string $tag_command
563 * @param string $tag_args
564 * @param string $output
567 function _compile_compiler_tag($tag_command, $tag_args, &$output)
570 $have_function = true;
573 * First we check if the compiler function has already been registered
574 * or loaded from a plugin file.
576 if (isset($this->_plugins
['compiler'][$tag_command])) {
578 $plugin_func = $this->_plugins
['compiler'][$tag_command][0];
579 if (!is_callable($plugin_func)) {
580 $message = "compiler function '$tag_command' is not implemented";
581 $have_function = false;
585 * Otherwise we need to load plugin file and look for the function
588 else if ($plugin_file = $this->_get_plugin_filepath('compiler', $tag_command)) {
591 include_once $plugin_file;
593 $plugin_func = 'smarty_compiler_' . $tag_command;
594 if (!is_callable($plugin_func)) {
595 $message = "plugin function $plugin_func() not found in $plugin_file\n";
596 $have_function = false;
598 $this->_plugins
['compiler'][$tag_command] = array($plugin_func, null, null, null, true);
603 * True return value means that we either found a plugin or a
604 * dynamically registered function. False means that we didn't and the
605 * compiler should now emit code to load custom function plugin for this
609 if ($have_function) {
610 $output = call_user_func_array($plugin_func, array($tag_args, &$this));
612 $output = '<?php ' . $this->_push_cacheable_state('compiler', $tag_command)
614 . $this->_pop_cacheable_state('compiler', $tag_command) . ' ?>';
617 $this->_syntax_error($message, E_USER_WARNING
, __FILE__
, __LINE__
);
627 * compile block function tag
629 * sets $output to compiled block function tag
630 * @param string $tag_command
631 * @param string $tag_args
632 * @param string $tag_modifier
633 * @param string $output
636 function _compile_block_tag($tag_command, $tag_args, $tag_modifier, &$output)
638 if ($tag_command{0} == '/') {
640 $tag_command = substr($tag_command, 1);
645 $have_function = true;
648 * First we check if the block function has already been registered
649 * or loaded from a plugin file.
651 if (isset($this->_plugins
['block'][$tag_command])) {
653 $plugin_func = $this->_plugins
['block'][$tag_command][0];
654 if (!is_callable($plugin_func)) {
655 $message = "block function '$tag_command' is not implemented";
656 $have_function = false;
660 * Otherwise we need to load plugin file and look for the function
663 else if ($plugin_file = $this->_get_plugin_filepath('block', $tag_command)) {
666 include_once $plugin_file;
668 $plugin_func = 'smarty_block_' . $tag_command;
669 if (!function_exists($plugin_func)) {
670 $message = "plugin function $plugin_func() not found in $plugin_file\n";
671 $have_function = false;
673 $this->_plugins
['block'][$tag_command] = array($plugin_func, null, null, null, true);
680 } else if (!$have_function) {
681 $this->_syntax_error($message, E_USER_WARNING
, __FILE__
, __LINE__
);
686 * Even though we've located the plugin function, compilation
687 * happens only once, so the plugin will still need to be loaded
688 * at runtime for future requests.
690 $this->_add_plugin('block', $tag_command);
693 $this->_push_tag($tag_command);
695 $this->_pop_tag($tag_command);
698 $output = '<?php ' . $this->_push_cacheable_state('block', $tag_command);
699 $attrs = $this->_parse_attrs($tag_args);
700 $arg_list = $this->_compile_arg_list('block', $tag_command, $attrs, $_cache_attrs='');
701 $output .= "$_cache_attrs\$this->_tag_stack[] = array('$tag_command', array(".implode(',', $arg_list).')); ';
702 $output .= $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], null, $this, $_block_repeat=true);';
703 $output .= 'while ($_block_repeat) { ob_start(); ?>';
705 $output = '<?php $this->_block_content = ob_get_contents(); ob_end_clean(); ';
706 $_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)';
707 if ($tag_modifier != '') {
708 $this->_parse_modifiers($_out_tag_text, $tag_modifier);
710 $output .= 'echo '.$_out_tag_text.'; } ';
711 $output .= " array_pop(\$this->_tag_stack); " . $this->_pop_cacheable_state('block', $tag_command) . '?>';
719 * compile custom function tag
721 * @param string $tag_command
722 * @param string $tag_args
723 * @param string $tag_modifier
726 function _compile_custom_tag($tag_command, $tag_args, $tag_modifier, &$output)
729 $have_function = true;
732 * First we check if the custom function has already been registered
733 * or loaded from a plugin file.
735 if (isset($this->_plugins
['function'][$tag_command])) {
737 $plugin_func = $this->_plugins
['function'][$tag_command][0];
738 if (!is_callable($plugin_func)) {
739 $message = "custom function '$tag_command' is not implemented";
740 $have_function = false;
744 * Otherwise we need to load plugin file and look for the function
747 else if ($plugin_file = $this->_get_plugin_filepath('function', $tag_command)) {
750 include_once $plugin_file;
752 $plugin_func = 'smarty_function_' . $tag_command;
753 if (!function_exists($plugin_func)) {
754 $message = "plugin function $plugin_func() not found in $plugin_file\n";
755 $have_function = false;
757 $this->_plugins
['function'][$tag_command] = array($plugin_func, null, null, null, true);
764 } else if (!$have_function) {
765 $this->_syntax_error($message, E_USER_WARNING
, __FILE__
, __LINE__
);
769 /* declare plugin to be loaded on display of the template that
770 we compile right now */
771 $this->_add_plugin('function', $tag_command);
773 $_cacheable_state = $this->_push_cacheable_state('function', $tag_command);
774 $attrs = $this->_parse_attrs($tag_args);
775 $arg_list = $this->_compile_arg_list('function', $tag_command, $attrs, $_cache_attrs='');
777 $output = $this->_compile_plugin_call('function', $tag_command).'(array('.implode(',', $arg_list)."), \$this)";
778 if($tag_modifier != '') {
779 $this->_parse_modifiers($output, $tag_modifier);
783 $output = '<?php ' . $_cacheable_state . $_cache_attrs . 'echo ' . $output . ';'
784 . $this->_pop_cacheable_state('function', $tag_command) . "?>" . $this->_additional_newline
;
791 * compile a registered object tag
793 * @param string $tag_command
794 * @param array $attrs
795 * @param string $tag_modifier
798 function _compile_registered_object_tag($tag_command, $attrs, $tag_modifier)
800 if ($tag_command{0} == '/') {
802 $tag_command = substr($tag_command, 1);
807 list($object, $obj_comp) = explode('->', $tag_command);
811 $_assign_var = false;
812 foreach ($attrs as $arg_name => $arg_value) {
813 if($arg_name == 'assign') {
814 $_assign_var = $arg_value;
815 unset($attrs['assign']);
818 if (is_bool($arg_value))
819 $arg_value = $arg_value ?
'true' : 'false';
820 $arg_list[] = "'$arg_name' => $arg_value";
824 if($this->_reg_objects
[$object][2]) {
825 // smarty object argument format
826 $args = "array(".implode(',', (array)$arg_list)."), \$this";
828 // traditional argument format
829 $args = implode(',', array_values($attrs));
838 if(!is_object($this->_reg_objects
[$object][0])) {
839 $this->_trigger_fatal_error("registered '$object' is not an object" , $this->_current_file
, $this->_current_line_no
, __FILE__
, __LINE__
);
840 } elseif(!empty($this->_reg_objects
[$object][1]) && !in_array($obj_comp, $this->_reg_objects
[$object][1])) {
841 $this->_trigger_fatal_error("'$obj_comp' is not a registered component of object '$object'", $this->_current_file
, $this->_current_line_no
, __FILE__
, __LINE__
);
842 } elseif(method_exists($this->_reg_objects
[$object][0], $obj_comp)) {
844 if(in_array($obj_comp, $this->_reg_objects
[$object][3])) {
847 $prefix = "\$this->_tag_stack[] = array('$obj_comp', $args); ";
848 $prefix .= "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], null, \$this, \$_block_repeat=true); ";
849 $prefix .= "while (\$_block_repeat) { ob_start();";
853 $prefix = "\$this->_obj_block_content = ob_get_contents(); ob_end_clean(); ";
854 $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)";
855 $postfix = "} array_pop(\$this->_tag_stack);";
859 $return = "\$this->_reg_objects['$object'][0]->$obj_comp($args)";
863 $return = "\$this->_reg_objects['$object'][0]->$obj_comp";
866 if($return != null) {
867 if($tag_modifier != '') {
868 $this->_parse_modifiers($return, $tag_modifier);
871 if(!empty($_assign_var)) {
872 $output = "\$this->assign('" . $this->_dequote($_assign_var) ."', $return);";
874 $output = 'echo ' . $return . ';';
875 $newline = $this->_additional_newline
;
881 return '<?php ' . $prefix . $output . $postfix . "?>" . $newline;
885 * Compile {insert ...} tag
887 * @param string $tag_args
890 function _compile_insert_tag($tag_args)
892 $attrs = $this->_parse_attrs($tag_args);
893 $name = $this->_dequote($attrs['name']);
896 $this->_syntax_error("missing insert name", E_USER_ERROR
, __FILE__
, __LINE__
);
899 if (!empty($attrs['script'])) {
900 $delayed_loading = true;
902 $delayed_loading = false;
905 foreach ($attrs as $arg_name => $arg_value) {
906 if (is_bool($arg_value))
907 $arg_value = $arg_value ?
'true' : 'false';
908 $arg_list[] = "'$arg_name' => $arg_value";
911 $this->_add_plugin('insert', $name, $delayed_loading);
913 $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))";
915 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
;
919 * Compile {include ...} tag
921 * @param string $tag_args
924 function _compile_include_tag($tag_args)
926 $attrs = $this->_parse_attrs($tag_args);
929 if (empty($attrs['file'])) {
930 $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR
, __FILE__
, __LINE__
);
933 foreach ($attrs as $arg_name => $arg_value) {
934 if ($arg_name == 'file') {
935 $include_file = $arg_value;
937 } else if ($arg_name == 'assign') {
938 $assign_var = $arg_value;
941 if (is_bool($arg_value))
942 $arg_value = $arg_value ?
'true' : 'false';
943 $arg_list[] = "'$arg_name' => $arg_value";
948 if (isset($assign_var)) {
949 $output .= "ob_start();\n";
953 "\$_smarty_tpl_vars = \$this->_tpl_vars;\n";
956 $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))";
957 $output .= "\$this->_smarty_include($_params);\n" .
958 "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" .
959 "unset(\$_smarty_tpl_vars);\n";
961 if (isset($assign_var)) {
962 $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n";
972 * Compile {include ...} tag
974 * @param string $tag_args
977 function _compile_include_php_tag($tag_args)
979 $attrs = $this->_parse_attrs($tag_args);
981 if (empty($attrs['file'])) {
982 $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR
, __FILE__
, __LINE__
);
985 $assign_var = (empty($attrs['assign'])) ?
'' : $this->_dequote($attrs['assign']);
986 $once_var = (empty($attrs['once']) ||
$attrs['once']=='false') ?
'false' : 'true';
989 foreach($attrs as $arg_name => $arg_value) {
990 if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') {
991 if(is_bool($arg_value))
992 $arg_value = $arg_value ?
'true' : 'false';
993 $arg_list[] = "'$arg_name' => $arg_value";
997 $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', $arg_list)."))";
999 return "<?php require_once(SMARTY_DIR . 'core' . DIRECTORY_SEPARATOR . 'core.smarty_include_php.php');\nsmarty_core_smarty_include_php($_params, \$this); ?>" . $this->_additional_newline
;
1004 * Compile {section ...} tag
1006 * @param string $tag_args
1009 function _compile_section_start($tag_args)
1011 $attrs = $this->_parse_attrs($tag_args);
1012 $arg_list = array();
1015 $section_name = $attrs['name'];
1016 if (empty($section_name)) {
1017 $this->_syntax_error("missing section name", E_USER_ERROR
, __FILE__
, __LINE__
);
1020 $output .= "if (isset(\$this->_sections[$section_name])) unset(\$this->_sections[$section_name]);\n";
1021 $section_props = "\$this->_sections[$section_name]";
1023 foreach ($attrs as $attr_name => $attr_value) {
1024 switch ($attr_name) {
1026 $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n";
1030 if (is_bool($attr_value))
1031 $show_attr_value = $attr_value ?
'true' : 'false';
1033 $show_attr_value = "(bool)$attr_value";
1034 $output .= "{$section_props}['show'] = $show_attr_value;\n";
1038 $output .= "{$section_props}['$attr_name'] = $attr_value;\n";
1043 $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n";
1047 $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n";
1051 $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR
, __FILE__
, __LINE__
);
1056 if (!isset($attrs['show']))
1057 $output .= "{$section_props}['show'] = true;\n";
1059 if (!isset($attrs['loop']))
1060 $output .= "{$section_props}['loop'] = 1;\n";
1062 if (!isset($attrs['max']))
1063 $output .= "{$section_props}['max'] = {$section_props}['loop'];\n";
1065 $output .= "if ({$section_props}['max'] < 0)\n" .
1066 " {$section_props}['max'] = {$section_props}['loop'];\n";
1068 if (!isset($attrs['step']))
1069 $output .= "{$section_props}['step'] = 1;\n";
1071 if (!isset($attrs['start']))
1072 $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n";
1074 $output .= "if ({$section_props}['start'] < 0)\n" .
1075 " {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" .
1077 " {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n";
1080 $output .= "if ({$section_props}['show']) {\n";
1081 if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) {
1082 $output .= " {$section_props}['total'] = {$section_props}['loop'];\n";
1084 $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";
1086 $output .= " if ({$section_props}['total'] == 0)\n" .
1087 " {$section_props}['show'] = false;\n" .
1089 " {$section_props}['total'] = 0;\n";
1091 $output .= "if ({$section_props}['show']):\n";
1093 for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1;
1094 {$section_props}['iteration'] <= {$section_props}['total'];
1095 {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n";
1096 $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n";
1097 $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n";
1098 $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n";
1099 $output .= "{$section_props}['first'] = ({$section_props}['iteration'] == 1);\n";
1100 $output .= "{$section_props}['last'] = ({$section_props}['iteration'] == {$section_props}['total']);\n";
1109 * Compile {foreach ...} tag.
1111 * @param string $tag_args
1114 function _compile_foreach_start($tag_args)
1116 $attrs = $this->_parse_attrs($tag_args);
1117 $arg_list = array();
1119 if (empty($attrs['from'])) {
1120 $this->_syntax_error("missing 'from' attribute", E_USER_ERROR
, __FILE__
, __LINE__
);
1123 if (empty($attrs['item'])) {
1124 $this->_syntax_error("missing 'item' attribute", E_USER_ERROR
, __FILE__
, __LINE__
);
1127 $from = $attrs['from'];
1128 $item = $this->_dequote($attrs['item']);
1129 if (isset($attrs['name']))
1130 $name = $attrs['name'];
1134 $output .= "if (isset(\$this->_foreach[$name])) unset(\$this->_foreach[$name]);\n";
1135 $foreach_props = "\$this->_foreach[$name]";
1140 foreach ($attrs as $attr_name => $attr_value) {
1141 switch ($attr_name) {
1143 $key = $this->_dequote($attrs['key']);
1144 $key_part = "\$this->_tpl_vars['$key'] => ";
1148 $output .= "{$foreach_props}['$attr_name'] = $attr_value;\n";
1154 $output .= "{$foreach_props}['total'] = count(\$_from = (array)$from);\n";
1155 $output .= "{$foreach_props}['show'] = {$foreach_props}['total'] > 0;\n";
1156 $output .= "if ({$foreach_props}['show']):\n";
1157 $output .= "{$foreach_props}['iteration'] = 0;\n";
1158 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1159 $output .= " {$foreach_props}['iteration']++;\n";
1160 $output .= " {$foreach_props}['first'] = ({$foreach_props}['iteration'] == 1);\n";
1161 $output .= " {$foreach_props}['last'] = ({$foreach_props}['iteration'] == {$foreach_props}['total']);\n";
1163 $output .= "if (count(\$_from = (array)$from)):\n";
1164 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1173 * Compile {capture} .. {/capture} tags
1175 * @param boolean $start true if this is the {capture} tag
1176 * @param string $tag_args
1180 function _compile_capture_tag($start, $tag_args = '')
1182 $attrs = $this->_parse_attrs($tag_args);
1185 if (isset($attrs['name']))
1186 $buffer = $attrs['name'];
1188 $buffer = "'default'";
1190 if (isset($attrs['assign']))
1191 $assign = $attrs['assign'];
1194 $output = "<?php ob_start(); ?>";
1195 $this->_capture_stack
[] = array($buffer, $assign);
1197 list($buffer, $assign) = array_pop($this->_capture_stack
);
1198 $output = "<?php \$this->_smarty_vars['capture'][$buffer] = ob_get_contents(); ";
1199 if (isset($assign)) {
1200 $output .= " \$this->assign($assign, ob_get_contents());";
1202 $output .= "ob_end_clean(); ?>";
1209 * Compile {if ...} tag
1211 * @param string $tag_args
1212 * @param boolean $elseif if true, uses elseif instead of if
1215 function _compile_if_tag($tag_args, $elseif = false)
1218 /* Tokenize args for 'if' tag. */
1219 preg_match_all('/(?>
1220 ' . $this->_obj_call_regexp
. '(?:' . $this->_mod_regexp
. '*)? | # valid object call
1221 ' . $this->_var_regexp
. '(?:' . $this->_mod_regexp
. '*)? | # var or quoted string
1222 \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@ | # valid non-word token
1223 \b\w+\b | # valid word token
1225 )/x', $tag_args, $match);
1227 $tokens = $match[0];
1229 // make sure we have balanced parenthesis
1230 $token_count = array_count_values($tokens);
1231 if(isset($token_count['(']) && $token_count['('] != $token_count[')']) {
1232 $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR
, __FILE__
, __LINE__
);
1235 $is_arg_stack = array();
1237 for ($i = 0; $i < count($tokens); $i++
) {
1239 $token = &$tokens[$i];
1241 switch (strtolower($token)) {
1314 array_push($is_arg_stack, $i);
1318 /* If last token was a ')', we operate on the parenthesized
1319 expression. The start of the expression is on the stack.
1320 Otherwise, we operate on the last encountered token. */
1321 if ($tokens[$i-1] == ')')
1322 $is_arg_start = array_pop($is_arg_stack);
1324 $is_arg_start = $i-1;
1325 /* Construct the argument for 'is' expression, so it knows
1326 what to operate on. */
1327 $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
1329 /* Pass all tokens from next one until the end to the
1330 'is' expression parsing function. The function will
1331 return modified tokens, where the first one is the result
1332 of the 'is' expression and the rest are the tokens it
1334 $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+
1));
1336 /* Replace the old tokens with the new ones. */
1337 array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);
1339 /* Adjust argument start so that it won't change from the
1340 current position for the next iteration. */
1345 if(preg_match('!^' . $this->_func_regexp
. '$!', $token) ) {
1347 if($this->security
&&
1348 !in_array($token, $this->security_settings
['IF_FUNCS'])) {
1349 $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR
, __FILE__
, __LINE__
);
1351 } elseif(preg_match('!^' . $this->_obj_call_regexp
. '|' . $this->_var_regexp
. '(?:' . $this->_mod_regexp
. '*)$!', $token)) {
1352 // object or variable
1353 $token = $this->_parse_var_props($token);
1354 } elseif(is_numeric($token)) {
1357 $this->_syntax_error("unidentified token '$token'", E_USER_ERROR
, __FILE__
, __LINE__
);
1364 return '<?php elseif ('.implode(' ', $tokens).'): ?>';
1366 return '<?php if ('.implode(' ', $tokens).'): ?>';
1370 function _compile_arg_list($type, $name, $attrs, &$cache_code) {
1371 $arg_list = array();
1373 if (isset($type) && isset($name)
1374 && isset($this->_plugins
[$type])
1375 && isset($this->_plugins
[$type][$name])
1376 && empty($this->_plugins
[$type][$name][4])
1377 && is_array($this->_plugins
[$type][$name][5])
1379 /* we have a list of parameters that should be cached */
1380 $_cache_attrs = $this->_plugins
[$type][$name][5];
1381 $_count = $this->_cache_attrs_count++
;
1382 $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');";
1385 /* no parameters are cached */
1386 $_cache_attrs = null;
1389 foreach ($attrs as $arg_name => $arg_value) {
1390 if (is_bool($arg_value))
1391 $arg_value = $arg_value ?
'true' : 'false';
1392 if (is_null($arg_value))
1393 $arg_value = 'null';
1394 if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) {
1395 $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)";
1397 $arg_list[] = "'$arg_name' => $arg_value";
1404 * Parse is expression
1406 * @param string $is_arg
1407 * @param array $tokens
1410 function _parse_is_expr($is_arg, $tokens)
1413 $negate_expr = false;
1415 if (($first_token = array_shift($tokens)) == 'not') {
1416 $negate_expr = true;
1417 $expr_type = array_shift($tokens);
1419 $expr_type = $first_token;
1421 switch ($expr_type) {
1423 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1425 $expr_arg = $tokens[$expr_end++
];
1426 $expr = "!(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1428 $expr = "!(1 & $is_arg)";
1432 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1434 $expr_arg = $tokens[$expr_end++
];
1435 $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1437 $expr = "(1 & $is_arg)";
1441 if (@$tokens[$expr_end] == 'by') {
1443 $expr_arg = $tokens[$expr_end++
];
1444 $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")";
1446 $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR
, __FILE__
, __LINE__
);
1451 $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR
, __FILE__
, __LINE__
);
1459 array_splice($tokens, 0, $expr_end, $expr);
1466 * Parse attribute string
1468 * @param string $tag_args
1471 function _parse_attrs($tag_args)
1474 /* Tokenize tag attributes. */
1475 preg_match_all('/(?:' . $this->_obj_call_regexp
. '|' . $this->_qstr_regexp
. ' | (?>[^"\'=\s]+)
1478 /x', $tag_args, $match);
1479 $tokens = $match[0];
1483 0 - expecting attribute name
1485 2 - expecting attribute value (not '=') */
1488 foreach ($tokens as $token) {
1491 /* If the token is a valid identifier, we set attribute name
1492 and go to state 1. */
1493 if (preg_match('!^\w+$!', $token)) {
1494 $attr_name = $token;
1497 $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR
, __FILE__
, __LINE__
);
1501 /* If the token is '=', then we go to state 2. */
1502 if ($token == '=') {
1505 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR
, __FILE__
, __LINE__
);
1509 /* If token is not '=', we set the attribute value and go to
1511 if ($token != '=') {
1512 /* We booleanize the token if it's a non-quoted possible
1514 if (preg_match('!^(on|yes|true)$!', $token)) {
1516 } else if (preg_match('!^(off|no|false)$!', $token)) {
1518 } else if ($token == 'null') {
1520 } else if (preg_match('!^-?([0-9]+|0[xX][0-9a-fA-F]+)$!', $token)) {
1521 /* treat integer literally */
1522 } else if (!preg_match('!^' . $this->_obj_call_regexp
. '|' . $this->_var_regexp
. '(?:' . $this->_mod_regexp
. ')*$!', $token)) {
1523 /* treat as a string, double-quote it escaping quotes */
1524 $token = '"'.addslashes($token).'"';
1527 $attrs[$attr_name] = $token;
1530 $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR
, __FILE__
, __LINE__
);
1533 $last_token = $token;
1538 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR
, __FILE__
, __LINE__
);
1540 $this->_syntax_error("missing attribute value", E_USER_ERROR
, __FILE__
, __LINE__
);
1544 $this->_parse_vars_props($attrs);
1550 * compile multiple variables and section properties tokens into
1553 * @param array $tokens
1555 function _parse_vars_props(&$tokens)
1557 foreach($tokens as $key => $val) {
1558 $tokens[$key] = $this->_parse_var_props($val);
1563 * compile single variable and section properties token into
1566 * @param string $val
1567 * @param string $tag_attrs
1570 function _parse_var_props($val)
1574 if(preg_match('!^(' . $this->_obj_call_regexp
. '|' . $this->_dvar_regexp
. ')(' . $this->_mod_regexp
. '*)$!', $val, $match)) {
1575 // $ variable or object
1576 $return = $this->_parse_var($match[1]);
1577 $modifiers = $match[2];
1578 if (!empty($this->default_modifiers
) && !preg_match('!(^|\|)smarty:nodefaults($|\|)!',$modifiers)) {
1579 $_default_mod_string = implode('|',(array)$this->default_modifiers
);
1580 $modifiers = empty($modifiers) ?
$_default_mod_string : $_default_mod_string . '|' . $modifiers;
1582 $this->_parse_modifiers($return, $modifiers);
1584 } elseif (preg_match('!^' . $this->_db_qstr_regexp
. '(?:' . $this->_mod_regexp
. '*)$!', $val)) {
1585 // double quoted text
1586 preg_match('!^(' . $this->_db_qstr_regexp
. ')('. $this->_mod_regexp
. '*)$!', $val, $match);
1587 $return = $this->_expand_quoted_text($match[1]);
1588 if($match[2] != '') {
1589 $this->_parse_modifiers($return, $match[2]);
1593 elseif(preg_match('!^' . $this->_si_qstr_regexp
. '(?:' . $this->_mod_regexp
. '*)$!', $val)) {
1594 // single quoted text
1595 preg_match('!^(' . $this->_si_qstr_regexp
. ')('. $this->_mod_regexp
. '*)$!', $val, $match);
1596 if($match[2] != '') {
1597 $this->_parse_modifiers($match[1], $match[2]);
1601 elseif(preg_match('!^' . $this->_cvar_regexp
. '(?:' . $this->_mod_regexp
. '*)$!', $val)) {
1603 return $this->_parse_conf_var($val);
1605 elseif(preg_match('!^' . $this->_svar_regexp
. '(?:' . $this->_mod_regexp
. '*)$!', $val)) {
1607 return $this->_parse_section_prop($val);
1609 elseif(!in_array($val, $this->_permitted_tokens
) && !is_numeric($val)) {
1611 return $this->_expand_quoted_text('"' . $val .'"');
1617 * expand quoted text with embedded variables
1619 * @param string $var_expr
1622 function _expand_quoted_text($var_expr)
1624 // if contains unescaped $, expand it
1625 if(preg_match_all('%(?:\`(?<!\\\\)\$' . $this->_dvar_guts_regexp
. '\`)|(?:(?<!\\\\)\$\w+(\[[a-zA-Z0-9]+\])*)%', $var_expr, $_match)) {
1626 $_match = $_match[0];
1629 foreach($_match as $_var) {
1630 $var_expr = str_replace ($_var, '".(' . $this->_parse_var(str_replace('`','',$_var)) . ')."', $var_expr);
1632 $_return = preg_replace('%\.""|(?<!\\\\)""\.%', '', $var_expr);
1634 $_return = $var_expr;
1636 // replace double quoted literal string with single quotes
1637 $_return = preg_replace('!^"([\s\w]+)"$!',"'\\1'",$_return);
1642 * parse variable expression into PHP code
1644 * @param string $var_expr
1645 * @param string $output
1648 function _parse_var($var_expr)
1651 $_math_vars = preg_split('!('.$this->_dvar_math_regexp
.'|'.$this->_qstr_regexp
.')!', $var_expr, -1, PREG_SPLIT_DELIM_CAPTURE
);
1653 if(count($_math_vars) > 1) {
1655 $_complete_var = "";
1657 // simple check if there is any math, to stop recursion (due to modifiers with "xx % yy" as parameter)
1658 foreach($_math_vars as $_k => $_math_var) {
1659 $_math_var = $_math_vars[$_k];
1661 if(!empty($_math_var) ||
is_numeric($_math_var)) {
1662 // hit a math operator, so process the stuff which came before it
1663 if(preg_match('!^' . $this->_dvar_math_regexp
. '$!', $_math_var)) {
1665 if(!empty($_complete_var) ||
is_numeric($_complete_var)) {
1666 $_output .= $this->_parse_var($_complete_var);
1669 // just output the math operator to php
1670 $_output .= $_math_var;
1672 if(empty($_first_var))
1673 $_first_var = $_complete_var;
1675 $_complete_var = "";
1677 // 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
1678 for($_i = $_k +
1; $_i <= count($_math_vars); $_i +
= 2) {
1679 // fetch -> because it gets splitted at - and move it back together
1680 if( /* prevent notice */ (isset($_math_vars[$_i]) && isset($_math_vars[$_i+
1])) && ($_math_vars[$_i] === '-' && $_math_vars[$_i+
1]{0} === '>')) {
1681 $_math_var .= $_math_vars[$_i].$_math_vars[$_i+
1];
1682 $_math_vars[$_i] = $_math_vars[$_i+
1] = '';
1687 $_complete_var .= $_math_var;
1692 if(!empty($_complete_var) ||
is_numeric($_complete_var))
1693 $_output .= $this->_parse_var($_complete_var, true);
1695 // get the modifiers working (only the last var from math + modifier is left)
1696 $var_expr = $_complete_var;
1700 // prevent cutting of first digit in the number (we _definitly_ got a number if the first char is a digit)
1701 if(is_numeric($var_expr{0}))
1702 $_var_ref = $var_expr;
1704 $_var_ref = substr($var_expr, 1);
1707 // get [foo] and .foo and ->foo and (...) pieces
1708 preg_match_all('!(?:^\w+)|' . $this->_obj_params_regexp
. '|(?:' . $this->_var_bracket_regexp
. ')|->\$?\w+|\.\$?\w+|\S+!', $_var_ref, $match);
1710 $_indexes = $match[0];
1711 $_var_name = array_shift($_indexes);
1713 /* Handle $smarty.* variable references as a special case. */
1714 if ($_var_name == 'smarty') {
1716 * If the reference could be compiled, use the compiled output;
1717 * otherwise, fall back on the $smarty variable generated at
1720 if (($smarty_ref = $this->_compile_smarty_ref($_indexes)) !== null) {
1721 $_output = $smarty_ref;
1723 $_var_name = substr(array_shift($_indexes), 1);
1724 $_output = "\$this->_smarty_vars['$_var_name']";
1726 } elseif(is_numeric($_var_name) && is_numeric($var_expr{0})) {
1727 // because . is the operator for accessing arrays thru inidizes we need to put it together again for floating point numbers
1728 if(count($_indexes) > 0)
1730 $_var_name .= implode("", $_indexes);
1731 $_indexes = array();
1733 $_output = $_var_name;
1735 $_output = "\$this->_tpl_vars['$_var_name']";
1738 foreach ($_indexes as $_index) {
1739 if ($_index{0} == '[') {
1740 $_index = substr($_index, 1, -1);
1741 if (is_numeric($_index)) {
1742 $_output .= "[$_index]";
1743 } elseif ($_index{0} == '$') {
1744 if (strpos($_index, '.') !== false) {
1745 $_output .= '[' . $this->_parse_var($_index) . ']';
1747 $_output .= "[\$this->_tpl_vars['" . substr($_index, 1) . "']]";
1750 $_var_parts = explode('.', $_index);
1751 $_var_section = $_var_parts[0];
1752 $_var_section_prop = isset($_var_parts[1]) ?
$_var_parts[1] : 'index';
1753 $_output .= "[\$this->_sections['$_var_section']['$_var_section_prop']]";
1755 } else if ($_index{0} == '.') {
1756 if ($_index{1} == '$')
1757 $_output .= "[\$this->_tpl_vars['" . substr($_index, 2) . "']]";
1759 $_output .= "['" . substr($_index, 1) . "']";
1760 } else if (substr($_index,0,2) == '->') {
1761 if(substr($_index,2,2) == '__') {
1762 $this->_syntax_error('call to internal object members is not allowed', E_USER_ERROR
, __FILE__
, __LINE__
);
1763 } elseif($this->security
&& substr($_index, 2, 1) == '_') {
1764 $this->_syntax_error('(secure) call to private object member is not allowed', E_USER_ERROR
, __FILE__
, __LINE__
);
1765 } elseif ($_index{2} == '$') {
1766 if ($this->security
) {
1767 $this->_syntax_error('(secure) call to dynamic object member is not allowed', E_USER_ERROR
, __FILE__
, __LINE__
);
1769 $_output .= '->{(($_var=$this->_tpl_vars[\''.substr($_index,3).'\']) && substr($_var,0,2)!=\'__\') ? $_var : $this->trigger_error("cannot access property \\"$_var\\"")}';
1772 $_output .= $_index;
1774 } elseif ($_index{0} == '(') {
1775 $_index = $this->_parse_parenth_args($_index);
1776 $_output .= $_index;
1778 $_output .= $_index;
1787 * parse arguments in function call parenthesis
1789 * @param string $parenth_args
1792 function _parse_parenth_args($parenth_args)
1794 preg_match_all('!' . $this->_param_regexp
. '!',$parenth_args, $match);
1798 $orig_vals = $match;
1799 $this->_parse_vars_props($match);
1800 return str_replace($orig_vals, $match, $parenth_args);
1804 * parse configuration variable expression into PHP code
1806 * @param string $conf_var_expr
1808 function _parse_conf_var($conf_var_expr)
1810 $parts = explode('|', $conf_var_expr, 2);
1811 $var_ref = $parts[0];
1812 $modifiers = isset($parts[1]) ?
$parts[1] : '';
1814 $var_name = substr($var_ref, 1, -1);
1816 $output = "\$this->_config[0]['vars']['$var_name']";
1818 $this->_parse_modifiers($output, $modifiers);
1824 * parse section property expression into PHP code
1826 * @param string $section_prop_expr
1829 function _parse_section_prop($section_prop_expr)
1831 $parts = explode('|', $section_prop_expr, 2);
1832 $var_ref = $parts[0];
1833 $modifiers = isset($parts[1]) ?
$parts[1] : '';
1835 preg_match('!%(\w+)\.(\w+)%!', $var_ref, $match);
1836 $section_name = $match[1];
1837 $prop_name = $match[2];
1839 $output = "\$this->_sections['$section_name']['$prop_name']";
1841 $this->_parse_modifiers($output, $modifiers);
1848 * parse modifier chain into PHP code
1850 * sets $output to parsed modified chain
1851 * @param string $output
1852 * @param string $modifier_string
1854 function _parse_modifiers(&$output, $modifier_string)
1856 preg_match_all('!\|(@?\w+)((?>:(?:'. $this->_qstr_regexp
. '|[^|]+))*)!', '|' . $modifier_string, $_match);
1857 list(, $_modifiers, $modifier_arg_strings) = $_match;
1859 for ($_i = 0, $_for_max = count($_modifiers); $_i < $_for_max; $_i++
) {
1860 $_modifier_name = $_modifiers[$_i];
1862 if($_modifier_name == 'smarty') {
1863 // skip smarty modifier
1867 preg_match_all('!:(' . $this->_qstr_regexp
. '|[^:]+)!', $modifier_arg_strings[$_i], $_match);
1868 $_modifier_args = $_match[1];
1870 if ($_modifier_name{0} == '@') {
1871 $_map_array = false;
1872 $_modifier_name = substr($_modifier_name, 1);
1877 if (empty($this->_plugins
['modifier'][$_modifier_name])
1878 && !$this->_get_plugin_filepath('modifier', $_modifier_name)
1879 && function_exists($_modifier_name)) {
1880 if ($this->security
&& !in_array($_modifier_name, $this->security_settings
['MODIFIER_FUNCS'])) {
1881 $this->_trigger_fatal_error("[plugin] (secure mode) modifier '$_modifier_name' is not allowed" , $this->_current_file
, $this->_current_line_no
, __FILE__
, __LINE__
);
1883 $this->_plugins
['modifier'][$_modifier_name] = array($_modifier_name, null, null, false);
1886 $this->_add_plugin('modifier', $_modifier_name);
1888 $this->_parse_vars_props($_modifier_args);
1890 if($_modifier_name == 'default') {
1891 // supress notifications of default modifier vars and args
1892 if($output{0} == '$') {
1893 $output = '@' . $output;
1895 if(isset($_modifier_args[0]) && $_modifier_args[0]{0} == '$') {
1896 $_modifier_args[0] = '@' . $_modifier_args[0];
1899 if (count($_modifier_args) > 0)
1900 $_modifier_args = ', '.implode(', ', $_modifier_args);
1902 $_modifier_args = '';
1905 $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))";
1909 $output = $this->_compile_plugin_call('modifier', $_modifier_name)."($output$_modifier_args)";
1919 * @param string $type
1920 * @param string $name
1921 * @param boolean? $delayed_loading
1923 function _add_plugin($type, $name, $delayed_loading = null)
1925 if (!isset($this->_plugin_info
[$type])) {
1926 $this->_plugin_info
[$type] = array();
1928 if (!isset($this->_plugin_info
[$type][$name])) {
1929 $this->_plugin_info
[$type][$name] = array($this->_current_file
,
1930 $this->_current_line_no
,
1937 * Compiles references of type $smarty.foo
1939 * @param string $indexes
1942 function _compile_smarty_ref(&$indexes)
1944 /* Extract the reference name. */
1945 $_ref = substr($indexes[0], 1);
1946 foreach($indexes as $_index_no=>$_index) {
1947 if ($_index{0} != '.' && $_index_no<2 ||
!preg_match('!^(\.|\[|->)!', $_index)) {
1948 $this->_syntax_error('$smarty' . implode('', array_slice($indexes, 0, 2)) . ' is an invalid reference', E_USER_ERROR
, __FILE__
, __LINE__
);
1954 $compiled_ref = 'time()';
1960 array_shift($indexes);
1961 $_var = $this->_parse_var_props(substr($indexes[0], 1));
1962 if ($_ref == 'foreach')
1963 $compiled_ref = "\$this->_foreach[$_var]";
1965 $compiled_ref = "\$this->_sections[$_var]";
1969 $compiled_ref = ($this->request_use_auto_globals
) ?
'$_GET' : "\$GLOBALS['HTTP_GET_VARS']";
1973 $compiled_ref = ($this->request_use_auto_globals
) ?
'$_POST' : "\$GLOBALS['HTTP_POST_VARS']";
1977 $compiled_ref = ($this->request_use_auto_globals
) ?
'$_COOKIE' : "\$GLOBALS['HTTP_COOKIE_VARS']";
1981 $compiled_ref = ($this->request_use_auto_globals
) ?
'$_ENV' : "\$GLOBALS['HTTP_ENV_VARS']";
1985 $compiled_ref = ($this->request_use_auto_globals
) ?
'$_SERVER' : "\$GLOBALS['HTTP_SERVER_VARS']";
1989 $compiled_ref = ($this->request_use_auto_globals
) ?
'$_SESSION' : "\$GLOBALS['HTTP_SESSION_VARS']";
1993 * These cases are handled either at run-time or elsewhere in the
1997 if ($this->request_use_auto_globals
) {
1998 $compiled_ref = '$_REQUEST';
2001 $this->_init_smarty_vars
= true;
2009 $compiled_ref = "'$this->_current_file'";
2014 $compiled_ref = "'$this->_version'";
2019 array_shift($indexes);
2020 $_val = $this->_parse_var_props(substr($indexes[0],1));
2021 $compiled_ref = '@constant(' . $_val . ')';
2026 $compiled_ref = "\$this->_config[0]['vars']";
2031 $this->_syntax_error('$smarty.' . $_ref . ' is an unknown reference', E_USER_ERROR
, __FILE__
, __LINE__
);
2035 if (isset($_max_index) && count($indexes) > $_max_index) {
2036 $this->_syntax_error('$smarty' . implode('', $indexes) .' is an invalid reference', E_USER_ERROR
, __FILE__
, __LINE__
);
2039 array_shift($indexes);
2040 return $compiled_ref;
2044 * compiles call to plugin of type $type with name $name
2045 * returns a string containing the function-name or method call
2046 * without the paramter-list that would have follow to make the
2047 * call valid php-syntax
2049 * @param string $type
2050 * @param string $name
2053 function _compile_plugin_call($type, $name) {
2054 if (isset($this->_plugins
[$type][$name])) {
2056 if (is_array($this->_plugins
[$type][$name][0])) {
2057 return ((is_object($this->_plugins
[$type][$name][0][0])) ?
2058 "\$this->_plugins['$type']['$name'][0][0]->" /* method callback */
2059 : (string)($this->_plugins
[$type][$name][0][0]).'::' /* class callback */
2060 ). $this->_plugins
[$type][$name][0][1];
2063 /* function callback */
2064 return $this->_plugins
[$type][$name][0];
2068 /* plugin not loaded -> auto-loadable-plugin */
2069 return 'smarty_'.$type.'_'.$name;
2075 * load pre- and post-filters
2077 function _load_filters()
2079 if (count($this->_plugins
['prefilter']) > 0) {
2080 foreach ($this->_plugins
['prefilter'] as $filter_name => $prefilter) {
2081 if ($prefilter === false) {
2082 unset($this->_plugins
['prefilter'][$filter_name]);
2083 $_params = array('plugins' => array(array('prefilter', $filter_name, null, null, false)));
2084 require_once(SMARTY_DIR
. 'core' . DIRECTORY_SEPARATOR
. 'core.load_plugins.php');
2085 smarty_core_load_plugins($_params, $this);
2089 if (count($this->_plugins
['postfilter']) > 0) {
2090 foreach ($this->_plugins
['postfilter'] as $filter_name => $postfilter) {
2091 if ($postfilter === false) {
2092 unset($this->_plugins
['postfilter'][$filter_name]);
2093 $_params = array('plugins' => array(array('postfilter', $filter_name, null, null, false)));
2094 require_once(SMARTY_DIR
. 'core' . DIRECTORY_SEPARATOR
. 'core.load_plugins.php');
2095 smarty_core_load_plugins($_params, $this);
2103 * Quote subpattern references
2105 * @param string $string
2108 function _quote_replace($string)
2110 return preg_replace('![\\$]\d!', '\\\\\\0', $string);
2114 * display Smarty syntax error
2116 * @param string $error_msg
2117 * @param integer $error_type
2118 * @param string $file
2119 * @param integer $line
2121 function _syntax_error($error_msg, $error_type = E_USER_ERROR
, $file=null, $line=null)
2123 $this->_trigger_fatal_error("syntax error: $error_msg", $this->_current_file
, $this->_current_line_no
, $file, $line, $error_type);
2128 * check if the compilation changes from cacheable to
2129 * non-cacheable state with the beginning of the current
2130 * plugin. return php-code to reflect the transition.
2133 function _push_cacheable_state($type, $name) {
2134 $_cacheable = !isset($this->_plugins
[$type][$name]) ||
$this->_plugins
[$type][$name][4];
2136 ||
0<$this->_cacheable_state++
) return '';
2137 if (!isset($this->_cache_serial
)) $this->_cache_serial
= md5(uniqid('Smarty'));
2138 $_ret = 'if ($this->caching) { echo \'{nocache:'
2139 . $this->_cache_serial
. '#' . $this->_nocache_count
2146 * check if the compilation changes from non-cacheable to
2147 * cacheable state with the end of the current plugin return
2148 * php-code to reflect the transition.
2151 function _pop_cacheable_state($type, $name) {
2152 $_cacheable = !isset($this->_plugins
[$type][$name]) ||
$this->_plugins
[$type][$name][4];
2154 ||
--$this->_cacheable_state
>0) return '';
2155 return 'if ($this->caching) { echo \'{/nocache:'
2156 . $this->_cache_serial
. '#' . ($this->_nocache_count++
)
2162 * push opening tag-name, file-name and line-number on the tag-stack
2163 * @param: string the opening tag's name
2165 function _push_tag($open_tag)
2167 array_push($this->_tag_stack
, array($open_tag, $this->_current_line_no
));
2171 * pop closing tag-name
2172 * raise an error if this stack-top doesn't match with the closing tag
2173 * @param: string the closing tag's name
2174 * @return: string the opening tag's name
2176 function _pop_tag($close_tag)
2179 if (count($this->_tag_stack
)>0) {
2180 list($_open_tag, $_line_no) = array_pop($this->_tag_stack
);
2181 if ($close_tag == $_open_tag) {
2184 if ($close_tag == 'if' && ($_open_tag == 'else' ||
$_open_tag == 'elseif' )) {
2185 return $this->_pop_tag($close_tag);
2187 if ($close_tag == 'section' && $_open_tag == 'sectionelse') {
2188 $this->_pop_tag($close_tag);
2191 if ($close_tag == 'foreach' && $_open_tag == 'foreachelse') {
2192 $this->_pop_tag($close_tag);
2195 $message = " expected {/$_open_tag} (opened line $_line_no).";
2197 $this->_syntax_error("mismatched tag {/$close_tag}.$message",
2198 E_USER_ERROR
, __FILE__
, __LINE__
);
2204 * compare to values by their string length
2211 function _smarty_sort_length($a, $b)
2216 if(strlen($a) == strlen($b))
2217 return ($a > $b) ?
-1 : 1;
2219 return (strlen($a) > strlen($b)) ?
-1 : 1;