mirror of
https://github.com/smarty-php/smarty.git
synced 2026-08-09 15:02:15 +02:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
139797a165 | ||
|
|
6709d000cd | ||
|
|
aa6edc3c0b | ||
|
|
73da7e90f3 | ||
|
|
c0d7a36124 | ||
|
|
8cc9a75964 | ||
|
|
21d7fbb67e | ||
|
|
1fc41e385d | ||
|
|
fe325daec0 | ||
|
|
3f0f308a7b | ||
|
|
b390e50974 |
@@ -34,6 +34,7 @@ jobs:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
|
||||
compiler:
|
||||
- default
|
||||
|
||||
@@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [5.7.0] - 2025-11-19
|
||||
- PHP 8.5 support
|
||||
|
||||
|
||||
## [5.6.0] - 2025-10-03
|
||||
- Added support for shorttags in functions [#1005](https://github.com/smarty-php/smarty/issues/1005)
|
||||
|
||||
|
||||
## [5.5.2] - 2025-08-26
|
||||
|
||||
|
||||
|
||||
@@ -115,5 +115,7 @@ If you are a maintainer, you can publish the document using [mike](https://githu
|
||||
mike deploy 5.x
|
||||
```
|
||||
|
||||
Then, push the `gh-pages` branch.
|
||||
|
||||
## Attribution
|
||||
This guide is based on the **contributing.md**. [Make your own](https://contributing.md/)!
|
||||
|
||||
@@ -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 v5 can be run with PHP 7.2 to PHP 8.4.
|
||||
Smarty v5 can be run with PHP 7.2 to PHP 8.5.
|
||||
|
||||
## Installation
|
||||
Smarty versions 3.1.11 or later can be installed with [Composer](https://getcomposer.org/).
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- Support for Laravel Collections style object chaining for objects return from function calls implemented as modifiers [#1151](https://github.com/smarty-php/smarty/issues/1151)
|
||||
@@ -0,0 +1 @@
|
||||
- Update documentation to clarify that include inline is currently not implemented in Smarty v5 [#1152](https://github.com/smarty-php/smarty/issues/1152)
|
||||
@@ -47,11 +47,13 @@ available within the included template.
|
||||
|
||||
## Option Flags
|
||||
|
||||
| Name | Description |
|
||||
|---------|--------------------------------------------------------------------------------------|
|
||||
| nocache | Disables caching of this subtemplate |
|
||||
| caching | Enable caching of this subtemplate |
|
||||
| inline | If set, merge the compile-code of the subtemplate into the compiled calling template |
|
||||
| Name | Description |
|
||||
|-----------|--------------------------------------------------------------------------------------|
|
||||
| nocache | Disables caching of this subtemplate |
|
||||
| caching | Enable caching of this subtemplate |
|
||||
| inline \* | If set, merge the compile-code of the subtemplate into the compiled calling template |
|
||||
|
||||
\* The `inline` option flag is currently not implemented in Smarty v5. Using it will not trigger an error, however.
|
||||
|
||||
## Examples
|
||||
```smarty
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Getting started
|
||||
|
||||
## Requirements
|
||||
Smarty can be run with PHP 7.2 to PHP 8.4.
|
||||
Smarty can be run with PHP 7.2 to PHP 8.5.
|
||||
|
||||
## Installation
|
||||
Smarty can be installed with [Composer](https://getcomposer.org/).
|
||||
|
||||
@@ -127,8 +127,6 @@ class File extends Base
|
||||
&& (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api'))) < 1
|
||||
) {
|
||||
opcache_invalidate($_template->getCached()->filepath, true);
|
||||
} elseif (function_exists('apc_compile_file')) {
|
||||
apc_compile_file($_template->getCached()->filepath);
|
||||
}
|
||||
$cached = $_template->getCached();
|
||||
$cached->timestamp = $cached->exists = is_file($cached->filepath);
|
||||
@@ -223,10 +221,8 @@ class File extends Base
|
||||
$_filepath = (string)$_file;
|
||||
// directory ?
|
||||
if ($_file->isDir()) {
|
||||
if (!$_cache->isDot()) {
|
||||
// delete folder if empty
|
||||
@rmdir($_file->getPathname());
|
||||
}
|
||||
// delete folder if empty
|
||||
@rmdir($_file->getPathname());
|
||||
} else {
|
||||
// delete only php files
|
||||
if (substr($_filepath, -4) !== '.php') {
|
||||
@@ -279,8 +275,6 @@ class File extends Base
|
||||
&& (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1)
|
||||
) {
|
||||
opcache_invalidate($_filepath, true);
|
||||
} elseif (function_exists('apc_delete_file')) {
|
||||
apc_delete_file($_filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Smarty\Compile;
|
||||
|
||||
/**
|
||||
* This class handles compiling the attributes.
|
||||
*/
|
||||
class AttributeCompiler
|
||||
{
|
||||
/**
|
||||
* Array of names of required attributes required by tag
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $required_attributes = [];
|
||||
|
||||
/**
|
||||
* Array of names of optional attribute required by tag
|
||||
* use array('_any') if there is no restriction of attributes names
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $optional_attributes = [];
|
||||
|
||||
/**
|
||||
* Shorttag attribute order defined by its names
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $shorttag_order = [];
|
||||
|
||||
/**
|
||||
* Array of names of valid option flags
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $option_flags = [];
|
||||
|
||||
public function __construct(
|
||||
array $required_attributes = [],
|
||||
array $optional_attributes = [],
|
||||
array $shorttag_order = [],
|
||||
array $option_flags = []
|
||||
) {
|
||||
$this->required_attributes = $required_attributes;
|
||||
$this->optional_attributes = $optional_attributes;
|
||||
$this->shorttag_order = $shorttag_order;
|
||||
$this->option_flags = $option_flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks if the attributes passed are valid
|
||||
* The attributes passed for the tag to compile are checked against the list of required and
|
||||
* optional attributes. Required attributes must be present. Optional attributes are check against
|
||||
* the corresponding list. The keyword '_any' specifies that any attribute will be accepted
|
||||
* as valid
|
||||
*
|
||||
* @param object $compiler compiler object
|
||||
* @param array $attributes attributes applied to the tag
|
||||
*
|
||||
* @return array of mapped attributes for further processing
|
||||
*/
|
||||
public function getAttributes($compiler, $attributes)
|
||||
{
|
||||
$_indexed_attr = [];
|
||||
$options = array_fill_keys($this->option_flags, true);
|
||||
foreach ($attributes as $key => $mixed) {
|
||||
// shorthand ?
|
||||
if (!is_array($mixed)) {
|
||||
// options flag ?
|
||||
if (isset($options[trim($mixed, '\'"')])) {
|
||||
$_indexed_attr[trim($mixed, '\'"')] = true;
|
||||
// shorthand attribute ?
|
||||
} elseif (isset($this->shorttag_order[$key])) {
|
||||
$_indexed_attr[$this->shorttag_order[$key]] = $mixed;
|
||||
} else {
|
||||
// too many shorthands
|
||||
$compiler->trigger_template_error('too many shorthand attributes', null, true);
|
||||
}
|
||||
// named attribute
|
||||
} else {
|
||||
foreach ($mixed as $k => $v) {
|
||||
// options flag?
|
||||
if (isset($options[$k])) {
|
||||
if (is_bool($v)) {
|
||||
$_indexed_attr[$k] = $v;
|
||||
} else {
|
||||
if (is_string($v)) {
|
||||
$v = trim($v, '\'" ');
|
||||
}
|
||||
|
||||
// Mapping array for boolean option value
|
||||
static $optionMap = [1 => true, 0 => false, 'true' => true, 'false' => false];
|
||||
|
||||
if (isset($optionMap[$v])) {
|
||||
$_indexed_attr[$k] = $optionMap[$v];
|
||||
} else {
|
||||
$compiler->trigger_template_error(
|
||||
"illegal value '" . var_export($v, true) .
|
||||
"' for options flag '{$k}'",
|
||||
null,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
// must be named attribute
|
||||
} else {
|
||||
$_indexed_attr[$k] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if all required attributes present
|
||||
foreach ($this->required_attributes as $attr) {
|
||||
if (!isset($_indexed_attr[$attr])) {
|
||||
$compiler->trigger_template_error("missing '{$attr}' attribute", null, true);
|
||||
}
|
||||
}
|
||||
// check for not allowed attributes
|
||||
if ($this->optional_attributes !== ['_any']) {
|
||||
$allowedAttributes = array_fill_keys(
|
||||
array_merge(
|
||||
$this->required_attributes,
|
||||
$this->optional_attributes,
|
||||
$this->option_flags
|
||||
),
|
||||
true
|
||||
);
|
||||
foreach ($_indexed_attr as $key => $dummy) {
|
||||
if (!isset($allowedAttributes[$key]) && $key !== 0) {
|
||||
$compiler->trigger_template_error("unexpected '{$key}' attribute", null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// default 'false' for all options flags not set
|
||||
foreach ($this->option_flags as $flag) {
|
||||
if (!isset($_indexed_attr[$flag])) {
|
||||
$_indexed_attr[$flag] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $_indexed_attr;
|
||||
}
|
||||
}
|
||||
+6
-78
@@ -82,84 +82,12 @@ abstract class Base implements CompilerInterface {
|
||||
* @return array of mapped attributes for further processing
|
||||
*/
|
||||
protected function getAttributes($compiler, $attributes) {
|
||||
$_indexed_attr = [];
|
||||
$options = array_fill_keys($this->option_flags, true);
|
||||
foreach ($attributes as $key => $mixed) {
|
||||
// shorthand ?
|
||||
if (!is_array($mixed)) {
|
||||
// options flag ?
|
||||
if (isset($options[trim($mixed, '\'"')])) {
|
||||
$_indexed_attr[trim($mixed, '\'"')] = true;
|
||||
// shorthand attribute ?
|
||||
} elseif (isset($this->shorttag_order[$key])) {
|
||||
$_indexed_attr[$this->shorttag_order[$key]] = $mixed;
|
||||
} else {
|
||||
// too many shorthands
|
||||
$compiler->trigger_template_error('too many shorthand attributes', null, true);
|
||||
}
|
||||
// named attribute
|
||||
} else {
|
||||
foreach ($mixed as $k => $v) {
|
||||
// options flag?
|
||||
if (isset($options[$k])) {
|
||||
if (is_bool($v)) {
|
||||
$_indexed_attr[$k] = $v;
|
||||
} else {
|
||||
if (is_string($v)) {
|
||||
$v = trim($v, '\'" ');
|
||||
}
|
||||
|
||||
// Mapping array for boolean option value
|
||||
static $optionMap = [1 => true, 0 => false, 'true' => true, 'false' => false];
|
||||
|
||||
if (isset($optionMap[$v])) {
|
||||
$_indexed_attr[$k] = $optionMap[$v];
|
||||
} else {
|
||||
$compiler->trigger_template_error(
|
||||
"illegal value '" . var_export($v, true) .
|
||||
"' for options flag '{$k}'",
|
||||
null,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
// must be named attribute
|
||||
} else {
|
||||
$_indexed_attr[$k] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if all required attributes present
|
||||
foreach ($this->required_attributes as $attr) {
|
||||
if (!isset($_indexed_attr[$attr])) {
|
||||
$compiler->trigger_template_error("missing '{$attr}' attribute", null, true);
|
||||
}
|
||||
}
|
||||
// check for not allowed attributes
|
||||
if ($this->optional_attributes !== ['_any']) {
|
||||
$allowedAttributes = array_fill_keys(
|
||||
array_merge(
|
||||
$this->required_attributes,
|
||||
$this->optional_attributes,
|
||||
$this->option_flags
|
||||
),
|
||||
true
|
||||
);
|
||||
foreach ($_indexed_attr as $key => $dummy) {
|
||||
if (!isset($allowedAttributes[$key]) && $key !== 0) {
|
||||
$compiler->trigger_template_error("unexpected '{$key}' attribute", null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// default 'false' for all options flags not set
|
||||
foreach ($this->option_flags as $flag) {
|
||||
if (!isset($_indexed_attr[$flag])) {
|
||||
$_indexed_attr[$flag] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $_indexed_attr;
|
||||
return (new AttributeCompiler(
|
||||
$this->required_attributes,
|
||||
$this->optional_attributes,
|
||||
$this->shorttag_order,
|
||||
$this->option_flags
|
||||
))->getAttributes($compiler, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,24 +3,18 @@
|
||||
* Smarty Internal Plugin Compile Registered Function
|
||||
* Compiles code for the execution of a registered function
|
||||
*
|
||||
|
||||
|
||||
* @author Uwe Tews
|
||||
*/
|
||||
|
||||
namespace Smarty\Compile;
|
||||
|
||||
use Smarty\Compiler\Template;
|
||||
use Smarty\CompilerException;
|
||||
use Smarty\FunctionHandler\AttributeFunctionHandlerInterface;
|
||||
|
||||
/**
|
||||
* Smarty Internal Plugin Compile Registered Function Class
|
||||
*
|
||||
|
||||
|
||||
*/
|
||||
class FunctionCallCompiler extends Base {
|
||||
|
||||
/**
|
||||
* Attribute definition: Overwrites base class.
|
||||
*
|
||||
@@ -51,17 +45,27 @@ class FunctionCallCompiler extends Base {
|
||||
*/
|
||||
public function compile($args, Template $compiler, $parameter = [], $tag = null, $function = null): string
|
||||
{
|
||||
|
||||
// check and get attributes
|
||||
$_attr = $this->getAttributes($compiler, $args);
|
||||
unset($_attr['nocache']);
|
||||
|
||||
$_paramsArray = $this->formatParamsArray($_attr);
|
||||
$_params = 'array(' . implode(',', $_paramsArray) . ')';
|
||||
|
||||
|
||||
if ($functionHandler = $compiler->getSmarty()->getFunctionHandler($function)) {
|
||||
|
||||
$attribute_overrides = [];
|
||||
|
||||
if ($functionHandler instanceof AttributeFunctionHandlerInterface) {
|
||||
$attribute_overrides = $functionHandler->getSupportedAttributes();
|
||||
}
|
||||
|
||||
// check and get attributes
|
||||
$_attr = (new AttributeCompiler(
|
||||
$attribute_overrides['required_attributes'] ?? $this->required_attributes,
|
||||
$attribute_overrides['optional_attributes'] ?? $this->optional_attributes,
|
||||
$attribute_overrides['shorttag_order'] ?? $this->shorttag_order,
|
||||
$attribute_overrides['option_flags'] ?? $this->option_flags
|
||||
))->getAttributes($compiler, $args);
|
||||
|
||||
unset($_attr['nocache']);
|
||||
|
||||
$_paramsArray = $this->formatParamsArray($_attr);
|
||||
$_params = 'array(' . implode(',', $_paramsArray) . ')';
|
||||
|
||||
// not cacheable?
|
||||
$compiler->tag_nocache = $compiler->tag_nocache || !$functionHandler->isCacheable();
|
||||
$output = "\$_smarty_tpl->getSmarty()->getFunctionHandler(" . var_export($function, true) . ")";
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Smarty\FunctionHandler;
|
||||
|
||||
use Smarty\Template;
|
||||
|
||||
/**
|
||||
* Abstract implementation for function handlers which support custom attributes
|
||||
*/
|
||||
abstract class AttributeBase implements AttributeFunctionHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Array of names of required attribute required by tag
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $required_attributes = [];
|
||||
|
||||
/**
|
||||
* Array of names of optional attribute required by tag
|
||||
* use array('_any') if there is no restriction of attributes names
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $optional_attributes = [];
|
||||
|
||||
/**
|
||||
* Shorttag attribute order defined by its names
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $shorttag_order = [];
|
||||
|
||||
/**
|
||||
* Array of names of valid option flags
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $option_flags = [];
|
||||
|
||||
/**
|
||||
* Return whether the output is cacheable.
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $cacheable = true;
|
||||
|
||||
/**
|
||||
* Return whether the output is cacheable.
|
||||
* @return bool
|
||||
*/
|
||||
public function isCacheable(): bool
|
||||
{
|
||||
return $this->cacheable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function body
|
||||
* @param mixed $params The supplied parameters.
|
||||
* @param Smarty\Template $template
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function handle($params, Template $template): ?string;
|
||||
|
||||
/**
|
||||
* Return the support attributes for this function.
|
||||
* @return array<string, array>
|
||||
*/
|
||||
public function getSupportedAttributes(): array
|
||||
{
|
||||
return [
|
||||
'required_attributes' => $this->required_attributes,
|
||||
'optional_attributes' => $this->optional_attributes,
|
||||
'shorttag_order' => $this->shorttag_order,
|
||||
'option_flags' => $this->option_flags,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Smarty\FunctionHandler;
|
||||
|
||||
/**
|
||||
* Function handler interface with support for specifying supported properties
|
||||
*/
|
||||
interface AttributeFunctionHandlerInterface extends FunctionHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Returns an array with the supported attributes, flags, and shorttags
|
||||
* @return array<string, array>
|
||||
*/
|
||||
public function getSupportedAttributes(): array;
|
||||
}
|
||||
+742
-731
File diff suppressed because it is too large
Load Diff
@@ -1062,12 +1062,22 @@ object(res) ::= varindexed(vi) objectchain(oc). {
|
||||
}
|
||||
}
|
||||
|
||||
// optional objectchain - empty
|
||||
optobjectchain(res) ::= . {
|
||||
res = '';
|
||||
}
|
||||
|
||||
// optional objectchain - present
|
||||
optobjectchain(res) ::= objectchain(oc). {
|
||||
res = oc;
|
||||
}
|
||||
|
||||
// single element
|
||||
objectchain(res) ::= objectelement(oe). {
|
||||
res = oe;
|
||||
}
|
||||
|
||||
// chain of elements
|
||||
// chain of elements
|
||||
objectchain(res) ::= objectchain(oc) objectelement(oe). {
|
||||
res = oc.oe;
|
||||
}
|
||||
@@ -1111,7 +1121,7 @@ objectelement(res)::= PTR method(f). {
|
||||
//
|
||||
// function
|
||||
//
|
||||
function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP. {
|
||||
function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP optobjectchain(oc). {
|
||||
|
||||
if (f == 'isset') {
|
||||
res = '(true';
|
||||
@@ -1125,15 +1135,15 @@ function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP. {
|
||||
res .= ' && (' . $value . ' !== null)';
|
||||
}
|
||||
}
|
||||
res .= ')';
|
||||
res .= ')' . oc;
|
||||
} elseif (f == 'empty') {
|
||||
if (count(v) != 1) {
|
||||
throw new CompilerException("Invalid number of arguments for empty. empty expects at exactly one parameter.");
|
||||
}
|
||||
if (is_array(v[0])) {
|
||||
res .= '( !' . v[0][0] . ' || empty(' . v[0][1] . '))';
|
||||
res = '( !' . v[0][0] . ' || empty(' . v[0][1] . '))' . oc;
|
||||
} else {
|
||||
res = 'false == ' . v[0];
|
||||
res = 'false == ' . v[0] . oc;
|
||||
}
|
||||
} else {
|
||||
$p = array();
|
||||
@@ -1144,7 +1154,7 @@ function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP. {
|
||||
$p[] = $value;
|
||||
}
|
||||
}
|
||||
res = $this->compiler->compileModifierInExpression(f, $p);
|
||||
res = $this->compiler->compileModifierInExpression(f, $p) . oc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-7
@@ -54,7 +54,7 @@ class Smarty extends \Smarty\TemplateBase {
|
||||
/**
|
||||
* smarty version
|
||||
*/
|
||||
const SMARTY_VERSION = '5.5.2';
|
||||
const SMARTY_VERSION = '5.7.0';
|
||||
|
||||
/**
|
||||
* define caching modes
|
||||
@@ -1344,10 +1344,8 @@ class Smarty extends \Smarty\TemplateBase {
|
||||
}
|
||||
$_filepath = (string)$_file;
|
||||
if ($_file->isDir()) {
|
||||
if (!$_compile->isDot()) {
|
||||
// delete folder if empty
|
||||
@rmdir($_file->getPathname());
|
||||
}
|
||||
// delete folder if empty
|
||||
@rmdir($_file->getPathname());
|
||||
} else {
|
||||
// delete only php files
|
||||
if (substr($_filepath, -4) !== '.php') {
|
||||
@@ -1385,8 +1383,6 @@ class Smarty extends \Smarty\TemplateBase {
|
||||
&& (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api')) < 1)
|
||||
) {
|
||||
opcache_invalidate($_filepath, true);
|
||||
} elseif (function_exists('apc_delete_file')) {
|
||||
apc_delete_file($_filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,8 +251,6 @@ class Compiled extends GeneratedPhpFile {
|
||||
&& (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1)
|
||||
) {
|
||||
opcache_invalidate($this->filepath, true);
|
||||
} elseif (function_exists('apc_compile_file')) {
|
||||
apc_compile_file($this->filepath);
|
||||
}
|
||||
}
|
||||
if (defined('HHVM_VERSION')) {
|
||||
|
||||
@@ -104,7 +104,7 @@ abstract class TemplateBase extends Data {
|
||||
}
|
||||
// register the object
|
||||
$smarty->registered_objects[$object_name] =
|
||||
[$object, (array)$allowed_methods_properties, (boolean)$format, (array)$block_methods];
|
||||
[$object, (array)$allowed_methods_properties, (bool)$format, (array)$block_methods];
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
@@ -291,10 +291,8 @@ KEY `name` (`name`)
|
||||
}
|
||||
// directory ?
|
||||
if ($file->isDir()) {
|
||||
if (!$ri->isDot()) {
|
||||
// delete folder if empty
|
||||
@rmdir($file->getPathname());
|
||||
}
|
||||
// delete folder if empty
|
||||
@rmdir($file->getPathname());
|
||||
} else {
|
||||
unlink($file->getPathname());
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Smarty PHPunit tests for cache resource Apc
|
||||
*
|
||||
|
||||
* @author Uwe Tews
|
||||
*/
|
||||
include_once __DIR__ . '/../Memcache/CacheResourceCustomMemcacheTest.php';
|
||||
include_once __DIR__ . '/../_shared/PHPunitplugins/cacheresource.apctest.php';
|
||||
|
||||
/**
|
||||
* class for cache resource file tests
|
||||
*
|
||||
*
|
||||
* @preserveGlobalState disabled
|
||||
*
|
||||
*/
|
||||
class CacheResourceCustomApcTest extends CacheResourceCustomMemcacheTest
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
if (!function_exists('apc_cache_info') || ini_get('apc.enable_cli')) {
|
||||
$this->markTestSkipped('APC cache not available');
|
||||
}
|
||||
$this->setUpSmarty(__DIR__);
|
||||
parent::setUp();
|
||||
$this->smarty->setCachingType('apc');
|
||||
$this->smarty->registerCacheResource('apc', new Smarty_CacheResource_Apctest());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Smarty\Smarty;
|
||||
use Smarty\Template;
|
||||
use Smarty\Template\Cached;
|
||||
|
||||
require_once __DIR__ . '/../../../__shared/cacheresources/cacheresource.apc.php';
|
||||
|
||||
class Smarty_CacheResource_Apctest extends Smarty_CacheResource_Apc
|
||||
{
|
||||
public $lockTime = 0;
|
||||
|
||||
public function hasLock(Smarty $smarty, Cached $cached)
|
||||
{
|
||||
if ($this->lockTime) {
|
||||
$this->lockTime--;
|
||||
if (!$this->lockTime) {
|
||||
$this->releaseLock($smarty, $cached);
|
||||
}
|
||||
}
|
||||
return parent::hasLock($smarty, $cached);
|
||||
}
|
||||
|
||||
public function get(Template $_template)
|
||||
{
|
||||
$this->contents = array();
|
||||
$this->timestamps = array();
|
||||
$t = $this->getContent($_template);
|
||||
|
||||
return $t ? $t : null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
use Smarty\Compile\AttributeCompiler;
|
||||
use Smarty\Compiler\Template;
|
||||
|
||||
class AttributeCompilerTest extends PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* The template compiler.
|
||||
*/
|
||||
private $template_compiler;
|
||||
|
||||
/**
|
||||
* The attributes
|
||||
*/
|
||||
private $attributes = [];
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* Set up attribute compiler class
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->template_compiler = $this->createMock(Template::class);
|
||||
|
||||
// reset attributes to empty arrays
|
||||
$this->attributes = [
|
||||
'required_attributes' => [],
|
||||
'optional_attributes' => [],
|
||||
'shorttag_order' => [],
|
||||
'option_flags' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the attribute compiler for testing.
|
||||
*/
|
||||
private function createAttributeCompiler() {
|
||||
return new AttributeCompiler(
|
||||
$this->attributes['required_attributes'],
|
||||
$this->attributes['optional_attributes'],
|
||||
$this->attributes['shorttag_order'],
|
||||
$this->attributes['option_flags']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests shorthand attribute compiling.
|
||||
*/
|
||||
public function testAttributeCompiler(): void
|
||||
{
|
||||
$this->attributes['shorttag_order'] = ['shorttag'];
|
||||
$this->attributes['required_attributes'] = ['required'];
|
||||
$this->attributes['option_flags'] = ['option', 'option_two'];
|
||||
|
||||
$payload = [
|
||||
0 => 'shorttag value',
|
||||
1 => [
|
||||
'required' => 'required_value'
|
||||
],
|
||||
2 => 'option',
|
||||
];
|
||||
|
||||
$this->assertEquals(
|
||||
$this->createAttributeCompiler()
|
||||
->getAttributes($this->template_compiler, $payload),
|
||||
[
|
||||
'shorttag' => 'shorttag value',
|
||||
'required' => 'required_value',
|
||||
'option' => true,
|
||||
'option_two' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests normal optional attribute compiling.
|
||||
*/
|
||||
public function testAttributeCompilerOptionalArguments(): void
|
||||
{
|
||||
$this->attributes['optional_attributes'] = ['optional'];
|
||||
|
||||
$payload = [
|
||||
0 => [
|
||||
'optional' => 'optional value'
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals(
|
||||
$this->createAttributeCompiler()
|
||||
->getAttributes($this->template_compiler, $payload),
|
||||
[
|
||||
'optional' => 'optional value',
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
$this->createAttributeCompiler()
|
||||
->getAttributes($this->template_compiler, []),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests any attribute compiling.
|
||||
*/
|
||||
public function testAttributeCompilerAnyOptionalArguments(): void
|
||||
{
|
||||
$this->attributes['optional_attributes'] = ['_any'];
|
||||
|
||||
$payload = [
|
||||
0 => [
|
||||
'optional' => 'optional value'
|
||||
],
|
||||
1 => [
|
||||
'optional_two' => 'optional value two'
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertEquals(
|
||||
$this->createAttributeCompiler()
|
||||
->getAttributes($this->template_compiler, $payload),
|
||||
[
|
||||
'optional' => 'optional value',
|
||||
'optional_two' => 'optional value two',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the attribute compiler tries to throw a too many shorthand attributes error.
|
||||
*/
|
||||
public function testAttributeCompilerTooManyShorthands(): void
|
||||
{
|
||||
$payload = [
|
||||
0 => 'option one',
|
||||
];
|
||||
|
||||
$this->template_compiler
|
||||
->expects(self::once())
|
||||
->method('trigger_template_error')
|
||||
->with('too many shorthand attributes', null, true);
|
||||
|
||||
$this->createAttributeCompiler()
|
||||
->getAttributes($this->template_compiler, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the attribute compiler tries to throw a missing required attribute error.
|
||||
*/
|
||||
public function testAttributeCompilerWithMissingRequiredAttributes(): void
|
||||
{
|
||||
$this->attributes['required_attributes'] = ['required'];
|
||||
|
||||
$this->template_compiler
|
||||
->expects(self::once())
|
||||
->method('trigger_template_error')
|
||||
->with('missing \'required\' attribute', null, true);
|
||||
|
||||
$this->createAttributeCompiler()
|
||||
->getAttributes($this->template_compiler, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the attribute compiler tries to throw a illegal value template error.
|
||||
*/
|
||||
public function testAttributeCompilerWithInvalidOptionAttribute(): void
|
||||
{
|
||||
$this->attributes['option_flags'] = ['option'];
|
||||
|
||||
$this->template_compiler
|
||||
->expects(self::once())
|
||||
->method('trigger_template_error')
|
||||
->with('illegal value \'\'foo\'\' for options flag \'option\'', null, true);
|
||||
|
||||
$this->createAttributeCompiler()
|
||||
->getAttributes($this->template_compiler, [0 => ['option' => 'foo']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the attribute compiler tries to throw an unexpected attribute error.
|
||||
*/
|
||||
public function testAttributeCompilerWithInvalidUnexpectedAttribute(): void
|
||||
{
|
||||
$this->template_compiler
|
||||
->expects(self::once())
|
||||
->method('trigger_template_error')
|
||||
->with('unexpected \'unexpected\' attribute', null, true);
|
||||
|
||||
$this->createAttributeCompiler()
|
||||
->getAttributes($this->template_compiler, [0 => ['unexpected' => 'bar']]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
use Smarty\Compile\FunctionCallCompiler;
|
||||
use Smarty\Compiler\Template;
|
||||
use Smarty\FunctionHandler\AttributeFunctionHandlerInterface;
|
||||
use Smarty\Smarty;
|
||||
|
||||
class FunctionCallCompilerTest extends PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
* Set up attribute compiler class
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->smarty = $this->createMock(Smarty::class);
|
||||
$this->template_compiler = $this->createMock(Template::class);
|
||||
$this->template_compiler
|
||||
->expects(self::once())
|
||||
->method('getSmarty')
|
||||
->willReturn($this->smarty);
|
||||
}
|
||||
|
||||
public function testAttributeFunctionHandlerInterface(): void
|
||||
{
|
||||
$attribute_function_handler = $this->createMock(AttributeFunctionHandlerInterface::class);
|
||||
|
||||
$attribute_function_handler
|
||||
->expects(self::once())
|
||||
->method('getSupportedAttributes')
|
||||
->willReturn([
|
||||
'required_attributes' => ['required'],
|
||||
'optional_attributes' => ['optional'],
|
||||
'shorttag_order' => ['short'],
|
||||
'option_flags' => ['option'],
|
||||
]);
|
||||
|
||||
$args = [
|
||||
0 => 'short',
|
||||
1 => 'option',
|
||||
2 => [
|
||||
'optional' => 'optional',
|
||||
],
|
||||
3 => [
|
||||
'required' => 'required',
|
||||
],
|
||||
];
|
||||
|
||||
$this->smarty
|
||||
->expects(self::once())
|
||||
->method('getFunctionHandler')
|
||||
->with('method')
|
||||
->willReturn($attribute_function_handler);
|
||||
|
||||
$function_call_compiler = new FunctionCallCompiler();
|
||||
|
||||
$this->assertEquals(
|
||||
$function_call_compiler->compile($args, $this->template_compiler, [], null, 'method'),
|
||||
'$_smarty_tpl->getSmarty()->getFunctionHandler(\'method\')->handle(array(\'short\'=>short,\'option\'=>1,\'optional\'=>optional,\'required\'=>required), $_smarty_tpl)'
|
||||
);
|
||||
}
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/**
|
||||
* Smarty PHPunit tests for object chain functionality after function calls
|
||||
* Tests the new feature: {$x = collect($data)->filter()->values()->toJson()}
|
||||
*
|
||||
* @author Smarty Vibe
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper class to simulate a chainable collection object
|
||||
*/
|
||||
class ChainableCollection
|
||||
{
|
||||
private $data;
|
||||
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function filter()
|
||||
{
|
||||
$this->data = array_filter($this->data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function values()
|
||||
{
|
||||
$this->data = array_values($this->data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toJson()
|
||||
{
|
||||
return json_encode($this->data);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return count($this->data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier plugin that returns a chainable object (simulates collect())
|
||||
*/
|
||||
function smarty_modifier_collect($data)
|
||||
{
|
||||
return new ChainableCollection($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier plugin that returns an object with methods
|
||||
*/
|
||||
function smarty_modifier_create_object($value = null)
|
||||
{
|
||||
return new class {
|
||||
public function getName() {
|
||||
return 'TestObject';
|
||||
}
|
||||
|
||||
public function getNext() {
|
||||
return new class {
|
||||
public function getValue() {
|
||||
return 'ChainedValue';
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* class for function object chain tests
|
||||
*
|
||||
* @preserveGlobalState disabled
|
||||
*
|
||||
*/
|
||||
class FunctionObjectChainTest extends PHPUnit_Smarty
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->setUpSmarty(__DIR__);
|
||||
|
||||
// Register modifier plugins that return chainable objects
|
||||
// These are called like functions: {collect($data)}
|
||||
$this->smarty->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'collect', 'smarty_modifier_collect');
|
||||
$this->smarty->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'create_object', 'smarty_modifier_create_object');
|
||||
}
|
||||
|
||||
public function testInit()
|
||||
{
|
||||
$this->cleanDirs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the NEW feature: function call followed by method chain
|
||||
* This is the core test for: {$x = collect($data)->filter()->values()->toJson()}
|
||||
*/
|
||||
public function testFunctionCallWithMethodChain()
|
||||
{
|
||||
$data = [1, 2, 0, 3, null, 4, '', 5];
|
||||
$this->smarty->assign('data', $data);
|
||||
|
||||
// Test the new syntax: function()->method()->method()->method()
|
||||
$result = $this->smarty->fetch('string:{collect($data)->filter()->values()->toJson()}');
|
||||
$this->assertEquals('[1,2,3,4,5]', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test function call with method chain assigned to a variable
|
||||
* This tests: {$x = collect($data)->filter()->values()->toJson()}
|
||||
*/
|
||||
public function testFunctionCallWithMethodChainAssignment()
|
||||
{
|
||||
$data = ['a', 'b', '', 'c', null, 'd'];
|
||||
$this->smarty->assign('data', $data);
|
||||
|
||||
// Test assignment with chained methods
|
||||
$result = $this->smarty->fetch('string:{$result = collect($data)->filter()->values()->toJson()}{$result}');
|
||||
$this->assertEquals('["a","b","c","d"]', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test function call with single method chain
|
||||
*/
|
||||
public function testFunctionCallWithSingleMethod()
|
||||
{
|
||||
$data = [1, 2, 3];
|
||||
$this->smarty->assign('data', $data);
|
||||
|
||||
$result = $this->smarty->fetch('string:{collect($data)->count()}');
|
||||
$this->assertEquals('3', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test function call with nested method chains
|
||||
*/
|
||||
public function testFunctionCallWithNestedChain()
|
||||
{
|
||||
$result = $this->smarty->fetch('string:{create_object("")->getNext()->getValue()}');
|
||||
$this->assertEquals('ChainedValue', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that old syntax still works (two-step process)
|
||||
*/
|
||||
public function testOldSyntaxStillWorks()
|
||||
{
|
||||
$data = [1, 2, 0, 3];
|
||||
$this->smarty->assign('data', $data);
|
||||
|
||||
// Old syntax: assign to variable first, then chain
|
||||
$result = $this->smarty->fetch('string:{$x = collect($data)}{$x->filter()->values()->toJson()}');
|
||||
$this->assertEquals('[1,2,3]', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test object chain functionality using template file
|
||||
*/
|
||||
public function testFunctionObjectChainFromTemplateFile()
|
||||
{
|
||||
$data = ['foo', '', 'bar', null, 'baz'];
|
||||
$this->smarty->assign('data', $data);
|
||||
|
||||
$result = $this->smarty->fetch('test_function_chain.tpl');
|
||||
// Expected: JSON of filtered array and count
|
||||
$this->assertStringContainsString('["foo","bar","baz"]', $result);
|
||||
$this->assertStringContainsString('3', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test complex chaining with multiple operations
|
||||
* This demonstrates the power of the new feature
|
||||
*/
|
||||
public function testComplexChaining()
|
||||
{
|
||||
$data = [1, 2, 0, 3, '', 4, null, 5, false, 6];
|
||||
$this->smarty->assign('data', $data);
|
||||
|
||||
// Complex chain: collect -> filter -> values -> toArray
|
||||
$result = $this->smarty->fetch('string:{$filtered = collect($data)->filter()->values()->toArray()}{$filtered|@json_encode}');
|
||||
$this->assertEquals('[1,2,3,4,5,6]', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that demonstrates the benefit: one line vs multiple lines
|
||||
*/
|
||||
public function testNewSyntaxVsOldSyntax()
|
||||
{
|
||||
$data = [10, 20, 0, 30];
|
||||
$this->smarty->assign('data', $data);
|
||||
|
||||
// NEW syntax (single line)
|
||||
$new = $this->smarty->fetch('string:{collect($data)->filter()->count()}');
|
||||
|
||||
// OLD syntax (multiple steps)
|
||||
$old = $this->smarty->fetch('string:{$temp = collect($data)}{$temp = $temp->filter()}{$temp->count()}');
|
||||
|
||||
// Both should produce the same result
|
||||
$this->assertEquals('3', $new);
|
||||
$this->assertEquals($new, $old);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{* Test template for NEW function object chain feature *}
|
||||
{* Test: function()->method()->method()->method() *}
|
||||
{collect($data)->filter()->values()->toJson()}
|
||||
{collect($data)->filter()->count()}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* APC CacheResource
|
||||
* CacheResource Implementation based on the KeyValueStore API to use
|
||||
* memcache as the storage resource for Smarty's output caching.
|
||||
* *
|
||||
*
|
||||
|
||||
* @author Uwe Tews
|
||||
*/
|
||||
class Smarty_CacheResource_Apc extends \Smarty\Cacheresource\KeyValueStore
|
||||
{
|
||||
|
||||
/**
|
||||
* Read values for a set of keys from cache
|
||||
*
|
||||
* @param array $keys list of keys to fetch
|
||||
*
|
||||
* @return array list of values with the given keys used as indexes
|
||||
* @return boolean true on success, false on failure
|
||||
*/
|
||||
protected function read(array $keys)
|
||||
{
|
||||
$_res = array();
|
||||
$res = apc_fetch($keys);
|
||||
foreach ($res as $k => $v) {
|
||||
$_res[ $k ] = $v;
|
||||
}
|
||||
return $_res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save values for a set of keys to cache
|
||||
*
|
||||
* @param array $keys list of values to save
|
||||
* @param int $expire expiration time
|
||||
*
|
||||
* @return boolean true on success, false on failure
|
||||
*/
|
||||
protected function write(array $keys, $expire = null)
|
||||
{
|
||||
foreach ($keys as $k => $v) {
|
||||
apc_store($k, $v, $expire);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove values from cache
|
||||
*
|
||||
* @param array $keys list of keys to delete
|
||||
*
|
||||
* @return boolean true on success, false on failure
|
||||
*/
|
||||
protected function delete(array $keys)
|
||||
{
|
||||
foreach ($keys as $k) {
|
||||
apc_delete($k);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove *all* values from cache
|
||||
*
|
||||
* @return boolean true on success, false on failure
|
||||
*/
|
||||
protected function purge()
|
||||
{
|
||||
return apc_clear_cache('user');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM php:8.5-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
|
||||
Reference in New Issue
Block a user