/framework/vendor/smarty2/lib/Smarty_Compiler.class.php
PHP | 1632 lines | 1105 code | 195 blank | 332 comment | 228 complexity | 348841a029ab7a7e5f51ca3b2cd3b3b1 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
1<?php 2 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.14 25 * @copyright 2001-2005 New Digital Group, Inc. 26 * @package Smarty 27 */ 28 29/* $Id: Smarty_Compiler.class.php,v 1.381 2006/05/25 14:46:18 boots Exp $ */ 30 31/** 32 * Template compiling class 33 * @package Smarty 34 */ 35class Smarty_Compiler extends Smarty { 36 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; 73 74 var $_strip_depth = 0; 75 var $_additional_newline = "\n"; 76 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 = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; 87 88 // matches single quoted strings: 89 // 'foobar' 90 // 'foo\'bar' 91 $this->_si_qstr_regexp = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\''; 92 93 // matches single or double quoted strings 94 $this->_qstr_regexp = '(?:' . $this->_db_qstr_regexp . '|' . $this->_si_qstr_regexp . ')'; 95 96 // matches bracket portion of vars 97 // [0] 98 // [foo] 99 // [$bar] 100 $this->_var_bracket_regexp = '\[\$?[\w\.]+\]'; 101 102 // matches numerical constants 103 // 30 104 // -12 105 // 13.22 106 $this->_num_const_regexp = '(?:\-?\d+(?:\.\d+)?)'; 107 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; 121 122 // matches config vars: 123 // #foo# 124 // #foobar123_foo# 125 $this->_cvar_regexp = '\#\w+\#'; 126 127 // matches section vars: 128 // %foo.bar% 129 $this->_svar_regexp = '\%\w+\.\w+\%'; 130 131 // matches all valid variables (no quotes, no modifiers) 132 $this->_avar_regexp = '(?:' . $this->_dvar_regexp . '|' 133 . $this->_cvar_regexp . '|' . $this->_svar_regexp . ')'; 134 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 . ')'; 143 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 . ')*)?)'; 165 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 .'))*)'; 176 177 // matches valid function name: 178 // foo123 179 // _foo_bar 180 $this->_func_regexp = '[a-zA-Z_]\w*'; 181 182 // matches valid registered object: 183 // foo->bar 184 $this->_reg_obj_regexp = '[a-zA-Z_]\w*->[a-zA-Z_]\w*'; 185 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*)'; 197 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 . ')))*)?\))'; 206 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 } 215 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 { 227 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 } 235 236 $this->_load_filters(); 237 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, '~'); 242 243 /* un-hide hidden xml open tags */ 244 $source_content = preg_replace("~<({$ldq}(.*?){$rdq})[?]~s", '< \\1', $source_content); 245 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 } 259 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"; 262 263 preg_match_all($search, $source_content, $match, PREG_SET_ORDER); 264 $this->_folded_blocks = $match; 265 reset($this->_folded_blocks); 266 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); 274 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); 280 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 } 310 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 } 323 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 = ''; 355 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]; 365 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 } 370 371 if (!empty($this->_cache_serial)) { 372 $compiled_content = "<?php \$this->_cache_serials['".$this->_cache_include."'] = '".$this->_cache_serial."'; ?>" . $compiled_content; 373 } 374 375 // remove unnecessary close/open tags 376 $compiled_content = preg_replace('~\?>\n?<\?php~', '', $compiled_content); 377 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 } 391 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"; 395 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 } 412 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 } 417 418 $compiled_content = $template_header . $compiled_content; 419 return true; 420 } 421 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 ''; 433 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 } 441 442 $tag_command = $match[1]; 443 $tag_modifier = isset($match[2]) ? $match[2] : null; 444 $tag_args = isset($match[3]) ? $match[3] : null; 445 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 } 451 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 } 456 457 switch ($tag_command) { 458 case 'include': 459 return $this->_compile_include_tag($tag_args); 460 461 case 'include_php': 462 return $this->_compile_include_php_tag($tag_args); 463 464 case 'if': 465 $this->_push_tag('if'); 466 return $this->_compile_if_tag($tag_args); 467 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: ?>'; 475 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); 483 484 case '/if': 485 $this->_pop_tag('if'); 486 return '<?php endif; ?>'; 487 488 case 'capture': 489 return $this->_compile_capture_tag(true, $tag_args); 490 491 case '/capture': 492 return $this->_compile_capture_tag(false); 493 494 case 'ldelim': 495 return $this->left_delimiter; 496 497 case 'rdelim': 498 return $this->right_delimiter; 499 500 case 'section': 501 $this->_push_tag('section'); 502 return $this->_compile_section_start($tag_args); 503 504 case 'sectionelse': 505 $this->_push_tag('sectionelse'); 506 return "<?php endfor; else: ?>"; 507 break; 508 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; ?>"; 515 516 case 'foreach': 517 $this->_push_tag('foreach'); 518 return $this->_compile_foreach_start($tag_args); 519 break; 520 521 case 'foreachelse': 522 $this->_push_tag('foreachelse'); 523 return "<?php endforeach; else: ?>"; 524 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; 532 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 ''; 549 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 ''; 559 560 case 3: /* literal */ 561 return "<?php echo '" . strtr($block[2], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>" . $this->_additional_newline; 562 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; 571 572 case 'insert': 573 return $this->_compile_insert_tag($tag_args); 574 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 } 585 586 } 587 } 588 589 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; 603 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; 622 623 include_once $plugin_file; 624 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 } 633 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 } 656 657 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; 675 676 $found = false; 677 $have_function = true; 678 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; 697 698 include_once $plugin_file; 699 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); 706 707 } 708 } 709 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 } 716 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); 723 724 if ($start_tag) 725 $this->_push_tag($tag_command); 726 else 727 $this->_pop_tag($tag_command); 728 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 } 746 747 return true; 748 } 749 750 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; 763 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; 782 783 include_once $plugin_file; 784 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); 791 792 } 793 } 794 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 } 801 802 /* declare plugin to be loaded on display of the template that 803 we compile right now */ 804 $this->_add_plugin('function', $tag_command); 805 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); 810 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 } 815 816 if($output != '') { 817 $output = '<?php ' . $_cacheable_state . $_cache_attrs . 'echo ' . $output . ';' 818 . $this->_pop_cacheable_state('function', $tag_command) . "?>" . $this->_additional_newline; 819 } 820 821 return true; 822 } 823 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 } 840 841 list($object, $obj_comp) = explode('->', $tag_command); 842 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 } 857 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 } 868 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 } 899 900 if($return != null) { 901 if($tag_modifier != '') { 902 $this->_parse_modifiers($return, $tag_modifier); 903 } 904 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 } 914 915 return '<?php ' . $prefix . $output . $postfix . "?>" . $newline; 916 } 917 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']); 928 929 if (empty($name)) { 930 $this->_syntax_error("missing insert name", E_USER_ERROR, __FILE__, __LINE__); 931 } 932 933 if (!empty($attrs['script'])) { 934 $delayed_loading = true; 935 } else { 936 $delayed_loading = false; 937 } 938 939 foreach ($attrs as $arg_name => $arg_value) { 940 if (is_bool($arg_value)) 941 $arg_value = $arg_value ? 'true' : 'false'; 942 $arg_list[] = "'$arg_name' => $arg_value"; 943 } 944 945 $this->_add_plugin('insert', $name, $delayed_loading); 946 947 $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))"; 948 949 return "<?php require_once(SMARTY_CORE_DIR . 'core.run_insert_handler.php');\necho smarty_core_run_insert_handler($_params, \$this); ?>" . $this->_additional_newline; 950 } 951 952 /** 953 * Compile {include ...} tag 954 * 955 * @param string $tag_args 956 * @return string 957 */ 958 function _compile_include_tag($tag_args) 959 { 960 $attrs = $this->_parse_attrs($tag_args); 961 $arg_list = array(); 962 963 if (empty($attrs['file'])) { 964 $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR, __FILE__, __LINE__); 965 } 966 967 foreach ($attrs as $arg_name => $arg_value) { 968 if ($arg_name == 'file') { 969 $include_file = $arg_value; 970 continue; 971 } else if ($arg_name == 'assign') { 972 $assign_var = $arg_value; 973 continue; 974 } 975 if (is_bool($arg_value)) 976 $arg_value = $arg_value ? 'true' : 'false'; 977 $arg_list[] = "'$arg_name' => $arg_value"; 978 } 979 980 $output = '<?php '; 981 982 if (isset($assign_var)) { 983 $output .= "ob_start();\n"; 984 } 985 986 $output .= 987 "\$_smarty_tpl_vars = \$this->_tpl_vars;\n"; 988 989 990 $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))"; 991 $output .= "\$this->_smarty_include($_params);\n" . 992 "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" . 993 "unset(\$_smarty_tpl_vars);\n"; 994 995 if (isset($assign_var)) { 996 $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n"; 997 } 998 999 $output .= ' ?>'; 1000 1001 return $output; 1002 1003 } 1004 1005 /** 1006 * Compile {include ...} tag 1007 * 1008 * @param string $tag_args 1009 * @return string 1010 */ 1011 function _compile_include_php_tag($tag_args) 1012 { 1013 $attrs = $this->_parse_attrs($tag_args); 1014 1015 if (empty($attrs['file'])) { 1016 $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR, __FILE__, __LINE__); 1017 } 1018 1019 $assign_var = (empty($attrs['assign'])) ? '' : $this->_dequote($attrs['assign']); 1020 $once_var = (empty($attrs['once']) || $attrs['once']=='false') ? 'false' : 'true'; 1021 1022 $arg_list = array(); 1023 foreach($attrs as $arg_name => $arg_value) { 1024 if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') { 1025 if(is_bool($arg_value)) 1026 $arg_value = $arg_value ? 'true' : 'false'; 1027 $arg_list[] = "'$arg_name' => $arg_value"; 1028 } 1029 } 1030 1031 $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', $arg_list)."))"; 1032 1033 return "<?php require_once(SMARTY_CORE_DIR . 'core.smarty_include_php.php');\nsmarty_core_smarty_include_php($_params, \$this); ?>" . $this->_additional_newline; 1034 } 1035 1036 1037 /** 1038 * Compile {section ...} tag 1039 * 1040 * @param string $tag_args 1041 * @return string 1042 */ 1043 function _compile_section_start($tag_args) 1044 { 1045 $attrs = $this->_parse_attrs($tag_args); 1046 $arg_list = array(); 1047 1048 $output = '<?php '; 1049 $section_name = $attrs['name']; 1050 if (empty($section_name)) { 1051 $this->_syntax_error("missing section name", E_USER_ERROR, __FILE__, __LINE__); 1052 } 1053 1054 $output .= "unset(\$this->_sections[$section_name]);\n"; 1055 $section_props = "\$this->_sections[$section_name]"; 1056 1057 foreach ($attrs as $attr_name => $attr_value) { 1058 switch ($attr_name) { 1059 case 'loop': 1060 $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n"; 1061 break; 1062 1063 case 'show': 1064 if (is_bool($attr_value)) 1065 $show_attr_value = $attr_value ? 'true' : 'false'; 1066 else 1067 $show_attr_value = "(bool)$attr_value"; 1068 $output .= "{$section_props}['show'] = $show_attr_value;\n"; 1069 break; 1070 1071 case 'name': 1072 $output .= "{$section_props}['$attr_name'] = $attr_value;\n"; 1073 break; 1074 1075 case 'max': 1076 case 'start': 1077 $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n"; 1078 break; 1079 1080 case 'step': 1081 $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n"; 1082 break; 1083 1084 default: 1085 $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR, __FILE__, __LINE__); 1086 break; 1087 } 1088 } 1089 1090 if (!isset($attrs['show'])) 1091 $output .= "{$section_props}['show'] = true;\n"; 1092 1093 if (!isset($attrs['loop'])) 1094 $output .= "{$section_props}['loop'] = 1;\n"; 1095 1096 if (!isset($attrs['max'])) 1097 $output .= "{$section_props}['max'] = {$section_props}['loop'];\n"; 1098 else 1099 $output .= "if ({$section_props}['max'] < 0)\n" . 1100 " {$section_props}['max'] = {$section_props}['loop'];\n"; 1101 1102 if (!isset($attrs['step'])) 1103 $output .= "{$section_props}['step'] = 1;\n"; 1104 1105 if (!isset($attrs['start'])) 1106 $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n"; 1107 else { 1108 $output .= "if ({$section_props}['start'] < 0)\n" . 1109 " {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" . 1110 "else\n" . 1111 " {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n"; 1112 } 1113 1114 $output .= "if ({$section_props}['show']) {\n"; 1115 if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) { 1116 $output .= " {$section_props}['total'] = {$section_props}['loop'];\n"; 1117 } else { 1118 $output .= " {$section_props}['total'] = min(ceil(({$section_props}['step'] > 0 ? {$section_props}['loop'] - {$section_props}['start'] : {$section_props}['start']+1)/abs({$section_props}['step'])), {$section_props}['max']);\n"; 1119 } 1120 $output .= " if ({$section_props}['total'] == 0)\n" . 1121 " {$section_props}['show'] = false;\n" . 1122 "} else\n" . 1123 " {$section_props}['total'] = 0;\n"; 1124 1125 $output .= "if ({$section_props}['show']):\n"; 1126 $output .= " 1127 for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1; 1128 {$section_props}['iteration'] <= {$section_props}['total']; 1129 {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n"; 1130 $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n"; 1131 $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n"; 1132 $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n"; 1133 $output .= "{$section_props}['first'] = ({$section_props}['iteration'] == 1);\n"; 1134 $output .= "{$section_props}['last'] = ({$section_props}['iteration'] == {$section_props}['total']);\n"; 1135 1136 $output .= "?>"; 1137 1138 return $output; 1139 } 1140 1141 1142 /** 1143 * Compile {foreach ...} tag. 1144 * 1145 * @param string $tag_args 1146 * @return string 1147 */ 1148 function _compile_foreach_start($tag_args) 1149 { 1150 $attrs = $this->_parse_attrs($tag_args); 1151 $arg_list = array(); 1152 1153 if (empty($attrs['from'])) { 1154 return $this->_syntax_error("foreach: missing 'from' attribute", E_USER_ERROR, __FILE__, __LINE__); 1155 } 1156 $from = $attrs['from']; 1157 1158 if (empty($attrs['item'])) { 1159 return $this->_syntax_error("foreach: missing 'item' attribute", E_USER_ERROR, __FILE__, __LINE__); 1160 } 1161 $item = $this->_dequote($attrs['item']); 1162 if (!preg_match('~^\w+$~', $item)) { 1163 return $this->_syntax_error("'foreach: item' must be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__); 1164 } 1165 1166 if (isset($attrs['key'])) { 1167 $key = $this->_dequote($attrs['key']); 1168 if (!preg_match('~^\w+$~', $key)) { 1169 return $this->_syntax_error("foreach: 'key' must to be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__); 1170 } 1171 $key_part = "\$this->_tpl_vars['$key'] => "; 1172 } else { 1173 $key = null; 1174 $key_part = ''; 1175 } 1176 1177 if (isset($attrs['name'])) { 1178 $name = $attrs['name']; 1179 } else { 1180 $name = null; 1181 } 1182 1183 $output = '<?php '; 1184 $output .= "\$_from = $from; if (!is_array(\$_from) && !is_object(\$_from)) { settype(\$_from, 'array'); }"; 1185 if (isset($name)) { 1186 $foreach_props = "\$this->_foreach[$name]"; 1187 $output .= "{$foreach_props} = array('total' => count(\$_from), 'iteration' => 0);\n"; 1188 $output .= "if ({$foreach_props}['total'] > 0):\n"; 1189 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n"; 1190 $output .= " {$foreach_props}['iteration']++;\n"; 1191 } else { 1192 $output .= "if (count(\$_from)):\n"; 1193 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n"; 1194 } 1195 $output .= '?>'; 1196 1197 return $output; 1198 } 1199 1200 1201 /** 1202 * Compile {capture} .. {/capture} tags 1203 * 1204 * @param boolean $start true if this is the {capture} tag 1205 * @param string $tag_args 1206 * @return string 1207 */ 1208 1209 function _compile_capture_tag($start, $tag_args = '') 1210 { 1211 $attrs = $this->_parse_attrs($tag_args); 1212 1213 if ($start) { 1214 if (isset($attrs['name'])) 1215 $buffer = $attrs['name']; 1216 else 1217 $buffer = "'default'"; 1218 1219 if (isset($attrs['assign'])) 1220 $assign = $attrs['assign']; 1221 else 1222 $assign = null; 1223 $output = "<?php ob_start(); ?>"; 1224 $this->_capture_stack[] = array($buffer, $assign); 1225 } else { 1226 list($buffer, $assign) = array_pop($this->_capture_stack); 1227 $output = "<?php \$this->_smarty_vars['capture'][$buffer] = ob_get_contents(); "; 1228 if (isset($assign)) { 1229 $output .= " \$this->assign($assign, ob_get_contents());"; 1230 } 1231 $output .= "ob_end_clean(); ?>"; 1232 } 1233 1234 return $output; 1235 } 1236 1237 /** 1238 * Compile {if ...} tag 1239 * 1240 * @param string $tag_args 1241 * @param boolean $elseif if true, uses elseif instead of if 1242 * @return string 1243 */ 1244 function _compile_if_tag($tag_args, $elseif = false) 1245 { 1246 1247 /* Tokenize args for 'if' tag. */ 1248 preg_match_all('~(?> 1249 ' . $this->_obj_call_regexp . '(?:' . $this->_mod_regexp . '*)? | # valid object call 1250 ' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)? | # var or quoted string 1251 \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@ | # valid non-word token 1252 \b\w+\b | # valid word token 1253 \S+ # anything else 1254 )~x', $tag_args, $match); 1255 1256 $tokens = $match[0]; 1257 1258 if(empty($tokens)) { 1259 $_error_msg = $elseif ? "'elseif'" : "'if'"; 1260 $_error_msg .= ' statement requires arguments'; 1261 $this->_syntax_error($_error_msg, E_USER_ERROR, __FILE__, __LINE__); 1262 } 1263 1264 1265 // make sure we have balanced parenthesis 1266 $token_count = array_count_values($tokens); 1267 if(isset($token_count['(']) && $token_count['('] != $token_count[')']) { 1268 $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR, __FILE__, __LINE__); 1269 } 1270 1271 $is_arg_stack = array(); 1272 1273 for ($i = 0; $i < count($tokens); $i++) { 1274 1275 $token = &$tokens[$i]; 1276 1277 switch (strtolower($token)) { 1278 case '!': 1279 case '%': 1280 case '!==': 1281 case '==': 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 break; 1305 1306 case 'eq': 1307 $token = '=='; 1308 break; 1309 1310 case 'ne': 1311 case 'neq': 1312 $token = '!='; 1313 break; 1314 1315 case 'lt': 1316 $token = '<'; 1317 break; 1318 1319 case 'le': 1320 case 'lte': 1321 $token = '<='; 1322 break; 1323 1324 case 'gt': 1325 $token = '>'; 1326 break; 1327 1328 case 'ge': 1329 case 'gte': 1330 $token = '>='; 1331 break; 1332 1333 case 'and': 1334 $token = '&&'; 1335 break; 1336 1337 case 'or': 1338 $token = '||'; 1339 break; 1340 1341 case 'not': 1342 $token = '!'; 1343 break; 1344 1345 case 'mod': 1346 $token = '%'; 1347 break; 1348 1349 case '(': 1350 array_push($is_arg_stack, $i); 1351 break; 1352 1353 case 'is': 1354 /* If last token was a ')', we operate on the parenthesized 1355 expression. The start of the expression is on the stack. 1356 Otherwise, we operate on the last encountered token. */ 1357 if ($tokens[$i-1] == ')') 1358 $is_arg_start = array_pop($is_arg_stack); 1359 else 1360 $is_arg_start = $i-1; 1361 /* Construct the argument for 'is' expression, so it knows 1362 what to operate on. */ 1363 $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start)); 1364 1365 /* Pass all tokens from next one until the end to the 1366 'is' expression parsing function. The function will 1367 return modified tokens, where the first one is the result 1368 of the 'is' expression and the rest are the tokens it 1369 didn't touch. */ 1370 $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1)); 1371 1372 /* Replace the old tokens with the new ones. */ 1373 array_splice($tokens, $is_arg_start, count($tokens), $new_tokens); 1374 1375 /* Adjust argument start so that it won't change from the 1376 current position for the next iteration. */ 1377 $i = $is_arg_start; 1378 break; 1379 1380 default: 1381 if(preg_match('~^' . $this->_func_regexp . '$~', $token) ) { 1382 // function call 1383 if($this->security && 1384 !in_array($token, $this->security_settings['IF_FUNCS'])) { 1385 $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__); 1386 } 1387 } elseif(preg_match('~^' . $this->_var_regexp . '$~', $token) && (strpos('+-*/^%&|', substr($token, -1)) === false) && isset($tokens[$i+1]) && $tokens[$i+1] == '(') { 1388 // variable function call 1389 $this->_syntax_error("variable function call '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__); 1390 } elseif(preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$~', $token)) { 1391 // object or variable 1392 $token = $this->_parse_var_props($token); 1393 } elseif(is_numeric($token)) { 1394 // number, skip it 1395 } else { 1396 $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__); 1397 } 1398 break; 1399 } 1400 } 1401 1402 if ($elseif) 1403 return '<?php elseif ('.implode(' ', $tokens).'): ?>'; 1404 else 1405 return '<?php if ('.implode(' ', $tokens).'): ?>'; 1406 } 1407 1408 1409 function _compile_arg_list($type, $name, $attrs, &$cache_code) { 1410 $arg_list = array(); 1411 1412 if (isset($type) && isset($name) 1413 && isset($this->_plugins[$type]) 1414 && isset($this->_plugins[$type][$name]) 1415 && empty($this->_plugins[$type][$name][4]) 1416 && is_array($this->_plugins[$type][$name][5]) 1417 ) { 1418 /* we have a list of parameters that should be cached */ 1419 $_cache_attrs = $this->_plugins[$type][$name][5]; 1420 $_count = $this->_cache_attrs_count++; 1421 $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');"; 1422 1423 } else { 1424 /* no parameters are cached */ 1425 $_cache_attrs = null; 1426 } 1427 1428 foreach ($attrs as $arg_name => $arg_value) { 1429 if (is_bool($arg_value)) 1430 $arg_value = $arg_value ? 'true' : 'false'; 1431 if (is_null($arg_value)) 1432 $arg_value = 'null'; 1433 if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) { 1434 $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)"; 1435 } else { 1436 $arg_list[] = "'$arg_name' => $arg_value"; 1437 } 1438 } 1439 return $arg_list; 1440 } 1441 1442 /** 1443 * Parse is expression 1444 * 1445 * @param string $is_arg 1446 * @param array $tokens 1447 * @return array 1448 */ 1449 function _parse_is_expr($is_arg, $tokens) 1450 { 1451 $expr_end = 0; 1452 $negate_expr = false; 1453 1454 if (($first_token = array_shift($tokens)) == 'not') { 1455 $negate_expr = true; 1456 $expr_type = array_shift($tokens); 1457 } else 1458 $expr_type = $first_token; 1459 1460 switch ($expr_type) { 1461 case 'even': 1462 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') { 1463 $expr_end++; 1464 $expr_arg = $tokens[$expr_end++]; 1465 $expr = "!(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))"; 1466 } else 1467 $expr = "!(1 & $is_arg)"; 1468 break; 1469 1470 case 'odd': 1471 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') { 1472 $expr_end++; 1473 $expr_arg = $tokens[$expr_end++]; 1474 $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))"; 1475 } else 1476 $expr = "(1 & $is_arg)"; 1477 break; 1478 1479 case 'div': 1480 if (@$tokens[$expr_end] == 'by') { 1481 $expr_end++; 1482 $expr_arg = $tokens[$expr_end++]; 1483 $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")"; 1484 } else { 1485 $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__); 1486 } 1487 break; 1488 1489 default: 1490 $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__); 1491 break; 1492 } 1493 1494 if ($negate_expr) { 1495 $expr = "!($expr)"; 1496 } 1497 1498 array_splice($tokens, 0, $expr_end, $expr); 1499 1500 return $tokens; 1501 } 1502 1503 1504 /** 1505 * Parse attribute string 1506 * 1507 * @param string $tag_args 1508 * @return array 1509 */ 1510 function _parse_attrs($tag_args) 1511 { 1512 1513 /* Tokenize tag attributes. */ 1514 preg_match_all('~(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+) 1515 )+ | 1516 [=] 1517 ~x', $tag_args, $match); 1518 $tokens = $match[0]; 1519 1520 $attrs = array(); 1521 /* Parse state: 1522 0 - expecting attribute name 1523 1 - expecting '=' 1524 2 - expecting attribute value (not '=') */ 1525 $state = 0; 1526 1527 foreach ($tokens as $token) { 1528 switch ($state) { 1529 case 0: 1530 /* If the token is a valid identifier, we set attribute name 1531 and go to state 1. */ 1532 if (preg_match('~^\w+$~', $token)) { 1533 $attr_name = $token; 1534 $state = 1; 1535 } else 1536 $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__); 1537 break; 1538 1539 case 1: 1540 /* If the token is '=', then we go to state 2. */ 1541 if ($token == '=') { 1542 $state = 2; 1543 } else 1544 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__); 1545 break; 1546 1547 case 2: 1548 /* If token is not '=', we set the attribute value and go to 1549 state 0. */ 1550 if ($token != '=') { 1551 /* We booleanize the token if it's a non-quoted possible 1552 boolean value. */ 1553 if (preg_match('~^(on|yes|true)$~', $token)) { 1554 $token = 'true'; 1555 } else if (preg_match('~^(off|no|false)$~', $token)) { 1556 $token = 'false'; 1557 } else if ($token == 'null') { 1558 $token = 'null'; 1559 } else if (preg_match('~^' . $this->_num_const_regexp . '|0[xX][0-9a-fA-F]+$~', $token)) { 1560 /* treat integer literally */ 1561 } else if (!preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$~', $token)) { 1562 /* treat as a string, double-quote it escaping quotes */ 1563 $token = '"'.addslashes($token).'"'; 1564 } 1565 1566 $attrs[$attr_name] = $token; 1567 $state = 0; 1568 } else 1569 $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__); 1570 break; 1571 } 1572 $last_token = $token; 1573 } 1574 1575 if($state != 0) { 1576 if($state == 1) { 1577 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__); 1578 } else { 1579 $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__); 1580 } 1581 } 1582 1583 $this->_parse_vars_props($attrs); 1584 1585 return $attrs; 1586 } 1587 1588 /** 1589 * compile multiple variables and section properties tokens into 1590 * PHP code 1591 * 1592 * @param array $tokens 1593 */ 1594 function _parse_vars_props(&$tokens) 1595 { 1596 foreach($tokens as $key => $val) { 1597 $tokens[$key] = $this->_parse_var_props($val); 1598 } 1599 } 1600 1601 /** 1602 * compile single variable and section properties token into 1603 * PHP code 1604 * 1605 * @param string $val 1606 * @param string $tag_attrs 1607 * @return string 1608 */ 1609 function _parse_var_props($val) 1610 { 1611 $val = trim($val); 1612 1613 if(preg_match('~^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$~', $val, $match)) { 1614 // $ variable or object 1615 $return = $this->_parse_var($match[1]); 1616 $modifiers = $match[2]; 1617 if (!empty($this->default_modifiers) && !preg_match('~(^|\|)smarty:nodefaults($|\|)~',$modifiers)) { 1618 $_default_mod_string = implode('|',(array)$this->default_modifiers); 1619 $modifiers = empty($modifiers) ? $_default_mod_string : $_default_mod_string . '|' . $modifiers; 1620 } 1621 $this->_parse_modifiers($return, $modifiers); 1622 return $return; 1623 } elseif (preg_match('~^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) { 1624 // double quoted text 1625 preg_match('~^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match); 1626 $return = $this->_expand_quoted_text($match[1]); 1627 if($match[2] != '') { 1628 $this->_parse_modifiers($return, $match[2]); 1629 } 1630 return $return; 1631 } 1632 elseif(preg_match('~