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
This commit is contained in:
Simon Wisselink
2024-12-23 01:29:07 +01:00
committed by GitHub
parent f47ac761af
commit cf9de567c1
7 changed files with 1172 additions and 955 deletions

View File

@ -505,7 +505,7 @@ class Template extends BaseCompiler {
* *
* @return string * @return string
*/ */
public function compileVariable($variable) { public function triggerTagNoCache($variable): void {
if (!strpos($variable, '(')) { if (!strpos($variable, '(')) {
// not a variable variable // not a variable variable
$var = trim($variable, '\''); $var = trim($variable, '\'');
@ -516,7 +516,6 @@ class Template extends BaseCompiler {
false false
)->isNocache(); )->isNocache();
} }
return '$_smarty_tpl->getValue(' . $variable . ')';
} }
/** /**

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ use \Smarty\ParseTree\Code;
use \Smarty\ParseTree\Dq; use \Smarty\ParseTree\Dq;
use \Smarty\ParseTree\DqContent; use \Smarty\ParseTree\DqContent;
use \Smarty\ParseTree\Tag; use \Smarty\ParseTree\Tag;
use \Smarty\CompilerException;
/** /**
* Smarty Template Parser Class * Smarty Template Parser Class
@ -306,7 +306,8 @@ smartytag(A) ::= SIMPELOUTPUT(B). {
$attributes[] = 'nocache'; $attributes[] = 'nocache';
$var = $match[1]; $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} // simple tag like {name}
@ -375,7 +376,7 @@ outattr(A) ::= output(B) attributes(C). {
A = array(B,C); A = array(B,C);
} }
output(A) ::= variable(B). { output(A) ::= variablevalue(B). {
A = B; A = B;
} }
output(A) ::= value(B). { output(A) ::= value(B). {
@ -689,7 +690,8 @@ nullcoalescing(res) ::= expr(v) QMARK QMARK expr(e2). {
// ternary // ternary
// //
ternary(res) ::= expr(v) QMARK DOLLARID(e1) COLON expr(e2). { 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). { ternary(res) ::= expr(v) QMARK value(e1) COLON expr(e2). {
@ -706,7 +708,7 @@ ternary(res) ::= expr(v) QMARK COLON expr(e2). {
} }
// value // value
value(res) ::= variable(v). { value(res) ::= variablevalue(v). {
res = v; res = v;
} }
@ -724,7 +726,7 @@ value(res) ::= TYPECAST(t) value(v). {
res = t.v; res = t.v;
} }
value(res) ::= variable(v) INCDEC(o). { value(res) ::= variablevalue(v) INCDEC(o). {
res = v.o; res = v.o;
} }
@ -771,10 +773,10 @@ value(res) ::= OPENP expr(e) CLOSEP. {
res = '('. e .')'; res = '('. e .')';
} }
value(res) ::= variable(v1) INSTANCEOF(i) ns1(v2). { value(res) ::= variablevalue(v1) INSTANCEOF(i) ns1(v2). {
res = v1.i.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; res = v1.i.v2;
} }
@ -797,7 +799,8 @@ value(res) ::= varindexed(vi) DOUBLECOLON static_class_access(r). {
if (vi['var'] === '\'smarty\'') { if (vi['var'] === '\'smarty\'') {
$this->compiler->appendPrefixCode("<?php {$prefixVar} = ". (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']).';?>'); $this->compiler->appendPrefixCode("<?php {$prefixVar} = ". (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']).';?>');
} else { } 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]; 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 // variables
// //
// Smarty variable (optional array) // Smarty variable (optional array)
variable(res) ::= DOLLARID(i). { 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). { variable(res) ::= varindexed(vi). {
if (vi['var'] === '\'smarty\'') { if (vi['var'] === '\'smarty\'') {
$smarty_var = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']); $smarty_var = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']);
res = $smarty_var; res = array('true', $smarty_var);
} else { } else {
// used for array reset,next,prev,end,current // used for array reset,next,prev,end,current
$this->last_variable = vi['var']; $this->last_variable = vi['var'];
$this->last_index = vi['smarty_internal_index']; $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 with property
variable(res) ::= varvar(v) AT ID(p). { variable(res) ::= varvar(v) AT ID(p). {
res = '$_smarty_tpl->getVariable('. v .')->'.p; res = array('true', '$_smarty_tpl->getVariable('. v .')->'.p);
} }
// object // object
variable(res) ::= object(o). { variable(res) ::= object(o). {
res = o; res = array('true', o);
} }
// config variable // config variable
variable(res) ::= HATCH ID(i) HATCH. { configvariable(res) ::= HATCH ID(i) HATCH. {
res = $this->compiler->compileConfigVariable('\'' . i . '\''); 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)'; 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); 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)'; 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). { varindexed(res) ::= DOLLARID(i) arrayindex(a). {
res = array('var'=>'\''.substr(i,1).'\'', 'smarty_internal_index'=>a); res = array('var'=>'\''.substr(i,1).'\'', 'smarty_internal_index'=>a);
} }
@ -918,14 +955,17 @@ arrayindex ::= . {
// single index definition // single index definition
// Smarty2 style index // Smarty2 style index
indexdef(res) ::= DOT DOLLARID(i). { 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). { 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). { 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). { indexdef(res) ::= DOT ID(i). {
@ -956,9 +996,10 @@ indexdef(res) ::= OPENB INTEGER(n) CLOSEB. {
res = '['.n.']'; res = '['.n.']';
} }
indexdef(res) ::= OPENB DOLLARID(i) CLOSEB. { 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.']'; res = '['.v.']';
} }
indexdef(res) ::= OPENB value(v) CLOSEB. { indexdef(res) ::= OPENB value(v) CLOSEB. {
@ -1000,7 +1041,8 @@ varvarele(res) ::= ID(s). {
} }
varvarele(res) ::= SIMPELOUTPUT(i). { varvarele(res) ::= SIMPELOUTPUT(i). {
$var = trim(substr(i, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' $'); $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 // variable sections of element
@ -1015,7 +1057,8 @@ object(res) ::= varindexed(vi) objectchain(oc). {
if (vi['var'] === '\'smarty\'') { if (vi['var'] === '\'smarty\'') {
res = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']).oc; res = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']).oc;
} else { } 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) { if ($this->security) {
$this->compiler->trigger_template_error (self::ERR2); $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). { objectelement(res)::= PTR LDEL expr(e) RDEL arrayindex(a). {
@ -1067,8 +1111,41 @@ objectelement(res)::= PTR method(f). {
// //
// function // function
// //
function(res) ::= ns1(f) OPENP params(p) CLOSEP. { function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP. {
res = $this->compiler->compileModifierInExpression(f, p);
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); $this->compiler->trigger_template_error (self::ERR2);
} }
$prefixVar = $this->compiler->getNewPrefixVariable(); $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) .')'; res = $prefixVar .'('. implode(',',p) .')';
} }
@ -1282,7 +1360,7 @@ doublequoted(res) ::= doublequotedcontent(o). {
res = new Dq($this, o); res = new Dq($this, o);
} }
doublequotedcontent(res) ::= BACKTICK variable(v) BACKTICK. { doublequotedcontent(res) ::= BACKTICK variablevalue(v) BACKTICK. {
res = new Code('(string)'.v); res = new Code('(string)'.v);
} }
@ -1294,7 +1372,7 @@ doublequotedcontent(res) ::= DOLLARID(i). {
res = new Code('(string)$_smarty_tpl->getValue(\''. substr(i,1) .'\')'); 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); res = new Code('(string)'.v);
} }

View File

@ -65,15 +65,34 @@ class UndefinedTemplateVarTest extends PHPUnit_Smarty
*/ */
public function testError() public function testError()
{ {
$this->smarty->error_unassigned = true; $this->smarty->error_unassigned = true;
$this->expectException(PHPUnit\Framework\Error\Error::class); $this->expectException(PHPUnit\Framework\Error\Error::class);
$this->expectExceptionMessage('Undefined '); $this->expectExceptionMessage('Undefined ');
$e1 = error_reporting(); $e1 = error_reporting();
$this->assertEquals('undefined = ', $this->smarty->fetch('001_main.tpl')); $this->assertEquals('undefined = ', $this->smarty->fetch('001_main.tpl'));
$e2 = error_reporting(); $e2 = error_reporting();
$this->assertEquals($e1, $e2); $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() { public function testUndefinedSimpleVar() {
$this->smarty->muteUndefinedOrNullWarnings(); $this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $undef}def{/if}b'); $tpl = $this->smarty->createTemplate('string:a{if $undef}def{/if}b');
@ -133,30 +152,30 @@ class UndefinedTemplateVarTest extends PHPUnit_Smarty
} }
public function testDereferenceOnNull() { public function testDereferenceOnNull() {
$this->smarty->setErrorReporting(E_ALL & ~E_WARNING & ~E_NOTICE); $this->smarty->setErrorReporting(E_ALL & ~E_WARNING & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings(); $this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b'); $tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', null); $this->smarty->assign('object', null);
$this->assertEquals("ab", $this->smarty->fetch($tpl)); $this->assertEquals("ab", $this->smarty->fetch($tpl));
} }
public function testDereferenceOnBool() { public function testDereferenceOnBool() {
$this->smarty->setErrorReporting(E_ALL & ~E_NOTICE); $this->smarty->setErrorReporting(E_ALL & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings(); $this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b'); $tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', false); $this->smarty->assign('object', false);
$this->assertEquals("ab", $this->smarty->fetch($tpl)); $this->assertEquals("ab", $this->smarty->fetch($tpl));
} }
public function testDereferenceOnString() { public function testDereferenceOnString() {
$this->smarty->setErrorReporting(E_ALL & ~E_NOTICE); $this->smarty->setErrorReporting(E_ALL & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings(); $this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b'); $tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', 'xyz'); $this->smarty->assign('object', 'xyz');
$this->assertEquals("ab", $this->smarty->fetch($tpl)); $this->assertEquals("ab", $this->smarty->fetch($tpl));
} }
} }

View File

@ -0,0 +1 @@
undefined = {if empty($foo)}{/if}

View File

@ -0,0 +1 @@
undefined = {if isset($foo)}{/if}

View File

@ -41,10 +41,9 @@ class CompileIfTest extends PHPUnit_Smarty
$name = empty($testName) ? $testNumber : $testName; $name = empty($testName) ? $testNumber : $testName;
$file = "testIf_{$name}.tpl"; $file = "testIf_{$name}.tpl";
$this->makeTemplateFile($file, $code); $this->makeTemplateFile($file, $code);
$this->smarty->assignGlobal('file', $file); $this->smarty->assign('file', $file);
$this->smarty->assign('bar', 'buh'); $this->smarty->assign('bar', 'buh');
$this->assertEquals($result, $this->smarty->fetch($file), $this->assertEquals($result, $this->smarty->fetch($file), "testIf - {$code} - {$name}");
"testIf - {$code} - {$name}");
} }
/* /*