From 62099617ae8c979ba83a701ec030321239a8bda2 Mon Sep 17 00:00:00 2001 From: uwetews Date: Fri, 9 Sep 2016 22:54:42 +0200 Subject: [PATCH] - improvement {foreach} observe {break n} and {continue n} nesting levels when restoring saved $item and $key variables --- change_log.txt | 3 +- libs/Smarty.class.php | 2 +- .../smarty_internal_compile_break.php | 49 ++++++++++++++--- .../smarty_internal_compile_continue.php | 53 ++++--------------- .../smarty_internal_compile_foreach.php | 18 ++++++- .../smarty_internal_runtime_foreach.php | 30 +++++++---- .../smarty_internal_templatecompilerbase.php | 24 +++++++-- 7 files changed, 107 insertions(+), 72 deletions(-) diff --git a/change_log.txt b/change_log.txt index 8243e478..6e0c7aed 100644 --- a/change_log.txt +++ b/change_log.txt @@ -2,6 +2,7 @@ 09.09.2016 - bugfix/optimization {foreach} did not execute the {foreachelse} when iterating empty objects https://github.com/smarty-php/smarty/pull/287 - bugfix {foreach} must keep the @properties when restoring a saved $item variable as the properties might be used outside {foreach} https://github.com/smarty-php/smarty/issues/267 + - improvement {foreach} observe {break n} and {continue n} nesting levels when restoring saved $item and $key variables 08.09.2016 - bugfix implement wrapper for removed method getConfigVariable() https://github.com/smarty-php/smarty/issues/286 @@ -52,7 +53,7 @@ 23.07.2016 - bugfix setTemplateDir('/') and setTemplateDir('') did create wrong absolute filepath https://github.com/smarty-php/smarty/issues/245 - optimization of filepath normalization - - improvement remove double funtion declaration in plugin shared.escape_special_cars.php https://github.com/smarty-php/smarty/issues/229 + - improvement remove double function declaration in plugin shared.escape_special_cars.php https://github.com/smarty-php/smarty/issues/229 19.07.2016 - bugfix multiple {include} with relative filepath within {block}{/block} could fail https://github.com/smarty-php/smarty/issues/246 diff --git a/libs/Smarty.class.php b/libs/Smarty.class.php index 09bfd413..323f8a9f 100644 --- a/libs/Smarty.class.php +++ b/libs/Smarty.class.php @@ -114,7 +114,7 @@ class Smarty extends Smarty_Internal_TemplateBase /** * smarty version */ - const SMARTY_VERSION = '3.1.31-dev/15'; + const SMARTY_VERSION = '3.1.31-dev/16'; /** * define variable scopes diff --git a/libs/sysplugins/smarty_internal_compile_break.php b/libs/sysplugins/smarty_internal_compile_break.php index 0555eccc..50157382 100644 --- a/libs/sysplugins/smarty_internal_compile_break.php +++ b/libs/sysplugins/smarty_internal_compile_break.php @@ -40,9 +40,31 @@ class Smarty_Internal_Compile_Break extends Smarty_Internal_CompileBase * @param array $parameter array with compilation parameter * * @return string compiled code - * @throws \SmartyCompilerException */ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler, $parameter) + { + list($levels, $foreachLevels) = $this->checkLevels($args, $compiler); + $output = "getTagCompiler('foreach'); + $output .= $foreachCompiler->compileRestore($foreachLevels); + } + $output .= "break {$levels};?>"; + return $output; + } + + /** + * check attributes and return array of break and foreach levels + * + * @param array $args array with attributes from parser + * @param \Smarty_Internal_TemplateCompilerBase $compiler compiler object + * @param string $tag tag name + * + * @return array + * @throws \SmartyCompilerException + */ + public function checkLevels($args, Smarty_Internal_TemplateCompilerBase $compiler, $tag = 'break') { static $_is_loopy = array('for' => true, 'foreach' => true, 'while' => true, 'section' => true); // check and get attributes @@ -56,22 +78,33 @@ class Smarty_Internal_Compile_Break extends Smarty_Internal_CompileBase if (!is_numeric($_attr[ 'levels' ])) { $compiler->trigger_template_error('level attribute must be a numeric constant', null, true); } - $_levels = $_attr[ 'levels' ]; + $levels = $_attr[ 'levels' ]; } else { - $_levels = 1; + $levels = 1; } - $level_count = $_levels; + $level_count = $levels; $stack_count = count($compiler->_tag_stack) - 1; - while ($level_count > 0 && $stack_count >= 0) { + $foreachLevels = 0; + $lastTag = ''; + while ($level_count >= 0 && $stack_count >= 0) { if (isset($_is_loopy[ $compiler->_tag_stack[ $stack_count ][ 0 ] ])) { + $lastTag = $compiler->_tag_stack[ $stack_count ][ 0 ]; + if ($level_count === 0) { + break; + } $level_count --; + if ($compiler->_tag_stack[ $stack_count ][ 0 ] === 'foreach') { + $foreachLevels ++; + } } $stack_count --; } if ($level_count != 0) { - $compiler->trigger_template_error("cannot break {$_levels} level(s)", null, true); + $compiler->trigger_template_error("cannot {$tag} {$levels} level(s)", null, true); } - - return ""; + if ($lastTag === 'foreach' && $tag === 'break') { + $foreachLevels --; + } + return array($levels, $foreachLevels); } } diff --git a/libs/sysplugins/smarty_internal_compile_continue.php b/libs/sysplugins/smarty_internal_compile_continue.php index ac41f418..7492c7df 100644 --- a/libs/sysplugins/smarty_internal_compile_continue.php +++ b/libs/sysplugins/smarty_internal_compile_continue.php @@ -14,23 +14,8 @@ * @package Smarty * @subpackage Compiler */ -class Smarty_Internal_Compile_Continue extends Smarty_Internal_CompileBase +class Smarty_Internal_Compile_Continue extends Smarty_Internal_Compile_Break { - /** - * Attribute definition: Overwrites base class. - * - * @var array - * @see Smarty_Internal_CompileBase - */ - public $optional_attributes = array('levels'); - - /** - * Attribute definition: Overwrites base class. - * - * @var array - * @see Smarty_Internal_CompileBase - */ - public $shorttag_order = array('levels'); /** * Compiles code for the {continue} tag @@ -44,34 +29,14 @@ class Smarty_Internal_Compile_Continue extends Smarty_Internal_CompileBase */ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler, $parameter) { - static $_is_loopy = array('for' => true, 'foreach' => true, 'while' => true, 'section' => true); - // check and get attributes - $_attr = $this->getAttributes($compiler, $args); - - if ($_attr[ 'nocache' ] === true) { - $compiler->trigger_template_error('nocache option not allowed', null, true); + list($levels, $foreachLevels) = $this->checkLevels($args, $compiler, 'continue'); + $output = " 1) { + /* @var Smarty_Internal_Compile_Foreach $foreachCompiler */ + $foreachCompiler = $compiler->getTagCompiler('foreach'); + $output .= $foreachCompiler->compileRestore($foreachLevels - 1); } - - if (isset($_attr[ 'levels' ])) { - if (!is_numeric($_attr[ 'levels' ])) { - $compiler->trigger_template_error('level attribute must be a numeric constant', null, true); - } - $_levels = $_attr[ 'levels' ]; - } else { - $_levels = 1; - } - $level_count = $_levels; - $stack_count = count($compiler->_tag_stack) - 1; - while ($level_count > 0 && $stack_count >= 0) { - if (isset($_is_loopy[ $compiler->_tag_stack[ $stack_count ][ 0 ] ])) { - $level_count --; - } - $stack_count --; - } - if ($level_count != 0) { - $compiler->trigger_template_error("cannot continue {$_levels} level(s)", null, true); - } - - return ""; + $output .= "continue {$levels};?>"; + return $output; } } diff --git a/libs/sysplugins/smarty_internal_compile_foreach.php b/libs/sysplugins/smarty_internal_compile_foreach.php index 56d760cd..5ddd42d8 100644 --- a/libs/sysplugins/smarty_internal_compile_foreach.php +++ b/libs/sysplugins/smarty_internal_compile_foreach.php @@ -231,7 +231,7 @@ class Smarty_Internal_Compile_Foreach extends Smarty_Internal_Compile_Private_Fo if (isset($itemAttr[ 'last' ])) { $output .= "{$itemVar}->last = {$itemVar}->iteration == {$itemVar}->total;\n"; } - if ($this->isNamed) { + if (isset($foreachVar)) { if (isset($namedAttr[ 'iteration' ])) { $output .= "{$foreachVar}->value['iteration']++;\n"; } @@ -252,6 +252,18 @@ class Smarty_Internal_Compile_Foreach extends Smarty_Internal_Compile_Private_Fo return $output; } + + /** + * Compiles code for to restore saved template variables + * + * @param int $levels number of levels to restore + * + * @return string compiled code + */ + public function compileRestore($levels) + { + return "\$_smarty_tpl->smarty->ext->_foreach->restore(\$_smarty_tpl, {$levels});\n"; + } } /** @@ -323,7 +335,9 @@ class Smarty_Internal_Compile_Foreachclose extends Smarty_Internal_CompileBase $output .= "}\n"; } $output .= "}\n"; - $output .= "\$_smarty_tpl->smarty->ext->_foreach->restore(\$_smarty_tpl);\n"; + /* @var Smarty_Internal_Compile_Foreach $foreachCompiler */ + $foreachCompiler = $compiler->getTagCompiler('foreach'); + $output .= $foreachCompiler->compileRestore(1); $output .= "?>\n"; return $output; } diff --git a/libs/sysplugins/smarty_internal_runtime_foreach.php b/libs/sysplugins/smarty_internal_runtime_foreach.php index 812a8bd3..1e8655d9 100644 --- a/libs/sysplugins/smarty_internal_runtime_foreach.php +++ b/libs/sysplugins/smarty_internal_runtime_foreach.php @@ -93,20 +93,28 @@ class Smarty_Internal_Runtime_Foreach /** * Restore saved variables * + * will be called by {break n} or {continue n} for the required number of levels + * * @param \Smarty_Internal_Template $tpl + * @param int $levels number of levels */ - public function restore(Smarty_Internal_Template $tpl) + public function restore(Smarty_Internal_Template $tpl, $levels = 1) { - $saveVars = array_pop($this->stack); - if (isset($saveVars['item'])) { - $item = &$saveVars['item']; - $tpl->tpl_vars[ $item[0] ]->value = $item[1]->value; - } - if (isset($saveVars['key'])) { - $tpl->tpl_vars[ $saveVars['key'][0] ] = $saveVars['key'][1]; - } - if (isset($saveVars['named'])) { - $tpl->tpl_vars[ $saveVars['named'][0] ] = $saveVars['named'][1]; + while ($levels) { + $saveVars = array_pop($this->stack); + if (!empty($saveVars)) { + if (isset($saveVars[ 'item' ])) { + $item = &$saveVars[ 'item' ]; + $tpl->tpl_vars[ $item[ 0 ] ]->value = $item[ 1 ]->value; + } + if (isset($saveVars[ 'key' ])) { + $tpl->tpl_vars[ $saveVars[ 'key' ][ 0 ] ] = $saveVars[ 'key' ][ 1 ]; + } + if (isset($saveVars[ 'named' ])) { + $tpl->tpl_vars[ $saveVars[ 'named' ][ 0 ] ] = $saveVars[ 'named' ][ 1 ]; + } + } + $levels--; } } diff --git a/libs/sysplugins/smarty_internal_templatecompilerbase.php b/libs/sysplugins/smarty_internal_templatecompilerbase.php index 7210374e..22afedfe 100644 --- a/libs/sysplugins/smarty_internal_templatecompilerbase.php +++ b/libs/sysplugins/smarty_internal_templatecompilerbase.php @@ -844,9 +844,26 @@ abstract class Smarty_Internal_TemplateCompilerBase * @param mixed $param2 optional parameter * @param mixed $param3 optional parameter * - * @return string compiled code + * @return string|bool compiled code or false */ public function callTagCompiler($tag, $args, $param1 = null, $param2 = null, $param3 = null) + { + $tagCompiler = $this->getTagCompiler($tag); + // compile this tag + return $tagCompiler === false ? false : $tagCompiler->compile($args, $this, $param1, $param2, $param3); + } + + /** + * lazy loads internal compile plugin for tag compile objects cached for reuse. + * + * class name format: Smarty_Internal_Compile_TagName + * plugin filename format: Smarty_Internal_TagName.php + * + * @param string $tag tag name + * + * @return Smarty_Internal_CompileBase|bool tag compiler object or false if not found + */ + public function getTagCompiler($tag) { // re-use object if already exists if (!isset(self::$_tag_objects[ $tag ])) { @@ -860,12 +877,9 @@ abstract class Smarty_Internal_TemplateCompilerBase self::$_tag_objects[ $tag ] = new $class_name; } else { self::$_tag_objects[ $tag ] = false; - return false; } } - // compile this tag - return self::$_tag_objects[ $tag ] === false ? false : - self::$_tag_objects[ $tag ]->compile($args, $this, $param1, $param2, $param3); + return self::$_tag_objects[ $tag ]; } /**