From 9ef066fa8549cef76472014e98c8c6a5b5aad8eb Mon Sep 17 00:00:00 2001 From: Simon Wisselink Date: Sun, 25 Feb 2024 23:46:52 +0100 Subject: [PATCH] WIP. Added split and join in favor of explode and implode modifiers. Updated docs. --- docs/designers/language-modifiers/index.md | 109 +++++++++--------- .../language-modifier-count.md | 21 ++++ .../language-modifier-debug-print-var.md | 26 +++++ .../language-modifier-isset.md | 11 ++ .../language-modifier-join.md | 26 +++++ .../language-modifier-noprint.md | 9 ++ .../language-modifier-split.md | 32 +++++ .../language-modifier-upper.md | 2 +- mkdocs.yml | 13 ++- src/Extension/DefaultExtension.php | 51 +++++++- .../TagTests/PluginFunction/IssetTest.php | 4 + .../PluginModifierExplodeTest.php | 4 +- .../PluginModifierImplodeTest.php | 15 ++- .../PluginModifier/PluginModifierJoinTest.php | 43 +++++++ .../PluginModifierSplitTest.php | 53 +++++++++ 15 files changed, 353 insertions(+), 66 deletions(-) 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-noprint.md create mode 100644 docs/designers/language-modifiers/language-modifier-split.md create mode 100644 tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierJoinTest.php create mode 100644 tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierSplitTest.php diff --git a/docs/designers/language-modifiers/index.md b/docs/designers/language-modifiers/index.md index 329ed958..3f52c2e7 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). +(colon). -- [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) +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. +## Combining Modifiers - > **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}`. +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. -- Custom modifiers can be registered - with the [`registerPlugin()`](../../programmers/api-functions/api-register-plugin.md) - function. +```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 00000000..36436a39 --- /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 00000000..ce3f86a7 --- /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 00000000..83e31dfa --- /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 00000000..9a044714 --- /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-noprint.md b/docs/designers/language-modifiers/language-modifier-noprint.md new file mode 100644 index 00000000..5dbfaa30 --- /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-split.md b/docs/designers/language-modifiers/language-modifier-split.md new file mode 100644 index 00000000..caef884f --- /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-upper.md b/docs/designers/language-modifiers/language-modifier-upper.md index 3173059c..edce9638 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 58d0c738..62ff9210 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-string-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/Extension/DefaultExtension.php b/src/Extension/DefaultExtension.php index 5ebcce47..83f110f7 100644 --- a/src/Extension/DefaultExtension.php +++ b/src/Extension/DefaultExtension.php @@ -57,11 +57,13 @@ class DefaultExtension extends Base { 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; @@ -214,7 +216,7 @@ class DefaultExtension extends Base { * > 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; @@ -520,6 +522,26 @@ class DefaultExtension extends Base { * @return array */ 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); @@ -538,9 +560,32 @@ class DefaultExtension extends Base { */ 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)) { - 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 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); diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginFunction/IssetTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginFunction/IssetTest.php index a54b244a..d0316346 100644 --- a/tests/UnitTests/TemplateSource/TagTests/PluginFunction/IssetTest.php +++ b/tests/UnitTests/TemplateSource/TagTests/PluginFunction/IssetTest.php @@ -15,6 +15,10 @@ class IssetTest extends \PHPUnit_Smarty { $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 ecefc6f8..8b689b17 100644 --- a/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierExplodeTest.php +++ b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierExplodeTest.php @@ -18,9 +18,7 @@ class PluginModifierExplodeTest extends \PHPUnit_Smarty } /** - * @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 index 71a4419d..aa0d6acd 100644 --- a/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierImplodeTest.php +++ b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierImplodeTest.php @@ -12,13 +12,18 @@ class PluginModifierImplodeTest extends PHPUnit_Smarty { $this->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:","}'); @@ -34,14 +39,18 @@ class PluginModifierImplodeTest extends PHPUnit_Smarty $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}'); diff --git a/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierJoinTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierJoinTest.php new file mode 100644 index 00000000..7fb6a958 --- /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/PluginModifierSplitTest.php b/tests/UnitTests/TemplateSource/TagTests/PluginModifier/PluginModifierSplitTest.php new file mode 100644 index 00000000..bfb769bf --- /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' => '[""]', + ], + ]; + } +}