Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using PHP functions and static class methods in expressions now also triggers a deprecation notice #880

Merged
merged 10 commits into from
Mar 18, 2024
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Using unregistered static class methods in expressions now also triggers a deprecation notice because we will drop support for this in the next major release [#813](https://github.com/smarty-php/smarty/issues/813)

## [4.4.1] - 2024-02-26
- Fixed internal release-tooling

Expand Down
3 changes: 3 additions & 0 deletions lexer/smarty_internal_templateparser.y
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,9 @@ value(res) ::= ns1(c)DOUBLECOLON static_class_access(s). {
if (isset($this->smarty->registered_classes[c])) {
res = $this->smarty->registered_classes[c].'::'.s[0].s[1];
} else {
trigger_error('Using unregistered static method "' . c.'::'.s[0] . '" in a template is deprecated and will be ' .
'removed in a future release. Use Smarty::registerClass to explicitly register ' .
'a class for access.', E_USER_DEPRECATED);
res = c.'::'.s[0].s[1];
}
} else {
Expand Down
8 changes: 5 additions & 3 deletions libs/sysplugins/smarty_internal_compile_private_modifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,11 @@ public function compile($args, Smarty_Internal_TemplateCompilerBase $compiler, $
if (!is_object($compiler->smarty->security_policy)
|| $compiler->smarty->security_policy->isTrustedPhpModifier($modifier, $compiler)
) {
trigger_error('Using php-function "' . $modifier . '" as a modifier is deprecated and will be ' .
'removed in a future release. Use Smarty::registerPlugin to explicitly register ' .
'a custom modifier.', E_USER_DEPRECATED);
if (!in_array($modifier, ['time', 'join', 'is_array', 'in_array'])) {
trigger_error('Using unregistered function "' . $modifier . '" in a template is deprecated and will be ' .
'removed in a future release. Use Smarty::registerPlugin to explicitly register ' .
'a custom modifier.', E_USER_DEPRECATED);
}
$output = "{$modifier}({$params})";
}
$compiler->known_modifier_type[ $modifier ] = $type;
Expand Down
12 changes: 11 additions & 1 deletion libs/sysplugins/smarty_internal_templatecompilerbase.php
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,17 @@ public function compilePHPFunctionCall($name, $parameter)
return $func_name . '(' . $parameter[ 0 ] . ')';
}
} else {
return $name . '(' . implode(',', $parameter) . ')';
$first_param = array_shift($parameter);
$modifier = array_merge(array($name), $parameter);
// Now, compile the function call as a modifier
return $this->compileTag(
'private_modifier',
array(),
array(
'modifierlist' => array($modifier),
'value' => $first_param
)
);
}
} else {
$this->trigger_template_error("unknown function '{$name}'");
Expand Down
3 changes: 3 additions & 0 deletions libs/sysplugins/smarty_internal_templateparser.php
Original file line number Diff line number Diff line change
Expand Up @@ -2425,6 +2425,9 @@ public function yy_r99(){
if (isset($this->smarty->registered_classes[$this->yystack[$this->yyidx + -2]->minor])) {
$this->_retvalue = $this->smarty->registered_classes[$this->yystack[$this->yyidx + -2]->minor].'::'.$this->yystack[$this->yyidx + 0]->minor[0].$this->yystack[$this->yyidx + 0]->minor[1];
} else {
trigger_error('Using unregistered static method "' . $this->yystack[$this->yyidx + -2]->minor.'::'.$this->yystack[$this->yyidx + 0]->minor[0] . '" in a template is deprecated and will be ' .
'removed in a future release. Use Smarty::registerClass to explicitly register ' .
'a class for access.', E_USER_DEPRECATED);
$this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.'::'.$this->yystack[$this->yyidx + 0]->minor[0].$this->yystack[$this->yyidx + 0]->minor[1];
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion libs/sysplugins/smarty_security.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ public function __construct($smarty)
*
* @param string $function_name
* @param object $compiler compiler object
*
* @deprecated
* @return boolean true if function is trusted
*/
public function isTrustedPhpFunction($function_name, $compiler)
Expand Down
6 changes: 5 additions & 1 deletion phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
timeoutForSmallTests="1"
timeoutForMediumTests="10"
timeoutForLargeTests="60"
verbose="false">
verbose="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
convertDeprecationsToExceptions="true">
<testsuites>
<testsuite name="foo">
<directory>./tests/UnitTests/</directory>
Expand Down
18 changes: 10 additions & 8 deletions run-tests-for-all-php-versions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
# - ./run-tests-for-all-php-versions.sh --group 20221124
# - ./run-tests-for-all-php-versions.sh --exclude-group slow

docker-compose run php71 ./run-tests.sh $@ && \
docker-compose run php72 ./run-tests.sh $@ && \
docker-compose run php73 ./run-tests.sh $@ && \
docker-compose run php74 ./run-tests.sh $@ && \
docker-compose run php80 ./run-tests.sh $@ && \
docker-compose run php81 ./run-tests.sh $@ && \
docker-compose run php82 ./run-tests.sh $@ && \
docker-compose run php83 ./run-tests.sh $@
COMPOSE_CMD="mutagen-compose"

$COMPOSE_CMD run --rm php71 ./run-tests.sh $@ && \
$COMPOSE_CMD run --rm php72 ./run-tests.sh $@ && \
$COMPOSE_CMD run --rm php73 ./run-tests.sh $@ && \
$COMPOSE_CMD run --rm php74 ./run-tests.sh $@ && \
$COMPOSE_CMD run --rm php80 ./run-tests.sh $@ && \
$COMPOSE_CMD run --rm php81 ./run-tests.sh $@ && \
$COMPOSE_CMD run --rm php82 ./run-tests.sh $@ && \
$COMPOSE_CMD run --rm php83 ./run-tests.sh $@
2 changes: 1 addition & 1 deletion tests/PHPUnit_Smarty.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public function setUpSmarty($dir = null)
$this->smarty->setCacheDir(__DIR__ . '/cache');
}

$this->getSmartyObj();
$this->smarty->setErrorReporting(E_ALL &~ E_USER_DEPRECATED);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/UnitTests/A_Core/LoadPlugin/LoadPluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ public function testLoadPluginSmartyTemplateClass()
*/
public function testLoadPluginSmartyPluginCounter()
{
$this->assertTrue($this->smarty->loadPlugin('Smarty_Function_Counter') == true);
$this->assertTrue($this->smarty->loadPlugin('Smarty_function_counter') == true);
}
}
4 changes: 2 additions & 2 deletions tests/UnitTests/SecurityTests/SecurityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function testSecurityLoaded()
*/
public function testTrustedPHPFunction()
{
$this->assertEquals("5", $this->smarty->fetch('string:{assign var=foo value=[1,2,3,4,5]}{sizeof($foo)}'));
$this->assertEquals("5", $this->smarty->fetch('string:{assign var=foo value=[1,2,3,4,5]}{count($foo)}'));
}

/**
Expand All @@ -75,7 +75,7 @@ public function testDisabledTrustedPHPFunction()
{
$this->smarty->security_policy->php_functions = array('null');
$this->smarty->disableSecurity();
$this->assertEquals("5", $this->smarty->fetch('string:{assign var=foo value=[1,2,3,4,5]}{sizeof($foo)}'));
$this->assertEquals("5", $this->smarty->fetch('string:{assign var=foo value=[1,2,3,4,5]}{count($foo)}'));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function testAllVariablesAccessable()
*/
public function testClearAllAssignInTemplate()
{
error_reporting((error_reporting() & ~(E_NOTICE | E_USER_NOTICE | E_WARNING)));
$this->smarty->setErrorReporting($this->smarty->error_reporting & ~(E_NOTICE | E_USER_NOTICE | E_WARNING));
$this->_tpl->clearAllAssign();
$this->assertEquals('foobar', $this->smarty->fetch($this->_tpl));
}
Expand All @@ -56,7 +56,7 @@ public function testClearAllAssignInTemplate()
*/
public function testClearAllAssignInData()
{
error_reporting((error_reporting() & ~(E_NOTICE | E_USER_NOTICE | E_WARNING)));
$this->smarty->setErrorReporting($this->smarty->error_reporting & ~(E_NOTICE | E_USER_NOTICE | E_WARNING));
$this->_data->clearAllAssign();
$this->assertEquals('fooblar', $this->smarty->fetch($this->_tpl));
}
Expand All @@ -66,7 +66,7 @@ public function testClearAllAssignInData()
*/
public function testClearAllAssignInSmarty()
{
error_reporting((error_reporting() & ~(E_NOTICE | E_USER_NOTICE | E_WARNING)));
$this->smarty->setErrorReporting($this->smarty->error_reporting & ~(E_NOTICE | E_USER_NOTICE | E_WARNING));
$this->smarty->clearAllAssign();
$this->assertEquals('barblar', $this->smarty->fetch($this->_tpl));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public function testEmpty1()
public function testEmpty2()
{
$this->smarty->disableSecurity();
$this->smarty->registerPlugin('modifier', 'pass', 'pass');
$this->smarty->assign('var', array(null,
false,
(int) 0,
Expand All @@ -78,6 +79,7 @@ public function testEmpty2()
public function testEmpty3()
{
$this->smarty->disableSecurity();
$this->smarty->registerPlugin('modifier', 'pass', 'pass');
$this->smarty->assign('var', array(true,
(int) 1,
(float) 0.1,
Expand Down Expand Up @@ -114,6 +116,7 @@ public function testEmpty4()
public function testIsset1()
{
$this->smarty->disableSecurity();
$this->smarty->registerPlugin('modifier', 'pass', 'pass');
$this->smarty->assign('isNull', null);
$this->smarty->assign('isSet', 1);
$this->smarty->assign('arr', array('isNull' => null, 'isSet' => 1));
Expand Down Expand Up @@ -155,7 +158,7 @@ public function testIsset2()
public function testIsset3($strTemplate, $result)
{
$this->smarty->disableSecurity();

$this->smarty->registerPlugin('modifier', 'intval', 'intval');
$this->smarty->assign('varobject', new TestIsset());
$this->smarty->assign('vararray', $vararray = array(
'keythatexists' => false,
Expand Down Expand Up @@ -196,6 +199,105 @@ public function dataTestIsset3()
array('{if isset($_varsimple{$key})}true{else}false{/if}', 'true'),
);
}

/**
* Tests various PHP functions (deprecated)
* @dataProvider dataVariousPHPFunctions
*/
public function testVariousPHPFunctions($strTemplate, $value, $expected) {
$this->smarty->disableSecurity();
$this->cleanDirs();
$this->smarty->assign('value', $value);
$this->assertEquals($expected, $this->smarty->fetch('string:' . $strTemplate));
}

/**
* Data provider for testIsset3
*/
public function dataVariousPHPFunctions()
{
return array(
array('{$a = count($value)}{$a}', array(1,2,3), '3'),
array('{$a = in_array("b", $value)}{$a}', array(1,'b',3), true),
array('{$a = strlen(uniqid())}{$a}', '', 13),
array('{$a = date("Y", $value)}{$a}', strtotime("01-01-2030"), 2030),
array('{$a = PhpFunctionTest::sayHi($value)}{$a}', 'mario', 'hi mario'),
array('{$a = pass($value)}{$a}', 'mario', 'mario'),
array('{$a = 1}{$b = Closure::fromCallable($value)}{$a}', 'strlen', 1),
);
}

public static function sayHi($value) {
return 'hi ' . $value;
}

/**
* Tests that each function that will not be supported in Smarty 5 does throw an E_USER_DEPRECATED notice.
* @dataProvider dataDeprecationNoticesForSmarty5
* @deprecated
*/
public function testDeprecationNoticesForSmarty5($strTemplateSource, $expected = '', $shouldTriggerDeprecationNotice = false) {

// this overrides the error reporting level set in \PHPUnit_Smarty::setUpSmarty
$previousErrorReporting = $this->smarty->error_reporting;
$this->smarty->setErrorReporting($previousErrorReporting | E_USER_DEPRECATED);

$this->smarty->assign('a', 'a');
$this->smarty->assign('ar', [1,2]);
$this->smarty->assign('f', 3.14);

$errorMessage = '';

try {
$output = $this->smarty->fetch('string:' . $strTemplateSource);
} catch (Exception $e) {
$errorMessage = $e->getMessage();
}

if ($shouldTriggerDeprecationNotice) {
$this->assertStringContainsString('Using unregistered function', $errorMessage);
} else {
$this->assertEquals($expected, $output);
$this->assertEquals('', $errorMessage);
}

$this->smarty->setErrorReporting($previousErrorReporting);
}

public function dataDeprecationNoticesForSmarty5()
{

return [

['{if empty($a)}{else}b{/if}', 'b', false],
['{json_encode($a)}', '"a"', false],
['{nl2br($a)}', 'a', false],
['{$a|nl2br}', 'a', false],
['{round($f, 1)}', '3.1', false],
['{$f|round}', '3', false],
['{str_repeat($a, 2)}', 'aa', false],
['{$a|str_repeat:3}', 'aaa', false],
['{$a|strip_tags}', 'a', false],
['{$a|strlen}', '1', false],
['{$a|substr:-1}', 'a', false],
['{$f|substr:-1}', '4', false],
['{$ar|count}', '2', false],
['{foreach "."|explode:$f as $n}{$n}{/foreach}', '314', false],
['{"-"|implode:$ar}', '1-2', false],
['{"-"|join:$ar}', '1-2', false],
['{$f|wordwrap:2:"k":true}', "3.k14", false],
['{$f|number_format:1:","}', "3,1", false],
['{if in_array(1, $ar)}yes{/if}', "yes", false],
['{if is_array($ar)}yes{/if}', "yes", false],
['{if time() gt 0}yes{/if}', "yes", false],

['{if array_chunk($ar, 2)}x{else}y{/if}', '', true],
['{$a|addslashes}', '', true],
['{$a|sha1}', '', true],
['{$a|get_parent_class}', '', true],
];
}

}

/**
Expand Down
Loading