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