From 41d80b99ac4fe8d8bdd9ae370841dfd081a7669b Mon Sep 17 00:00:00 2001 From: Simon Wisselink Date: Mon, 26 Feb 2024 14:35:19 +0100 Subject: [PATCH] Implemented support for substr, implode and json_encode as modifiers. (#940) * Implemented support for substr, implode and json_encode as modifiers. Fixes #939 * Added split and join in favor of explode and implode modifiers. * Documented all available modifiers --- changelog/939.md | 1 + docs/designers/language-modifiers/index.md | 117 +++++++++--------- .../language-modifier-count.md | 21 ++++ .../language-modifier-debug-print-var.md | 26 ++++ .../language-modifier-isset.md | 11 ++ .../language-modifier-join.md | 26 ++++ .../language-modifier-json-encode.md | 27 ++++ .../language-modifier-noprint.md | 9 ++ .../language-modifier-number-format.md | 32 +++++ .../language-modifier-round.md | 35 ++++++ .../language-modifier-split.md | 32 +++++ .../language-modifier-str-repeat.md | 14 +++ .../language-modifier-strlen.md | 9 ++ .../language-modifier-substr.md | 25 ++++ .../language-modifier-upper.md | 2 +- mkdocs.yml | 13 +- .../Modifier/JsonEncodeModifierCompiler.php | 14 +++ .../Modifier/SubstrModifierCompiler.php | 15 +++ src/Compile/ModifierCompiler.php | 13 +- src/Compiler/Configfile.php | 4 +- src/Extension/DefaultExtension.php | 71 ++++++++++- .../RegisterModifier/RegisterModifierTest.php | 3 +- .../TagTests/PluginFunction/IssetTest.php | 4 + .../PluginModifierExplodeTest.php | 4 +- .../PluginModifierImplodeTest.php | 61 +++++++++ .../PluginModifier/PluginModifierJoinTest.php | 43 +++++++ .../PluginModifierJsonEncodeTest.php | 72 +++++++++++ .../PluginModifierSplitTest.php | 53 ++++++++ .../PluginModifierSubstrTest.php | 44 +++++++ 29 files changed, 727 insertions(+), 74 deletions(-) create mode 100644 changelog/939.md create mode 100644 docs/designers/language-modifiers/language-modifier-count.md create mode 100644 docs/designers/language-modifiers/language-modifier-debug-print-var.md create mode 100644 docs/designers/language-modifiers/language-modifier-isset.md create mode 100644 docs/designers/language-modifiers/language-modifier-join.md create mode 100644 docs/designers/language-modifiers/language-modifier-json-encode.md create mode 100644 docs/designers/language-modifiers/language-modifier-noprint.md create mode 100644 docs/designers/language-modifiers/language-modifier-number-format.md create mode 100644 docs/designers/language-modifiers/language-modifier-round.md create mode 100644 docs/designers/language-modifiers/language-modifier-split.md create mode 100644 docs/designers/language-modifiers/language-modifier-str-repeat.md create mode 100644 docs/designers/language-modifiers/language-modifier-strlen.md create mode 100644 docs/designers/language-modifiers/language-modifier-substr.md create mode 100644 src/Compile/Modifier/JsonEncodeModifierCompiler.php create mode 100644 src/Compile/Modifier/SubstrModifierCompiler.php create mode 100644 tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierImplodeTest.php create mode 100644 tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierJoinTest.php create mode 100644 tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierJsonEncodeTest.php create mode 100644 tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierSplitTest.php create mode 100644 tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierSubstrTest.php diff --git a/changelog/939.md b/changelog/939.md new file mode 100644 index 000000000..ae6767497 --- /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/docs/designers/language-modifiers/index.md b/docs/designers/language-modifiers/index.md index 329ed958c..3f52c2e77 100644 --- a/docs/designers/language-modifiers/index.md +++ b/docs/designers/language-modifiers/index.md @@ -6,34 +6,10 @@ or strings. To apply a modifier, specify the value followed by a `|` (pipe) and the modifier name. A modifier may accept additional parameters that affect its behavior. These parameters follow the modifier name and are separated by a `:` -(colon). Also, *all php-functions can be used as modifiers implicitly* -(more below) and modifiers can be -[combined](../language-combining-modifiers.md). - -- [capitalize](language-modifier-capitalize.md) -- [cat](language-modifier-cat.md) -- [count_characters](language-modifier-count-characters.md) -- [count_paragraphs](language-modifier-count-paragraphs.md) -- [count_sentences](language-modifier-count-sentences.md) -- [count_words](language-modifier-count-words.md) -- [date_format](language-modifier-date-format.md) -- [default](language-modifier-default.md) -- [escape](language-modifier-escape.md) -- [from_charset](language-modifier-from-charset.md) -- [indent](language-modifier-indent.md) -- [lower](language-modifier-lower.md) -- [nl2br](language-modifier-nl2br.md) -- [regex_replace](language-modifier-regex-replace.md) -- [replace](language-modifier-replace.md) -- [spacify](language-modifier-spacify.md) -- [string_format](language-modifier-string-format.md) -- [strip](language-modifier-strip.md) -- [strip_tags](language-modifier-strip-tags.md) -- [to_charset](language-modifier-to-charset.md) -- [truncate](language-modifier-truncate.md) -- [unescape](language-modifier-unescape.md) -- [upper](language-modifier-upper.md) -- [wordwrap](language-modifier-wordwrap.md) +(colon). + +Modifiers can be applied to any type of variables, including arrays +and objects. ## Examples @@ -65,40 +41,63 @@ These parameters follow the modifier name and are separated by a `:` {* php's count *} {$myArray|@count} -{* this will uppercase and truncate the whole array *} +{* this will uppercase the whole array *} ``` - -- Modifiers can be applied to any type of variables, including arrays - and objects. - - > **Note** - > - > The default behavior was changed with Smarty 3. In Smarty 2.x, you - > had to use an "`@`" symbol to apply a modifier to an array, such - > as `{$articleTitle|@count}`. With Smarty 3, the "`@`" is no - > longer necessary, and is ignored. - > - > If you want a modifier to apply to each individual item of an - > array, you will either need to loop the array in the template, or - > provide for this functionality inside your modifier function. - - > **Note** - > - > Second, in Smarty 2.x, modifiers were applied to the result of - > math expressions like `{8+2}`, meaning that - > `{8+2|count_characters}` would give `2`, as 8+2=10 and 10 is two - > characters long. With Smarty 3, modifiers are applied to the - > variables or atomic expressions before executing the calculations, - > so since 2 is one character long, `{8+2|count_characters}` - > gives 9. To get the old result use parentheses like - > `{(8+2)|count_characters}`. - -- Custom modifiers can be registered - with the [`registerPlugin()`](../../programmers/api-functions/api-register-plugin.md) - function. + +## Combining Modifiers + +You can apply any number of modifiers to a variable. They will be +applied in the order they are combined, from left to right. They must be +separated with a `|` (pipe) character. + +```php +assign('articleTitle', 'Smokers are Productive, but Death Cuts Efficiency.'); +``` + +where template is: + +```smarty +{$articleTitle} +{$articleTitle|upper|spacify} +{$articleTitle|lower|spacify|truncate} +{$articleTitle|lower|truncate:30|spacify} +{$articleTitle|lower|spacify|truncate:30:". . ."} +``` + + +The above example will output: + +``` +Smokers are Productive, but Death Cuts Efficiency. +S M O K E R S A R ....snip.... H C U T S E F F I C I E N C Y . +s m o k e r s a r ....snip.... b u t d e a t h c u t s... +s m o k e r s a r e p r o d u c t i v e , b u t . . . +s m o k e r s a r e p. . . +``` + +## Using modifiers in expressions + +Modifiers can also be used in expressions. For example, you can use the [isset modifier](./language-modifier-isset.md) +to test if a variable holds a value different from null. + +```smarty +{if $varA|isset} + variable A is set +{/if} +``` + +You can also use modifiers in expressions in a PHP-style syntax: + +```smarty +{if isset($varA)} + variable A is set +{/if} +``` See also [`registerPlugin()`](../../programmers/api-functions/api-register-plugin.md), [combining modifiers](../language-combining-modifiers.md). and [extending smarty with diff --git a/docs/designers/language-modifiers/language-modifier-count.md b/docs/designers/language-modifiers/language-modifier-count.md new file mode 100644 index 000000000..36436a398 --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-count.md @@ -0,0 +1,21 @@ +# count + +Returns the number of elements in an array (or Countable object). Will return 0 for null. +Returns 1 for any other type (such as a string). + +If the optional mode parameter is set to 1, count() will recursively count the array. +This is particularly useful for counting all the elements of a multidimensional array. + +## Basic usage +```smarty +{if $myVar|count > 3}4 or more{/if} +{if count($myVar) > 3}4 or more{/if} +``` + + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|--------------------------------------------------------| +| 1 | int | No | If set to 1, count() will recursively count the array. | + diff --git a/docs/designers/language-modifiers/language-modifier-debug-print-var.md b/docs/designers/language-modifiers/language-modifier-debug-print-var.md new file mode 100644 index 000000000..ce3f86a73 --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-debug-print-var.md @@ -0,0 +1,26 @@ +# debug_print_var + + + +Returns the value of the given variable in a human-readable format in HTML. +Used in the [debug console](../chapter-debugging-console.md), but you can also use it in your template +while developing to see what is going on under the hood. + +> **Note** +> +> Use for debugging only! Since you may accidentally reveal sensitive information or introduce vulnerabilities such as XSS using this +method never use it in production. + +## Basic usage +```smarty +{$myVar|debug_print_var} +``` + + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|------------------------------------------------------------------------| +| 1 | int | No | maximum recursion depth if $var is an array or object (defaults to 10) | +| 2 | int | No | maximum string length if $var is a string (defaults to 40) | + diff --git a/docs/designers/language-modifiers/language-modifier-isset.md b/docs/designers/language-modifiers/language-modifier-isset.md new file mode 100644 index 000000000..83e31dfa9 --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-isset.md @@ -0,0 +1,11 @@ +# isset + +Returns true if the variable(s) passed to it are different from null. + +If multiple parameters are supplied then isset() will return true only if all of the parameters are +not null. + +## Basic usage +```smarty +{if $myVar|isset}all set!{/if} +``` diff --git a/docs/designers/language-modifiers/language-modifier-join.md b/docs/designers/language-modifiers/language-modifier-join.md new file mode 100644 index 000000000..9a044714d --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-join.md @@ -0,0 +1,26 @@ +# join + +Returns a string containing all the element of the given array +with the separator string between each. + +## Basic usage + +For `$myArray` populated with `['a','b','c']`, the following will return the string `abc`. +```smarty +{$myArray|join} +``` + + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|--------|----------|-------------------------------------------------------------| +| 1 | string | No | glue used between array elements. Defaults to empty string. | + +## Examples + + +For `$myArray` populated with `[1,2,3]`, the following will return the string `1-2-3`. +```smarty +{$myArray|join:"-"} +``` \ No newline at end of file diff --git a/docs/designers/language-modifiers/language-modifier-json-encode.md b/docs/designers/language-modifiers/language-modifier-json-encode.md new file mode 100644 index 000000000..4e70f0c26 --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-json-encode.md @@ -0,0 +1,27 @@ +# json_encode + +Transforms a value into a valid JSON string. + +## Basic usage +```smarty +{$user|json_encode} +``` +Depending on the value of `$user` this would return a string in JSON-format, e.g. `{"username":"my_username","email":"my_username@smarty.net"}`. + + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------------------------------------------------------------------------------------| +| 1 | int | No | bitmask of flags, directly passed to [PHP's json_encode](https://www.php.net/json_encode) | + + +## Examples + +By passing `16` as the second parameter, you can force json_encode to always format the JSON-string as an object. +Without it, an array `$myArray = ["a","b"]` would be formatted as a javascript array: + +```smarty +{$myArray|json_encode} # renders: ["a","b"] +{$myArray|json_encode:16} # renders: {"0":"a","1":"b"} +``` \ No newline at end of file diff --git a/docs/designers/language-modifiers/language-modifier-noprint.md b/docs/designers/language-modifiers/language-modifier-noprint.md new file mode 100644 index 000000000..5dbfaa30d --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-noprint.md @@ -0,0 +1,9 @@ +# noprint + +Always returns an empty string. This can be used to call a function or a method on an object that +returns output, and suppress the output. + +## Basic usage +```smarty +{$controller->sendEmail()|noprint} +``` diff --git a/docs/designers/language-modifiers/language-modifier-number-format.md b/docs/designers/language-modifiers/language-modifier-number-format.md new file mode 100644 index 000000000..44c3acf44 --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-number-format.md @@ -0,0 +1,32 @@ +# number_format + +Allows you to format a number using decimals and a thousands-separator. By default, the number of decimals is 0 +and the number is rounded. + +## Basic usage +```smarty +{$num = 2000.151} +{$num|number_format} # renders: 2,000 +``` + + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|--------|----------|---------------------------------------| +| 1 | int | No | number of decimals (defaults to 0) | +| 2 | string | No | decimal separator (defaults to ".") | +| 3 | string | No | thousands-separator (defaults to ",") | + + +## Examples + +```smarty +{$num = 2000.151} +{$num|number_format:2} # renders: 2,000.15 +``` + +```smarty +{$num = 2000.151} +{$num|number_format:2:".":""} # renders: 2000.15 +``` \ No newline at end of file diff --git a/docs/designers/language-modifiers/language-modifier-round.md b/docs/designers/language-modifiers/language-modifier-round.md new file mode 100644 index 000000000..c05b899a9 --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-round.md @@ -0,0 +1,35 @@ +# round + +Rounds a number to the specified precision. + +## Basic usage +```smarty +{3.14|round} # renders: 3 +``` + +```smarty +{3.141592|round:2} # renders: 3.14 +``` + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|---------------------------| +| 1 | int | No | precision (defaults to 0) | +| 2 | int | No | mode (defaults to 1) | + +If 'precision' is negative, the number is rounded to the nearest power of 10. See examples below. + +The parameter 'mode' defines how the rounding is done. By default, 2.5 is rounded to 3, whereas 2.45 is rounded to 2. +You usually don't need to change this. For more details on rounding modes, +see [PHP's documentation on round](https://www.php.net/manual/en/function.round). + +## Examples + +By passing `16` as the second parameter, you can force json_encode to always format the JSON-string as an object. +Without it, an array `$myArray = ["a","b"]` would be formatted as a javascript array: + +```smarty +{$myArray|json_encode} # renders: ["a","b"] +{$myArray|json_encode:16} # renders: {"0":"a","1":"b"} +``` \ No newline at end of file diff --git a/docs/designers/language-modifiers/language-modifier-split.md b/docs/designers/language-modifiers/language-modifier-split.md new file mode 100644 index 000000000..caef884f5 --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-split.md @@ -0,0 +1,32 @@ +# split + +Splits a string into an array, using the optional second parameter as the separator. + +## Basic usage + +For `$chars` populated with `'abc'`, the following will produce a html list with 3 elements (a, b and c). +```smarty +
    + {foreach $chars|split as $char} +
  1. {$char|escape}
  2. + {/foreach} +
+``` + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|--------|----------|------------------------------------------------------------------------------------------------------------------------------| +| 1 | string | No | separator used to split the string on. Defaults to empty string, causing each character in the source string to be separate. | + +## Examples + + +For `$ids` populated with `'1,2,3'`, the following will produce a html list with 3 elements (1, 2 and 3). +```smarty +
    + {foreach $ids|split:',' as $id} +
  1. {$id|escape}
  2. + {/foreach} +
+``` \ No newline at end of file diff --git a/docs/designers/language-modifiers/language-modifier-str-repeat.md b/docs/designers/language-modifiers/language-modifier-str-repeat.md new file mode 100644 index 000000000..1ae1824d6 --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-str-repeat.md @@ -0,0 +1,14 @@ +# str_repeat + +Repeats the given value n times. + +## Basic usage +```smarty +{"hi"|str_repeat:2} # renders: hihi +``` + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-----------------------| +| 1 | int | yes | number of repetitions | diff --git a/docs/designers/language-modifiers/language-modifier-strlen.md b/docs/designers/language-modifiers/language-modifier-strlen.md new file mode 100644 index 000000000..4c1f37ab7 --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-strlen.md @@ -0,0 +1,9 @@ +# strlen + +Returns the length (number of characters) in the given string, including spaces. + +## Basic usage +```smarty +{"Smarty"|strlen} # renders: 6 +{156|strlen} # renders: 3 +``` diff --git a/docs/designers/language-modifiers/language-modifier-substr.md b/docs/designers/language-modifiers/language-modifier-substr.md new file mode 100644 index 000000000..a79d8a4d1 --- /dev/null +++ b/docs/designers/language-modifiers/language-modifier-substr.md @@ -0,0 +1,25 @@ +# substr + +Returns a part (substring) of the given string starting at a given offset. + +## Basic usage +```smarty +{"Smarty"|substr:2} # renders: arty +{"Smarty"|substr:2:3} # renders: art +``` + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-----------------------------------------------------| +| 1 | int | yes | offset (zero based, can be negative) | +| 2 | int | no | length of substring returned (unlimited of omitted) | + + +## Examples + +When used with a negative offset, the substring starts n characters from the end of the string counting backwards. +```smarty +{"Smarty"|substr:-2} # renders: ty +{"Smarty"|substr:-2:1} # renders: t +``` \ No newline at end of file diff --git a/docs/designers/language-modifiers/language-modifier-upper.md b/docs/designers/language-modifiers/language-modifier-upper.md index 3173059c9..edce96381 100644 --- a/docs/designers/language-modifiers/language-modifier-upper.md +++ b/docs/designers/language-modifiers/language-modifier-upper.md @@ -29,5 +29,5 @@ If Strike isn't Settled Quickly it may Last a While. IF STRIKE ISN'T SETTLED QUICKLY IT MAY LAST A WHILE. ``` -See also [`lower`](lower) and +See also [`lower`](./language-modifier-lower.md) and [`capitalize`](language-modifier-capitalize.md). diff --git a/mkdocs.yml b/mkdocs.yml index 58d0c7387..223cab28b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -49,29 +49,40 @@ nav: - 'Introduction': 'designers/language-modifiers/index.md' - 'capitalize': 'designers/language-modifiers/language-modifier-capitalize.md' - 'cat': 'designers/language-modifiers/language-modifier-cat.md' + - 'count': 'designers/language-modifiers/language-modifier-count.md' - 'count_characters': 'designers/language-modifiers/language-modifier-count-characters.md' - 'count_paragraphs': 'designers/language-modifiers/language-modifier-count-paragraphs.md' - 'count_sentences': 'designers/language-modifiers/language-modifier-count-sentences.md' - 'count_words': 'designers/language-modifiers/language-modifier-count-words.md' - 'date_format': 'designers/language-modifiers/language-modifier-date-format.md' + - 'debug_print_var': 'designers/language-modifiers/language-modifier-debug-print-var.md' - 'default': 'designers/language-modifiers/language-modifier-default.md' - 'escape': 'designers/language-modifiers/language-modifier-escape.md' - 'from_charset': 'designers/language-modifiers/language-modifier-from-charset.md' - 'indent': 'designers/language-modifiers/language-modifier-indent.md' + - 'isset': 'designers/language-modifiers/language-modifier-isset.md' + - 'join': 'designers/language-modifiers/language-modifier-join.md' + - 'json_encode': 'designers/language-modifiers/language-modifier-json-encode.md' - 'lower': 'designers/language-modifiers/language-modifier-lower.md' + - 'noprint': 'designers/language-modifiers/language-modifier-noprint.md' + - 'number_format': 'designers/language-modifiers/language-modifier-number-format.md' - 'nl2br': 'designers/language-modifiers/language-modifier-nl2br.md' - 'regex_replace': 'designers/language-modifiers/language-modifier-regex-replace.md' - 'replace': 'designers/language-modifiers/language-modifier-replace.md' + - 'round': 'designers/language-modifiers/language-modifier-round.md' - 'spacify': 'designers/language-modifiers/language-modifier-spacify.md' + - 'split': 'designers/language-modifiers/language-modifier-split.md' + - 'str_repeat': 'designers/language-modifiers/language-modifier-str-repeat.md' - 'string_format': 'designers/language-modifiers/language-modifier-string-format.md' - 'strip': 'designers/language-modifiers/language-modifier-strip.md' - 'strip_tags': 'designers/language-modifiers/language-modifier-strip-tags.md' + - 'strlen': 'designers/language-modifiers/language-modifier-strlen.md' + - 'substr': 'designers/language-modifiers/language-modifier-substr.md' - 'to_charset': 'designers/language-modifiers/language-modifier-to-charset.md' - 'truncate': 'designers/language-modifiers/language-modifier-truncate.md' - 'unescape': 'designers/language-modifiers/language-modifier-unescape.md' - 'upper': 'designers/language-modifiers/language-modifier-upper.md' - 'wordwrap': 'designers/language-modifiers/language-modifier-wordwrap.md' - - 'Combining Modifiers': 'designers/language-combining-modifiers.md' - 'Builtin Tags': - 'Introduction': 'designers/language-builtin-functions/index.md' - '{append}': 'designers/language-builtin-functions/language-function-append.md' diff --git a/src/Compile/Modifier/JsonEncodeModifierCompiler.php b/src/Compile/Modifier/JsonEncodeModifierCompiler.php new file mode 100644 index 000000000..4f191a31f --- /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 @@ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter = $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 7297a62b2..84c14f9e7 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 @@ public function compileTemplate(Template $template) { ) . "\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 070274c73..83f110f77 100644 --- a/src/Extension/DefaultExtension.php +++ b/src/Extension/DefaultExtension.php @@ -28,6 +28,7 @@ public function getModifierCompiler(string $modifier): ?\Smarty\Compile\Modifier 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 @@ public function getModifierCompiler(string $modifier): ?\Smarty\Compile\Modifier 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,11 +56,14 @@ public function getModifierCallback(string $modifierName) { 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 'join': return [$this, 'smarty_modifier_join']; 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']; case 'replace': return [$this, 'smarty_modifier_replace']; case 'spacify': return [$this, 'smarty_modifier_spacify']; + case 'split': return [$this, 'smarty_modifier_split']; case 'truncate': return [$this, 'smarty_modifier_truncate']; } return null; @@ -211,7 +216,7 @@ public function smarty_modifier_count($arrayOrObject, $mode = 0) { * > 1 would be returned, unless value was null, in which case 0 would be returned. */ - if ($arrayOrObject instanceof Countable || is_array($arrayOrObject)) { + if ($arrayOrObject instanceof \Countable || is_array($arrayOrObject)) { return count($arrayOrObject, (int) $mode); } elseif ($arrayOrObject === null) { return 0; @@ -518,10 +523,74 @@ private function mb_to_unicode($string, $encoding = null) { */ public function smarty_modifier_explode($separator, $string, ?int $limit = null) { + trigger_error("Using explode is deprecated. " . + "Use split, using the array first, separator second.", E_USER_DEPRECATED); // provide $string default to prevent deprecation errors in PHP >=8.1 return explode($separator, $string ?? '', $limit ?? PHP_INT_MAX); } + /** + * Smarty split modifier plugin + * Type: modifier + * Name: split + * Purpose: split a string by a string + * + * @param string $string + * @param string $separator + * @param int|null $limit + * + * @return array + */ + public function smarty_modifier_split($string, $separator, ?int $limit = null) + { + // provide $string default to prevent deprecation errors in PHP >=8.1 + 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 = '') + { + + trigger_error("Using implode is deprecated. " . + "Use join using the array first, separator second.", E_USER_DEPRECATED); + + if (is_array($separator)) { + return implode((string) ($values ?? ''), (array) $separator); + } + return implode((string) ($separator ?? ''), (array) $values); + } + + /** + * Smarty join modifier plugin + * Type: modifier + * Name: join + * Purpose: join an array of values into a single string + * + * @param array $values + * @param string $separator + * + * @return string + */ + public function smarty_modifier_join($values, $separator = '') + { + if (is_array($separator)) { + trigger_error("Using join with the separator first is deprecated. " . + "Call join 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 267448d9b..05ee74592 100644 --- a/tests/UnitTests/SmartyMethodsTests/RegisterModifier/RegisterModifierTest.php +++ b/tests/UnitTests/SmartyMethodsTests/RegisterModifier/RegisterModifierTest.php @@ -103,7 +103,8 @@ public function testNativePHPModifiers($template, $expectedValue) 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/PluginFunction/IssetTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginFunction/IssetTest.php index a54b244a2..d03163469 100644 --- a/tests/UnitTests/TemplateSource/TagTests/PluginFunction/IssetTest.php +++ b/tests/UnitTests/TemplateSource/TagTests/PluginFunction/IssetTest.php @@ -15,6 +15,10 @@ public function testEmptyStringIsset() { $this->assertEquals("yay", $this->smarty->fetch("string:{if isset('')}yay{/if}")); } + public function testEmptyStringIssetModifierSyntax() { + $this->assertEquals("yay", $this->smarty->fetch("string:{if ''|isset}yay{/if}")); + } + public function testFalseIsset() { $this->smarty->assign('test', false); $this->assertEquals("yay", $this->smarty->fetch("string:{if isset(\$test)}yay{/if}")); diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierExplodeTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierExplodeTest.php index ecefc6f8b..8b689b17a 100644 --- a/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierExplodeTest.php +++ b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierExplodeTest.php @@ -18,9 +18,7 @@ public function setUp(): void } /** - * @return void - * @throws \Smarty\Exception - * + * @deprecated * @dataProvider explodeDataProvider */ public function testExplode($template, $subject, $expectedString) diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierImplodeTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierImplodeTest.php new file mode 100644 index 000000000..aa0d6acd0 --- /dev/null +++ b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierImplodeTest.php @@ -0,0 +1,61 @@ +setUpSmarty(__DIR__); + } + /** + * @deprecated + */ + public function testDefault() + { + $tpl = $this->smarty->createTemplate('string:{$v|implode}'); + $tpl->assign("v", ["1", "2"]); + $this->assertEquals("12", $this->smarty->fetch($tpl)); + } + /** + * @deprecated + */ + 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)); + } + /** + * @deprecated + */ + 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)); + } + /** + * @deprecated + */ + 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/PluginModifierJoinTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierJoinTest.php new file mode 100644 index 000000000..7fb6a958b --- /dev/null +++ b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierJoinTest.php @@ -0,0 +1,43 @@ +setUpSmarty(__DIR__); + } + + public function testDefault() + { + $tpl = $this->smarty->createTemplate('string:{$v|join}'); + $tpl->assign("v", ["1", "2"]); + $this->assertEquals("12", $this->smarty->fetch($tpl)); + } + public function testWithSeparator() + { + $tpl = $this->smarty->createTemplate('string:{$v|join:","}'); + $tpl->assign("v", ["a", "b"]); + $this->assertEquals("a,b", $this->smarty->fetch($tpl)); + } + + public function testInConditional() + { + $tpl = $this->smarty->createTemplate('string:{if join($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 join($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 000000000..9a2878122 --- /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/PluginModifierSplitTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierSplitTest.php new file mode 100644 index 000000000..bfb769bf5 --- /dev/null +++ b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierSplitTest.php @@ -0,0 +1,53 @@ +setUpSmarty(__DIR__); + $this->smarty->registerPlugin('modifier', 'json_encode', 'json_encode'); + } + + /** + * @dataProvider explodeDataProvider + */ + public function testSplit($template, $subject, $expectedString) + { + $this->smarty->assign('subject', $subject); + + $tpl = $this->smarty->createTemplate($template); + $res = $this->smarty->fetch($tpl); + + $this->assertEquals($expectedString, $res); + } + + public function explodeDataProvider() + { + return [ + 'default' => [ + 'template' => 'string:{$subject|split:","|json_encode}', + 'subject' => 'a,b,c,d', + 'expectedString' => '["a","b","c","d"]', + ], + 'withNoDelimiterFound' => [ + 'template' => 'string:{$subject|split:","|json_encode}', + 'subject' => 'abcd', + 'expectedString' => '["abcd"]', + ], + 'withNull' => [ + 'template' => 'string:{$subject|split:","|json_encode}', + 'subject' => null, + 'expectedString' => '[""]', + ], + ]; + } +} diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierSubstrTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierSubstrTest.php new file mode 100644 index 000000000..cc59e107f --- /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)); + } + +}