MODX Revolution PHP Cross Reference Content Management Systems

Source: /core/model/smarty/Smarty_Compiler.class.php - 2326 lines - 92369 bytes - Summary - Text - Print

Description: Project:     Smarty: the PHP compiling template engine File:        Smarty_Compiler.class.php This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.

   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.19
  25   * @copyright 2001-2005 New Digital Group, Inc.
  26   * @package Smarty
  27   */
  28  
  29  /* $Id: Smarty_Compiler.class.php 2736 2007-09-16 14:47:53Z mohrt $ */
  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          $output = '';
 438          /* Matched comment. */
 439          if (substr($template_tag, 0, 1) == '*' && substr($template_tag, -1) == '*')
 440              return '';
 441          
 442          /* Split tag into two three parts: command, command modifiers and the arguments. */
 443          if(! preg_match('~^(?:(' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp
 444                  . '|\/?' . $this->_reg_obj_regexp . '|\/?' . $this->_func_regexp . ')(' . $this->_mod_regexp . '*))
 445                        (?:\s+(.*))?$
 446                      ~xs', $template_tag, $match)) {
 447              $this->_syntax_error("unrecognized tag: $template_tag", E_USER_ERROR, __FILE__, __LINE__);
 448          }
 449          
 450          $tag_command = $match[1];
 451          $tag_modifier = isset($match[2]) ? $match[2] : null;
 452          $tag_args = isset($match[3]) ? $match[3] : null;
 453  
 454          if (preg_match('~^' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '$~', $tag_command)) {
 455              /* tag name is a variable or object */
 456              $_return = $this->_parse_var_props($tag_command . $tag_modifier);
 457              return "<?php echo $_return; ?>" . $this->_additional_newline;
 458          }
 459  
 460          /* If the tag name is a registered object, we process it. */
 461          if (preg_match('~^\/?' . $this->_reg_obj_regexp . '$~', $tag_command)) {
 462              return $this->_compile_registered_object_tag($tag_command, $this->_parse_attrs($tag_args), $tag_modifier);
 463          }
 464  
 465          switch ($tag_command) {
 466              case 'include':
 467                  return $this->_compile_include_tag($tag_args);
 468  
 469              case 'include_php':
 470                  return $this->_compile_include_php_tag($tag_args);
 471  
 472              case 'if':
 473                  $this->_push_tag('if');
 474                  return $this->_compile_if_tag($tag_args);
 475  
 476              case 'else':
 477                  list($_open_tag) = end($this->_tag_stack);
 478                  if ($_open_tag != 'if' && $_open_tag != 'elseif')
 479                      $this->_syntax_error('unexpected {else}', E_USER_ERROR, __FILE__, __LINE__);
 480                  else
 481                      $this->_push_tag('else');
 482                  return '<?php else: ?>';
 483  
 484              case 'elseif':
 485                  list($_open_tag) = end($this->_tag_stack);
 486                  if ($_open_tag != 'if' && $_open_tag != 'elseif')
 487                      $this->_syntax_error('unexpected {elseif}', E_USER_ERROR, __FILE__, __LINE__);
 488                  if ($_open_tag == 'if')
 489                      $this->_push_tag('elseif');
 490                  return $this->_compile_if_tag($tag_args, true);
 491  
 492              case '/if':
 493                  $this->_pop_tag('if');
 494                  return '<?php endif; ?>';
 495  
 496              case 'capture':
 497                  return $this->_compile_capture_tag(true, $tag_args);
 498  
 499              case '/capture':
 500                  return $this->_compile_capture_tag(false);
 501  
 502              case 'ldelim':
 503                  return $this->left_delimiter;
 504  
 505              case 'rdelim':
 506                  return $this->right_delimiter;
 507  
 508              case 'section':
 509                  $this->_push_tag('section');
 510                  return $this->_compile_section_start($tag_args);
 511  
 512              case 'sectionelse':
 513                  $this->_push_tag('sectionelse');
 514                  return "<?php endfor; else: ?>";
 515                  break;
 516  
 517              case '/section':
 518                  $_open_tag = $this->_pop_tag('section');
 519                  if ($_open_tag == 'sectionelse')
 520                      return "<?php endif; ?>";
 521                  else
 522                      return "<?php endfor; endif; ?>";
 523  
 524              case 'foreach':
 525                  $this->_push_tag('foreach');
 526                  return $this->_compile_foreach_start($tag_args);
 527                  break;
 528  
 529              case 'foreachelse':
 530                  $this->_push_tag('foreachelse');
 531                  return "<?php endforeach; else: ?>";
 532  
 533              case '/foreach':
 534                  $_open_tag = $this->_pop_tag('foreach');
 535                  if ($_open_tag == 'foreachelse')
 536                      return "<?php endif; unset(\$_from); ?>";
 537                  else
 538                      return "<?php endforeach; endif; unset(\$_from); ?>";
 539                  break;
 540  
 541              case 'strip':
 542              case '/strip':
 543                  if (substr($tag_command, 0, 1)=='/') {
 544                      $this->_pop_tag('strip');
 545                      if (--$this->_strip_depth==0) { /* outermost closing {/strip} */
 546                          $this->_additional_newline = "\n";
 547                          return '{' . $tag_command . '}';
 548                      }
 549                  } else {
 550                      $this->_push_tag('strip');
 551                      if ($this->_strip_depth++==0) { /* outermost opening {strip} */
 552                          $this->_additional_newline = "";
 553                          return '{' . $tag_command . '}';
 554                      }
 555                  }
 556                  return '';
 557  
 558              case 'php':
 559                  /* handle folded tags replaced by {php} */
 560                  list(, $block) = each($this->_folded_blocks);
 561                  $this->_current_line_no += substr_count($block[0], "\n");
 562                  /* the number of matched elements in the regexp in _compile_file()
 563                     determins the type of folded tag that was found */
 564                  switch (count($block)) {
 565                      case 2: /* comment */
 566                          return '';
 567  
 568                      case 3: /* literal */
 569                          return "<?php echo '" . strtr($block[2], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>" . $this->_additional_newline;
 570  
 571                      case 4: /* php */
 572                          if ($this->security && !$this->security_settings['PHP_TAGS']) {
 573                              $this->_syntax_error("(secure mode) php tags not permitted", E_USER_WARNING, __FILE__, __LINE__);
 574                              return;
 575                          }
 576                          return '<?php ' . $block[3] .' ?>';
 577                  }
 578                  break;
 579  
 580              case 'insert':
 581                  return $this->_compile_insert_tag($tag_args);
 582  
 583              default:
 584                  if ($this->_compile_compiler_tag($tag_command, $tag_args, $output)) {
 585                      return $output;
 586                  } else if ($this->_compile_block_tag($tag_command, $tag_args, $tag_modifier, $output)) {
 587                      return $output;
 588                  } else if ($this->_compile_custom_tag($tag_command, $tag_args, $tag_modifier, $output)) {
 589                      return $output;                    
 590                  } else {
 591                      $this->_syntax_error("unrecognized tag '$tag_command'", E_USER_ERROR, __FILE__, __LINE__);
 592                  }
 593  
 594          }
 595      }
 596  
 597  
 598      /**
 599       * compile the custom compiler tag
 600       *
 601       * sets $output to the compiled custom compiler tag
 602       * @param string $tag_command
 603       * @param string $tag_args
 604       * @param string $output
 605       * @return boolean
 606       */
 607      function _compile_compiler_tag($tag_command, $tag_args, &$output)
 608      {
 609          $found = false;
 610          $have_function = true;
 611  
 612          /*
 613           * First we check if the compiler function has already been registered
 614           * or loaded from a plugin file.
 615           */
 616          if (isset($this->_plugins['compiler'][$tag_command])) {
 617              $found = true;
 618              $plugin_func = $this->_plugins['compiler'][$tag_command][0];
 619              if (!is_callable($plugin_func)) {
 620                  $message = "compiler function '$tag_command' is not implemented";
 621                  $have_function = false;
 622              }
 623          }
 624          /*
 625           * Otherwise we need to load plugin file and look for the function
 626           * inside it.
 627           */
 628          else if ($plugin_file = $this->_get_plugin_filepath('compiler', $tag_command)) {
 629              $found = true;
 630  
 631              include_once $plugin_file;
 632  
 633              $plugin_func = 'smarty_compiler_' . $tag_command;
 634              if (!is_callable($plugin_func)) {
 635                  $message = "plugin function $plugin_func() not found in $plugin_file\n";
 636                  $have_function = false;
 637              } else {
 638                  $this->_plugins['compiler'][$tag_command] = array($plugin_func, null, null, null, true);
 639              }
 640          }
 641  
 642          /*
 643           * True return value means that we either found a plugin or a
 644           * dynamically registered function. False means that we didn't and the
 645           * compiler should now emit code to load custom function plugin for this
 646           * tag.
 647           */
 648          if ($found) {
 649              if ($have_function) {
 650                  $output = call_user_func_array($plugin_func, array($tag_args, &$this));
 651                  if($output != '') {
 652                  $output = '<?php ' . $this->_push_cacheable_state('compiler', $tag_command)
 653                                     . $output
 654                                     . $this->_pop_cacheable_state('compiler', $tag_command) . ' ?>';
 655                  }
 656              } else {
 657                  $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
 658              }
 659              return true;
 660          } else {
 661              return false;
 662          }
 663      }
 664  
 665  
 666      /**
 667       * compile block function tag
 668       *
 669       * sets $output to compiled block function tag
 670       * @param string $tag_command
 671       * @param string $tag_args
 672       * @param string $tag_modifier
 673       * @param string $output
 674       * @return boolean
 675       */
 676      function _compile_block_tag($tag_command, $tag_args, $tag_modifier, &$output)
 677      {
 678          if (substr($tag_command, 0, 1) == '/') {
 679              $start_tag = false;
 680              $tag_command = substr($tag_command, 1);
 681          } else
 682              $start_tag = true;
 683  
 684          $found = false;
 685          $have_function = true;
 686  
 687          /*
 688           * First we check if the block function has already been registered
 689           * or loaded from a plugin file.
 690           */
 691          if (isset($this->_plugins['block'][$tag_command])) {
 692              $found = true;
 693              $plugin_func = $this->_plugins['block'][$tag_command][0];
 694              if (!is_callable($plugin_func)) {
 695                  $message = "block function '$tag_command' is not implemented";
 696                  $have_function = false;
 697              }
 698          }
 699          /*
 700           * Otherwise we need to load plugin file and look for the function
 701           * inside it.
 702           */
 703          else if ($plugin_file = $this->_get_plugin_filepath('block', $tag_command)) {
 704              $found = true;
 705  
 706              include_once $plugin_file;
 707  
 708              $plugin_func = 'smarty_block_' . $tag_command;
 709              if (!function_exists($plugin_func)) {
 710                  $message = "plugin function $plugin_func() not found in $plugin_file\n";
 711                  $have_function = false;
 712              } else {
 713                  $this->_plugins['block'][$tag_command] = array($plugin_func, null, null, null, true);
 714  
 715              }
 716          }
 717  
 718          if (!$found) {
 719              return false;
 720          } else if (!$have_function) {
 721              $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
 722              return true;
 723          }
 724  
 725          /*
 726           * Even though we've located the plugin function, compilation
 727           * happens only once, so the plugin will still need to be loaded
 728           * at runtime for future requests.
 729           */
 730          $this->_add_plugin('block', $tag_command);
 731  
 732          if ($start_tag)
 733              $this->_push_tag($tag_command);
 734          else
 735              $this->_pop_tag($tag_command);
 736  
 737          if ($start_tag) {
 738              $output = '<?php ' . $this->_push_cacheable_state('block', $tag_command);
 739              $attrs = $this->_parse_attrs($tag_args);
 740              $_cache_attrs='';
 741              $arg_list = $this->_compile_arg_list('block', $tag_command, $attrs, $_cache_attrs);
 742              $output .= "$_cache_attrs\$this->_tag_stack[] = array('$tag_command', array(".implode(',', $arg_list).')); ';
 743              $output .= '$_block_repeat=true;' . $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], null, $this, $_block_repeat);';
 744              $output .= 'while ($_block_repeat) { ob_start(); ?>';
 745          } else {
 746              $output = '<?php $_block_content = ob_get_contents(); ob_end_clean(); ';
 747              $_out_tag_text = $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], $_block_content, $this, $_block_repeat)';
 748              if ($tag_modifier != '') {
 749                  $this->_parse_modifiers($_out_tag_text, $tag_modifier);
 750              }
 751              $output .= '$_block_repeat=false;echo ' . $_out_tag_text . '; } ';
 752              $output .= " array_pop(\$this->_tag_stack); " . $this->_pop_cacheable_state('block', $tag_command) . '?>';
 753          }
 754  
 755          return true;
 756      }
 757  
 758  
 759      /**
 760       * compile custom function tag
 761       *
 762       * @param string $tag_command
 763       * @param string $tag_args
 764       * @param string $tag_modifier
 765       * @return string
 766       */
 767      function _compile_custom_tag($tag_command, $tag_args, $tag_modifier, &$output)
 768      {
 769          $found = false;
 770          $have_function = true;
 771  
 772          /*
 773           * First we check if the custom function has already been registered
 774           * or loaded from a plugin file.
 775           */
 776          if (isset($this->_plugins['function'][$tag_command])) {
 777              $found = true;
 778              $plugin_func = $this->_plugins['function'][$tag_command][0];
 779              if (!is_callable($plugin_func)) {
 780                  $message = "custom function '$tag_command' is not implemented";
 781                  $have_function = false;
 782              }
 783          }
 784          /*
 785           * Otherwise we need to load plugin file and look for the function
 786           * inside it.
 787           */
 788          else if ($plugin_file = $this->_get_plugin_filepath('function', $tag_command)) {
 789              $found = true;
 790  
 791              include_once $plugin_file;
 792  
 793              $plugin_func = 'smarty_function_' . $tag_command;
 794              if (!function_exists($plugin_func)) {
 795                  $message = "plugin function $plugin_func() not found in $plugin_file\n";
 796                  $have_function = false;
 797              } else {
 798                  $this->_plugins['function'][$tag_command] = array($plugin_func, null, null, null, true);
 799  
 800              }
 801          }
 802  
 803          if (!$found) {
 804              return false;
 805          } else if (!$have_function) {
 806              $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
 807              return true;
 808          }
 809  
 810          /* declare plugin to be loaded on display of the template that
 811             we compile right now */
 812          $this->_add_plugin('function', $tag_command);
 813  
 814          $_cacheable_state = $this->_push_cacheable_state('function', $tag_command);
 815          $attrs = $this->_parse_attrs($tag_args);
 816          $_cache_attrs = '';
 817          $arg_list = $this->_compile_arg_list('function', $tag_command, $attrs, $_cache_attrs);
 818  
 819          $output = $this->_compile_plugin_call('function', $tag_command).'(array('.implode(',', $arg_list)."), \$this)";
 820          if($tag_modifier != '') {
 821              $this->_parse_modifiers($output, $tag_modifier);
 822          }
 823  
 824          if($output != '') {
 825              $output =  '<?php ' . $_cacheable_state . $_cache_attrs . 'echo ' . $output . ';'
 826                  . $this->_pop_cacheable_state('function', $tag_command) . "?>" . $this->_additional_newline;
 827          }
 828  
 829          return true;
 830      }
 831  
 832      /**
 833       * compile a registered object tag
 834       *
 835       * @param string $tag_command
 836       * @param array $attrs
 837       * @param string $tag_modifier
 838       * @return string
 839       */
 840      function _compile_registered_object_tag($tag_command, $attrs, $tag_modifier)
 841      {
 842          if (substr($tag_command, 0, 1) == '/') {
 843              $start_tag = false;
 844              $tag_command = substr($tag_command, 1);
 845          } else {
 846              $start_tag = true;
 847          }
 848  
 849          list($object, $obj_comp) = explode('->', $tag_command);
 850  
 851          $arg_list = array();
 852          if(count($attrs)) {
 853              $_assign_var = false;
 854              foreach ($attrs as $arg_name => $arg_value) {
 855                  if($arg_name == 'assign') {
 856                      $_assign_var = $arg_value;
 857                      unset($attrs['assign']);
 858                      continue;
 859                  }
 860                  if (is_bool($arg_value))
 861                      $arg_value = $arg_value ? 'true' : 'false';
 862                  $arg_list[] = "'$arg_name' => $arg_value";
 863              }
 864          }
 865  
 866          if($this->_reg_objects[$object][2]) {
 867              // smarty object argument format
 868              $args = "array(".implode(',', (array)$arg_list)."), \$this";
 869          } else {
 870              // traditional argument format
 871              $args = implode(',', array_values($attrs));
 872              if (empty($args)) {
 873                  $args = '';
 874              }
 875          }
 876  
 877          $prefix = '';
 878          $postfix = '';
 879          $newline = '';
 880          if(!is_object($this->_reg_objects[$object][0])) {
 881              $this->_trigger_fatal_error("registered '$object' is not an object" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
 882          } elseif(!empty($this->_reg_objects[$object][1]) && !in_array($obj_comp, $this->_reg_objects[$object][1])) {
 883              $this->_trigger_fatal_error("'$obj_comp' is not a registered component of object '$object'", $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
 884          } elseif(method_exists($this->_reg_objects[$object][0], $obj_comp)) {
 885              // method
 886              if(in_array($obj_comp, $this->_reg_objects[$object][3])) {
 887                  // block method
 888                  if ($start_tag) {
 889                      $prefix = "\$this->_tag_stack[] = array('$obj_comp', $args); ";
 890                      $prefix .= "\$_block_repeat=true; \$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], null, \$this, \$_block_repeat); ";
 891                      $prefix .= "while (\$_block_repeat) { ob_start();";
 892                      $return = null;
 893                      $postfix = '';
 894                  } else {
 895                      $prefix = "\$_obj_block_content = ob_get_contents(); ob_end_clean(); \$_block_repeat=false;";
 896                      $return = "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], \$_obj_block_content, \$this, \$_block_repeat)";
 897                      $postfix = "} array_pop(\$this->_tag_stack);";
 898                  }
 899              } else {
 900                  // non-block method
 901                  $return = "\$this->_reg_objects['$object'][0]->$obj_comp($args)";
 902              }
 903          } else {
 904              // property
 905              $return = "\$this->_reg_objects['$object'][0]->$obj_comp";
 906          }
 907  
 908          if($return != null) {
 909              if($tag_modifier != '') {
 910                  $this->_parse_modifiers($return, $tag_modifier);
 911              }
 912  
 913              if(!empty($_assign_var)) {
 914                  $output = "\$this->assign('" . $this->_dequote($_assign_var) ."',  $return);";
 915              } else {
 916                  $output = 'echo ' . $return . ';';
 917                  $newline = $this->_additional_newline;
 918              }
 919          } else {
 920              $output = '';
 921          }
 922  
 923          return '<?php ' . $prefix . $output . $postfix . "?>" . $newline;
 924      }
 925  
 926      /**
 927       * Compile {insert ...} tag
 928       *
 929       * @param string $tag_args
 930       * @return string
 931       */
 932      function _compile_insert_tag($tag_args)
 933      {
 934          $attrs = $this->_parse_attrs($tag_args);
 935          $name = $this->_dequote($attrs['name']);
 936  
 937          if (empty($name)) {
 938              return $this->_syntax_error("missing insert name", E_USER_ERROR, __FILE__, __LINE__);
 939          }
 940          
 941          if (!preg_match('~^\w+$~', $name)) {
 942              return $this->_syntax_error("'insert: 'name' must be an insert function name", E_USER_ERROR, __FILE__, __LINE__);
 943          }
 944  
 945          if (!empty($attrs['script'])) {
 946              $delayed_loading = true;
 947          } else {
 948              $delayed_loading = false;
 949          }
 950  
 951          foreach ($attrs as $arg_name => $arg_value) {
 952              if (is_bool($arg_value))
 953                  $arg_value = $arg_value ? 'true' : 'false';
 954              $arg_list[] = "'$arg_name' => $arg_value";
 955          }
 956  
 957          $this->_add_plugin('insert', $name, $delayed_loading);
 958  
 959          $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))";
 960  
 961          return "<?php require_once(SMARTY_CORE_DIR . 'core.run_insert_handler.php');\necho smarty_core_run_insert_handler($_params, \$this); ?>" . $this->_additional_newline;
 962      }
 963  
 964      /**
 965       * Compile {include ...} tag
 966       *
 967       * @param string $tag_args
 968       * @return string
 969       */
 970      function _compile_include_tag($tag_args)
 971      {
 972          $attrs = $this->_parse_attrs($tag_args);
 973          $arg_list = array();
 974  
 975          if (empty($attrs['file'])) {
 976              $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR, __FILE__, __LINE__);
 977          }
 978  
 979          foreach ($attrs as $arg_name => $arg_value) {
 980              if ($arg_name == 'file') {
 981                  $include_file = $arg_value;
 982                  continue;
 983              } else if ($arg_name == 'assign') {
 984                  $assign_var = $arg_value;
 985                  continue;
 986              }
 987              if (is_bool($arg_value))
 988                  $arg_value = $arg_value ? 'true' : 'false';
 989              $arg_list[] = "'$arg_name' => $arg_value";
 990          }
 991  
 992          $output = '<?php ';
 993  
 994          if (isset($assign_var)) {
 995              $output .= "ob_start();\n";
 996          }
 997  
 998          $output .=
 999              "\$_smarty_tpl_vars = \$this->_tpl_vars;\n";
1000  
1001  
1002          $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))";
1003          $output .= "\$this->_smarty_include($_params);\n" .
1004          "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" .
1005          "unset(\$_smarty_tpl_vars);\n";
1006  
1007          if (isset($assign_var)) {
1008              $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n";
1009          }
1010  
1011          $output .= ' ?>';
1012  
1013          return $output;
1014  
1015      }
1016  
1017      /**
1018       * Compile {include ...} tag
1019       *
1020       * @param string $tag_args
1021       * @return string
1022       */
1023      function _compile_include_php_tag($tag_args)
1024      {
1025          $attrs = $this->_parse_attrs($tag_args);
1026  
1027          if (empty($attrs['file'])) {
1028              $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR, __FILE__, __LINE__);
1029          }
1030  
1031          $assign_var = (empty($attrs['assign'])) ? '' : $this->_dequote($attrs['assign']);
1032          $once_var = (empty($attrs['once']) || $attrs['once']=='false') ? 'false' : 'true';
1033  
1034          $arg_list = array();
1035          foreach($attrs as $arg_name => $arg_value) {
1036              if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') {
1037                  if(is_bool($arg_value))
1038                      $arg_value = $arg_value ? 'true' : 'false';
1039                  $arg_list[] = "'$arg_name' => $arg_value";
1040              }
1041          }
1042  
1043          $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', $arg_list)."))";
1044  
1045          return "<?php require_once(SMARTY_CORE_DIR . 'core.smarty_include_php.php');\nsmarty_core_smarty_include_php($_params, \$this); ?>" . $this->_additional_newline;
1046      }
1047  
1048  
1049      /**
1050       * Compile {section ...} tag
1051       *
1052       * @param string $tag_args
1053       * @return string
1054       */
1055      function _compile_section_start($tag_args)
1056      {
1057          $attrs = $this->_parse_attrs($tag_args);
1058          $arg_list = array();
1059  
1060          $output = '<?php ';
1061          $section_name = $attrs['name'];
1062          if (empty($section_name)) {
1063              $this->_syntax_error("missing section name", E_USER_ERROR, __FILE__, __LINE__);
1064          }
1065  
1066          $output .= "unset(\$this->_sections[$section_name]);\n";
1067          $section_props = "\$this->_sections[$section_name]";
1068  
1069          foreach ($attrs as $attr_name => $attr_value) {
1070              switch ($attr_name) {
1071                  case 'loop':
1072                      $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n";
1073                      break;
1074  
1075                  case 'show':
1076                      if (is_bool($attr_value))
1077                          $show_attr_value = $attr_value ? 'true' : 'false';
1078                      else
1079                          $show_attr_value = "(bool)$attr_value";
1080                      $output .= "{$section_props}['show'] = $show_attr_value;\n";
1081                      break;
1082  
1083                  case 'name':
1084                      $output .= "{$section_props}['$attr_name'] = $attr_value;\n";
1085                      break;
1086  
1087                  case 'max':
1088                  case 'start':
1089                      $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n";
1090                      break;
1091  
1092                  case 'step':
1093                      $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n";
1094                      break;
1095  
1096                  default:
1097                      $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR, __FILE__, __LINE__);
1098                      break;
1099              }
1100          }
1101  
1102          if (!isset($attrs['show']))
1103              $output .= "{$section_props}['show'] = true;\n";
1104  
1105          if (!isset($attrs['loop']))
1106              $output .= "{$section_props}['loop'] = 1;\n";
1107  
1108          if (!isset($attrs['max']))
1109              $output .= "{$section_props}['max'] = {$section_props}['loop'];\n";
1110          else
1111              $output .= "if ({$section_props}['max'] < 0)\n" .
1112                         "    {$section_props}['max'] = {$section_props}['loop'];\n";
1113  
1114          if (!isset($attrs['step']))
1115              $output .= "{$section_props}['step'] = 1;\n";
1116  
1117          if (!isset($attrs['start']))
1118              $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n";
1119          else {
1120              $output .= "if ({$section_props}['start'] < 0)\n" .
1121                         "    {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" .
1122                         "else\n" .
1123                         "    {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n";
1124          }
1125  
1126          $output .= "if ({$section_props}['show']) {\n";
1127          if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) {
1128              $output .= "    {$section_props}['total'] = {$section_props}['loop'];\n";
1129          } else {
1130              $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";
1131          }
1132          $output .= "    if ({$section_props}['total'] == 0)\n" .
1133                     "        {$section_props}['show'] = false;\n" .
1134                     "} else\n" .
1135                     "    {$section_props}['total'] = 0;\n";
1136  
1137          $output .= "if ({$section_props}['show']):\n";
1138          $output .= "
1139              for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1;
1140                   {$section_props}['iteration'] <= {$section_props}['total'];
1141                   {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n";
1142          $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n";
1143          $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n";
1144          $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n";
1145          $output .= "{$section_props}['first']      = ({$section_props}['iteration'] == 1);\n";
1146          $output .= "{$section_props}['last']       = ({$section_props}['iteration'] == {$section_props}['total']);\n";
1147  
1148          $output .= "?>";
1149  
1150          return $output;
1151      }
1152  
1153  
1154      /**
1155       * Compile {foreach ...} tag.
1156       *
1157       * @param string $tag_args
1158       * @return string
1159       */
1160      function _compile_foreach_start($tag_args)
1161      {
1162          $attrs = $this->_parse_attrs($tag_args);
1163          $arg_list = array();
1164  
1165          if (empty($attrs['from'])) {
1166              return $this->_syntax_error("foreach: missing 'from' attribute", E_USER_ERROR, __FILE__, __LINE__);
1167          }
1168          $from = $attrs['from'];
1169  
1170          if (empty($attrs['item'])) {
1171              return $this->_syntax_error("foreach: missing 'item' attribute", E_USER_ERROR, __FILE__, __LINE__);
1172          }
1173          $item = $this->_dequote($attrs['item']);
1174          if (!preg_match('~^\w+$~', $item)) {
1175              return $this->_syntax_error("foreach: 'item' must be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1176          }
1177  
1178          if (isset($attrs['key'])) {
1179              $key  = $this->_dequote($attrs['key']);
1180              if (!preg_match('~^\w+$~', $key)) {
1181                  return $this->_syntax_error("foreach: 'key' must to be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1182              }
1183              $key_part = "\$this->_tpl_vars['$key'] => ";
1184          } else {
1185              $key = null;
1186              $key_part = '';
1187          }
1188  
1189          if (isset($attrs['name'])) {
1190              $name = $attrs['name'];
1191          } else {
1192              $name = null;
1193          }
1194  
1195          $output = '<?php ';
1196          $output .= "\$_from = $from; if (!is_array(\$_from) && !is_object(\$_from)) { settype(\$_from, 'array'); }";
1197          if (isset($name)) {
1198              $foreach_props = "\$this->_foreach[$name]";
1199              $output .= "{$foreach_props} = array('total' => count(\$_from), 'iteration' => 0);\n";
1200              $output .= "if ({$foreach_props}['total'] > 0):\n";
1201              $output .= "    foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1202              $output .= "        {$foreach_props}['iteration']++;\n";
1203          } else {
1204              $output .= "if (count(\$_from)):\n";
1205              $output .= "    foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1206          }
1207          $output .= '?>';
1208  
1209          return $output;
1210      }
1211  
1212  
1213      /**
1214       * Compile {capture} .. {/capture} tags
1215       *
1216       * @param boolean $start true if this is the {capture} tag
1217       * @param string $tag_args
1218       * @return string
1219       */
1220  
1221      function _compile_capture_tag($start, $tag_args = '')
1222      {
1223          $attrs = $this->_parse_attrs($tag_args);
1224  
1225          if ($start) {
1226              $buffer = isset($attrs['name']) ? $attrs['name'] : "'default'";
1227              $assign = isset($attrs['assign']) ? $attrs['assign'] : null;
1228              $append = isset($attrs['append']) ? $attrs['append'] : null;
1229              
1230              $output = "<?php ob_start(); ?>";
1231              $this->_capture_stack[] = array($buffer, $assign, $append);
1232          } else {
1233              list($buffer, $assign, $append) = array_pop($this->_capture_stack);
1234              $output = "<?php \$this->_smarty_vars['capture'][$buffer] = ob_get_contents(); ";
1235              if (isset($assign)) {
1236                  $output .= " \$this->assign($assign, ob_get_contents());";
1237              }
1238              if (isset($append)) {
1239                  $output .= " \$this->append($append, ob_get_contents());";
1240              }
1241              $output .= "ob_end_clean(); ?>";
1242          }
1243  
1244          return $output;
1245      }
1246  
1247      /**
1248       * Compile {if ...} tag
1249       *
1250       * @param string $tag_args
1251       * @param boolean $elseif if true, uses elseif instead of if
1252       * @return string
1253       */
1254      function _compile_if_tag($tag_args, $elseif = false)
1255      {
1256  
1257          /* Tokenize args for 'if' tag. */
1258          preg_match_all('~(?>
1259                  ' . $this->_obj_call_regexp . '(?:' . $this->_mod_regexp . '*)? | # valid object call
1260                  ' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)?    | # var or quoted string
1261                  \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@    | # valid non-word token
1262                  \b\w+\b                                                        | # valid word token
1263                  \S+                                                           # anything else
1264                  )~x', $tag_args, $match);
1265  
1266          $tokens = $match[0];
1267  
1268          if(empty($tokens)) {
1269              $_error_msg = $elseif ? "'elseif'" : "'if'";
1270              $_error_msg .= ' statement requires arguments'; 
1271              $this->_syntax_error($_error_msg, E_USER_ERROR, __FILE__, __LINE__);
1272          }
1273              
1274                  
1275          // make sure we have balanced parenthesis
1276          $token_count = array_count_values($tokens);
1277          if(isset($token_count['(']) && $token_count['('] != $token_count[')']) {
1278              $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR, __FILE__, __LINE__);
1279          }
1280  
1281          $is_arg_stack = array();
1282  
1283          for ($i = 0; $i < count($tokens); $i++) {
1284  
1285              $token = &$tokens[$i];
1286  
1287              switch (strtolower($token)) {
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                  case '@':
1314                      break;
1315  
1316                  case 'eq':
1317                      $token = '==';
1318                      break;
1319  
1320                  case 'ne':
1321                  case 'neq':
1322                      $token = '!=';
1323                      break;
1324  
1325                  case 'lt':
1326                      $token = '<';
1327                      break;
1328  
1329                  case 'le':
1330                  case 'lte':
1331                      $token = '<=';
1332                      break;
1333  
1334                  case 'gt':
1335                      $token = '>';
1336                      break;
1337  
1338                  case 'ge':
1339                  case 'gte':
1340                      $token = '>=';
1341                      break;
1342  
1343                  case 'and':
1344                      $token = '&&';
1345                      break;
1346  
1347                  case 'or':
1348                      $token = '||';
1349                      break;
1350  
1351                  case 'not':
1352                      $token = '!';
1353                      break;
1354  
1355                  case 'mod':
1356                      $token = '%';
1357                      break;
1358  
1359                  case '(':
1360                      array_push($is_arg_stack, $i);
1361                      break;
1362  
1363                  case 'is':
1364                      /* If last token was a ')', we operate on the parenthesized
1365                         expression. The start of the expression is on the stack.
1366                         Otherwise, we operate on the last encountered token. */
1367                      if ($tokens[$i-1] == ')')
1368                          $is_arg_start = array_pop($is_arg_stack);
1369                      else
1370                          $is_arg_start = $i-1;
1371                      /* Construct the argument for 'is' expression, so it knows
1372                         what to operate on. */
1373                      $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
1374  
1375                      /* Pass all tokens from next one until the end to the
1376                         'is' expression parsing function. The function will
1377                         return modified tokens, where the first one is the result
1378                         of the 'is' expression and the rest are the tokens it
1379                         didn't touch. */
1380                      $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));
1381  
1382                      /* Replace the old tokens with the new ones. */
1383                      array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);
1384  
1385                      /* Adjust argument start so that it won't change from the
1386                         current position for the next iteration. */
1387                      $i = $is_arg_start;
1388                      break;
1389  
1390                  default:
1391                      if(preg_match('~^' . $this->_func_regexp . '$~', $token) ) {
1392                              // function call
1393                              if($this->security &&
1394                                 !in_array($token, $this->security_settings['IF_FUNCS'])) {
1395                                  $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);
1396                              }
1397                      } elseif(preg_match('~^' . $this->_var_regexp . '$~', $token) && (strpos('+-*/^%&|', substr($token, -1)) === false) && isset($tokens[$i+1]) && $tokens[$i+1] == '(') {
1398                          // variable function call
1399                          $this->_syntax_error("variable function call '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);                      
1400                      } elseif(preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$~', $token)) {
1401                          // object or variable
1402                          $token = $this->_parse_var_props($token);
1403                      } elseif(is_numeric($token)) {
1404                          // number, skip it
1405                      } else {
1406                          $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__);
1407                      }
1408                      break;
1409              }
1410          }
1411  
1412          if ($elseif)
1413              return '<?php elseif ('.implode(' ', $tokens).'): ?>';
1414          else
1415              return '<?php if ('.implode(' ', $tokens).'): ?>';
1416      }
1417  
1418  
1419      function _compile_arg_list($type, $name, $attrs, &$cache_code) {
1420          $arg_list = array();
1421  
1422          if (isset($type) && isset($name)
1423              && isset($this->_plugins[$type])
1424              && isset($this->_plugins[$type][$name])
1425              && empty($this->_plugins[$type][$name][4])
1426              && is_array($this->_plugins[$type][$name][5])
1427              ) {
1428              /* we have a list of parameters that should be cached */
1429              $_cache_attrs = $this->_plugins[$type][$name][5];
1430              $_count = $this->_cache_attrs_count++;
1431              $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');";
1432  
1433          } else {
1434              /* no parameters are cached */
1435              $_cache_attrs = null;
1436          }
1437  
1438          foreach ($attrs as $arg_name => $arg_value) {
1439              if (is_bool($arg_value))
1440                  $arg_value = $arg_value ? 'true' : 'false';
1441              if (is_null($arg_value))
1442                  $arg_value = 'null';
1443              if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) {
1444                  $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)";
1445              } else {
1446                  $arg_list[] = "'$arg_name' => $arg_value";
1447              }
1448          }
1449          return $arg_list;
1450      }
1451  
1452      /**
1453       * Parse is expression
1454       *
1455       * @param string $is_arg
1456       * @param array $tokens
1457       * @return array
1458       */
1459      function _parse_is_expr($is_arg, $tokens)
1460      {
1461          $expr_end = 0;
1462          $negate_expr = false;
1463  
1464          if (($first_token = array_shift($tokens)) == 'not') {
1465              $negate_expr = true;
1466              $expr_type = array_shift($tokens);
1467          } else
1468              $expr_type = $first_token;
1469  
1470          switch ($expr_type) {
1471              case 'even':
1472                  if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1473                      $expr_end++;
1474                      $expr_arg = $tokens[$expr_end++];
1475                      $expr = "!(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1476                  } else
1477                      $expr = "!(1 & $is_arg)";
1478                  break;
1479  
1480              case 'odd':
1481                  if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1482                      $expr_end++;
1483                      $expr_arg = $tokens[$expr_end++];
1484                      $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1485                  } else
1486                      $expr = "(1 & $is_arg)";
1487                  break;
1488  
1489              case 'div':
1490                  if (@$tokens[$expr_end] == 'by') {
1491                      $expr_end++;
1492                      $expr_arg = $tokens[$expr_end++];
1493                      $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")";
1494                  } else {
1495                      $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__);
1496                  }
1497                  break;
1498  
1499              default:
1500                  $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__);
1501                  break;
1502          }
1503  
1504          if ($negate_expr) {
1505              $expr = "!($expr)";
1506          }
1507  
1508          array_splice($tokens, 0, $expr_end, $expr);
1509  
1510          return $tokens;
1511      }
1512  
1513  
1514      /**
1515       * Parse attribute string
1516       *
1517       * @param string $tag_args
1518       * @return array
1519       */
1520      function _parse_attrs($tag_args)
1521      {
1522  
1523          /* Tokenize tag attributes. */
1524          preg_match_all('~(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+)
1525                           )+ |
1526                           [=]
1527                          ~x', $tag_args, $match);
1528          $tokens       = $match[0];
1529  
1530          $attrs = array();
1531          /* Parse state:
1532              0 - expecting attribute name
1533              1 - expecting '='
1534              2 - expecting attribute value (not '=') */
1535          $state = 0;
1536  
1537          foreach ($tokens as $token) {
1538              switch ($state) {
1539                  case 0:
1540                      /* If the token is a valid identifier, we set attribute name
1541                         and go to state 1. */
1542                      if (preg_match('~^\w+$~', $token)) {
1543                          $attr_name = $token;
1544                          $state = 1;
1545                      } else
1546                          $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__);
1547                      break;
1548  
1549                  case 1:
1550                      /* If the token is '=', then we go to state 2. */
1551                      if ($token == '=') {
1552                          $state = 2;
1553                      } else
1554                          $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1555                      break;
1556  
1557                  case 2:
1558                      /* If token is not '=', we set the attribute value and go to
1559                         state 0. */
1560                      if ($token != '=') {
1561                          /* We booleanize the token if it's a non-quoted possible
1562                             boolean value. */
1563                          if (preg_match('~^(on|yes|true)$~', $token)) {
1564                              $token = 'true';
1565                          } else if (preg_match('~^(off|no|false)$~', $token)) {
1566                              $token = 'false';
1567                          } else if ($token == 'null') {
1568                              $token = 'null';
1569                          } else if (preg_match('~^' . $this->_num_const_regexp . '|0[xX][0-9a-fA-F]+$~', $token)) {
1570                              /* treat integer literally */
1571                          } else if (!preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$~', $token)) {
1572                              /* treat as a string, double-quote it escaping quotes */
1573                              $token = '"'.addslashes($token).'"';
1574                          }
1575  
1576                          $attrs[$attr_name] = $token;
1577                          $state = 0;
1578                      } else
1579                          $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__);
1580                      break;
1581              }
1582              $last_token = $token;
1583          }
1584  
1585          if($state != 0) {
1586              if($state == 1) {
1587                  $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1588              } else {
1589                  $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__);
1590              }
1591          }
1592  
1593          $this->_parse_vars_props($attrs);
1594  
1595          return $attrs;
1596      }
1597  
1598      /**
1599       * compile multiple variables and section properties tokens into
1600       * PHP code
1601       *
1602       * @param array $tokens
1603       */
1604      function _parse_vars_props(&$tokens)
1605      {
1606          foreach($tokens as $key => $val) {
1607              $tokens[$key] = $this->_parse_var_props($val);
1608          }
1609      }
1610  
1611      /**
1612       * compile single variable and section properties token into
1613       * PHP code
1614       *
1615       * @param string $val
1616       * @param string $tag_attrs
1617       * @return string
1618       */
1619      function _parse_var_props($val)
1620      {
1621          $val = trim($val);
1622  
1623          if(preg_match('~^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$~', $val, $match)) {
1624              // $ variable or object
1625              $return = $this->_parse_var($match[1]);
1626              $modifiers = $match[2];
1627              if (!empty($this->default_modifiers) && !preg_match('~(^|\|)smarty:nodefaults($|\|)~',$modifiers)) {
1628                  $_default_mod_string = implode('|',(array)$this->default_modifiers);
1629                  $modifiers = empty($modifiers) ? $_default_mod_string : $_default_mod_string . '|' . $modifiers;
1630              }
1631              $this->_parse_modifiers($return, $modifiers);
1632              return $return;
1633          } elseif (preg_match('~^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1634                  // double quoted text
1635                  preg_match('~^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1636                  $return = $this->_expand_quoted_text($match[1]);
1637                  if($match[2] != '') {
1638                      $this->_parse_modifiers($return, $match[2]);
1639                  }
1640                  return $return;
1641              }
1642          elseif(preg_match('~^' . $this->_num_const_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1643                  // numerical constant
1644                  preg_match('~^(' . $this->_num_const_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1645                  if($match[2] != '') {
1646                      $this->_parse_modifiers($match[1], $match[2]);
1647                      return $match[1];
1648                  }
1649              }
1650          elseif(preg_match('~^' . $this->_si_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1651                  // single quoted text
1652                  preg_match('~^(' . $this->_si_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1653                  if($match[2] != '') {
1654                      $this->_parse_modifiers($match[1], $match[2]);
1655                      return $match[1];
1656                  }
1657              }
1658          elseif(preg_match('~^' . $this->_cvar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1659                  // config var
1660                  return $this->_parse_conf_var($val);
1661              }
1662          elseif(preg_match('~^' . $this->_svar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1663                  // section var
1664                  return $this->_parse_section_prop($val);
1665              }
1666          elseif(!in_array($val, $this->_permitted_tokens) && !is_numeric($val)) {
1667              // literal string
1668              return $this->_expand_quoted_text('"' . strtr($val, array('\\' => '\\\\', '"' => '\\"')) .'"');
1669          }
1670          return $val;
1671      }
1672  
1673      /**
1674       * expand quoted text with embedded variables
1675       *
1676       * @param string $var_expr
1677       * @return string
1678       */
1679      function _expand_quoted_text($var_expr)
1680      {
1681          // if contains unescaped $, expand it
1682          if(preg_match_all('~(?:\`(?<!\\\\)\$' . $this->_dvar_guts_regexp . '(?:' . $this->_obj_ext_regexp . ')*\`)|(?:(?<!\\\\)\$\w+(\[[a-zA-Z0-9]+\])*)~', $var_expr, $_match)) {
1683              $_match = $_match[0];
1684              $_replace = array();
1685              foreach($_match as $_var) {
1686                  $_replace[$_var] = '".(' . $this->_parse_var(str_replace('`','',$_var)) . ')."';
1687              }
1688              $var_expr = strtr($var_expr, $_replace);
1689              $_return = preg_replace('~\.""|(?<!\\\\)""\.~', '', $var_expr);
1690          } else {
1691              $_return = $var_expr;
1692          }
1693          // replace double quoted literal string with single quotes
1694          $_return = preg_replace('~^"([\s\w]+)"$~',"'\\1'",$_return);
1695          return $_return;
1696      }
1697  
1698      /**
1699       * parse variable expression into PHP code
1700       *
1701       * @param string $var_expr
1702       * @param string $output
1703       * @return string
1704       */
1705      function _parse_var($var_expr)
1706      {
1707          $_has_math = false;
1708          $_math_vars = preg_split('~('.$this->_dvar_math_regexp.'|'.$this->_qstr_regexp.')~', $var_expr, -1, PREG_SPLIT_DELIM_CAPTURE);
1709  
1710          if(count($_math_vars) > 1) {
1711              $_first_var = "";
1712              $_complete_var = "";
1713              $_output = "";
1714              // simple check if there is any math, to stop recursion (due to modifiers with "xx % yy" as parameter)
1715              foreach($_math_vars as $_k => $_math_var) {
1716                  $_math_var = $_math_vars[$_k];
1717  
1718                  if(!empty($_math_var) || is_numeric($_math_var)) {
1719                      // hit a math operator, so process the stuff which came before it
1720                      if(preg_match('~^' . $this->_dvar_math_regexp . '$~', $_math_var)) {
1721                          $_has_math = true;
1722                          if(!empty($_complete_var) || is_numeric($_complete_var)) {
1723                              $_output .= $this->_parse_var($_complete_var);
1724                          }
1725  
1726                          // just output the math operator to php
1727                          $_output .= $_math_var;
1728  
1729                          if(empty($_first_var))
1730                              $_first_var = $_complete_var;
1731  
1732                          $_complete_var = "";
1733                      } else {
1734                          $_complete_var .= $_math_var;
1735                      }
1736                  }
1737              }
1738              if($_has_math) {
1739                  if(!empty($_complete_var) || is_numeric($_complete_var))
1740                      $_output .= $this->_parse_var($_complete_var);
1741  
1742                  // get the modifiers working (only the last var from math + modifier is left)
1743                  $var_expr = $_complete_var;
1744              }
1745          }
1746  
1747          // prevent cutting of first digit in the number (we _definitly_ got a number if the first char is a digit)
1748          if(is_numeric(substr($var_expr, 0, 1)))
1749              $_var_ref = $var_expr;
1750          else
1751              $_var_ref = substr($var_expr, 1);
1752          
1753          if(!$_has_math) {
1754              
1755              // get [foo] and .foo and ->foo and (...) pieces
1756              preg_match_all('~(?:^\w+)|' . $this->_obj_params_regexp . '|(?:' . $this->_var_bracket_regexp . ')|->\$?\w+|\.\$?\w+|\S+~', $_var_ref, $match);
1757                          
1758              $_indexes = $match[0];
1759              $_var_name = array_shift($_indexes);
1760  
1761              /* Handle $smarty.* variable references as a special case. */
1762              if ($_var_name == 'smarty') {
1763                  /*
1764                   * If the reference could be compiled, use the compiled output;
1765                   * otherwise, fall back on the $smarty variable generated at
1766                   * run-time.
1767                   */
1768                  if (($smarty_ref = $this->_compile_smarty_ref($_indexes)) !== null) {
1769                      $_output = $smarty_ref;
1770                  } else {
1771                      $_var_name = substr(array_shift($_indexes), 1);
1772                      $_output = "\$this->_smarty_vars['$_var_name']";
1773                  }
1774              } elseif(is_numeric($_var_name) && is_numeric(substr($var_expr, 0, 1))) {
1775                  // because . is the operator for accessing arrays thru inidizes we need to put it together again for floating point numbers
1776                  if(count($_indexes) > 0)
1777                  {
1778                      $_var_name .= implode("", $_indexes);
1779                      $_indexes = array();
1780                  }
1781                  $_output = $_var_name;
1782              } else {
1783                  $_output = "\$this->_tpl_vars['$_var_name']";
1784              }
1785  
1786              foreach ($_indexes as $_index) {
1787                  if (substr($_index, 0, 1) == '[') {
1788                      $_index = substr($_index, 1, -1);
1789                      if (is_numeric($_index)) {
1790                          $_output .= "[$_index]";
1791                      } elseif (substr($_index, 0, 1) == '$') {
1792                          if (strpos($_index, '.') !== false) {
1793                              $_output .= '[' . $this->_parse_var($_index) . ']';
1794                          } else {
1795                              $_output .= "[\$this->_tpl_vars['" . substr($_index, 1) . "']]";
1796                          }
1797                      } else {
1798                          $_var_parts = explode('.', $_index);
1799                          $_var_section = $_var_parts[0];
1800                          $_var_section_prop = isset($_var_parts[1]) ? $_var_parts[1] : 'index';
1801                          $_output .= "[\$this->_sections['$_var_section']['$_var_section_prop']]";
1802                      }
1803                  } else if (substr($_index, 0, 1) == '.') {
1804                      if (substr($_index, 1, 1) == '$')
1805                          $_output .= "[\$this->_tpl_vars['" . substr($_index, 2) . "']]";
1806                      else
1807                          $_output .= "['" . substr($_index, 1) . "']";
1808                  } else if (substr($_index,0,2) == '->') {
1809                      if(substr($_index,2,2) == '__') {
1810                          $this->_syntax_error('call to internal object members is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1811                      } elseif($this->security && substr($_index, 2, 1) == '_') {
1812                          $this->_syntax_error('(secure) call to private object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1813                      } elseif (substr($_index, 2, 1) == '$') {
1814                          if ($this->security) {
1815                              $this->_syntax_error('(secure) call to dynamic object member is not allowed', E_USER_ERROR, __FILE__, __LINE__);
1816                          } else {
1817                              $_output .= '->{(($_var=$this->_tpl_vars[\''.substr($_index,3).'\']) && substr($_var,0,2)!=\'__\') ? $_var : $this->trigger_error("cannot access property \\"$_var\\"")}';
1818                          }
1819                      } else {
1820                          $_output .= $_index;
1821                      }
1822                  } elseif (substr($_index, 0, 1) == '(') {
1823                      $_index = $this->_parse_parenth_args($_index);
1824                      $_output .= $_index;
1825                  } else {
1826                      $_output .= $_index;
1827                  }
1828              }
1829          }
1830  
1831          return $_output;
1832      }
1833  
1834      /**
1835       * parse arguments in function call parenthesis
1836       *
1837       * @param string $parenth_args
1838       * @return string
1839       */
1840      function _parse_parenth_args($parenth_args)
1841      {
1842          preg_match_all('~' . $this->_param_regexp . '~',$parenth_args, $match);
1843          $orig_vals = $match = $match[0];
1844          $this->_parse_vars_props($match);
1845          $replace = array();
1846          for ($i = 0, $count = count($match); $i < $count; $i++) {
1847              $replace[$orig_vals[$i]] = $match[$i];
1848          }
1849          return strtr($parenth_args, $replace);
1850      }
1851  
1852      /**
1853       * parse configuration variable expression into PHP code
1854       *
1855       * @param string $conf_var_expr
1856       */
1857      function _parse_conf_var($conf_var_expr)
1858      {
1859          $parts = explode('|', $conf_var_expr, 2);
1860          $var_ref = $parts[0];
1861          $modifiers = isset($parts[1]) ? $parts[1] : '';
1862  
1863          $var_name = substr($var_ref, 1, -1);
1864  
1865          $output = "\$this->_config[0]['vars']['$var_name']";
1866  
1867          $this->_parse_modifiers($output, $modifiers);
1868  
1869          return $output;
1870      }
1871  
1872      /**
1873       * parse section property expression into PHP code
1874       *
1875       * @param string $section_prop_expr
1876       * @return string
1877       */
1878      function _parse_section_prop($section_prop_expr)
1879      {
1880          $parts = explode('|', $section_prop_expr, 2);
1881          $var_ref = $parts[0];
1882          $modifiers = isset($parts[1]) ? $parts[1] : '';
1883  
1884          preg_match('!%(\w+)\.(\w+)%!', $var_ref, $match);
1885          $section_name = $match[1];
1886          $prop_name = $match[2];
1887  
1888          $output = "\$this->_sections['$section_name']['$prop_name']";
1889  
1890          $this->_parse_modifiers($output, $modifiers);
1891  
1892          return $output;
1893      }
1894  
1895  
1896      /**
1897       * parse modifier chain into PHP code
1898       *
1899       * sets $output to parsed modified chain
1900       * @param string $output
1901       * @param string $modifier_string
1902       */
1903      function _parse_modifiers(&$output, $modifier_string)
1904      {
1905          preg_match_all('~\|(@?\w+)((?>:(?:'. $this->_qstr_regexp . '|[^|]+))*)~', '|' . $modifier_string, $_match);
1906          list(, $_modifiers, $modifier_arg_strings) = $_match;
1907  
1908          for ($_i = 0, $_for_max = count($_modifiers); $_i < $_for_max; $_i++) {
1909              $_modifier_name = $_modifiers[$_i];
1910  
1911              if($_modifier_name == 'smarty') {
1912                  // skip smarty modifier
1913                  continue;
1914              }
1915  
1916              preg_match_all('~:(' . $this->_qstr_regexp . '|[^:]+)~', $modifier_arg_strings[$_i], $_match);
1917              $_modifier_args = $_match[1];
1918  
1919              if (substr($_modifier_name, 0, 1) == '@') {
1920                  $_map_array = false;
1921                  $_modifier_name = substr($_modifier_name, 1);
1922              } else {
1923                  $_map_array = true;
1924              }
1925  
1926              if (empty($this->_plugins['modifier'][$_modifier_name])
1927                  && !$this->_get_plugin_filepath('modifier', $_modifier_name)
1928                  && function_exists($_modifier_name)) {
1929                  if ($this->security && !in_array($_modifier_name, $this->security_settings['MODIFIER_FUNCS'])) {
1930                      $this->_trigger_fatal_error("[plugin] (secure mode) modifier '$_modifier_name' is not allowed" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
1931                  } else {
1932                      $this->_plugins['modifier'][$_modifier_name] = array($_modifier_name,  null, null, false);
1933                  }
1934              }
1935              $this->_add_plugin('modifier', $_modifier_name);
1936  
1937              $this->_parse_vars_props($_modifier_args);
1938  
1939              if($_modifier_name == 'default') {
1940                  // supress notifications of default modifier vars and args
1941                  if(substr($output, 0, 1) == '$') {
1942                      $output = '@' . $output;
1943                  }
1944                  if(isset($_modifier_args[0]) && substr($_modifier_args[0], 0, 1) == '$') {
1945                      $_modifier_args[0] = '@' . $_modifier_args[0];
1946                  }
1947              }
1948              if (count($_modifier_args) > 0)
1949                  $_modifier_args = ', '.implode(', ', $_modifier_args);
1950              else
1951                  $_modifier_args = '';
1952  
1953              if ($_map_array) {
1954                  $output = "((is_array(\$_tmp=$output)) ? \$this->_run_mod_handler('$_modifier_name', true, \$_tmp$_modifier_args) : " . $this->_compile_plugin_call('modifier', $_modifier_name) . "(\$_tmp$_modifier_args))";
1955  
1956              } else {
1957  
1958                  $output = $this->_compile_plugin_call('modifier', $_modifier_name)."($output$_modifier_args)";
1959  
1960              }
1961          }
1962      }
1963  
1964  
1965      /**
1966       * add plugin
1967       *
1968       * @param string $type
1969       * @param string $name
1970       * @param boolean? $delayed_loading
1971       */
1972      function _add_plugin($type, $name, $delayed_loading = null)
1973      {
1974          if (!isset($this->_plugin_info[$type])) {
1975              $this->_plugin_info[$type] = array();
1976          }
1977          if (!isset($this->_plugin_info[$type][$name])) {
1978              $this->_plugin_info[$type][$name] = array($this->_current_file,
1979                                                        $this->_current_line_no,
1980                                                        $delayed_loading);
1981          }
1982      }
1983  
1984  
1985      /**
1986       * Compiles references of type $smarty.foo
1987       *
1988       * @param string $indexes
1989       * @return string
1990       */
1991      function _compile_smarty_ref(&$indexes)
1992      {
1993          /* Extract the reference name. */
1994          $_ref = substr($indexes[0], 1);
1995          foreach($indexes as $_index_no=>$_index) {
1996              if (substr($_index, 0, 1) != '.' && $_index_no<2 || !preg_match('~^(\.|\[|->)~', $_index)) {
1997                  $this->_syntax_error('$smarty' . implode('', array_slice($indexes, 0, 2)) . ' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
1998              }
1999          }
2000  
2001          switch ($_ref) {
2002              case 'now':
2003                  $compiled_ref = 'time()';
2004                  $_max_index = 1;
2005                  break;
2006  
2007              case 'foreach':
2008                  array_shift($indexes);
2009                  $_var = $this->_parse_var_props(substr($indexes[0], 1));
2010                  $_propname = substr($indexes[1], 1);
2011                  $_max_index = 1;
2012                  switch ($_propname) {
2013                      case 'index':
2014                          array_shift($indexes);
2015                          $compiled_ref = "(\$this->_foreach[$_var]['iteration']-1)";
2016                          break;
2017                          
2018                      case 'first':
2019                          array_shift($indexes);
2020                          $compiled_ref = "(\$this->_foreach[$_var]['iteration'] <= 1)";
2021                          break;
2022  
2023                      case 'last':
2024                          array_shift($indexes);
2025                          $compiled_ref = "(\$this->_foreach[$_var]['iteration'] == \$this->_foreach[$_var]['total'])";
2026                          break;
2027                          
2028                      case 'show':
2029                          array_shift($indexes);
2030                          $compiled_ref = "(\$this->_foreach[$_var]['total'] > 0)";
2031                          break;
2032                          
2033                      default:
2034                          unset($_max_index);
2035                          $compiled_ref = "\$this->_foreach[$_var]";
2036                  }
2037                  break;
2038  
2039              case 'section':
2040                  array_shift($indexes);
2041                  $_var = $this->_parse_var_props(substr($indexes[0], 1));
2042                  $compiled_ref = "\$this->_sections[$_var]";
2043                  break;
2044  
2045              case 'get':
2046                  $compiled_ref = ($this->request_use_auto_globals) ? '$_GET' : "\$GLOBALS['HTTP_GET_VARS']";
2047                  break;
2048  
2049              case 'post':
2050                  $compiled_ref = ($this->request_use_auto_globals) ? '$_POST' : "\$GLOBALS['HTTP_POST_VARS']";
2051                  break;
2052  
2053              case 'cookies':
2054                  $compiled_ref = ($this->request_use_auto_globals) ? '$_COOKIE' : "\$GLOBALS['HTTP_COOKIE_VARS']";
2055                  break;
2056  
2057              case 'env':
2058                  $compiled_ref = ($this->request_use_auto_globals) ? '$_ENV' : "\$GLOBALS['HTTP_ENV_VARS']";
2059                  break;
2060  
2061              case 'server':
2062                  $compiled_ref = ($this->request_use_auto_globals) ? '$_SERVER' : "\$GLOBALS['HTTP_SERVER_VARS']";
2063                  break;
2064  
2065              case 'session':
2066                  $compiled_ref = ($this->request_use_auto_globals) ? '$_SESSION' : "\$GLOBALS['HTTP_SESSION_VARS']";
2067                  break;
2068  
2069              /*
2070               * These cases are handled either at run-time or elsewhere in the
2071               * compiler.
2072               */
2073              case 'request':
2074                  if ($this->request_use_auto_globals) {
2075                      $compiled_ref = '$_REQUEST';
2076                      break;
2077                  } else {
2078                      $this->_init_smarty_vars = true;
2079                  }
2080                  return null;
2081  
2082              case 'capture':
2083                  return null;
2084  
2085              case 'template':
2086                  $compiled_ref = "'$this->_current_file'";
2087                  $_max_index = 1;
2088                  break;
2089  
2090              case 'version':
2091                  $compiled_ref = "'$this->_version'";
2092                  $_max_index = 1;
2093                  break;
2094  
2095              case 'const':
2096                  if ($this->security && !$this->security_settings['ALLOW_CONSTANTS']) {
2097                      $this->_syntax_error("(secure mode) constants not permitted",
2098                                           E_USER_WARNING, __FILE__, __LINE__);
2099                      return;
2100                  }
2101                  array_shift($indexes);
2102                  if (preg_match('!^\.\w+$!', $indexes[0])) {
2103                      $compiled_ref = '@' . substr($indexes[0], 1);
2104                  } else {
2105                      $_val = $this->_parse_var_props(substr($indexes[0], 1));
2106                      $compiled_ref = '@constant(' . $_val . ')';
2107                  }
2108                  $_max_index = 1;
2109                  break;
2110  
2111              case 'config':
2112                  $compiled_ref = "\$this->_config[0]['vars']";
2113                  $_max_index = 3;
2114                  break;
2115  
2116              case 'ldelim':
2117                  $compiled_ref = "'$this->left_delimiter'";
2118                  break;
2119  
2120              case 'rdelim':
2121                  $compiled_ref = "'$this->right_delimiter'";
2122                  break;
2123                  
2124              default:
2125                  $this->_syntax_error('$smarty.' . $_ref . ' is an unknown reference', E_USER_ERROR, __FILE__, __LINE__);
2126                  break;
2127          }
2128  
2129          if (isset($_max_index) && count($indexes) > $_max_index) {
2130              $this->_syntax_error('$smarty' . implode('', $indexes) .' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__);
2131          }
2132  
2133          array_shift($indexes);
2134          return $compiled_ref;
2135      }
2136  
2137      /**
2138       * compiles call to plugin of type $type with name $name
2139       * returns a string containing the function-name or method call
2140       * without the paramter-list that would have follow to make the
2141       * call valid php-syntax
2142       *
2143       * @param string $type
2144       * @param string $name
2145       * @return string
2146       */
2147      function _compile_plugin_call($type, $name) {
2148          if (isset($this->_plugins[$type][$name])) {
2149              /* plugin loaded */
2150              if (is_array($this->_plugins[$type][$name][0])) {
2151                  return ((is_object($this->_plugins[$type][$name][0][0])) ?
2152                          "\$this->_plugins['$type']['$name'][0][0]->"    /* method callback */
2153                          : (string)($this->_plugins[$type][$name][0][0]).'::'    /* class callback */
2154                         ). $this->_plugins[$type][$name][0][1];
2155  
2156              } else {
2157                  /* function callback */
2158                  return $this->_plugins[$type][$name][0];
2159  
2160              }
2161          } else {
2162              /* plugin not loaded -> auto-loadable-plugin */
2163              return 'smarty_'.$type.'_'.$name;
2164  
2165          }
2166      }
2167  
2168      /**
2169       * load pre- and post-filters
2170       */
2171      function _load_filters()
2172      {
2173          if (count($this->_plugins['prefilter']) > 0) {
2174              foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
2175                  if ($prefilter === false) {
2176                      unset($this->_plugins['prefilter'][$filter_name]);
2177                      $_params = array('plugins' => array(array('prefilter', $filter_name, null, null, false)));
2178                      require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
2179                      smarty_core_load_plugins($_params, $this);
2180                  }
2181              }
2182          }
2183          if (count($this->_plugins['postfilter']) > 0) {
2184              foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
2185                  if ($postfilter === false) {
2186                      unset($this->_plugins['postfilter'][$filter_name]);
2187                      $_params = array('plugins' => array(array('postfilter', $filter_name, null, null, false)));
2188                      require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');
2189                      smarty_core_load_plugins($_params, $this);
2190                  }
2191              }
2192          }
2193      }
2194  
2195  
2196      /**
2197       * Quote subpattern references
2198       *
2199       * @param string $string
2200       * @return string
2201       */
2202      function _quote_replace($string)
2203      {
2204          return strtr($string, array('\\' => '\\\\', '$' => '\\$'));
2205      }
2206  
2207      /**
2208       * display Smarty syntax error
2209       *
2210       * @param string $error_msg
2211       * @param integer $error_type
2212       * @param string $file
2213       * @param integer $line
2214       */
2215      function _syntax_error($error_msg, $error_type = E_USER_ERROR, $file=null, $line=null)
2216      {
2217          $this->_trigger_fatal_error("syntax error: $error_msg", $this->_current_file, $this->_current_line_no, $file, $line, $error_type);
2218      }
2219  
2220  
2221      /**
2222       * check if the compilation changes from cacheable to
2223       * non-cacheable state with the beginning of the current
2224       * plugin. return php-code to reflect the transition.
2225       * @return string
2226       */
2227      function _push_cacheable_state($type, $name) {
2228          $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2229          if ($_cacheable
2230              || 0<$this->_cacheable_state++) return '';
2231          if (!isset($this->_cache_serial)) $this->_cache_serial = md5(uniqid('Smarty'));
2232          $_ret = 'if ($this->caching && !$this->_cache_including): echo \'{nocache:'
2233              . $this->_cache_serial . '#' . $this->_nocache_count
2234              . '}\'; endif;';
2235          return $_ret;
2236      }
2237  
2238  
2239      /**
2240       * check if the compilation changes from non-cacheable to
2241       * cacheable state with the end of the current plugin return
2242       * php-code to reflect the transition.
2243       * @return string
2244       */
2245      function _pop_cacheable_state($type, $name) {
2246          $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4];
2247          if ($_cacheable
2248              || --$this->_cacheable_state>0) return '';
2249          return 'if ($this->caching && !$this->_cache_including): echo \'{/nocache:'
2250              . $this->_cache_serial . '#' . ($this->_nocache_count++)
2251              . '}\'; endif;';
2252      }
2253  
2254  
2255      /**
2256       * push opening tag-name, file-name and line-number on the tag-stack
2257       * @param string the opening tag's name
2258       */
2259      function _push_tag($open_tag)
2260      {
2261          array_push($this->_tag_stack, array($open_tag, $this->_current_line_no));
2262      }
2263  
2264      /**
2265       * pop closing tag-name
2266       * raise an error if this stack-top doesn't match with the closing tag
2267       * @param string the closing tag's name
2268       * @return string the opening tag's name
2269       */
2270      function _pop_tag($close_tag)
2271      {
2272          $message = '';
2273          if (count($this->_tag_stack)>0) {
2274              list($_open_tag, $_line_no) = array_pop($this->_tag_stack);
2275              if ($close_tag == $_open_tag) {
2276                  return $_open_tag;
2277              }
2278              if ($close_tag == 'if' && ($_open_tag == 'else' || $_open_tag == 'elseif' )) {
2279                  return $this->_pop_tag($close_tag);
2280              }
2281              if ($close_tag == 'section' && $_open_tag == 'sectionelse') {
2282                  $this->_pop_tag($close_tag);
2283                  return $_open_tag;
2284              }
2285              if ($close_tag == 'foreach' && $_open_tag == 'foreachelse') {
2286                  $this->_pop_tag($close_tag);
2287                  return $_open_tag;
2288              }
2289              if ($_open_tag == 'else' || $_open_tag == 'elseif') {
2290                  $_open_tag = 'if';
2291              } elseif ($_open_tag == 'sectionelse') {
2292                  $_open_tag = 'section';
2293              } elseif ($_open_tag == 'foreachelse') {
2294                  $_open_tag = 'foreach';
2295              }
2296              $message = " expected {/$_open_tag} (opened line $_line_no).";
2297          }
2298          $this->_syntax_error("mismatched tag {/$close_tag}.$message",
2299                               E_USER_ERROR, __FILE__, __LINE__);
2300      }
2301  
2302  }
2303  
2304  /**
2305   * compare to values by their string length
2306   *
2307   * @access private
2308   * @param string $a
2309   * @param string $b
2310   * @return 0|-1|1
2311   */
2312  function _smarty_sort_length($a, $b)
2313  {
2314      if($a == $b)
2315          return 0;
2316  
2317      if(strlen($a) == strlen($b))
2318          return ($a > $b) ? -1 : 1;
2319  
2320      return (strlen($a) > strlen($b)) ? -1 : 1;
2321  }
2322  
2323  
2324  /* vim: set et: */
2325  
2326  ?>

title

Description

title

Description

title

Description

title

title

Body