Skip to content

Commit

Permalink
Using unsupported PHP functions and unregistered static class methods…
Browse files Browse the repository at this point in the history
… in modifiers and expressions now triggers a deprecation notice

* Using unsupported PHP functions and unregistered static class methods in modifiers and expressions now triggers a deprecation notice because we will drop support for this in the next major release
  • Loading branch information
wisskid authored Mar 18, 2024
1 parent e2cb987 commit c7a2713
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 22 deletions.
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

0 comments on commit c7a2713

Please sign in to comment.