| [ Index ] |
PHP Cross Reference of Xoops v2.4.5 code documentation |
[Summary view] [Print] [Text view]
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.26 25 * @copyright 2001-2005 New Digital Group, Inc. 26 * @package Smarty 27 */ 28 29 /* $Id: Smarty_Compiler.class.php 3536 2009-08-31 01:44:52Z trabis $ */ 30 31 /** 32 * Template compiling class 33 * @package Smarty 34 */ 35 class 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 // run template source through prefilter functions 244 if (count($this->_plugins['prefilter']) > 0) { 245 foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) { 246 if ($prefilter === false) continue; 247 if ($prefilter[3] || is_callable($prefilter[0])) { 248 $source_content = call_user_func_array($prefilter[0], 249 array($source_content, &$this)); 250 $this->_plugins['prefilter'][$filter_name][3] = true; 251 } else { 252 $this->_trigger_fatal_error("[plugin] prefilter '$filter_name' is not implemented"); 253 } 254 } 255 } 256 257 /* fetch all special blocks */ 258 $search = "~{$ldq}\*(.*?)\*{$rdq}|{$ldq}\s*literal\s*{$rdq}(.*?){$ldq}\s*/literal\s*{$rdq}|{$ldq}\s*php\s*{$rdq}(.*?){$ldq}\s*/php\s*{$rdq}~s"; 259 260 preg_match_all($search, $source_content, $match, PREG_SET_ORDER); 261 $this->_folded_blocks = $match; 262 reset($this->_folded_blocks); 263 264 /* replace special blocks by "{php}" */ 265 $source_content = preg_replace($search.'e', "'" 266 . $this->_quote_replace($this->left_delimiter) . 'php' 267 . "' . str_repeat(\"\n\", substr_count('\\0', \"\n\")) .'" 268 . $this->_quote_replace($this->right_delimiter) 269 . "'" 270 , $source_content); 271 272 /* Gather all template tags. */ 273 preg_match_all("~{$ldq}\s*(.*?)\s*{$rdq}~s", $source_content, $_match); 274 $template_tags = $_match[1]; 275 /* Split content by template tags to obtain non-template content. */ 276 $text_blocks = preg_split("~{$ldq}.*?{$rdq}~s", $source_content); 277 278 /* loop through text blocks */ 279 for ($curr_tb = 0, $for_max = count($text_blocks); $curr_tb < $for_max; $curr_tb++) { 280 /* match anything resembling php tags */ 281 if (preg_match_all('~(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?\s*php\s*[\"\']?)~is', $text_blocks[$curr_tb], $sp_match)) { 282 /* replace tags with placeholders to prevent recursive replacements */ 283 $sp_match[1] = array_unique($sp_match[1]); 284 usort($sp_match[1], '_smarty_sort_length'); 285 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) { 286 $text_blocks[$curr_tb] = str_replace($sp_match[1][$curr_sp],'%%%SMARTYSP'.$curr_sp.'%%%',$text_blocks[$curr_tb]); 287 } 288 /* process each one */ 289 for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) { 290 if ($this->php_handling == SMARTY_PHP_PASSTHRU) { 291 /* echo php contents */ 292 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '<?php echo \''.str_replace("'", "\'", $sp_match[1][$curr_sp]).'\'; ?>'."\n", $text_blocks[$curr_tb]); 293 } else if ($this->php_handling == SMARTY_PHP_QUOTE) { 294 /* quote php tags */ 295 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', htmlspecialchars($sp_match[1][$curr_sp]), $text_blocks[$curr_tb]); 296 } else if ($this->php_handling == SMARTY_PHP_REMOVE) { 297 /* remove php tags */ 298 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '', $text_blocks[$curr_tb]); 299 } else { 300 /* SMARTY_PHP_ALLOW, but echo non php starting tags */ 301 $sp_match[1][$curr_sp] = preg_replace('~(<\?(?!php|=|$))~i', '<?php echo \'\\1\'?>'."\n", $sp_match[1][$curr_sp]); 302 $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', $sp_match[1][$curr_sp], $text_blocks[$curr_tb]); 303 } 304 } 305 } 306 } 307 308 /* Compile the template tags into PHP code. */ 309 $compiled_tags = array(); 310 for ($i = 0, $for_max = count($template_tags); $i < $for_max; $i++) { 311 $this->_current_line_no += substr_count($text_blocks[$i], "\n"); 312 $compiled_tags[] = $this->_compile_tag($template_tags[$i]); 313 $this->_current_line_no += substr_count($template_tags[$i], "\n"); 314 } 315 if (count($this->_tag_stack)>0) { 316 list($_open_tag, $_line_no) = end($this->_tag_stack); 317 $this->_syntax_error("unclosed tag \{$_open_tag} (opened line $_line_no).", E_USER_ERROR, __FILE__, __LINE__); 318 return; 319 } 320 321 /* Reformat $text_blocks between 'strip' and '/strip' tags, 322 removing spaces, tabs and newlines. */ 323 $strip = false; 324 for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) { 325 if ($compiled_tags[$i] == '{strip}') { 326 $compiled_tags[$i] = ''; 327 $strip = true; 328 /* remove leading whitespaces */ 329 $text_blocks[$i + 1] = ltrim($text_blocks[$i + 1]); 330 } 331 if ($strip) { 332 /* strip all $text_blocks before the next '/strip' */ 333 for ($j = $i + 1; $j < $for_max; $j++) { 334 /* remove leading and trailing whitespaces of each line */ 335 $text_blocks[$j] = preg_replace('![\t ]*[\r\n]+[\t ]*!', '', $text_blocks[$j]); 336 if ($compiled_tags[$j] == '{/strip}') { 337 /* remove trailing whitespaces from the last text_block */ 338 $text_blocks[$j] = rtrim($text_blocks[$j]); 339 } 340 $text_blocks[$j] = "<?php echo '" . strtr($text_blocks[$j], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>"; 341 if ($compiled_tags[$j] == '{/strip}') { 342 $compiled_tags[$j] = "\n"; /* slurped by php, but necessary 343 if a newline is following the closing strip-tag */ 344 $strip = false; 345 $i = $j; 346 break; 347 } 348 } 349 } 350 } 351 $compiled_content = ''; 352 353 $tag_guard = '%%%SMARTYOTG' . md5(uniqid(rand(), true)) . '%%%'; 354 355 /* Interleave the compiled contents and text blocks to get the final result. */ 356 for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) { 357 if ($compiled_tags[$i] == '') { 358 // tag result empty, remove first newline from following text block 359 $text_blocks[$i+1] = preg_replace('~^(\r\n|\r|\n)~', '', $text_blocks[$i+1]); 360 } 361 // replace legit PHP tags with placeholder 362 $text_blocks[$i] = str_replace('<?', $tag_guard, $text_blocks[$i]); 363 $compiled_tags[$i] = str_replace('<?', $tag_guard, $compiled_tags[$i]); 364 365 $compiled_content .= $text_blocks[$i] . $compiled_tags[$i]; 366 } 367 $compiled_content .= str_replace('<?', $tag_guard, $text_blocks[$i]); 368 369 // escape php tags created by interleaving 370 $compiled_content = str_replace('<?', "<?php echo '<?' ?>\n", $compiled_content); 371 $compiled_content = preg_replace("~(?<!')language\s*=\s*[\"\']?\s*php\s*[\"\']?~", "<?php echo 'language=php' ?>\n", $compiled_content); 372 373 // recover legit tags 374 $compiled_content = str_replace($tag_guard, '<?', $compiled_content); 375 376 // remove \n from the end of the file, if any 377 if (strlen($compiled_content) && (substr($compiled_content, -1) == "\n") ) { 378 $compiled_content = substr($compiled_content, 0, -1); 379 } 380 381 if (!empty($this->_cache_serial)) { 382 $compiled_content = "<?php \$this->_cache_serials['".$this->_cache_include."'] = '".$this->_cache_serial."'; ?>" . $compiled_content; 383 } 384 385 // run compiled template through postfilter functions 386 if (count($this->_plugins['postfilter']) > 0) { 387 foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) { 388 if ($postfilter === false) continue; 389 if ($postfilter[3] || is_callable($postfilter[0])) { 390 $compiled_content = call_user_func_array($postfilter[0], 391 array($compiled_content, &$this)); 392 $this->_plugins['postfilter'][$filter_name][3] = true; 393 } else { 394 $this->_trigger_fatal_error("Smarty plugin error: postfilter '$filter_name' is not implemented"); 395 } 396 } 397 } 398 399 // put header at the top of the compiled template 400 $template_header = "<?php /* Smarty version ".$this->_version.", created on ".strftime("%Y-%m-%d %H:%M:%S")."\n"; 401 $template_header .= " compiled from ".strtr(urlencode($resource_name), array('%2F'=>'/', '%3A'=>':'))." */ ?>\n"; 402 403 /* Emit code to load needed plugins. */ 404 $this->_plugins_code = ''; 405 if (count($this->_plugin_info)) { 406 $_plugins_params = "array('plugins' => array("; 407 foreach ($this->_plugin_info as $plugin_type => $plugins) { 408 foreach ($plugins as $plugin_name => $plugin_info) { 409 $_plugins_params .= "array('$plugin_type', '$plugin_name', '" . strtr($plugin_info[0], array("'" => "\\'", "\\" => "\\\\")) . "', $plugin_info[1], "; 410 $_plugins_params .= $plugin_info[2] ? 'true),' : 'false),'; 411 } 412 } 413 $_plugins_params .= '))'; 414 $plugins_code = "<?php require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');\nsmarty_core_load_plugins($_plugins_params, \$this); ?>\n"; 415 $template_header .= $plugins_code; 416 $this->_plugin_info = array(); 417 $this->_plugins_code = $plugins_code; 418 } 419 420 if ($this->_init_smarty_vars) { 421 $template_header .= "<?php require_once(SMARTY_CORE_DIR . 'core.assign_smarty_interface.php');\nsmarty_core_assign_smarty_interface(null, \$this); ?>\n"; 422 $this->_init_smarty_vars = false; 423 } 424 425 $compiled_content = $template_header . $compiled_content; 426 return true; 427 } 428 429 /** 430 * Compile a template tag 431 * 432 * @param string $template_tag 433 * @return string 434 */ 435 function _compile_tag($template_tag) 436 { 437 /* Matched comment. */ 438 if (substr($template_tag, 0, 1) == '*' && substr($template_tag, -1) == '*') 439 return ''; 440 441 /* Split tag into two three parts: command, command modifiers and the arguments. */ 442 if(! preg_match('~^(?:(' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp 443 . '|\/?' . $this->_reg_obj_regexp . '|\/?' . $this->_func_regexp . ')(' . $this->_mod_regexp . '*)) 444 (?:\s+(.*))?$ 445 ~xs', $template_tag, $match)) { 446 $this->_syntax_error("unrecognized tag: $template_tag", E_USER_ERROR, __FILE__, __LINE__); 447 } 448 449 $tag_command = $match[1]; 450 $tag_modifier = isset($match[2]) ? $match[2] : null; 451 $tag_args = isset($match[3]) ? $match[3] : null; 452 453 if (preg_match('~^' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '$~', $tag_command)) { 454 /* tag name is a variable or object */ 455 $_return = $this->_parse_var_props($tag_command . $tag_modifier); 456 return "<?php echo $_return; ?>" . $this->_additional_newline; 457 } 458 459 /* If the tag name is a registered object, we process it. */ 460 if (preg_match('~^\/?' . $this->_reg_obj_regexp . '$~', $tag_command)) { 461 return $this->_compile_registered_object_tag($tag_command, $this->_parse_attrs($tag_args), $tag_modifier); 462 } 463 464 switch ($tag_command) { 465 case 'include': 466 return $this->_compile_include_tag($tag_args); 467 468 case 'include_php': 469 return $this->_compile_include_php_tag($tag_args); 470 471 case 'if': 472 $this->_push_tag('if'); 473 return $this->_compile_if_tag($tag_args); 474 475 case 'else': 476 list($_open_tag) = end($this->_tag_stack); 477 if ($_open_tag != 'if' && $_open_tag != 'elseif') 478 $this->_syntax_error('unexpected {else}', E_USER_ERROR, __FILE__, __LINE__); 479 else 480 $this->_push_tag('else'); 481 return '<?php else: ?>'; 482 483 case 'elseif': 484 list($_open_tag) = end($this->_tag_stack); 485 if ($_open_tag != 'if' && $_open_tag != 'elseif') 486 $this->_syntax_error('unexpected {elseif}', E_USER_ERROR, __FILE__, __LINE__); 487 if ($_open_tag == 'if') 488 $this->_push_tag('elseif'); 489 return $this->_compile_if_tag($tag_args, true); 490 491 case '/if': 492 $this->_pop_tag('if'); 493 return '<?php endif; ?>'; 494 495 case 'capture': 496 return $this->_compile_capture_tag(true, $tag_args); 497 498 case '/capture': 499 return $this->_compile_capture_tag(false); 500 501 case 'ldelim': 502 return $this->left_delimiter; 503 504 case 'rdelim': 505 return $this->right_delimiter; 506 507 case 'section': 508 $this->_push_tag('section'); 509 return $this->_compile_section_start($tag_args); 510 511 case 'sectionelse': 512 $this->_push_tag('sectionelse'); 513 return "<?php endfor; else: ?>"; 514 break; 515 516 case '/section': 517 $_open_tag = $this->_pop_tag('section'); 518 if ($_open_tag == 'sectionelse') 519 return "<?php endif; ?>"; 520 else 521 return "<?php endfor; endif; ?>"; 522 523 case 'foreach': 524 $this->_push_tag('foreach'); 525 return $this->_compile_foreach_start($tag_args); 526 break; 527 528 case 'foreachelse': 529 $this->_push_tag('foreachelse'); 530 return "<?php endforeach; else: ?>"; 531 532 case '/foreach': 533 $_open_tag = $this->_pop_tag('foreach'); 534 if ($_open_tag == 'foreachelse') 535 return "<?php endif; unset(\$_from); ?>"; 536 else 537 return "<?php endforeach; endif; unset(\$_from); ?>"; 538 break; 539 540 case 'strip': 541 case '/strip': 542 if (substr($tag_command, 0, 1)=='/') { 543 $this->_pop_tag('strip'); 544 if (--$this->_strip_depth==0) { /* outermost closing {/strip} */ 545 $this->_additional_newline = "\n"; 546 return '{' . $tag_command . '}'; 547 } 548 } else { 549 $this->_push_tag('strip'); 550 if ($this->_strip_depth++==0) { /* outermost opening {strip} */ 551 $this->_additional_newline = ""; 552 return '{' . $tag_command . '}'; 553 } 554 } 555 return ''; 556 557 case 'php': 558 /* handle folded tags replaced by {php} */ 559 list(, $block) = each($this->_folded_blocks); 560 $this->_current_line_no += substr_count($block[0], "\n"); 561 /* the number of matched elements in the regexp in _compile_file() 562 determins the type of folded tag that was found */ 563 switch (count($block)) { 564 case 2: /* comment */ 565 return ''; 566 567 case 3: /* literal */ 568 return "<?php echo '" . strtr($block[2], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>" . $this->_additional_newline; 569 570 case 4: /* php */ 571 if ($this->security && !$this->security_settings['PHP_TAGS']) { 572 $this->_syntax_error("(secure mode) php tags not permitted", E_USER_WARNING, __FILE__, __LINE__); 573 return; 574 } 575 return '<?php ' . $block[3] .' ?>'; 576 } 577 break; 578 579 case 'insert': 580 return $this->_compile_insert_tag($tag_args); 581 582 default: 583 if ($this->_compile_compiler_tag($tag_command, $tag_args, $output)) { 584 return $output; 585 } else if ($this->_compile_block_tag($tag_command, $tag_args, $tag_modifier, $output)) { 586 return $output; 587 } else if ($this->_compile_custom_tag($tag_command, $tag_args, $tag_modifier, $output)) { 588 return $output; 589 } else { 590 $this->_syntax_error("unrecognized tag '$tag_command'", E_USER_ERROR, __FILE__, __LINE__); 591 } 592 593 } 594 } 595 596 597 /** 598 * compile the custom compiler tag 599 * 600 * sets $output to the compiled custom compiler tag 601 * @param string $tag_command 602 * @param string $tag_args 603 * @param string $output 604 * @return boolean 605 */ 606 function _compile_compiler_tag($tag_command, $tag_args, &$output) 607 { 608 $found = false; 609 $have_function = true; 610 611 /* 612 * First we check if the compiler function has already been registered 613 * or loaded from a plugin file. 614 */ 615 if (isset($this->_plugins['compiler'][$tag_command])) { 616 $found = true; 617 $plugin_func = $this->_plugins['compiler'][$tag_command][0]; 618 if (!is_callable($plugin_func)) { 619 $message = "compiler function '$tag_command' is not implemented"; 620 $have_function = false; 621 } 622 } 623 /* 624 * Otherwise we need to load plugin file and look for the function 625 * inside it. 626 */ 627 else if ($plugin_file = $this->_get_plugin_filepath('compiler', $tag_command)) { 628 $found = true; 629 630 include_once $plugin_file; 631 632 $plugin_func = 'smarty_compiler_' . $tag_command; 633 if (!is_callable($plugin_func)) { 634 $message = "plugin function $plugin_func() not found in $plugin_file\n"; 635 $have_function = false; 636 } else { 637 $this->_plugins['compiler'][$tag_command] = array($plugin_func, null, null, null, true); 638 } 639 } 640 641 /* 642 * True return value means that we either found a plugin or a 643 * dynamically registered function. False means that we didn't and the 644 * compiler should now emit code to load custom function plugin for this 645 * tag. 646 */ 647 if ($found) { 648 if ($have_function) { 649 $output = call_user_func_array($plugin_func, array($tag_args, &$this)); 650 if($output != '') { 651 $output = '<?php ' . $this->_push_cacheable_state('compiler', $tag_command) 652 . $output 653 . $this->_pop_cacheable_state('compiler', $tag_command) . ' ?>'; 654 } 655 } else { 656 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__); 657 } 658 return true; 659 } else { 660 return false; 661 } 662 } 663 664 665 /** 666 * compile block function tag 667 * 668 * sets $output to compiled block function tag 669 * @param string $tag_command 670 * @param string $tag_args 671 * @param string $tag_modifier 672 * @param string $output 673 * @return boolean 674 */ 675 function _compile_block_tag($tag_command, $tag_args, $tag_modifier, &$output) 676 { 677 if (substr($tag_command, 0, 1) == '/') { 678 $start_tag = false; 679 $tag_command = substr($tag_command, 1); 680 } else 681 $start_tag = true; 682 683 $found = false; 684 $have_function = true; 685 686 /* 687 * First we check if the block function has already been registered 688 * or loaded from a plugin file. 689 */ 690 if (isset($this->_plugins['block'][$tag_command])) { 691 $found = true; 692 $plugin_func = $this->_plugins['block'][$tag_command][0]; 693 if (!is_callable($plugin_func)) { 694 $message = "block function '$tag_command' is not implemented"; 695 $have_function = false; 696 } 697 } 698 /* 699 * Otherwise we need to load plugin file and look for the function 700 * inside it. 701 */ 702 else if ($plugin_file = $this->_get_plugin_filepath('block', $tag_command)) { 703 $found = true; 704 705 include_once $plugin_file; 706 707 $plugin_func = 'smarty_block_' . $tag_command; 708 if (!function_exists($plugin_func)) { 709 $message = "plugin function $plugin_func() not found in $plugin_file\n"; 710 $have_function = false; 711 } else { 712 $this->_plugins['block'][$tag_command] = array($plugin_func, null, null, null, true); 713 714 } 715 } 716 717 if (!$found) { 718 return false; 719 } else if (!$have_function) { 720 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__); 721 return true; 722 } 723 724 /* 725 * Even though we've located the plugin function, compilation 726 * happens only once, so the plugin will still need to be loaded 727 * at runtime for future requests. 728 */ 729 $this->_add_plugin('block', $tag_command); 730 731 if ($start_tag) 732 $this->_push_tag($tag_command); 733 else 734 $this->_pop_tag($tag_command); 735 736 if ($start_tag) { 737 $output = '<?php ' . $this->_push_cacheable_state('block', $tag_command); 738 $attrs = $this->_parse_attrs($tag_args); 739 $_cache_attrs=''; 740 $arg_list = $this->_compile_arg_list('block', $tag_command, $attrs, $_cache_attrs); 741 $output .= "$_cache_attrs\$this->_tag_stack[] = array('$tag_command', array(".implode(',', $arg_list).')); '; 742 $output .= '$_block_repeat=true;' . $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], null, $this, $_block_repeat);'; 743 $output .= 'while ($_block_repeat) { ob_start(); ?>'; 744 } else { 745 $output = '<?php $_block_content = ob_get_contents(); ob_end_clean(); '; 746 $_out_tag_text = $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], $_block_content, $this, $_block_repeat)'; 747 if ($tag_modifier != '') { 748 $this->_parse_modifiers($_out_tag_text, $tag_modifier); 749 } 750 $output .= '$_block_repeat=false;echo ' . $_out_tag_text . '; } '; 751 $output .= " array_pop(\$this->_tag_stack); " . $this->_pop_cacheable_state('block', $tag_command) . '?>'; 752 } 753 754 return true; 755 } 756 757 758 /** 759 * compile custom function tag 760 * 761 * @param string $tag_command 762 * @param string $tag_args 763 * @param string $tag_modifier 764 * @return string 765 */ 766 function _compile_custom_tag($tag_command, $tag_args, $tag_modifier, &$output) 767 { 768 $found = false; 769 $have_function = true; 770 771 /* 772 * First we check if the custom function has already been registered 773 * or loaded from a plugin file. 774 */ 775 if (isset($this->_plugins['function'][$tag_command])) { 776 $found = true; 777 $plugin_func = $this->_plugins['function'][$tag_command][0]; 778 if (!is_callable($plugin_func)) { 779 $message = "custom function '$tag_command' is not implemented"; 780 $have_function = false; 781 } 782 } 783 /* 784 * Otherwise we need to load plugin file and look for the function 785 * inside it. 786 */ 787 else if ($plugin_file = $this->_get_plugin_filepath('function', $tag_command)) { 788 $found = true; 789 790 include_once $plugin_file; 791 792 $plugin_func = 'smarty_function_' . $tag_command; 793 if (!function_exists($plugin_func)) { 794 $message = "plugin function $plugin_func() not found in $plugin_file\n"; 795 $have_function = false; 796 } else { 797 $this->_plugins['function'][$tag_command] = array($plugin_func, null, null, null, true); 798 799 } 800 } 801 802 if (!$found) { 803 return false; 804 } else if (!$have_function) { 805 $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__); 806 return true; 807 } 808 809 /* declare plugin to be loaded on display of the template that 810 we compile right now */ 811 $this->_add_plugin('function', $tag_command); 812 813 $_cacheable_state = $this->_push_cacheable_state('function', $tag_command); 814 $attrs = $this->_parse_attrs($tag_args); 815 $_cache_attrs = ''; 816 $arg_list = $this->_compile_arg_list('function', $tag_command, $attrs, $_cache_attrs); 817 818 $output = $this->_compile_plugin_call('function', $tag_command).'(array('.implode(',', $arg_list)."), \$this)"; 819 if($tag_modifier != '') { 820 $this->_parse_modifiers($output, $tag_modifier); 821 } 822 823 if($output != '') { 824 $output = '<?php ' . $_cacheable_state . $_cache_attrs . 'echo ' . $output . ';' 825 . $this->_pop_cacheable_state('function', $tag_command) . "?>" . $this->_additional_newline; 826 } 827 828 return true; 829 } 830 831 /** 832 * compile a registered object tag 833 * 834 * @param string $tag_command 835 * @param array $attrs 836 * @param string $tag_modifier 837 * @return string 838 */ 839 function _compile_registered_object_tag($tag_command, $attrs, $tag_modifier) 840 { 841 if (substr($tag_command, 0, 1) == '/') { 842 $start_tag = false; 843 $tag_command = substr($tag_command, 1); 844 } else { 845 $start_tag = true; 846 } 847 848 list($object, $obj_comp) = explode('->', $tag_command); 849 850 $arg_list = array(); 851 if(count($attrs)) { 852 $_assign_var = false; 853 foreach ($attrs as $arg_name => $arg_value) { 854 if($arg_name == 'assign') { 855 $_assign_var = $arg_value; 856 unset($attrs['assign']); 857 continue; 858 } 859 if (is_bool($arg_value)) 860 $arg_value = $arg_value ? 'true' : 'false'; 861 $arg_list[] = "'$arg_name' => $arg_value"; 862 } 863 } 864 865 if($this->_reg_objects[$object][2]) { 866 // smarty object argument format 867 $args = "array(".implode(',', (array)$arg_list)."), \$this"; 868 } else { 869 // traditional argument format 870 $args = implode(',', array_values($attrs)); 871 if (empty($args)) { 872 $args = ''; 873 } 874 } 875 876 $prefix = ''; 877 $postfix = ''; 878 $newline = ''; 879 if(!is_object($this->_reg_objects[$object][0])) { 880 $this->_trigger_fatal_error("registered '$object' is not an object" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__); 881 } elseif(!empty($this->_reg_objects[$object][1]) && !in_array($obj_comp, $this->_reg_objects[$object][1])) { 882 $this->_trigger_fatal_error("'$obj_comp' is not a registered component of object '$object'", $this->_current_file, $this->_current_line_no, __FILE__, __LINE__); 883 } elseif(method_exists($this->_reg_objects[$object][0], $obj_comp)) { 884 // method 885 if(in_array($obj_comp, $this->_reg_objects[$object][3])) { 886 // block method 887 if ($start_tag) { 888 $prefix = "\$this->_tag_stack[] = array('$obj_comp', $args); "; 889 $prefix .= "\$_block_repeat=true; \$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], null, \$this, \$_block_repeat); "; 890 $prefix .= "while (\$_block_repeat) { ob_start();"; 891 $return = null; 892 $postfix = ''; 893 } else { 894 $prefix = "\$_obj_block_content = ob_get_contents(); ob_end_clean(); \$_block_repeat=false;"; 895 $return = "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], \$_obj_block_content, \$this, \$_block_repeat)"; 896 $postfix = "} array_pop(\$this->_tag_stack);"; 897 } 898 } else { 899 // non-block method 900 $return = "\$this->_reg_objects['$object'][0]->$obj_comp($args)"; 901 } 902 } else { 903 // property 904 $return = "\$this->_reg_objects['$object'][0]->$obj_comp"; 905 } 906 907 if($return != null) { 908 if($tag_modifier != '') { 909 $this->_parse_modifiers($return, $tag_modifier); 910 } 911 912 if(!empty($_assign_var)) { 913 $output = "\$this->assign('" . $this->_dequote($_assign_var) ."', $return);"; 914 } else { 915 $output = 'echo ' . $return . ';'; 916 $newline = $this->_additional_newline; 917 } 918 } else { 919 $output = ''; 920 } 921 922 return '<?php ' . $prefix . $output . $postfix . "?>" . $newline; 923 } 924 925 /** 926 * Compile {insert ...} tag 927 * 928 * @param string $tag_args 929 * @return string 930 */ 931 function _compile_insert_tag($tag_args) 932 { 933 $attrs = $this->_parse_attrs($tag_args); 934 $name = $this->_dequote($attrs['name']); 935 936 if (empty($name)) { 937 return $this->_syntax_error("missing insert name", E_USER_ERROR, __FILE__, __LINE__); 938 } 939 940 if (!preg_match('~^\w+$~', $name)) { 941 return $this->_syntax_error("'insert: 'name' must be an insert function name", E_USER_ERROR, __FILE__, __LINE__); 942 } 943 944 if (!empty($attrs['script'])) { 945 $delayed_loading = true; 946 } else { 947 $delayed_loading = false; 948 } 949 950 foreach ($attrs as $arg_name => $arg_value) { 951 if (is_bool($arg_value)) 952 $arg_value = $arg_value ? 'true' : 'false'; 953 $arg_list[] = "'$arg_name' => $arg_value"; 954 } 955 956 $this->_add_plugin('insert', $name, $delayed_loading); 957 958 $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))"; 959 960 return "<?php require_once(SMARTY_CORE_DIR . 'core.run_insert_handler.php');\necho smarty_core_run_insert_handler($_params, \$this); ?>" . $this->_additional_newline; 961 } 962 963 /** 964 * Compile {include ...} tag 965 * 966 * @param string $tag_args 967 * @return string 968 */ 969 function _compile_include_tag($tag_args) 970 { 971 $attrs = $this->_parse_attrs($tag_args); 972 $arg_list = array(); 973 974 if (empty($attrs['file'])) { 975 $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR, __FILE__, __LINE__); 976 } 977 978 foreach ($attrs as $arg_name => $arg_value) { 979 if ($arg_name == 'file') { 980 $include_file = $arg_value; 981 continue; 982 } else if ($arg_name == 'assign') { 983 $assign_var = $arg_value; 984 continue; 985 } 986 if (is_bool($arg_value)) 987 $arg_value = $arg_value ? 'true' : 'false'; 988 $arg_list[] = "'$arg_name' => $arg_value"; 989 } 990 991 $output = '<?php '; 992 993 if (isset($assign_var)) { 994 $output .= "ob_start();\n"; 995 } 996 997 $output .= 998 "\$_smarty_tpl_vars = \$this->_tpl_vars;\n"; 999 1000 1001 $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))"; 1002 $output .= "\$this->_smarty_include($_params);\n" . 1003 "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" . 1004 "unset(\$_smarty_tpl_vars);\n"; 1005 1006 if (isset($assign_var)) { 1007 $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n"; 1008 } 1009 1010 $output .= ' ?>'; 1011 1012 return $output; 1013 1014 } 1015 1016 /** 1017 * Compile {include ...} tag 1018 * 1019 * @param string $tag_args 1020 * @return string 1021 */ 1022 function _compile_include_php_tag($tag_args) 1023 { 1024 $attrs = $this->_parse_attrs($tag_args); 1025 1026 if (empty($attrs['file'])) { 1027 $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR, __FILE__, __LINE__); 1028 } 1029 1030 $assign_var = (empty($attrs['assign'])) ? '' : $this->_dequote($attrs['assign']); 1031 $once_var = (empty($attrs['once']) || $attrs['once']=='false') ? 'false' : 'true'; 1032 1033 $arg_list = array(); 1034 foreach($attrs as $arg_name => $arg_value) { 1035 if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') { 1036 if(is_bool($arg_value)) 1037 $arg_value = $arg_value ? 'true' : 'false'; 1038 $arg_list[] = "'$arg_name' => $arg_value"; 1039 } 1040 } 1041 1042 $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', $arg_list)."))"; 1043 1044 return "<?php require_once(SMARTY_CORE_DIR . 'core.smarty_include_php.php');\nsmarty_core_smarty_include_php($_params, \$this); ?>" . $this->_additional_newline; 1045 } 1046 1047 1048 /** 1049 * Compile {section ...} tag 1050 * 1051 * @param string $tag_args 1052 * @return string 1053 */ 1054 function _compile_section_start($tag_args) 1055 { 1056 $attrs = $this->_parse_attrs($tag_args); 1057 $arg_list = array(); 1058 1059 $output = '<?php '; 1060 $section_name = $attrs['name']; 1061 if (empty($section_name)) { 1062 $this->_syntax_error("missing section name", E_USER_ERROR, __FILE__, __LINE__); 1063 } 1064 1065 $output .= "unset(\$this->_sections[$section_name]);\n"; 1066 $section_props = "\$this->_sections[$section_name]"; 1067 1068 foreach ($attrs as $attr_name => $attr_value) { 1069 switch ($attr_name) { 1070 case 'loop': 1071 $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n"; 1072 break; 1073 1074 case 'show': 1075 if (is_bool($attr_value)) 1076 $show_attr_value = $attr_value ? 'true' : 'false'; 1077 else 1078 $show_attr_value = "(bool)$attr_value"; 1079 $output .= "{$section_props}['show'] = $show_attr_value;\n"; 1080 break; 1081 1082 case 'name': 1083 $output .= "{$section_props}['$attr_name'] = $attr_value;\n"; 1084 break; 1085 1086 case 'max': 1087 case 'start': 1088 $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n"; 1089 break; 1090 1091 case 'step': 1092 $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n"; 1093 break; 1094 1095 default: 1096 $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR, __FILE__, __LINE__); 1097 break; 1098 } 1099 } 1100 1101 if (!isset($attrs['show'])) 1102 $output .= "{$section_props}['show'] = true;\n"; 1103 1104 if (!isset($attrs['loop'])) 1105 $output .= "{$section_props}['loop'] = 1;\n"; 1106 1107 if (!isset($attrs['max'])) 1108 $output .= "{$section_props}['max'] = {$section_props}['loop'];\n"; 1109 else 1110 $output .= "if ({$section_props}['max'] < 0)\n" . 1111 " {$section_props}['max'] = {$section_props}['loop'];\n"; 1112 1113 if (!isset($attrs['step'])) 1114 $output .= "{$section_props}['step'] = 1;\n"; 1115 1116 if (!isset($attrs['start'])) 1117 $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n"; 1118 else { 1119 $output .= "if ({$section_props}['start'] < 0)\n" . 1120 " {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" . 1121 "else\n" . 1122 " {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n"; 1123 } 1124 1125 $output .= "if ({$section_props}['show']) {\n"; 1126 if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) { 1127 $output .= " {$section_props}['total'] = {$section_props}['loop'];\n"; 1128 } else { 1129 $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"; 1130 } 1131 $output .= " if ({$section_props}['total'] == 0)\n" . 1132 " {$section_props}['show'] = false;\n" . 1133 "} else\n" . 1134 " {$section_props}['total'] = 0;\n"; 1135 1136 $output .= "if ({$section_props}['show']):\n"; 1137 $output .= " 1138 for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1; 1139 {$section_props}['iteration'] <= {$section_props}['total']; 1140 {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n"; 1141 $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n"; 1142 $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n"; 1143 $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n"; 1144 $output .= "{$section_props}['first'] = ({$section_props}['iteration'] == 1);\n"; 1145 $output .= "{$section_props}['last'] = ({$section_props}['iteration'] == {$section_props}['total']);\n"; 1146 1147 $output .= "?>"; 1148 1149 return $output; 1150 } 1151 1152 1153 /** 1154 * Compile {foreach ...} tag. 1155 * 1156 * @param string $tag_args 1157 * @return string 1158 */ 1159 function _compile_foreach_start($tag_args) 1160 { 1161 $attrs = $this->_parse_attrs($tag_args); 1162 $arg_list = array(); 1163 1164 if (empty($attrs['from'])) { 1165 return $this->_syntax_error("foreach: missing 'from' attribute", E_USER_ERROR, __FILE__, __LINE__); 1166 } 1167 $from = $attrs['from']; 1168 1169 if (empty($attrs['item'])) { 1170 return $this->_syntax_error("foreach: missing 'item' attribute", E_USER_ERROR, __FILE__, __LINE__); 1171 } 1172 $item = $this->_dequote($attrs['item']); 1173 if (!preg_match('~^\w+$~', $item)) { 1174 return $this->_syntax_error("foreach: 'item' must be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__); 1175 } 1176 1177 if (isset($attrs['key'])) { 1178 $key = $this->_dequote($attrs['key']); 1179 if (!preg_match('~^\w+$~', $key)) { 1180 return $this->_syntax_error("foreach: 'key' must to be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__); 1181 } 1182 $key_part = "\$this->_tpl_vars['$key'] => "; 1183 } else { 1184 $key = null; 1185 $key_part = ''; 1186 } 1187 1188 if (isset($attrs['name'])) { 1189 $name = $attrs['name']; 1190 } else { 1191 $name = null; 1192 } 1193 1194 $output = '<?php '; 1195 $output .= "\$_from = $from; if (!is_array(\$_from) && !is_object(\$_from)) { settype(\$_from, 'array'); }"; 1196 if (isset($name)) { 1197 $foreach_props = "\$this->_foreach[$name]"; 1198 $output .= "{$foreach_props} = array('total' => count(\$_from), 'iteration' => 0);\n"; 1199 $output .= "if ({$foreach_props}['total'] > 0):\n"; 1200 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n"; 1201 $output .= " {$foreach_props}['iteration']++;\n"; 1202 } else { 1203 $output .= "if (count(\$_from)):\n"; 1204 $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n"; 1205 } 1206 $output .= '?>'; 1207 1208 return $output; 1209 } 1210 1211 1212 /** 1213 * Compile {capture} .. {/capture} tags 1214 * 1215 * @param boolean $start true if this is the {capture} tag 1216 * @param string $tag_args 1217 * @return string 1218 */ 1219 1220 function _compile_capture_tag($start, $tag_args = '') 1221 { 1222 $attrs = $this->_parse_attrs($tag_args); 1223 1224 if ($start) { 1225 $buffer = isset($attrs['name']) ? $attrs['name'] : "'default'"; 1226 $assign = isset($attrs['assign']) ? $attrs['assign'] : null; 1227 $append = isset($attrs['append']) ? $attrs['append'] : null; 1228 1229 $output = "<?php ob_start(); ?>"; 1230 $this->_capture_stack[] = array($buffer, $assign, $append); 1231 } else { 1232 list($buffer, $assign, $append) = array_pop($this->_capture_stack); 1233 $output = "<?php \$this->_smarty_vars['capture'][$buffer] = ob_get_contents(); "; 1234 if (isset($assign)) { 1235 $output .= " \$this->assign($assign, ob_get_contents());"; 1236 } 1237 if (isset($append)) { 1238 $output .= " \$this->append($append, ob_get_contents());"; 1239 } 1240 $output .= "ob_end_clean(); ?>"; 1241 } 1242 1243 return $output; 1244 } 1245 1246 /** 1247 * Compile {if ...} tag 1248 * 1249 * @param string $tag_args 1250 * @param boolean $elseif if true, uses elseif instead of if 1251 * @return string 1252 */ 1253 function _compile_if_tag($tag_args, $elseif = false) 1254 { 1255 1256 /* Tokenize args for 'if' tag. */ 1257 preg_match_all('~(?> 1258 ' . $this->_obj_call_regexp . '(?:' . $this->_mod_regexp . '*)? | # valid object call 1259 ' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)? | # var or quoted string 1260 \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@ | # valid non-word token 1261 \b\w+\b | # valid word token 1262 \S+ # anything else 1263 )~x', $tag_args, $match); 1264 1265 $tokens = $match[0]; 1266 1267 if(empty($tokens)) { 1268 $_error_msg = $elseif ? "'elseif'" : "'if'"; 1269 $_error_msg .= ' statement requires arguments'; 1270 $this->_syntax_error($_error_msg, E_USER_ERROR, __FILE__, __LINE__); 1271 } 1272 1273 1274 // make sure we have balanced parenthesis 1275 $token_count = array_count_values($tokens); 1276 if(isset($token_count['(']) && $token_count['('] != $token_count[')']) { 1277 $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR, __FILE__, __LINE__); 1278 } 1279 1280 $is_arg_stack = array(); 1281 1282 for ($i = 0; $i < count($tokens); $i++) { 1283 1284 $token = &$tokens[$i]; 1285 1286 switch (strtolower($token)) { 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 case '+': 1309 case '-': 1310 case '*': 1311 case '/': 1312 case '@': 1313 break; 1314 1315 case 'eq': 1316 $token = '=='; 1317 break; 1318 1319 case 'ne': 1320 case 'neq': 1321 $token = '!='; 1322 break; 1323 1324 case 'lt': 1325 $token = '<'; 1326 break; 1327 1328 case 'le': 1329 case 'lte': 1330 $token = '<='; 1331 break; 1332 1333 case 'gt': 1334 $token = '>'; 1335 break; 1336 1337 case 'ge': 1338 case 'gte': 1339 $token = '>='; 1340 break; 1341 1342 case 'and': 1343 $token = '&&'; 1344 break; 1345 1346 case 'or': 1347 $token = '||'; 1348 break; 1349 1350 case 'not': 1351 $token = '!'; 1352 break; 1353 1354 case 'mod': 1355 $token = '%'; 1356 break; 1357 1358 case '(': 1359 array_push($is_arg_stack, $i); 1360 break; 1361 1362 case 'is': 1363 /* If last token was a ')', we operate on the parenthesized 1364 expression. The start of the expression is on the stack. 1365 Otherwise, we operate on the last encountered token. */ 1366 if ($tokens[$i-1] == ')') { 1367 $is_arg_start = array_pop($is_arg_stack); 1368 if ($is_arg_start != 0) { 1369 if (preg_match('~^' . $this->_func_regexp . '$~', $tokens[$is_arg_start-1])) { 1370 $is_arg_start--; 1371 } 1372 } 1373 } else 1374 $is_arg_start = $i-1; 1375 /* Construct the argument for 'is' expression, so it knows 1376 what to operate on. */ 1377 $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start)); 1378 1379 /* Pass all tokens from next one until the end to the 1380 'is' expression parsing function. The function will 1381 return modified tokens, where the first one is the result 1382 of the 'is' expression and the rest are the tokens it 1383 didn't touch. */ 1384 $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1)); 1385 1386 /* Replace the old tokens with the new ones. */ 1387 array_splice($tokens, $is_arg_start, count($tokens), $new_tokens); 1388 1389 /* Adjust argument start so that it won't change from the 1390 current position for the next iteration. */ 1391 $i = $is_arg_start; 1392 break; 1393 1394 default: 1395 if(preg_match('~^' . $this->_func_regexp . '$~', $token) ) { 1396 // function call 1397 if($this->security && 1398 !in_array($token, $this->security_settings['IF_FUNCS'])) { 1399 $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__); 1400 } 1401 } elseif(preg_match('~^' . $this->_var_regexp . '$~', $token) && (strpos('+-*/^%&|', substr($token, -1)) === false) && isset($tokens[$i+1]) && $tokens[$i+1] == '(') { 1402 // variable function call 1403 $this->_syntax_error("variable function call '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__); 1404 } elseif(preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$~', $token)) { 1405 // object or variable 1406 $token = $this->_parse_var_props($token); 1407 } elseif(is_numeric($token)) { 1408 // number, skip it 1409 } else { 1410 $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__); 1411 } 1412 break; 1413 } 1414 } 1415 1416 if ($elseif) 1417 return '<?php elseif ('.implode(' ', $tokens).'): ?>'; 1418 else 1419 return '<?php if ('.implode(' ', $tokens).'): ?>'; 1420 } 1421 1422 1423 function _compile_arg_list($type, $name, $attrs, &$cache_code) { 1424 $arg_list = array(); 1425 1426 if (isset($type) && isset($name) 1427 && isset($this->_plugins[$type]) 1428 && isset($this->_plugins[$type][$name]) 1429 && empty($this->_plugins[$type][$name][4]) 1430 && is_array($this->_plugins[$type][$name][5]) 1431 ) { 1432 /* we have a list of parameters that should be cached */ 1433 $_cache_attrs = $this->_plugins[$type][$name][5]; 1434 $_count = $this->_cache_attrs_count++; 1435 $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');"; 1436 1437 } else { 1438 /* no parameters are cached */ 1439 $_cache_attrs = null; 1440 } 1441 1442 foreach ($attrs as $arg_name => $arg_value) { 1443 if (is_bool($arg_value)) 1444 $arg_value = $arg_value ? 'true' : 'false'; 1445 if (is_null($arg_value)) 1446 $arg_value = 'null'; 1447 if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) { 1448 $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)"; 1449 } else { 1450 $arg_list[] = "'$arg_name' => $arg_value"; 1451 } 1452 } 1453 return $arg_list; 1454 } 1455 1456 /** 1457 * Parse is expression 1458 * 1459 * @param string $is_arg 1460 * @param array $tokens 1461 * @return array 1462 */ 1463 function _parse_is_expr($is_arg, $tokens) 1464 { 1465 $expr_end = 0; 1466 $negate_expr = false; 1467 1468 if (($first_token = array_shift($tokens)) == 'not') { 1469 $negate_expr = true; 1470 $expr_type = array_shift($tokens); 1471 } else 1472 $expr_type = $first_token; 1473 1474 switch ($expr_type) { 1475 case 'even': 1476 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') { 1477 $expr_end++; 1478 $expr_arg = $tokens[$expr_end++]; 1479 $expr = "!(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))"; 1480 } else 1481 $expr = "!(1 & $is_arg)"; 1482 break; 1483 1484 case 'odd': 1485 if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') { 1486 $expr_end++; 1487 $expr_arg = $tokens[$expr_end++]; 1488 $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))"; 1489 } else 1490 $expr = "(1 & $is_arg)"; 1491 break; 1492 1493 case 'div': 1494 if (@$tokens[$expr_end] == 'by') { 1495 $expr_end++; 1496 $expr_arg = $tokens[$expr_end++]; 1497 $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")"; 1498 } else { 1499 $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__); 1500 } 1501 break; 1502 1503 default: 1504 $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__); 1505 break; 1506 } 1507 1508 if ($negate_expr) { 1509 $expr = "!($expr)"; 1510 } 1511 1512 array_splice($tokens, 0, $expr_end, $expr); 1513 1514 return $tokens; 1515 } 1516 1517 1518 /** 1519 * Parse attribute string 1520 * 1521 * @param string $tag_args 1522 * @return array 1523 */ 1524 function _parse_attrs($tag_args) 1525 { 1526 1527 /* Tokenize tag attributes. */ 1528 preg_match_all('~(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+) 1529 )+ | 1530 [=] 1531 ~x', $tag_args, $match); 1532 $tokens = $match[0]; 1533 1534 $attrs = array(); 1535 /* Parse state: 1536 0 - expecting attribute name 1537 1 - expecting '=' 1538 2 - expecting attribute value (not '=') */ 1539 $state = 0; 1540 1541 foreach ($tokens as $token) { 1542 switch ($state) { 1543 case 0: 1544 /* If the token is a valid identifier, we set attribute name 1545 and go to state 1. */ 1546 if (preg_match('~^\w+$~', $token)) { 1547 $attr_name = $token; 1548 $state = 1; 1549 } else 1550 $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__); 1551 break; 1552 1553 case 1: 1554 /* If the token is '=', then we go to state 2. */ 1555 if ($token == '=') { 1556 $state = 2; 1557 } else 1558 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__); 1559 break; 1560 1561 case 2: 1562 /* If token is not '=', we set the attribute value and go to 1563 state 0. */ 1564 if ($token != '=') { 1565 /* We booleanize the token if it's a non-quoted possible 1566 boolean value. */ 1567 if (preg_match('~^(on|yes|true)$~', $token)) { 1568 $token = 'true'; 1569 } else if (preg_match('~^(off|no|false)$~', $token)) { 1570 $token = 'false'; 1571 } else if ($token == 'null') { 1572 $token = 'null'; 1573 } else if (preg_match('~^' . $this->_num_const_regexp . '|0[xX][0-9a-fA-F]+$~', $token)) { 1574 /* treat integer literally */ 1575 } else if (!preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$~', $token)) { 1576 /* treat as a string, double-quote it escaping quotes */ 1577 $token = '"'.addslashes($token).'"'; 1578 } 1579 1580 $attrs[$attr_name] = $token; 1581 $state = 0; 1582 } else 1583 $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__); 1584 break; 1585 } 1586 $last_token = $token; 1587 } 1588 1589 if($state != 0) { 1590 if($state == 1) { 1591 $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__); 1592 } else { 1593 $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__); 1594 } 1595 } 1596 1597 $this->_parse_vars_props($attrs); 1598 1599 return $attrs; 1600 } 1601 1602 /** 1603 * compile multiple variables and section properties tokens into 1604 * PHP code 1605 * 1606 * @param array $tokens 1607 */ 1608 function _parse_vars_props(&$tokens) 1609 { 1610 foreach($tokens as $key => $val) { 1611 $tokens[$key] = $this->_parse_var_props($val); 1612 } 1613 } 1614 1615 /** 1616 * compile single variable and section properties token into 1617 * PHP code 1618 * 1619 * @param string $val 1620 * @param string $tag_attrs 1621 * @return string 1622 */ 1623 function _parse_var_props($val) 1624 { 1625 $val = trim($val); 1626 1627 if(preg_match('~^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$~', $val, $match)) { 1628 // $ variable or object 1629 $return = $this->_parse_var($match[1]); 1630 $modifiers = $match[2]; 1631 if (!empty($this->default_modifiers) && !preg_match('~(^|\|)smarty:nodefaults($|\|)~',$modifiers)) { 1632 $_default_mod_string = implode('|',(array)$this->default_modifiers); 1633 $modifiers = empty($modifiers) ? $_default_mod_string : $_default_mod_string . '|' . $modifiers; 1634 } 1635 $this->_parse_modifiers($return, $modifiers); 1636 return $return; 1637 } elseif (preg_match('~^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) { 1638 // double quoted text 1639 preg_match('~^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match); 1640 $return = $this->_expand_quoted_text($match[1]); 1641 if($match[2] != '') { 1642 $this->_parse_modifiers($return, $match[2]); 1643 } 1644 return $return; 1645 } 1646 elseif(preg_match('~^' . $this->_num_const_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) { 1647 // numerical constant 1648 preg_match('~^(' . $this->_num_const_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match); 1649 if($match[2] != '') { 1650 $this->_parse_modifiers($match[1], $match[2]); 1651 return $match[1]; 1652 } 1653 } 1654 elseif(preg_match('~^' . $this->_si_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) { 1655 // single quoted text 1656 preg_match('~^(' . $this->_si_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match); 1657 if($match[2] != '') { 1658 $this->_parse_modifiers($match[1], $match[2]); 1659 return $match[1]; 1660 } 1661 } 1662 elseif(preg_match('~^' . $this->_cvar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) { 1663 // config var 1664 return $this->_parse_conf_var($val); 1665 } 1666 elseif(preg_match('~^' . $this->_svar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) { 1667 // section var 1668 return $this->_parse_section_prop($val); 1669 } 1670 elseif(!in_array($val, $this->_permitted_tokens) && !is_numeric($val)) { 1671 // literal string 1672 return $this->_expand_quoted_text('"' . strtr($val, array('\\' => '\\\\', '"' => '\\"')) .'"'); 1673 } 1674 return $val; 1675 } 1676 1677 /** 1678 * expand quoted text with embedded variables 1679 * 1680 * @param string $var_expr 1681 * @return string 1682 */ 1683 function _expand_quoted_text($var_expr) 1684 { 1685 // if contains unescaped $, expand it 1686 if(preg_match_all('~(?:\`(?<!\\\\)\$' . $this->_dvar_guts_regexp . '(?:' . $this->_obj_ext_regexp . ')*\`)|(?:(?<!\\\\)\$\w+(\[[a-zA-Z0-9]+\])*)~', $var_expr, $_match)) { 1687 $_match = $_match[0]; 1688 $_replace = array(); 1689 foreach($_match as $_var) { 1690 $_replace[$_var] = '".(' . $this->_parse_var(str_replace('`','',$_var)) . ')."'; 1691 } 1692 $var_expr = strtr($var_expr, $_replace); 1693 $_return = preg_replace('~\.""|(?<!\\\\)""\.~', '', $var_expr); 1694 } else { 1695 $_return = $var_expr; 1696 } 1697 // replace double quoted literal string with single quotes 1698 $_return = preg_replace('~^"([\s\w]+)"$~',"'\\1'",$_return); 1699 return $_return; 1700 } 1701 1702 /** 1703 * parse variable expression into PHP code 1704 * 1705 * @param string $var_expr 1706 * @param string $output 1707 * @return string 1708 */ 1709 function _parse_var($var_expr) 1710 { 1711 $_has_math = false; 1712 $_math_vars = preg_split('~('.$this->_dvar_math_regexp.'|'.$this->_qstr_regexp.')~', $var_expr, -1, PREG_SPLIT_DELIM_CAPTURE); 1713 1714 if(count($_math_vars) > 1) { 1715 $_first_var = ""; 1716 $_complete_var = ""; 1717 $_output = ""; 1718 // simple check if there is any math, to stop recursion (due to modifiers with "xx % yy" as parameter) 1719 foreach($_math_vars as $_k => $_math_var) { 1720 $_math_var = $_math_vars[$_k]; 1721 1722 if(!empty($_math_var) || is_numeric($_math_var)) { 1723 // hit a math operator, so process the stuff which came before it 1724 if(preg_match('~^' . $this->_dvar_math_regexp . '$~', $_math_var)) { 1725 $_has_math = true; 1726 if(!empty($_complete_var) || is_numeric($_complete_var)) { 1727 $_output .= $this->_parse_var($_complete_var); 1728 } 1729 1730 // just output the math operator to php 1731 $_output .= $_math_var; 1732 1733 if(empty($_first_var)) 1734 $_first_var = $_complete_var; 1735 1736 $_complete_var = ""; 1737 } else { 1738 $_complete_var .= $_math_var; 1739 } 1740 } 1741 } 1742 if($_has_math) { 1743 if(!empty($_complete_var) || is_numeric($_complete_var)) 1744 $_output .= $this->_parse_var($_complete_var); 1745 1746 // get the modifiers working (only the last var from math + modifier is left) 1747 $var_expr = $_complete_var; 1748 } 1749 } 1750 1751 // prevent cutting of first digit in the number (we _definitly_ got a number if the first char is a digit) 1752 if(is_numeric(substr($var_expr, 0, 1))) 1753 $_var_ref = $var_expr; 1754 else 1755 $_var_ref = substr($var_expr, 1); 1756 1757 if(!$_has_math) { 1758 1759 // get [foo] and .foo and ->foo and (...) pieces 1760 preg_match_all('~(?:^\w+)|' . $this->_obj_params_regexp . '|(?:' . $this->_var_bracket_regexp . ')|->\$?\w+|\.\$?\w+|\S+~', $_var_ref, $match); 1761 1762 $_indexes = $match[0]; 1763 $_var_name = array_shift($_indexes); 1764 1765 /* Handle $smarty.* variable references as a special case. */ 1766 if ($_var_name == 'smarty') { 1767 /* 1768 * If the reference could be compiled, use the compiled output; 1769 * otherwise, fall back on the $smarty variable generated at 1770 * run-time. 1771 */ 1772 if (($smarty_ref = $this->_compile_smarty_ref($_indexes)) !== null) { 1773 $_output = $smarty_ref; 1774 } else { 1775 $_var_name = substr(array_shift($_indexes), 1); 1776 $_output = "\$this->_smarty_vars['$_var_name']"; 1777 } 1778 } elseif(is_numeric($_var_name) && is_numeric(substr($var_expr, 0, 1))) { 1779 // because . is the operator for accessing arrays thru inidizes we need to put it together again for floating point numbers 1780 if(count($_indexes) > 0) 1781 { 1782 $_var_name .= implode("", $_indexes); 1783 $_indexes = array(); 1784 } 1785 $_output = $_var_name; 1786 } else { 1787 $_output = "\$this->_tpl_vars['$_var_name']"; 1788 } 1789 1790 foreach ($_indexes as $_index) { 1791 if (substr($_index, 0, 1) == '[') { 1792 $_index = substr($_index, 1, -1); 1793 if (is_numeric($_index)) { 1794 $_output .= "[$_index]"; 1795 } elseif (substr($_index, 0, 1) == '$') { 1796