Convert isset and empty to modifiercomilers, and smooth the error handling to fix unit tests.

This commit is contained in:
Simon Wisselink
2023-01-24 12:01:20 +01:00
parent 431d77505f
commit 9cc60f5e38
14 changed files with 127 additions and 86 deletions

View File

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

View File

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

View File

@@ -0,0 +1,25 @@
<?php
namespace Smarty\Compile\Modifier;
use Smarty\CompilerException;
/**
* Smarty isset modifier plugin
*/
class IssetModifierCompiler extends Base {
public function compile($params, \Smarty\Compiler\Template $compiler) {
$params = array_filter($params, function($v) { return !empty($v); });
if (count($params) < 1) {
throw new CompilerException("Invalid number of arguments for isset. isset expects at least one parameter.");
}
$tests = [];
foreach ($params as $param) {
$tests[] = 'null !== (' . $param . ' ?? null)';
}
return '(' . implode(' && ', $tests) . ')';
}
}

View File

@@ -11,6 +11,8 @@
namespace Smarty\Compile;
use Smarty\Compile\Base;
use Smarty\Compiler\Template;
use Smarty\CompilerException;
/**
* Smarty Internal Plugin Compile Modifier Class
@@ -35,9 +37,8 @@ class ModifierCompiler extends Base {
$compiler->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);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Smarty\Extension;
use Smarty\Exception;
class CallbackWrapper {
/**
* @var callback
*/
private $callback;
/**
* @var string
*/
private $modifierName;
/**
* @param string $modifierName
* @param callback $callback
*/
public function __construct(string $modifierName, $callback) {
$this->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);
}
}
}

View File

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

View File

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

View File

@@ -1,27 +0,0 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* empty(mixed $var): bool
*
* Returns true if var does not exist or has a value that is empty or equal to zero, aka falsey, see conversion to
* boolean. Otherwise returns false.
*
* No warning is generated if the variable does not exist. That means empty() is essentially the concise equivalent
* to !isset($var) || $var == false.
*/
class EmptyHandler extends Base {
public function handle($params, Template $template) {
if (count($params) !== 1) {
throw new Exception("Invalid number of arguments for empty. empty expects exactly 1 parameter.");
}
return empty(reset($params));
}
}

View File

@@ -1,30 +0,0 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* Determines if a variable is declared and is different than null
* `isset(mixed $var, mixed ...$vars): bool`
*
* Returns true if var exists and has any value other than null. false otherwise.
*/
class IssetHandler extends Base {
public function handle($params, Template $template) {
if (count($params) === 0) {
throw new Exception("Invalid number of arguments for isset. isset expects at least 1 parameter.");
}
foreach ($params as $param) {
if (!isset($param)) {
return false;
}
}
return true;
}
}

View File

@@ -7,6 +7,7 @@ use RecursiveIteratorIterator;
use Smarty\Cacheresource\File;
use Smarty\Extension\Base;
use Smarty\Extension\BCPluginsAdapter;
use Smarty\Extension\CallbackWrapper;
use Smarty\Extension\CoreExtension;
use Smarty\Extension\DefaultExtension;
use Smarty\Extension\ExtensionInterface;
@@ -1672,7 +1673,7 @@ class Smarty extends \Smarty\TemplateBase
public function getModifierCallback(string $modifierName) {
foreach ($this->getExtensions() as $extension) {
if ($callback = $extension->getModifierCallback($modifierName)) {
return $callback;
return [new CallbackWrapper($modifierName, $callback), 'handle'];
}
}
return null;

View File

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

View File

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

View File

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

View File

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