From a1d38f60a9d3b0484197941dab613d518cf7098b Mon Sep 17 00:00:00 2001 From: Simon Wisselink Date: Mon, 3 Feb 2025 11:29:21 +0100 Subject: [PATCH] Fixed that modifiers called like function would be compiled to modifier name instead of calling the registered callback --- changelog/1100.md | 1 + .../smarty_internal_templatecompilerbase.php | 12 +- ...egisterModifierFirstClassCallablesTest.php | 156 ++++++++++++++++++ 3 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 changelog/1100.md create mode 100644 tests/UnitTests/SmartyMethodsTests/RegisterModifier/RegisterModifierFirstClassCallablesTest.php diff --git a/changelog/1100.md b/changelog/1100.md new file mode 100644 index 00000000..d1b69006 --- /dev/null +++ b/changelog/1100.md @@ -0,0 +1 @@ +- Fixed that modifiers called like function would be compiled to modifier name instead of calling the registered callback [#1100](https://github.com/smarty-php/smarty/issues/1100) diff --git a/libs/sysplugins/smarty_internal_templatecompilerbase.php b/libs/sysplugins/smarty_internal_templatecompilerbase.php index 254e1bf2..ea62e868 100644 --- a/libs/sysplugins/smarty_internal_templatecompilerbase.php +++ b/libs/sysplugins/smarty_internal_templatecompilerbase.php @@ -617,7 +617,9 @@ abstract class Smarty_Internal_TemplateCompilerBase { if (!$this->smarty->security_policy || $this->smarty->security_policy->isTrustedPhpFunction($name, $this)) { if (strcasecmp($name, 'isset') === 0 || strcasecmp($name, 'empty') === 0 - || strcasecmp($name, 'array') === 0 || is_callable($name) + || strcasecmp($name, 'array') === 0 + || is_callable($name) + || isset($this->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER][$name]) ) { $func_name = smarty_strtolower_ascii($name); @@ -665,6 +667,14 @@ abstract class Smarty_Internal_TemplateCompilerBase 'a custom modifier.', E_USER_DEPRECATED); } + if (isset($this->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER][$name])) { + return sprintf( + 'call_user_func_array($_smarty_tpl->registered_plugins[ \'%s\' ][ %s ][ 0 ], array( %s ))', + Smarty::PLUGIN_MODIFIER, + var_export($name, true), + implode(',', $parameter) + ); + } return $name . '(' . implode(',', $parameter) . ')'; } } else { diff --git a/tests/UnitTests/SmartyMethodsTests/RegisterModifier/RegisterModifierFirstClassCallablesTest.php b/tests/UnitTests/SmartyMethodsTests/RegisterModifier/RegisterModifierFirstClassCallablesTest.php new file mode 100644 index 00000000..b8dd6c00 --- /dev/null +++ b/tests/UnitTests/SmartyMethodsTests/RegisterModifier/RegisterModifierFirstClassCallablesTest.php @@ -0,0 +1,156 @@ +modifier / unregister->modifier methods + * + * @package PHPunit + * @author Uwe Tews + */ + +/** + * class for register->modifier / unregister->modifier methods tests + * + * @runTestsInSeparateProcess + * @preserveGlobalState disabled + * @backupStaticAttributes enabled + */ +class RegisterModifierTest extends PHPUnit_Smarty +{ + public function setUp(): void + { + $this->setUpSmarty(__DIR__); + } + + + public function testInit() + { + $this->cleanDirs(); + } + /** + * test register->modifier method for function + */ + public function testRegisterModifier() + { + $this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'testmodifier', 'mymodifier'); + $this->assertEquals('mymodifier', $this->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER]['testmodifier'][0]); + $this->smarty->assign('foo', 'foo'); + $this->smarty->assign('bar', 'bar'); + $this->assertEquals('foo function blar bar', $this->smarty->fetch('eval:{$foo|testmodifier:blar:$bar}')); + } + + /** + * test register->modifier method for classes + */ + public function testRegisterModifierClass() + { + $this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'testmodifier', array('mymodifierclass', 'static_method')); + $this->smarty->assign('foo', 'foo'); + $this->smarty->assign('bar', 'bar'); + $this->assertEquals('foo static blar bar', $this->smarty->fetch('eval:{$foo|testmodifier:blar:$bar}')); + } + + /** + * test register->modifier method for objects + */ + public function testRegisterModifierObject() + { + $obj = new mymodifierclass; + $this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'testmodifier', array($obj, 'object_method')); + $this->smarty->assign('foo', 'foo'); + $this->smarty->assign('bar', 'bar'); + $this->assertEquals('foo object blar bar', $this->smarty->fetch('eval:{$foo|testmodifier:blar:$bar}')); + } + + /** + * test unregister->modifier method + */ + public function testUnregisterModifier() + { + $this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'testmodifier', 'mymodifier'); + $this->smarty->unregisterPlugin(Smarty::PLUGIN_MODIFIER, 'testmodifier'); + $this->assertFalse(isset($this->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER]['testmodifier'])); + } + + /** + * test unregister->modifier method not registered + */ + public function testUnregisterModifierNotRegistered() + { + $this->smarty->unregisterPlugin(Smarty::PLUGIN_MODIFIER, 'testmodifier'); + $this->assertFalse(isset($this->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER]['testmodifier'])); + } + + /** + * test unregister->modifier method other registered + */ + public function testUnregisterModifierOtherRegistered() + { + $this->smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'testmodifier', 'mymodifier'); + $this->smarty->unregisterPlugin(Smarty::PLUGIN_MODIFIER, 'testmodifier'); + $this->assertTrue(isset($this->smarty->registered_plugins[Smarty::PLUGIN_BLOCK]['testmodifier'])); + } + + public function testRegisterFirstClassCallable() { + // skip test if PHP version is lower than 8.1 + if (PHP_VERSION_ID < 81000) { + $this->markTestSkipped('PHP 8.1 or later is required'); + } + + $this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'testmodifier', strrev(...)); + $this->assertEquals('mosredna', $this->smarty->fetch('string:{"andersom"|testmodifier}')); + } + + public function testRegisterFirstClassCallableSameName() { + + // skip test if PHP version is lower than 8.1 + if (PHP_VERSION_ID < 81000) { + $this->markTestSkipped('PHP 8.1 or later is required'); + } + + $this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'mymodifier', mymodifier(...)); + $this->assertEquals('mosredna', $this->smarty->fetch('string:{"andersom"|mymodifier:"":""}')); + } + public function testRegisterFirstClassCallableAsFunc() { + // skip test if PHP version is lower than 8.1 + if (PHP_VERSION_ID < 81000) { + $this->markTestSkipped('PHP 8.1 or later is required'); + } + + $this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'kprint_r_out', strrev(...)); + $this->smarty->assign('myVar', 'andersom'); + $this->assertEquals('mosredna', $this->smarty->fetch('string:{kprint_r_out($myVar)}')); + } + + /** + * @return void + * @throws SmartyException + * @group RegisterFirstClassCallableSameNameAsPhpFunc + */ + public function testRegisterFirstClassCallableSameNameAsPhpFunc() { + // skip test if PHP version is lower than 8.1 + if (PHP_VERSION_ID < 81000) { + $this->markTestSkipped('PHP 8.1 or later is required'); + } + + $this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'mymodifier', strrev(...)); + $this->assertEquals('mosredna', $this->smarty->fetch('string:{mymodifier("andersom","","")}')); + } + +} + +function mymodifier($a, $b, $c) +{ + return "$a function $b $c"; +} + +class mymodifierclass +{ + static function static_method($a, $b, $c) + { + return "$a static $b $c"; + } + + public function object_method($a, $b, $c) + { + return "$a object $b $c"; + } +}