From 9cc60f5e38f3298a72060d45873b0fcfb83a6d67 Mon Sep 17 00:00:00 2001 From: Simon Wisselink Date: Tue, 24 Jan 2023 12:01:20 +0100 Subject: [PATCH] Convert isset and empty to modifiercomilers, and smooth the error handling to fix unit tests. --- src/Compile/FunctionCallCompiler.php | 25 +++++++------ .../Modifier/EmptyModifierCompiler.php | 19 ++++++++++ .../Modifier/IssetModifierCompiler.php | 25 +++++++++++++ src/Compile/ModifierCompiler.php | 20 +++++++++-- src/Extension/CallbackWrapper.php | 35 +++++++++++++++++++ src/Extension/DefaultExtension.php | 4 +-- src/FunctionHandler/Count.php | 2 +- src/FunctionHandler/EmptyHandler.php | 27 -------------- src/FunctionHandler/IssetHandler.php | 30 ---------------- src/Smarty.php | 3 +- .../UnitTests/SecurityTests/FunctionTest.php | 2 +- .../TagTests/PluginFunction/EmptyTest.php | 2 +- .../TagTests/PluginFunction/IssetTest.php | 4 +-- .../PHPfunctions/PhpFunctionTest.php | 15 ++++---- 14 files changed, 127 insertions(+), 86 deletions(-) create mode 100644 src/Compile/Modifier/EmptyModifierCompiler.php create mode 100644 src/Compile/Modifier/IssetModifierCompiler.php create mode 100644 src/Extension/CallbackWrapper.php delete mode 100644 src/FunctionHandler/EmptyHandler.php delete mode 100644 src/FunctionHandler/IssetHandler.php diff --git a/src/Compile/FunctionCallCompiler.php b/src/Compile/FunctionCallCompiler.php index 849655ce..89b91021 100644 --- a/src/Compile/FunctionCallCompiler.php +++ b/src/Compile/FunctionCallCompiler.php @@ -55,23 +55,28 @@ class FunctionCallCompiler extends Base { $_attr = $this->getAttributes($compiler, $args); unset($_attr['nocache']); - if (!$functionHandler = $compiler->getSmarty()->getFunctionHandler($function)) { - throw new CompilerException("Cannot compile unknown function $function."); - } - - // not cacheable? - $compiler->tag_nocache = $compiler->tag_nocache || !$functionHandler->isCacheable(); - $_paramsArray = $this->formatParamsArray($_attr); - $_params = 'array(' . implode(',', $_paramsArray) . ')'; - $output = "\$_smarty_tpl->getSmarty()->getFunctionHandler(" . var_export($function, true) . ")"; - $output .= "->handle($_params, \$_smarty_tpl)"; + try { + $value = array_shift($_attr); + $output = $compiler->compileModifier([array_merge([$function], $_attr)], $value); + } catch (\Smarty\CompilerException $e) { + if ($functionHandler = $compiler->getSmarty()->getFunctionHandler($function)) { + + // not cacheable? + $compiler->tag_nocache = $compiler->tag_nocache || !$functionHandler->isCacheable(); + $output = "\$_smarty_tpl->getSmarty()->getFunctionHandler(" . var_export($function, true) . ")"; + $output .= "->handle($_params, \$_smarty_tpl)"; + } else { + throw $e; + } + } if (!empty($parameter['modifierlist'])) { $output = $compiler->compileModifier($parameter['modifierlist'], $output); } + return $output; } } diff --git a/src/Compile/Modifier/EmptyModifierCompiler.php b/src/Compile/Modifier/EmptyModifierCompiler.php new file mode 100644 index 00000000..6bb6c11c --- /dev/null +++ b/src/Compile/Modifier/EmptyModifierCompiler.php @@ -0,0 +1,19 @@ +has_code = true; - // check and get attributes - $_attr = $this->getAttributes($compiler, $args); $output = $parameter['value']; + // loop over list of modifiers foreach ($parameter['modifierlist'] as $single_modifier) { /* @var string $modifier */ @@ -74,4 +75,19 @@ class ModifierCompiler extends Base { } return $output; } + + /** + * Wether this class will be able to compile the given modifier. + * @param string $modifier + * @param Template $compiler + * + * @return bool + * @throws CompilerException + */ + public function canCompileForModifier(string $modifier, \Smarty\Compiler\Template $compiler): bool { + return $compiler->getModifierCompiler($modifier) + || $compiler->getSmarty()->getModifierCallback($modifier) + || $compiler->getPluginFromDefaultHandler($modifier, \Smarty\Smarty::PLUGIN_MODIFIERCOMPILER) + || $compiler->getPluginFromDefaultHandler($modifier, \Smarty\Smarty::PLUGIN_MODIFIER); + } } diff --git a/src/Extension/CallbackWrapper.php b/src/Extension/CallbackWrapper.php new file mode 100644 index 00000000..827dd78b --- /dev/null +++ b/src/Extension/CallbackWrapper.php @@ -0,0 +1,35 @@ +callback = $callback; + $this->modifierName = $modifierName; + } + + public function handle(...$params) { + try { + return call_user_func_array($this->callback, $params); + } catch (\ArgumentCountError $e) { + throw new Exception("Invalid number of arguments to modifier " . $this->modifierName); + } + } + +} \ No newline at end of file diff --git a/src/Extension/DefaultExtension.php b/src/Extension/DefaultExtension.php index 2a2c2938..75410a6d 100644 --- a/src/Extension/DefaultExtension.php +++ b/src/Extension/DefaultExtension.php @@ -23,9 +23,11 @@ class DefaultExtension extends Base { case 'count_sentences': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\CountSentencesModifierCompiler(); break; case 'count_words': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\CountWordsModifierCompiler(); break; case 'default': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\DefaultModifierCompiler(); break; + case 'empty': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\EmptyModifierCompiler(); break; case 'escape': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\EscapeModifierCompiler(); break; case 'from_charset': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\FromCharsetModifierCompiler(); break; case 'indent': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\IndentModifierCompiler(); break; + case 'isset': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\IssetModifierCompiler(); break; case 'lower': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\LowerModifierCompiler(); break; case 'nl2br': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\Nl2brModifierCompiler(); break; case 'noprint': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\NoPrintModifierCompiler(); break; @@ -72,7 +74,6 @@ class DefaultExtension extends Base { case 'count': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Count(); break; case 'counter': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Counter(); break; case 'cycle': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Cycle(); break; - case 'empty': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\EmptyHandler(); break; case 'fetch': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Fetch(); break; case 'html_checkboxes': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlCheckboxes(); break; case 'html_image': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlImage(); break; @@ -83,7 +84,6 @@ class DefaultExtension extends Base { case 'html_table': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlTable(); break; case 'in_array': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\InArray(); break; case 'is_array': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\IsArray(); break; - case 'isset': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\IssetHandler(); break; case 'mailto': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Mailto(); break; case 'math': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Math(); break; case 'strlen': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Strlen(); break; diff --git a/src/FunctionHandler/Count.php b/src/FunctionHandler/Count.php index 5d26273b..768d809b 100644 --- a/src/FunctionHandler/Count.php +++ b/src/FunctionHandler/Count.php @@ -20,7 +20,7 @@ class Count extends Base { $params = array_values($params ?? []); if (count($params) < 1 || count($params) > 2) { - throw new Exception("Invalid number of arguments for count. count expects 2 or 3 parameters."); + throw new Exception("Invalid number of arguments for count. count expects 1 or 2 parameters."); } $value = $params[0]; diff --git a/src/FunctionHandler/EmptyHandler.php b/src/FunctionHandler/EmptyHandler.php deleted file mode 100644 index 3a89ab3b..00000000 --- a/src/FunctionHandler/EmptyHandler.php +++ /dev/null @@ -1,27 +0,0 @@ -getExtensions() as $extension) { if ($callback = $extension->getModifierCallback($modifierName)) { - return $callback; + return [new CallbackWrapper($modifierName, $callback), 'handle']; } } return null; diff --git a/tests/UnitTests/SecurityTests/FunctionTest.php b/tests/UnitTests/SecurityTests/FunctionTest.php index 6c74e398..3ef09b15 100644 --- a/tests/UnitTests/SecurityTests/FunctionTest.php +++ b/tests/UnitTests/SecurityTests/FunctionTest.php @@ -32,7 +32,7 @@ class FunctionTest extends PHPUnit_Smarty { $this->smarty->enableSecurity(); $this->expectException(\Smarty\CompilerException::class); - $this->expectExceptionMessage('Cannot compile unknown function unknown'); + $this->expectExceptionMessage('unknown modifier'); $this->smarty->fetch('eval:{unknown()}'); } } diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginFunction/EmptyTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginFunction/EmptyTest.php index 2e73db63..34a56eb3 100644 --- a/tests/UnitTests/TemplateSource/TagTests/PluginFunction/EmptyTest.php +++ b/tests/UnitTests/TemplateSource/TagTests/PluginFunction/EmptyTest.php @@ -36,7 +36,7 @@ class EmptyTest extends \PHPUnit_Smarty { } public function testInvalidParameters() { - $this->expectException(\Smarty\Exception::class); + $this->expectException(\Smarty\CompilerException::class); $this->expectExceptionMessage('Invalid number of arguments'); $this->assertEquals("", $this->smarty->fetch("string:{empty(3, 'foo')}")); } diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginFunction/IssetTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginFunction/IssetTest.php index a27c7e71..a54b244a 100644 --- a/tests/UnitTests/TemplateSource/TagTests/PluginFunction/IssetTest.php +++ b/tests/UnitTests/TemplateSource/TagTests/PluginFunction/IssetTest.php @@ -37,9 +37,9 @@ class IssetTest extends \PHPUnit_Smarty { } public function testInvalidParameters() { - $this->expectException(\Smarty\Exception::class); + $this->expectException(\Smarty\CompilerException::class); $this->expectExceptionMessage('Invalid number of arguments'); - $this->assertEquals("", $this->smarty->fetch("string:{empty(3, 'foo')}")); + $this->assertEquals("", $this->smarty->fetch("string:{if isset()}blurp{/if}")); } } \ No newline at end of file diff --git a/tests/UnitTests/TemplateSource/ValueTests/PHPfunctions/PhpFunctionTest.php b/tests/UnitTests/TemplateSource/ValueTests/PHPfunctions/PhpFunctionTest.php index 8b92092f..cfbb32bb 100644 --- a/tests/UnitTests/TemplateSource/ValueTests/PHPfunctions/PhpFunctionTest.php +++ b/tests/UnitTests/TemplateSource/ValueTests/PHPfunctions/PhpFunctionTest.php @@ -57,6 +57,7 @@ class PhpFunctionTest extends PHPUnit_Smarty public function testEmpty2() { $this->smarty->disableSecurity(); + $this->getSmarty()->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'pass', function ($v) { return $v; }); $this->smarty->assign('var', array(null, false, (int) 0, @@ -78,6 +79,7 @@ class PhpFunctionTest extends PHPUnit_Smarty public function testEmpty3() { $this->smarty->disableSecurity(); + $this->getSmarty()->registerPlugin(\Smarty\Smarty::PLUGIN_FUNCTION, 'pass', function ($v) { return $v; }); $this->smarty->assign('var', array(true, (int) 1, (float) 0.1, @@ -97,6 +99,7 @@ class PhpFunctionTest extends PHPUnit_Smarty public function testEmpty4() { $this->smarty->disableSecurity(); + $this->getSmarty()->registerPlugin(\Smarty\Smarty::PLUGIN_FUNCTION, 'pass', function ($v) { return $v; }); $this->smarty->assign('var', new TestIsset()); $expected = ' true , false , false , true , true , true , false '; $this->assertEquals($expected, $this->smarty->fetch('string:{strip}{if empty($var->isNull)} true {else} false {/IF} @@ -114,6 +117,7 @@ class PhpFunctionTest extends PHPUnit_Smarty public function testIsset1() { $this->smarty->disableSecurity(); + $this->getSmarty()->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'pass', function ($v) { return $v; }); $this->smarty->assign('isNull', null); $this->smarty->assign('isSet', 1); $this->smarty->assign('arr', array('isNull' => null, 'isSet' => 1)); @@ -135,6 +139,7 @@ class PhpFunctionTest extends PHPUnit_Smarty { $this->smarty->disableSecurity(); $this->smarty->assign('var', new TestIsset()); + $this->smarty->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'pass', function ($v) { return $v; }); $expected = ' false , true , true , false , false , false , true '; $this->assertEquals($expected, $this->smarty->fetch('string:{strip}{if isset($var->isNull)} true {else} false {/IF} ,{if isset($var->isSet)} true {else} false {/IF} @@ -155,6 +160,7 @@ class PhpFunctionTest extends PHPUnit_Smarty public function testIsset3($strTemplate, $result) { $this->smarty->disableSecurity(); + $this->smarty->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'intval', 'intval'); $this->smarty->assign('varobject', new TestIsset()); $this->smarty->assign('vararray', $vararray = array( @@ -198,15 +204,6 @@ class PhpFunctionTest extends PHPUnit_Smarty } } -/** - * @param mixed $v - * - * @return mixed - */ -function pass($v) { - return $v; -} - /** * Class TestIsset */