Compare commits

...

48 Commits

Author SHA1 Message Date
Simon Wisselink 78d259d3b9 Merge branch 'release/5.8.0' 2026-02-15 15:27:15 +01:00
Simon Wisselink 4ed569c674 version bump 2026-02-15 15:27:13 +01:00
Simon Wisselink a46478d0cb changelogs 2026-02-15 15:26:43 +01:00
Simon Wisselink 5487e31c4b Add support for Backed Enums (#1171)
* Add support for Backed Enums
Fixes #1012

Also added docs (and docs for matches operator)
2026-02-15 15:23:55 +01:00
Simon Wisselink 12ce28e265 Regex matches operator (#1169)
* Regex matches operator support
2026-02-15 14:44:48 +01:00
Simon Wisselink 139797a165 Support for Laravel Collections style object chaining (#1168)
* Support for Laravel Collections style object chaining for objects return from function calls implemented as modifiers
Fixes #1151

* explain publishing docs
2026-02-11 00:02:52 +01:00
hirosan 6709d000cd Fix static analysis warnings for isDot() and remove deprecated APC support (#1164)
* Fix static analysis warnings for isDot()
* Remove deprecated APC support
* Remove redundant isDot() check and fix static analysis warnings
2026-01-08 11:21:57 +01:00
Simon Wisselink aa6edc3c0b Document missing inline implementation. Fixed #1152 (#1156) 2025-12-21 22:58:11 +01:00
Simon Wisselink 73da7e90f3 Merge branch 'release/5.7.0' 2025-11-19 22:36:38 +01:00
Simon Wisselink c0d7a36124 version bump 2025-11-19 22:36:36 +01:00
Simon Wisselink 8cc9a75964 Php8.5 support (#1138)
* PHP 8.5 support (using RC docker image for php 8.5 unit tests)
2025-11-19 22:33:49 +01:00
Wim Wisselink 21d7fbb67e Non-canonical cast (boolean) fix (#1145) 2025-10-17 14:45:38 +02:00
Simon Wisselink 1fc41e385d Merge branch 'release/5.6.0' 2025-10-03 23:22:25 +02:00
Simon Wisselink fe325daec0 version bump 2025-10-03 23:22:23 +02:00
Simon Wisselink 3f0f308a7b added changelog 2025-10-03 23:19:22 +02:00
pharixces b390e50974 Add support for shorttags in functions (#1142)
* Add support for shorttags in functions

Co-authored-by: Anne Zijlstra <a.zijlstra@iwink.nl>
Co-authored-by: Simon Wisselink <s.wisselink@iwink.nl>
2025-10-03 23:17:55 +02:00
Simon Wisselink 0e46ae3add Merge branch 'release/5.5.2' 2025-08-26 10:38:06 +02:00
Simon Wisselink 4d793ee04c version bump 2025-08-26 10:38:05 +02:00
Simon Wisselink 7677b84058 Fixed escaping of array/object keys in debug_print_var 2025-08-26 10:32:02 +02:00
Simon Wisselink e7457d78cb Add iWink and Temma logos 2025-05-27 22:39:34 +02:00
Simon Wisselink cbcd66e1de Merge branch 'release/5.5.1' 2025-05-19 11:29:42 +02:00
Simon Wisselink 46cd224efb version bump 2025-05-19 11:29:40 +02:00
Simon Wisselink d3b716a0b9 changelog 2025-05-19 11:29:30 +02:00
pharixces 693502c341 Bugfix/bcpluginsadapter support modifiercompiler (#1132)
* Add support for modifiercompiler in the BCPluginsAdapter class.

---------

Co-authored-by: Anne Zijlstra <a.zijlstra@iwink.nl>
2025-05-19 11:28:10 +02:00
Shad bb968469fa add empty and in_array modifier doc (#1113) 2025-05-06 14:23:46 +02:00
Simon Wisselink 372f7cf41a Merge branch 'release/5.5.0' 2025-05-03 23:36:46 +02:00
Simon Wisselink f659193c94 version bump 2025-05-03 23:36:44 +02:00
Simon Wisselink 0b6166482a changelog 2025-05-03 23:36:27 +02:00
hirosan 4ff25bbe59 Support trailing comma in array (#1128) 2025-05-03 23:34:15 +02:00
hirosan afbd71b868 Update getting-started.md (#1111)
Indicate support for php8.4 in docs.
2025-04-16 08:11:00 +02:00
Simon Wisselink e26f82d02c Merge branch 'release/5.4.5' 2025-04-15 13:20:08 +02:00
Simon Wisselink 6ad6a35824 version bump 2025-04-15 13:20:06 +02:00
Simon Wisselink baa53399f5 changelog 2025-04-15 13:19:54 +02:00
gherosh 37bea77ea7 Update StringEval.php (#1126)
Fix: Implicitly marking parameter $_template as nullable is deprecated, the explicit nullable type must be used instead
2025-04-15 13:19:00 +02:00
Simon Wisselink 0d33f3acde Added sponsor 2025-04-13 22:34:16 +02:00
Simon Wisselink 05adbf45f3 Drop jit runs from CI unit tests.
Fixes #1125
2025-04-13 22:20:14 +02:00
Simon Wisselink df4795b803 Merge branch 'release/5.4.4' 2025-04-13 22:12:10 +02:00
Simon Wisselink 5401418258 version bump 2025-04-13 22:12:08 +02:00
Simon Wisselink a5bbba3f05 Fix syntax error occurring when registering a function plugin that ends with the string 'close' (#1124)
Fixes #1122
2025-04-10 23:47:44 +02:00
Simon Wisselink a0664a4ab2 Added funding options 2025-04-10 22:39:35 +02:00
Simon Wisselink a4b8466205 Added unit tests to prevent regressions of issue #1100 that was fixed in v4 2025-02-13 23:20:05 +01:00
Simon Wisselink 5d1ea5806a change SMARTY_VERSION to $smarty.version.
Fixes #1073
2024-12-28 17:40:25 +01:00
Simon Wisselink ab247c723b Add upgrading remarks on magic APi methods.
Fixes #979
2024-12-27 23:37:21 +01:00
Simon Wisselink fb997111f4 Add note to docs about plugins that expect parameters by reference 2024-12-27 23:08:17 +01:00
Simon Wisselink c6bff57950 Merge branch 'release/5.4.3' 2024-12-23 01:38:44 +01:00
Simon Wisselink 07faafe4da version bump 2024-12-23 01:38:41 +01:00
Simon Wisselink 295b7fb582 move to official php8.4 image for unit testing in CI 2024-12-23 01:34:55 +01:00
Simon Wisselink cf9de567c1 Restore special handling of isset and empty as it was in v4. Fixes #1063 (#1093)
* Restore special handling of isset and empty as it was in v4. Fixes #1063
2024-12-23 01:29:07 +01:00
56 changed files with 3003 additions and 1555 deletions
+1
View File
@@ -0,0 +1 @@
github: [wisskid]
+1 -21
View File
@@ -34,35 +34,15 @@ jobs:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
compiler:
- default
include:
- os: ubuntu-latest
php-version: "8.0"
compiler: jit
- os: ubuntu-latest
php-version: "8.1"
compiler: jit
- 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@v4
- name: Override PHP ini values for JIT compiler
if: matrix.compiler == 'jit'
run: echo "PHP_INI_VALUES::assert.exception=1, zend.assertions=1, opcache.enable=1, opcache.enable_cli=1, opcache.optimization_level=-1, opcache.jit=1255, opcache.jit_buffer_size=32M" >> $GITHUB_ENV
- name: Install PHP with extensions
uses: shivammathur/setup-php@v2
with:
+41
View File
@@ -6,6 +6,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [5.8.0] - 2026-02-15
- Added support for Backed Enums for php versions >= 8.1 [#1171](https://github.com/smarty-php/smarty/pull/1171)
- Added support for new 'matches' operator doing regex matching [#1169](https://github.com/smarty-php/smarty/pull/1169)
- Update documentation to clarify that include inline is currently not implemented in Smarty v5 [#1152](https://github.com/smarty-php/smarty/issues/1152)
- 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)
## [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
- Fixed escaping of array/object keys in debug_print_var
## [5.5.1] - 2025-05-19
- Fix missing support for loading modifiercompilers from plugin dir in BCPluginsAdapter [#1132](https://github.com/smarty-php/smarty/pull/1132)
## [5.5.0] - 2025-05-03
- Support trailing comma in array [#1013](https://github.com/smarty-php/smarty/issues/1013)
## [5.4.5] - 2025-04-15
- Fixed deprecation notice in StringEval in PHP8.4 [#1119](https://github.com/smarty-php/smarty/issues/1119)
## [5.4.4] - 2025-04-13
- Fix syntax error occurring when registering a function plugin that ends with the string 'close' [#1122](https://github.com/smarty-php/smarty/issues/1122)
- Replace SMARTY_VERSION constant with $smarty.version in debug.tpl [#1073](https://github.com/smarty-php/smarty/issues/1073)
## [5.4.3] - 2024-12-23
- Fix PHP backtraces by qualifying/replacing `call_user_func_array` calls [#1074](https://github.com/smarty-php/smarty/issues/1074)
## [5.4.2] - 2024-11-20
+2
View File
@@ -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/)!
+9 -1
View File
@@ -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/).
@@ -18,3 +18,11 @@ composer require smarty/smarty
````
More in the [Getting Started](./docs/getting-started.md) section of the docs.
## Sponsors
Smarty is sponsored by:
- Marc Laporte [@marclaporte](https://github.com/marclaporte)
- [Temma](https://github.com/Digicreon/Temma), the MVC framework based on Smarty
Thank you!
-1
View File
@@ -1 +0,0 @@
- Fix PHP backtraces by qualifying/replacing `call_user_func_array` calls [#1074](https://github.com/smarty-php/smarty/issues/1074)
@@ -50,6 +50,120 @@ separated from surrounding elements by spaces. Note that items listed in
| is \[not\] odd by | | $a is not odd by $b | \[not\] an odd grouping | ($a / $b) % 2 != 0 |
| is in | | $a is in $b | exists in array | in_array($a, $b) |
| is \[not\] in | | $a is not in $b | does not exist in array | !in_array($a, $b) |
| matches | | $a matches $b | regex pattern match | preg_match($b, $a) |
## Regex Matching Operator
The `matches` operator allows you to test if a string matches a regular expression pattern.
### Basic Usage
```smarty
{if "hello" matches "/^[a-z]+$/"}
String matches the pattern!
{/if}
{if $email matches "/^[^@]+@[^@]+\.[^@]+$/"}
Valid email format
{else}
Invalid email format
{/if}
```
### Using Variables
```smarty
{$pattern = '/^[a-zA-Z0-9]{8,}$/'}
{if $password matches $pattern}
Password meets requirements
{else}
Password must be at least 8 alphanumeric characters
{/if}
```
### Pattern Modifiers
The `matches` operator supports all standard PHP regex modifiers:
```smarty
{* Case insensitive matching *}
{if "HELLO" matches "/hello/i"}
Matches (case insensitive)
{/if}
{* Multiline mode *}
{if "line1\nline2" matches "/line2$/m"}
Matches in multiline mode
{/if}
{* Dot matches newlines *}
{if "hello\nworld" matches "/hello.world/s"}
Matches with dotall modifier
{/if}
```
### Complex Conditions
The `matches` operator can be combined with other operators:
```smarty
{if $username matches "/^[a-z]+$/" && $username|length > 3}
Valid username
{else}
Username must be lowercase letters and at least 4 characters
{/if}
{if $input matches "/^[0-9]+$/" || $input matches "/^[a-z]+$/"}
Input is either numeric or lowercase letters
{else}
Invalid input format
{/if}
```
### Practical Examples
**Email Validation:**
```smarty
{if $email matches "/^[^@\s]+@[^@\s]+\.[^@\s]+$/"}
Valid email address
{else}
Please enter a valid email address
{/if}
```
**Password Strength:**
```smarty
{if $password matches "/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/"}
Strong password
{else}
Password must contain uppercase, lowercase, numbers and be at least 8 characters
{/if}
```
**URL Validation:**
```smarty
{if $url matches "/^https?:\/\/(www\.)?[a-z0-9\-]+(\.[a-z]{2,})+/i"}
Valid URL format
{else}
Please enter a valid URL
{/if}
```
**Numeric Validation:**
```smarty
{if $input matches "/^[0-9]+$/"}
Valid numeric input
{else}
Please enter numbers only
{/if}
```
### Notes
- The `matches` operator uses PHP's `preg_match()` function internally
- Pattern delimiters must be valid regex delimiters (typically `/`)
- Invalid patterns will cause PHP warnings but won't break template execution
- For complex regex patterns, consider using variables for better readability
## Ternary
You can use the `?:` (or ternary) operator to test one expression and present the value
@@ -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
@@ -0,0 +1,10 @@
# empty
Returns true if var does not exist or has a value that is empty or equal to zero, aka falsey, see conversion to boolean. Otherwise returns false.
## Basic usage
```smarty
{if $myVar|empty}it's an empty variable{/if}
{if empty($myVar)}it's an empty variable{/if}
```
@@ -0,0 +1,17 @@
# in_array
test if value is contained in an array
## Basic usage
```smarty
{if in_array('value2', $myarray)} value2 is in myarray{/if}
```
Can be replaced by operator *is in*
```smarty
{if 'value2' is in $myarray}value2 is in myarray{/if}
```
See Also [operators](../language-basic-syntax/language-syntax-operators.md)
@@ -123,4 +123,89 @@ this will output:
```html
name: Zaphod Beeblebrox<br />
email: zaphod@slartibartfast.example.com<br />
```
```
## Backed Enums (PHP 8.1+)
Smarty supports accessing properties of [backed enums](https://www.php.net/manual/en/language.enumerations.backed.php) introduced in PHP 8.1.
### Accessing Enum Properties
You can access the `name` and `value` properties of backed enum cases:
```smarty
{* Access enum case properties *}
<option id="{MyEnum::Foo->name}">{MyEnum::Foo->value}</option>
```
### Complete Example
```php
<?php
use Smarty\Smarty;
// Define a backed enum
enum Status: string {
case Active = 'active';
case Inactive = 'inactive';
case Pending = 'pending';
}
$smarty = new Smarty();
$smarty->assign('currentStatus', Status::Active);
$smarty->display('template.tpl');
```
`template.tpl`:
```smarty
{* Display enum properties *}
Current status: {$currentStatus->name} (value: {$currentStatus->value})
{* Use in HTML attributes *}
<select name="status">
<option value="{Status::Active->value}" {if $currentStatus->name === 'Active'}selected{/if}>Active</option>
<option value="{Status::Inactive->value}" {if $currentStatus->name === 'Inactive'}selected{/if}>Inactive</option>
<option value="{Status::Pending->value}" {if $currentStatus->name === 'Pending'}selected{/if}>Pending</option>
</select>
```
This would output:
```html
Current status: Active (value: active)
<select name="status">
<option value="active" selected>Active</option>
<option value="inactive">Inactive</option>
<option value="pending">Pending</option>
</select>
```
### Integer-backed Enums
Integer-backed enums work the same way:
```php
<?php
enum Priority: int {
case Low = 1;
case Medium = 2;
case High = 3;
}
$smarty->assign('priority', Priority::High);
```
```smarty
{* Access integer enum properties *}
Priority level: {$priority->value} ({$priority->name})
```
Output:
```html
Priority level: 3 (High)
```
> **Note**: Backed enum support requires PHP 8.1 or higher. The enum must be registered or available in the current namespace.
+2
View File
@@ -22,6 +22,8 @@ Some of Smarty's features:
- [Template Inheritance](api/inheritance.md) for
easy management of template content.
- [Plugin](api/extending/introduction.md) architecture
- Regex pattern matching with the [`matches`](designers/language-basic-syntax/language-syntax-operators.md#regex-matching-operator) operator
- Support for PHP 8.1+ [backed enums](designers/language-variables/language-assigned-variables.md#backed-enums-php-81)
## Separation of presentation from application code
- This means templates can certainly contain logic under the condition
+1 -1
View File
@@ -1,7 +1,7 @@
# Getting started
## Requirements
Smarty can be run with PHP 7.2 to PHP 8.3.
Smarty can be run with PHP 7.2 to PHP 8.5.
## Installation
Smarty can be installed with [Composer](https://getcomposer.org/).
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 343.3 174.5" style="enable-background:new 0 0 343.3 174.5;" xml:space="preserve">
<style type="text/css">
.st0{fill:#E60000;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M333.8,0H9.5C4.3,0,0,4.3,0,9.5v155.4c0,5.3,4.3,9.5,9.5,9.5h324.3c5.3,0,9.5-4.3,9.5-9.5V9.5
C343.3,4.3,339,0,333.8,0z"/>
<g>
<path class="st1" d="M148.7,58.5c-5.2,0-4.7,4.3-4.7,9.5v38.6c0,5.3,4.3,9.5,9.5,9.5c5.2,0,9.5-4.3,9.5-9.5V81.5
C163,65.7,154,58.5,148.7,58.5L148.7,58.5z"/>
<path class="st1" d="M47.1,72.6c-5.2,0-4.7,4.3-4.7,9.5v24.5c0,5.2,4.3,9.5,9.5,9.5c5.2,0,9.5-4.3,9.5-9.5V95.5
C61.4,79.8,52.4,72.6,47.1,72.6L47.1,72.6z"/>
<path class="st1" d="M200.7,58.3c-7.4,0-13.8,6.9-15.2,11.4c-3-7.6-8.5-11.2-12.2-11.2c-5.2,0-4.7,4.3-4.7,9.5l0.1,9.5v29.1
c0,5.2,4.3,9.5,9.5,9.5c5.2,0,9.5-4.3,9.5-9.5V84.4c0-3.8,1.2-8.4,7-8.4c1.7,0,6.8,0,6.8,8.4v22.2c0,5.2,4.3,9.5,9.5,9.5
c5.2,0,9.5-4.3,9.5-9.5V79.8C220.5,66.5,212.9,58.3,200.7,58.3L200.7,58.3z"/>
<path class="st1" d="M130.8,58.4c-4.4,0-7.7,2.6-9.1,7.1l-6.1,19.9l-6.1-19.7c-1.4-4.6-5.1-7.4-9.6-7.4h-0.9
c-4.5,0-8.2,2.8-9.6,7.4l-5.4,17.5c-1.5-4.9-5.8-31.4-31.1-33c-8.7-0.5-11.2,4.7-12,7.6c-0.7,2.8-0.4,5.2,1.3,7.3
c1.7,2.2,5,2.5,8.9,4C61,73,63.6,81.8,65.9,87.3c0.8,1.9,6.4,20.8,6.4,20.8c1.8,5.2,5.4,8.1,10.2,8.1H83c4.8,0,8.4-2.9,10-7.8
l6.3-19.7l6.2,19.7c1.6,5,5.2,7.8,10,7.8h0.5c4.8,0,8.4-2.9,10.2-8L139.2,71l0-0.1c0.2-0.7,0.6-2.3,0.6-3.6
C139.8,62.3,135.9,58.4,130.8,58.4L130.8,58.4z"/>
<path class="st1" d="M276,99.9c-0.2-0.2-0.4-0.4-0.6-0.6l-14.1-14.4l11.3-9.6l0,0c1.9-1.8,4.1-4.3,4.1-8c0-4.3-3.2-8.7-8.6-8.7
c-3.6,0-5.9,1.9-8,3.9l-15,14.2c-1.6-12.5-9.2-18.4-14-18.4c-4.2,0-4.8,2.7-4.8,6.5c-0.1,1.1-0.1,2.4-0.1,3.7v38
c0,4.9,3.8,9.1,8.6,9.6c0.3,0,0.7,0.1,1,0.1c5.3,0,9.5-4.4,9.5-9.6v-7.5l3.3-3l14.5,16.4c0.6,0.6,1.3,1.3,2.1,1.9
c1.1,0.9,2.5,1.5,4.2,1.8c0.1,0,1.2,0.1,1.8,0.1c4.3-0.1,8.7-3.2,8.6-8.6C279.6,104.1,277.9,101.9,276,99.9L276,99.9z"/>
<circle class="st1" cx="293.3" cy="106.7" r="9.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+68
View File
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="195mm"
height="43.093071mm"
viewBox="0 0 195 43.093071"
version="1.1"
id="svg1156"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1153" />
<g
id="layer1"
transform="translate(-6.9662436,-7.8467567)">
<g
id="g21305-1"
style="font-size:33.5139px;line-height:1.25;stroke-width:0.0700042"
transform="translate(1.0553387,0.93988827)">
<g
id="g21248-5">
<rect
style="font-size:33.5139px;line-height:1.25;fill:#107ef4;fill-opacity:1;stroke:none;stroke-width:0.0727743"
id="rect846-9-0-1-2-7-2-1-9"
width="43.093071"
height="43.093071"
x="5.9109049"
y="6.9068685"
ry="2.4179134" />
<path
d="M 22.234889,18.179756 V 36.734061 H 19.567887 V 18.179756 h -6.882585 v -2.35155 h 16.403494 v 2.35155 z"
style="font-size:31.75px;line-height:1.25;font-family:FreeSans;-inkscape-font-specification:FreeSans;fill:#ffffff;stroke-width:0.238978"
id="path16874-7-9-2-4" />
<path
d="m 41.725989,35.328866 v 1.806679 q -0.946355,0.258097 -1.634613,0.258097 -2.380228,0 -2.46626,-2.380228 -1.691969,2.380228 -4.674422,2.380228 -1.548582,0 -2.580969,-0.745614 v 6.395069 H 27.989497 V 21.707084 h 2.380228 v 10.639329 q 0,1.347839 0.831645,2.150808 0.831646,0.802968 2.23684,0.802968 1.864034,0 2.982454,-1.376517 1.11842,-1.405195 1.11842,-3.699389 v -8.517199 h 2.380227 v 12.503362 q 0,1.175775 1.290485,1.175775 0.229419,0 0.516193,-0.05736 z"
style="font-size:31.75px;line-height:1.25;font-family:FreeSans;-inkscape-font-specification:FreeSans;fill:#ffffff;stroke-width:0.238978"
id="path16871-6-1-9-9" />
</g>
<g
aria-label="Temma"
transform="matrix(0.91459055,0,0,0.9686482,-4.3308403,0.67015143)"
id="text87274-2-5-0"
style="font-size:39.8454px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';fill:#000000;stroke-width:0.0205099">
<path
d="m 89.719297,15.597759 v 4.50253 h -10.24872 v 23.30956 h -5.578356 v -23.30956 h -10.24872 v -4.50253 z"
style="font-weight:600;font-family:Poppins;-inkscape-font-specification:'Poppins Semi-Bold';fill:#000000"
id="path7781-61-0-9" />
<path
d="m 109.19647,31.894528 q 0,1.195362 -0.15939,2.151652 H 92.899673 q 0.199227,2.390724 1.673504,3.745467 1.4743,1.354744 3.62597,1.354744 3.107933,0 4.422823,-2.669642 h 6.01666 q -0.95629,3.187632 -3.66578,5.259593 -2.70948,2.032115 -6.654173,2.032115 -3.18765,0 -5.737768,-1.394588 -2.51026,-1.434435 -3.944694,-4.024386 -1.394589,-2.589951 -1.394589,-5.97681 0,-3.426705 1.394589,-6.016656 1.394589,-2.589951 3.904849,-3.98454 2.510273,-1.394589 5.777613,-1.394589 3.147783,0 5.618193,1.354743 2.51026,1.354744 3.86501,3.865004 1.39459,2.470415 1.39459,5.697893 z m -5.77759,-1.593816 q -0.0398,-2.151652 -1.55397,-3.426705 -1.51411,-1.314898 -3.705613,-1.314898 -2.07197,0 -3.50643,1.275053 -1.394586,1.235207 -1.713349,3.46655 z"
style="font-weight:600;font-family:Poppins;-inkscape-font-specification:'Poppins Semi-Bold';fill:#000000"
id="path7783-8-3-1" />
<path
d="m 140.67428,21.016733 q 4.06423,0 6.53465,2.51026 2.51026,2.470415 2.51026,6.9331 v 12.949756 h -5.57836 V 31.217156 q 0,-2.589951 -1.3149,-3.944695 -1.3149,-1.394589 -3.58608,-1.394589 -2.27119,0 -3.62594,1.394589 -1.31489,1.354744 -1.31489,3.944695 v 12.192693 h -5.57836 V 31.217156 q 0,-2.589951 -1.3149,-3.944695 -1.3149,-1.394589 -3.58608,-1.394589 -2.31104,0 -3.66578,1.394589 -1.3149,1.354744 -1.3149,3.944695 v 12.192693 h -5.57836 V 21.335496 h 5.57836 v 2.669642 q 1.07583,-1.394589 2.74933,-2.191497 1.71336,-0.796908 3.74547,-0.796908 2.58995,0 4.62207,1.115671 2.03211,1.075826 3.14778,3.107942 1.07583,-1.91258 3.10795,-3.068096 2.07196,-1.155517 4.46268,-1.155517 z"
style="font-weight:600;font-family:Poppins;-inkscape-font-specification:'Poppins Semi-Bold';fill:#000000"
id="path7785-7-6-7" />
<path
d="m 182.4322,21.016733 q 4.06423,0 6.53465,2.51026 2.51026,2.470415 2.51026,6.9331 v 12.949756 h -5.57836 V 31.217156 q 0,-2.589951 -1.31489,-3.944695 -1.3149,-1.394589 -3.58609,-1.394589 -2.27119,0 -3.62593,1.394589 -1.3149,1.354744 -1.3149,3.944695 v 12.192693 h -5.57836 V 31.217156 q 0,-2.589951 -1.31489,-3.944695 -1.3149,-1.394589 -3.58609,-1.394589 -2.31103,0 -3.66578,1.394589 -1.3149,1.354744 -1.3149,3.944695 v 12.192693 h -5.57835 V 21.335496 h 5.57835 v 2.669642 q 1.07583,-1.394589 2.74934,-2.191497 1.71335,-0.796908 3.74546,-0.796908 2.58996,0 4.62207,1.115671 2.03212,1.075826 3.14779,3.107942 1.07582,-1.91258 3.10794,-3.068096 2.07196,-1.155517 4.46268,-1.155517 z"
style="font-weight:600;font-family:Poppins;-inkscape-font-specification:'Poppins Semi-Bold';fill:#000000"
id="path7787-9-1-7" />
<path
d="m 195.34205,32.292982 q 0,-3.347014 1.3149,-5.936965 1.35474,-2.589951 3.62593,-3.98454 2.31104,-1.394589 5.14006,-1.394589 2.47041,0 4.3033,0.996135 1.87274,0.996135 2.98841,2.51026 v -3.147787 h 5.6182 v 22.074353 h -5.6182 v -3.227478 q -1.07583,1.553971 -2.98841,2.589952 -1.87273,0.996134 -4.34315,0.996134 -2.78917,0 -5.10021,-1.434434 -2.27119,-1.434434 -3.62593,-4.024385 -1.3149,-2.629797 -1.3149,-6.016656 z m 17.3726,0.07969 q 0,-2.032116 -0.79691,-3.46655 -0.79691,-1.47428 -2.15165,-2.231343 -1.35475,-0.796908 -2.90872,-0.796908 -1.55397,0 -2.86887,0.757063 -1.31489,0.757062 -2.15165,2.231342 -0.79691,1.434435 -0.79691,3.426705 0,1.99227 0.79691,3.506395 0.83676,1.47428 2.15165,2.271188 1.35475,0.796908 2.86887,0.796908 1.55397,0 2.90872,-0.757062 1.35474,-0.796908 2.15165,-2.231343 0.79691,-1.47428 0.79691,-3.506395 z"
style="font-weight:600;font-family:Poppins;-inkscape-font-specification:'Poppins Semi-Bold';fill:#000000"
id="path7789-2-0-1" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

+30 -1
View File
@@ -8,6 +8,25 @@ It allows you to write **templates**, using **variables**, **modifiers**, **func
<p>
The number of pixels is: {math equation="x * y" x=$height y=$width}.
</p>
<p>
{if $email matches "/^[^@]+@[^@]+\.[^@]+$/"}
Valid email address
{else}
Please enter a valid email
{/if}
</p>
```
```html
<h1>Hello world</h1>
<p>
The number of pixels is: 307200.
</p>
<p>
Valid email address
</p>
```
When this template is rendered, with the value "Hello world" for the variable $title, 640 for $width,
@@ -32,4 +51,14 @@ and 480 for $height, the result is:
- [Troubleshooting](./appendixes/troubleshooting.md)
## Source code
- [Smarty repository at GitHub](https://github.com/smarty-php/smarty)
- [Smarty repository at GitHub](https://github.com/smarty-php/smarty)
## Sponsors
### <img src="img/iwink-logo.svg" alt="iWink logo" width="100">
[iWink](https://www.iwink.nl/), fullservice digital agency
### <img src="img/temma-logo.svg" alt="Temma logo" width="100">
[Temma](https://www.temma.net/), the MVC framework based on Smarty
+14
View File
@@ -49,6 +49,11 @@ $smarty->registerPlugin('modifier', 'my_special_func', 'my_special_func');
But you may want to consider writing a proper [extension](api/extending/extensions.md).
#### Passing parameters by reference
You cannot use plugins that expect a parameter by reference anymore. PHP-function such as `reset()`, `prev()`,
`next()` and `end()` can be registered as plugin, but they won't work because they expect a parameter by
reference and Smarty will try to pass it by value.
### Removed undocumented tags
Smarty 4 still supported some tags that have been carried over from previous version, but have never been documented.
@@ -102,6 +107,15 @@ Search your code for the following changes:
- `$smarty->assignByRef()` should be replaced with `$smarty->assign()`
- `$smarty->loadPlugin()` should be replaced with `$smarty->registerPlugin()`
### Removed undocumented magic API methods
Smarty v4 allowed setting (internal) properties for which no setter existed by using magic methods
that start with either `set` or `get`. This has been removed in Smarty 5.
For example, `$smarty->setErrorUnassigned(true);` would set the `error_unassigned` property.
Every setter of getter that was ever documented or unit tested has been kept.
### Removed PHP constants
The following constants have been removed to prevent global side effects.
+2
View File
@@ -57,9 +57,11 @@ nav:
- 'date_format': 'designers/language-modifiers/language-modifier-date-format.md'
- 'debug_print_var': 'designers/language-modifiers/language-modifier-debug-print-var.md'
- 'default': 'designers/language-modifiers/language-modifier-default.md'
- 'empty': "designers/language-modifiers/language-modifier-empty.md"
- 'escape': 'designers/language-modifiers/language-modifier-escape.md'
- 'from_charset': 'designers/language-modifiers/language-modifier-from-charset.md'
- 'indent': 'designers/language-modifiers/language-modifier-indent.md'
- 'in_array': 'designers/language-modifiers/language-modifier-in_array.md'
- 'is_array': 'designers/language-modifiers/language-modifier-is_array.md'
- 'isset': 'designers/language-modifiers/language-modifier-isset.md'
- 'join': 'designers/language-modifiers/language-modifier-join.md'
+2 -8
View File
@@ -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);
}
}
}
+144
View File
@@ -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
View File
@@ -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);
}
/**
+20 -16
View File
@@ -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) . ")";
+7 -8
View File
@@ -505,7 +505,7 @@ class Template extends BaseCompiler {
*
* @return string
*/
public function compileVariable($variable) {
public function triggerTagNoCache($variable): void {
if (!strpos($variable, '(')) {
// not a variable variable
$var = trim($variable, '\'');
@@ -516,7 +516,6 @@ class Template extends BaseCompiler {
false
)->isNocache();
}
return '$_smarty_tpl->getValue(' . $variable . ')';
}
/**
@@ -1147,12 +1146,12 @@ class Template extends BaseCompiler {
}
// check if tag is a function
if ($this->smarty->getFunctionHandler($base_tag)) {
if (!isset($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedTag($base_tag, $this)) {
if ($this->smarty->getFunctionHandler($tag)) {
if (!isset($this->smarty->security_policy) || $this->smarty->security_policy->isTrustedTag($tag, $this)) {
return (new \Smarty\Compile\PrintExpressionCompiler())->compile(
['nofilter'], // functions are never auto-escaped
$this,
['value' => $this->compileFunctionCall($base_tag, $args, $parameter)]
['value' => $this->compileFunctionCall($tag, $args, $parameter)]
);
}
}
@@ -1165,16 +1164,16 @@ class Template extends BaseCompiler {
}
// the default plugin handler is a handler of last resort, it may also handle not specifically registered tags.
if ($callback = $this->getPluginFromDefaultHandler($base_tag, Smarty::PLUGIN_COMPILER)) {
if ($callback = $this->getPluginFromDefaultHandler($tag, Smarty::PLUGIN_COMPILER)) {
if (!empty($parameter['modifierlist'])) {
throw new CompilerException('No modifiers allowed on ' . $base_tag);
throw new CompilerException('No modifiers allowed on ' . $tag);
}
$tagCompiler = new \Smarty\Compile\Tag\BCPluginWrapper($callback);
return $tagCompiler->compile($args, $this, $parameter);
}
if ($this->getPluginFromDefaultHandler($base_tag, Smarty::PLUGIN_FUNCTION)) {
return $this->defaultHandlerFunctionCallCompiler->compile($args, $this, $parameter, $tag, $base_tag);
return $this->defaultHandlerFunctionCallCompiler->compile($args, $this, $parameter, $tag, $tag);
}
if ($this->getPluginFromDefaultHandler($base_tag, Smarty::PLUGIN_BLOCK)) {
+2 -2
View File
@@ -168,7 +168,6 @@ class BCPluginsAdapter extends Base {
}
public function loadPluginsFromDir(string $path) {
foreach([
'function',
'modifier',
@@ -177,6 +176,7 @@ class BCPluginsAdapter extends Base {
'prefilter',
'postfilter',
'outputfilter',
'modifiercompiler',
] as $type) {
foreach (glob($path . $type . '.?*.php') as $filename) {
$pluginName = $this->getPluginNameFromFilename($filename);
@@ -226,4 +226,4 @@ class BCPluginsAdapter extends Base {
return $matches[1];
}
}
}
+2 -2
View File
@@ -320,7 +320,7 @@ class DefaultExtension extends Base {
break;
}
foreach ($var as $curr_key => $curr_val) {
$results .= '<br>' . str_repeat('&nbsp;', $depth * 2) . '<b>' . strtr($curr_key, $_replace) .
$results .= '<br>' . str_repeat('&nbsp;', $depth * 2) . '<b>' . htmlspecialchars(strtr($curr_key, $_replace)) .
'</b> =&gt; ' .
$this->smarty_modifier_debug_print_var($curr_val, $max, $length, ++$depth, $objects);
$depth--;
@@ -338,7 +338,7 @@ class DefaultExtension extends Base {
}
$objects[] = $var;
foreach ($object_vars as $curr_key => $curr_val) {
$results .= '<br>' . str_repeat('&nbsp;', $depth * 2) . '<b> -&gt;' . strtr($curr_key, $_replace) .
$results .= '<br>' . str_repeat('&nbsp;', $depth * 2) . '<b> -&gt;' . htmlspecialchars(strtr($curr_key, $_replace)) .
'</b> = ' . $this->smarty_modifier_debug_print_var($curr_val, $max, $length, ++$depth, $objects);
$depth--;
}
+77
View File
@@ -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;
}
+44 -38
View File
@@ -160,6 +160,7 @@ class TemplateLexer
'LOGOP' => '"<", "==" ... logical operator',
'TLOGOP' => '"lt", "eq" ... logical operator; "is div by" ... if condition',
'SCOND' => '"is even" ... if condition',
'MATCHES' => '"matches" regex operator',
);
/**
@@ -567,7 +568,7 @@ class TemplateLexer
public function yylex3()
{
if (!isset($this->yy_global_pattern3)) {
$this->yy_global_pattern3 = $this->replace("/\G(\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal)|\G([\"])|\G('[^'\\\\]*(?:\\\\.[^'\\\\]*)*')|\G([$][0-9]*[a-zA-Z_]\\w*)|\G([$])|\G(\\s+is\\s+(not\\s+)?in\\s+)|\G(\\s+as\\s+)|\G(\\s+to\\s+)|\G(\\s+step\\s+)|\G(\\s+instanceof\\s+)|\G(\\s*([!=][=]{1,2}|[<][=>]?|[>][=]?|[&|]{2})\\s*)|\G(\\s+(eq|ne|neq|gt|ge|gte|lt|le|lte|mod|and|or|xor)\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even|div)\\s+by\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even))|\G([!]\\s*|not\\s+)|\G([(](int(eger)?|bool(ean)?|float|double|real|string|binary|array|object)[)]\\s*)|\G(\\s*[(]\\s*)|\G(\\s*[)])|\G(\\[\\s*)|\G(\\s*\\])|\G(\\s*[-][>]\\s*)|\G(\\s*[=][>]\\s*)|\G(\\s*[=]\\s*)|\G(([+]|[-]){2})|\G(\\s*([+]|[-])\\s*)|\G(\\s*([*]{1,2}|[%\/^&]|[<>]{2})\\s*)|\G([@])|\G(array\\s*[(]\\s*)|\G([#])|\G(\\s+[0-9]*[a-zA-Z_][a-zA-Z0-9_\-:]*\\s*[=]\\s*)|\G(([0-9]*[a-zA-Z_]\\w*)?(\\\\[0-9]*[a-zA-Z_]\\w*)+)|\G([0-9]*[a-zA-Z_]\\w*)|\G(\\d+)|\G([`])|\G([|][@]?)|\G([.])|\G(\\s*[,]\\s*)|\G(\\s*[;]\\s*)|\G([:]{2})|\G(\\s*[:]\\s*)|\G(\\s*[?]\\s*)|\G(0[xX][0-9a-fA-F]+)|\G(\\s+)|\G([\S\s])/isS");
$this->yy_global_pattern3 = $this->replace("/\G(\\s*SMARTYrdel)|\G((SMARTYldel)SMARTYal)|\G([\"])|\G('[^'\\\\]*(?:\\\\.[^'\\\\]*)*')|\G([$][0-9]*[a-zA-Z_]\\w*)|\G([$])|\G(\\s+is\\s+(not\\s+)?in\\s+)|\G(\\s+matches\\s+)|\G(\\s+as\\s+)|\G(\\s+to\\s+)|\G(\\s+step\\s+)|\G(\\s+instanceof\\s+)|\G(\\s*([!=][=]{1,2}|[<][=>]?|[>][=]?|[&|]{2})\\s*)|\G(\\s+(eq|ne|neq|gt|ge|gte|lt|le|lte|mod|and|or|xor)\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even|div)\\s+by\\s+)|\G(\\s+is\\s+(not\\s+)?(odd|even))|\G([!]\\s*|not\\s+)|\G([(](int(eger)?|bool(ean)?|float|double|real|string|binary|array|object)[)]\\s*)|\G(\\s*[(]\\s*)|\G(\\s*[)])|\G(\\[\\s*)|\G(\\s*\\])|\G(\\s*[-][>]\\s*)|\G(\\s*[=][>]\\s*)|\G(\\s*[=]\\s*)|\G(([+]|[-]){2})|\G(\\s*([+]|[-])\\s*)|\G(\\s*([*]{1,2}|[%\/^&]|[<>]{2})\\s*)|\G([@])|\G(array\\s*[(]\\s*)|\G([#])|\G(\\s+[0-9]*[a-zA-Z_][a-zA-Z0-9_\-:]*\\s*[=]\\s*)|\G(([0-9]*[a-zA-Z_]\\w*)?(\\\\[0-9]*[a-zA-Z_]\\w*)+)|\G([0-9]*[a-zA-Z_]\\w*)|\G(\\d+)|\G([`])|\G([|][@]?)|\G([.])|\G(\\s*[,]\\s*)|\G(\\s*[;]\\s*)|\G([:]{2})|\G(\\s*[:]\\s*)|\G(\\s*[?]\\s*)|\G(0[xX][0-9a-fA-F]+)|\G(\\s+)|\G([\S\s])/isS");
}
if (!isset($this->dataLength)) {
$this->dataLength = strlen($this->data);
@@ -662,119 +663,124 @@ class TemplateLexer
public function yy_r3_10()
{
$this->token = \Smarty\Parser\TemplateParser::TP_AS;
$this->token = \Smarty\Parser\TemplateParser::TP_MATCHES;
}
public function yy_r3_11()
{
$this->token = \Smarty\Parser\TemplateParser::TP_TO;
$this->token = \Smarty\Parser\TemplateParser::TP_AS;
}
public function yy_r3_12()
{
$this->token = \Smarty\Parser\TemplateParser::TP_STEP;
$this->token = \Smarty\Parser\TemplateParser::TP_TO;
}
public function yy_r3_13()
{
$this->token = \Smarty\Parser\TemplateParser::TP_INSTANCEOF;
$this->token = \Smarty\Parser\TemplateParser::TP_STEP;
}
public function yy_r3_14()
{
$this->token = \Smarty\Parser\TemplateParser::TP_INSTANCEOF;
}
public function yy_r3_15()
{
$this->token = \Smarty\Parser\TemplateParser::TP_LOGOP;
}
public function yy_r3_16()
public function yy_r3_17()
{
$this->token = \Smarty\Parser\TemplateParser::TP_SLOGOP;
}
public function yy_r3_18()
public function yy_r3_19()
{
$this->token = \Smarty\Parser\TemplateParser::TP_TLOGOP;
}
public function yy_r3_21()
public function yy_r3_22()
{
$this->token = \Smarty\Parser\TemplateParser::TP_SINGLECOND;
}
public function yy_r3_24()
public function yy_r3_25()
{
$this->token = \Smarty\Parser\TemplateParser::TP_NOT;
}
public function yy_r3_25()
public function yy_r3_26()
{
$this->token = \Smarty\Parser\TemplateParser::TP_TYPECAST;
}
public function yy_r3_29()
public function yy_r3_30()
{
$this->token = \Smarty\Parser\TemplateParser::TP_OPENP;
}
public function yy_r3_30()
public function yy_r3_31()
{
$this->token = \Smarty\Parser\TemplateParser::TP_CLOSEP;
}
public function yy_r3_31()
public function yy_r3_32()
{
$this->token = \Smarty\Parser\TemplateParser::TP_OPENB;
}
public function yy_r3_32()
public function yy_r3_33()
{
$this->token = \Smarty\Parser\TemplateParser::TP_CLOSEB;
}
public function yy_r3_33()
public function yy_r3_34()
{
$this->token = \Smarty\Parser\TemplateParser::TP_PTR;
}
public function yy_r3_34()
public function yy_r3_35()
{
$this->token = \Smarty\Parser\TemplateParser::TP_APTR;
}
public function yy_r3_35()
public function yy_r3_36()
{
$this->token = \Smarty\Parser\TemplateParser::TP_EQUAL;
}
public function yy_r3_36()
public function yy_r3_37()
{
$this->token = \Smarty\Parser\TemplateParser::TP_INCDEC;
}
public function yy_r3_38()
public function yy_r3_39()
{
$this->token = \Smarty\Parser\TemplateParser::TP_UNIMATH;
}
public function yy_r3_40()
public function yy_r3_41()
{
$this->token = \Smarty\Parser\TemplateParser::TP_MATH;
}
public function yy_r3_42()
public function yy_r3_43()
{
$this->token = \Smarty\Parser\TemplateParser::TP_AT;
}
public function yy_r3_43()
public function yy_r3_44()
{
$this->token = \Smarty\Parser\TemplateParser::TP_ARRAYOPEN;
}
public function yy_r3_44()
public function yy_r3_45()
{
$this->token = \Smarty\Parser\TemplateParser::TP_HATCH;
}
public function yy_r3_45()
public function yy_r3_46()
{
// resolve conflicts with shorttag and right_delimiter starting with '='
@@ -786,73 +792,73 @@ class TemplateLexer
$this->token = \Smarty\Parser\TemplateParser::TP_ATTR;
}
}
public function yy_r3_46()
public function yy_r3_47()
{
$this->token = \Smarty\Parser\TemplateParser::TP_NAMESPACE;
}
public function yy_r3_49()
public function yy_r3_50()
{
$this->token = \Smarty\Parser\TemplateParser::TP_ID;
}
public function yy_r3_50()
public function yy_r3_51()
{
$this->token = \Smarty\Parser\TemplateParser::TP_INTEGER;
}
public function yy_r3_51()
public function yy_r3_52()
{
$this->token = \Smarty\Parser\TemplateParser::TP_BACKTICK;
$this->yypopstate();
}
public function yy_r3_52()
public function yy_r3_53()
{
$this->token = \Smarty\Parser\TemplateParser::TP_VERT;
}
public function yy_r3_53()
public function yy_r3_54()
{
$this->token = \Smarty\Parser\TemplateParser::TP_DOT;
}
public function yy_r3_54()
public function yy_r3_55()
{
$this->token = \Smarty\Parser\TemplateParser::TP_COMMA;
}
public function yy_r3_55()
public function yy_r3_56()
{
$this->token = \Smarty\Parser\TemplateParser::TP_SEMICOLON;
}
public function yy_r3_56()
public function yy_r3_57()
{
$this->token = \Smarty\Parser\TemplateParser::TP_DOUBLECOLON;
}
public function yy_r3_57()
public function yy_r3_58()
{
$this->token = \Smarty\Parser\TemplateParser::TP_COLON;
}
public function yy_r3_58()
public function yy_r3_59()
{
$this->token = \Smarty\Parser\TemplateParser::TP_QMARK;
}
public function yy_r3_59()
public function yy_r3_60()
{
$this->token = \Smarty\Parser\TemplateParser::TP_HEX;
}
public function yy_r3_60()
public function yy_r3_61()
{
$this->token = \Smarty\Parser\TemplateParser::TP_SPACE;
}
public function yy_r3_61()
public function yy_r3_62()
{
$this->token = \Smarty\Parser\TemplateParser::TP_TEXT;
+5
View File
@@ -160,6 +160,7 @@ class TemplateLexer
'LOGOP' => '"<", "==" ... logical operator',
'TLOGOP' => '"lt", "eq" ... logical operator; "is div by" ... if condition',
'SCOND' => '"is even" ... if condition',
'MATCHES' => '"matches" regex operator',
);
/**
@@ -325,6 +326,7 @@ class TemplateLexer
tlop = ~\s+is\s+(not\s+)?(odd|even|div)\s+by\s+~
scond = ~\s+is\s+(not\s+)?(odd|even)~
isin = ~\s+is\s+(not\s+)?in\s+~
matches = ~\s+matches\s+~
as = ~\s+as\s+~
to = ~\s+to\s+~
step = ~\s+step\s+~
@@ -469,6 +471,9 @@ class TemplateLexer
isin {
$this->token = \Smarty\Parser\TemplateParser::TP_ISIN;
}
matches {
$this->token = \Smarty\Parser\TemplateParser::TP_MATCHES;
}
as {
$this->token = \Smarty\Parser\TemplateParser::TP_AS;
}
+1357 -1160
View File
File diff suppressed because it is too large Load Diff
+138 -32
View File
@@ -19,7 +19,7 @@ use \Smarty\ParseTree\Code;
use \Smarty\ParseTree\Dq;
use \Smarty\ParseTree\DqContent;
use \Smarty\ParseTree\Tag;
use \Smarty\CompilerException;
/**
* Smarty Template Parser Class
@@ -306,7 +306,8 @@ smartytag(A) ::= SIMPELOUTPUT(B). {
$attributes[] = 'nocache';
$var = $match[1];
}
A = $this->compiler->compilePrintExpression($this->compiler->compileVariable('\''.$var.'\''), $attributes);
$this->compiler->triggerTagNoCache($var);
A = $this->compiler->compilePrintExpression('$_smarty_tpl->getValue(\''.$var.'\')', $attributes);
}
// simple tag like {name}
@@ -375,7 +376,7 @@ outattr(A) ::= output(B) attributes(C). {
A = array(B,C);
}
output(A) ::= variable(B). {
output(A) ::= variablevalue(B). {
A = B;
}
output(A) ::= value(B). {
@@ -680,6 +681,11 @@ expr(res) ::= expr(e1) isin(c) value(v). {
res = c . e1.',(array)'.v.')';
}
// regex matching
expr(res) ::= expr(e1) matchop(c) value(e2). {
res = c . e2 . ',' . e1 . ') ';
}
// null coalescing
nullcoalescing(res) ::= expr(v) QMARK QMARK expr(e2). {
res = v.' ?? '.e2;
@@ -689,7 +695,8 @@ nullcoalescing(res) ::= expr(v) QMARK QMARK expr(e2). {
// ternary
//
ternary(res) ::= expr(v) QMARK DOLLARID(e1) COLON expr(e2). {
res = v.' ? '. $this->compiler->compileVariable('\''.substr(e1,1).'\'') . ' : '.e2;
$this->compiler->triggerTagNoCache(substr(e1,1));
res = v.' ? $_smarty_tpl->getValue(\''.substr(e1,1).'\') : '.e2;
}
ternary(res) ::= expr(v) QMARK value(e1) COLON expr(e2). {
@@ -706,7 +713,7 @@ ternary(res) ::= expr(v) QMARK COLON expr(e2). {
}
// value
value(res) ::= variable(v). {
value(res) ::= variablevalue(v). {
res = v;
}
@@ -724,7 +731,7 @@ value(res) ::= TYPECAST(t) value(v). {
res = t.v;
}
value(res) ::= variable(v) INCDEC(o). {
value(res) ::= variablevalue(v) INCDEC(o). {
res = v.o;
}
@@ -771,10 +778,10 @@ value(res) ::= OPENP expr(e) CLOSEP. {
res = '('. e .')';
}
value(res) ::= variable(v1) INSTANCEOF(i) ns1(v2). {
value(res) ::= variablevalue(v1) INSTANCEOF(i) ns1(v2). {
res = v1.i.v2;
}
value(res) ::= variable(v1) INSTANCEOF(i) variable(v2). {
value(res) ::= variablevalue(v1) INSTANCEOF(i) variablevalue(v2). {
res = v1.i.v2;
}
@@ -797,7 +804,8 @@ value(res) ::= varindexed(vi) DOUBLECOLON static_class_access(r). {
if (vi['var'] === '\'smarty\'') {
$this->compiler->appendPrefixCode("<?php {$prefixVar} = ". (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']).';?>');
} else {
$this->compiler->appendPrefixCode("<?php {$prefixVar} = ". $this->compiler->compileVariable(vi['var']).vi['smarty_internal_index'].';?>');
$this->compiler->triggerTagNoCache(vi['var']);
$this->compiler->appendPrefixCode("<?php {$prefixVar} = \$_smarty_tpl->getValue(" . vi['var'] . ')'.vi['smarty_internal_index'].';?>');
}
res = $prefixVar .'::'.r[0].r[1];
}
@@ -847,54 +855,88 @@ ns1(res) ::= NAMESPACE(i). {
}
// variable lists
// multiple variables
variablelist(res) ::= variablelist(l) COMMA variable(v). {
res = array_merge(l,array(v));
}
variablelist(res) ::= variablelist(l) COMMA expr(e). {
res = array_merge(l,array(e));
}
// single variable
variablelist(res) ::= variable(v). {
res = array(v);
}
// single expression
variablelist(res) ::= expr(e). {
res = array(e);
}
// no variable
variablelist(res) ::= . {
res = array();
}
//
// variables
//
// Smarty variable (optional array)
variable(res) ::= DOLLARID(i). {
res = $this->compiler->compileVariable('\''.substr(i,1).'\'');
$this->compiler->triggerTagNoCache(substr(i,1));
res = array('$_smarty_tpl->hasVariable(\''.substr(i,1).'\')','$_smarty_tpl->getValue(\''.substr(i,1).'\')');
}
variable(res) ::= varindexed(vi). {
if (vi['var'] === '\'smarty\'') {
$smarty_var = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']);
res = $smarty_var;
res = array('true', $smarty_var);
} else {
// used for array reset,next,prev,end,current
$this->last_variable = vi['var'];
$this->last_index = vi['smarty_internal_index'];
res = $this->compiler->compileVariable(vi['var']).vi['smarty_internal_index'];
$this->compiler->triggerTagNoCache(vi['var']);
res = array('true', '$_smarty_tpl->getValue(' . vi['var'] . ')'.vi['smarty_internal_index']);
}
}
// variable with property
variable(res) ::= varvar(v) AT ID(p). {
res = '$_smarty_tpl->getVariable('. v .')->'.p;
res = array('true', '$_smarty_tpl->getVariable('. v .')->'.p);
}
// object
variable(res) ::= object(o). {
res = o;
res = array('true', o);
}
// config variable
variable(res) ::= HATCH ID(i) HATCH. {
configvariable(res) ::= HATCH ID(i) HATCH. {
res = $this->compiler->compileConfigVariable('\'' . i . '\'');
}
variable(res) ::= HATCH ID(i) HATCH arrayindex(a). {
configvariable(res) ::= HATCH ID(i) HATCH arrayindex(a). {
res = '(is_array($tmp = ' . $this->compiler->compileConfigVariable('\'' . i . '\'') . ') ? $tmp'.a.' :null)';
}
variable(res) ::= HATCH variable(v) HATCH. {
configvariable(res) ::= HATCH variablevalue(v) HATCH. {
res = $this->compiler->compileConfigVariable(v);
}
variable(res) ::= HATCH variable(v) HATCH arrayindex(a). {
configvariable(res) ::= HATCH variablevalue(v) HATCH arrayindex(a). {
res = '(is_array($tmp = ' . $this->compiler->compileConfigVariable(v) . ') ? $tmp'.a.' : null)';
}
variablevalue(res) ::= variable(v). {
res = v[1];
}
variablevalue(res) ::= configvariable(v). {
res = v;
}
varindexed(res) ::= DOLLARID(i) arrayindex(a). {
res = array('var'=>'\''.substr(i,1).'\'', 'smarty_internal_index'=>a);
}
@@ -918,14 +960,17 @@ arrayindex ::= . {
// single index definition
// Smarty2 style index
indexdef(res) ::= DOT DOLLARID(i). {
res = '['.$this->compiler->compileVariable('\''.substr(i,1).'\'').']';
$this->compiler->triggerTagNoCache(substr(i,1));
res = '[$_smarty_tpl->getValue(\''.substr(i,1).'\')]';
}
indexdef(res) ::= DOT varvar(v). {
res = '['.$this->compiler->compileVariable(v).']';
$this->compiler->triggerTagNoCache(v);
res = '[$_smarty_tpl->getValue(' . v . ')]';
}
indexdef(res) ::= DOT varvar(v) AT ID(p). {
res = '['.$this->compiler->compileVariable(v).'->'.p.']';
$this->compiler->triggerTagNoCache(v);
res = '[$_smarty_tpl->getValue(' . v . ')->'.p.']';
}
indexdef(res) ::= DOT ID(i). {
@@ -956,9 +1001,10 @@ indexdef(res) ::= OPENB INTEGER(n) CLOSEB. {
res = '['.n.']';
}
indexdef(res) ::= OPENB DOLLARID(i) CLOSEB. {
res = '['.$this->compiler->compileVariable('\''.substr(i,1).'\'').']';
$this->compiler->triggerTagNoCache(substr(i,1));
res = '[$_smarty_tpl->getValue(\''.substr(i,1).'\')]';
}
indexdef(res) ::= OPENB variable(v) CLOSEB. {
indexdef(res) ::= OPENB variablevalue(v) CLOSEB. {
res = '['.v.']';
}
indexdef(res) ::= OPENB value(v) CLOSEB. {
@@ -1000,7 +1046,8 @@ varvarele(res) ::= ID(s). {
}
varvarele(res) ::= SIMPELOUTPUT(i). {
$var = trim(substr(i, $this->compiler->getLdelLength(), -$this->compiler->getRdelLength()), ' $');
res = $this->compiler->compileVariable('\''.$var.'\'');
$this->compiler->triggerTagNoCache($var);
res = '$_smarty_tpl->getValue(\''.$var.'\')';
}
// variable sections of element
@@ -1015,16 +1062,27 @@ object(res) ::= varindexed(vi) objectchain(oc). {
if (vi['var'] === '\'smarty\'') {
res = (new \Smarty\Compile\SpecialVariableCompiler())->compile(array(),$this->compiler,vi['smarty_internal_index']).oc;
} else {
res = $this->compiler->compileVariable(vi['var']).vi['smarty_internal_index'].oc;
$this->compiler->triggerTagNoCache(vi['var']);
res = '$_smarty_tpl->getValue(' . vi['var'] . ')'.vi['smarty_internal_index'].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;
}
@@ -1041,7 +1099,8 @@ objectelement(res)::= PTR varvar(v) arrayindex(a). {
if ($this->security) {
$this->compiler->trigger_template_error (self::ERR2);
}
res = '->{'.$this->compiler->compileVariable(v).a.'}';
$this->compiler->triggerTagNoCache(v);
res = '->{$_smarty_tpl->getValue(' . v . ')'.a.'}';
}
objectelement(res)::= PTR LDEL expr(e) RDEL arrayindex(a). {
@@ -1067,8 +1126,41 @@ objectelement(res)::= PTR method(f). {
//
// function
//
function(res) ::= ns1(f) OPENP params(p) CLOSEP. {
res = $this->compiler->compileModifierInExpression(f, p);
function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP optobjectchain(oc). {
if (f == 'isset') {
res = '(true';
if (count(v) == 0) {
throw new CompilerException("Invalid number of arguments for isset. isset expects at least one parameter.");
}
foreach (v as $value) {
if (is_array($value)) {
res .= ' && (' . $value[0] . ' && null !== (' . $value[1] . ' ?? null))';
} else {
res .= ' && (' . $value . ' !== null)';
}
}
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] . '))' . oc;
} else {
res = 'false == ' . v[0] . oc;
}
} else {
$p = array();
foreach (v as $value) {
if (is_array($value)) {
$p[] = $value[1];
} else {
$p[] = $value;
}
}
res = $this->compiler->compileModifierInExpression(f, $p) . oc;
}
}
@@ -1087,7 +1179,8 @@ method(res) ::= DOLLARID(f) OPENP params(p) CLOSEP. {
$this->compiler->trigger_template_error (self::ERR2);
}
$prefixVar = $this->compiler->getNewPrefixVariable();
$this->compiler->appendPrefixCode("<?php {$prefixVar} = ".$this->compiler->compileVariable('\''.substr(f,1).'\'').';?>');
$this->compiler->triggerTagNoCache(substr(f,1));
$this->compiler->appendPrefixCode("<?php {$prefixVar} = \$_smarty_tpl->getValue('".substr(f,1).'\')'.';?>');
res = $prefixVar .'('. implode(',',p) .')';
}
@@ -1166,6 +1259,11 @@ static_class_access(res) ::= ID(v). {
res = array(v, '');
}
// static class constant with object chain
static_class_access(res) ::= ID(v) objectchain(oc). {
res = array(v, oc);
}
// static class variables
static_class_access(res) ::= DOLLARID(v) arrayindex(a). {
res = array(v, a, 'property');
@@ -1225,6 +1323,10 @@ scond(res) ::= SINGLECOND(o). {
res = $scond[$op];
}
matchop(res) ::= MATCHES(o). {
res = 'preg_match(';
}
//
// ARRAY element assignment
//
@@ -1243,6 +1345,10 @@ arrayelements(res) ::= arrayelements(a1) COMMA arrayelement(a). {
res = a1.','.a;
}
arrayelements(res) ::= arrayelements(a) COMMA. {
res = a.',';
}
arrayelements ::= . {
return;
}
@@ -1282,7 +1388,7 @@ doublequoted(res) ::= doublequotedcontent(o). {
res = new Dq($this, o);
}
doublequotedcontent(res) ::= BACKTICK variable(v) BACKTICK. {
doublequotedcontent(res) ::= BACKTICK variablevalue(v) BACKTICK. {
res = new Code('(string)'.v);
}
@@ -1294,7 +1400,7 @@ doublequotedcontent(res) ::= DOLLARID(i). {
res = new Code('(string)$_smarty_tpl->getValue(\''. substr(i,1) .'\')');
}
doublequotedcontent(res) ::= LDEL variable(v) RDEL. {
doublequotedcontent(res) ::= LDEL variablevalue(v) RDEL. {
res = new Code('(string)'.v);
}
+1 -1
View File
@@ -31,7 +31,7 @@ class StringEval extends RecompiledPlugin
*
* @return void
*/
public function populate(\Smarty\Template\Source $source, \Smarty\Template $_template = null)
public function populate(\Smarty\Template\Source $source, ?\Smarty\Template $_template = null)
{
$source->uid = sha1($source->name);
$source->timestamp = $source->exists = true;
+3 -7
View File
@@ -54,7 +54,7 @@ class Smarty extends \Smarty\TemplateBase {
/**
* smarty version
*/
const SMARTY_VERSION = '5.4.2';
const SMARTY_VERSION = '5.8.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);
}
}
}
-2
View File
@@ -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')) {
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -108,7 +108,7 @@
</head>
<body>
<h1>Smarty {\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)}
+2 -4
View File
@@ -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());
}
@@ -65,15 +65,34 @@ class UndefinedTemplateVarTest extends PHPUnit_Smarty
*/
public function testError()
{
$this->smarty->error_unassigned = true;
$this->smarty->error_unassigned = true;
$this->expectException(PHPUnit\Framework\Error\Error::class);
$this->expectExceptionMessage('Undefined ');
$this->expectExceptionMessage('Undefined ');
$e1 = error_reporting();
$this->assertEquals('undefined = ', $this->smarty->fetch('001_main.tpl'));
$e2 = error_reporting();
$this->assertEquals($e1, $e2);
}
public function testNoError()
{
$this->smarty->error_unassigned = false;
$e1 = error_reporting();
$this->assertEquals('undefined = ', $this->smarty->fetch('001_main.tpl'));
$e2 = error_reporting();
$this->assertEquals($e1, $e2);
}
public function testNoErrorForIssetOrEmpty()
{
$this->smarty->error_unassigned = true;
$e1 = error_reporting();
$this->assertEquals('undefined = ', $this->smarty->fetch('001_isset.tpl'));
$this->assertEquals('undefined = ', $this->smarty->fetch('001_empty.tpl'));
$e2 = error_reporting();
$this->assertEquals($e1, $e2);
}
public function testUndefinedSimpleVar() {
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $undef}def{/if}b');
@@ -133,30 +152,30 @@ class UndefinedTemplateVarTest extends PHPUnit_Smarty
}
public function testDereferenceOnNull() {
$this->smarty->setErrorReporting(E_ALL & ~E_WARNING & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', null);
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
public function testDereferenceOnNull() {
$this->smarty->setErrorReporting(E_ALL & ~E_WARNING & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', null);
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
public function testDereferenceOnBool() {
$this->smarty->setErrorReporting(E_ALL & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', false);
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
public function testDereferenceOnBool() {
$this->smarty->setErrorReporting(E_ALL & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', false);
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
public function testDereferenceOnString() {
$this->smarty->setErrorReporting(E_ALL & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', 'xyz');
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
public function testDereferenceOnString() {
$this->smarty->setErrorReporting(E_ALL & ~E_NOTICE);
$this->smarty->muteUndefinedOrNullWarnings();
$tpl = $this->smarty->createTemplate('string:a{if $object->myprop}def{/if}b');
$this->smarty->assign('object', 'xyz');
$this->assertEquals("ab", $this->smarty->fetch($tpl));
}
}
@@ -0,0 +1 @@
undefined = {if empty($foo)}{/if}
@@ -0,0 +1 @@
undefined = {if isset($foo)}{/if}
@@ -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)'
);
}
}
@@ -192,6 +192,15 @@ class RegisterFunctionTest extends PHPUnit_Smarty
];
}
/**
* test registerPlugin for function name ending in 'close' #1122
*/
public function testRegisterFunctionEndingInClose()
{
$this->smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'window_close', 'myfunction');
$this->assertEquals('hello world 1', $this->smarty->fetch('eval:{window_close value=1}'));
}
}
function myfunction($params, $smarty)
@@ -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\Smarty::PLUGIN_MODIFIER, 'testmodifier', eval('return strrev(...);'));
$this->assertEquals('mosredna', $this->smarty->fetch('string:{"andersom"|testmodifier}'));
}
public function testRegisterFirstClassCallableSameName()
{
$this->smarty->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'mymodifier', eval('return strrev(...);'));
$this->assertEquals('mosredna', $this->smarty->fetch('string:{"andersom"|mymodifier}'));
}
public function testRegisterFirstClassCallableAsFunc()
{
$this->smarty->registerPlugin(\Smarty\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\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";
}
@@ -135,6 +135,20 @@ class RegisterModifierTest extends PHPUnit_Smarty
$this->assertEquals($expectedValue, $this->smarty->fetch('string:' . $template));
}
public function testRegisterNativePhpFuncAsString()
{
$this->smarty->registerPlugin(\Smarty\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\Smarty::PLUGIN_MODIFIER, 'k_xyz_a', 'strrev');
$this->smarty->assign('myVar', 'andersom');
$this->assertEquals('mosredna', $this->smarty->fetch('string:{k_xyz_a($myVar)}'));
}
}
class WildcardExtension extends \Smarty\Extension\Base {
@@ -64,6 +64,21 @@ class StaticClassAccessTest extends PHPUnit_Smarty
$this->assertEquals('3', $this->smarty->fetch($tpl));
}
/**
* test static class constant chain
*/
public function testRegisteredBackedEnum()
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Enums only available after PHP >= 8.1');
return;
} else {
$this->smarty->registerClass('RegisteredBackedEnum', MyBackedEnum::class);
$tpl = $this->smarty->createTemplate('eval:{RegisteredBackedEnum::A->value}');
$this->assertEquals('3', $this->smarty->fetch($tpl));
}
}
/**
* test static class method
*/
@@ -134,3 +149,7 @@ class mystaticclass
return $i * $i;
}
}
if (PHP_VERSION_ID >= 80100) {
eval('enum MyBackedEnum: int { case A = 3; }');
}
@@ -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);
}
}
@@ -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()}
@@ -41,10 +41,9 @@ class CompileIfTest extends PHPUnit_Smarty
$name = empty($testName) ? $testNumber : $testName;
$file = "testIf_{$name}.tpl";
$this->makeTemplateFile($file, $code);
$this->smarty->assignGlobal('file', $file);
$this->smarty->assign('file', $file);
$this->smarty->assign('bar', 'buh');
$this->assertEquals($result, $this->smarty->fetch($file),
"testIf - {$code} - {$name}");
$this->assertEquals($result, $this->smarty->fetch($file), "testIf - {$code} - {$name}");
}
/*
@@ -70,6 +70,8 @@ class ArrayTest extends PHPUnit_Smarty
array('{$foo=[1,2,[7,8,9],4,5]}{$x=2}{$foo.$x.0}', '7', 'T13', $i++),
array('{$foo=[1,2,[7,8,9],4,5]}{$x=0}{$foo.2.$x}', '7', 'T14', $i++),
array('{$foo=[1,2,[7,8,9],4,5]}{$x=[1,0]}{$foo.2.{$x.1}}', '7', 'T15', $i++),
array('{$foo=[1,2,3,4,5,]}{foreach $foo as $bar}{$bar}{/foreach}', '12345', 'T16', $i++),
array('{$foo=[1,2,3,4,5,[6,7,8,],]}{$foo[5][2]}', '8', 'T17', $i++),
);
}
}
@@ -0,0 +1,118 @@
<?php
require_once __DIR__ . '/../../../../Bootstrap.php';
class MatchesOperatorTest extends PHPUnit_Smarty {
public function setUp(): void
{
$this->setUpSmarty(__DIR__);
}
public function testInit()
{
$this->cleanDirs();
}
/**
* Test basic regex matching functionality
*/
public function testBasicMatches() {
// Test string matching pattern
$this->assertEquals('Match!', $this->smarty->fetch('string:{if "hello" matches "/^[a-z]+$/"}Match!{else}No match{/if}'));
// Test no match
$this->assertEquals('No match', $this->smarty->fetch('string:{if "123" matches "/^[a-z]+$/"}Match!{else}No match{/if}'));
// Test with variables
$this->smarty->assign('name', 'hello');
$this->smarty->assign('pattern', '/^[a-z]+$/');
$this->assertEquals('Match!', $this->smarty->fetch('string:{if $name matches $pattern}Match!{else}No match{/if}'));
// Test no match with variables
$this->smarty->assign('number', '123');
$this->assertEquals('No match', $this->smarty->fetch('string:{if $number matches $pattern}Match!{else}No match{/if}'));
}
/**
* Test different regex patterns
*/
public function testDifferentPatterns() {
// Test numeric pattern
$this->assertEquals('Match!', $this->smarty->fetch('string:{if "123" matches "/^[0-9]+$/"}Match!{else}No match{/if}'));
// Test email pattern
$this->assertEquals('Match!', $this->smarty->fetch('string:{if "test@example.com" matches "/^[^@]+@[^@]+\.[^@]+$/"}Match!{else}No match{/if}'));
// Test case insensitive pattern
$this->assertEquals('Match!', $this->smarty->fetch('string:{if "Hello" matches "/hello/i"}Match!{else}No match{/if}'));
// Test pattern with special characters
$this->assertEquals('Match!', $this->smarty->fetch('string:{if "hello-world" matches "/^[a-z-]+$/"}Match!{else}No match{/if}'));
}
/**
* Test regex patterns with modifiers
*/
public function testPatternModifiers() {
// Test case insensitive modifier
$this->assertEquals('Match!', $this->smarty->fetch('string:{if "HELLO" matches "/hello/i"}Match!{else}No match{/if}'));
// Test multiline modifier
$this->assertEquals('Match!', $this->smarty->fetch('string:{if "hello\nworld" matches "/world$/m"}Match!{else}No match{/if}'));
// Test dotall modifier
$this->assertEquals('Match!', $this->smarty->fetch('string:{if "hello\nworld" matches "/hello.world/s"}Match!{else}No match{/if}'));
}
/**
* Test complex expressions with matches operator
*/
public function testComplexExpressions() {
// Test with logical AND
$this->smarty->assign('name', 'hello');
$this->smarty->assign('pattern', '/^[a-z]+$/');
$this->smarty->assign('length', 5);
$this->assertEquals('Valid!', $this->smarty->fetch('string:{if $name matches $pattern && $length > 3}Valid!{else}Invalid{/if}'));
// Test with logical OR
$this->assertEquals('Valid!', $this->smarty->fetch('string:{if $name matches $pattern || $length < 3}Valid!{else}Invalid{/if}'));
// Test in elseif
$this->assertEquals('ElseIf!', $this->smarty->fetch('string:{if $name matches "/^[0-9]+$/"}No{elseif $name matches $pattern}ElseIf!{else}Else{/if}'));
// Test in while loop condition (should work with constant patterns)
$result = $this->smarty->fetch('string:{assign var="i" value=0}{while $i < 2 && "test" matches "/test/"}{$i}{assign var="i" value=$i+1}{/while}');
$this->assertEquals('01', $result);
}
/**
* Test edge cases and error handling
*/
public function testEdgeCases() {
// Test with empty string
$this->assertEquals('No match', $this->smarty->fetch('string:{if "" matches "/.+/"}Match!{else}No match{/if}'));
// Test with complex pattern
$this->assertEquals('Match!', $this->smarty->fetch('string:{if "abc123" matches "/^[a-z]+[0-9]+$/"}Match!{else}No match{/if}'));
// Test with single character pattern
$this->assertEquals('Match!', $this->smarty->fetch('string:{if "a" matches "/a/"}Match!{else}No match{/if}'));
}
/**
* Test that invalid patterns still work (they will cause PHP warnings but not break the template)
* Note: This test is commented out because invalid patterns cause PHP warnings that fail the test
* In production, invalid patterns would cause warnings but the template would still execute
*/
/*
public function testInvalidPatterns() {
// This will cause a PHP warning but should not break the template execution
$this->smarty->assign('invalid_pattern', 'not-a-valid-pattern');
$this->smarty->assign('name', 'hello');
// The template should execute and reach the else branch due to the invalid pattern
$result = $this->smarty->fetch('string:{if $name matches $invalid_pattern}Match!{else}No match{/if}');
$this->assertEquals('No match', $result);
}
*/
}
@@ -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');
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
FROM php:8.4-rc-cli-bullseye
FROM php:8.4-cli-bullseye
## Basic utilities
RUN apt-get update -yqq && apt-get install -y curl apt-utils git zip unzip
+10
View File
@@ -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