diff --git a/CHANGELOG.md b/CHANGELOG.md index 6be5190e6..9d98abbb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Too many shorthand attributes error when using a modifier as a function with more than 3 parameters in an expression [#949](https://github.com/smarty-php/smarty/issues/949) + +### Removed +- Dropped support for undocumented `{time()}` added in v5.0.0 since we already have the documented `{$smarty.now}` + ## [5.0.0-rc3] - 2024-02-26 ### Added diff --git a/docs/designers/language-basic-syntax/language-syntax-variables.md b/docs/designers/language-basic-syntax/language-syntax-variables.md index c0285d078..78d309ace 100644 --- a/docs/designers/language-basic-syntax/language-syntax-variables.md +++ b/docs/designers/language-basic-syntax/language-syntax-variables.md @@ -88,9 +88,6 @@ Object chaining: {$object->method1($x)->method2($y)} -Direct PHP function access: - -{time()} ``` > **Note** diff --git a/src/Compile/FunctionCallCompiler.php b/src/Compile/FunctionCallCompiler.php index 89b91021b..8934c8d7c 100644 --- a/src/Compile/FunctionCallCompiler.php +++ b/src/Compile/FunctionCallCompiler.php @@ -34,7 +34,7 @@ class FunctionCallCompiler extends Base { * * @var array */ - protected $shorttag_order = ['var1', 'var2', 'var3']; + protected $shorttag_order = []; /** * Compiles code for the execution of a registered function @@ -58,19 +58,15 @@ public function compile($args, Template $compiler, $parameter = [], $tag = null, $_paramsArray = $this->formatParamsArray($_attr); $_params = 'array(' . implode(',', $_paramsArray) . ')'; - 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 ($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 { + $compiler->trigger_template_error("unknown function '{$function}'", null, true); } if (!empty($parameter['modifierlist'])) { diff --git a/src/Compile/Modifier/IsArrayModifierCompiler.php b/src/Compile/Modifier/IsArrayModifierCompiler.php new file mode 100644 index 000000000..0e66bb521 --- /dev/null +++ b/src/Compile/Modifier/IsArrayModifierCompiler.php @@ -0,0 +1,19 @@ +functionCallCompiler->compile($args, $this, $parameter, $base_tag, $base_tag); } + public function compileModifierInExpression(string $function, array $_attr) { + $value = array_shift($_attr); + return $this->compileModifier([array_merge([$function], $_attr)], $value); + } + /** * @return TemplateParser|null */ diff --git a/src/Extension/DefaultExtension.php b/src/Extension/DefaultExtension.php index 83f110f77..e400dddbb 100644 --- a/src/Extension/DefaultExtension.php +++ b/src/Extension/DefaultExtension.php @@ -2,6 +2,8 @@ namespace Smarty\Extension; +use Smarty\Exception; + class DefaultExtension extends Base { private $modifiers = []; @@ -27,6 +29,7 @@ public function getModifierCompiler(string $modifier): ?\Smarty\Compile\Modifier 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 'is_array': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\IsArrayModifierCompiler(); break; case 'isset': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\IssetModifierCompiler(); break; case 'json_encode': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\JsonEncodeModifierCompiler(); break; case 'lower': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\LowerModifierCompiler(); break; @@ -57,6 +60,7 @@ public function getModifierCallback(string $modifierName) { case 'escape': return [$this, 'smarty_modifier_escape']; case 'explode': return [$this, 'smarty_modifier_explode']; case 'implode': return [$this, 'smarty_modifier_implode']; + case 'in_array': return [$this, 'smarty_modifier_in_array']; case 'join': return [$this, 'smarty_modifier_join']; case 'mb_wordwrap': return [$this, 'smarty_modifier_mb_wordwrap']; case 'number_format': return [$this, 'smarty_modifier_number_format']; @@ -87,12 +91,8 @@ public function getFunctionHandler(string $functionName): ?\Smarty\FunctionHandl case 'html_select_date': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlSelectDate(); break; case 'html_select_time': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlSelectTime(); break; 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 '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; - case 'time': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Time(); break; } return $this->functionHandlers[$functionName] ?? null; @@ -570,6 +570,23 @@ public function smarty_modifier_implode($values, $separator = '') return implode((string) ($separator ?? ''), (array) $values); } + /** + * Smarty in_array modifier plugin + * Type: modifier + * Name: in_array + * Purpose: test if value is contained in an array + * + * @param mixed $needle + * @param array $array + * @param bool $strict + * + * @return bool + */ + public function smarty_modifier_in_array($needle, $array, $strict = false) + { + return in_array($needle, (array) $array, (bool) $strict); + } + /** * Smarty join modifier plugin * Type: modifier diff --git a/src/FunctionHandler/InArray.php b/src/FunctionHandler/InArray.php deleted file mode 100644 index 1e630eb30..000000000 --- a/src/FunctionHandler/InArray.php +++ /dev/null @@ -1,30 +0,0 @@ - 3) { - throw new Exception("Invalid number of arguments for in_array. in_arrays expects 2 or 3 parameters."); - } - - // default to false, true if param 3 is set to true - $needle = $params[0]; - $haystack = (array) $params[1]; - $strict = count($params) == 3 && $params[2]; - - return in_array($needle, $haystack, $strict); - } - -} \ No newline at end of file diff --git a/src/FunctionHandler/IsArray.php b/src/FunctionHandler/IsArray.php deleted file mode 100644 index 06cd585d6..000000000 --- a/src/FunctionHandler/IsArray.php +++ /dev/null @@ -1,21 +0,0 @@ - 0) { - throw new Exception("Invalid number of arguments for time. time expects no parameters."); - } - return time(); - } - -} \ No newline at end of file diff --git a/src/Parser/TemplateParser.php b/src/Parser/TemplateParser.php index 6304581a0..e55c1f034 100644 --- a/src/Parser/TemplateParser.php +++ b/src/Parser/TemplateParser.php @@ -2675,7 +2675,7 @@ public function yy_r147(){ } // line 1063 "src/Parser/TemplateParser.y" public function yy_r148(){ - $this->_retvalue = $this->compiler->compileFunctionCall($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); + $this->_retvalue = $this->compiler->compileModifierInExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } // line 1071 "src/Parser/TemplateParser.y" public function yy_r149(){ diff --git a/src/Parser/TemplateParser.y b/src/Parser/TemplateParser.y index b081f4b13..7305dcc81 100644 --- a/src/Parser/TemplateParser.y +++ b/src/Parser/TemplateParser.y @@ -1061,7 +1061,7 @@ objectelement(res)::= PTR method(f). { // function // function(res) ::= ns1(f) OPENP params(p) CLOSEP. { - res = $this->compiler->compileFunctionCall(f, p); + res = $this->compiler->compileModifierInExpression(f, p); } diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginFunction/TimeTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginFunction/TimeTest.php deleted file mode 100644 index d64fd92d2..000000000 --- a/tests/UnitTests/TemplateSource/TagTests/PluginFunction/TimeTest.php +++ /dev/null @@ -1,20 +0,0 @@ -setUpSmarty(__DIR__); - } - - public function testBasicSyntax() { - $this->assertStringMatchesFormat('%d', $this->smarty->fetch("string:{time()}")); - } - - public function testInvalidParameters() { - $this->expectException(\Smarty\Exception::class); - $this->expectExceptionMessage('Invalid number of arguments'); - $this->assertEquals("", $this->smarty->fetch("string:{time(3, 'foo')}")); - } - -} \ 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 cfbb32bb1..e2a388625 100644 --- a/tests/UnitTests/TemplateSource/ValueTests/PHPfunctions/PhpFunctionTest.php +++ b/tests/UnitTests/TemplateSource/ValueTests/PHPfunctions/PhpFunctionTest.php @@ -79,7 +79,7 @@ public function testEmpty2() public function testEmpty3() { $this->smarty->disableSecurity(); - $this->getSmarty()->registerPlugin(\Smarty\Smarty::PLUGIN_FUNCTION, 'pass', function ($v) { return $v; }); + $this->getSmarty()->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'pass', function ($v) { return $v; }); $this->smarty->assign('var', array(true, (int) 1, (float) 0.1, @@ -99,7 +99,7 @@ public function testEmpty3() public function testEmpty4() { $this->smarty->disableSecurity(); - $this->getSmarty()->registerPlugin(\Smarty\Smarty::PLUGIN_FUNCTION, 'pass', function ($v) { return $v; }); + $this->getSmarty()->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, '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} diff --git a/tests/UnitTests/TemplateSource/_Issues/TooManyShorthandAttributes949Test.php b/tests/UnitTests/TemplateSource/_Issues/TooManyShorthandAttributes949Test.php new file mode 100644 index 000000000..cfe55752f --- /dev/null +++ b/tests/UnitTests/TemplateSource/_Issues/TooManyShorthandAttributes949Test.php @@ -0,0 +1,16 @@ +registerPlugin('modifier', 'var_dump', 'var_dump'); + $templateStr = "eval:{\$a = 'blah'}{\$b = array()}{if var_dump('', \$a, \$b, 2)|noprint}blah{else}nah{/if}"; + $this->assertEquals( + 'nah', + $smarty->fetch($templateStr) + ); + } + +} \ No newline at end of file