Removed PHP functions and checks for the already removed php modifiers. Re-implemented functions as regular functions. Probably should compile these directly.

This commit is contained in:
Simon Wisselink
2023-01-07 23:06:47 +01:00
parent e595cd2a5d
commit fd64cc688a
21 changed files with 393 additions and 217 deletions

View File

@@ -8,10 +8,6 @@ template engine. Every `{if}` must be paired with a matching `{/if}`.
functions are recognized, such as *\|\|*, *or*, *&&*, *and*,
*is\_array()*, etc.
If securty is enabled, only PHP functions from `$php_functions` property
of the securty policy are allowed. See the
[Security](#advanced.features.security) section for details.
The following is a list of recognized qualifiers, which must be
separated from surrounding elements by spaces. Note that items listed in
\[brackets\] are optional. PHP equivalents are shown where applicable.

View File

@@ -53,19 +53,6 @@ instance of the Smarty\_Security class. These are the possible settings:
static classes. To disable access to all static classes set
\$static\_classes = null.
- `$php_functions` is an array of PHP functions that are considered
trusted and can be used from within template. To disable access to
all PHP functions set \$php\_functions = null. An empty array (
\$php\_functions = array() ) will allow all PHP functions. The
default is array(\'isset\', \'empty\', \'count\', \'sizeof\',
\'in\_array\', \'is\_array\',\'time\',\'nl2br\').
- `$php_modifiers` is an array of PHP functions that are considered
trusted and can be used from within template as modifier. To disable
access to all PHP modifier set \$php\_modifier = null. An empty
array ( \$php\_modifier = array() ) will allow all PHP functions.
The default is array(\'escape\',\'count\').
- `$streams` is an array of streams that are considered trusted and
can be used from within template. To disable access to all streams
set \$streams = null. An empty array ( \$streams = array() ) will
@@ -105,10 +92,7 @@ Smarty\_Security class or create an instance of it.
<?php
class My_Security_Policy extends \Smarty\Security {
// disable all PHP functions
public $php_functions = null;
// allow everthing as modifier
public $php_modifiers = array();
public $allow_constants = false;
}
$smarty = new Smarty();
// enable security
@@ -119,10 +103,7 @@ Smarty\_Security class or create an instance of it.
<?php
$smarty = new Smarty();
$my_security_policy = new \Smarty\Security($smarty);
// disable all PHP functions
$my_security_policy->php_functions = null;
// allow everthing as modifier
$my_security_policy->php_modifiers = array();
$my_security_policy->allow_constants = false;
// enable security
$smarty->enableSecurity($my_security_policy);
?>

View File

@@ -11,6 +11,7 @@
namespace Smarty\Compile;
use Smarty\Compiler\Template;
use Smarty\CompilerException;
/**
* Smarty Internal Plugin Compile Registered Function Class
@@ -28,6 +29,13 @@ class FunctionCallCompiler extends Base {
*/
public $optional_attributes = ['_any'];
/**
* Shorttag attribute order defined by its names
*
* @var array
*/
protected $shorttag_order = ['var1', 'var2', 'var3'];
/**
* Compiles code for the execution of a registered function
*
@@ -42,12 +50,14 @@ class FunctionCallCompiler extends Base {
* @throws \Smarty\Exception
*/
public function compile($args, Template $compiler, $parameter = [], $tag = null, $function = null) {
// check and get attributes
$_attr = $this->getAttributes($compiler, $args);
unset($_attr['nocache']);
$functionHandler = $compiler->smarty->getFunctionHandler($function);
if (!$functionHandler = $compiler->smarty->getFunctionHandler($function)) {
throw new CompilerException("Cannot compile unknown function $function.");
}
// not cacheable?
$compiler->tag_nocache = $compiler->tag_nocache || !$functionHandler->isCacheable();
@@ -65,6 +75,6 @@ class FunctionCallCompiler extends Base {
if (!empty($parameter['modifierlist'])) {
$output = $compiler->compileModifier($parameter['modifierlist'], $output);
}
return "<?php echo {$output};?>\n";
return $output;
}
}

View File

@@ -529,77 +529,6 @@ class Template extends BaseCompiler {
return '$_smarty_tpl->getConfigVariable(' . $variable . ')';
}
/**
* compile PHP function call
*
* @param string $name
* @param array $parameter
*
* @return string
* @throws \Smarty\CompilerException
*/
public function compilePHPFunctionCall($name, $parameter) {
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)
) {
$func_name = smarty_strtolower_ascii($name);
if ($func_name === 'isset') {
if (count($parameter) === 0) {
$this->trigger_template_error('Illegal number of parameter in "isset()"');
}
$pa = [];
foreach ($parameter as $p) {
$pa[] = $this->syntaxMatchesVariable($p) ? 'isset(' . $p . ')' : '(' . $p . ' !== null )';
}
return '(' . implode(' && ', $pa) . ')';
} elseif (in_array(
$func_name,
[
'empty',
'reset',
'current',
'end',
'prev',
'next',
]
)
) {
if (count($parameter) !== 1) {
$this->trigger_template_error("Illegal number of parameter in '{$func_name()}'");
}
if ($func_name === 'empty') {
return $func_name . '(' .
str_replace("')->value", "',null,true,false)->value", $parameter[0]) . ')';
} else {
return $func_name . '(' . $parameter[0] . ')';
}
} else {
return $name . '(' . implode(',', $parameter) . ')';
}
} else {
$this->trigger_template_error("unknown function '{$name}'");
}
}
}
/**
* Determines whether the passed string represents a valid (PHP) variable.
* This is important, because `isset()` only works on variables and `empty()` can only be passed
* a variable prior to php5.5
*
* @param $string
*
* @return bool
*/
private function syntaxMatchesVariable($string) {
static $regex_pattern = '/^\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*((->)[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*|\[.*]*\])*$/';
return 1 === preg_match($regex_pattern, trim($string));
}
/**
* This method is called from parser to process a text content section if strip is enabled
* - remove text from inheritance child templates as they may generate output
@@ -1278,7 +1207,11 @@ class Template extends BaseCompiler {
// check if tag is a function
if ($this->smarty->getFunctionHandler($base_tag)) {
if (!isset($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedTag($base_tag, $this)) {
return $this->functionCallCompiler->compile($args, $this, $parameter, $tag, $base_tag);
return (new \Smarty\Compile\PrintExpressionCompiler())->compile(
[],
$this,
['value' => $this->compileFunctionCall($base_tag, $args, $parameter)]
);
}
}
@@ -1483,4 +1416,8 @@ class Template extends BaseCompiler {
);
}
public function compileFunctionCall(string $base_tag, array $args, array $parameter = []) {
return $this->functionCallCompiler->compile($args, $this, $parameter, $base_tag, $base_tag);
}
}

View File

@@ -69,8 +69,10 @@ class DefaultExtension extends Base {
}
switch ($functionName) {
case 'count': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Count(); break;
case 'counter': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Counter(); break;
case 'cycle': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Cycle(); break;
case 'empty': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\EmptyHandler(); break;
case 'fetch': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Fetch(); break;
case 'html_checkboxes': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlCheckboxes(); break;
case 'html_image': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlImage(); break;
@@ -79,8 +81,12 @@ class DefaultExtension extends Base {
case 'html_select_date': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlSelectDate(); break;
case 'html_select_time': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlSelectTime(); break;
case 'html_table': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlTable(); break;
case 'in_array': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\InArray(); break;
case 'is_array': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\IsArray(); break;
case 'isset': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\IssetHandler(); break;
case 'mailto': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Mailto(); break;
case 'math': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Math(); break;
case 'time': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Time(); break;
}
return $this->functionHandlers[$functionName] ?? null;

View File

@@ -0,0 +1,36 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* count(Countable|array $value, int $mode = COUNT_NORMAL): int
* If the optional mode parameter is set to COUNT_RECURSIVE (or 1), count() will recursively count the array.
* This is particularly useful for counting all the elements of a multidimensional array.
*
* Returns the number of elements in value. Prior to PHP 8.0.0, if the parameter was neither an array nor an object that
* implements the Countable interface, 1 would be returned, unless value was null, in which case 0 would be returned.
*/
class Count extends Base {
public function handle($params, Template $template) {
$params = array_values($params ?? []);
if (count($params) < 1 || count($params) > 2) {
throw new Exception("Invalid number of arguments for in_array. in_arrays expects 2 or 3 parameters.");
}
$value = $params[0];
if ($value instanceof \Countable) {
return $value->count();
}
$mode = count($params) == 2 ? (int) $params[1] : COUNT_NORMAL;
return count((array) $value, $mode);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* empty(mixed $var): bool
*
* Returns true if var does not exist or has a value that is empty or equal to zero, aka falsey, see conversion to
* boolean. Otherwise returns false.
*
* No warning is generated if the variable does not exist. That means empty() is essentially the concise equivalent
* to !isset($var) || $var == false.
*/
class EmptyHandler extends Base {
public function handle($params, Template $template) {
if (count($params) !== 1) {
throw new Exception("Invalid number of arguments for empty. empty expects exactly 1 parameter.");
}
return empty(reset($params));
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* in_array(mixed $needle, array $haystack, bool $strict = false): bool
* Returns true if needle is found in the array, false otherwise
*/
class InArray extends Base {
public function handle($params, Template $template) {
$params = array_values($params ?? []);
if (count($params) < 2 || count($params) > 3) {
throw new Exception("Invalid number of arguments for in_array. in_arrays expects 2 or 3 parameters.");
}
// default to false, true if param 3 is set to true
$needle = $params[0];
$haystack = (array) $params[1];
$strict = count($params) == 3 && $params[2];
return in_array($needle, $haystack, $strict);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* is_array(mixed $value): bool
* Returns true if value is an array, false otherwise.
*/
class IsArray extends Base {
public function handle($params, Template $template) {
if (count($params) !== 1) {
throw new Exception("Invalid number of arguments for is_array. is_array expects exactly 1 parameter.");
}
return is_array(reset($params));
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* Determines if a variable is declared and is different than null
* `isset(mixed $var, mixed ...$vars): bool`
*
* Returns true if var exists and has any value other than null. false otherwise.
*/
class IssetHandler extends Base {
public function handle($params, Template $template) {
if (count($params) === 0) {
throw new Exception("Invalid number of arguments for isset. isset expects at least 1 parameter.");
}
foreach ($params as $param) {
if (!isset($param)) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Smarty\FunctionHandler;
use Smarty\Exception;
use Smarty\Template;
/**
* is_array(mixed $value): bool
* Returns true if value is an array, false otherwise.
*/
class Time extends Base {
public function handle($params, Template $template) {
if (count($params) > 0) {
throw new Exception("Invalid number of arguments for time. time expects no parameters.");
}
return time();
}
}

View File

@@ -1037,7 +1037,7 @@ objectelement(res)::= PTR method(f). {
// function
//
function(res) ::= ns1(f) OPENP params(p) CLOSEP. {
res = $this->compiler->compilePHPFunctionCall(f, p);
res = $this->compiler->compileFunctionCall(f, p);
}
@@ -1071,7 +1071,7 @@ params(res) ::= expr(e). {
res = array(e);
}
// kein parameter
// no parameter
params(res) ::= . {
res = array();
}

View File

@@ -94,24 +94,6 @@ class Security {
*/
public $trusted_static_properties = [];
/**
* This is an array of trusted PHP functions.
* If empty all functions are allowed.
* To disable all PHP functions set $php_functions = null.
*
* @var array
*/
public $php_functions = ['isset', 'empty', 'count', 'sizeof', 'in_array', 'is_array', 'time',];
/**
* This is an array of trusted PHP modifiers.
* If empty all modifiers are allowed.
* To disable all modifier set $php_modifiers = null.
*
* @var array
*/
public $php_modifiers = ['escape', 'count', 'sizeof', 'nl2br',];
/**
* This is an array of allowed tags.
* If empty no restriction by allowed_tags.
@@ -216,27 +198,6 @@ class Security {
*/
protected $_secure_dir = [];
/**
* Cache for $php_resource_dir lookup
*
* @var array
*/
protected $_php_resource_dir = null;
/**
* Cache for $trusted_dir lookup
*
* @var array
*/
protected $_trusted_dir = null;
/**
* Cache for $_include_array lookup
*
* @var array
*/
protected $_include_dir = [];
/**
* @param Smarty $smarty
*/
@@ -244,24 +205,6 @@ class Security {
$this->smarty = $smarty;
}
/**
* Check if PHP function is trusted.
*
* @param string $function_name
* @param object $compiler compiler object
*
* @return boolean true if function is trusted
*/
public function isTrustedPhpFunction($function_name, $compiler) {
if (isset($this->php_functions)
&& (empty($this->php_functions) || in_array($function_name, $this->php_functions))
) {
return true;
}
$compiler->trigger_template_error("PHP function '{$function_name}' not allowed by security setting");
return false; // should not, but who knows what happens to the compiler in the future?
}
/**
* Check if static class is trusted.
*
@@ -317,25 +260,6 @@ class Security {
return false; // should not, but who knows what happens to the compiler in the future?
}
/**
* Check if PHP modifier is trusted.
*
* @param string $modifier_name
* @param object $compiler compiler object
*
* @return boolean true if modifier is trusted
* @deprecated
*/
public function isTrustedPhpModifier($modifier_name, $compiler) {
if (isset($this->php_modifiers)
&& (empty($this->php_modifiers) || in_array($modifier_name, $this->php_modifiers))
) {
return true;
}
$compiler->trigger_template_error("modifier '{$modifier_name}' not allowed by security setting");
return false; // should not, but who knows what happens to the compiler in the future?
}
/**
* Check if tag is trusted.
*

View File

@@ -64,7 +64,7 @@ class PHPUnit_Smarty extends PHPUnit\Framework\TestCase
*/
public static function setUpBeforeClass(): void
{
error_reporting(E_ALL & ~E_STRICT);
error_reporting(E_ALL & ~E_STRICT & ~E_DEPRECATED & ~E_USER_DEPRECATED);
self::$init = true;
self::$pluginsdir =self::getSmartyPluginsDir();
}

View File

@@ -40,32 +40,9 @@ class SecurityTest extends PHPUnit_Smarty
/**
* test trusted PHP function
*/
public function testTrustedPHPFunction()
public function testTrustedFunction()
{
$this->assertEquals("5", $this->smarty->fetch('string:{assign var=foo value=[1,2,3,4,5]}{sizeof($foo)}'));
}
/**
* test not trusted PHP function
*
*
*/
public function testNotTrustedPHPFunction()
{
$this->expectException(\Smarty\Exception::class);
$this->expectExceptionMessage('PHP function \'sizeof\' not allowed by security setting');
$this->smarty->security_policy->php_functions = array('null');
$this->smarty->fetch('string:{assign var=foo value=[1,2,3,4,5]}{sizeof($foo)}');
}
/**
* test not trusted PHP function at disabled security
*/
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)}'));
}
/**
@@ -74,7 +51,7 @@ class SecurityTest extends PHPUnit_Smarty
*/
public function testTrustedModifier()
{
$this->assertEquals("5", @$this->smarty->fetch('string:{assign var=foo value=[1,2,3,4,5]}{$foo|@sizeof}'));
$this->assertEquals("5", @$this->smarty->fetch('string:{assign var=foo value=[1,2,3,4,5]}{$foo|@count}'));
}
/**
@@ -87,21 +64,9 @@ class SecurityTest extends PHPUnit_Smarty
{
$this->expectException(\Smarty\Exception::class);
$this->expectExceptionMessage('modifier \'sizeof\' not allowed by security setting');
$this->smarty->security_policy->php_modifiers = array('null');
@$this->smarty->fetch('string:{assign var=foo value=[1,2,3,4,5]}{$foo|@sizeof}');
}
/**
* test not trusted modifier at disabled security
* @deprecated
*/
public function testDisabledTrustedModifier()
{
$this->smarty->security_policy->php_modifiers = array('null');
$this->smarty->disableSecurity();
@$this->assertEquals("5", $this->smarty->fetch('string:{assign var=foo value=[1,2,3,4,5]}{$foo|@sizeof}'));
}
/**
* test allowed tags
*/

View File

@@ -0,0 +1,28 @@
<?php
namespace UnitTests\TemplateSource\TagTests\PluginFunction;
class CountTest extends \PHPUnit_Smarty {
public function setUp(): void {
$this->setUpSmarty(__DIR__);
}
public function testBasicSyntax() {
$this->assertEquals('3', $this->smarty->fetch("string:{count([1,2,3])}"));
}
public function testNonRecursive() {
$this->assertEquals('3', $this->smarty->fetch("string:{count([1,2,[3,4]])}"));
}
public function testRecursive() {
$this->assertEquals('5', $this->smarty->fetch("string:{count([1,2,[3,4]], 1)}"));
}
public function testInvalidParameters() {
$this->expectException(\Smarty\Exception::class);
$this->expectExceptionMessage('Invalid number of arguments');
$this->assertEquals("", $this->smarty->fetch("string:{count()}"));
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace UnitTests\TemplateSource\TagTests\PluginFunction;
class EmptyTest extends \PHPUnit_Smarty {
public function setUp(): void {
$this->setUpSmarty(__DIR__);
}
public function testBasicSyntax() {
$this->assertEquals("yay", $this->smarty->fetch("string:{if empty(\$noSuch)}yay{/if}"));
}
public function testEmptyStringIsEmpty() {
$this->assertEquals("yay", $this->smarty->fetch("string:{if empty('')}yay{/if}"));
}
public function testFalseIsEmpty() {
$this->smarty->assign('test', false);
$this->assertEquals("yay", $this->smarty->fetch("string:{if empty(\$test)}yay{/if}"));
}
public function testIntZeroIsEmpty() {
$this->smarty->assign('test', 0);
$this->assertEquals("yay", $this->smarty->fetch("string:{if empty(\$test)}yay{/if}"));
}
public function testStringZeroIsEmpty() {
$this->smarty->assign('test', '0');
$this->assertEquals("yay", $this->smarty->fetch("string:{if empty(\$test)}yay{/if}"));
}
public function testIntThreeIsNotEmpty() {
$this->smarty->assign('test', 3);
$this->assertEquals("nay", $this->smarty->fetch("string:{if empty(\$test)}yay{else}nay{/if}"));
}
public function testInvalidParameters() {
$this->expectException(\Smarty\Exception::class);
$this->expectExceptionMessage('Invalid number of arguments');
$this->assertEquals("", $this->smarty->fetch("string:{empty(3, 'foo')}"));
}
}

View File

@@ -0,0 +1,27 @@
<?php
class InArrayTest extends \PHPUnit_Smarty {
public function setUp(): void
{
$this->setUpSmarty(__DIR__);
}
public function testBasicSyntax()
{
$this->assertEquals("yay", $this->smarty->fetch("string:{if in_array(3,[3])}yay{/if}"));
}
public function testNotInArray()
{
$this->assertEquals("nay", $this->smarty->fetch("string:{if in_array(2,[3])}yay{else}nay{/if}"));
}
public function testInvalidParameters()
{
$this->expectException(\Smarty\Exception::class);
$this->expectExceptionMessage('Invalid number of arguments');
$this->assertEquals("", $this->smarty->fetch("string:{in_array('foo')}"));
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace UnitTests\TemplateSource\TagTests\PluginFunction;
class IsArrayTest extends \PHPUnit_Smarty {
public function setUp(): void {
$this->setUpSmarty(__DIR__);
}
public function testBasicSyntax() {
$this->assertEquals("yay", $this->smarty->fetch("string:{if is_array([3])}yay{/if}"));
}
public function testIntNotIsArray() {
$this->assertEquals("nay", $this->smarty->fetch("string:{if is_array(2)}yay{else}nay{/if}"));
}
public function testStringNotIsArray() {
$this->assertEquals("nay", $this->smarty->fetch("string:{if is_array('foo')}yay{else}nay{/if}"));
}
public function testInvalidParameters() {
$this->expectException(\Smarty\Exception::class);
$this->expectExceptionMessage('Invalid number of arguments');
$this->assertEquals("", $this->smarty->fetch("string:{is_array(3, 'foo')}"));
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace UnitTests\TemplateSource\TagTests\PluginFunction;
class IssetTest extends \PHPUnit_Smarty {
public function setUp(): void {
$this->setUpSmarty(__DIR__);
}
public function testBasicSyntax() {
$this->assertEquals("nay", $this->smarty->fetch("string:{if isset(\$noSuch)}yay{else}nay{/if}"));
}
public function testEmptyStringIsset() {
$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}"));
}
public function testIntZeroIsset() {
$this->smarty->assign('test', 0);
$this->assertEquals("yay", $this->smarty->fetch("string:{if isset(\$test)}yay{/if}"));
}
public function testMultivar() {
$this->smarty->assign('test', 0);
$this->smarty->assign('test2', 'pizza');
$this->assertEquals("yay", $this->smarty->fetch("string:{if isset(\$test, \$test2)}yay{/if}"));
}
public function testMultivarOneNotset() {
$this->smarty->assign('test', 0);
$this->assertEquals("nay", $this->smarty->fetch("string:{if isset(\$test, \$test2)}yay{else}nay{/if}"));
}
public function testInvalidParameters() {
$this->expectException(\Smarty\Exception::class);
$this->expectExceptionMessage('Invalid number of arguments');
$this->assertEquals("", $this->smarty->fetch("string:{empty(3, 'foo')}"));
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace UnitTests\TemplateSource\TagTests\PluginFunction;
class TimeTest extends \PHPUnit_Smarty {
public function setUp(): void {
$this->setUpSmarty(__DIR__);
}
public function testBasicSyntax() {
$this->assertStringMatchesFormat('%d', $this->smarty->fetch("string:{time()}"));
}
public function testInvalidParameters() {
$this->expectException(\Smarty\Exception::class);
$this->expectExceptionMessage('Invalid number of arguments');
$this->assertEquals("", $this->smarty->fetch("string:{time(3, 'foo')}"));
}
}