mirror of
https://github.com/smarty-php/smarty.git
synced 2026-08-04 20:44:13 +02:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 838d6ad1c6 | |||
| 187f7b6246 | |||
| a1ccdb0518 | |||
| c285f84fc6 | |||
| a8d77c8666 | |||
| 7721e5d786 | |||
| 3e6a478e93 | |||
| c438c79d7d | |||
| e1ef353ccf | |||
| 5cb412d040 | |||
| 272a407e9d | |||
| c4851c12e3 | |||
| 48f9d291de | |||
| 4fbff32417 | |||
| bf87aee3f0 | |||
| ffc4415a4f | |||
| c11676e85a | |||
| 44a08bea7c | |||
| 2adb8801a5 | |||
| 9fc96a13db | |||
| 09e211cbfd | |||
| 76881c8d33 | |||
| 4549822cdd | |||
| 55799a9b65 | |||
| 6592bd4ad3 | |||
| 7320402766 | |||
| 1115eb6f65 | |||
| 9656f553d1 | |||
| 42b869e3a0 | |||
| 4cb00900d7 | |||
| 204f3bc74e | |||
| 409802db04 | |||
| 1015e5472b | |||
| c7a271323b | |||
| e2cb9873ff | |||
| f4152e9b81 | |||
| 5d0dd09632 | |||
| 9d559827fd | |||
| 5e2a9fbb26 | |||
| 9614a59199 | |||
| ee58a2173b | |||
| 29dd621b53 | |||
| de1bc77f7f | |||
| 326b2da8da | |||
| e0cbbdf6ea | |||
| b4bbc17f1c | |||
| 6e067ed327 | |||
| 3931d8f54b | |||
| d8c1dfe56e | |||
| 6887e55ded | |||
| 642fd69fea | |||
| 17a7d6fb5b | |||
| b96a5c3953 | |||
| 2ff66e0fcc |
@@ -32,6 +32,8 @@ jobs:
|
||||
- "8.0"
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
|
||||
compiler:
|
||||
- default
|
||||
@@ -46,7 +48,12 @@ jobs:
|
||||
- os: ubuntu-latest
|
||||
php-version: "8.2"
|
||||
compiler: jit
|
||||
|
||||
- os: ubuntu-latest
|
||||
php-version: "8.3"
|
||||
compiler: jit
|
||||
- os: ubuntu-latest
|
||||
php-version: "8.4"
|
||||
compiler: jit
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
@@ -6,6 +6,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [4.5.7] - 2026-06-29
|
||||
- Security: prevent symlinks inside a trusted `secure_dir`/template directory from being used to read files outside of it (CWE-22 path traversal), affecting `{include}` and `{fetch}` of local files
|
||||
- Security: `{html_image}` now escapes the `file`, `path_prefix`, `href`/`link`, `width` and `height` attributes (it already escaped `alt` and pass-through attributes), and `{html_select_date}` casts `day_size`/`month_size`/`year_size` to int (matching `{html_select_time}`), preventing untrusted values passed into these attributes from breaking out of the generated HTML (CWE-79)
|
||||
- Security: `{fetch}` no longer follows HTTP redirects for remote resources while a security policy is active, preventing an open redirect on a trusted host from bypassing `trusted_uri` (CWE-918 server-side request forgery)
|
||||
|
||||
|
||||
## [4.5.6] - 2025-08-26
|
||||
- Fixed that modifiers called like function would be compiled to modifier name instead of calling the registered callback [#1100](https://github.com/smarty-php/smarty/issues/1100)
|
||||
- Replace SMARTY_VERSION constant with $smarty.version in debug.tpl [#1073](https://github.com/smarty-php/smarty/issues/1073)
|
||||
|
||||
|
||||
- Fixed escaping of array/object keys in debug_print_var
|
||||
|
||||
## [4.5.5] - 2024-11-21
|
||||
|
||||
- Support the deprecations introduced in PHP 8.4 and added tests for PHP 8.4 [#1084](https://github.com/smarty-php/smarty/pull/1084)
|
||||
|
||||
## [4.5.4] - 2024-08-14
|
||||
- Fixed that using `count()` would trigger a deprecation notice. [#813](https://github.com/smarty-php/smarty/issues/813)
|
||||
|
||||
|
||||
## [4.5.3] - 2024-05-28
|
||||
- Fixed a code injection vulnerability in extends-tag. This addresses CVE-2024-35226.
|
||||
|
||||
|
||||
## [4.5.2] - 2024-04-06
|
||||
- Fixed argument must be passed by reference error introduced in v4.5.1 [#964](https://github.com/smarty-php/smarty/issues/964)
|
||||
|
||||
## [4.5.1] - 2024-03-18
|
||||
- Using unregistered static class methods in expressions now also triggers a deprecation notice because we will drop support for this in the next major release [#813](https://github.com/smarty-php/smarty/issues/813)
|
||||
|
||||
## [4.5.0] - 2024-03-18
|
||||
- (this release accidentally didn't contain any changes, fixed in 4.5.1)
|
||||
|
||||
## [4.4.1] - 2024-02-26
|
||||
- Fixed internal release-tooling
|
||||
|
||||
## [4.4.0] - 2024-02-26
|
||||
- Using the `|implode`, `|json_encode` and `|substr` modifiers does not generate a deprecation warning anymore as they will continue to be supported in v5 [#939](https://github.com/smarty-php/smarty/issues/939)
|
||||
|
||||
### Added
|
||||
- PHP8.3 support [#925](https://github.com/smarty-php/smarty/issues/925)
|
||||
|
||||
### Fixed
|
||||
- Incorrect compilation of expressions when escape_html=true [#930](https://github.com/smarty-php/smarty/pull/930)
|
||||
|
||||
## [4.3.4] - 2023-09-14
|
||||
|
||||
## [4.3.3] - 2023-09-14
|
||||
|
||||
### Fixed
|
||||
- `|strip_tags` does not work if the input is 0 [#890](https://github.com/smarty-php/smarty/issues/890)
|
||||
- Use of negative numbers in {math} equations [#895](https://github.com/smarty-php/smarty/issues/895)
|
||||
|
||||
@@ -7,7 +7,7 @@ Smarty is a template engine for PHP, facilitating the separation of presentation
|
||||
Read the [documentation](https://smarty-php.github.io/smarty/) to find out how to use it.
|
||||
|
||||
## Requirements
|
||||
Smarty can be run with PHP 7.1 to PHP 8.2.
|
||||
Smarty can be run with PHP 7.1 to PHP 8.4.
|
||||
|
||||
## Installation
|
||||
Smarty versions 3.1.11 or later can be installed with [Composer](https://getcomposer.org/).
|
||||
|
||||
@@ -13,12 +13,12 @@ class Smarty_Resource_Extendsall extends Smarty_Internal_Resource_Extends
|
||||
/**
|
||||
* populate Source Object with meta data from Resource
|
||||
*
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$uid = '';
|
||||
$sources = array();
|
||||
|
||||
@@ -42,3 +42,13 @@ services:
|
||||
service: base
|
||||
build:
|
||||
dockerfile: ./utilities/testrunners/php82/Dockerfile
|
||||
php83:
|
||||
extends:
|
||||
service: base
|
||||
build:
|
||||
dockerfile: ./utilities/testrunners/php83/Dockerfile
|
||||
php84:
|
||||
extends:
|
||||
service: base
|
||||
build:
|
||||
dockerfile: ./utilities/testrunners/php84/Dockerfile
|
||||
|
||||
@@ -178,14 +178,14 @@ The above example will output:
|
||||
</p>
|
||||
<p>
|
||||
name: Jack Jones<br />
|
||||
home phone: 777-555-5555<br />
|
||||
cell phone: 888-555-5555<br />
|
||||
home: 777-555-5555<br />
|
||||
cell: 888-555-5555<br />
|
||||
e-mail: jack@myexample.com
|
||||
</p>
|
||||
<p>
|
||||
name: Jane Munson<br />
|
||||
home phone: 000-555-5555<br />
|
||||
cell phone: 123456<br />
|
||||
home: 000-555-5555<br />
|
||||
cell: 123456<br />
|
||||
e-mail: jane@myexample.com
|
||||
</p>
|
||||
```
|
||||
|
||||
@@ -12,10 +12,12 @@ These parameters follow the modifier name and are separated by a `:`
|
||||
|
||||
- [capitalize](language-modifier-capitalize.md)
|
||||
- [cat](language-modifier-cat.md)
|
||||
- [count](language-modifier-count.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)
|
||||
- [debug_print_var](language-modifier-debug-print-var.md)
|
||||
- [date_format](language-modifier-date-format.md)
|
||||
- [default](language-modifier-default.md)
|
||||
- [escape](language-modifier-escape.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. |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Getting started
|
||||
|
||||
## Requirements
|
||||
Smarty can be run with PHP 7.1 to PHP 8.2.
|
||||
Smarty can be run with PHP 7.1 to PHP 8.4.
|
||||
|
||||
## Installation
|
||||
Smarty can be installed with [Composer](https://getcomposer.org/).
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Upgrading from an older version
|
||||
|
||||
## Upgrading from v3 to v4
|
||||
|
||||
Smarty 4 is mostly identical to Smarty 3. Most notably, it adds support for PHP8 and drops support for PHP7.0 and below.
|
||||
Additionally, some deprecated features that have long been discouraged have been dropped from the language.
|
||||
|
||||
### Muting PHP8 warnings
|
||||
If you simultaneously upgrade Smarty to v4 van PHP to v8, you may notice your error logs filling up with warnings about undefined or null template vars
|
||||
due to a change in how PHP handles these. This may be helpful to spot errors, but if you find this annoying, you can use
|
||||
`$smarty->muteUndefinedOrNullWarnings()` to make Smarty convert these warnings into notices.
|
||||
|
||||
### ASP tags
|
||||
You can no longer user ASP-style tags like `<% %>` and `<%= %>` in your templates.
|
||||
Replace them with `{...}` tags.
|
||||
|
||||
### SmartyBC
|
||||
Check your codebase for `SmartyBC`.
|
||||
We have dropped deprecated API calls that where only accessible through the SmartyBC class.
|
||||
|
||||
### No more embedded PHP
|
||||
We have completely dropped support for `{php}` and `{include_php}` tags and embedded PHP in templates.
|
||||
Check your templates for this, and rewrite any embedded PHP blocks, by moving logic to your PHP files or by
|
||||
creating a [plugin function](./programmers/plugins/plugins-functions.md).
|
||||
|
||||
### Other changes
|
||||
|
||||
Search your code for the following changes:
|
||||
|
||||
- `SMARTY_RESOURCE_CHAR_SET` and `SMARTY_RESOURCE_DATE_FORMAT` constants have been removed
|
||||
- `Smarty::muteExpectedErrors` and `Smarty::unmuteExpectedErrors` API methods have been removed
|
||||
- `Smarty::getVariable` method has been removed. Use [Smarty::getTemplateVars](programmers/api-functions/api-get-template-vars.md) instead.
|
||||
- [Smarty::registerResource](programmers/api-functions/api-register-resource.md) no longer accepts an array of callback functions
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -785,6 +785,9 @@ value(res) ::= ns1(c)DOUBLECOLON static_class_access(s). {
|
||||
if (isset($this->smarty->registered_classes[c])) {
|
||||
res = $this->smarty->registered_classes[c].'::'.s[0].s[1];
|
||||
} else {
|
||||
trigger_error('Using unregistered static method "' . c.'::'.s[0] . '" in a template is deprecated and will be ' .
|
||||
'removed in a future release. Use Smarty::registerClass to explicitly register ' .
|
||||
'a class for access.', E_USER_DEPRECATED);
|
||||
res = c.'::'.s[0].s[1];
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -107,7 +107,7 @@ class Smarty extends Smarty_Internal_TemplateBase
|
||||
/**
|
||||
* smarty version
|
||||
*/
|
||||
const SMARTY_VERSION = '4.3.2';
|
||||
const SMARTY_VERSION = '4.5.7';
|
||||
/**
|
||||
* define variable scopes
|
||||
*/
|
||||
@@ -1036,11 +1036,11 @@ class Smarty extends Smarty_Internal_TemplateBase
|
||||
/**
|
||||
* Get unique template id
|
||||
*
|
||||
* @param string $template_name
|
||||
* @param null|mixed $cache_id
|
||||
* @param null|mixed $compile_id
|
||||
* @param null $caching
|
||||
* @param \Smarty_Internal_Template $template
|
||||
* @param string $template_name
|
||||
* @param null|mixed $cache_id
|
||||
* @param null|mixed $compile_id
|
||||
* @param null $caching
|
||||
* @param \Smarty_Internal_Template|null $template
|
||||
*
|
||||
* @return string
|
||||
* @throws \SmartyException
|
||||
@@ -1050,7 +1050,7 @@ class Smarty extends Smarty_Internal_TemplateBase
|
||||
$cache_id = null,
|
||||
$compile_id = null,
|
||||
$caching = null,
|
||||
Smarty_Internal_Template $template = null
|
||||
?Smarty_Internal_Template $template = null
|
||||
) {
|
||||
$template_name = (strpos($template_name, ':') === false) ? "{$this->default_resource_type}:{$template_name}" :
|
||||
$template_name;
|
||||
|
||||
+2
-1
@@ -108,7 +108,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Smarty {Smarty::SMARTY_VERSION} Debug Console
|
||||
<h1>Smarty {$smarty.version} Debug Console
|
||||
- {if isset($template_name)}{$template_name|debug_print_var nofilter} {/if}{if !empty($template_data)}Total Time {$execution_time|string_format:"%.5f"}{/if}</h1>
|
||||
|
||||
{if !empty($template_data)}
|
||||
@@ -144,6 +144,7 @@
|
||||
{$vars['attributes']|debug_print_var nofilter}
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</table>
|
||||
|
||||
|
||||
@@ -191,7 +191,24 @@ function smarty_function_fetch($params, $template)
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$content = @file_get_contents($params[ 'file' ]);
|
||||
if ($protocol && isset($template->smarty->security_policy)) {
|
||||
// Remote resource (e.g. https://) reached through file_get_contents().
|
||||
// isTrustedUri() only validates the initial URL, but file_get_contents()
|
||||
// follows redirects by default, so an open redirect on an otherwise
|
||||
// trusted host could be used to reach a non-trusted target (SSRF).
|
||||
// Disable redirect-following while a security policy is in effect.
|
||||
$context = stream_context_create(
|
||||
array(
|
||||
'http' => array(
|
||||
'follow_location' => 0,
|
||||
'max_redirects' => 1,
|
||||
),
|
||||
)
|
||||
);
|
||||
$content = @file_get_contents($params[ 'file' ], false, $context);
|
||||
} else {
|
||||
$content = @file_get_contents($params[ 'file' ]);
|
||||
}
|
||||
if ($content === false) {
|
||||
throw new SmartyException("{fetch} cannot read resource '" . $params[ 'file' ] . "'");
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ function smarty_function_html_image($params, Smarty_Internal_Template $template)
|
||||
break;
|
||||
case 'link':
|
||||
case 'href':
|
||||
$prefix = '<a href="' . $_val . '">';
|
||||
$prefix = '<a href="' . smarty_function_escape_special_chars($_val) . '">';
|
||||
$suffix = '</a>';
|
||||
break;
|
||||
default:
|
||||
@@ -153,6 +153,11 @@ function smarty_function_html_image($params, Smarty_Internal_Template $template)
|
||||
$width = round($width * $_resize);
|
||||
$height = round($height * $_resize);
|
||||
}
|
||||
return $prefix . '<img src="' . $path_prefix . $file . '" alt="' . $alt . '" width="' . $width . '" height="' .
|
||||
$height . '"' . $extra . ' />' . $suffix;
|
||||
// $alt and the pass-through attributes ($extra) are already escaped above;
|
||||
// escape the remaining value-context params at output time so untrusted
|
||||
// values cannot break out of the attribute (CWE-79). The unescaped $file/
|
||||
// $width/$height are still used for getimagesize()/DPI math above.
|
||||
return $prefix . '<img src="' . smarty_function_escape_special_chars($path_prefix . $file) . '" alt="' . $alt
|
||||
. '" width="' . smarty_function_escape_special_chars($width) . '" height="'
|
||||
. smarty_function_escape_special_chars($height) . '"' . $extra . ' />' . $suffix;
|
||||
}
|
||||
|
||||
@@ -131,9 +131,6 @@ function smarty_function_html_select_date($params, Smarty_Internal_Template $tem
|
||||
case 'day_value_format':
|
||||
case 'month_format':
|
||||
case 'month_value_format':
|
||||
case 'day_size':
|
||||
case 'month_size':
|
||||
case 'year_size':
|
||||
case 'all_extra':
|
||||
case 'day_extra':
|
||||
case 'month_extra':
|
||||
@@ -151,6 +148,13 @@ function smarty_function_html_select_date($params, Smarty_Internal_Template $tem
|
||||
case 'year_id':
|
||||
$$_key = (string)$_value;
|
||||
break;
|
||||
case 'day_size':
|
||||
case 'month_size':
|
||||
case 'year_size':
|
||||
// numeric HTML size attribute; cast to int (consistent with
|
||||
// html_select_time) so it cannot break out of size="…" (CWE-79)
|
||||
$$_key = (int)$_value;
|
||||
break;
|
||||
case 'display_days':
|
||||
case 'display_months':
|
||||
case 'display_years':
|
||||
|
||||
@@ -31,7 +31,7 @@ function smarty_modifier_debug_print_var($var, $max = 10, $length = 40, $depth =
|
||||
break;
|
||||
}
|
||||
foreach ($var as $curr_key => $curr_val) {
|
||||
$results .= '<br>' . str_repeat(' ', $depth * 2) . '<b>' . strtr($curr_key, $_replace) .
|
||||
$results .= '<br>' . str_repeat(' ', $depth * 2) . '<b>' . htmlspecialchars(strtr($curr_key, $_replace)) .
|
||||
'</b> => ' .
|
||||
smarty_modifier_debug_print_var($curr_val, $max, $length, ++$depth, $objects);
|
||||
$depth--;
|
||||
@@ -49,7 +49,7 @@ function smarty_modifier_debug_print_var($var, $max = 10, $length = 40, $depth =
|
||||
}
|
||||
$objects[] = $var;
|
||||
foreach ($object_vars as $curr_key => $curr_val) {
|
||||
$results .= '<br>' . str_repeat(' ', $depth * 2) . '<b> ->' . strtr($curr_key, $_replace) .
|
||||
$results .= '<br>' . str_repeat(' ', $depth * 2) . '<b> ->' . htmlspecialchars(strtr($curr_key, $_replace)) .
|
||||
'</b> = ' . smarty_modifier_debug_print_var($curr_val, $max, $length, ++$depth, $objects);
|
||||
$depth--;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Smarty plugin
|
||||
*
|
||||
* @package Smarty
|
||||
* @subpackage PluginsModifier
|
||||
*/
|
||||
|
||||
function smarty_modifier_implode($values, $separator = '')
|
||||
{
|
||||
if (is_array($separator)) {
|
||||
return implode((string) ($values ?? ''), (array) $separator);
|
||||
}
|
||||
return implode((string) ($separator ?? ''), (array) $values);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Smarty plugin
|
||||
*
|
||||
* @package Smarty
|
||||
* @subpackage PluginsModifierCompiler
|
||||
*/
|
||||
function smarty_modifiercompiler_json_encode($params) {
|
||||
return 'json_encode(' . $params[0] . (isset($params[1]) ? ', (int) ' . $params[1] : '') . ')';
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Smarty plugin
|
||||
*
|
||||
* @package Smarty
|
||||
* @subpackage PluginsModifierCompiler
|
||||
*/
|
||||
function smarty_modifiercompiler_substr($params) {
|
||||
return 'substr((string) ' . $params[0] . ', (int) ' . $params[1] .
|
||||
(isset($params[2]) ? ', (int) ' . $params[2] : '') . ')';
|
||||
}
|
||||
@@ -44,15 +44,15 @@ abstract class Smarty_CacheResource
|
||||
/**
|
||||
* Read the cached template and process header
|
||||
*
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Template_Cached $cached cached object
|
||||
* @param boolean $update flag if called because cache update
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Template_Cached|null $cached cached object
|
||||
* @param boolean $update flag if called because cache update
|
||||
*
|
||||
* @return boolean true or false if the cached content does not exist
|
||||
*/
|
||||
abstract public function process(
|
||||
Smarty_Internal_Template $_template,
|
||||
Smarty_Template_Cached $cached = null,
|
||||
?Smarty_Template_Cached $cached = null,
|
||||
$update = false
|
||||
);
|
||||
|
||||
|
||||
@@ -124,15 +124,15 @@ abstract class Smarty_CacheResource_Custom extends Smarty_CacheResource
|
||||
/**
|
||||
* Read the cached template and process the header
|
||||
*
|
||||
* @param \Smarty_Internal_Template $_smarty_tpl do not change variable name, is used by compiled template
|
||||
* @param Smarty_Template_Cached $cached cached object
|
||||
* @param boolean $update flag if called because cache update
|
||||
* @param \Smarty_Internal_Template $_smarty_tpl do not change variable name, is used by compiled template
|
||||
* @param Smarty_Template_Cached|null $cached cached object
|
||||
* @param boolean $update flag if called because cache update
|
||||
*
|
||||
* @return boolean true or false if the cached content does not exist
|
||||
*/
|
||||
public function process(
|
||||
Smarty_Internal_Template $_smarty_tpl,
|
||||
Smarty_Template_Cached $cached = null,
|
||||
?Smarty_Template_Cached $cached = null,
|
||||
$update = false
|
||||
) {
|
||||
if (!$cached) {
|
||||
|
||||
@@ -88,15 +88,15 @@ abstract class Smarty_CacheResource_KeyValueStore extends Smarty_CacheResource
|
||||
/**
|
||||
* Read the cached template and process the header
|
||||
*
|
||||
* @param \Smarty_Internal_Template $_smarty_tpl do not change variable name, is used by compiled template
|
||||
* @param Smarty_Template_Cached $cached cached object
|
||||
* @param boolean $update flag if called because cache update
|
||||
* @param \Smarty_Internal_Template $_smarty_tpl do not change variable name, is used by compiled template
|
||||
* @param Smarty_Template_Cached|null $cached cached object
|
||||
* @param boolean $update flag if called because cache update
|
||||
*
|
||||
* @return boolean true or false if the cached content does not exist
|
||||
*/
|
||||
public function process(
|
||||
Smarty_Internal_Template $_smarty_tpl,
|
||||
Smarty_Template_Cached $cached = null,
|
||||
?Smarty_Template_Cached $cached = null,
|
||||
$update = false
|
||||
) {
|
||||
if (!$cached) {
|
||||
|
||||
@@ -88,15 +88,15 @@ class Smarty_Internal_CacheResource_File extends Smarty_CacheResource
|
||||
/**
|
||||
* Read the cached template and process its header
|
||||
*
|
||||
* @param \Smarty_Internal_Template $_smarty_tpl do not change variable name, is used by compiled template
|
||||
* @param Smarty_Template_Cached $cached cached object
|
||||
* @param bool $update flag if called because cache update
|
||||
* @param \Smarty_Internal_Template $_smarty_tpl do not change variable name, is used by compiled template
|
||||
* @param Smarty_Template_Cached|null $cached cached object
|
||||
* @param bool $update flag if called because cache update
|
||||
*
|
||||
* @return boolean true or false if the cached content does not exist
|
||||
*/
|
||||
public function process(
|
||||
Smarty_Internal_Template $_smarty_tpl,
|
||||
Smarty_Template_Cached $cached = null,
|
||||
?Smarty_Template_Cached $cached = null,
|
||||
$update = false
|
||||
) {
|
||||
$_smarty_tpl->cached->valid = false;
|
||||
|
||||
@@ -30,7 +30,7 @@ class Smarty_Internal_Compile_Extends extends Smarty_Internal_Compile_Shared_Inh
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $optional_attributes = array('extends_resource');
|
||||
public $optional_attributes = array();
|
||||
|
||||
/**
|
||||
* Attribute definition: Overwrites base class.
|
||||
@@ -62,29 +62,7 @@ class Smarty_Internal_Compile_Extends extends Smarty_Internal_Compile_Shared_Inh
|
||||
}
|
||||
// add code to initialize inheritance
|
||||
$this->registerInit($compiler, true);
|
||||
$file = trim($_attr[ 'file' ], '\'"');
|
||||
if (strlen($file) > 8 && substr($file, 0, 8) === 'extends:') {
|
||||
// generate code for each template
|
||||
$files = array_reverse(explode('|', substr($file, 8)));
|
||||
$i = 0;
|
||||
foreach ($files as $file) {
|
||||
if ($file[ 0 ] === '"') {
|
||||
$file = trim($file, '".');
|
||||
} else {
|
||||
$file = "'{$file}'";
|
||||
}
|
||||
$i++;
|
||||
if ($i === count($files) && isset($_attr[ 'extends_resource' ])) {
|
||||
$this->compileEndChild($compiler);
|
||||
}
|
||||
$this->compileInclude($compiler, $file);
|
||||
}
|
||||
if (!isset($_attr[ 'extends_resource' ])) {
|
||||
$this->compileEndChild($compiler);
|
||||
}
|
||||
} else {
|
||||
$this->compileEndChild($compiler, $_attr[ 'file' ]);
|
||||
}
|
||||
$this->compileEndChild($compiler, $_attr[ 'file' ]);
|
||||
$compiler->has_code = false;
|
||||
return '';
|
||||
}
|
||||
@@ -115,44 +93,4 @@ class Smarty_Internal_Compile_Extends extends Smarty_Internal_Compile_Shared_Inh
|
||||
'') . ");\n?>"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add code for including subtemplate to end of template
|
||||
*
|
||||
* @param \Smarty_Internal_TemplateCompilerBase $compiler
|
||||
* @param string $template subtemplate name
|
||||
*
|
||||
* @throws \SmartyCompilerException
|
||||
* @throws \SmartyException
|
||||
*/
|
||||
private function compileInclude(Smarty_Internal_TemplateCompilerBase $compiler, $template)
|
||||
{
|
||||
$compiler->parser->template_postfix[] = new Smarty_Internal_ParseTree_Tag(
|
||||
$compiler->parser,
|
||||
$compiler->compileTag(
|
||||
'include',
|
||||
array(
|
||||
$template,
|
||||
array('scope' => 'parent')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create source code for {extends} from source components array
|
||||
*
|
||||
* @param \Smarty_Internal_Template $template
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function extendsSourceArrayCode(Smarty_Internal_Template $template)
|
||||
{
|
||||
$resources = array();
|
||||
foreach ($template->source->components as $source) {
|
||||
$resources[] = $source->resource;
|
||||
}
|
||||
return $template->smarty->left_delimiter . 'extends file=\'extends:' . join('|', $resources) .
|
||||
'\' extends_resource=true' . $template->smarty->right_delimiter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,9 +109,11 @@ class Smarty_Internal_Compile_Private_Modifier extends Smarty_Internal_CompileBa
|
||||
if (!is_object($compiler->smarty->security_policy)
|
||||
|| $compiler->smarty->security_policy->isTrustedPhpModifier($modifier, $compiler)
|
||||
) {
|
||||
trigger_error('Using php-function "' . $modifier . '" as a modifier is deprecated and will be ' .
|
||||
'removed in a future release. Use Smarty::registerPlugin to explicitly register ' .
|
||||
'a custom modifier.', E_USER_DEPRECATED);
|
||||
if (!in_array($modifier, ['time', 'join', 'is_array', 'in_array'])) {
|
||||
trigger_error('Using unregistered function "' . $modifier . '" in a template is deprecated and will be ' .
|
||||
'removed in a future release. Use Smarty::registerPlugin to explicitly register ' .
|
||||
'a custom modifier.', E_USER_DEPRECATED);
|
||||
}
|
||||
$output = "{$modifier}({$params})";
|
||||
}
|
||||
$compiler->known_modifier_type[ $modifier ] = $type;
|
||||
|
||||
@@ -93,7 +93,7 @@ class Smarty_Internal_Compile_Private_Print_Expression extends Smarty_Internal_C
|
||||
}
|
||||
// autoescape html
|
||||
if ($compiler->template->smarty->escape_html) {
|
||||
$output = "htmlspecialchars((string) {$output}, ENT_QUOTES, '" . addslashes(Smarty::$_CHARSET) . "')";
|
||||
$output = "htmlspecialchars((string) ({$output}), ENT_QUOTES, '" . addslashes(Smarty::$_CHARSET) . "')";
|
||||
}
|
||||
// loop over registered filters
|
||||
if (!empty($compiler->template->smarty->registered_filters[ Smarty::FILTER_VARIABLE ])) {
|
||||
|
||||
@@ -190,7 +190,7 @@ abstract class Smarty_Internal_Data
|
||||
*
|
||||
* @return mixed variable value or or array of variables
|
||||
*/
|
||||
public function getTemplateVars($varName = null, Smarty_Internal_Data $_ptr = null, $searchParents = true)
|
||||
public function getTemplateVars($varName = null, ?Smarty_Internal_Data $_ptr = null, $searchParents = true)
|
||||
{
|
||||
return $this->ext->getTemplateVars->getTemplateVars($this, $varName, $_ptr, $searchParents);
|
||||
}
|
||||
@@ -200,7 +200,7 @@ abstract class Smarty_Internal_Data
|
||||
*
|
||||
* @param \Smarty_Internal_Data|null $data
|
||||
*/
|
||||
public function _mergeVars(Smarty_Internal_Data $data = null)
|
||||
public function _mergeVars(?Smarty_Internal_Data $data = null)
|
||||
{
|
||||
if (isset($data)) {
|
||||
if (!empty($this->tpl_vars)) {
|
||||
|
||||
@@ -94,7 +94,7 @@ class Smarty_Internal_ErrorHandler
|
||||
}
|
||||
|
||||
if ($this->allowUndefinedArrayKeys && preg_match(
|
||||
'/^(Undefined index|Undefined array key|Trying to access array offset on value of type)/',
|
||||
'/^(Undefined index|Undefined array key|Trying to access array offset on)/',
|
||||
$errstr
|
||||
)) {
|
||||
return; // suppresses this error
|
||||
|
||||
@@ -24,14 +24,14 @@ class Smarty_Internal_Method_CreateData
|
||||
* @api Smarty::createData()
|
||||
* @link https://www.smarty.net/docs/en/api.create.data.tpl
|
||||
*
|
||||
* @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj
|
||||
* @param \Smarty_Internal_Template|\Smarty_Internal_Data|\Smarty_Data|\Smarty $parent next higher level of Smarty
|
||||
* variables
|
||||
* @param string $name optional data block name
|
||||
* @param \Smarty_Internal_TemplateBase|\Smarty_Internal_Template|\Smarty $obj
|
||||
* @param \Smarty_Internal_Template|\Smarty_Internal_Data|\Smarty_Data|\Smarty|null $parent next higher level of Smarty
|
||||
* variables
|
||||
* @param string $name optional data block name
|
||||
*
|
||||
* @return \Smarty_Data data object
|
||||
*/
|
||||
public function createData(Smarty_Internal_TemplateBase $obj, Smarty_Internal_Data $parent = null, $name = null)
|
||||
public function createData(Smarty_Internal_TemplateBase $obj, ?Smarty_Internal_Data $parent = null, $name = null)
|
||||
{
|
||||
/* @var Smarty $smarty */
|
||||
$smarty = $obj->_getSmartyObj();
|
||||
|
||||
@@ -24,17 +24,17 @@ class Smarty_Internal_Method_GetTemplateVars
|
||||
* @api Smarty::getTemplateVars()
|
||||
* @link https://www.smarty.net/docs/en/api.get.template.vars.tpl
|
||||
*
|
||||
* @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $data
|
||||
* @param string $varName variable name or null
|
||||
* @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $_ptr optional pointer to data object
|
||||
* @param bool $searchParents include parent templates?
|
||||
* @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty $data
|
||||
* @param string $varName variable name or null
|
||||
* @param \Smarty_Internal_Data|\Smarty_Internal_Template|\Smarty|null $_ptr optional pointer to data object
|
||||
* @param bool $searchParents include parent templates?
|
||||
*
|
||||
* @return mixed variable value or or array of variables
|
||||
*/
|
||||
public function getTemplateVars(
|
||||
Smarty_Internal_Data $data,
|
||||
$varName = null,
|
||||
Smarty_Internal_Data $_ptr = null,
|
||||
?Smarty_Internal_Data $_ptr = null,
|
||||
$searchParents = true
|
||||
) {
|
||||
if (isset($varName)) {
|
||||
@@ -87,7 +87,7 @@ class Smarty_Internal_Method_GetTemplateVars
|
||||
public function _getVariable(
|
||||
Smarty_Internal_Data $data,
|
||||
$varName,
|
||||
Smarty_Internal_Data $_ptr = null,
|
||||
?Smarty_Internal_Data $_ptr = null,
|
||||
$searchParents = true,
|
||||
$errorEnable = true
|
||||
) {
|
||||
|
||||
@@ -22,11 +22,11 @@ class Smarty_Internal_Resource_Eval extends Smarty_Resource_Recompiled
|
||||
* populate Source Object with meta data from Resource
|
||||
*
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$source->uid = $source->filepath = sha1($source->name);
|
||||
$source->timestamp = $source->exists = true;
|
||||
|
||||
@@ -28,11 +28,11 @@ class Smarty_Internal_Resource_Extends extends Smarty_Resource
|
||||
* populate Source Object with meta data from Resource
|
||||
*
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
*
|
||||
* @throws SmartyException
|
||||
*/
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$uid = '';
|
||||
$sources = array();
|
||||
|
||||
@@ -21,11 +21,11 @@ class Smarty_Internal_Resource_File extends Smarty_Resource
|
||||
* populate Source Object with meta data from Resource
|
||||
*
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
*
|
||||
* @throws \SmartyException
|
||||
*/
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$source->filepath = $this->buildFilepath($source, $_template);
|
||||
if ($source->filepath !== false) {
|
||||
@@ -93,12 +93,12 @@ class Smarty_Internal_Resource_File extends Smarty_Resource
|
||||
* build template filepath by traversing the template_dir array
|
||||
*
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
*
|
||||
* @return string fully qualified filepath
|
||||
* @throws SmartyException
|
||||
*/
|
||||
protected function buildFilepath(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
protected function buildFilepath(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$file = $source->name;
|
||||
// absolute file ?
|
||||
|
||||
@@ -23,11 +23,11 @@ class Smarty_Internal_Resource_Stream extends Smarty_Resource_Recompiled
|
||||
* populate Source Object with meta data from Resource
|
||||
*
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
if (strpos($source->resource, '://') !== false) {
|
||||
$source->filepath = $source->resource;
|
||||
|
||||
@@ -22,11 +22,11 @@ class Smarty_Internal_Resource_String extends Smarty_Resource
|
||||
* populate Source Object with meta data from Resource
|
||||
*
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$source->uid = $source->filepath = sha1($source->name . $source->smarty->_joined_template_dir);
|
||||
$source->timestamp = $source->exists = true;
|
||||
|
||||
@@ -17,11 +17,11 @@ class Smarty_Internal_Runtime_CodeFrame
|
||||
/**
|
||||
* Create code frame for compiled and cached templates
|
||||
*
|
||||
* @param Smarty_Internal_Template $_template
|
||||
* @param string $content optional template content
|
||||
* @param string $functions compiled template function and block code
|
||||
* @param bool $cache flag for cache file
|
||||
* @param \Smarty_Internal_TemplateCompilerBase $compiler
|
||||
* @param Smarty_Internal_Template $_template
|
||||
* @param string $content optional template content
|
||||
* @param string $functions compiled template function and block code
|
||||
* @param bool $cache flag for cache file
|
||||
* @param \Smarty_Internal_TemplateCompilerBase|null $compiler
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@@ -30,7 +30,7 @@ class Smarty_Internal_Runtime_CodeFrame
|
||||
$content = '',
|
||||
$functions = '',
|
||||
$cache = false,
|
||||
Smarty_Internal_TemplateCompilerBase $compiler = null
|
||||
?Smarty_Internal_TemplateCompilerBase $compiler = null
|
||||
) {
|
||||
// build property code
|
||||
$properties[ 'version' ] = Smarty::SMARTY_VERSION;
|
||||
|
||||
@@ -168,7 +168,7 @@ class Smarty_Internal_Runtime_Inheritance
|
||||
public function process(
|
||||
Smarty_Internal_Template $tpl,
|
||||
Smarty_Internal_Block $block,
|
||||
Smarty_Internal_Block $parent = null
|
||||
?Smarty_Internal_Block $parent = null
|
||||
) {
|
||||
if ($block->hide && !isset($block->child)) {
|
||||
return;
|
||||
|
||||
@@ -149,7 +149,7 @@ class Smarty_Internal_Template extends Smarty_Internal_TemplateBase
|
||||
public function __construct(
|
||||
$template_resource,
|
||||
Smarty $smarty,
|
||||
Smarty_Internal_Data $_parent = null,
|
||||
?Smarty_Internal_Data $_parent = null,
|
||||
$_cache_id = null,
|
||||
$_compile_id = null,
|
||||
$_caching = null,
|
||||
|
||||
@@ -386,7 +386,7 @@ abstract class Smarty_Internal_TemplateCompilerBase
|
||||
public function compileTemplate(
|
||||
Smarty_Internal_Template $template,
|
||||
$nocache = null,
|
||||
Smarty_Internal_TemplateCompilerBase $parent_compiler = null
|
||||
?Smarty_Internal_TemplateCompilerBase $parent_compiler = null
|
||||
) {
|
||||
// get code frame of compiled template
|
||||
$_compiled_code = $template->smarty->ext->_codeFrame->create(
|
||||
@@ -407,9 +407,9 @@ abstract class Smarty_Internal_TemplateCompilerBase
|
||||
/**
|
||||
* Compile template source and run optional post filter
|
||||
*
|
||||
* @param \Smarty_Internal_Template $template
|
||||
* @param null|bool $nocache flag if template must be compiled in nocache mode
|
||||
* @param \Smarty_Internal_TemplateCompilerBase $parent_compiler
|
||||
* @param \Smarty_Internal_Template $template
|
||||
* @param null|bool $nocache flag if template must be compiled in nocache mode
|
||||
* @param \Smarty_Internal_TemplateCompilerBase|null $parent_compiler
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
@@ -417,7 +417,7 @@ abstract class Smarty_Internal_TemplateCompilerBase
|
||||
public function compileTemplateSource(
|
||||
Smarty_Internal_Template $template,
|
||||
$nocache = null,
|
||||
Smarty_Internal_TemplateCompilerBase $parent_compiler = null
|
||||
?Smarty_Internal_TemplateCompilerBase $parent_compiler = null
|
||||
) {
|
||||
try {
|
||||
// save template object in compiler class
|
||||
@@ -455,15 +455,29 @@ abstract class Smarty_Internal_TemplateCompilerBase
|
||||
$this->smarty->_current_file = $this->template->source->filepath;
|
||||
// get template source
|
||||
if (!empty($this->template->source->components)) {
|
||||
// we have array of inheritance templates by extends: resource
|
||||
// generate corresponding source code sequence
|
||||
$_content =
|
||||
Smarty_Internal_Compile_Extends::extendsSourceArrayCode($this->template);
|
||||
$_compiled_code = '<?php $_smarty_tpl->_loadInheritance(); $_smarty_tpl->inheritance->init($_smarty_tpl, true); ?>';
|
||||
|
||||
$i = 0;
|
||||
$reversed_components = array_reverse($this->template->getSource()->components);
|
||||
foreach ($reversed_components as $source) {
|
||||
$i++;
|
||||
if ($i === count($reversed_components)) {
|
||||
$_compiled_code .= '<?php $_smarty_tpl->inheritance->endChild($_smarty_tpl); ?>';
|
||||
}
|
||||
$_compiled_code .= $this->compileTag(
|
||||
'include',
|
||||
[
|
||||
var_export($source->resource, true),
|
||||
['scope' => 'parent'],
|
||||
]
|
||||
);
|
||||
}
|
||||
$_compiled_code = $this->postFilter($_compiled_code, $this->template);
|
||||
} else {
|
||||
// get template source
|
||||
$_content = $this->template->source->getContent();
|
||||
$_compiled_code = $this->postFilter($this->doCompile($this->preFilter($_content), true));
|
||||
}
|
||||
$_compiled_code = $this->postFilter($this->doCompile($this->preFilter($_content), true));
|
||||
if (!empty($this->required_plugins[ 'compiled' ]) || !empty($this->required_plugins[ 'nocache' ])) {
|
||||
$_compiled_code = '<?php ' . $this->compileRequiredPlugins() . "?>\n" . $_compiled_code;
|
||||
}
|
||||
@@ -603,7 +617,8 @@ abstract class Smarty_Internal_TemplateCompilerBase
|
||||
{
|
||||
if (!$this->smarty->security_policy || $this->smarty->security_policy->isTrustedPhpFunction($name, $this)) {
|
||||
if (strcasecmp($name, 'isset') === 0 || strcasecmp($name, 'empty') === 0
|
||||
|| strcasecmp($name, 'array') === 0 || is_callable($name)
|
||||
|| strcasecmp($name, 'array') === 0
|
||||
|| (is_callable($name) && !isset($this->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER][$name]))
|
||||
) {
|
||||
$func_name = smarty_strtolower_ascii($name);
|
||||
|
||||
@@ -635,17 +650,42 @@ abstract class Smarty_Internal_TemplateCompilerBase
|
||||
}
|
||||
if ($func_name === 'empty') {
|
||||
return $func_name . '(' .
|
||||
str_replace("')->value", "',null,true,false)->value", $parameter[ 0 ]) . ')';
|
||||
str_replace("')->value", "',null,true,false)->value", $parameter[0]) . ')';
|
||||
} else {
|
||||
return $func_name . '(' . $parameter[ 0 ] . ')';
|
||||
return $func_name . '(' . $parameter[0] . ')';
|
||||
}
|
||||
} else {
|
||||
|
||||
if (
|
||||
!$this->smarty->loadPlugin('smarty_modifiercompiler_' . $name)
|
||||
&& !isset($this->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER][$name])
|
||||
&& !in_array($name, ['time', 'join', 'is_array', 'in_array', 'count'])
|
||||
) {
|
||||
trigger_error('Using unregistered function "' . $name . '" in a template is deprecated and will be ' .
|
||||
'removed in a future release. Use Smarty::registerPlugin to explicitly register ' .
|
||||
'a custom modifier.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
return $name . '(' . implode(',', $parameter) . ')';
|
||||
}
|
||||
} else {
|
||||
$this->trigger_template_error("unknown function '{$name}'");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER][$name])) {
|
||||
if ($name === $this->smarty->registered_plugins[Smarty::PLUGIN_MODIFIER][$name][0]) {
|
||||
return $name . '(' . implode(',', $parameter) . ')';
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'call_user_func_array($_smarty_tpl->registered_plugins[ \'%s\' ][ %s ][ 0 ], array( %s ))',
|
||||
Smarty::PLUGIN_MODIFIER,
|
||||
var_export($name, true),
|
||||
implode(',', $parameter)
|
||||
);
|
||||
}
|
||||
|
||||
$this->trigger_template_error("unknown function '{$name}'");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2425,6 +2425,9 @@ public static $yy_action = array(
|
||||
if (isset($this->smarty->registered_classes[$this->yystack[$this->yyidx + -2]->minor])) {
|
||||
$this->_retvalue = $this->smarty->registered_classes[$this->yystack[$this->yyidx + -2]->minor].'::'.$this->yystack[$this->yyidx + 0]->minor[0].$this->yystack[$this->yyidx + 0]->minor[1];
|
||||
} else {
|
||||
trigger_error('Using unregistered static method "' . $this->yystack[$this->yyidx + -2]->minor.'::'.$this->yystack[$this->yyidx + 0]->minor[0] . '" in a template is deprecated and will be ' .
|
||||
'removed in a future release. Use Smarty::registerClass to explicitly register ' .
|
||||
'a class for access.', E_USER_DEPRECATED);
|
||||
$this->_retvalue = $this->yystack[$this->yyidx + -2]->minor.'::'.$this->yystack[$this->yyidx + 0]->minor[0].$this->yystack[$this->yyidx + 0]->minor[1];
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -165,16 +165,16 @@ abstract class Smarty_Resource
|
||||
* wrapper for backward compatibility to versions < 3.1.22
|
||||
* Either [$_template] or [$smarty, $template_resource] must be specified
|
||||
*
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty $smarty smarty object
|
||||
* @param string $template_resource resource identifier
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
* @param Smarty|null $smarty smarty object
|
||||
* @param string $template_resource resource identifier
|
||||
*
|
||||
* @return \Smarty_Template_Source Source Object
|
||||
* @throws \SmartyException
|
||||
*/
|
||||
public static function source(
|
||||
Smarty_Internal_Template $_template = null,
|
||||
Smarty $smarty = null,
|
||||
?Smarty_Internal_Template $_template = null,
|
||||
?Smarty $smarty = null,
|
||||
$template_resource = null
|
||||
) {
|
||||
return Smarty_Template_Source::load($_template, $smarty, $template_resource);
|
||||
@@ -193,10 +193,10 @@ abstract class Smarty_Resource
|
||||
/**
|
||||
* populate Source Object with meta data from Resource
|
||||
*
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
*/
|
||||
abstract public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null);
|
||||
abstract public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null);
|
||||
|
||||
/**
|
||||
* populate Source Object with timestamp and exists from Resource
|
||||
|
||||
@@ -42,10 +42,10 @@ abstract class Smarty_Resource_Custom extends Smarty_Resource
|
||||
/**
|
||||
* populate Source Object with meta data from Resource
|
||||
*
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
*/
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$source->filepath = $source->type . ':' . $this->generateSafeName($source->name);
|
||||
$source->uid = sha1($source->type . ':' . $source->name);
|
||||
|
||||
@@ -253,7 +253,7 @@ class Smarty_Security
|
||||
*
|
||||
* @param string $function_name
|
||||
* @param object $compiler compiler object
|
||||
*
|
||||
* @deprecated
|
||||
* @return boolean true if function is trusted
|
||||
*/
|
||||
public function isTrustedPhpFunction($function_name, $compiler)
|
||||
@@ -590,12 +590,34 @@ class Smarty_Security
|
||||
*/
|
||||
private function _checkDir($filepath, $dirs)
|
||||
{
|
||||
$directory = dirname($this->smarty->_realpath($filepath, true)) . DIRECTORY_SEPARATOR;
|
||||
// Resolve the canonical, symlink-free path of the requested file so that
|
||||
// a symlink located inside a trusted directory cannot be abused to read
|
||||
// a file outside of it (CWE-22 path traversal). Smarty::_realpath() only
|
||||
// normalizes the path as a string and does not follow symlinks, so we
|
||||
// fall back to it only when the file does not yet exist on disk (e.g.
|
||||
// config/cache paths that are validated before being written).
|
||||
$realpath = @realpath($filepath);
|
||||
$resolved = $realpath !== false ? $realpath : $this->smarty->_realpath($filepath, true);
|
||||
$directory = dirname($resolved) . DIRECTORY_SEPARATOR;
|
||||
|
||||
// Canonicalize the trusted directories the same way. This keeps
|
||||
// legitimate symlinked deployment paths working (e.g. a Capistrano-style
|
||||
// "current" release symlink, or macOS' /var -> /private/var): both the
|
||||
// file and the trusted directories are compared after symlinks have been
|
||||
// resolved.
|
||||
$trusted = array();
|
||||
foreach ($dirs as $dir => $unused) {
|
||||
$trusted[ $dir ] = true;
|
||||
if (($dirRealpath = @realpath($dir)) !== false) {
|
||||
$trusted[ rtrim($dirRealpath, '\\/') . DIRECTORY_SEPARATOR ] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$_directory = array();
|
||||
if (!preg_match('#[\\\\/][.][.][\\\\/]#', $directory)) {
|
||||
while (true) {
|
||||
// test if the directory is trusted
|
||||
if (isset($dirs[ $directory ])) {
|
||||
if (isset($trusted[ $directory ])) {
|
||||
return $_directory;
|
||||
}
|
||||
// abort if we've reached root
|
||||
|
||||
@@ -63,16 +63,16 @@ class Smarty_Template_Config extends Smarty_Template_Source
|
||||
* initialize Source Object for given resource
|
||||
* Either [$_template] or [$smarty, $template_resource] must be specified
|
||||
*
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty $smarty smarty object
|
||||
* @param string $template_resource resource identifier
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
* @param Smarty|null $smarty smarty object
|
||||
* @param string $template_resource resource identifier
|
||||
*
|
||||
* @return Smarty_Template_Config Source Object
|
||||
* @throws SmartyException
|
||||
*/
|
||||
public static function load(
|
||||
Smarty_Internal_Template $_template = null,
|
||||
Smarty $smarty = null,
|
||||
?Smarty_Internal_Template $_template = null,
|
||||
?Smarty $smarty = null,
|
||||
$template_resource = null
|
||||
) {
|
||||
static $_incompatible_resources = array('extends' => true, 'php' => true);
|
||||
|
||||
@@ -148,16 +148,16 @@ class Smarty_Template_Source
|
||||
* initialize Source Object for given resource
|
||||
* Either [$_template] or [$smarty, $template_resource] must be specified
|
||||
*
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty $smarty smarty object
|
||||
* @param string $template_resource resource identifier
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
* @param Smarty|null $smarty smarty object
|
||||
* @param string $template_resource resource identifier
|
||||
*
|
||||
* @return Smarty_Template_Source Source Object
|
||||
* @throws SmartyException
|
||||
*/
|
||||
public static function load(
|
||||
Smarty_Internal_Template $_template = null,
|
||||
Smarty $smarty = null,
|
||||
?Smarty_Internal_Template $_template = null,
|
||||
?Smarty $smarty = null,
|
||||
$template_resource = null
|
||||
) {
|
||||
if ($_template) {
|
||||
|
||||
@@ -21,7 +21,7 @@ class SmartyCompilerException extends SmartyException
|
||||
int $code = 0,
|
||||
?string $filename = null,
|
||||
?int $line = null,
|
||||
Throwable $previous = null
|
||||
?Throwable $previous = null
|
||||
) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
|
||||
+5
-4
@@ -8,13 +8,14 @@ else
|
||||
fi
|
||||
|
||||
git checkout -b "release/$1"
|
||||
sed -i "s/## \\[Unreleased\\]/## \\[Unreleased\\]\\n\\n## \\[$1\\] - $(date +%Y-%m-%d)/" CHANGELOG.md
|
||||
sed -i "s/const SMARTY_VERSION = '[^']\+';/const SMARTY_VERSION = '$1';/" libs/Smarty.class.php
|
||||
|
||||
git add CHANGELOG.md libs/Smarty.class.php
|
||||
php utilities/update-changelog.php $1
|
||||
php utilities/update-smarty-version-number.php $1
|
||||
|
||||
git add changelog CHANGELOG.md libs/Smarty.class.php
|
||||
git commit -m "version bump"
|
||||
|
||||
git checkout master
|
||||
git checkout support/4
|
||||
git pull
|
||||
git merge --no-ff "release/$1"
|
||||
git branch -d "release/$1"
|
||||
|
||||
+6
-2
@@ -26,7 +26,9 @@ markdown_extensions:
|
||||
|
||||
nav:
|
||||
- Home: 'index.md'
|
||||
- 'Getting started': 'getting-started.md'
|
||||
- 'Getting started':
|
||||
- Introduction: 'getting-started.md'
|
||||
- 'Upgrading from an older version': 'upgrading.md'
|
||||
- 'Designers':
|
||||
- 'Basic Syntax':
|
||||
- Introduction: 'designers/language-basic-syntax/index.md'
|
||||
@@ -47,10 +49,12 @@ 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'
|
||||
- 'debug_print_var': 'designers/language-modifiers/language-modifier-debug-print-var.md'
|
||||
- 'date_format': 'designers/language-modifiers/language-modifier-date-format.md'
|
||||
- 'default': 'designers/language-modifiers/language-modifier-default.md'
|
||||
- 'escape': 'designers/language-modifiers/language-modifier-escape.md'
|
||||
@@ -120,4 +124,4 @@ nav:
|
||||
- 'programmers/caching.md'
|
||||
- 'programmers/resources.md'
|
||||
- 'programmers/advanced-features.md'
|
||||
- 'programmers/plugins.md'
|
||||
- 'programmers/plugins.md'
|
||||
|
||||
+5
-1
@@ -15,7 +15,11 @@
|
||||
timeoutForSmallTests="1"
|
||||
timeoutForMediumTests="10"
|
||||
timeoutForLargeTests="60"
|
||||
verbose="false">
|
||||
verbose="false"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
convertDeprecationsToExceptions="true">
|
||||
<testsuites>
|
||||
<testsuite name="foo">
|
||||
<directory>./tests/UnitTests/</directory>
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
# - ./run-tests-for-all-php-versions.sh --group 20221124
|
||||
# - ./run-tests-for-all-php-versions.sh --exclude-group slow
|
||||
|
||||
docker-compose run php71 ./run-tests.sh $@ && \
|
||||
docker-compose run php72 ./run-tests.sh $@ && \
|
||||
docker-compose run php73 ./run-tests.sh $@ && \
|
||||
docker-compose run php74 ./run-tests.sh $@ && \
|
||||
docker-compose run php80 ./run-tests.sh $@ && \
|
||||
docker-compose run php81 ./run-tests.sh $@
|
||||
COMPOSE_CMD="mutagen-compose"
|
||||
|
||||
$COMPOSE_CMD run --rm php71 ./run-tests.sh $@ && \
|
||||
$COMPOSE_CMD run --rm php72 ./run-tests.sh $@ && \
|
||||
$COMPOSE_CMD run --rm php73 ./run-tests.sh $@ && \
|
||||
$COMPOSE_CMD run --rm php74 ./run-tests.sh $@ && \
|
||||
$COMPOSE_CMD run --rm php80 ./run-tests.sh $@ && \
|
||||
$COMPOSE_CMD run --rm php81 ./run-tests.sh $@ && \
|
||||
$COMPOSE_CMD run --rm php82 ./run-tests.sh $@ && \
|
||||
$COMPOSE_CMD run --rm php83 ./run-tests.sh $@ && \
|
||||
$COMPOSE_CMD run --rm php84 ./run-tests.sh $@
|
||||
|
||||
@@ -68,7 +68,7 @@ class PHPUnit_Smarty extends PHPUnit\Framework\TestCase
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
error_reporting(E_ALL & ~E_STRICT);
|
||||
error_reporting(E_ALL);
|
||||
self::$init = true;
|
||||
self::$pluginsdir =self::getSmartyPluginsDir();
|
||||
}
|
||||
@@ -149,7 +149,7 @@ class PHPUnit_Smarty extends PHPUnit\Framework\TestCase
|
||||
$this->smarty->setCacheDir(__DIR__ . '/cache');
|
||||
}
|
||||
|
||||
$this->getSmartyObj();
|
||||
$this->smarty->setErrorReporting(E_ALL &~ E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,4 +30,36 @@ class AutoEscapeTest extends PHPUnit_Smarty
|
||||
$tpl->assign('foo', '<a@b.c>');
|
||||
$this->assertEquals("<a@b.c>", $this->smarty->fetch($tpl));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* test 'escapeHtml' property
|
||||
* @group issue906
|
||||
*/
|
||||
public function testAutoEscapeDoesNotEscapeFunctionPlugins()
|
||||
{
|
||||
$this->smarty->registerPlugin(
|
||||
Smarty::PLUGIN_FUNCTION,
|
||||
'horizontal_rule',
|
||||
function ($params, $smarty) { return "<hr>"; }
|
||||
);
|
||||
$tpl = $this->smarty->createTemplate('eval:{horizontal_rule}');
|
||||
$this->assertEquals("<hr>", $this->smarty->fetch($tpl));
|
||||
}
|
||||
|
||||
/**
|
||||
* test 'escapeHtml' property
|
||||
* @group issue906
|
||||
*/
|
||||
public function testAutoEscapeDoesNotEscapeBlockPlugins()
|
||||
{
|
||||
$this->smarty->registerPlugin(
|
||||
Smarty::PLUGIN_BLOCK,
|
||||
'paragraphify',
|
||||
function ($params, $content) { return $content == null ? null : "<p>".$content."</p>"; }
|
||||
);
|
||||
$tpl = $this->smarty->createTemplate('eval:{paragraphify}hi{/paragraphify}');
|
||||
$this->assertEquals("<p>hi</p>", $this->smarty->fetch($tpl));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,6 +49,6 @@ class LoadPluginTest extends PHPUnit_Smarty
|
||||
*/
|
||||
public function testLoadPluginSmartyPluginCounter()
|
||||
{
|
||||
$this->assertTrue($this->smarty->loadPlugin('Smarty_Function_Counter') == true);
|
||||
$this->assertTrue($this->smarty->loadPlugin('Smarty_function_counter') == true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ class Smarty_Resource_Filetest extends Smarty_Internal_Resource_File
|
||||
/**
|
||||
* populate Source Object with meta data from Resource
|
||||
*
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
*/
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
parent::populate($source, $_template);
|
||||
if ($source->exists) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
class Smarty_Resource_Db4 extends Smarty_Resource
|
||||
{
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$source->filepath = 'db4:';
|
||||
$source->uid = sha1($source->resource);
|
||||
|
||||
+3
-3
@@ -38,10 +38,10 @@ class Smarty_Resource_Ambiguous extends Smarty_Internal_Resource_File
|
||||
/**
|
||||
* populate Source Object with meta data from Resource
|
||||
*
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template $_template template object
|
||||
* @param Smarty_Template_Source $source source object
|
||||
* @param Smarty_Internal_Template|null $_template template object
|
||||
*/
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$segment = '';
|
||||
if ($this->segment) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
class Smarty_Resource_Db extends Smarty_Resource_Recompiled {
|
||||
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null) {
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null) {
|
||||
$source->filepath = 'db:';
|
||||
$source->uid = sha1($source->resource);
|
||||
$source->timestamp = 1000000000;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
class Smarty_Resource_Db2 extends Smarty_Resource_Recompiled
|
||||
{
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$source->filepath = 'db2:';
|
||||
$source->uid = sha1($source->resource);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
class Smarty_Resource_Db3 extends Smarty_Resource
|
||||
{
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$source->filepath = 'db3:';
|
||||
$source->uid = sha1($source->resource);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
class Smarty_Resource_Db4 extends Smarty_Resource
|
||||
{
|
||||
public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null)
|
||||
public function populate(Smarty_Template_Source $source, ?Smarty_Internal_Template $_template = null)
|
||||
{
|
||||
$source->filepath = 'db4:';
|
||||
$source->uid = sha1($source->resource);
|
||||
|
||||
@@ -52,7 +52,7 @@ class SecurityTest extends PHPUnit_Smarty
|
||||
*/
|
||||
public function testTrustedPHPFunction()
|
||||
{
|
||||
$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)}'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +75,7 @@ class SecurityTest extends PHPUnit_Smarty
|
||||
{
|
||||
$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)}'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,10 +152,10 @@ class SecurityTest extends PHPUnit_Smarty
|
||||
*/
|
||||
public function testAllowedModifier1()
|
||||
{
|
||||
error_reporting(E_ALL & E_STRICT);
|
||||
error_reporting(E_ALL);
|
||||
$this->smarty->security_policy->allowed_modifiers = array('capitalize');
|
||||
$this->assertEquals("Hello World", $this->smarty->fetch('string:{"hello world"|capitalize}'));
|
||||
error_reporting(E_ALL | E_STRICT);
|
||||
error_reporting(E_ALL);
|
||||
}
|
||||
|
||||
public function testAllowedModifier2()
|
||||
@@ -375,6 +375,87 @@ class SecurityTest extends PHPUnit_Smarty
|
||||
$this->smarty->display('string:{$smarty.template_object}');
|
||||
}
|
||||
|
||||
/**
|
||||
* A symlink located inside a trusted secure_dir must not be usable to read
|
||||
* a file outside of it (CWE-22 path traversal via symlink).
|
||||
*/
|
||||
public function testSymlinkEscapeFromSecureDirIsRejected()
|
||||
{
|
||||
list($secureDir, $outsideFile) = $this->createSymlinkFixture('secret-outside-content');
|
||||
$link = $secureDir . DIRECTORY_SEPARATOR . 'finance_doc';
|
||||
if (!@symlink($outsideFile, $link)) {
|
||||
$this->markTestSkipped('Unable to create symlinks on this platform');
|
||||
}
|
||||
|
||||
$this->smarty->security_policy->secure_dir = array($secureDir . DIRECTORY_SEPARATOR);
|
||||
|
||||
$this->expectException('SmartyException');
|
||||
$this->expectExceptionMessage('not trusted file path');
|
||||
// Use forward slashes: backslashes in a double-quoted template string are
|
||||
// interpreted as escape sequences (\f, \r, ...), which would corrupt a
|
||||
// Windows path. Forward slashes work on every platform.
|
||||
$this->smarty->fetch('string:{include file="' . str_replace('\\', '/', $link) . '"}');
|
||||
}
|
||||
|
||||
/**
|
||||
* A symlink that stays inside the trusted secure_dir must keep working, so
|
||||
* legitimate (e.g. deployment) symlinks are not broken by the fix above.
|
||||
*/
|
||||
public function testSymlinkWithinSecureDirIsAllowed()
|
||||
{
|
||||
list($secureDir) = $this->createSymlinkFixture('secret-outside-content');
|
||||
$target = $secureDir . DIRECTORY_SEPARATOR . 'real.tpl';
|
||||
file_put_contents($target, 'inside-content');
|
||||
$link = $secureDir . DIRECTORY_SEPARATOR . 'linked.tpl';
|
||||
if (!@symlink($target, $link)) {
|
||||
$this->markTestSkipped('Unable to create symlinks on this platform');
|
||||
}
|
||||
|
||||
$this->smarty->security_policy->secure_dir = array($secureDir . DIRECTORY_SEPARATOR);
|
||||
|
||||
// Forward slashes so backslashes in a Windows path are not mistaken for
|
||||
// escape sequences inside the double-quoted template string.
|
||||
$this->assertEquals('inside-content', $this->smarty->fetch('string:{include file="' . str_replace('\\', '/', $link) . '"}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a temporary directory tree for the symlink tests: a (canonicalized)
|
||||
* secure directory plus a file located outside of it. The tree is removed in
|
||||
* tearDown(). Returns array(secureDir, outsideFile).
|
||||
*/
|
||||
private function createSymlinkFixture($outsideContent)
|
||||
{
|
||||
$base = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'smarty_symlink_' . getmypid() . '_' . uniqid();
|
||||
$secureDir = $base . DIRECTORY_SEPARATOR . 'secure';
|
||||
mkdir($secureDir, 0777, true);
|
||||
$outsideFile = $base . DIRECTORY_SEPARATOR . 'outside.txt';
|
||||
file_put_contents($outsideFile, $outsideContent);
|
||||
|
||||
// Canonicalize so secure_dir is symlink-free (sys_get_temp_dir() itself
|
||||
// may sit under a symlink, e.g. /var -> /private/var on macOS).
|
||||
$this->symlinkFixtureDir = realpath($base);
|
||||
return array(realpath($secureDir), realpath($outsideFile));
|
||||
}
|
||||
|
||||
/** @var string|null temp dir created by createSymlinkFixture(), removed in tearDown */
|
||||
private $symlinkFixtureDir = null;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (!empty($this->symlinkFixtureDir) && is_dir($this->symlinkFixtureDir)) {
|
||||
$it = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($this->symlinkFixtureDir, \FilesystemIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
foreach ($it as $entry) {
|
||||
($entry->isDir() && !$entry->isLink()) ? rmdir($entry->getPathname()) : unlink($entry->getPathname());
|
||||
}
|
||||
rmdir($this->symlinkFixtureDir);
|
||||
$this->symlinkFixtureDir = null;
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class mysecuritystaticclass
|
||||
|
||||
@@ -46,7 +46,7 @@ class ClearAllAssignTest extends PHPUnit_Smarty
|
||||
*/
|
||||
public function testClearAllAssignInTemplate()
|
||||
{
|
||||
error_reporting((error_reporting() & ~(E_NOTICE | E_USER_NOTICE | E_WARNING)));
|
||||
$this->smarty->setErrorReporting($this->smarty->error_reporting & ~(E_NOTICE | E_USER_NOTICE | E_WARNING));
|
||||
$this->_tpl->clearAllAssign();
|
||||
$this->assertEquals('foobar', $this->smarty->fetch($this->_tpl));
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class ClearAllAssignTest extends PHPUnit_Smarty
|
||||
*/
|
||||
public function testClearAllAssignInData()
|
||||
{
|
||||
error_reporting((error_reporting() & ~(E_NOTICE | E_USER_NOTICE | E_WARNING)));
|
||||
$this->smarty->setErrorReporting($this->smarty->error_reporting & ~(E_NOTICE | E_USER_NOTICE | E_WARNING));
|
||||
$this->_data->clearAllAssign();
|
||||
$this->assertEquals('fooblar', $this->smarty->fetch($this->_tpl));
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class ClearAllAssignTest extends PHPUnit_Smarty
|
||||
*/
|
||||
public function testClearAllAssignInSmarty()
|
||||
{
|
||||
error_reporting((error_reporting() & ~(E_NOTICE | E_USER_NOTICE | E_WARNING)));
|
||||
$this->smarty->setErrorReporting($this->smarty->error_reporting & ~(E_NOTICE | E_USER_NOTICE | E_WARNING));
|
||||
$this->smarty->clearAllAssign();
|
||||
$this->assertEquals('barblar', $this->smarty->fetch($this->_tpl));
|
||||
}
|
||||
|
||||
@@ -109,4 +109,17 @@ class GetTemplateVarsTest extends PHPUnit_Smarty
|
||||
$this->assertEquals("bar2", $this->smarty->getTemplateVars('foo2', $data2, false));
|
||||
$this->assertEquals("", $this->smarty->getTemplateVars('blar', $data2, false));
|
||||
}
|
||||
|
||||
public function testSimpleCallReturnsArrayWithAllValues()
|
||||
{
|
||||
$this->smarty->assign('foo', 'bar');
|
||||
$this->smarty->assign('i', 3);
|
||||
|
||||
$vars = $this->smarty->getTemplateVars();
|
||||
|
||||
$this->assertArrayHasKey('foo', $vars);
|
||||
$this->assertArrayHasKey('i', $vars);
|
||||
$this->assertEquals('bar', $vars['foo']);
|
||||
$this->assertEquals(3,$vars['i']);
|
||||
}
|
||||
}
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// first class callables where introduced in PHP 8.1
|
||||
if (PHP_VERSION_ID >= 80100) {
|
||||
|
||||
/**
|
||||
* class for register modifier with (first class) callables tests
|
||||
*
|
||||
* @runTestsInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
* @backupStaticAttributes enabled
|
||||
*/
|
||||
class RegisterModifierFirstClassCallablesTest extends PHPUnit_Smarty
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->setUpSmarty(__DIR__);
|
||||
}
|
||||
|
||||
|
||||
public function testInit()
|
||||
{
|
||||
$this->cleanDirs();
|
||||
}
|
||||
|
||||
public function testRegisterFirstClassCallable()
|
||||
{
|
||||
$this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'testmodifier', eval('return strrev(...);'));
|
||||
$this->assertEquals('mosredna', $this->smarty->fetch('string:{"andersom"|testmodifier}'));
|
||||
}
|
||||
|
||||
public function testRegisterFirstClassCallableSameName()
|
||||
{
|
||||
$this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'mymodifier', eval('return strrev(...);'));
|
||||
$this->assertEquals('mosredna', $this->smarty->fetch('string:{"andersom"|mymodifier}'));
|
||||
}
|
||||
|
||||
public function testRegisterFirstClassCallableAsFunc()
|
||||
{
|
||||
$this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'kprint_r_out', eval('return strrev(...);'));
|
||||
$this->smarty->assign('myVar', 'andersom');
|
||||
$this->assertEquals('mosredna', $this->smarty->fetch('string:{kprint_r_out($myVar)}'));
|
||||
}
|
||||
|
||||
public function testRegisterFirstClassCallableSameNameAsPhpFunc()
|
||||
{
|
||||
$this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'mymodifierfcc', eval('return strrev(...);'));
|
||||
$this->assertEquals('mosredna', $this->smarty->fetch('string:{mymodifierfcc("andersom")}'));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
function mymodifierfcc($a, $b, $c)
|
||||
{
|
||||
return "$a function $b $c";
|
||||
}
|
||||
@@ -88,6 +88,22 @@ class RegisterModifierTest extends PHPUnit_Smarty
|
||||
$this->smarty->unregisterPlugin(Smarty::PLUGIN_MODIFIER, 'testmodifier');
|
||||
$this->assertTrue(isset($this->smarty->registered_plugins[Smarty::PLUGIN_BLOCK]['testmodifier']));
|
||||
}
|
||||
|
||||
|
||||
public function testRegisterNativePhpFuncAsString()
|
||||
{
|
||||
$this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'strrev', 'strrev');
|
||||
$this->smarty->assign('myVar', 'andersom');
|
||||
$this->assertEquals('mosredna', $this->smarty->fetch('string:{strrev($myVar)}'));
|
||||
}
|
||||
|
||||
public function testRegisterNativePhpFuncUnderDifferentName()
|
||||
{
|
||||
$this->smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'k_xyz_a', 'strrev');
|
||||
$this->smarty->assign('myVar', 'andersom');
|
||||
$this->assertEquals('mosredna', $this->smarty->fetch('string:{k_xyz_a($myVar)}'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function mymodifier($a, $b, $c)
|
||||
|
||||
@@ -1371,8 +1371,38 @@ class CompileBlockExtendsTest extends PHPUnit_Smarty
|
||||
);
|
||||
}
|
||||
|
||||
public function testBlockWithAssign() {
|
||||
$this->assertEquals('Captured content is: Content with lots of html here', $this->smarty->fetch('038_child.tpl'));
|
||||
}
|
||||
public function testBlockWithAssign() {
|
||||
$this->assertEquals('Captured content is: Content with lots of html here', $this->smarty->fetch('038_child.tpl'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test escaping of file parameter
|
||||
*/
|
||||
public function testEscaping()
|
||||
{
|
||||
$this->expectException(SmartyException::class);
|
||||
$this->expectExceptionMessageRegExp('/Unable to load.*/');
|
||||
$this->assertEquals('hello world', $this->smarty->fetch('escaping.tpl'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test escaping of file parameter 2
|
||||
*/
|
||||
public function testEscaping2()
|
||||
{
|
||||
$this->expectException(SmartyException::class);
|
||||
$this->expectExceptionMessageRegExp('/Unable to load.*/');
|
||||
$this->assertEquals('hello world', $this->smarty->fetch('escaping2.tpl'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test escaping of file parameter 3
|
||||
*/
|
||||
public function testEscaping3()
|
||||
{
|
||||
$this->expectException(SmartyException::class);
|
||||
$this->expectExceptionMessageRegExp('/Unable to load.*/');
|
||||
$this->assertEquals('hello world', $this->smarty->fetch('escaping3.tpl'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{extends "extends:helloworld.tpl', var_dump(shell_exec('ls')), 1, 2, 3, 4, 5, 6);}}?>"}
|
||||
@@ -0,0 +1 @@
|
||||
{extends 'extends:"helloworld.tpl\', var_dump(shell_exec(\'ls\')), 1, 2, 3, 4, 5, 6);}}?>'}
|
||||
@@ -0,0 +1 @@
|
||||
{extends file='extends:"helloworld.tpl'|cat:"', var_dump(shell_exec('ls')), 1, 2, 3, 4, 5, 6);}}?>"}
|
||||
@@ -82,6 +82,18 @@ class CompileIncludeTest extends PHPUnit_Smarty
|
||||
$this->assertEquals('I1I2I3', $content, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* test template name escaping
|
||||
*/
|
||||
public function testIncludeFilenameEscaping()
|
||||
{
|
||||
$this->expectException(SmartyException::class);
|
||||
$this->expectExceptionMessageRegExp('/Unable to load.*/');
|
||||
$tpl = $this->smarty->createTemplate('test_include_security.tpl');
|
||||
$content = $this->smarty->fetch($tpl);
|
||||
$this->assertEquals("hello world", $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* test standard output
|
||||
*
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{include file="helloworld.tpl', var_dump(shell_exec('ls')), 1, 2, 3, 4, 5, 6);}}?>"}
|
||||
@@ -82,4 +82,99 @@ class PluginFunctionFetchTest extends PHPUnit_Smarty
|
||||
$this->smarty->fetch('string:{fetch file="/templates/../etc/passwd"}');
|
||||
}
|
||||
|
||||
/**
|
||||
* When a security policy is in effect, {fetch} of a remote resource must not
|
||||
* follow redirects, otherwise an open redirect on a trusted host could be
|
||||
* used to bypass trusted_uri and reach an internal target (SSRF, CWE-918).
|
||||
*
|
||||
* @runInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
public function testFetchRemoteDisablesRedirectsUnderSecurity()
|
||||
{
|
||||
FetchContextCaptureStreamWrapper::$capturedOptions = null;
|
||||
stream_wrapper_register('ssrftest', 'FetchContextCaptureStreamWrapper');
|
||||
try {
|
||||
$this->smarty->enableSecurity();
|
||||
$this->smarty->security_policy->trusted_uri[] = '/^ssrftest:\/\/allowed$/';
|
||||
|
||||
$result = $this->smarty->fetch('string:{fetch file="ssrftest://allowed/data"}');
|
||||
|
||||
$this->assertSame('BODY', $result);
|
||||
$this->assertIsArray(FetchContextCaptureStreamWrapper::$capturedOptions);
|
||||
$this->assertArrayHasKey('http', FetchContextCaptureStreamWrapper::$capturedOptions);
|
||||
$this->assertSame(0, FetchContextCaptureStreamWrapper::$capturedOptions['http']['follow_location']);
|
||||
$this->assertLessThanOrEqual(1, FetchContextCaptureStreamWrapper::$capturedOptions['http']['max_redirects']);
|
||||
} finally {
|
||||
stream_wrapper_unregister('ssrftest');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Without a security policy there is no trusted_uri to bypass, so the
|
||||
* redirect-disabling stream context is not applied (backwards compatible).
|
||||
*
|
||||
* @runInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
public function testFetchRemoteKeepsDefaultBehaviorWithoutSecurity()
|
||||
{
|
||||
FetchContextCaptureStreamWrapper::$capturedOptions = null;
|
||||
stream_wrapper_register('ssrftest', 'FetchContextCaptureStreamWrapper');
|
||||
try {
|
||||
$result = $this->smarty->fetch('string:{fetch file="ssrftest://allowed/data"}');
|
||||
|
||||
$this->assertSame('BODY', $result);
|
||||
$this->assertSame(array(), FetchContextCaptureStreamWrapper::$capturedOptions);
|
||||
} finally {
|
||||
stream_wrapper_unregister('ssrftest');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal custom stream wrapper used by the fetch SSRF tests: it records the
|
||||
* stream context options that {fetch} passes to file_get_contents() and returns
|
||||
* a fixed body so the call succeeds without touching the network.
|
||||
*/
|
||||
class FetchContextCaptureStreamWrapper
|
||||
{
|
||||
/** @var resource|null populated by PHP when a context is passed */
|
||||
public $context;
|
||||
|
||||
/** @var array|null options captured from the context on the last open */
|
||||
public static $capturedOptions = null;
|
||||
|
||||
private $read = false;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
self::$capturedOptions = isset($this->context) ? stream_context_get_options($this->context) : array();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
if ($this->read) {
|
||||
return '';
|
||||
}
|
||||
$this->read = true;
|
||||
return 'BODY';
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return $this->read;
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* Smarty PHPunit tests of the {html_image} function plugin
|
||||
*
|
||||
* @package PHPunit
|
||||
*/
|
||||
|
||||
/**
|
||||
* class for {html_image} tests
|
||||
*
|
||||
* @runTestsInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
* @backupStaticAttributes enabled
|
||||
*/
|
||||
class PluginFunctionHtmlImageTest extends PHPUnit_Smarty
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->setUpSmarty(__DIR__);
|
||||
}
|
||||
|
||||
public function testInit()
|
||||
{
|
||||
$this->cleanDirs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Passing both width and height skips the getimagesize() lookup, so no real
|
||||
* image file is needed to render the tag.
|
||||
*/
|
||||
private function render($params)
|
||||
{
|
||||
$tpl = $this->smarty->createTemplate('eval:{html_image file=$file width=$width height=$height href=$href path_prefix=$path_prefix}');
|
||||
$tpl->assign($params + array(
|
||||
'file' => 'pic.jpg',
|
||||
'width' => 44,
|
||||
'height' => 68,
|
||||
'href' => '',
|
||||
'path_prefix' => '',
|
||||
));
|
||||
return $tpl->fetch();
|
||||
}
|
||||
|
||||
public function testHrefIsEscaped()
|
||||
{
|
||||
$result = $this->render(array('href' => '"><script>alert(1)</script>'));
|
||||
$this->assertStringNotContainsString('<script>', $result);
|
||||
$this->assertStringContainsString('<script>', $result);
|
||||
}
|
||||
|
||||
public function testWidthIsEscaped()
|
||||
{
|
||||
$result = $this->render(array('width' => '44" onload="alert(1)'));
|
||||
$this->assertStringNotContainsString('onload="', $result);
|
||||
$this->assertStringContainsString('"', $result);
|
||||
}
|
||||
|
||||
public function testHeightIsEscaped()
|
||||
{
|
||||
$result = $this->render(array('height' => '68" onmouseover="alert(1)'));
|
||||
$this->assertStringNotContainsString('onmouseover="', $result);
|
||||
}
|
||||
|
||||
public function testFileAndPathPrefixAreEscaped()
|
||||
{
|
||||
$result = $this->render(array('file' => 'pic.jpg"><script>alert(1)</script>', 'path_prefix' => '"><b>'));
|
||||
$this->assertStringNotContainsString('<script>', $result);
|
||||
$this->assertStringNotContainsString('<b>', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Benign values must be unchanged (no breakage, no double-encoding of an
|
||||
* ampersand already present in a URL).
|
||||
*/
|
||||
public function testBenignValuesAreUnchanged()
|
||||
{
|
||||
$result = $this->render(array('width' => 44, 'height' => 68, 'href' => 'detail.php?id=1&page=2'));
|
||||
$this->assertStringContainsString('width="44"', $result);
|
||||
$this->assertStringContainsString('height="68"', $result);
|
||||
$this->assertStringContainsString('src="pic.jpg"', $result);
|
||||
$this->assertStringContainsString('href="detail.php?id=1&page=2"', $result);
|
||||
$this->assertStringNotContainsString('&amp;', $result);
|
||||
}
|
||||
}
|
||||
+13
@@ -308,6 +308,19 @@ class PluginFunctionHtmlSelectDateTest extends PHPUnit_Smarty
|
||||
$this->assertEquals($result, $tpl->fetch());
|
||||
}
|
||||
|
||||
/**
|
||||
* day_size/month_size/year_size are numeric HTML size attributes and are
|
||||
* cast to int (consistent with html_select_time) so an untrusted value
|
||||
* cannot break out of size="…" (CWE-79).
|
||||
*/
|
||||
public function testSizeIsCastToInt()
|
||||
{
|
||||
$tpl = $this->smarty->createTemplate('eval:{html_select_date time=' . $this->now . ' year_size="5\" onfocus=alert(1) x=\""}');
|
||||
$result = $tpl->fetch();
|
||||
$this->assertStringContainsString('size="5"', $result);
|
||||
$this->assertStringNotContainsString('onfocus', $result);
|
||||
}
|
||||
|
||||
public function testFieldOrder()
|
||||
{
|
||||
$n = "\n";
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Smarty PHPunit tests of modifier
|
||||
*/
|
||||
|
||||
/**
|
||||
* class for modifier tests
|
||||
*
|
||||
* @runTestsInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
* @backupStaticAttributes enabled
|
||||
*/
|
||||
class PluginModifierImplodeTest extends PHPUnit_Smarty
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->setUpSmarty(__DIR__);
|
||||
}
|
||||
|
||||
public function testDefault()
|
||||
{
|
||||
$tpl = $this->smarty->createTemplate('string:{""|implode:$v}');
|
||||
$tpl->assign("v", ["1", "2"]);
|
||||
$this->assertEquals("12", $this->smarty->fetch($tpl));
|
||||
}
|
||||
public function testWithSeparator()
|
||||
{
|
||||
$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));
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Smarty PHPunit tests of modifier
|
||||
*/
|
||||
|
||||
/**
|
||||
* class for modifier tests
|
||||
*
|
||||
* @runTestsInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
* @backupStaticAttributes enabled
|
||||
*/
|
||||
class PluginModifierJsonEncodeTest extends PHPUnit_Smarty
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->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}}'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Smarty PHPunit tests of modifier
|
||||
*/
|
||||
|
||||
/**
|
||||
* class for modifier tests
|
||||
*
|
||||
* @runTestsInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
* @backupStaticAttributes enabled
|
||||
*/
|
||||
class PluginModifierSubstrTest extends PHPUnit_Smarty
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -148,4 +148,37 @@ class CompileSectionTest extends PHPUnit_Smarty
|
||||
);
|
||||
}
|
||||
|
||||
public function testNestedArrayAsLoopParameter()
|
||||
{
|
||||
$data = [
|
||||
['name' => 'John Smith', 'home' => '555-555-5555',
|
||||
'cell' => '666-555-5555', 'email' => 'john@myexample.com'],
|
||||
['name' => 'Jack Jones', 'home' => '777-555-5555',
|
||||
'cell' => '888-555-5555', 'email' => 'jack@myexample.com'],
|
||||
['name' => 'Jane Munson', 'home' => '000-555-5555',
|
||||
'cell' => '123456', 'email' => 'jane@myexample.com']
|
||||
];
|
||||
$this->smarty->assign('contacts',$data);
|
||||
$result = $this->smarty->fetch('string:{section name=customer loop=$contacts}
|
||||
name: {$contacts[customer].name}
|
||||
home: {$contacts[customer].home}
|
||||
cell: {$contacts[customer].cell}
|
||||
e-mail: {$contacts[customer].email}
|
||||
{/section}');
|
||||
$this->assertEquals('name: John Smith
|
||||
home: 555-555-5555
|
||||
cell: 666-555-5555
|
||||
e-mail: john@myexample.com
|
||||
name: Jack Jones
|
||||
home: 777-555-5555
|
||||
cell: 888-555-5555
|
||||
e-mail: jack@myexample.com
|
||||
name: Jane Munson
|
||||
home: 000-555-5555
|
||||
cell: 123456
|
||||
e-mail: jane@myexample.com
|
||||
', $result);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -438,7 +438,31 @@ class CompileFunctionTest extends PHPUnit_Smarty
|
||||
*/
|
||||
public function testIllegalFunctionName() {
|
||||
$this->expectException(SmartyCompilerException::class);
|
||||
$this->smarty->fetch('string:{function name=\'rce(){};echo "hi";function \'}{/function}');
|
||||
$this->smarty->fetch('string:{function name=\'rce(){};echo "hi";function \'}{/function}');
|
||||
}
|
||||
|
||||
/**
|
||||
* test shorthand function definition with regular call
|
||||
*/
|
||||
public function testShorthand1()
|
||||
{
|
||||
$this->assertEquals("gribus", $this->smarty->fetch('shorthand1.tpl'));
|
||||
}
|
||||
|
||||
/**
|
||||
* test normal function definition with shorthand call
|
||||
*/
|
||||
public function testShorthand2()
|
||||
{
|
||||
$this->assertEquals("gribus", $this->smarty->fetch('shorthand2.tpl'));
|
||||
}
|
||||
|
||||
/**
|
||||
* test shorthand function definition with shorthand call
|
||||
*/
|
||||
public function testShorthand3()
|
||||
{
|
||||
$this->assertEquals("gribus", $this->smarty->fetch('shorthand3.tpl'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
{function blah}gribus{/function}
|
||||
{call name=blah}
|
||||
@@ -0,0 +1,2 @@
|
||||
{function name=blah}gribus{/function}
|
||||
{blah}
|
||||
@@ -0,0 +1,2 @@
|
||||
{function blah}gribus{/function}
|
||||
{blah}
|
||||
@@ -57,6 +57,7 @@ class PhpFunctionTest extends PHPUnit_Smarty
|
||||
public function testEmpty2()
|
||||
{
|
||||
$this->smarty->disableSecurity();
|
||||
$this->smarty->registerPlugin('modifier', 'pass', 'pass');
|
||||
$this->smarty->assign('var', array(null,
|
||||
false,
|
||||
(int) 0,
|
||||
@@ -78,6 +79,7 @@ class PhpFunctionTest extends PHPUnit_Smarty
|
||||
public function testEmpty3()
|
||||
{
|
||||
$this->smarty->disableSecurity();
|
||||
$this->smarty->registerPlugin('modifier', 'pass', 'pass');
|
||||
$this->smarty->assign('var', array(true,
|
||||
(int) 1,
|
||||
(float) 0.1,
|
||||
@@ -114,6 +116,7 @@ class PhpFunctionTest extends PHPUnit_Smarty
|
||||
public function testIsset1()
|
||||
{
|
||||
$this->smarty->disableSecurity();
|
||||
$this->smarty->registerPlugin('modifier', 'pass', 'pass');
|
||||
$this->smarty->assign('isNull', null);
|
||||
$this->smarty->assign('isSet', 1);
|
||||
$this->smarty->assign('arr', array('isNull' => null, 'isSet' => 1));
|
||||
@@ -155,7 +158,7 @@ class PhpFunctionTest extends PHPUnit_Smarty
|
||||
public function testIsset3($strTemplate, $result)
|
||||
{
|
||||
$this->smarty->disableSecurity();
|
||||
|
||||
$this->smarty->registerPlugin('modifier', 'intval', 'intval');
|
||||
$this->smarty->assign('varobject', new TestIsset());
|
||||
$this->smarty->assign('vararray', $vararray = array(
|
||||
'keythatexists' => false,
|
||||
@@ -196,6 +199,107 @@ class PhpFunctionTest extends PHPUnit_Smarty
|
||||
array('{if isset($_varsimple{$key})}true{else}false{/if}', 'true'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests various PHP functions (deprecated)
|
||||
* @dataProvider dataVariousPHPFunctions
|
||||
*/
|
||||
public function testVariousPHPFunctions($strTemplate, $value, $expected) {
|
||||
$this->smarty->disableSecurity();
|
||||
$this->cleanDirs();
|
||||
$this->smarty->assign('value', $value);
|
||||
$this->assertEquals($expected, $this->smarty->fetch('string:' . $strTemplate));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testIsset3
|
||||
*/
|
||||
public function dataVariousPHPFunctions()
|
||||
{
|
||||
return array(
|
||||
array('{$a = count($value)}{$a}', array(1,2,3), '3'),
|
||||
array('{$a = in_array("b", $value)}{$a}', array(1,'b',3), true),
|
||||
array('{$a = strlen(uniqid())}{$a}', '', 13),
|
||||
array('{$a = date("Y", $value)}{$a}', strtotime("01-01-2030"), 2030),
|
||||
array('{$a = PhpFunctionTest::sayHi($value)}{$a}', 'mario', 'hi mario'),
|
||||
array('{$a = pass($value)}{$a}', 'mario', 'mario'),
|
||||
array('{$a = 1}{$b = Closure::fromCallable($value)}{$a}', 'strlen', 1),
|
||||
);
|
||||
}
|
||||
|
||||
public static function sayHi($value) {
|
||||
return 'hi ' . $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that each function that will not be supported in Smarty 5 does throw an E_USER_DEPRECATED notice.
|
||||
* @dataProvider dataDeprecationNoticesForSmarty5
|
||||
* @deprecated
|
||||
*/
|
||||
public function testDeprecationNoticesForSmarty5($strTemplateSource, $expected = '', $shouldTriggerDeprecationNotice = false) {
|
||||
|
||||
// this overrides the error reporting level set in \PHPUnit_Smarty::setUpSmarty
|
||||
$previousErrorReporting = $this->smarty->error_reporting;
|
||||
$this->smarty->setErrorReporting($previousErrorReporting | E_USER_DEPRECATED);
|
||||
|
||||
$this->smarty->assign('a', 'a');
|
||||
$this->smarty->assign('ar', [1,2]);
|
||||
$this->smarty->assign('f', 3.14);
|
||||
|
||||
$errorMessage = '';
|
||||
$output = '';
|
||||
|
||||
try {
|
||||
$output = $this->smarty->fetch('string:' . $strTemplateSource);
|
||||
} catch (Exception $e) {
|
||||
$errorMessage = $e->getMessage();
|
||||
}
|
||||
|
||||
if ($shouldTriggerDeprecationNotice) {
|
||||
$this->assertStringContainsString('Using unregistered function', $errorMessage);
|
||||
} else {
|
||||
$this->assertEquals($expected, $output);
|
||||
$this->assertEquals('', $errorMessage);
|
||||
}
|
||||
|
||||
$this->smarty->setErrorReporting($previousErrorReporting);
|
||||
}
|
||||
|
||||
public function dataDeprecationNoticesForSmarty5()
|
||||
{
|
||||
|
||||
return [
|
||||
|
||||
['{if empty($a)}{else}b{/if}', 'b', false],
|
||||
['{json_encode($a)}', '"a"', false],
|
||||
['{nl2br($a)}', 'a', false],
|
||||
['{$a|nl2br}', 'a', false],
|
||||
['{round($f, 1)}', '3.1', false],
|
||||
['{$f|round}', '3', false],
|
||||
['{str_repeat($a, 2)}', 'aa', false],
|
||||
['{$a|str_repeat:3}', 'aaa', false],
|
||||
['{$a|strip_tags}', 'a', false],
|
||||
['{$a|strlen}', '1', false],
|
||||
['{$a|substr:-1}', 'a', false],
|
||||
['{$f|substr:-1}', '4', false],
|
||||
['{$ar|count}', '2', false],
|
||||
['{count($ar)}', '2', false],
|
||||
['{foreach "."|explode:$f as $n}{$n}{/foreach}', '314', false],
|
||||
['{"-"|implode:$ar}', '1-2', false],
|
||||
['{"-"|join:$ar}', '1-2', false],
|
||||
['{$f|wordwrap:2:"k":true}', "3.k14", false],
|
||||
['{$f|number_format:1:","}', "3,1", false],
|
||||
['{if in_array(1, $ar)}yes{/if}', "yes", false],
|
||||
['{if is_array($ar)}yes{/if}', "yes", false],
|
||||
['{if time() gt 0}yes{/if}', "yes", false],
|
||||
|
||||
['{if array_chunk($ar, 2)}x{else}y{/if}', '', true],
|
||||
['{$a|addslashes}', '', true],
|
||||
['{$a|sha1}', '', true],
|
||||
['{$a|get_parent_class}', '', true],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -299,4 +299,12 @@ class ScopeTest extends PHPUnit_Smarty
|
||||
$this->smarty->assign('scope', 'none');
|
||||
$r = $this->smarty->fetch('test_function_scope.tpl');
|
||||
}
|
||||
|
||||
public function testFunctionScopeIsLocalByDefault()
|
||||
{
|
||||
$r = $this->smarty->fetch('string:{function name=test}{$var="b"}{/function}{$var="a"}{test}{$var}');
|
||||
$this->assertEquals('a', $r);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Smarty PHPunit test reproducing issue #1189.
|
||||
*
|
||||
* When a parent template is {include}d, then a child template that {extends}
|
||||
* the parent overrides a {block}, a subsequent {include} of the parent in the
|
||||
* same render must still show the parent's block content.
|
||||
*
|
||||
* The block override from the extending child must not leak into the later
|
||||
* include of the parent template.
|
||||
*
|
||||
* @see https://github.com/smarty-php/smarty/issues/1189
|
||||
*
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class IncludeExtendsBlockLeakIssue1189Test extends PHPUnit_Smarty
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->setUpSmarty(__DIR__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequence: include parent -> include child(extends parent) -> include parent.
|
||||
* Expected: PARENT CHILD PARENT
|
||||
* Bug (#1189): PARENT CHILD CHILD
|
||||
*/
|
||||
public function testBlockOverrideDoesNotLeakIntoLaterParentInclude()
|
||||
{
|
||||
$result = $this->smarty->fetch('top.tpl');
|
||||
$this->assertSame('PARENT CHILD PARENT', preg_replace('/\s+/', ' ', trim($result)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
{extends file="parent.tpl"}
|
||||
{block name=message}CHILD{/block}
|
||||
@@ -0,0 +1 @@
|
||||
{block name=message}PARENT{/block}
|
||||
@@ -0,0 +1 @@
|
||||
{include file="parent.tpl"} {include file="child.tpl"} {include file="parent.tpl"}
|
||||
@@ -32,4 +32,11 @@ class ExtendsIssue419Test extends PHPUnit_Smarty
|
||||
$this->assertEquals('child', $this->smarty->fetch('extends:001_parent.tpl|001_child.tpl'));
|
||||
}
|
||||
|
||||
public function testextendsSecurity()
|
||||
{
|
||||
$this->expectException(SmartyException::class);
|
||||
$this->expectExceptionMessageRegExp('/Unable to load.*/');
|
||||
$this->assertEquals('child', $this->smarty->fetch('string:{include "001_parent.tpl\', var_dump(shell_exec(\'ls\')), 1, 2, 3, 4, 5, 6);}}?>"}'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
class ArgumentMustBePassedByReference961Test extends PHPUnit_Smarty
|
||||
{
|
||||
|
||||
/**
|
||||
* @group issue961
|
||||
*/
|
||||
public function testReset()
|
||||
{
|
||||
$smarty = new Smarty();
|
||||
$smarty->registerPlugin('modifier', 'reset', 'reset');
|
||||
$templateStr = "string:{reset(\$ar)}";
|
||||
$smarty->assign('ar', [1,2,3]);
|
||||
$this->assertEquals(
|
||||
'1',
|
||||
$smarty->fetch($templateStr)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group issue961
|
||||
* @deprecated
|
||||
*/
|
||||
public function testResetAsModifier()
|
||||
{
|
||||
$smarty = new Smarty();
|
||||
try {
|
||||
$templateStr = "string:{\$ar|reset}";
|
||||
$smarty->assign('ar', [1,2,3]);
|
||||
$this->assertEquals(
|
||||
'1',
|
||||
$smarty->fetch($templateStr)
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group issue961
|
||||
*/
|
||||
public function testResetInExpression()
|
||||
{
|
||||
$smarty = new Smarty();
|
||||
$smarty->registerPlugin('modifier', 'reset', 'reset');
|
||||
$templateStr = "string:{if reset(\$ar)}ok{/if}";
|
||||
$smarty->assign('ar', [1,2,3]);
|
||||
$this->assertEquals(
|
||||
'ok',
|
||||
$smarty->fetch($templateStr)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group issue961
|
||||
*/
|
||||
public function testMatch()
|
||||
{
|
||||
$smarty = new Smarty();
|
||||
$smarty->registerPlugin('modifier', 'preg_match', 'preg_match');
|
||||
$templateStr = 'string:{assign var="match" value=null}{if preg_match(\'/([a-z]{4})/\', "a test", $match)}{$match.1}{/if}';
|
||||
$this->assertEquals(
|
||||
'test',
|
||||
$smarty->fetch($templateStr)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM php:8.3-cli
|
||||
|
||||
## Basic utilities
|
||||
RUN apt-get update -yqq && apt-get install -y curl apt-utils git zip unzip
|
||||
|
||||
## Composer
|
||||
COPY ./utilities/testrunners/shared/install-composer.sh /root/install-composer.sh
|
||||
WORKDIR /root
|
||||
RUN sh ./install-composer.sh
|
||||
RUN mv ./composer.phar /usr/local/bin/composer
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM php:8.4-rc-cli-bullseye
|
||||
|
||||
## Basic utilities
|
||||
RUN apt-get update -yqq && apt-get install -y curl apt-utils git zip unzip
|
||||
|
||||
## Composer
|
||||
COPY ./utilities/testrunners/shared/install-composer.sh /root/install-composer.sh
|
||||
WORKDIR /root
|
||||
RUN sh ./install-composer.sh
|
||||
RUN mv ./composer.phar /usr/local/bin/composer
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
// This takes the *.md files in changelog/ dir and inserts them
|
||||
// right below the '## [Unreleased]' marker in CHANGELOG.md
|
||||
|
||||
$path_to_main_changelog = 'CHANGELOG.md';
|
||||
$marker = '## [Unreleased]';
|
||||
$changelog_files_pattern = 'changelog/*.md';
|
||||
$new_version = $argv[1];
|
||||
|
||||
$file_contents = file_get_contents($path_to_main_changelog);
|
||||
|
||||
foreach (glob($changelog_files_pattern) as $filename) {
|
||||
$content_to_insert = file_get_contents($filename);
|
||||
|
||||
if (!endsWithNewline($content_to_insert)) {
|
||||
$content_to_insert .= "\n";
|
||||
}
|
||||
|
||||
$file_contents = str_replace(
|
||||
$marker,
|
||||
$marker . $content_to_insert,
|
||||
$file_contents
|
||||
);
|
||||
unlink($filename);
|
||||
}
|
||||
|
||||
|
||||
// add the version number and date
|
||||
$file_contents = str_replace(
|
||||
$marker,
|
||||
$marker . sprintf("\n\n## [%s] - %s\n", $new_version, date('Y-m-d')),
|
||||
$file_contents
|
||||
);
|
||||
|
||||
file_put_contents($path_to_main_changelog, $file_contents);
|
||||
|
||||
|
||||
function endsWithNewline($str): bool
|
||||
{
|
||||
return preg_match('/[\r\n]$/', $str) === 1;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
// This takes the Smarty class file and updates the SMARTY_VERSION constant
|
||||
|
||||
$path_to_smarty_class = 'libs/Smarty.class.php';
|
||||
$pattern = "/const SMARTY_VERSION = '[^']+'/";
|
||||
$replacement = "const SMARTY_VERSION = '" . $argv[1] . "'";
|
||||
|
||||
$file_contents = preg_replace($pattern, $replacement, file_get_contents($path_to_smarty_class));
|
||||
|
||||
file_put_contents($path_to_smarty_class, $file_contents);
|
||||
Reference in New Issue
Block a user