PageRenderTime 59ms CodeModel.GetById 17ms app.highlight 34ms RepoModel.GetById 0ms app.codeStats 1ms

/framework/vendor/smarty2/lib/Smarty_Compiler.class.php

http://zoop.googlecode.com/
PHP | 1632 lines | 1105 code | 195 blank | 332 comment | 228 complexity | 348841a029ab7a7e5f51ca3b2cd3b3b1 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
   1<?php
   2
   3/**
   4 * Project:     Smarty: the PHP compiling template engine
   5 * File:        Smarty_Compiler.class.php
   6 *
   7 * This library is free software; you can redistribute it and/or
   8 * modify it under the terms of the GNU Lesser General Public
   9 * License as published by the Free Software Foundation; either
  10 * version 2.1 of the License, or (at your option) any later version.
  11 *
  12 * This library is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15 * Lesser General Public License for more details.
  16 *
  17 * You should have received a copy of the GNU Lesser General Public
  18 * License along with this library; if not, write to the Free Software
  19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20 *
  21 * @link http://smarty.php.net/
  22 * @author Monte Ohrt <monte at ohrt dot com>
  23 * @author Andrei Zmievski <andrei@php.net>
  24 * @version 2.6.14
  25 * @copyright 2001-2005 New Digital Group, Inc.
  26 * @package Smarty
  27 */
  28
  29/* $Id: Smarty_Compiler.class.php,v 1.381 2006/05/25 14:46:18 boots Exp $ */
  30
  31/**
  32 * Template compiling class
  33 * @package Smarty
  34 */
  35class Smarty_Compiler extends Smarty {
  36
  37    // internal vars
  38    /**#@+
  39     * @access private
  40     */
  41    var $_folded_blocks         =   array();    // keeps folded template blocks
  42    var $_current_file          =   null;       // the current template being compiled
  43    var $_current_line_no       =   1;          // line number for error messages
  44    var $_capture_stack         =   array();    // keeps track of nested capture buffers
  45    var $_plugin_info           =   array();    // keeps track of plugins to load
  46    var $_init_smarty_vars      =   false;
  47    var $_permitted_tokens      =   array('true','false','yes','no','on','off','null');
  48    var $_db_qstr_regexp        =   null;        // regexps are setup in the constructor
  49    var $_si_qstr_regexp        =   null;
  50    var $_qstr_regexp           =   null;
  51    var $_func_regexp           =   null;
  52    var $_reg_obj_regexp        =   null;
  53    var $_var_bracket_regexp    =   null;
  54    var $_num_const_regexp      =   null;
  55    var $_dvar_guts_regexp      =   null;
  56    var $_dvar_regexp           =   null;
  57    var $_cvar_regexp           =   null;
  58    var $_svar_regexp           =   null;
  59    var $_avar_regexp           =   null;
  60    var $_mod_regexp            =   null;
  61    var $_var_regexp            =   null;
  62    var $_parenth_param_regexp  =   null;
  63    var $_func_call_regexp      =   null;
  64    var $_obj_ext_regexp        =   null;
  65    var $_obj_start_regexp      =   null;
  66    var $_obj_params_regexp     =   null;
  67    var $_obj_call_regexp       =   null;
  68    var $_cacheable_state       =   0;
  69    var $_cache_attrs_count     =   0;
  70    var $_nocache_count         =   0;
  71    var $_cache_serial          =   null;
  72    var $_cache_include         =   null;
  73
  74    var $_strip_depth           =   0;
  75    var $_additional_newline    =   "\n";
  76
  77    /**#@-*/
  78    /**
  79     * The class constructor.
  80     */
  81    function Smarty_Compiler()
  82    {
  83        // matches double quoted strings:
  84        // "foobar"
  85        // "foo\"bar"
  86        $this->_db_qstr_regexp = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
  87
  88        // matches single quoted strings:
  89        // 'foobar'
  90        // 'foo\'bar'
  91        $this->_si_qstr_regexp = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
  92
  93        // matches single or double quoted strings
  94        $this->_qstr_regexp = '(?:' . $this->_db_qstr_regexp . '|' . $this->_si_qstr_regexp . ')';
  95
  96        // matches bracket portion of vars
  97        // [0]
  98        // [foo]
  99        // [$bar]
 100        $this->_var_bracket_regexp = '\[\$?[\w\.]+\]';
 101
 102        // matches numerical constants
 103        // 30
 104        // -12
 105        // 13.22
 106        $this->_num_const_regexp = '(?:\-?\d+(?:\.\d+)?)';
 107
 108        // matches $ vars (not objects):
 109        // $foo
 110        // $foo.bar
 111        // $foo.bar.foobar
 112        // $foo[0]
 113        // $foo[$bar]
 114        // $foo[5][blah]
 115        // $foo[5].bar[$foobar][4]
 116        $this->_dvar_math_regexp = '(?:[\+\*\/\%]|(?:-(?!>)))';
 117        $this->_dvar_math_var_regexp = '[\$\w\.\+\-\*\/\%\d\>\[\]]';
 118        $this->_dvar_guts_regexp = '\w+(?:' . $this->_var_bracket_regexp
 119                . ')*(?:\.\$?\w+(?:' . $this->_var_bracket_regexp . ')*)*(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?';
 120        $this->_dvar_regexp = '\$' . $this->_dvar_guts_regexp;
 121
 122        // matches config vars:
 123        // #foo#
 124        // #foobar123_foo#
 125        $this->_cvar_regexp = '\#\w+\#';
 126
 127        // matches section vars:
 128        // %foo.bar%
 129        $this->_svar_regexp = '\%\w+\.\w+\%';
 130
 131        // matches all valid variables (no quotes, no modifiers)
 132        $this->_avar_regexp = '(?:' . $this->_dvar_regexp . '|'
 133           . $this->_cvar_regexp . '|' . $this->_svar_regexp . ')';
 134
 135        // matches valid variable syntax:
 136        // $foo
 137        // $foo
 138        // #foo#
 139        // #foo#
 140        // "text"
 141        // "text"
 142        $this->_var_regexp = '(?:' . $this->_avar_regexp . '|' . $this->_qstr_regexp . ')';
 143
 144        // matches valid object call (one level of object nesting allowed in parameters):
 145        // $foo->bar
 146        // $foo->bar()
 147        // $foo->bar("text")
 148        // $foo->bar($foo, $bar, "text")
 149        // $foo->bar($foo, "foo")
 150        // $foo->bar->foo()
 151        // $foo->bar->foo->bar()
 152        // $foo->bar($foo->bar)
 153        // $foo->bar($foo->bar())
 154        // $foo->bar($foo->bar($blah,$foo,44,"foo",$foo[0].bar))
 155        $this->_obj_ext_regexp = '\->(?:\$?' . $this->_dvar_guts_regexp . ')';
 156        $this->_obj_restricted_param_regexp = '(?:'
 157                . '(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')(?:' . $this->_obj_ext_regexp . '(?:\((?:(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')'
 158                . '(?:\s*,\s*(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . '))*)?\))?)*)';
 159        $this->_obj_single_param_regexp = '(?:\w+|' . $this->_obj_restricted_param_regexp . '(?:\s*,\s*(?:(?:\w+|'
 160                . $this->_var_regexp . $this->_obj_restricted_param_regexp . ')))*)';
 161        $this->_obj_params_regexp = '\((?:' . $this->_obj_single_param_regexp
 162                . '(?:\s*,\s*' . $this->_obj_single_param_regexp . ')*)?\)';
 163        $this->_obj_start_regexp = '(?:' . $this->_dvar_regexp . '(?:' . $this->_obj_ext_regexp . ')+)';
 164        $this->_obj_call_regexp = '(?:' . $this->_obj_start_regexp . '(?:' . $this->_obj_params_regexp . ')?(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?)';
 165        
 166        // matches valid modifier syntax:
 167        // |foo
 168        // |@foo
 169        // |foo:"bar"
 170        // |foo:$bar
 171        // |foo:"bar":$foobar
 172        // |foo|bar
 173        // |foo:$foo->bar
 174        $this->_mod_regexp = '(?:\|@?\w+(?::(?:\w+|' . $this->_num_const_regexp . '|'
 175           . $this->_obj_call_regexp . '|' . $this->_avar_regexp . '|' . $this->_qstr_regexp .'))*)';
 176
 177        // matches valid function name:
 178        // foo123
 179        // _foo_bar
 180        $this->_func_regexp = '[a-zA-Z_]\w*';
 181
 182        // matches valid registered object:
 183        // foo->bar
 184        $this->_reg_obj_regexp = '[a-zA-Z_]\w*->[a-zA-Z_]\w*';
 185
 186        // matches valid parameter values:
 187        // true
 188        // $foo
 189        // $foo|bar
 190        // #foo#
 191        // #foo#|bar
 192        // "text"
 193        // "text"|bar
 194        // $foo->bar
 195        $this->_param_regexp = '(?:\s*(?:' . $this->_obj_call_regexp . '|'
 196           . $this->_var_regexp . '|' . $this->_num_const_regexp  . '|\w+)(?>' . $this->_mod_regexp . '*)\s*)';
 197
 198        // matches valid parenthesised function parameters:
 199        //
 200        // "text"
 201        //    $foo, $bar, "text"
 202        // $foo|bar, "foo"|bar, $foo->bar($foo)|bar
 203        $this->_parenth_param_regexp = '(?:\((?:\w+|'
 204                . $this->_param_regexp . '(?:\s*,\s*(?:(?:\w+|'
 205                . $this->_param_regexp . ')))*)?\))';
 206
 207        // matches valid function call:
 208        // foo()
 209        // foo_bar($foo)
 210        // _foo_bar($foo,"bar")
 211        // foo123($foo,$foo->bar(),"foo")
 212        $this->_func_call_regexp = '(?:' . $this->_func_regexp . '\s*(?:'
 213           . $this->_parenth_param_regexp . '))';
 214    }
 215
 216    /**
 217     * compile a resource
 218     *
 219     * sets $compiled_content to the compiled source
 220     * @param string $resource_name
 221     * @param string $source_content
 222     * @param string $compiled_content
 223     * @return true
 224     */
 225    function _compile_file($resource_name, $source_content, &$compiled_content)
 226    {
 227
 228        if ($this->security) {
 229            // do not allow php syntax to be executed unless specified
 230            if ($this->php_handling == SMARTY_PHP_ALLOW &&
 231                !$this->security_settings['PHP_HANDLING']) {
 232                $this->php_handling = SMARTY_PHP_PASSTHRU;
 233            }
 234        }
 235
 236        $this->_load_filters();
 237
 238        $this->_current_file = $resource_name;
 239        $this->_current_line_no = 1;
 240        $ldq = preg_quote($this->left_delimiter, '~');
 241        $rdq = preg_quote($this->right_delimiter, '~');
 242
 243        /* un-hide hidden xml open tags  */
 244        $source_content = preg_replace("~<({$ldq}(.*?){$rdq})[?]~s", '< \\1', $source_content);
 245
 246        // run template source through prefilter functions
 247        if (count($this->_plugins['prefilter']) > 0) {
 248            foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) {
 249                if ($prefilter === false) continue;
 250                if ($prefilter[3] || is_callable($prefilter[0])) {
 251                    $source_content = call_user_func_array($prefilter[0],
 252                                                            array($source_content, &$this));
 253                    $this->_plugins['prefilter'][$filter_name][3] = true;
 254                } else {
 255                    $this->_trigger_fatal_error("[plugin] prefilter '$filter_name' is not implemented");
 256                }
 257            }
 258        }
 259
 260        /* fetch all special blocks */
 261        $search = "~{$ldq}\*(.*?)\*{$rdq}|{$ldq}\s*literal\s*{$rdq}(.*?){$ldq}\s*/literal\s*{$rdq}|{$ldq}\s*php\s*{$rdq}(.*?){$ldq}\s*/php\s*{$rdq}~s";
 262
 263        preg_match_all($search, $source_content, $match,  PREG_SET_ORDER);
 264        $this->_folded_blocks = $match;
 265        reset($this->_folded_blocks);
 266
 267        /* replace special blocks by "{php}" */
 268        $source_content = preg_replace($search.'e', "'"
 269                                       . $this->_quote_replace($this->left_delimiter) . 'php'
 270                                       . "' . str_repeat(\"\n\", substr_count('\\0', \"\n\")) .'"
 271                                       . $this->_quote_replace($this->right_delimiter)
 272                                       . "'"
 273                                       , $source_content);
 274
 275        /* Gather all template tags. */
 276        preg_match_all("~{$ldq}\s*(.*?)\s*{$rdq}~s", $source_content, $_match);
 277        $template_tags = $_match[1];
 278        /* Split content by template tags to obtain non-template content. */
 279        $text_blocks = preg_split("~{$ldq}.*?{$rdq}~s", $source_content);
 280
 281        /* loop through text blocks */
 282        for ($curr_tb = 0, $for_max = count($text_blocks); $curr_tb < $for_max; $curr_tb++) {
 283            /* match anything resembling php tags */
 284            if (preg_match_all('~(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?php[\"\']?)~is', $text_blocks[$curr_tb], $sp_match)) {
 285                /* replace tags with placeholders to prevent recursive replacements */
 286                $sp_match[1] = array_unique($sp_match[1]);
 287                usort($sp_match[1], '_smarty_sort_length');
 288                for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) {
 289                    $text_blocks[$curr_tb] = str_replace($sp_match[1][$curr_sp],'%%%SMARTYSP'.$curr_sp.'%%%',$text_blocks[$curr_tb]);
 290                }
 291                /* process each one */
 292                for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) {
 293                    if ($this->php_handling == SMARTY_PHP_PASSTHRU) {
 294                        /* echo php contents */
 295                        $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '<?php echo \''.str_replace("'", "\'", $sp_match[1][$curr_sp]).'\'; ?>'."\n", $text_blocks[$curr_tb]);
 296                    } else if ($this->php_handling == SMARTY_PHP_QUOTE) {
 297                        /* quote php tags */
 298                        $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', htmlspecialchars($sp_match[1][$curr_sp]), $text_blocks[$curr_tb]);
 299                    } else if ($this->php_handling == SMARTY_PHP_REMOVE) {
 300                        /* remove php tags */
 301                        $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '', $text_blocks[$curr_tb]);
 302                    } else {
 303                        /* SMARTY_PHP_ALLOW, but echo non php starting tags */
 304                        $sp_match[1][$curr_sp] = preg_replace('~(<\?(?!php|=|$))~i', '<?php echo \'\\1\'?>'."\n", $sp_match[1][$curr_sp]);
 305                        $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', $sp_match[1][$curr_sp], $text_blocks[$curr_tb]);
 306                    }
 307                }
 308            }
 309        }
 310
 311        /* Compile the template tags into PHP code. */
 312        $compiled_tags = array();
 313        for ($i = 0, $for_max = count($template_tags); $i < $for_max; $i++) {
 314            $this->_current_line_no += substr_count($text_blocks[$i], "\n");
 315            $compiled_tags[] = $this->_compile_tag($template_tags[$i]);
 316            $this->_current_line_no += substr_count($template_tags[$i], "\n");
 317        }
 318        if (count($this->_tag_stack)>0) {
 319            list($_open_tag, $_line_no) = end($this->_tag_stack);
 320            $this->_syntax_error("unclosed tag \{$_open_tag} (opened line $_line_no).", E_USER_ERROR, __FILE__, __LINE__);
 321            return;
 322        }
 323
 324        /* Reformat $text_blocks between 'strip' and '/strip' tags,
 325           removing spaces, tabs and newlines. */
 326        $strip = false;
 327        for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) {
 328            if ($compiled_tags[$i] == '{strip}') {
 329                $compiled_tags[$i] = '';
 330                $strip = true;
 331                /* remove leading whitespaces */
 332                $text_blocks[$i + 1] = ltrim($text_blocks[$i + 1]);
 333            }
 334            if ($strip) {
 335                /* strip all $text_blocks before the next '/strip' */
 336                for ($j = $i + 1; $j < $for_max; $j++) {
 337                    /* remove leading and trailing whitespaces of each line */
 338                    $text_blocks[$j] = preg_replace('![\t ]*[\r\n]+[\t ]*!', '', $text_blocks[$j]);
 339                    if ($compiled_tags[$j] == '{/strip}') {                       
 340                        /* remove trailing whitespaces from the last text_block */
 341                        $text_blocks[$j] = rtrim($text_blocks[$j]);
 342                    }
 343                    $text_blocks[$j] = "<?php echo '" . strtr($text_blocks[$j], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>";
 344                    if ($compiled_tags[$j] == '{/strip}') {
 345                        $compiled_tags[$j] = "\n"; /* slurped by php, but necessary
 346                                    if a newline is following the closing strip-tag */
 347                        $strip = false;
 348                        $i = $j;
 349                        break;
 350                    }
 351                }
 352            }
 353        }
 354        $compiled_content = '';
 355
 356        /* Interleave the compiled contents and text blocks to get the final result. */
 357        for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) {
 358            if ($compiled_tags[$i] == '') {
 359                // tag result empty, remove first newline from following text block
 360                $text_blocks[$i+1] = preg_replace('~^(\r\n|\r|\n)~', '', $text_blocks[$i+1]);
 361            }
 362            $compiled_content .= $text_blocks[$i].$compiled_tags[$i];
 363        }
 364        $compiled_content .= $text_blocks[$i];
 365
 366        // remove \n from the end of the file, if any
 367        if (strlen($compiled_content) && (substr($compiled_content, -1) == "\n") ) {
 368            $compiled_content = substr($compiled_content, 0, -1);
 369        }
 370
 371        if (!empty($this->_cache_serial)) {
 372            $compiled_content = "<?php \$this->_cache_serials['".$this->_cache_include."'] = '".$this->_cache_serial."'; ?>" . $compiled_content;
 373        }
 374
 375        // remove unnecessary close/open tags
 376        $compiled_content = preg_replace('~\?>\n?<\?php~', '', $compiled_content);
 377
 378        // run compiled template through postfilter functions
 379        if (count($this->_plugins['postfilter']) > 0) {
 380            foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) {
 381                if ($postfilter === false) continue;
 382                if ($postfilter[3] || is_callable($postfilter[0])) {
 383                    $compiled_content = call_user_func_array($postfilter[0],
 384                                                              array($compiled_content, &$this));
 385                    $this->_plugins['postfilter'][$filter_name][3] = true;
 386                } else {
 387                    $this->_trigger_fatal_error("Smarty plugin error: postfilter '$filter_name' is not implemented");
 388                }
 389            }
 390        }
 391
 392        // put header at the top of the compiled template
 393        $template_header = "<?php /* Smarty version ".$this->_version.", created on ".strftime("%Y-%m-%d %H:%M:%S")."\n";
 394        $template_header .= "         compiled from ".strtr(urlencode($resource_name), array('%2F'=>'/', '%3A'=>':'))." */ ?>\n";
 395
 396        /* Emit code to load needed plugins. */
 397        $this->_plugins_code = '';
 398        if (count($this->_plugin_info)) {
 399            $_plugins_params = "array('plugins' => array(";
 400            foreach ($this->_plugin_info as $plugin_type => $plugins) {
 401                foreach ($plugins as $plugin_name => $plugin_info) {
 402                    $_plugins_params .= "array('$plugin_type', '$plugin_name', '" . strtr($plugin_info[0], array("'" => "\\'", "\\" => "\\\\")) . "', $plugin_info[1], ";
 403                    $_plugins_params .= $plugin_info[2] ? 'true),' : 'false),';
 404                }
 405            }
 406            $_plugins_params .= '))';
 407            $plugins_code = "<?php require_once(SMARTY_CORE_DIR . 'core.load_plugins.php');\nsmarty_core_load_plugins($_plugins_params, \$this); ?>\n";
 408            $template_header .= $plugins_code;
 409            $this->_plugin_info = array();
 410            $this->_plugins_code = $plugins_code;
 411        }
 412
 413        if ($this->_init_smarty_vars) {
 414            $template_header .= "<?php require_once(SMARTY_CORE_DIR . 'core.assign_smarty_interface.php');\nsmarty_core_assign_smarty_interface(null, \$this); ?>\n";
 415            $this->_init_smarty_vars = false;
 416        }
 417
 418        $compiled_content = $template_header . $compiled_content;
 419        return true;
 420    }
 421
 422    /**
 423     * Compile a template tag
 424     *
 425     * @param string $template_tag
 426     * @return string
 427     */
 428    function _compile_tag($template_tag)
 429    {
 430        /* Matched comment. */
 431        if (substr($template_tag, 0, 1) == '*' && substr($template_tag, -1) == '*')
 432            return '';
 433        
 434        /* Split tag into two three parts: command, command modifiers and the arguments. */
 435        if(! preg_match('~^(?:(' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp
 436                . '|\/?' . $this->_reg_obj_regexp . '|\/?' . $this->_func_regexp . ')(' . $this->_mod_regexp . '*))
 437                      (?:\s+(.*))?$
 438                    ~xs', $template_tag, $match)) {
 439            $this->_syntax_error("unrecognized tag: $template_tag", E_USER_ERROR, __FILE__, __LINE__);
 440        }
 441        
 442        $tag_command = $match[1];
 443        $tag_modifier = isset($match[2]) ? $match[2] : null;
 444        $tag_args = isset($match[3]) ? $match[3] : null;
 445
 446        if (preg_match('~^' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '$~', $tag_command)) {
 447            /* tag name is a variable or object */
 448            $_return = $this->_parse_var_props($tag_command . $tag_modifier);
 449            return "<?php echo $_return; ?>" . $this->_additional_newline;
 450        }
 451
 452        /* If the tag name is a registered object, we process it. */
 453        if (preg_match('~^\/?' . $this->_reg_obj_regexp . '$~', $tag_command)) {
 454            return $this->_compile_registered_object_tag($tag_command, $this->_parse_attrs($tag_args), $tag_modifier);
 455        }
 456
 457        switch ($tag_command) {
 458            case 'include':
 459                return $this->_compile_include_tag($tag_args);
 460
 461            case 'include_php':
 462                return $this->_compile_include_php_tag($tag_args);
 463
 464            case 'if':
 465                $this->_push_tag('if');
 466                return $this->_compile_if_tag($tag_args);
 467
 468            case 'else':
 469                list($_open_tag) = end($this->_tag_stack);
 470                if ($_open_tag != 'if' && $_open_tag != 'elseif')
 471                    $this->_syntax_error('unexpected {else}', E_USER_ERROR, __FILE__, __LINE__);
 472                else
 473                    $this->_push_tag('else');
 474                return '<?php else: ?>';
 475
 476            case 'elseif':
 477                list($_open_tag) = end($this->_tag_stack);
 478                if ($_open_tag != 'if' && $_open_tag != 'elseif')
 479                    $this->_syntax_error('unexpected {elseif}', E_USER_ERROR, __FILE__, __LINE__);
 480                if ($_open_tag == 'if')
 481                    $this->_push_tag('elseif');
 482                return $this->_compile_if_tag($tag_args, true);
 483
 484            case '/if':
 485                $this->_pop_tag('if');
 486                return '<?php endif; ?>';
 487
 488            case 'capture':
 489                return $this->_compile_capture_tag(true, $tag_args);
 490
 491            case '/capture':
 492                return $this->_compile_capture_tag(false);
 493
 494            case 'ldelim':
 495                return $this->left_delimiter;
 496
 497            case 'rdelim':
 498                return $this->right_delimiter;
 499
 500            case 'section':
 501                $this->_push_tag('section');
 502                return $this->_compile_section_start($tag_args);
 503
 504            case 'sectionelse':
 505                $this->_push_tag('sectionelse');
 506                return "<?php endfor; else: ?>";
 507                break;
 508
 509            case '/section':
 510                $_open_tag = $this->_pop_tag('section');
 511                if ($_open_tag == 'sectionelse')
 512                    return "<?php endif; ?>";
 513                else
 514                    return "<?php endfor; endif; ?>";
 515
 516            case 'foreach':
 517                $this->_push_tag('foreach');
 518                return $this->_compile_foreach_start($tag_args);
 519                break;
 520
 521            case 'foreachelse':
 522                $this->_push_tag('foreachelse');
 523                return "<?php endforeach; else: ?>";
 524
 525            case '/foreach':
 526                $_open_tag = $this->_pop_tag('foreach');
 527                if ($_open_tag == 'foreachelse')
 528                    return "<?php endif; unset(\$_from); ?>";
 529                else
 530                    return "<?php endforeach; endif; unset(\$_from); ?>";
 531                break;
 532
 533            case 'strip':
 534            case '/strip':
 535                if (substr($tag_command, 0, 1)=='/') {
 536                    $this->_pop_tag('strip');
 537                    if (--$this->_strip_depth==0) { /* outermost closing {/strip} */
 538                        $this->_additional_newline = "\n";
 539                        return '{' . $tag_command . '}';
 540                    }
 541                } else {
 542                    $this->_push_tag('strip');
 543                    if ($this->_strip_depth++==0) { /* outermost opening {strip} */
 544                        $this->_additional_newline = "";
 545                        return '{' . $tag_command . '}';
 546                    }
 547                }
 548                return '';
 549
 550            case 'php':
 551                /* handle folded tags replaced by {php} */
 552                list(, $block) = each($this->_folded_blocks);
 553                $this->_current_line_no += substr_count($block[0], "\n");
 554                /* the number of matched elements in the regexp in _compile_file()
 555                   determins the type of folded tag that was found */
 556                switch (count($block)) {
 557                    case 2: /* comment */
 558                        return '';
 559
 560                    case 3: /* literal */
 561                        return "<?php echo '" . strtr($block[2], array("'"=>"\'", "\\"=>"\\\\")) . "'; ?>" . $this->_additional_newline;
 562
 563                    case 4: /* php */
 564                        if ($this->security && !$this->security_settings['PHP_TAGS']) {
 565                            $this->_syntax_error("(secure mode) php tags not permitted", E_USER_WARNING, __FILE__, __LINE__);
 566                            return;
 567                        }
 568                        return '<?php ' . $block[3] .' ?>';
 569                }
 570                break;
 571
 572            case 'insert':
 573                return $this->_compile_insert_tag($tag_args);
 574
 575            default:
 576                if ($this->_compile_compiler_tag($tag_command, $tag_args, $output)) {
 577                    return $output;
 578                } else if ($this->_compile_block_tag($tag_command, $tag_args, $tag_modifier, $output)) {
 579                    return $output;
 580                } else if ($this->_compile_custom_tag($tag_command, $tag_args, $tag_modifier, $output)) {
 581                    return $output;                    
 582                } else {
 583                    $this->_syntax_error("unrecognized tag '$tag_command'", E_USER_ERROR, __FILE__, __LINE__);
 584                }
 585
 586        }
 587    }
 588
 589
 590    /**
 591     * compile the custom compiler tag
 592     *
 593     * sets $output to the compiled custom compiler tag
 594     * @param string $tag_command
 595     * @param string $tag_args
 596     * @param string $output
 597     * @return boolean
 598     */
 599    function _compile_compiler_tag($tag_command, $tag_args, &$output)
 600    {
 601        $found = false;
 602        $have_function = true;
 603
 604        /*
 605         * First we check if the compiler function has already been registered
 606         * or loaded from a plugin file.
 607         */
 608        if (isset($this->_plugins['compiler'][$tag_command])) {
 609            $found = true;
 610            $plugin_func = $this->_plugins['compiler'][$tag_command][0];
 611            if (!is_callable($plugin_func)) {
 612                $message = "compiler function '$tag_command' is not implemented";
 613                $have_function = false;
 614            }
 615        }
 616        /*
 617         * Otherwise we need to load plugin file and look for the function
 618         * inside it.
 619         */
 620        else if ($plugin_file = $this->_get_plugin_filepath('compiler', $tag_command)) {
 621            $found = true;
 622
 623            include_once $plugin_file;
 624
 625            $plugin_func = 'smarty_compiler_' . $tag_command;
 626            if (!is_callable($plugin_func)) {
 627                $message = "plugin function $plugin_func() not found in $plugin_file\n";
 628                $have_function = false;
 629            } else {
 630                $this->_plugins['compiler'][$tag_command] = array($plugin_func, null, null, null, true);
 631            }
 632        }
 633
 634        /*
 635         * True return value means that we either found a plugin or a
 636         * dynamically registered function. False means that we didn't and the
 637         * compiler should now emit code to load custom function plugin for this
 638         * tag.
 639         */
 640        if ($found) {
 641            if ($have_function) {
 642                $output = call_user_func_array($plugin_func, array($tag_args, &$this));
 643                if($output != '') {
 644                $output = '<?php ' . $this->_push_cacheable_state('compiler', $tag_command)
 645                                   . $output
 646                                   . $this->_pop_cacheable_state('compiler', $tag_command) . ' ?>';
 647                }
 648            } else {
 649                $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
 650            }
 651            return true;
 652        } else {
 653            return false;
 654        }
 655    }
 656
 657
 658    /**
 659     * compile block function tag
 660     *
 661     * sets $output to compiled block function tag
 662     * @param string $tag_command
 663     * @param string $tag_args
 664     * @param string $tag_modifier
 665     * @param string $output
 666     * @return boolean
 667     */
 668    function _compile_block_tag($tag_command, $tag_args, $tag_modifier, &$output)
 669    {
 670        if (substr($tag_command, 0, 1) == '/') {
 671            $start_tag = false;
 672            $tag_command = substr($tag_command, 1);
 673        } else
 674            $start_tag = true;
 675
 676        $found = false;
 677        $have_function = true;
 678
 679        /*
 680         * First we check if the block function has already been registered
 681         * or loaded from a plugin file.
 682         */
 683        if (isset($this->_plugins['block'][$tag_command])) {
 684            $found = true;
 685            $plugin_func = $this->_plugins['block'][$tag_command][0];
 686            if (!is_callable($plugin_func)) {
 687                $message = "block function '$tag_command' is not implemented";
 688                $have_function = false;
 689            }
 690        }
 691        /*
 692         * Otherwise we need to load plugin file and look for the function
 693         * inside it.
 694         */
 695        else if ($plugin_file = $this->_get_plugin_filepath('block', $tag_command)) {
 696            $found = true;
 697
 698            include_once $plugin_file;
 699
 700            $plugin_func = 'smarty_block_' . $tag_command;
 701            if (!function_exists($plugin_func)) {
 702                $message = "plugin function $plugin_func() not found in $plugin_file\n";
 703                $have_function = false;
 704            } else {
 705                $this->_plugins['block'][$tag_command] = array($plugin_func, null, null, null, true);
 706
 707            }
 708        }
 709
 710        if (!$found) {
 711            return false;
 712        } else if (!$have_function) {
 713            $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
 714            return true;
 715        }
 716
 717        /*
 718         * Even though we've located the plugin function, compilation
 719         * happens only once, so the plugin will still need to be loaded
 720         * at runtime for future requests.
 721         */
 722        $this->_add_plugin('block', $tag_command);
 723
 724        if ($start_tag)
 725            $this->_push_tag($tag_command);
 726        else
 727            $this->_pop_tag($tag_command);
 728
 729        if ($start_tag) {
 730            $output = '<?php ' . $this->_push_cacheable_state('block', $tag_command);
 731            $attrs = $this->_parse_attrs($tag_args);
 732            $_cache_attrs='';
 733            $arg_list = $this->_compile_arg_list('block', $tag_command, $attrs, $_cache_attrs);
 734            $output .= "$_cache_attrs\$this->_tag_stack[] = array('$tag_command', array(".implode(',', $arg_list).')); ';
 735            $output .= '$_block_repeat=true;' . $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], null, $this, $_block_repeat);';
 736            $output .= 'while ($_block_repeat) { ob_start(); ?>';
 737        } else {
 738            $output = '<?php $_block_content = ob_get_contents(); ob_end_clean(); ';
 739            $_out_tag_text = $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], $_block_content, $this, $_block_repeat)';
 740            if ($tag_modifier != '') {
 741                $this->_parse_modifiers($_out_tag_text, $tag_modifier);
 742            }
 743            $output .= '$_block_repeat=false;echo ' . $_out_tag_text . '; } ';
 744            $output .= " array_pop(\$this->_tag_stack); " . $this->_pop_cacheable_state('block', $tag_command) . '?>';
 745        }
 746
 747        return true;
 748    }
 749
 750
 751    /**
 752     * compile custom function tag
 753     *
 754     * @param string $tag_command
 755     * @param string $tag_args
 756     * @param string $tag_modifier
 757     * @return string
 758     */
 759    function _compile_custom_tag($tag_command, $tag_args, $tag_modifier, &$output)
 760    {
 761        $found = false;
 762        $have_function = true;
 763
 764        /*
 765         * First we check if the custom function has already been registered
 766         * or loaded from a plugin file.
 767         */
 768        if (isset($this->_plugins['function'][$tag_command])) {
 769            $found = true;
 770            $plugin_func = $this->_plugins['function'][$tag_command][0];
 771            if (!is_callable($plugin_func)) {
 772                $message = "custom function '$tag_command' is not implemented";
 773                $have_function = false;
 774            }
 775        }
 776        /*
 777         * Otherwise we need to load plugin file and look for the function
 778         * inside it.
 779         */
 780        else if ($plugin_file = $this->_get_plugin_filepath('function', $tag_command)) {
 781            $found = true;
 782
 783            include_once $plugin_file;
 784
 785            $plugin_func = 'smarty_function_' . $tag_command;
 786            if (!function_exists($plugin_func)) {
 787                $message = "plugin function $plugin_func() not found in $plugin_file\n";
 788                $have_function = false;
 789            } else {
 790                $this->_plugins['function'][$tag_command] = array($plugin_func, null, null, null, true);
 791
 792            }
 793        }
 794
 795        if (!$found) {
 796            return false;
 797        } else if (!$have_function) {
 798            $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__);
 799            return true;
 800        }
 801
 802        /* declare plugin to be loaded on display of the template that
 803           we compile right now */
 804        $this->_add_plugin('function', $tag_command);
 805
 806        $_cacheable_state = $this->_push_cacheable_state('function', $tag_command);
 807        $attrs = $this->_parse_attrs($tag_args);
 808        $_cache_attrs = '';
 809        $arg_list = $this->_compile_arg_list('function', $tag_command, $attrs, $_cache_attrs);
 810
 811        $output = $this->_compile_plugin_call('function', $tag_command).'(array('.implode(',', $arg_list)."), \$this)";
 812        if($tag_modifier != '') {
 813            $this->_parse_modifiers($output, $tag_modifier);
 814        }
 815
 816        if($output != '') {
 817            $output =  '<?php ' . $_cacheable_state . $_cache_attrs . 'echo ' . $output . ';'
 818                . $this->_pop_cacheable_state('function', $tag_command) . "?>" . $this->_additional_newline;
 819        }
 820
 821        return true;
 822    }
 823
 824    /**
 825     * compile a registered object tag
 826     *
 827     * @param string $tag_command
 828     * @param array $attrs
 829     * @param string $tag_modifier
 830     * @return string
 831     */
 832    function _compile_registered_object_tag($tag_command, $attrs, $tag_modifier)
 833    {
 834        if (substr($tag_command, 0, 1) == '/') {
 835            $start_tag = false;
 836            $tag_command = substr($tag_command, 1);
 837        } else {
 838            $start_tag = true;
 839        }
 840
 841        list($object, $obj_comp) = explode('->', $tag_command);
 842
 843        $arg_list = array();
 844        if(count($attrs)) {
 845            $_assign_var = false;
 846            foreach ($attrs as $arg_name => $arg_value) {
 847                if($arg_name == 'assign') {
 848                    $_assign_var = $arg_value;
 849                    unset($attrs['assign']);
 850                    continue;
 851                }
 852                if (is_bool($arg_value))
 853                    $arg_value = $arg_value ? 'true' : 'false';
 854                $arg_list[] = "'$arg_name' => $arg_value";
 855            }
 856        }
 857
 858        if($this->_reg_objects[$object][2]) {
 859            // smarty object argument format
 860            $args = "array(".implode(',', (array)$arg_list)."), \$this";
 861        } else {
 862            // traditional argument format
 863            $args = implode(',', array_values($attrs));
 864            if (empty($args)) {
 865                $args = 'null';
 866            }
 867        }
 868
 869        $prefix = '';
 870        $postfix = '';
 871        $newline = '';
 872        if(!is_object($this->_reg_objects[$object][0])) {
 873            $this->_trigger_fatal_error("registered '$object' is not an object" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
 874        } elseif(!empty($this->_reg_objects[$object][1]) && !in_array($obj_comp, $this->_reg_objects[$object][1])) {
 875            $this->_trigger_fatal_error("'$obj_comp' is not a registered component of object '$object'", $this->_current_file, $this->_current_line_no, __FILE__, __LINE__);
 876        } elseif(method_exists($this->_reg_objects[$object][0], $obj_comp)) {
 877            // method
 878            if(in_array($obj_comp, $this->_reg_objects[$object][3])) {
 879                // block method
 880                if ($start_tag) {
 881                    $prefix = "\$this->_tag_stack[] = array('$obj_comp', $args); ";
 882                    $prefix .= "\$_block_repeat=true; \$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], null, \$this, \$_block_repeat); ";
 883                    $prefix .= "while (\$_block_repeat) { ob_start();";
 884                    $return = null;
 885                    $postfix = '';
 886                } else {
 887                    $prefix = "\$_obj_block_content = ob_get_contents(); ob_end_clean(); \$_block_repeat=false;";
 888                    $return = "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], \$_obj_block_content, \$this, \$_block_repeat)";
 889                    $postfix = "} array_pop(\$this->_tag_stack);";
 890                }
 891            } else {
 892                // non-block method
 893                $return = "\$this->_reg_objects['$object'][0]->$obj_comp($args)";
 894            }
 895        } else {
 896            // property
 897            $return = "\$this->_reg_objects['$object'][0]->$obj_comp";
 898        }
 899
 900        if($return != null) {
 901            if($tag_modifier != '') {
 902                $this->_parse_modifiers($return, $tag_modifier);
 903            }
 904
 905            if(!empty($_assign_var)) {
 906                $output = "\$this->assign('" . $this->_dequote($_assign_var) ."',  $return);";
 907            } else {
 908                $output = 'echo ' . $return . ';';
 909                $newline = $this->_additional_newline;
 910            }
 911        } else {
 912            $output = '';
 913        }
 914
 915        return '<?php ' . $prefix . $output . $postfix . "?>" . $newline;
 916    }
 917
 918    /**
 919     * Compile {insert ...} tag
 920     *
 921     * @param string $tag_args
 922     * @return string
 923     */
 924    function _compile_insert_tag($tag_args)
 925    {
 926        $attrs = $this->_parse_attrs($tag_args);
 927        $name = $this->_dequote($attrs['name']);
 928
 929        if (empty($name)) {
 930            $this->_syntax_error("missing insert name", E_USER_ERROR, __FILE__, __LINE__);
 931        }
 932
 933        if (!empty($attrs['script'])) {
 934            $delayed_loading = true;
 935        } else {
 936            $delayed_loading = false;
 937        }
 938
 939        foreach ($attrs as $arg_name => $arg_value) {
 940            if (is_bool($arg_value))
 941                $arg_value = $arg_value ? 'true' : 'false';
 942            $arg_list[] = "'$arg_name' => $arg_value";
 943        }
 944
 945        $this->_add_plugin('insert', $name, $delayed_loading);
 946
 947        $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))";
 948
 949        return "<?php require_once(SMARTY_CORE_DIR . 'core.run_insert_handler.php');\necho smarty_core_run_insert_handler($_params, \$this); ?>" . $this->_additional_newline;
 950    }
 951
 952    /**
 953     * Compile {include ...} tag
 954     *
 955     * @param string $tag_args
 956     * @return string
 957     */
 958    function _compile_include_tag($tag_args)
 959    {
 960        $attrs = $this->_parse_attrs($tag_args);
 961        $arg_list = array();
 962
 963        if (empty($attrs['file'])) {
 964            $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR, __FILE__, __LINE__);
 965        }
 966
 967        foreach ($attrs as $arg_name => $arg_value) {
 968            if ($arg_name == 'file') {
 969                $include_file = $arg_value;
 970                continue;
 971            } else if ($arg_name == 'assign') {
 972                $assign_var = $arg_value;
 973                continue;
 974            }
 975            if (is_bool($arg_value))
 976                $arg_value = $arg_value ? 'true' : 'false';
 977            $arg_list[] = "'$arg_name' => $arg_value";
 978        }
 979
 980        $output = '<?php ';
 981
 982        if (isset($assign_var)) {
 983            $output .= "ob_start();\n";
 984        }
 985
 986        $output .=
 987            "\$_smarty_tpl_vars = \$this->_tpl_vars;\n";
 988
 989
 990        $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))";
 991        $output .= "\$this->_smarty_include($_params);\n" .
 992        "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" .
 993        "unset(\$_smarty_tpl_vars);\n";
 994
 995        if (isset($assign_var)) {
 996            $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n";
 997        }
 998
 999        $output .= ' ?>';
1000
1001        return $output;
1002
1003    }
1004
1005    /**
1006     * Compile {include ...} tag
1007     *
1008     * @param string $tag_args
1009     * @return string
1010     */
1011    function _compile_include_php_tag($tag_args)
1012    {
1013        $attrs = $this->_parse_attrs($tag_args);
1014
1015        if (empty($attrs['file'])) {
1016            $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR, __FILE__, __LINE__);
1017        }
1018
1019        $assign_var = (empty($attrs['assign'])) ? '' : $this->_dequote($attrs['assign']);
1020        $once_var = (empty($attrs['once']) || $attrs['once']=='false') ? 'false' : 'true';
1021
1022        $arg_list = array();
1023        foreach($attrs as $arg_name => $arg_value) {
1024            if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') {
1025                if(is_bool($arg_value))
1026                    $arg_value = $arg_value ? 'true' : 'false';
1027                $arg_list[] = "'$arg_name' => $arg_value";
1028            }
1029        }
1030
1031        $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', $arg_list)."))";
1032
1033        return "<?php require_once(SMARTY_CORE_DIR . 'core.smarty_include_php.php');\nsmarty_core_smarty_include_php($_params, \$this); ?>" . $this->_additional_newline;
1034    }
1035
1036
1037    /**
1038     * Compile {section ...} tag
1039     *
1040     * @param string $tag_args
1041     * @return string
1042     */
1043    function _compile_section_start($tag_args)
1044    {
1045        $attrs = $this->_parse_attrs($tag_args);
1046        $arg_list = array();
1047
1048        $output = '<?php ';
1049        $section_name = $attrs['name'];
1050        if (empty($section_name)) {
1051            $this->_syntax_error("missing section name", E_USER_ERROR, __FILE__, __LINE__);
1052        }
1053
1054        $output .= "unset(\$this->_sections[$section_name]);\n";
1055        $section_props = "\$this->_sections[$section_name]";
1056
1057        foreach ($attrs as $attr_name => $attr_value) {
1058            switch ($attr_name) {
1059                case 'loop':
1060                    $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n";
1061                    break;
1062
1063                case 'show':
1064                    if (is_bool($attr_value))
1065                        $show_attr_value = $attr_value ? 'true' : 'false';
1066                    else
1067                        $show_attr_value = "(bool)$attr_value";
1068                    $output .= "{$section_props}['show'] = $show_attr_value;\n";
1069                    break;
1070
1071                case 'name':
1072                    $output .= "{$section_props}['$attr_name'] = $attr_value;\n";
1073                    break;
1074
1075                case 'max':
1076                case 'start':
1077                    $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n";
1078                    break;
1079
1080                case 'step':
1081                    $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n";
1082                    break;
1083
1084                default:
1085                    $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR, __FILE__, __LINE__);
1086                    break;
1087            }
1088        }
1089
1090        if (!isset($attrs['show']))
1091            $output .= "{$section_props}['show'] = true;\n";
1092
1093        if (!isset($attrs['loop']))
1094            $output .= "{$section_props}['loop'] = 1;\n";
1095
1096        if (!isset($attrs['max']))
1097            $output .= "{$section_props}['max'] = {$section_props}['loop'];\n";
1098        else
1099            $output .= "if ({$section_props}['max'] < 0)\n" .
1100                       "    {$section_props}['max'] = {$section_props}['loop'];\n";
1101
1102        if (!isset($attrs['step']))
1103            $output .= "{$section_props}['step'] = 1;\n";
1104
1105        if (!isset($attrs['start']))
1106            $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n";
1107        else {
1108            $output .= "if ({$section_props}['start'] < 0)\n" .
1109                       "    {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" .
1110                       "else\n" .
1111                       "    {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n";
1112        }
1113
1114        $output .= "if ({$section_props}['show']) {\n";
1115        if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) {
1116            $output .= "    {$section_props}['total'] = {$section_props}['loop'];\n";
1117        } else {
1118            $output .= "    {$section_props}['total'] = min(ceil(({$section_props}['step'] > 0 ? {$section_props}['loop'] - {$section_props}['start'] : {$section_props}['start']+1)/abs({$section_props}['step'])), {$section_props}['max']);\n";
1119        }
1120        $output .= "    if ({$section_props}['total'] == 0)\n" .
1121                   "        {$section_props}['show'] = false;\n" .
1122                   "} else\n" .
1123                   "    {$section_props}['total'] = 0;\n";
1124
1125        $output .= "if ({$section_props}['show']):\n";
1126        $output .= "
1127            for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1;
1128                 {$section_props}['iteration'] <= {$section_props}['total'];
1129                 {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n";
1130        $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n";
1131        $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n";
1132        $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n";
1133        $output .= "{$section_props}['first']      = ({$section_props}['iteration'] == 1);\n";
1134        $output .= "{$section_props}['last']       = ({$section_props}['iteration'] == {$section_props}['total']);\n";
1135
1136        $output .= "?>";
1137
1138        return $output;
1139    }
1140
1141
1142    /**
1143     * Compile {foreach ...} tag.
1144     *
1145     * @param string $tag_args
1146     * @return string
1147     */
1148    function _compile_foreach_start($tag_args)
1149    {
1150        $attrs = $this->_parse_attrs($tag_args);
1151        $arg_list = array();
1152
1153        if (empty($attrs['from'])) {
1154            return $this->_syntax_error("foreach: missing 'from' attribute", E_USER_ERROR, __FILE__, __LINE__);
1155        }
1156        $from = $attrs['from'];
1157
1158        if (empty($attrs['item'])) {
1159            return $this->_syntax_error("foreach: missing 'item' attribute", E_USER_ERROR, __FILE__, __LINE__);
1160        }
1161        $item = $this->_dequote($attrs['item']);
1162        if (!preg_match('~^\w+$~', $item)) {
1163            return $this->_syntax_error("'foreach: item' must be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1164        }
1165
1166        if (isset($attrs['key'])) {
1167            $key  = $this->_dequote($attrs['key']);
1168            if (!preg_match('~^\w+$~', $key)) {
1169                return $this->_syntax_error("foreach: 'key' must to be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__);
1170            }
1171            $key_part = "\$this->_tpl_vars['$key'] => ";
1172        } else {
1173            $key = null;
1174            $key_part = '';
1175        }
1176
1177        if (isset($attrs['name'])) {
1178            $name = $attrs['name'];
1179        } else {
1180            $name = null;
1181        }
1182
1183        $output = '<?php ';
1184        $output .= "\$_from = $from; if (!is_array(\$_from) && !is_object(\$_from)) { settype(\$_from, 'array'); }";
1185        if (isset($name)) {
1186            $foreach_props = "\$this->_foreach[$name]";
1187            $output .= "{$foreach_props} = array('total' => count(\$_from), 'iteration' => 0);\n";
1188            $output .= "if ({$foreach_props}['total'] > 0):\n";
1189            $output .= "    foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1190            $output .= "        {$foreach_props}['iteration']++;\n";
1191        } else {
1192            $output .= "if (count(\$_from)):\n";
1193            $output .= "    foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n";
1194        }
1195        $output .= '?>';
1196
1197        return $output;
1198    }
1199
1200
1201    /**
1202     * Compile {capture} .. {/capture} tags
1203     *
1204     * @param boolean $start true if this is the {capture} tag
1205     * @param string $tag_args
1206     * @return string
1207     */
1208
1209    function _compile_capture_tag($start, $tag_args = '')
1210    {
1211        $attrs = $this->_parse_attrs($tag_args);
1212
1213        if ($start) {
1214            if (isset($attrs['name']))
1215                $buffer = $attrs['name'];
1216            else
1217                $buffer = "'default'";
1218
1219            if (isset($attrs['assign']))
1220                $assign = $attrs['assign'];
1221            else
1222                $assign = null;
1223            $output = "<?php ob_start(); ?>";
1224            $this->_capture_stack[] = array($buffer, $assign);
1225        } else {
1226            list($buffer, $assign) = array_pop($this->_capture_stack);
1227            $output = "<?php \$this->_smarty_vars['capture'][$buffer] = ob_get_contents(); ";
1228            if (isset($assign)) {
1229                $output .= " \$this->assign($assign, ob_get_contents());";
1230            }
1231            $output .= "ob_end_clean(); ?>";
1232        }
1233
1234        return $output;
1235    }
1236
1237    /**
1238     * Compile {if ...} tag
1239     *
1240     * @param string $tag_args
1241     * @param boolean $elseif if true, uses elseif instead of if
1242     * @return string
1243     */
1244    function _compile_if_tag($tag_args, $elseif = false)
1245    {
1246
1247        /* Tokenize args for 'if' tag. */
1248        preg_match_all('~(?>
1249                ' . $this->_obj_call_regexp . '(?:' . $this->_mod_regexp . '*)? | # valid object call
1250                ' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)?    | # var or quoted string
1251                \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@    | # valid non-word token
1252                \b\w+\b                                                        | # valid word token
1253                \S+                                                           # anything else
1254                )~x', $tag_args, $match);
1255
1256        $tokens = $match[0];
1257
1258        if(empty($tokens)) {
1259            $_error_msg = $elseif ? "'elseif'" : "'if'";
1260            $_error_msg .= ' statement requires arguments'; 
1261            $this->_syntax_error($_error_msg, E_USER_ERROR, __FILE__, __LINE__);
1262        }
1263            
1264                
1265        // make sure we have balanced parenthesis
1266        $token_count = array_count_values($tokens);
1267        if(isset($token_count['(']) && $token_count['('] != $token_count[')']) {
1268            $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR, __FILE__, __LINE__);
1269        }
1270
1271        $is_arg_stack = array();
1272
1273        for ($i = 0; $i < count($tokens); $i++) {
1274
1275            $token = &$tokens[$i];
1276
1277            switch (strtolower($token)) {
1278                case '!':
1279                case '%':
1280                case '!==':
1281                case '==':
1282                case '===':
1283                case '>':
1284                case '<':
1285                case '!=':
1286                case '<>':
1287                case '<<':
1288                case '>>':
1289                case '<=':
1290                case '>=':
1291                case '&&':
1292                case '||':
1293                case '|':
1294                case '^':
1295                case '&':
1296                case '~':
1297                case ')':
1298                case ',':
1299                case '+':
1300                case '-':
1301                case '*':
1302                case '/':
1303                case '@':
1304                    break;
1305
1306                case 'eq':
1307                    $token = '==';
1308                    break;
1309
1310                case 'ne':
1311                case 'neq':
1312                    $token = '!=';
1313                    break;
1314
1315                case 'lt':
1316                    $token = '<';
1317                    break;
1318
1319                case 'le':
1320                case 'lte':
1321                    $token = '<=';
1322                    break;
1323
1324                case 'gt':
1325                    $token = '>';
1326                    break;
1327
1328                case 'ge':
1329                case 'gte':
1330                    $token = '>=';
1331                    break;
1332
1333                case 'and':
1334                    $token = '&&';
1335                    break;
1336
1337                case 'or':
1338                    $token = '||';
1339                    break;
1340
1341                case 'not':
1342                    $token = '!';
1343                    break;
1344
1345                case 'mod':
1346                    $token = '%';
1347                    break;
1348
1349                case '(':
1350                    array_push($is_arg_stack, $i);
1351                    break;
1352
1353                case 'is':
1354                    /* If last token was a ')', we operate on the parenthesized
1355                       expression. The start of the expression is on the stack.
1356                       Otherwise, we operate on the last encountered token. */
1357                    if ($tokens[$i-1] == ')')
1358                        $is_arg_start = array_pop($is_arg_stack);
1359                    else
1360                        $is_arg_start = $i-1;
1361                    /* Construct the argument for 'is' expression, so it knows
1362                       what to operate on. */
1363                    $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start));
1364
1365                    /* Pass all tokens from next one until the end to the
1366                       'is' expression parsing function. The function will
1367                       return modified tokens, where the first one is the result
1368                       of the 'is' expression and the rest are the tokens it
1369                       didn't touch. */
1370                    $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));
1371
1372                    /* Replace the old tokens with the new ones. */
1373                    array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);
1374
1375                    /* Adjust argument start so that it won't change from the
1376                       current position for the next iteration. */
1377                    $i = $is_arg_start;
1378                    break;
1379
1380                default:
1381                    if(preg_match('~^' . $this->_func_regexp . '$~', $token) ) {
1382                            // function call
1383                            if($this->security &&
1384                               !in_array($token, $this->security_settings['IF_FUNCS'])) {
1385                                $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);
1386                            }
1387                    } elseif(preg_match('~^' . $this->_var_regexp . '$~', $token) && (strpos('+-*/^%&|', substr($token, -1)) === false) && isset($tokens[$i+1]) && $tokens[$i+1] == '(') {
1388                        // variable function call
1389                        $this->_syntax_error("variable function call '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__);                      
1390                    } elseif(preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$~', $token)) {
1391                        // object or variable
1392                        $token = $this->_parse_var_props($token);
1393                    } elseif(is_numeric($token)) {
1394                        // number, skip it
1395                    } else {
1396                        $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__);
1397                    }
1398                    break;
1399            }
1400        }
1401
1402        if ($elseif)
1403            return '<?php elseif ('.implode(' ', $tokens).'): ?>';
1404        else
1405            return '<?php if ('.implode(' ', $tokens).'): ?>';
1406    }
1407
1408
1409    function _compile_arg_list($type, $name, $attrs, &$cache_code) {
1410        $arg_list = array();
1411
1412        if (isset($type) && isset($name)
1413            && isset($this->_plugins[$type])
1414            && isset($this->_plugins[$type][$name])
1415            && empty($this->_plugins[$type][$name][4])
1416            && is_array($this->_plugins[$type][$name][5])
1417            ) {
1418            /* we have a list of parameters that should be cached */
1419            $_cache_attrs = $this->_plugins[$type][$name][5];
1420            $_count = $this->_cache_attrs_count++;
1421            $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');";
1422
1423        } else {
1424            /* no parameters are cached */
1425            $_cache_attrs = null;
1426        }
1427
1428        foreach ($attrs as $arg_name => $arg_value) {
1429            if (is_bool($arg_value))
1430                $arg_value = $arg_value ? 'true' : 'false';
1431            if (is_null($arg_value))
1432                $arg_value = 'null';
1433            if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) {
1434                $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)";
1435            } else {
1436                $arg_list[] = "'$arg_name' => $arg_value";
1437            }
1438        }
1439        return $arg_list;
1440    }
1441
1442    /**
1443     * Parse is expression
1444     *
1445     * @param string $is_arg
1446     * @param array $tokens
1447     * @return array
1448     */
1449    function _parse_is_expr($is_arg, $tokens)
1450    {
1451        $expr_end = 0;
1452        $negate_expr = false;
1453
1454        if (($first_token = array_shift($tokens)) == 'not') {
1455            $negate_expr = true;
1456            $expr_type = array_shift($tokens);
1457        } else
1458            $expr_type = $first_token;
1459
1460        switch ($expr_type) {
1461            case 'even':
1462                if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1463                    $expr_end++;
1464                    $expr_arg = $tokens[$expr_end++];
1465                    $expr = "!(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1466                } else
1467                    $expr = "!(1 & $is_arg)";
1468                break;
1469
1470            case 'odd':
1471                if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') {
1472                    $expr_end++;
1473                    $expr_arg = $tokens[$expr_end++];
1474                    $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))";
1475                } else
1476                    $expr = "(1 & $is_arg)";
1477                break;
1478
1479            case 'div':
1480                if (@$tokens[$expr_end] == 'by') {
1481                    $expr_end++;
1482                    $expr_arg = $tokens[$expr_end++];
1483                    $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")";
1484                } else {
1485                    $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__);
1486                }
1487                break;
1488
1489            default:
1490                $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__);
1491                break;
1492        }
1493
1494        if ($negate_expr) {
1495            $expr = "!($expr)";
1496        }
1497
1498        array_splice($tokens, 0, $expr_end, $expr);
1499
1500        return $tokens;
1501    }
1502
1503
1504    /**
1505     * Parse attribute string
1506     *
1507     * @param string $tag_args
1508     * @return array
1509     */
1510    function _parse_attrs($tag_args)
1511    {
1512
1513        /* Tokenize tag attributes. */
1514        preg_match_all('~(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+)
1515                         )+ |
1516                         [=]
1517                        ~x', $tag_args, $match);
1518        $tokens       = $match[0];
1519
1520        $attrs = array();
1521        /* Parse state:
1522            0 - expecting attribute name
1523            1 - expecting '='
1524            2 - expecting attribute value (not '=') */
1525        $state = 0;
1526
1527        foreach ($tokens as $token) {
1528            switch ($state) {
1529                case 0:
1530                    /* If the token is a valid identifier, we set attribute name
1531                       and go to state 1. */
1532                    if (preg_match('~^\w+$~', $token)) {
1533                        $attr_name = $token;
1534                        $state = 1;
1535                    } else
1536                        $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__);
1537                    break;
1538
1539                case 1:
1540                    /* If the token is '=', then we go to state 2. */
1541                    if ($token == '=') {
1542                        $state = 2;
1543                    } else
1544                        $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1545                    break;
1546
1547                case 2:
1548                    /* If token is not '=', we set the attribute value and go to
1549                       state 0. */
1550                    if ($token != '=') {
1551                        /* We booleanize the token if it's a non-quoted possible
1552                           boolean value. */
1553                        if (preg_match('~^(on|yes|true)$~', $token)) {
1554                            $token = 'true';
1555                        } else if (preg_match('~^(off|no|false)$~', $token)) {
1556                            $token = 'false';
1557                        } else if ($token == 'null') {
1558                            $token = 'null';
1559                        } else if (preg_match('~^' . $this->_num_const_regexp . '|0[xX][0-9a-fA-F]+$~', $token)) {
1560                            /* treat integer literally */
1561                        } else if (!preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$~', $token)) {
1562                            /* treat as a string, double-quote it escaping quotes */
1563                            $token = '"'.addslashes($token).'"';
1564                        }
1565
1566                        $attrs[$attr_name] = $token;
1567                        $state = 0;
1568                    } else
1569                        $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__);
1570                    break;
1571            }
1572            $last_token = $token;
1573        }
1574
1575        if($state != 0) {
1576            if($state == 1) {
1577                $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__);
1578            } else {
1579                $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__);
1580            }
1581        }
1582
1583        $this->_parse_vars_props($attrs);
1584
1585        return $attrs;
1586    }
1587
1588    /**
1589     * compile multiple variables and section properties tokens into
1590     * PHP code
1591     *
1592     * @param array $tokens
1593     */
1594    function _parse_vars_props(&$tokens)
1595    {
1596        foreach($tokens as $key => $val) {
1597            $tokens[$key] = $this->_parse_var_props($val);
1598        }
1599    }
1600
1601    /**
1602     * compile single variable and section properties token into
1603     * PHP code
1604     *
1605     * @param string $val
1606     * @param string $tag_attrs
1607     * @return string
1608     */
1609    function _parse_var_props($val)
1610    {
1611        $val = trim($val);
1612
1613        if(preg_match('~^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$~', $val, $match)) {
1614            // $ variable or object
1615            $return = $this->_parse_var($match[1]);
1616            $modifiers = $match[2];
1617            if (!empty($this->default_modifiers) && !preg_match('~(^|\|)smarty:nodefaults($|\|)~',$modifiers)) {
1618                $_default_mod_string = implode('|',(array)$this->default_modifiers);
1619                $modifiers = empty($modifiers) ? $_default_mod_string : $_default_mod_string . '|' . $modifiers;
1620            }
1621            $this->_parse_modifiers($return, $modifiers);
1622            return $return;
1623        } elseif (preg_match('~^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) {
1624                // double quoted text
1625                preg_match('~^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match);
1626                $return = $this->_expand_quoted_text($match[1]);
1627                if($match[2] != '') {
1628                    $this->_parse_modifiers($return, $match[2]);
1629                }
1630                return $return;
1631            }
1632        elseif(preg_match('~