Compare commits

...

5 Commits

Author SHA1 Message Date
Simon Wisselink c6bff57950 Merge branch 'release/5.4.3' 2024-12-23 01:38:44 +01:00
Simon Wisselink 07faafe4da version bump 2024-12-23 01:38:41 +01:00
Simon Wisselink 295b7fb582 move to official php8.4 image for unit testing in CI 2024-12-23 01:34:55 +01:00
Simon Wisselink cf9de567c1 Restore special handling of isset and empty as it was in v4. Fixes #1063 (#1093)
* Restore special handling of isset and empty as it was in v4. Fixes #1063
2024-12-23 01:29:07 +01:00
Simon Wisselink f47ac761af replace/qualify call_user_func_array. (#1083)
Fixes #1074
2024-11-20 23:05:14 +01:00
14 changed files with 1188 additions and 966 deletions
+4
View File
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [5.4.3] - 2024-12-23
- Fix PHP backtraces by qualifying/replacing `call_user_func_array` calls [#1074](https://github.com/smarty-php/smarty/issues/1074)
## [5.4.2] - 2024-11-20
+1 -1
View File
@@ -14,6 +14,6 @@ class BlockPluginWrapper extends Base {
}
public function handle($params, $content, Template $template, &$repeat) {
return call_user_func_array($this->callback, [$params, $content, &$template, &$repeat]);
return \call_user_func_array($this->callback, [$params, $content, &$template, &$repeat]);
}
}
+6 -6
View File
@@ -505,7 +505,7 @@ class Template extends BaseCompiler {
*
* @return string
*/
public function compileVariable($variable) {
public function triggerTagNoCache($variable): void {
if (!strpos($variable, '(')) {
// not a variable variable
$var = trim($variable, '\'');
@@ -516,7 +516,6 @@ class Template extends BaseCompiler {
false
)->isNocache();
}
return '$_smarty_tpl->getValue(' . $variable . ')';
}
/**
@@ -665,7 +664,7 @@ class Template extends BaseCompiler {
$script = null;
$cacheable = true;
$result = call_user_func_array(
$result = \call_user_func_array(
$defaultPluginHandlerFunc,
[
$tag,
@@ -1281,9 +1280,10 @@ class Template extends BaseCompiler {
}
// call post compile callbacks
foreach ($this->postCompileCallbacks as $cb) {
$parameter = $cb;
$parameter[0] = $this;
call_user_func_array($cb[0], $parameter);
$callbackFunction = $cb[0];
$parameters = $cb;
$parameters[0] = $this;
$callbackFunction(...$parameters);
}
// return compiled code
return $this->prefixCompiledCode . $this->parser->retvalue . $this->postfixCompiledCode;
+1 -1
View File
@@ -26,7 +26,7 @@ class CallbackWrapper {
public function handle(...$params) {
try {
return call_user_func_array($this->callback, $params);
return ($this->callback)(...$params);
} catch (\ArgumentCountError $e) {
throw new Exception("Invalid number of arguments to modifier " . $this->modifierName);
}
File diff suppressed because it is too large Load Diff
+109 -31
View File
@@ -19,7 +19,7 @@ use \Smarty\ParseTree\Code;
use \Smarty\ParseTree\Dq;
use \Smarty\ParseTree\DqContent;
use \Smarty\ParseTree\Tag;
use \Smarty\CompilerException;
/**
* Smarty Template Parser Class
@@ -306,7 +306,8 @@ smartytag(A) ::= SIMPELOUTPUT(B). {
$attributes[] = 'nocache';
$var = $match[1];
}
A = $this->compiler->compilePrintExpression($this->compiler->compileVariable('\''.$var.'\''), $attributes);
$this->compiler->triggerTagNoCache($var);
A = $this->compiler->compilePrintExpression('$_smarty_tpl->getValue(\''.$var.'\')', $attributes);
}
// simple tag like {name}
@@ -375,7 +376,7 @@ outattr(A) ::= output(B) attributes(C). {
A = array(B,C);
}
output(A) ::= variable(B). {
output(A) ::= variablevalue(B). {
A = B;
}
output(A) ::= value(B). {
@@ -689,7 +690,8 @@ nullcoalescing(res) ::= expr(v) QMARK QMARK expr(e2). {
// ternary
//
ternary(res) ::= expr(v) QMARK DOLLARID(e1) COLON expr(e2). {
res = v.' ? '. $this->compiler->compileVariable('\''.substr(e1,1).'\'') . ' : '.e2;
$this->compiler->triggerTagNoCache(substr(e1,1));
res = v.' ? $_smarty_tpl->getValue(\''.substr(e1,1).'\') : '.e2;
}
ternary(res) ::= expr(v) QMARK value(e1) COLON expr(e2). {
@@ -706,7 +708,7 @@ ternary(res) ::= expr(v) QMARK COLON expr(e2). {
}
// value
value(res) ::= variable(v). {
value(res) ::= variablevalue(v). {
res = v;
}
@@ -724,7 +726,7 @@ value(res) ::= TYPECAST(t) value(v). {
res = t.v;
}
value(res) ::= variable(v) INCDEC(o). {
value(res) ::= variablevalue(v) INCDEC(o). {
res = v.o;
}
@@ -771,10 +773,10 @@ value(res) ::= OPENP expr(e) CLOSEP. {
res = '('. e .')';
}
value(res) ::= variable(v1) INSTANCEOF(i) ns1(v2). {
value(res) ::= variablevalue(v1) INSTANCEOF(i) ns1(v2). {
res = v1.i.v2;
}
value(res) ::= variable(v1) INSTANCEOF(i) variable(v2). {
value(res) ::= variablevalue(v1) INSTANCEOF(i) variablevalue(v2). {
res = v1.i.v2;
}
@@ -797,7 +799,8 @@ value(res) ::= varindexed(vi) DOUBLECOLON static_class_access(r). {
if (vi['var'] === '\'smarty\'') {
$this->compiler->appendPrefixCode("<?php {$prefixVar} = ". (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']).';?>');
} else {
$this->compiler->appendPrefixCode("<?php {$prefixVar} = ". $this->compiler->compileVariable(vi['var']).vi['smarty_internal_index'].';?>');
$this->compiler->triggerTagNoCache(vi['var']);
$this->compiler->appendPrefixCode("<?php {$prefixVar} = \$_smarty_tpl->getValue(" . vi['var'] . ')'.vi['smarty_internal_index'].';?>');
}
res = $prefixVar .'::'.r[0].r[1];
}
@@ -847,54 +850,88 @@ ns1(res) ::= NAMESPACE(i). {
}
// variable lists
// multiple variables
variablelist(res) ::= variablelist(l) COMMA variable(v). {
res = array_merge(l,array(v));
}
variablelist(res) ::= variablelist(l) COMMA expr(e). {
res = array_merge(l,array(e));
}
// single variable
variablelist(res) ::= variable(v). {
res = array(v);
}
// single expression
variablelist(res) ::= expr(e). {
res = array(e);
}
// no variable
variablelist(res) ::= . {
res = array();
}
//
// variables
//
// Smarty variable (optional array)
variable(res) ::= DOLLARID(i). {
res = $this->compiler->compileVariable('\''.substr(i,1).'\'');
$this->compiler->triggerTagNoCache(substr(i,1));
res = array('$_smarty_tpl->hasVariable(\''.substr(i,1).'\')','$_smarty_tpl->getValue(\''.substr(i,1).'\')');
}
variable(res) ::= varindexed(vi). {
if (vi['var'] === '\'smarty\'') {
$smarty_var = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']);
res = $smarty_var;
res = array('true', $smarty_var);
} else {
// used for array reset,next,prev,end,current
$this->last_variable = vi['var'];
$this->last_index = vi['smarty_internal_index'];
res = $this->compiler->compileVariable(vi['var']).vi['smarty_internal_index'];
$this->compiler->triggerTagNoCache(vi['var']);
res = array('true', '$_smarty_tpl->getValue(' . vi['var'] . ')'.vi['smarty_internal_index']);
}
}
// variable with property
variable(res) ::= varvar(v) AT ID(p). {
res = '$_smarty_tpl->getVariable('. v .')->'.p;
res = array('true', '$_smarty_tpl->getVariable('. v .')->'.p);
}
// object
variable(res) ::= object(o). {
res = o;
res = array('true', o);
}
// config variable
variable(res) ::= HATCH ID(i) HATCH. {
configvariable(res) ::= HATCH ID(i) HATCH. {
res = $this->compiler->compileConfigVariable('\'' . i . '\'');
}
variable(res) ::= HATCH ID(i) HATCH arrayindex(a). {
configvariable(res) ::= HATCH ID(i) HATCH arrayindex(a). {
res = '(is_array($tmp = ' . $this->compiler->compileConfigVariable('\'' . i . '\'') . ') ? $tmp'.a.' :null)';
}
variable(res) ::= HATCH variable(v) HATCH. {
configvariable(res) ::= HATCH variablevalue(v) HATCH. {
res = $this->compiler->compileConfigVariable(v);
}
variable(res) ::= HATCH variable(v) HATCH arrayindex(a). {
configvariable(res) ::= HATCH variablevalue(v) HATCH arrayindex(a). {
res = '(is_array($tmp = ' . $this->compiler->compileConfigVariable(v) . ') ? $tmp'.a.' : null)';
}
variablevalue(res) ::= variable(v). {
res = v[1];
}
variablevalue(res) ::= configvariable(v). {
res = v;
}
varindexed(res) ::= DOLLARID(i) arrayindex(a). {
res = array('var'=>'\''.substr(i,1).'\'', 'smarty_internal_index'=>a);
}
@@ -918,14 +955,17 @@ arrayindex ::= . {
// single index definition
// Smarty2 style index
indexdef(res) ::= DOT DOLLARID(i). {
res = '['.$this->compiler->compileVariable('\''.substr(i,1).'\'').']';
$this->compiler->triggerTagNoCache(substr(i,1));
res = '[$_smarty_tpl->getValue(\''.substr(i,1).'\')]';
}
indexdef(res) ::= DOT varvar(v). {
res = '['.$this->compiler->compileVariable(v).']';
$this->compiler->triggerTagNoCache(v);
res = '[$_smarty_tpl->getValue(' . v . ')]';
}
indexdef(res) ::= DOT varvar(v) AT ID(p). {
res = '['.$this->compiler->compileVariable(v).'->'.p.']';
$this->compiler->triggerTagNoCache(v);
res = '[$_smarty_tpl->getValue(' . v . ')->'.p.']';
}
indexdef(res) ::= DOT ID(i). {
@@ -956,9 +996,10 @@ indexdef(res) ::= OPENB INTEGER(n) CLOSEB. {
res = '['.n.']';
}
indexdef(res) ::= OPENB DOLLARID(i) CLOSEB. {
res = '['.$this->compiler->compileVariable('\''.substr(i,1).'\'').']';
$this->compiler->triggerTagNoCache(substr(i,1));
res = '[$_smarty_tpl->getValue(\''.substr(i,1).'\')]';
}
indexdef(res) ::= OPENB variable(v) CLOSEB. {
indexdef(res) ::= OPENB variablevalue(v) CLOSEB. {
res = '['.v.']';
}
indexdef(res) ::= OPENB value(v) CLOSEB. {
@@ -1000,7 +1041,8 @@ varvarele(res) ::= ID(s). {
}
varvarele(res) ::= SIMPELOUTPUT(i). {
$var = trim(substr(i, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' $');
res = $this->compiler->compileVariable('\''.$var.'\'');
$this->compiler->triggerTagNoCache($var);
res = '$_smarty_tpl->getValue(\''.$var.'\')';
}
// variable sections of element
@@ -1015,7 +1057,8 @@ object(res) ::= varindexed(vi) objectchain(oc). {
if (vi['var'] === '\'smarty\'') {
res = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']).oc;
} else {
res = $this->compiler->compileVariable(vi['var']).vi['smarty_internal_index'].oc;
$this->compiler->triggerTagNoCache(vi['var']);
res = '$_smarty_tpl->getValue(' . vi['var'] . ')'.vi['smarty_internal_index'].oc;
}
}
@@ -1041,7 +1084,8 @@ objectelement(res)::= PTR varvar(v) arrayindex(a). {
if ($this->security) {
$this->compiler->trigger_template_error (self::ERR2);
}
res = '->{'.$this->compiler->compileVariable(v).a.'}';
$this->compiler->triggerTagNoCache(v);
res = '->{$_smarty_tpl->getValue(' . v . ')'.a.'}';
}
objectelement(res)::= PTR LDEL expr(e) RDEL arrayindex(a). {
@@ -1067,8 +1111,41 @@ objectelement(res)::= PTR method(f). {
//
// function
//
function(res) ::= ns1(f) OPENP params(p) CLOSEP. {
res = $this->compiler->compileModifierInExpression(f, p);
function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP. {
if (f == 'isset') {
res = '(true';
if (count(v) == 0) {
throw new CompilerException("Invalid number of arguments for isset. isset expects at least one parameter.");
}
foreach (v as $value) {
if (is_array($value)) {
res .= ' && (' . $value[0] . ' && null !== (' . $value[1] . ' ?? null))';
} else {
res .= ' && (' . $value . ' !== null)';
}
}
res .= ')';
} elseif (f == 'empty') {
if (count(v) != 1) {
throw new CompilerException("Invalid number of arguments for empty. empty expects at exactly one parameter.");
}
if (is_array(v[0])) {
res .= '( !' . v[0][0] . ' || empty(' . v[0][1] . '))';
} else {
res = 'false == ' . v[0];
}
} else {
$p = array();
foreach (v as $value) {
if (is_array($value)) {
$p[] = $value[1];
} else {
$p[] = $value;
}
}
res = $this->compiler->compileModifierInExpression(f, $p);
}
}
@@ -1087,7 +1164,8 @@ method(res) ::= DOLLARID(f) OPENP params(p) CLOSEP. {
$this->compiler->trigger_template_error (self::ERR2);
}
$prefixVar = $this->compiler->getNewPrefixVariable();
$this->compiler->appendPrefixCode("<?php {$prefixVar} = ".$this->compiler->compileVariable('\''.substr(f,1).'\'').';?>');
$this->compiler->triggerTagNoCache(substr(f,1));
$this->compiler->appendPrefixCode("<?php {$prefixVar} = \$_smarty_tpl->getValue('".substr(f,1).'\')'.';?>');
res = $prefixVar .'('. implode(',',p) .')';
}
@@ -1282,7 +1360,7 @@ doublequoted(res) ::= doublequotedcontent(o). {
res = new Dq($this, o);
}
doublequotedcontent(res) ::= BACKTICK variable(v) BACKTICK. {
doublequotedcontent(res) ::= BACKTICK variablevalue(v) BACKTICK. {
res = new Code('(string)'.v);
}
@@ -1294,7 +1372,7 @@ doublequotedcontent(res) ::= DOLLARID(i). {
res = new Code('(string)$_smarty_tpl->getValue(\''. substr(i,1) .'\')');
}
doublequotedcontent(res) ::= LDEL variable(v) RDEL. {
doublequotedcontent(res) ::= LDEL variablevalue(v) RDEL. {
res = new Code('(string)'.v);
}
+2 -2
View File
@@ -26,7 +26,7 @@ class DefaultPluginHandlerRuntime {
$script = null;
$cacheable = null;
return (call_user_func_array(
return (\call_user_func_array(
$this->defaultPluginHandler,
[
$tag,
@@ -54,7 +54,7 @@ class DefaultPluginHandlerRuntime {
$script = null;
$cacheable = null;
if (call_user_func_array(
if (\call_user_func_array(
$this->defaultPluginHandler,
[
$tag,
+1 -1
View File
@@ -54,7 +54,7 @@ class Smarty extends \Smarty\TemplateBase {
/**
* smarty version
*/
const SMARTY_VERSION = '5.4.2';
const SMARTY_VERSION = '5.4.3';
/**
* define caching modes
+1 -1
View File
@@ -203,7 +203,7 @@ class Source {
*/
public function _getDefaultTemplate($default_handler) {
$_content = $_timestamp = null;
$_return = call_user_func_array(
$_return = \call_user_func_array(
$default_handler,
[$this->type, $this->name, &$_content, &$_timestamp, $this->smarty]
);
@@ -65,15 +65,34 @@ class UndefinedTemplateVarTest extends PHPUnit_Smarty
*/
public function testError()
{
$this->smarty->error_unassigned = true;
$this->smarty->error_unassigned = true;
$this->expectException(PHPUnit\Framework\Error\Error::class);
$this->expectExceptionMessage('Undefined ');
$this->expectExceptionMessage('Undefined ');
$e1 = error_reporting();
$this->assertEquals('undefined = ', $this->smarty->fetch('001_main.tpl'));
$e2 = error_reporting();
$this->assertEquals($e1, $e2);
}
public function testNoError()
{
$this->smarty->error_unassigned = false;
$e1 = error_reporting();
$this->assertEquals('undefined = ', $this->smarty->fetch('001_main.tpl'));
$e2 = error_reporting();
$this->assertEquals($e1, $e2);
}
public function testNoErrorForIssetOrEmpty()
{
$this->smarty->error_unassigned = true;
$e1 = error_reporting();
$this->assertEquals('undefined = ', $this->smarty->fetch('001_isset.tpl'));
$this->assertEquals('undefined = ', $this->smarty->fetch('001_empty.tpl'));
$e2 = error_reporting();
$this->assertEquals($e1, $e2);
}
public function testUndefinedSimpleVar() {
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $undef}def{/if}b');
@@ -133,30 +152,30 @@ class UndefinedTemplateVarTest extends PHPUnit_Smarty
}
public function testDereferenceOnNull() {
$this->smarty->setErrorReporting(E_ALL & ~E_WARNING & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', null);
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
public function testDereferenceOnNull() {
$this->smarty->setErrorReporting(E_ALL & ~E_WARNING & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', null);
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
public function testDereferenceOnBool() {
$this->smarty->setErrorReporting(E_ALL & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', false);
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
public function testDereferenceOnBool() {
$this->smarty->setErrorReporting(E_ALL & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', false);
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
public function testDereferenceOnString() {
$this->smarty->setErrorReporting(E_ALL & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', 'xyz');
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
public function testDereferenceOnString() {
$this->smarty->setErrorReporting(E_ALL & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', 'xyz');
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
}
@@ -0,0 +1 @@
undefined = {if empty($foo)}{/if}
@@ -0,0 +1 @@
undefined = {if isset($foo)}{/if}
@@ -41,10 +41,9 @@ class CompileIfTest extends PHPUnit_Smarty
$name = empty($testName) ? $testNumber : $testName;
$file = "testIf_{$name}.tpl";
$this->makeTemplateFile($file, $code);
$this->smarty->assignGlobal('file', $file);
$this->smarty->assign('file', $file);
$this->smarty->assign('bar', 'buh');
$this->assertEquals($result, $this->smarty->fetch($file),
"testIf - {$code} - {$name}");
$this->assertEquals($result, $this->smarty->fetch($file), "testIf - {$code} - {$name}");
}
/*
+1 -1
View File
@@ -1,4 +1,4 @@
FROM php:8.4-rc-cli-bullseye
FROM php:8.4-cli-bullseye
## Basic utilities
RUN apt-get update -yqq && apt-get install -y curl apt-utils git zip unzip