Fix Too many shorthand attributes error when using a modifier as a function with more than 3 parameters in an expression (#953)

Fixes #949
This commit is contained in:
Simon Wisselink
2024-03-15 10:26:17 +01:00
committed by GitHub
parent 293bc20db0
commit 17da1f585e
15 changed files with 80 additions and 144 deletions

View File

@@ -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

View File

@@ -88,9 +88,6 @@ Object chaining:
{$object->method1($x)->method2($y)}
Direct PHP function access:
{time()}
```
> **Note**

View File

@@ -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 @@ class FunctionCallCompiler extends Base {
$_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'])) {

View File

@@ -0,0 +1,19 @@
<?php
namespace Smarty\Compile\Modifier;
use Smarty\CompilerException;
/**
* Smarty is_array modifier plugin
*/
class IsArrayModifierCompiler extends Base {
public function compile($params, \Smarty\Compiler\Template $compiler) {
if (count($params) !== 1) {
throw new CompilerException("Invalid number of arguments for is_array. is_array expects exactly 1 parameter.");
}
return 'is_array(' . $params[0] . ')';
}
}

View File

@@ -1369,6 +1369,11 @@ class Template extends BaseCompiler {
return $this->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
*/

View File

@@ -2,6 +2,8 @@
namespace Smarty\Extension;
use Smarty\Exception;
class DefaultExtension extends Base {
private $modifiers = [];
@@ -27,6 +29,7 @@ class DefaultExtension extends Base {
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 @@ class DefaultExtension extends Base {
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 @@ class DefaultExtension extends Base {
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 @@ class DefaultExtension extends Base {
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

View File

@@ -1,30 +0,0 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* in_array(mixed $needle, array $haystack, bool $strict = false): bool
* Returns true if needle is found in the array, false otherwise
*/
class InArray extends Base {
public function handle($params, Template $template) {
$params = array_values($params ?? []);
if (count($params) < 2 || count($params) > 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);
}
}

View File

@@ -1,21 +0,0 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* is_array(mixed $value): bool
* Returns true if value is an array, false otherwise.
*/
class IsArray extends Base {
public function handle($params, Template $template) {
if (count($params) !== 1) {
throw new Exception("Invalid number of arguments for is_array. is_array expects exactly 1 parameter.");
}
return is_array(reset($params));
}
}

View File

@@ -1,28 +0,0 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* Get string length
*
* strlen(string $string): int
*
* Returns length of the string on success, and 0 if the string is empty.
*/
class Strlen extends Base {
public function handle($params, Template $template) {
$params = array_values($params ?? []);
if (count($params) !== 1) {
throw new Exception("Invalid number of arguments for strlen. strlen expects exactly 1 parameter.");
}
return strlen((string) $params[0]);
}
}

View File

@@ -1,21 +0,0 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* is_array(mixed $value): bool
* Returns true if value is an array, false otherwise.
*/
class Time extends Base {
public function handle($params, Template $template) {
if (count($params) > 0) {
throw new Exception("Invalid number of arguments for time. time expects no parameters.");
}
return time();
}
}

View File

@@ -2675,7 +2675,7 @@ public static $yy_action = array(
}
// 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(){

View File

@@ -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);
}

View File

@@ -1,20 +0,0 @@
<?php
namespace UnitTests\TemplateSource\TagTests\PluginFunction;
class TimeTest extends \PHPUnit_Smarty {
public function setUp(): void {
$this->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')}"));
}
}

View File

@@ -79,7 +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->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 @@ class PhpFunctionTest extends PHPUnit_Smarty
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}

View File

@@ -0,0 +1,16 @@
<?php
class TooManyShorthandAttributes949Test extends PHPUnit_Smarty
{
public function testPregMatchAll() {
$smarty = new \Smarty\Smarty();
$smarty->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)
);
}
}