diff --git a/changelog/939.md b/changelog/939.md new file mode 100644 index 00000000..ae676749 --- /dev/null +++ b/changelog/939.md @@ -0,0 +1 @@ +- Add support for implode, substr and json_encode as modifiers/functions in templates [#939](https://github.com/smarty-php/smarty/issues/939) \ No newline at end of file diff --git a/src/Compile/Modifier/JsonEncodeModifierCompiler.php b/src/Compile/Modifier/JsonEncodeModifierCompiler.php new file mode 100644 index 00000000..4f191a31 --- /dev/null +++ b/src/Compile/Modifier/JsonEncodeModifierCompiler.php @@ -0,0 +1,14 @@ +getSmarty()->security_policy) || $compiler->getSmarty()->security_policy->isTrustedModifier($modifier, $compiler) ) { if ($handler = $compiler->getModifierCompiler($modifier)) { - $output = $handler->compile($single_modifier, $compiler); - + $output = $handler->compile($modifier_params, $compiler); } elseif ($compiler->getSmarty()->getModifierCallback($modifier)) { $output = sprintf( '$_smarty_tpl->getSmarty()->getModifierCallback(%s)(%s)', @@ -60,7 +63,7 @@ class ModifierCompiler extends Base { $params ); } elseif ($callback = $compiler->getPluginFromDefaultHandler($modifier, \Smarty\Smarty::PLUGIN_MODIFIERCOMPILER)) { - $output = (new \Smarty\Compile\Modifier\BCPluginWrapper($callback))->compile($single_modifier, $compiler); + $output = (new \Smarty\Compile\Modifier\BCPluginWrapper($callback))->compile($modifier_params, $compiler); } elseif ($function = $compiler->getPluginFromDefaultHandler($modifier, \Smarty\Smarty::PLUGIN_MODIFIER)) { if (!is_array($function)) { $output = "{$function}({$params})"; diff --git a/src/Compiler/Configfile.php b/src/Compiler/Configfile.php index 7297a62b..84c14f9e 100644 --- a/src/Compiler/Configfile.php +++ b/src/Compiler/Configfile.php @@ -65,8 +65,6 @@ class Configfile extends BaseCompiler { * @param Smarty $smarty global instance */ public function __construct(Smarty $smarty) { - $this->smarty = $smarty; - // get required plugins $this->smarty = $smarty; $this->config_data['sections'] = []; $this->config_data['vars'] = []; @@ -104,7 +102,7 @@ class Configfile extends BaseCompiler { ) . "\n", $this ); - /* @var ConfigfileParser $this->parser */ + $this->parser = new ConfigfileParser($this->lex, $this); if ($this->smarty->_parserdebug) { $this->parser->PrintTrace(); diff --git a/src/Extension/DefaultExtension.php b/src/Extension/DefaultExtension.php index 070274c7..5ebcce47 100644 --- a/src/Extension/DefaultExtension.php +++ b/src/Extension/DefaultExtension.php @@ -28,6 +28,7 @@ class DefaultExtension extends Base { case 'from_charset': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\FromCharsetModifierCompiler(); break; case 'indent': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\IndentModifierCompiler(); break; case 'isset': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\IssetModifierCompiler(); break; + case 'json_encode': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\JsonEncodeModifierCompiler(); break; case 'lower': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\LowerModifierCompiler(); break; case 'nl2br': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\Nl2brModifierCompiler(); break; case 'noprint': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\NoPrintModifierCompiler(); break; @@ -37,6 +38,7 @@ class DefaultExtension extends Base { case 'strip': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StripModifierCompiler(); break; case 'strip_tags': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StripTagsModifierCompiler(); break; case 'strlen': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StrlenModifierCompiler(); break; + case 'substr': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\SubstrModifierCompiler(); break; case 'to_charset': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\ToCharsetModifierCompiler(); break; case 'unescape': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\UnescapeModifierCompiler(); break; case 'upper': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\UpperModifierCompiler(); break; @@ -54,6 +56,7 @@ class DefaultExtension extends Base { case 'debug_print_var': return [$this, 'smarty_modifier_debug_print_var']; case 'escape': return [$this, 'smarty_modifier_escape']; case 'explode': return [$this, 'smarty_modifier_explode']; + case 'implode': return [$this, 'smarty_modifier_implode']; case 'mb_wordwrap': return [$this, 'smarty_modifier_mb_wordwrap']; case 'number_format': return [$this, 'smarty_modifier_number_format']; case 'regex_replace': return [$this, 'smarty_modifier_regex_replace']; @@ -522,6 +525,27 @@ class DefaultExtension extends Base { return explode($separator, $string ?? '', $limit ?? PHP_INT_MAX); } + /** + * Smarty implode modifier plugin + * Type: modifier + * Name: implode + * Purpose: join an array of values into a single string + * + * @param array $values + * @param string $separator + * + * @return string + */ + public function smarty_modifier_implode($values, $separator = '') + { + if (is_array($separator)) { + trigger_error("Using implode with the separator first is deprecated. " . + "Call implode using the array first, separator second.", E_USER_DEPRECATED); + return implode((string) ($values ?? ''), (array) $separator); + } + return implode((string) ($separator ?? ''), (array) $values); + } + /** * Smarty wordwrap modifier plugin * Type: modifier diff --git a/tests/UnitTests/SmartyMethodsTests/RegisterModifier/RegisterModifierTest.php b/tests/UnitTests/SmartyMethodsTests/RegisterModifier/RegisterModifierTest.php index 267448d9..05ee7459 100644 --- a/tests/UnitTests/SmartyMethodsTests/RegisterModifier/RegisterModifierTest.php +++ b/tests/UnitTests/SmartyMethodsTests/RegisterModifier/RegisterModifierTest.php @@ -103,7 +103,8 @@ class RegisterModifierTest extends PHPUnit_Smarty public function dataUnknownModifiers(): array { return [ - ['{"blah"|substr:1:2}', 'la'], + ['{" blah"|ltrim:" "}', 'blah'], + ['{"blah"|strrev}', 'halb'], ['{"blah"|ucfirst}', 'Blah'], ['{"blah"|md5}', md5('blah')], ]; diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierImplodeTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierImplodeTest.php new file mode 100644 index 00000000..71a4419d --- /dev/null +++ b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierImplodeTest.php @@ -0,0 +1,52 @@ +setUpSmarty(__DIR__); + } + + public function testDefault() + { + $tpl = $this->smarty->createTemplate('string:{$v|implode}'); + $tpl->assign("v", ["1", "2"]); + $this->assertEquals("12", $this->smarty->fetch($tpl)); + } + public function testWithSeparator() + { + $tpl = $this->smarty->createTemplate('string:{$v|implode:","}'); + $tpl->assign("v", ["a", "b"]); + $this->assertEquals("a,b", $this->smarty->fetch($tpl)); + } + /** + * @deprecated + */ + public function testLegacyArgumentOrder() + { + $tpl = $this->smarty->createTemplate('string:{","|implode:$v}'); + $tpl->assign("v", ["a", "b"]); + $this->assertEquals("a,b", $this->smarty->fetch($tpl)); + } + + public function testInConditional() + { + $tpl = $this->smarty->createTemplate('string:{if implode($v) == "abc"}good{else}bad{/if}'); + $tpl->assign("v", ['a','b','c']); + $this->assertEquals("good", $this->smarty->fetch($tpl)); + } + + public function testInConditionalWithSeparator() + { + $tpl = $this->smarty->createTemplate('string:{if implode($v, "-") == "a-b-c"}good{else}bad{/if}'); + $tpl->assign("v", ['a','b','c']); + $this->assertEquals("good", $this->smarty->fetch($tpl)); + } + +} diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierJsonEncodeTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierJsonEncodeTest.php new file mode 100644 index 00000000..9a287812 --- /dev/null +++ b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierJsonEncodeTest.php @@ -0,0 +1,72 @@ +setUpSmarty(__DIR__); + } + + /** + * @dataProvider dataForDefault + */ + public function testDefault($value, $expected) + { + $tpl = $this->smarty->createTemplate('string:{$v|json_encode}'); + $tpl->assign("v", $value); + $this->assertEquals($expected, $this->smarty->fetch($tpl)); + } + + /** + * @dataProvider dataForDefault + */ + public function testDefaultAsFunction($value, $expected) + { + $tpl = $this->smarty->createTemplate('string:{json_encode($v)}'); + $tpl->assign("v", $value); + $this->assertEquals($expected, $this->smarty->fetch($tpl)); + } + + public function dataForDefault() { + return [ + ["abc", '"abc"'], + [["abc"], '["abc"]'], + [["abc",["a"=>2]], '["abc",{"a":2}]'], + ]; + } + + /** + * @dataProvider dataForForceObject + */ + public function testForceObject($value, $expected) + { + $tpl = $this->smarty->createTemplate('string:{$v|json_encode:16}'); + $tpl->assign("v", $value); + $this->assertEquals($expected, $this->smarty->fetch($tpl)); + } + + /** + * @dataProvider dataForForceObject + */ + public function testForceObjectAsFunction($value, $expected) + { + $tpl = $this->smarty->createTemplate('string:{json_encode($v,16)}'); + $tpl->assign("v", $value); + $this->assertEquals($expected, $this->smarty->fetch($tpl)); + } + + public function dataForForceObject() { + return [ + ["abc", '"abc"'], + [["abc"], '{"0":"abc"}'], + [["abc",["a"=>2]], '{"0":"abc","1":{"a":2}}'], + ]; + } + +} diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierSubstrTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierSubstrTest.php new file mode 100644 index 00000000..cc59e107 --- /dev/null +++ b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierSubstrTest.php @@ -0,0 +1,44 @@ +setUpSmarty(__DIR__); + } + + public function testDefault() + { + $tpl = $this->smarty->createTemplate('string:{$v|substr:1}'); + $tpl->assign("v", "abc"); + $this->assertEquals("bc", $this->smarty->fetch($tpl)); + } + + public function testTwoArguments() + { + $tpl = $this->smarty->createTemplate('string:{$v|substr:1:1}'); + $tpl->assign("v", "abc"); + $this->assertEquals("b", $this->smarty->fetch($tpl)); + } + + public function testNegativeOffset() + { + $tpl = $this->smarty->createTemplate('string:{$v|substr:-1}'); + $tpl->assign("v", "abc"); + $this->assertEquals("c", $this->smarty->fetch($tpl)); + } + + public function testInConditional() + { + $tpl = $this->smarty->createTemplate('string:{if substr($v, -1) == "c"}good{else}bad{/if}'); + $tpl->assign("v", "abc"); + $this->assertEquals("good", $this->smarty->fetch($tpl)); + } + +}