mirror of
https://github.com/smarty-php/smarty.git
synced 2026-08-03 20:14:31 +02:00
Compare commits
118 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f9eef51c7 | |||
| 94a27cbbc7 | |||
| badc5ef3a0 | |||
| 2ae0f9a65f | |||
| b668745acf | |||
| 3c9f77a2e0 | |||
| 042dff64a9 | |||
| 1830aa7a12 | |||
| b83ffdd2d6 | |||
| ac27e1e5b6 | |||
| 17fae11a38 | |||
| 06594a1149 | |||
| 75a738ad44 | |||
| 11e69eca68 | |||
| 31e06fc087 | |||
| 99c048ce7a | |||
| 1c9b2ce1d3 | |||
| 29a77ded2f | |||
| 6d06c5e61f | |||
| ba745f5b91 | |||
| 0775fa84d1 | |||
| 56ab75df2c | |||
| 383e20e6b4 | |||
| c139883770 | |||
| 6e648ed809 | |||
| 3577fc7091 | |||
| ff2ef3b0cb | |||
| aa2dcd82cf | |||
| 78d259d3b9 | |||
| 4ed569c674 | |||
| a46478d0cb | |||
| 5487e31c4b | |||
| 12ce28e265 | |||
| 139797a165 | |||
| 6709d000cd | |||
| aa6edc3c0b | |||
| 73da7e90f3 | |||
| c0d7a36124 | |||
| 8cc9a75964 | |||
| 21d7fbb67e | |||
| 1fc41e385d | |||
| fe325daec0 | |||
| 3f0f308a7b | |||
| b390e50974 | |||
| 0e46ae3add | |||
| 4d793ee04c | |||
| 7677b84058 | |||
| e7457d78cb | |||
| cbcd66e1de | |||
| 46cd224efb | |||
| d3b716a0b9 | |||
| 693502c341 | |||
| bb968469fa | |||
| 372f7cf41a | |||
| f659193c94 | |||
| 0b6166482a | |||
| 4ff25bbe59 | |||
| afbd71b868 | |||
| e26f82d02c | |||
| 6ad6a35824 | |||
| baa53399f5 | |||
| 37bea77ea7 | |||
| 0d33f3acde | |||
| 05adbf45f3 | |||
| df4795b803 | |||
| 5401418258 | |||
| a5bbba3f05 | |||
| a0664a4ab2 | |||
| a4b8466205 | |||
| 5d1ea5806a | |||
| ab247c723b | |||
| fb997111f4 | |||
| c6bff57950 | |||
| 07faafe4da | |||
| 295b7fb582 | |||
| cf9de567c1 | |||
| f47ac761af | |||
| 642a97adcc | |||
| 3bba3ff5e9 | |||
| b3b43af816 | |||
| 1b06b37db2 | |||
| a1b4c9c551 | |||
| cd58df7a26 | |||
| 70ed68ff5b | |||
| 598ccde435 | |||
| cb09fda90d | |||
| c8f1853bfe | |||
| 2f781e2e65 | |||
| 30c6ee64ab | |||
| fd90f7eac9 | |||
| 77b91a072b | |||
| 9ede0e40fa | |||
| 1ccfca17d6 | |||
| d6153d4d4d | |||
| 2289fa69f1 | |||
| 3cb3585432 | |||
| a2193eb4cf | |||
| 8ecde47203 | |||
| b978cb348e | |||
| 353ca06d07 | |||
| 20c08ac40d | |||
| 4aa1273a80 | |||
| 2a87c65994 | |||
| cdee97d3f1 | |||
| 06d6a5efd9 | |||
| 0be92bc8a6 | |||
| 61db287b8f | |||
| 3293a873bd | |||
| 8d53d3cbf2 | |||
| 47c4864dd1 | |||
| f411247aa1 | |||
| 9a8702d937 | |||
| 3232277bc5 | |||
| bbd09c7bfa | |||
| 52dc8adafb | |||
| 6f054ecc2f | |||
| 4fec27ccc2 | |||
| fea0d02d99 |
@@ -0,0 +1 @@
|
||||
github: [wisskid]
|
||||
@@ -23,6 +23,7 @@ jobs:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
|
||||
php-version:
|
||||
- "7.2"
|
||||
@@ -32,31 +33,15 @@ jobs:
|
||||
- "8.1"
|
||||
- "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
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- 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
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install PHP with extensions
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -68,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Cache Composer packages
|
||||
id: composer-cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: vendor
|
||||
key: v5r2-${{ runner.os }}-php-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -76,7 +61,7 @@ jobs:
|
||||
v5r1-${{ runner.os }}-php-${{ matrix.php-version }}-
|
||||
|
||||
- name: Install dependencies
|
||||
uses: php-actions/composer@v6
|
||||
run: composer install
|
||||
|
||||
- name: Run make
|
||||
run: make -B
|
||||
|
||||
@@ -7,3 +7,5 @@ phpunit*
|
||||
.phpunit.result.cache
|
||||
vendor/*
|
||||
composer.lock
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file is the single source of truth for AI coding assistants working in this repo (including Claude Code, claude.ai/code).
|
||||
|
||||
## Project
|
||||
|
||||
Smarty v5 — PHP template engine. Single Composer package (`smarty/smarty`), namespace `Smarty\`, source in `src/`, autoloaded via PSR-4. Supports PHP 7.2–8.5.
|
||||
|
||||
Do not use PHP syntax newer than 7.2 in `src/` unless it is guarded for older runtimes.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
composer install
|
||||
|
||||
# Regenerate lexers and parsers (required before tests; CI runs this)
|
||||
make -B
|
||||
|
||||
# Run all tests
|
||||
php ./vendor/phpunit/phpunit/phpunit
|
||||
|
||||
# Run a single test file
|
||||
php ./vendor/phpunit/phpunit/phpunit tests/UnitTests/TemplateSource/TagTests/If/CompileIfTest.php
|
||||
|
||||
# Run tests matching a PHPUnit group
|
||||
php ./vendor/phpunit/phpunit/phpunit --group 20221124
|
||||
|
||||
# Run tests excluding slow group
|
||||
php ./vendor/phpunit/phpunit/phpunit --exclude-group slow
|
||||
```
|
||||
|
||||
There is no linter or static analysis configured. No typecheck step.
|
||||
|
||||
## Generated code — do not hand-edit
|
||||
|
||||
Four files are generated from grammar/lexer definitions via `make`:
|
||||
|
||||
| Source (edit this) | Generated (do not edit) |
|
||||
|---|---|
|
||||
| `src/Lexer/TemplateLexer.plex` | `src/Lexer/TemplateLexer.php` |
|
||||
| `src/Lexer/ConfigfileLexer.plex` | `src/Lexer/ConfigfileLexer.php` |
|
||||
| `src/Parser/TemplateParser.y` | `src/Parser/TemplateParser.php` |
|
||||
| `src/Parser/ConfigfileParser.y` | `src/Parser/ConfigfileParser.php` |
|
||||
|
||||
After editing a `.plex` or `.y` file, run `make -B` to regenerate. The generators require the `smarty/smarty-lexer` dev dependency.
|
||||
|
||||
## Architecture
|
||||
|
||||
- `src/Smarty.php` — main class, extends `TemplateBase`. Version constant: `Smarty::SMARTY_VERSION`.
|
||||
- `src/Compile/`, `src/Compiler/` — template compilation pipeline.
|
||||
- `src/Lexer/`, `src/Parser/` — lexer/parser (generated, see above).
|
||||
- `src/Extension/` — extension system (`ExtensionInterface`, `CoreExtension`, `DefaultExtension`, `BCPluginsAdapter`).
|
||||
- `src/Runtime/` — runtime helpers (foreach, capture, inheritance, tpl functions).
|
||||
- `src/Resource/`, `src/Cacheresource/` — template resource and cache resource handlers.
|
||||
- `src/BlockHandler/`, `src/FunctionHandler/`, `src/Filter/` — built-in tags, functions, and filters.
|
||||
- `libs/Smarty.class.php` — non-Composer autoload stub. Points to `src/`. Not the main source.
|
||||
- `src/functions.php` — global helper functions, always loaded via Composer `files` autoload.
|
||||
|
||||
## Tests
|
||||
|
||||
- Framework: PHPUnit 7.5/8.5 (bootstrap: `tests/Bootstrap.php`).
|
||||
- All tests extend `PHPUnit_Smarty` (defined in `tests/PHPUnit_Smarty.php`), which provides `setUpSmarty($dir)`.
|
||||
- Test suite root: `tests/UnitTests/`. Typical test `setUp()` calls `$this->setUpSmarty(__DIR__)`.
|
||||
- Each test directory may have its own `templates/`, `configs/` subdirectories. Compiled output goes to `templates_c/` and `cache/` (auto-created by the test harness).
|
||||
- Running the suite scatters generated `templates_c/`, `cache/`, and `templates_tmp/` directories (and empty runtime `templates/`/`configs/` dirs) throughout `tests/` and the repo root. These are not tracked — treat them as noise in `git status`, never commit them, and clean them with `git clean -fd` (exclude tool dirs like `.serena`).
|
||||
- Three test files are excluded in `phpunit.xml`: Memcache, APC, and HttpModifiedSince tests (require external services).
|
||||
- Tests needing MySQL/PDO are gated by constants in `tests/Config.php` (disabled by default).
|
||||
|
||||
## CI
|
||||
|
||||
GitHub Actions (`.github/workflows/ci.yml`): matrix of PHP 7.2–8.5 on ubuntu + windows. Steps: `composer install` → `make -B` → `phpunit`. No deploy step.
|
||||
|
||||
## Docs
|
||||
|
||||
Markdown in `docs/`, built with mkdocs + Material theme. Install the toolchain with `pip install -r docs/requirements.txt`, then preview with `mkdocs serve`. Published via `mike deploy 5.x`.
|
||||
|
||||
## Release
|
||||
|
||||
`./make-release.sh <version>` — only v5.x.x. Updates changelog and version constant, creates a merge commit and tag on `master`.
|
||||
|
||||
### Changelog
|
||||
|
||||
Every change that should appear in `CHANGELOG.md` must add a new markdown file under `changelog/`. At release time `utilities/update-changelog.php` concatenates all `changelog/*.md` files into the `## [Unreleased]` section, so:
|
||||
|
||||
- One file per change, containing a single line that starts with a dash (`- ...`). The filename is arbitrary; name it after the issue number (e.g. `1036.md`) when there is one, otherwise use a short descriptive slug.
|
||||
- Include a markdown link to the relevant issue when one exists, e.g. `[#1036](https://github.com/smarty-php/smarty/issues/1036)`. Omit the link when there is no public issue (e.g. embargoed security reports).
|
||||
+107
-2
@@ -6,15 +6,120 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [5.8.4] - 2026-06-29
|
||||
- Fixed a `TypeError` on PHP 8 when `Security::$static_classes` was set to a non-array value (e.g. the string `'none'`) to disable static class access; any non-array value now cleanly denies access. Use `Security::$static_classes = null` to disable access to all static classes.
|
||||
- Security: the built-in `stream:` resource type now validates the nested stream wrapper against the security policy, so a template such as `stream:php://filter/...` can no longer bypass `Security::$streams` (including `Security::$streams = null`) to read local files (CWE-22)
|
||||
|
||||
|
||||
## [5.8.3] - 2026-06-28
|
||||
- fixed a regression from #1189 where a child template's block override no longer applied to a template {include}d by the parent [#1192](https://github.com/smarty-php/smarty/issues/1192)
|
||||
|
||||
|
||||
## [5.8.2] - 2026-06-24
|
||||
- Security: prevent symlinks inside a trusted `secure_dir`/template directory from being used to read files outside of it (CWE-22 path traversal), affecting `{include}` and `{fetch}` of local files
|
||||
- Security: `{html_image}` now escapes the `file`, `path_prefix`, `href`/`link`, `width` and `height` attributes (it already escaped `alt` and pass-through attributes), and `{html_select_date}` casts `day_size`/`month_size`/`year_size` to int (matching `{html_select_time}`), preventing untrusted values passed into these attributes from breaking out of the generated HTML (CWE-79)
|
||||
- Security: `{fetch}` no longer follows HTTP redirects for remote resources while a security policy is active, preventing an open redirect on a trusted host from bypassing `trusted_uri` (CWE-918 server-side request forgery)
|
||||
- Fixed "Attempt to assign property step on null" error when using a {for} loop inside a block of an extended template [#1036](https://github.com/smarty-php/smarty/issues/1036)
|
||||
|
||||
|
||||
## [5.8.1] - 2026-06-23
|
||||
- Re-activated unit tests for user literals, which were previously disabled due to a bug in refactoring to v5.
|
||||
- fixed a bug where child template's block content leaked into subsequent rendering of the parent template [#1189](https://github.com/smarty-php/smarty/issues/1189)
|
||||
- Moved all unit test-generated output from inside the working tree to tmp files [#1178](https://github.com/smarty-php/smarty/issues/1178)
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
|
||||
- Support the deprecations introduced in PHP 8.4 and added tests for PHP 8.4 [#1043](https://github.com/smarty-php/smarty/pull/1043)
|
||||
|
||||
## [5.4.1] - 2024-08-29
|
||||
|
||||
|
||||
- Enable (and fix) unit tests for Windows [#1046](https://github.com/smarty-php/smarty/pull/1046)
|
||||
- Fix the use of "extends:" to define the inheritance tree on Windows [#1018](https://github.com/smarty-php/smarty/issues/1018)
|
||||
|
||||
## [5.4.0] - 2024-08-14
|
||||
- Fixing forced OpCache invalidation on every template include, which is resulting in fast raising wasted OpCache memory [#1007](https://github.com/smarty-php/smarty/issues/1007)
|
||||
- Improvement of auto-escaping [#1030](https://github.com/smarty-php/smarty/pull/1030)
|
||||
|
||||
|
||||
## [5.3.1] - 2024-06-16
|
||||
- Fixed error when using section with nocache [#1034](https://github.com/smarty-php/smarty/issues/1034)
|
||||
|
||||
|
||||
## [5.3.0] - 2024-05-30
|
||||
- Fix warning when calling hasVariable for an undefined variable [#977](https://github.com/smarty-php/smarty/issues/977)
|
||||
- Added `$smarty->prependTemplateDir()` method [#1022](https://github.com/smarty-php/smarty/issues/1022)
|
||||
|
||||
|
||||
## [5.2.0] - 2024-05-28
|
||||
- Fixed a code injection vulnerability in extends-tag. This addresses CVE-2024-35226.
|
||||
- Added `$smarty->setCacheModifiedCheck()` setter for cache_modified_check
|
||||
- Added a PSR-4 loading script to allow Smarty to be used without Composer [#1017](https://github.com/smarty-php/smarty/pull/1017)
|
||||
|
||||
|
||||
## [5.1.0] - 2024-04-22
|
||||
- Prevent deprecation notices during compilation in PHP8.3 [#996](https://github.com/smarty-php/smarty/issues/996)
|
||||
- Fix that getTemplateVars would return an array of objects instead of the assigned variables values [#994](https://github.com/smarty-php/smarty/issues/994)
|
||||
- Fix Smarty::assign() not returning $this when called with an array as first parameter [#972](https://github.com/smarty-php/smarty/pull/972)
|
||||
- Documented support for `{if $element is in $array}` syntax [#937](https://github.com/smarty-php/smarty/issues/937)
|
||||
- Added support for `{if $element is not in $array}` syntax [#937](https://github.com/smarty-php/smarty/issues/937)
|
||||
- Using stream variables in templates now throws a deprecation notice [#933](https://github.com/smarty-php/smarty/pull/933)
|
||||
- Internal compiler classes always return a string (the internal has_code flag has been removed for simplicity) [#918](https://github.com/smarty-php/smarty/pull/918)
|
||||
- Fix invalid classnames in Runtime code for foreach [#1000](https://github.com/smarty-php/smarty/issues/1000)
|
||||
|
||||
## [5.0.2] - 2024-03-28
|
||||
- Fix Smarty::assign() not returning $this when called with an array as first parameter [#972](https://github.com/smarty-php/smarty/pull/972)
|
||||
|
||||
## [5.0.1] - 2024-03-27
|
||||
- Fix error in Smarty\Smarty::compileAllTemplates() by including missing FilesystemIterator class [#966](https://github.com/smarty-php/smarty/issues/966)
|
||||
|
||||
|
||||
## [5.0.0] - 2024-03-25
|
||||
- Fixed that scoped variables would overwrite parent scope [#952](https://github.com/smarty-php/smarty/issues/952)
|
||||
- Removed publicly accessible `$tpl->_var_stack` variable
|
||||
|
||||
|
||||
### Fixed
|
||||
- Too many shorthand attributes error when using a modifier as a function with more than 3 parameters in an expression [#949](https://github.com/smarty-php/smarty/issues/949)
|
||||
|
||||
|
||||
@@ -100,6 +100,11 @@ Enhancement suggestions are tracked as [GitHub issues](https://github.com/smarty
|
||||
The [docs](docs/index.md) are written in markdown, configured in [mkdocs.yml](mkdocs.yml) and published
|
||||
to [GitHub pages](https://smarty-php.github.io/smarty) using [mkdocs](https://www.mkdocs.org/) and [mike](https://github.com/jimporter/mike).
|
||||
|
||||
You need Python to build the docs. Install the required packages first:
|
||||
```bash
|
||||
pip install -r docs/requirements.txt
|
||||
```
|
||||
|
||||
To preview the docs while you are writing, run:
|
||||
```bash
|
||||
mkdocs serve
|
||||
@@ -115,5 +120,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.3.
|
||||
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!
|
||||
@@ -11,10 +11,10 @@
|
||||
## include inline
|
||||
- Re-introduce merge_compiled_includes and the {include inline} attribute?
|
||||
|
||||
## Output buffering
|
||||
- Fix ob_ output buffering commands being scattered around the codebase
|
||||
## Output buffering (major)
|
||||
- Fix ob_ output buffering commands being scattered around the codebase: Smarty's output model is fundamentally "echo everything, wrap in a buffer to capture". An alternative that would be where rendering returns a string rather than echoing — but that touches the entire compiled template format (the unifunc functions all echo) and is a large change.
|
||||
|
||||
## Review public static vars
|
||||
## Review public static vars (major)
|
||||
- such as _CHARSET and _IS_WINDOWS
|
||||
|
||||
## Block / inheritance
|
||||
@@ -24,9 +24,8 @@
|
||||
## Plugin system
|
||||
- fix template security checks in one place in compiler
|
||||
|
||||
## Beatify output
|
||||
## Beatify output (major)
|
||||
- compiled templates could be proper classes, possibly using [nette/php-generator](https://packagist.org/packages/nette/php-generator)
|
||||
|
||||
## Unrelated / other
|
||||
- review (and avoid) use of 'clone' keyword
|
||||
- what is 'user literal support', why are unit tests skipped?
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
- Internal compiler classes always return a string (the internal has_code flag has been removed for simplicity) [#918](https://github.com/smarty-php/smarty/pull/918)
|
||||
@@ -1 +0,0 @@
|
||||
- Using stream variables in templates now throws a deprecation notice [#933](https://github.com/smarty-php/smarty/pull/933)
|
||||
@@ -1,2 +0,0 @@
|
||||
- Documented support for `{if $element is in $array}` syntax [#937](https://github.com/smarty-php/smarty/issues/937)
|
||||
- Added support for `{if $element is not in $array}` syntax [#937](https://github.com/smarty-php/smarty/issues/937)
|
||||
@@ -1 +0,0 @@
|
||||
- Fix Smarty::assign() not returning $this when called with an array as first parameter [#972](https://github.com/smarty-php/smarty/pull/972)
|
||||
@@ -1 +0,0 @@
|
||||
- Fix that getTemplateVars would return an array of objects instead of the assigned variables values [#994](https://github.com/smarty-php/smarty/issues/994)
|
||||
+5
-1
@@ -1,4 +1,3 @@
|
||||
version: "2"
|
||||
services:
|
||||
base:
|
||||
build:
|
||||
@@ -42,6 +41,11 @@ services:
|
||||
service: base
|
||||
build:
|
||||
dockerfile: ./utilities/testrunners/php83/Dockerfile
|
||||
php84:
|
||||
extends:
|
||||
service: base
|
||||
build:
|
||||
dockerfile: ./utilities/testrunners/php84/Dockerfile
|
||||
volumes:
|
||||
smarty-code:
|
||||
|
||||
|
||||
+42
-10
@@ -12,24 +12,27 @@ Use `getTemplateDir()` to retrieve the configured paths.
|
||||
<?php
|
||||
|
||||
// set a single directory where the config files are stored
|
||||
$smarty->setTemplateDir('./config');
|
||||
$smarty->setTemplateDir('./templates');
|
||||
|
||||
// set multiple directories where config files are stored
|
||||
$smarty->setTemplateDir(['./config', './config_2', './config_3']);
|
||||
// set multiple directories where templates are stored
|
||||
$smarty->setTemplateDir(['./templates', './templates_2', './templates_3']);
|
||||
|
||||
// add directory where config files are stored to the current list of dirs
|
||||
$smarty->addTemplateDir('./config_1');
|
||||
// add directory where templates files are stored to the current list of dirs
|
||||
$smarty->addTemplateDir('./templates_1');
|
||||
|
||||
// add multiple directories to the current list of dirs
|
||||
$smarty->addTemplateDir([
|
||||
'./config_2',
|
||||
'./config_3',
|
||||
'./templates_2',
|
||||
'./templates_3',
|
||||
]);
|
||||
|
||||
// chaining of method calls
|
||||
$smarty->setTemplateDir('./config')
|
||||
->addTemplateDir('./config_1')
|
||||
->addTemplateDir('./config_2');
|
||||
$smarty->setTemplateDir('./templates')
|
||||
->addTemplateDir('./templates_1')
|
||||
->addTemplateDir('./templates_2');
|
||||
|
||||
// insert a template dir before exising template dirs
|
||||
$smarty->prependTemplateDir('./more_important_templates')
|
||||
|
||||
// get all directories where config files are stored
|
||||
$template_dirs = $smarty->getTemplateDir();
|
||||
@@ -140,6 +143,35 @@ Enable auto-escaping for HTML as follows:
|
||||
$smarty->setEscapeHtml(true);
|
||||
```
|
||||
|
||||
When auto-escaping is enabled, the `|escape` modifier's default mode (`html`) has no effect,
|
||||
to avoid double-escaping. It is possible to force it with the `force` mode.
|
||||
Other modes (`htmlall`, `url`, `urlpathinfo`, `quotes`, `javascript`) may be used
|
||||
with the result you might expect, without double-escaping.
|
||||
|
||||
Even when auto-escaping is enabled, you might want to display the content of a variable without
|
||||
escaping it. To do so, use the `|raw` modifier.
|
||||
|
||||
Examples (with auto-escaping enabled):
|
||||
```smarty
|
||||
{* these three statements are identical *}
|
||||
{$myVar}
|
||||
{$myVar|escape}
|
||||
{$myVar|escape:'html'}
|
||||
|
||||
{* no double-escaping on these statements *}
|
||||
{$var|escape:'htmlall'}
|
||||
{$myVar|escape:'url'}
|
||||
{$myVar|escape:'urlpathinfo'}
|
||||
{$myVar|escape:'quotes'}
|
||||
{$myVar|escape:'javascript'}
|
||||
|
||||
{* no escaping at all *}
|
||||
{$myVar|raw}
|
||||
|
||||
{* force double-escaping *}
|
||||
{$myVar|escape:'force'}
|
||||
```
|
||||
|
||||
## Disabling compile check
|
||||
By default, Smarty tests to see if the
|
||||
current template has changed since the last time
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|----------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| file | Yes | The name of the config file to include |
|
||||
| section | No | The name of the section to load |
|
||||
| scope | no | How the scope of the loaded variables are treated, which must be one of local, parent or global. local means variables are loaded into the local template context. parent means variables are loaded into both the local context and the parent template that called it. global means variables are available to all templates. |
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,6 +27,13 @@ which item(s) are selected by default as well.
|
||||
- All parameters that are not in the list above are printed as
|
||||
name/value-pairs inside each of the created <input\>-tags.
|
||||
|
||||
> **Security note**
|
||||
>
|
||||
> The `separator` attribute is written into the generated HTML without escaping,
|
||||
> so it can contain markup such as `separator='<br />'`. If its value originates
|
||||
> from untrusted input, escape it yourself first to avoid cross-site scripting
|
||||
> (XSS). Option values and labels are escaped automatically.
|
||||
|
||||
## Examples
|
||||
```php
|
||||
<?php
|
||||
|
||||
@@ -28,6 +28,13 @@ selected by default as well.
|
||||
- All parameters that are not in the list above are output as
|
||||
name/value-pairs inside each of the created `<input>`-tags.
|
||||
|
||||
> **Security note**
|
||||
>
|
||||
> The `separator` attribute is written into the generated HTML without escaping,
|
||||
> so it can contain markup such as `separator='<br />'`. If its value originates
|
||||
> from untrusted input, escape it yourself first to avoid cross-site scripting
|
||||
> (XSS). Option values and labels are escaped automatically.
|
||||
|
||||
## Examples
|
||||
|
||||
```php
|
||||
|
||||
@@ -47,6 +47,14 @@ name/value-pairs inside the `<select>` tags of day, month and year.
|
||||
> There is an useful php function on the [date tips page](../../appendixes/tips.md)
|
||||
> for converting `{html_select_date}` form values to a timestamp.
|
||||
|
||||
> **Security note**
|
||||
>
|
||||
> The `*_extra` attributes, `field_separator`/`option_separator`, and any
|
||||
> unrecognised parameter (which is emitted as a raw attribute on the `<select>`
|
||||
> tag) are written into the generated HTML without escaping. If any of these
|
||||
> values originate from untrusted input, escape them yourself first to avoid
|
||||
> cross-site scripting (XSS).
|
||||
|
||||
## Exaples
|
||||
|
||||
Template code
|
||||
|
||||
@@ -47,6 +47,14 @@ parseable by PHP's [`strtotime()`](https://www.php.net/strtotime).
|
||||
| meridian\_empty | null | If supplied then the first element of the meridian's select-box has this value as it's label and "" as it's value. This is useful to make the select-box read "Please select an meridian" for example. |
|
||||
|
||||
|
||||
> **Security note**
|
||||
>
|
||||
> The `*_extra` attributes, `field_separator`/`option_separator`, and any
|
||||
> unrecognised parameter (which is emitted as a raw attribute on the `<select>`
|
||||
> tag) are written into the generated HTML without escaping. If any of these
|
||||
> values originate from untrusted input, escape them yourself first to avoid
|
||||
> cross-site scripting (XSS).
|
||||
|
||||
## Examples
|
||||
|
||||
```smarty
|
||||
|
||||
@@ -31,6 +31,15 @@ dumps an array of data into an HTML `<table>`.
|
||||
- `trailpad` is the value put into the trailing cells on the last
|
||||
table row if there are any present.
|
||||
|
||||
> **Security note**
|
||||
>
|
||||
> The `loop`/`cols` data and the `caption`, `trailpad`, `table_attr`, `tr_attr`,
|
||||
> `td_attr` and `th_attr` attributes are written into the generated HTML without
|
||||
> escaping (this is by design — e.g. `table_attr='border="1"'`). If any of these
|
||||
> values originate from untrusted input, escape them yourself first (e.g. with the
|
||||
> [`escape`](../language-modifiers/language-modifier-escape.md) modifier) to avoid
|
||||
> cross-site scripting (XSS).
|
||||
|
||||
## Examples
|
||||
|
||||
```php
|
||||
|
||||
@@ -24,6 +24,14 @@ spiders to lift email addresses off of a site.
|
||||
> you can use hex encoding too.
|
||||
|
||||
|
||||
> **Security note**
|
||||
>
|
||||
> The `extra` attribute is written into the generated `<a>` tag without escaping,
|
||||
> so that you can add attributes such as `extra='class="mailto"'`. If you pass a
|
||||
> value that originates from untrusted input, escape it yourself first (e.g. with
|
||||
> the [`escape`](../language-modifiers/language-modifier-escape.md) modifier) to
|
||||
> avoid cross-site scripting (XSS).
|
||||
|
||||
## 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}
|
||||
```
|
||||
@@ -73,6 +73,6 @@ This snippet is useful for emails, but see also
|
||||
<a href="mailto:{$EmailAddress|escape:'hex'}">{$EmailAddress|escape:'mail'}</a>
|
||||
```
|
||||
|
||||
See also [escaping smarty parsing](../language-basic-syntax/language-escaping.md),
|
||||
See also [auto-escaping](../../api/configuring.md#enabling-auto-escaping), [escaping smarty parsing](../language-basic-syntax/language-escaping.md),
|
||||
[`{mailto}`](../language-custom-functions/language-function-mailto.md) and the [obfuscating email
|
||||
addresses](../../appendixes/tips.md#obfuscating-e-mail-addresses) page.
|
||||
addresses](../../appendixes/tips.md#obfuscating-e-mail-addresses) pages.
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1,8 @@
|
||||
# raw
|
||||
|
||||
Prevents variable escaping when [auto-escaping](../../api/configuring.md#enabling-auto-escaping) is activated.
|
||||
|
||||
## Basic usage
|
||||
```smarty
|
||||
{$myVar|raw}
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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,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/).
|
||||
@@ -25,9 +25,17 @@ Here's how you create an instance of Smarty in your PHP scripts:
|
||||
```php
|
||||
<?php
|
||||
|
||||
// Instantiated via composer
|
||||
require 'vendor/autoload.php';
|
||||
use Smarty\Smarty;
|
||||
$smarty = new Smarty();
|
||||
|
||||
// or ...
|
||||
|
||||
// Instantiated directly
|
||||
require("/path/to/smarty/libs/Smarty.class.php");
|
||||
use Smarty\Smarty;
|
||||
$smarty = new Smarty();
|
||||
```
|
||||
|
||||
Now that the library files are in place, it's time to set up the Smarty
|
||||
|
||||
@@ -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 |
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.2.1, 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 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F5E0B4;}
|
||||
.st1{fill:#383535;}
|
||||
.st2{fill:#FBDE3D;}
|
||||
.st3{fill:#8C919F;}
|
||||
.st4{fill:#CACED5;}
|
||||
.st5{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<polygon class="st0" points="782.2,925.8 733.4,643.2 996.4,532.7 745.7,396.4 823.6,120.2 560.7,232.2 391.6,0 313.7,274.7 26.5,260.1 194.4,491.3 3.6,705.5 289.4,721.3 340.5,1000 529.1,787 "/>
|
||||
<path class="st1" d="M706.9,493.4c0-126-102.2-228.2-228.2-228.2c-126,0-228.2,102.2-228.2,228.2c0,81.8,43.1,153.6,107.8,193.8 v202.6l43.5,43.5l0.2-0.2h150.6l0.2,0.2l43.5-43.5V689C662.6,649,706.9,576.4,706.9,493.4z"/>
|
||||
<path class="st2" d="M682.2,492.9c0-112.7-91.3-204-204-204s-204,91.3-204,204c0,78,43.8,145.8,108.1,180.1v93.6h191.7V673 C638.4,638.7,682.2,571,682.2,492.9z"/>
|
||||
<g>
|
||||
<polygon class="st3" points="382.7,790.4 382.7,881.1 412,910.3 544.9,910.3 574.1,881.1 574.1,790.4 "/>
|
||||
<polygon class="st4" points="540,813.2 540,866.5 527,879.5 540.4,892.9 558.7,874.6 558.9,874.6 558.9,813.2 "/>
|
||||
</g>
|
||||
<path class="st5" d="M478.3,310.4c-5,0-9.1,4.1-9.1,9.1c0,5,4.1,9.1,9.1,9.1c90.7,0,164.3,73.5,164.3,164.3c0,5,4.1,9.1,9.1,9.1 s9.1-4.1,9.1-9.1C660.8,392.1,579.1,310.4,478.3,310.4z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -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
@@ -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
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Python dependencies for building/previewing the docs.
|
||||
#
|
||||
# pip install -r docs/requirements.txt
|
||||
# mkdocs serve # local preview
|
||||
# mike deploy 5.x # publish
|
||||
#
|
||||
# pymdown-extensions must be >=11: earlier releases pass filename=None to
|
||||
# Pygments, and Pygments >=2.19 then crashes with
|
||||
# "'NoneType' object has no attribute 'replace'" on any untitled code block.
|
||||
mkdocs>=1.6
|
||||
mkdocs-material>=9.7
|
||||
pymdown-extensions>=11
|
||||
Pygments>=2.19
|
||||
mike>=2.2
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// This is a stub PSR-4 loading script that gets all the pieces of //
|
||||
// Smarty 5.x loaded without requiring the use of composer. It's //
|
||||
// not really a 'class' file, but the name is used so we're //
|
||||
// backwards compatible with previous versions of Smarty. //
|
||||
// //
|
||||
// Example: //
|
||||
// require_once("/path/to/smarty/libs/Smarty.class.php"); //
|
||||
// //
|
||||
// $smarty = new Smarty\Smarty; //
|
||||
// $smarty->testInstall(); //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
define('__SMARTY_DIR', __DIR__ . '/../src/');
|
||||
|
||||
// Global function declarations
|
||||
require_once(__SMARTY_DIR . "/functions.php");
|
||||
|
||||
spl_autoload_register(function ($class) {
|
||||
// Class prefix
|
||||
$prefix = 'Smarty\\';
|
||||
|
||||
// Does the class use the namespace prefix?
|
||||
$len = strlen($prefix);
|
||||
if (strncmp($prefix, $class, $len) !== 0) {
|
||||
// If not, move to the next registered autoloader
|
||||
return;
|
||||
}
|
||||
|
||||
// Hack off the prefix part
|
||||
$relative_class = substr($class, $len);
|
||||
|
||||
// Build a path to the include file
|
||||
$file = __SMARTY_DIR . str_replace('\\', '/', $relative_class) . '.php';
|
||||
|
||||
// If the file exists, require it
|
||||
if (file_exists($file)) {
|
||||
require_once($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'
|
||||
@@ -68,6 +70,7 @@ nav:
|
||||
- 'noprint': 'designers/language-modifiers/language-modifier-noprint.md'
|
||||
- 'number_format': 'designers/language-modifiers/language-modifier-number-format.md'
|
||||
- 'nl2br': 'designers/language-modifiers/language-modifier-nl2br.md'
|
||||
- 'raw': 'designers/language-modifiers/language-modifier-raw.md'
|
||||
- 'regex_replace': 'designers/language-modifiers/language-modifier-regex-replace.md'
|
||||
- 'replace': 'designers/language-modifiers/language-modifier-replace.md'
|
||||
- 'round': 'designers/language-modifiers/language-modifier-round.md'
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
<testsuite name="foo">
|
||||
<directory>./tests/UnitTests/</directory>
|
||||
<exclude>./tests/UnitTests/CacheResourceTests/Memcache/CacheResourceCustomMemcacheTest.php</exclude>
|
||||
<exclude>./tests/UnitTests/CacheResourceTests/Apc/CacheResourceCustomApcTest.php</exclude>
|
||||
<exclude>./tests/UnitTests/CacheModify/ModifiedSince/HttpModifiedSinceTest.php</exclude>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# - ./run-tests-for-all-php-versions.sh --group 20221124
|
||||
# - ./run-tests-for-all-php-versions.sh --exclude-group slow
|
||||
|
||||
COMPOSE_CMD="mutagen-compose"
|
||||
COMPOSE_CMD="docker compose"
|
||||
|
||||
$COMPOSE_CMD run --rm php72 ./run-tests.sh $@ && \
|
||||
$COMPOSE_CMD run --rm php73 ./run-tests.sh $@ && \
|
||||
@@ -14,3 +14,4 @@ $COMPOSE_CMD run --rm php80 ./run-tests.sh $@ && \
|
||||
$COMPOSE_CMD run --rm php81 ./run-tests.sh $@ && \
|
||||
$COMPOSE_CMD run --rm php82 ./run-tests.sh $@
|
||||
$COMPOSE_CMD run --rm php83 ./run-tests.sh $@
|
||||
$COMPOSE_CMD run --rm php84 ./run-tests.sh $@
|
||||
|
||||
@@ -14,6 +14,6 @@ class BlockPluginWrapper extends Base {
|
||||
}
|
||||
|
||||
public function handle($params, $content, Template $template, &$repeat) {
|
||||
return call_user_func_array($this->callback, [$params, $content, &$template, &$repeat]);
|
||||
return \call_user_func_array($this->callback, [$params, $content, &$template, &$repeat]);
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ abstract class Base
|
||||
*/
|
||||
abstract public function process(
|
||||
Template $_template,
|
||||
Cached $cached = null,
|
||||
?Cached $cached = null,
|
||||
$update = false
|
||||
);
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ abstract class Custom extends Base
|
||||
*/
|
||||
public function process(
|
||||
Template $_smarty_tpl,
|
||||
\Smarty\Template\Cached $cached = null,
|
||||
?\Smarty\Template\Cached $cached = null,
|
||||
$update = false
|
||||
) {
|
||||
if (!$cached) {
|
||||
|
||||
@@ -99,7 +99,7 @@ class File extends Base
|
||||
*/
|
||||
public function process(
|
||||
Template $_smarty_tpl,
|
||||
Cached $cached = null,
|
||||
?Cached $cached = null,
|
||||
$update = false
|
||||
) {
|
||||
$_smarty_tpl->getCached()->setValid(false);
|
||||
@@ -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);
|
||||
@@ -145,7 +143,7 @@ class File extends Base
|
||||
*
|
||||
* @param Template $_template template object
|
||||
*
|
||||
* @return string content
|
||||
* @return string|false content
|
||||
*/
|
||||
public function retrieveCachedContent(Template $_template)
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ abstract class KeyValueStore extends Base
|
||||
*/
|
||||
public function process(
|
||||
Template $_smarty_tpl,
|
||||
Cached $cached = null,
|
||||
?Cached $cached = null,
|
||||
$update = false
|
||||
) {
|
||||
if (!$cached) {
|
||||
|
||||
@@ -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) . ")";
|
||||
|
||||
@@ -24,22 +24,32 @@ class EscapeModifierCompiler extends Base {
|
||||
}
|
||||
switch ($esc_type) {
|
||||
case 'html':
|
||||
case 'force':
|
||||
// in case of auto-escaping, and without the 'force' option, no double-escaping
|
||||
if ($compiler->getSmarty()->escape_html && $esc_type != 'force')
|
||||
return $params[0];
|
||||
// otherwise, escape the variable
|
||||
return 'htmlspecialchars((string)' . $params[ 0 ] . ', ENT_QUOTES, ' . var_export($char_set, true) . ', ' .
|
||||
var_export($double_encode, true) . ')';
|
||||
// no break
|
||||
case 'htmlall':
|
||||
$compiler->setRawOutput(true);
|
||||
return 'htmlentities(mb_convert_encoding((string)' . $params[ 0 ] . ', \'UTF-8\', ' .
|
||||
var_export($char_set, true) . '), ENT_QUOTES, \'UTF-8\', ' .
|
||||
var_export($double_encode, true) . ')';
|
||||
// no break
|
||||
case 'url':
|
||||
$compiler->setRawOutput(true);
|
||||
return 'rawurlencode((string)' . $params[ 0 ] . ')';
|
||||
case 'urlpathinfo':
|
||||
$compiler->setRawOutput(true);
|
||||
return 'str_replace("%2F", "/", rawurlencode((string)' . $params[ 0 ] . '))';
|
||||
case 'quotes':
|
||||
$compiler->setRawOutput(true);
|
||||
// escape unescaped single quotes
|
||||
return 'preg_replace("%(?<!\\\\\\\\)\'%", "\\\'", (string)' . $params[ 0 ] . ')';
|
||||
case 'javascript':
|
||||
$compiler->setRawOutput(true);
|
||||
// escape quotes and backslashes, newlines, etc.
|
||||
// see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
|
||||
return 'strtr((string)' .
|
||||
@@ -53,4 +63,4 @@ class EscapeModifierCompiler extends Base {
|
||||
}
|
||||
return '$_smarty_tpl->getSmarty()->getModifierCallback(\'escape\')(' . join(', ', $params) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Smarty\Compile\Modifier;
|
||||
|
||||
use Smarty\Exception;
|
||||
|
||||
/**
|
||||
* Smarty raw modifier plugin
|
||||
* Type: modifier
|
||||
* Name: raw
|
||||
* Purpose: when escaping is enabled by default, generates a raw output of a variable
|
||||
*
|
||||
* @author Amaury Bouchard
|
||||
*/
|
||||
|
||||
class RawModifierCompiler extends Base {
|
||||
|
||||
public function compile($params, \Smarty\Compiler\Template $compiler) {
|
||||
$compiler->setRawOutput(true);
|
||||
return ($params[0]);
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ class ModifierCompiler extends Base {
|
||||
}
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
return (string)$output;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,12 +82,13 @@ class PrintExpressionCompiler extends Base {
|
||||
$output = $compiler->compileModifier($modifierlist, $output);
|
||||
}
|
||||
|
||||
if ($compiler->getTemplate()->getSmarty()->escape_html) {
|
||||
if ($compiler->getTemplate()->getSmarty()->escape_html && !$compiler->isRawOutput()) {
|
||||
$output = "htmlspecialchars((string) ({$output}), ENT_QUOTES, '" . addslashes(\Smarty\Smarty::$_CHARSET) . "')";
|
||||
}
|
||||
|
||||
}
|
||||
$output = "<?php echo {$output};?>\n";
|
||||
$compiler->setRawOutput(false);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class ConfigLoad extends Base {
|
||||
* @var array
|
||||
* @see BasePlugin
|
||||
*/
|
||||
protected $optional_attributes = ['section', 'scope'];
|
||||
protected $optional_attributes = ['section'];
|
||||
|
||||
/**
|
||||
* Attribute definition: Overwrites base class.
|
||||
@@ -51,7 +51,7 @@ class ConfigLoad extends Base {
|
||||
* @var array
|
||||
* @see BasePlugin
|
||||
*/
|
||||
protected $option_flags = ['nocache', 'noscope'];
|
||||
protected $option_flags = [];
|
||||
|
||||
/**
|
||||
* Compiles code for the {config_load} tag
|
||||
@@ -66,9 +66,7 @@ class ConfigLoad extends Base {
|
||||
{
|
||||
// check and get attributes
|
||||
$_attr = $this->getAttributes($compiler, $args);
|
||||
if ($_attr['nocache'] === true) {
|
||||
$compiler->trigger_template_error('nocache option not allowed', null, true);
|
||||
}
|
||||
|
||||
// save possible attributes
|
||||
$conf_file = $_attr['file'];
|
||||
$section = $_attr['section'] ?? 'null';
|
||||
|
||||
@@ -32,7 +32,7 @@ class ExtendsTag extends Inheritance {
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $optional_attributes = ['extends_resource'];
|
||||
protected $optional_attributes = [];
|
||||
|
||||
/**
|
||||
* Attribute definition: Overwrites base class.
|
||||
@@ -64,29 +64,7 @@ class ExtendsTag extends Inheritance {
|
||||
}
|
||||
// add code to initialize inheritance
|
||||
$this->registerInit($compiler, true);
|
||||
$file = trim($_attr['file'], '\'"');
|
||||
if (strlen($file) > 8 && substr($file, 0, 8) === 'extends:') {
|
||||
// generate code for each template
|
||||
$files = array_reverse(explode('|', substr($file, 8)));
|
||||
$i = 0;
|
||||
foreach ($files as $file) {
|
||||
if ($file[0] === '"') {
|
||||
$file = trim($file, '".');
|
||||
} else {
|
||||
$file = "'{$file}'";
|
||||
}
|
||||
$i++;
|
||||
if ($i === count($files) && isset($_attr['extends_resource'])) {
|
||||
$this->compileEndChild($compiler);
|
||||
}
|
||||
$this->compileInclude($compiler, $file);
|
||||
}
|
||||
if (!isset($_attr['extends_resource'])) {
|
||||
$this->compileEndChild($compiler);
|
||||
}
|
||||
} else {
|
||||
$this->compileEndChild($compiler, $_attr['file']);
|
||||
}
|
||||
$this->compileEndChild($compiler, $_attr['file']);
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -106,42 +84,4 @@ class ExtendsTag extends Inheritance {
|
||||
(isset($template) ? ", {$template}, \$_smarty_current_dir" : '') . ");\n?>"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add code for including subtemplate to end of template
|
||||
*
|
||||
* @param \Smarty\Compiler\Template $compiler
|
||||
* @param string $template subtemplate name
|
||||
*
|
||||
* @throws \Smarty\CompilerException
|
||||
* @throws \Smarty\Exception
|
||||
*/
|
||||
private function compileInclude(\Smarty\Compiler\Template $compiler, $template) {
|
||||
$compiler->getParser()->template_postfix[] = new \Smarty\ParseTree\Tag(
|
||||
$compiler->getParser(),
|
||||
$compiler->compileTag(
|
||||
'include',
|
||||
[
|
||||
$template,
|
||||
['scope' => 'parent'],
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create source code for {extends} from source components array
|
||||
*
|
||||
* @param \Smarty\Template $template
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function extendsSourceArrayCode(\Smarty\Template $template) {
|
||||
$resources = [];
|
||||
foreach ($template->getSource()->components as $source) {
|
||||
$resources[] = $source->resource;
|
||||
}
|
||||
return $template->getLeftDelimiter() . 'extends file=\'extends:' . join('|', $resources) .
|
||||
'\' extends_resource=true' . $template->getRightDelimiter();
|
||||
}
|
||||
}
|
||||
|
||||
+15
-12
@@ -51,8 +51,9 @@ class ForTag extends Base {
|
||||
$var = $_statement['var'];
|
||||
$index = '';
|
||||
}
|
||||
$output .= "\$_smarty_tpl->assign($var, null);\n";
|
||||
$output .= "\$_smarty_tpl->tpl_vars[$var]->value{$index} = {$_statement['value']};\n";
|
||||
$itemVar = "\$_smarty_tpl->getVariable({$var})";
|
||||
$output .= "\$_smarty_tpl->assign($var, []);\n";
|
||||
$output .= "{$itemVar}->value{$index} = {$_statement['value']};\n";
|
||||
}
|
||||
if (is_array($_attr['var'])) {
|
||||
$var = $_attr['var']['var'];
|
||||
@@ -61,7 +62,8 @@ class ForTag extends Base {
|
||||
$var = $_attr['var'];
|
||||
$index = '';
|
||||
}
|
||||
$output .= "if ($_attr[ifexp]) {\nfor (\$_foo=true;$_attr[ifexp]; \$_smarty_tpl->tpl_vars[$var]->value{$index}$_attr[step]) {\n";
|
||||
$itemVar = "\$_smarty_tpl->getVariable({$var})";
|
||||
$output .= "if ($_attr[ifexp]) {\nfor (\$_foo=true;$_attr[ifexp]; {$itemVar}->value{$index}$_attr[step]) {\n";
|
||||
} else {
|
||||
$_statement = $_attr['start'];
|
||||
if (is_array($_statement['var'])) {
|
||||
@@ -71,21 +73,22 @@ class ForTag extends Base {
|
||||
$var = $_statement['var'];
|
||||
$index = '';
|
||||
}
|
||||
$output .= "\$_smarty_tpl->assign($var, null);";
|
||||
$itemVar = "\$_smarty_tpl->getVariable({$var})";
|
||||
$output .= "\$_smarty_tpl->assign($var, []);";
|
||||
if (isset($_attr['step'])) {
|
||||
$output .= "\$_smarty_tpl->tpl_vars[$var]->step = $_attr[step];";
|
||||
$output .= "{$itemVar}->step = $_attr[step];";
|
||||
} else {
|
||||
$output .= "\$_smarty_tpl->tpl_vars[$var]->step = 1;";
|
||||
$output .= "{$itemVar}->step = 1;";
|
||||
}
|
||||
if (isset($_attr['max'])) {
|
||||
$output .= "\$_smarty_tpl->tpl_vars[$var]->total = (int) min(ceil((\$_smarty_tpl->tpl_vars[$var]->step > 0 ? $_attr[to]+1 - ($_statement[value]) : $_statement[value]-($_attr[to])+1)/abs(\$_smarty_tpl->tpl_vars[$var]->step)),$_attr[max]);\n";
|
||||
$output .= "{$itemVar}->total = (int) min(ceil(({$itemVar}->step > 0 ? $_attr[to]+1 - ($_statement[value]) : $_statement[value]-($_attr[to])+1)/abs({$itemVar}->step)),$_attr[max]);\n";
|
||||
} else {
|
||||
$output .= "\$_smarty_tpl->tpl_vars[$var]->total = (int) ceil((\$_smarty_tpl->tpl_vars[$var]->step > 0 ? $_attr[to]+1 - ($_statement[value]) : $_statement[value]-($_attr[to])+1)/abs(\$_smarty_tpl->tpl_vars[$var]->step));\n";
|
||||
$output .= "{$itemVar}->total = (int) ceil(({$itemVar}->step > 0 ? $_attr[to]+1 - ($_statement[value]) : $_statement[value]-($_attr[to])+1)/abs({$itemVar}->step));\n";
|
||||
}
|
||||
$output .= "if (\$_smarty_tpl->tpl_vars[$var]->total > 0) {\n";
|
||||
$output .= "for (\$_smarty_tpl->tpl_vars[$var]->value{$index} = $_statement[value], \$_smarty_tpl->tpl_vars[$var]->iteration = 1;\$_smarty_tpl->tpl_vars[$var]->iteration <= \$_smarty_tpl->tpl_vars[$var]->total;\$_smarty_tpl->tpl_vars[$var]->value{$index} += \$_smarty_tpl->tpl_vars[$var]->step, \$_smarty_tpl->tpl_vars[$var]->iteration++) {\n";
|
||||
$output .= "\$_smarty_tpl->tpl_vars[$var]->first = \$_smarty_tpl->tpl_vars[$var]->iteration === 1;";
|
||||
$output .= "\$_smarty_tpl->tpl_vars[$var]->last = \$_smarty_tpl->tpl_vars[$var]->iteration === \$_smarty_tpl->tpl_vars[$var]->total;";
|
||||
$output .= "if ({$itemVar}->total > 0) {\n";
|
||||
$output .= "for ({$itemVar}->value{$index} = $_statement[value], {$itemVar}->iteration = 1;{$itemVar}->iteration <= {$itemVar}->total;{$itemVar}->value{$index} += {$itemVar}->step, {$itemVar}->iteration++) {\n";
|
||||
$output .= "{$itemVar}->first = {$itemVar}->iteration === 1;";
|
||||
$output .= "{$itemVar}->last = {$itemVar}->iteration === {$itemVar}->total;";
|
||||
}
|
||||
$output .= '?>';
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class Section extends ForeachSection {
|
||||
|
||||
if ($compiler->tag_nocache) {
|
||||
// push a {nocache} tag onto the stack to prevent caching of this block
|
||||
$this->openTag('nocache');
|
||||
$this->openTag($compiler, 'nocache');
|
||||
}
|
||||
|
||||
$this->openTag($compiler, 'section', ['section', $compiler->tag_nocache]);
|
||||
|
||||
@@ -33,7 +33,7 @@ class SectionClose extends Base {
|
||||
|
||||
if ($nocache_pushed) {
|
||||
// pop the pushed virtual nocache tag
|
||||
$this->closeTag('nocache');
|
||||
$this->closeTag($compiler, 'nocache');
|
||||
}
|
||||
|
||||
$output = "<?php\n";
|
||||
|
||||
@@ -41,7 +41,7 @@ class CodeFrame
|
||||
$content = '',
|
||||
$functions = '',
|
||||
$cache = false,
|
||||
\Smarty\Compiler\Template $compiler = null
|
||||
?\Smarty\Compiler\Template $compiler = null
|
||||
) {
|
||||
// build property code
|
||||
$properties[ 'version' ] = \Smarty\Smarty::SMARTY_VERSION;
|
||||
|
||||
+66
-26
@@ -313,6 +313,12 @@ class Template extends BaseCompiler {
|
||||
*/
|
||||
private $noCacheStackDepth = 0;
|
||||
|
||||
/**
|
||||
* disabled auto-escape (when set to true, the next variable output is not auto-escaped)
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $raw_output = false;
|
||||
|
||||
/**
|
||||
* Initialize compiler
|
||||
@@ -368,7 +374,7 @@ class Template extends BaseCompiler {
|
||||
* @throws CompilerException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function compileTemplateSource(\Smarty\Template $template, \Smarty\Compiler\Template $parent_compiler = null) {
|
||||
public function compileTemplateSource(\Smarty\Template $template, ?\Smarty\Compiler\Template $parent_compiler = null) {
|
||||
try {
|
||||
// save template object in compiler class
|
||||
$this->template = $template;
|
||||
@@ -403,21 +409,37 @@ class Template extends BaseCompiler {
|
||||
}
|
||||
// get template source
|
||||
if (!empty($this->template->getSource()->components)) {
|
||||
// we have array of inheritance templates by extends: resource
|
||||
// generate corresponding source code sequence
|
||||
$_content =
|
||||
ExtendsTag::extendsSourceArrayCode($this->template);
|
||||
|
||||
$_compiled_code = '<?php $_smarty_tpl->getInheritance()->init($_smarty_tpl, true); ?>';
|
||||
|
||||
$i = 0;
|
||||
$reversed_components = array_reverse($this->template->getSource()->components);
|
||||
foreach ($reversed_components as $source) {
|
||||
$i++;
|
||||
if ($i === count($reversed_components)) {
|
||||
$_compiled_code .= '<?php $_smarty_tpl->getInheritance()->endChild($_smarty_tpl); ?>';
|
||||
}
|
||||
$_compiled_code .= $this->compileTag(
|
||||
'include',
|
||||
[
|
||||
var_export($source->resource, true),
|
||||
['scope' => 'parent'],
|
||||
]
|
||||
);
|
||||
}
|
||||
$_compiled_code = $this->smarty->runPostFilters($_compiled_code, $this->template);
|
||||
} else {
|
||||
// get template source
|
||||
$_content = $this->template->getSource()->getContent();
|
||||
$_compiled_code = $this->smarty->runPostFilters(
|
||||
$this->doCompile(
|
||||
$this->smarty->runPreFilters($_content, $this->template),
|
||||
true
|
||||
),
|
||||
$this->template
|
||||
);
|
||||
}
|
||||
$_compiled_code = $this->smarty->runPostFilters(
|
||||
$this->doCompile(
|
||||
$this->smarty->runPreFilters($_content, $this->template),
|
||||
true
|
||||
),
|
||||
$this->template
|
||||
);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
if ($this->smarty->debugging) {
|
||||
$this->smarty->getDebug()->end_compile($this->template);
|
||||
@@ -483,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, '\'');
|
||||
@@ -494,7 +516,6 @@ class Template extends BaseCompiler {
|
||||
false
|
||||
)->isNocache();
|
||||
}
|
||||
return '$_smarty_tpl->getValue(' . $variable . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -643,7 +664,7 @@ class Template extends BaseCompiler {
|
||||
$script = null;
|
||||
$cacheable = true;
|
||||
|
||||
$result = call_user_func_array(
|
||||
$result = \call_user_func_array(
|
||||
$defaultPluginHandlerFunc,
|
||||
[
|
||||
$tag,
|
||||
@@ -680,7 +701,8 @@ class Template extends BaseCompiler {
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function appendCode($left, $right) {
|
||||
public function appendCode(string $left, string $right): string
|
||||
{
|
||||
if (preg_match('/\s*\?>\s?$/D', $left) && preg_match('/^<\?php\s+/', $right)) {
|
||||
$left = preg_replace('/\s*\?>\s?$/D', "\n", $left);
|
||||
$left .= preg_replace('/^<\?php\s+/', '', $right);
|
||||
@@ -1056,7 +1078,7 @@ class Template extends BaseCompiler {
|
||||
$prefixArray = array_merge($this->prefix_code, array_pop($this->prefixCodeStack));
|
||||
$this->prefixCodeStack[] = [];
|
||||
foreach ($prefixArray as $c) {
|
||||
$code = $this->appendCode($code, $c);
|
||||
$code = $this->appendCode($code, (string) $c);
|
||||
}
|
||||
$this->prefix_code = [];
|
||||
return $code;
|
||||
@@ -1124,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)]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1142,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)) {
|
||||
@@ -1258,9 +1280,10 @@ class Template extends BaseCompiler {
|
||||
}
|
||||
// call post compile callbacks
|
||||
foreach ($this->postCompileCallbacks as $cb) {
|
||||
$parameter = $cb;
|
||||
$parameter[0] = $this;
|
||||
call_user_func_array($cb[0], $parameter);
|
||||
$callbackFunction = $cb[0];
|
||||
$parameters = $cb;
|
||||
$parameters[0] = $this;
|
||||
$callbackFunction(...$parameters);
|
||||
}
|
||||
// return compiled code
|
||||
return $this->prefixCompiledCode . $this->parser->retvalue . $this->postfixCompiledCode;
|
||||
@@ -1469,4 +1492,21 @@ class Template extends BaseCompiler {
|
||||
public function getTagStack(): array {
|
||||
return $this->_tag_stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the next variable output be raw (true) or auto-escaped (false)
|
||||
* @return bool
|
||||
*/
|
||||
public function isRawOutput(): bool {
|
||||
return $this->raw_output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the next variable output be raw (true) or auto-escaped (false)
|
||||
* @param bool $raw_output
|
||||
* @return void
|
||||
*/
|
||||
public function setRawOutput(bool $raw_output): void {
|
||||
$this->raw_output = $raw_output;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ class CompilerException extends Exception {
|
||||
* @param int $code The Exception code.
|
||||
* @param string|null $filename The filename where the exception is thrown.
|
||||
* @param int|null $line The line number where the exception is thrown.
|
||||
* @param Throwable|null $previous The previous exception used for the exception chaining.
|
||||
* @param \Throwable|null $previous The previous exception used for the exception chaining.
|
||||
*/
|
||||
public function __construct(
|
||||
string $message = "",
|
||||
int $code = 0,
|
||||
?string $filename = null,
|
||||
?int $line = null,
|
||||
Throwable $previous = null
|
||||
?\Throwable $previous = null
|
||||
) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
|
||||
+1
-1
@@ -290,7 +290,7 @@ class Data
|
||||
* @return bool
|
||||
*/
|
||||
public function hasVariable($varName): bool {
|
||||
return !($this->getVariable($varName) instanceof UndefinedVariable);
|
||||
return !($this->getVariable($varName, true, false) instanceof UndefinedVariable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class CallbackWrapper {
|
||||
|
||||
public function handle(...$params) {
|
||||
try {
|
||||
return call_user_func_array($this->callback, $params);
|
||||
return ($this->callback)(...$params);
|
||||
} catch (\ArgumentCountError $e) {
|
||||
throw new Exception("Invalid number of arguments to modifier " . $this->modifierName);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class DefaultExtension extends Base {
|
||||
case 'lower': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\LowerModifierCompiler(); break;
|
||||
case 'nl2br': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\Nl2brModifierCompiler(); break;
|
||||
case 'noprint': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\NoPrintModifierCompiler(); break;
|
||||
case 'raw': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\RawModifierCompiler(); break;
|
||||
case 'round': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\RoundModifierCompiler(); break;
|
||||
case 'str_repeat': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StrRepeatModifierCompiler(); break;
|
||||
case 'string_format': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StringFormatModifierCompiler(); break;
|
||||
@@ -319,7 +320,7 @@ class DefaultExtension extends Base {
|
||||
break;
|
||||
}
|
||||
foreach ($var as $curr_key => $curr_val) {
|
||||
$results .= '<br>' . str_repeat(' ', $depth * 2) . '<b>' . strtr($curr_key, $_replace) .
|
||||
$results .= '<br>' . str_repeat(' ', $depth * 2) . '<b>' . htmlspecialchars(strtr($curr_key, $_replace)) .
|
||||
'</b> => ' .
|
||||
$this->smarty_modifier_debug_print_var($curr_val, $max, $length, ++$depth, $objects);
|
||||
$depth--;
|
||||
@@ -337,7 +338,7 @@ class DefaultExtension extends Base {
|
||||
}
|
||||
$objects[] = $var;
|
||||
foreach ($object_vars as $curr_key => $curr_val) {
|
||||
$results .= '<br>' . str_repeat(' ', $depth * 2) . '<b> ->' . strtr($curr_key, $_replace) .
|
||||
$results .= '<br>' . str_repeat(' ', $depth * 2) . '<b> ->' . htmlspecialchars(strtr($curr_key, $_replace)) .
|
||||
'</b> = ' . $this->smarty_modifier_debug_print_var($curr_val, $max, $length, ++$depth, $objects);
|
||||
$depth--;
|
||||
}
|
||||
@@ -753,4 +754,4 @@ class DefaultExtension extends Base {
|
||||
return $string;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -189,7 +189,22 @@ class Fetch extends Base {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$content = @file_get_contents($params['file']);
|
||||
if ($protocol && isset($template->getSmarty()->security_policy)) {
|
||||
// Remote resource (e.g. https://) reached through file_get_contents().
|
||||
// isTrustedUri() only validates the initial URL, but file_get_contents()
|
||||
// follows redirects by default, so an open redirect on an otherwise
|
||||
// trusted host could be used to reach a non-trusted target (SSRF).
|
||||
// Disable redirect-following while a security policy is in effect.
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'follow_location' => 0,
|
||||
'max_redirects' => 1,
|
||||
],
|
||||
]);
|
||||
$content = @file_get_contents($params['file'], false, $context);
|
||||
} else {
|
||||
$content = @file_get_contents($params['file']);
|
||||
}
|
||||
if ($content === false) {
|
||||
throw new Exception("{fetch} cannot read resource '" . $params['file'] . "'");
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class HtmlImage extends Base {
|
||||
break;
|
||||
case 'link':
|
||||
case 'href':
|
||||
$prefix = '<a href="' . $_val . '">';
|
||||
$prefix = '<a href="' . smarty_function_escape_special_chars($_val) . '">';
|
||||
$suffix = '</a>';
|
||||
break;
|
||||
default:
|
||||
@@ -143,7 +143,12 @@ class HtmlImage extends Base {
|
||||
$width = round($width * $_resize);
|
||||
$height = round($height * $_resize);
|
||||
}
|
||||
return $prefix . '<img src="' . $path_prefix . $file . '" alt="' . $alt . '" width="' . $width . '" height="' .
|
||||
$height . '"' . $extra . ' />' . $suffix;
|
||||
// $alt and the pass-through attributes ($extra) are already escaped above;
|
||||
// escape the remaining value-context params at output time so untrusted
|
||||
// values cannot break out of the attribute (CWE-79). The unescaped $file/
|
||||
// $width/$height are still used for getimagesize()/DPI math above.
|
||||
return $prefix . '<img src="' . smarty_function_escape_special_chars($path_prefix . $file) . '" alt="' . $alt
|
||||
. '" width="' . smarty_function_escape_special_chars($width) . '" height="'
|
||||
. smarty_function_escape_special_chars($height) . '"' . $extra . ' />' . $suffix;
|
||||
}
|
||||
}
|
||||
@@ -120,9 +120,6 @@ class HtmlSelectDate extends Base {
|
||||
case 'day_value_format':
|
||||
case 'month_format':
|
||||
case 'month_value_format':
|
||||
case 'day_size':
|
||||
case 'month_size':
|
||||
case 'year_size':
|
||||
case 'all_extra':
|
||||
case 'day_extra':
|
||||
case 'month_extra':
|
||||
@@ -140,6 +137,13 @@ class HtmlSelectDate extends Base {
|
||||
case 'year_id':
|
||||
$$_key = (string)$_value;
|
||||
break;
|
||||
case 'day_size':
|
||||
case 'month_size':
|
||||
case 'year_size':
|
||||
// numeric HTML size attribute; cast to int (consistent with
|
||||
// html_select_time) so it cannot break out of size="…" (CWE-79)
|
||||
$$_key = (int)$_value;
|
||||
break;
|
||||
case 'display_days':
|
||||
case 'display_months':
|
||||
case 'display_years':
|
||||
|
||||
+44
-38
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -47,18 +47,18 @@ class Dq extends Base
|
||||
if ($subtree instanceof Code) {
|
||||
$this->subtrees[ $last_subtree ]->data =
|
||||
$parser->compiler->appendCode(
|
||||
$this->subtrees[ $last_subtree ]->data,
|
||||
(string) $this->subtrees[ $last_subtree ]->data,
|
||||
'<?php echo ' . $subtree->data . ';?>'
|
||||
);
|
||||
} elseif ($subtree instanceof DqContent) {
|
||||
$this->subtrees[ $last_subtree ]->data =
|
||||
$parser->compiler->appendCode(
|
||||
$this->subtrees[ $last_subtree ]->data,
|
||||
(string) $this->subtrees[ $last_subtree ]->data,
|
||||
'<?php echo "' . $subtree->data . '";?>'
|
||||
);
|
||||
} else {
|
||||
$this->subtrees[ $last_subtree ]->data =
|
||||
$parser->compiler->appendCode($this->subtrees[ $last_subtree ]->data, $subtree->data);
|
||||
$parser->compiler->appendCode((string) $this->subtrees[ $last_subtree ]->data, (string) $subtree->data);
|
||||
}
|
||||
} else {
|
||||
$this->subtrees[] = $subtree;
|
||||
|
||||
@@ -62,9 +62,9 @@ class Tag extends Base
|
||||
public function assign_to_var(\Smarty\Parser\TemplateParser $parser)
|
||||
{
|
||||
$var = $parser->compiler->getNewPrefixVariable();
|
||||
$tmp = $parser->compiler->appendCode('<?php ob_start();?>', $this->data);
|
||||
$tmp = $parser->compiler->appendCode('<?php ob_start();?>', (string) $this->data);
|
||||
$tmp = $parser->compiler->appendCode($tmp, "<?php {$var}=ob_get_clean();?>");
|
||||
$parser->compiler->appendPrefixCode((string) $tmp);
|
||||
$parser->compiler->appendPrefixCode($tmp);
|
||||
return $var;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ class Template extends Base
|
||||
break;
|
||||
case 'tag':
|
||||
foreach ($chunk['subtrees'] as $subtree) {
|
||||
$text = $parser->compiler->appendCode($text, $subtree->to_smarty_php($parser));
|
||||
$text = $parser->compiler->appendCode($text, (string) $subtree->to_smarty_php($parser));
|
||||
}
|
||||
$code .= $text;
|
||||
break;
|
||||
|
||||
+1358
-1161
File diff suppressed because it is too large
Load Diff
+139
-33
@@ -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];
|
||||
}
|
||||
@@ -805,7 +813,7 @@ value(res) ::= varindexed(vi) DOUBLECOLON static_class_access(r). {
|
||||
// Smarty tag
|
||||
value(res) ::= smartytag(st). {
|
||||
$prefixVar = $this->compiler->getNewPrefixVariable();
|
||||
$tmp = $this->compiler->appendCode('<?php ob_start();?>', st);
|
||||
$tmp = $this->compiler->appendCode('<?php ob_start();?>', (string) st);
|
||||
$this->compiler->appendPrefixCode($this->compiler->appendCode($tmp, "<?php {$prefixVar} = ob_get_clean();?>"));
|
||||
res = $prefixVar;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ abstract class BasePlugin
|
||||
* @param Source $source source object
|
||||
* @param Template|null $_template template object
|
||||
*/
|
||||
abstract public function populate(Source $source, \Smarty\Template $_template = null);
|
||||
abstract public function populate(Source $source, ?\Smarty\Template $_template = null);
|
||||
|
||||
/**
|
||||
* populate Source Object with timestamp and exists from Resource
|
||||
|
||||
@@ -50,7 +50,7 @@ abstract class CustomPlugin extends BasePlugin {
|
||||
* @param Source $source source object
|
||||
* @param Template|null $_template template object
|
||||
*/
|
||||
public function populate(Source $source, Template $_template = null) {
|
||||
public function populate(Source $source, ?Template $_template = null) {
|
||||
$source->uid = sha1($source->type . ':' . $source->name);
|
||||
$mtime = $this->fetchTimestamp($source->name);
|
||||
if ($mtime !== null) {
|
||||
|
||||
@@ -23,7 +23,7 @@ class ExtendsPlugin extends BasePlugin
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function populate(Source $source, Template $_template = null)
|
||||
public function populate(Source $source, ?Template $_template = null)
|
||||
{
|
||||
$uid = '';
|
||||
$sources = array();
|
||||
@@ -93,7 +93,11 @@ class ExtendsPlugin extends BasePlugin
|
||||
*/
|
||||
public function getBasename(Source $source)
|
||||
{
|
||||
return str_replace(':', '.', basename($source->getResourceName()));
|
||||
$search = array(':');
|
||||
if (\Smarty\Smarty::$_IS_WINDOWS) {
|
||||
$search = array(':', '|');
|
||||
}
|
||||
return str_replace($search, '.', basename($source->getResourceName()));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -32,7 +32,7 @@ class FilePlugin extends BasePlugin {
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function populate(Source $source, Template $_template = null) {
|
||||
public function populate(Source $source, ?Template $_template = null) {
|
||||
|
||||
$source->uid = sha1(
|
||||
$source->name . ($source->isConfig ? $source->getSmarty()->_joined_config_dir :
|
||||
@@ -56,11 +56,14 @@ class FilePlugin extends BasePlugin {
|
||||
* @param Source $source source object
|
||||
*/
|
||||
public function populateTimestamp(Source $source) {
|
||||
if (!$source->exists && $path = $this->getFilePath($source->name, $source->getSmarty(), $source->isConfig)) {
|
||||
$source->timestamp = $source->exists = is_file($path);
|
||||
$path = $this->getFilePath($source->name, $source->getSmarty(), $source->isConfig);
|
||||
if (!$source->exists) {
|
||||
$source->exists = ($path !== false && is_file($path));
|
||||
}
|
||||
if ($source->exists && $path) {
|
||||
if ($source->exists && $path !== false) {
|
||||
$source->timestamp = filemtime($path);
|
||||
} else {
|
||||
$source->timestamp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class StreamPlugin extends RecompiledPlugin {
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populate(Source $source, Template $_template = null) {
|
||||
public function populate(Source $source, ?Template $_template = null) {
|
||||
$source->uid = false;
|
||||
$source->content = $this->getContent($source);
|
||||
$source->timestamp = $source->exists = !!$source->content;
|
||||
@@ -54,8 +54,19 @@ class StreamPlugin extends RecompiledPlugin {
|
||||
$filepath = str_replace(':', '://', $source->getFullResourceName());
|
||||
}
|
||||
|
||||
// Validate the underlying stream wrapper against the security policy.
|
||||
// When the built-in "stream" resource type is used (e.g.
|
||||
// stream:php://filter/...), BasePlugin::load() matches the "stream"
|
||||
// sysplugin before the stream_get_wrappers()/isTrustedStream() check,
|
||||
// so the nested wrapper ("php" here) is never validated. Parse the
|
||||
// wrapper scheme from the resolved path and check it explicitly so that
|
||||
// e.g. Security::$streams = null blocks it before fopen() (CWE-22/-441).
|
||||
$smarty = $source->getSmarty();
|
||||
if (is_object($smarty->security_policy) && ($_pos = strpos($filepath, '://')) !== false) {
|
||||
$smarty->security_policy->isTrustedStream(strtolower(substr($filepath, 0, $_pos)));
|
||||
}
|
||||
|
||||
$t = '';
|
||||
// the availability of the stream has already been checked in Smarty\Resource\Base::fetch()
|
||||
$fp = fopen($filepath, 'r+');
|
||||
if ($fp) {
|
||||
while (!feof($fp) && ($current_line = fgets($fp)) !== false) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,7 +31,7 @@ class StringPlugin extends BasePlugin {
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function populate(Source $source, Template $_template = null) {
|
||||
public function populate(Source $source, ?Template $_template = null) {
|
||||
$source->uid = sha1($source->name);
|
||||
$source->timestamp = $source->exists = true;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class DefaultPluginHandlerRuntime {
|
||||
$script = null;
|
||||
$cacheable = null;
|
||||
|
||||
return (call_user_func_array(
|
||||
return (\call_user_func_array(
|
||||
$this->defaultPluginHandler,
|
||||
[
|
||||
$tag,
|
||||
@@ -54,7 +54,7 @@ class DefaultPluginHandlerRuntime {
|
||||
$script = null;
|
||||
$cacheable = null;
|
||||
|
||||
if (call_user_func_array(
|
||||
if (\call_user_func_array(
|
||||
$this->defaultPluginHandler,
|
||||
[
|
||||
$tag,
|
||||
|
||||
@@ -116,22 +116,20 @@ class ForeachRuntime {
|
||||
*
|
||||
* @return int the count for arrays and objects that implement countable, 1 for other objects that don't, and 0
|
||||
* for empty elements
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function count($value) {
|
||||
if ($value instanceof IteratorAggregate) {
|
||||
public function count($value): int
|
||||
{
|
||||
if ($value instanceof \IteratorAggregate) {
|
||||
// Note: getIterator() returns a Traversable, not an Iterator
|
||||
// thus rewind() and valid() methods may not be present
|
||||
return iterator_count($value->getIterator());
|
||||
} elseif ($value instanceof Iterator) {
|
||||
return $value instanceof Generator ? 1 : iterator_count($value);
|
||||
} elseif ($value instanceof Countable) {
|
||||
} elseif ($value instanceof \Iterator) {
|
||||
return $value instanceof \Generator ? 1 : iterator_count($value);
|
||||
} elseif ($value instanceof \Countable) {
|
||||
return count($value);
|
||||
} elseif ($value instanceof PDOStatement) {
|
||||
return $value->rowCount();
|
||||
} elseif ($value instanceof Traversable) {
|
||||
return iterator_count($value);
|
||||
}
|
||||
return count((array)$value);
|
||||
return count((array) $value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -162,7 +162,7 @@ class InheritanceRuntime {
|
||||
private function processBlock(
|
||||
Template $tpl,
|
||||
\Smarty\Runtime\Block $block,
|
||||
\Smarty\Runtime\Block $parent = null
|
||||
?\Smarty\Runtime\Block $parent = null
|
||||
) {
|
||||
if ($block->hide && !isset($block->child)) {
|
||||
return;
|
||||
|
||||
+30
-4
@@ -52,7 +52,7 @@ class Security {
|
||||
/**
|
||||
* This is an array of trusted static classes.
|
||||
* If empty access to all static classes is allowed.
|
||||
* If set to 'none' none is allowed.
|
||||
* To disable access to all static classes set $static_classes = null.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@@ -206,7 +206,11 @@ class Security {
|
||||
* @return boolean true if class is trusted
|
||||
*/
|
||||
public function isTrustedStaticClass($class_name, $compiler) {
|
||||
if (isset($this->static_classes)
|
||||
// Only an array enables access: an empty array allows all classes, a
|
||||
// populated array is an allowlist. Any other value (null, or the
|
||||
// documented "none") denies all. Using is_array() rather than isset()
|
||||
// also avoids a PHP 8 TypeError from passing a non-array to in_array().
|
||||
if (is_array($this->static_classes)
|
||||
&& (empty($this->static_classes) || in_array($class_name, $this->static_classes))
|
||||
) {
|
||||
return true;
|
||||
@@ -474,12 +478,34 @@ class Security {
|
||||
* @throws \Smarty\Exception
|
||||
*/
|
||||
private function _checkDir($filepath, $dirs) {
|
||||
$directory = dirname($this->smarty->_realpath($filepath, true)) . DIRECTORY_SEPARATOR;
|
||||
// Resolve the canonical, symlink-free path of the requested file so that
|
||||
// a symlink located inside a trusted directory cannot be abused to read
|
||||
// a file outside of it (CWE-22 path traversal). Smarty::_realpath() only
|
||||
// normalizes the path as a string and does not follow symlinks, so we
|
||||
// fall back to it only when the file does not yet exist on disk (e.g.
|
||||
// config/cache paths that are validated before being written).
|
||||
$realpath = @realpath($filepath);
|
||||
$resolved = $realpath !== false ? $realpath : $this->smarty->_realpath($filepath, true);
|
||||
$directory = dirname($resolved) . DIRECTORY_SEPARATOR;
|
||||
|
||||
// Canonicalize the trusted directories the same way. This keeps
|
||||
// legitimate symlinked deployment paths working (e.g. a Capistrano-style
|
||||
// "current" release symlink, or macOS' /var -> /private/var): both the
|
||||
// file and the trusted directories are compared after symlinks have been
|
||||
// resolved.
|
||||
$trusted = [];
|
||||
foreach ($dirs as $dir => $unused) {
|
||||
$trusted[$dir] = true;
|
||||
if (($dirRealpath = @realpath($dir)) !== false) {
|
||||
$trusted[rtrim($dirRealpath, '\\/') . DIRECTORY_SEPARATOR] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$_directory = [];
|
||||
if (!preg_match('#[\\\\/][.][.][\\\\/]#', $directory)) {
|
||||
while (true) {
|
||||
// test if the directory is trusted
|
||||
if (isset($dirs[$directory])) {
|
||||
if (isset($trusted[$directory])) {
|
||||
return $_directory;
|
||||
}
|
||||
// abort if we've reached root
|
||||
|
||||
+27
-7
@@ -54,7 +54,7 @@ class Smarty extends \Smarty\TemplateBase {
|
||||
/**
|
||||
* smarty version
|
||||
*/
|
||||
const SMARTY_VERSION = '5.0.1';
|
||||
const SMARTY_VERSION = '5.8.4';
|
||||
|
||||
/**
|
||||
* define caching modes
|
||||
@@ -684,6 +684,21 @@ class Smarty extends \Smarty\TemplateBase {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a template directory before any existing directoires
|
||||
*
|
||||
* @param string $new_template_dir directory of template sources
|
||||
* @param bool $is_config true for config_dir
|
||||
*
|
||||
* @return static current Smarty instance for chaining
|
||||
*/
|
||||
public function prependTemplateDir($new_template_dir, $is_config = false) {
|
||||
$current_template_dirs = $is_config ? $this->config_dir : $this->template_dir;
|
||||
array_unshift($current_template_dirs, $new_template_dir);
|
||||
$this->setTemplateDir($current_template_dirs, $is_config);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add config directory(s)
|
||||
*
|
||||
@@ -1329,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') {
|
||||
@@ -1370,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2211,5 +2222,14 @@ class Smarty extends \Smarty\TemplateBase {
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if Smarty should check If-Modified-Since headers to determine cache validity.
|
||||
* @param bool $cache_modified_check
|
||||
* @return void
|
||||
*/
|
||||
public function setCacheModifiedCheck($cache_modified_check): void {
|
||||
$this->cache_modified_check = (bool) $cache_modified_check;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+9
-5
@@ -115,7 +115,7 @@ class Template extends TemplateBase {
|
||||
public function __construct(
|
||||
$template_resource,
|
||||
Smarty $smarty,
|
||||
\Smarty\Data $_parent = null,
|
||||
?\Smarty\Data $_parent = null,
|
||||
$_cache_id = null,
|
||||
$_compile_id = null,
|
||||
$_caching = null,
|
||||
@@ -248,7 +248,7 @@ class Template extends TemplateBase {
|
||||
$caching,
|
||||
$cache_lifetime,
|
||||
array $extra_vars = [],
|
||||
int $scope = null,
|
||||
?int $scope = null,
|
||||
?string $currentDir = null
|
||||
) {
|
||||
|
||||
@@ -260,7 +260,11 @@ class Template extends TemplateBase {
|
||||
|
||||
$tpl = $this->smarty->doCreateTemplate($template_name, $cache_id, $compile_id, $this, $caching, $cache_lifetime);
|
||||
|
||||
$tpl->inheritance = $this->getInheritance(); // re-use the same Inheritance object inside the inheritance tree
|
||||
// Re-use the same Inheritance object only inside an active inheritance tree, i.e. when this
|
||||
// (including) template already has one. A template outside any inheritance tree has no
|
||||
// Inheritance object (null); sub-templates it {include}s must then start with their own, so an
|
||||
// {include}d template that uses {block}/{extends} creates a fresh root via getInheritance().
|
||||
$tpl->inheritance = $this->inheritance;
|
||||
|
||||
if ($scope) {
|
||||
$tpl->defaultScope = $scope;
|
||||
@@ -462,7 +466,7 @@ class Template extends TemplateBase {
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createCodeFrame($content = '', $functions = '', $cache = false, \Smarty\Compiler\Template $compiler = null) {
|
||||
public function createCodeFrame($content = '', $functions = '', $cache = false, ?\Smarty\Compiler\Template $compiler = null) {
|
||||
return $this->getCodeFrameCompiler()->create($content, $functions, $cache, $compiler);
|
||||
}
|
||||
|
||||
@@ -531,7 +535,7 @@ class Template extends TemplateBase {
|
||||
*/
|
||||
public function getRightDelimiter()
|
||||
{
|
||||
return $this->right_delimiter ?? $this->getSmarty()->getRightDelimiter();;
|
||||
return $this->right_delimiter ?? $this->getSmarty()->getRightDelimiter();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -136,7 +136,7 @@ class Compiled extends GeneratedPhpFile {
|
||||
if ($this->exists && !$_smarty_tpl->getSmarty()->force_compile
|
||||
&& !($_smarty_tpl->compile_check && $_smarty_tpl->getSource()->getTimeStamp() > $this->getTimeStamp())
|
||||
) {
|
||||
$this->loadCompiledTemplate($_smarty_tpl);
|
||||
$this->loadCompiledTemplate($_smarty_tpl, false);
|
||||
}
|
||||
|
||||
if (!$this->isValid) {
|
||||
@@ -241,16 +241,17 @@ class Compiled extends GeneratedPhpFile {
|
||||
* HHVM requires a workaround because of a PHP incompatibility
|
||||
*
|
||||
* @param Template $_smarty_tpl do not change/remove variable name, is used by compiled template
|
||||
* @param bool $invalidateCachedFiles forces a revalidation of the file in opcache or apc cache (if available)
|
||||
*
|
||||
*/
|
||||
private function loadCompiledTemplate(Template $_smarty_tpl) {
|
||||
|
||||
if (function_exists('opcache_invalidate')
|
||||
&& (!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);
|
||||
private function loadCompiledTemplate(Template $_smarty_tpl, bool $invalidateCachedFiles = true) {
|
||||
|
||||
if ($invalidateCachedFiles) {
|
||||
if (function_exists('opcache_invalidate')
|
||||
&& (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1)
|
||||
) {
|
||||
opcache_invalidate($this->filepath, true);
|
||||
}
|
||||
}
|
||||
if (defined('HHVM_VERSION')) {
|
||||
eval('?>' . file_get_contents($this->filepath));
|
||||
|
||||
@@ -134,9 +134,9 @@ class Source {
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function load(
|
||||
Template $_template = null,
|
||||
Smarty $smarty = null,
|
||||
$template_resource = null
|
||||
?Template $_template = null,
|
||||
?Smarty $smarty = null,
|
||||
$template_resource = null
|
||||
) {
|
||||
if ($_template) {
|
||||
$smarty = $_template->getSmarty();
|
||||
@@ -203,7 +203,7 @@ class Source {
|
||||
*/
|
||||
public function _getDefaultTemplate($default_handler) {
|
||||
$_content = $_timestamp = null;
|
||||
$_return = call_user_func_array(
|
||||
$_return = \call_user_func_array(
|
||||
$default_handler,
|
||||
[$this->type, $this->name, &$_content, &$_timestamp, $this->smarty]
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ abstract class TemplateBase extends Data {
|
||||
* @api Smarty::createData()
|
||||
*
|
||||
*/
|
||||
public function createData(Data $parent = null, $name = null) {
|
||||
public function createData(?Data $parent = null, $name = null) {
|
||||
/* @var Smarty $smarty */
|
||||
$smarty = $this->getSmarty();
|
||||
$dataObj = new Data($parent, $smarty, $name);
|
||||
|
||||
+2
-1
@@ -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)}
|
||||
@@ -144,6 +144,7 @@
|
||||
{$vars['attributes']|debug_print_var nofilter}
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</table>
|
||||
|
||||
|
||||
@@ -15,4 +15,3 @@ if (!ini_get('date.timezone')) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
/*
|
||||
* Smarty PHPUnit Config
|
||||
*/
|
||||
define('individualFolders', true);
|
||||
define('MysqlCacheEnable', false);
|
||||
define('PdoCacheEnable', false);
|
||||
define('PdoGzipCacheEnable', false);
|
||||
|
||||
+130
-48
@@ -49,6 +49,37 @@ class PHPUnit_Smarty extends PHPUnit\Framework\TestCase
|
||||
*/
|
||||
public static $cwd = null;
|
||||
|
||||
/**
|
||||
* Temp directory base for this test class (compile, cache, templates_tmp)
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private static $tempBase = null;
|
||||
|
||||
/**
|
||||
* Unique token for the current test class's temp directory.
|
||||
* Generated once per class.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private static $tempId = null;
|
||||
|
||||
/**
|
||||
* Return the temp directory base for the current test class.
|
||||
*
|
||||
* @return string
|
||||
* @throws \LogicException If the temp directory base has not been initialized yet.
|
||||
*/
|
||||
public static function getTempBase(): string
|
||||
{
|
||||
if (self::$tempBase === null) {
|
||||
throw new \LogicException(
|
||||
'Temp directory base has not been initialized. Call setUpSmarty() before using temp-path helpers.'
|
||||
);
|
||||
}
|
||||
return self::$tempBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* PDO object for Mysql tests
|
||||
*
|
||||
@@ -64,7 +95,7 @@ class PHPUnit_Smarty extends PHPUnit\Framework\TestCase
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
error_reporting(E_ALL & ~E_STRICT & ~E_DEPRECATED & ~E_USER_DEPRECATED);
|
||||
error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
|
||||
self::$init = true;
|
||||
self::$pluginsdir =self::getSmartyPluginsDir();
|
||||
}
|
||||
@@ -75,8 +106,37 @@ class PHPUnit_Smarty extends PHPUnit\Framework\TestCase
|
||||
*/
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
//self::$pdo = null;
|
||||
self::$testNumber = 0;
|
||||
// Remove the unique temp directory for this test class unless the caller
|
||||
// wants to inspect the artifacts (e.g. for debugging a failure).
|
||||
if (!getenv('KEEP_SMARTY_TEST_ARTIFACTS') && self::$tempBase !== null && is_dir(self::$tempBase)) {
|
||||
self::removeDir(self::$tempBase);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively remove a directory, silently ignoring any errors.
|
||||
*
|
||||
* @param string $dir
|
||||
*/
|
||||
private static function removeDir(string $dir): void
|
||||
{
|
||||
$dir = rtrim($dir, DIRECTORY_SEPARATOR);
|
||||
$items = @scandir($dir);
|
||||
if ($items === false) {
|
||||
return;
|
||||
}
|
||||
foreach ($items as $item) {
|
||||
if ($item === '.' || $item === '..') {
|
||||
continue;
|
||||
}
|
||||
$path = $dir . DIRECTORY_SEPARATOR . $item;
|
||||
if (is_dir($path) && !is_link($path)) {
|
||||
self::removeDir($path);
|
||||
} else {
|
||||
@unlink($path);
|
||||
}
|
||||
}
|
||||
@rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,12 +149,46 @@ class PHPUnit_Smarty extends PHPUnit\Framework\TestCase
|
||||
public function __construct($name = null, array $data = array(), $dataName = '')
|
||||
{
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
if (!defined('individualFolders')) {
|
||||
define('individualFolders', true);
|
||||
}
|
||||
parent::__construct($name, $data, $dataName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the temp directory base for a given test directory.
|
||||
*
|
||||
* Returns a path unique to this test class run under sys_get_temp_dir(),
|
||||
* so that concurrent or sequential runs of different test classes never
|
||||
* share compiled/cached output. The unique token is generated once per
|
||||
* class lifetime and reset in tearDownAfterClass().
|
||||
*
|
||||
* Example:
|
||||
* /path/to/smarty/tests/UnitTests/TagTests/If
|
||||
* → /tmp/smarty-tests/UnitTests/TagTests/If/<unique-id>/
|
||||
*
|
||||
* @param string $dir absolute test directory
|
||||
* @return string absolute temp base directory (with trailing separator)
|
||||
*/
|
||||
private static function getTempDir($dir)
|
||||
{
|
||||
// Lazily generate a unique token for this test class.
|
||||
if (self::$tempId === null) {
|
||||
self::$tempId = uniqid('', true);
|
||||
}
|
||||
$testsRoot = realpath(__DIR__);
|
||||
$realDir = realpath($dir) ?: $dir;
|
||||
// compute relative path from tests/ root
|
||||
if (strpos($realDir, $testsRoot) === 0) {
|
||||
$relative = substr($realDir, strlen($testsRoot));
|
||||
} else {
|
||||
// fallback: use full path hash
|
||||
$relative = DIRECTORY_SEPARATOR . md5($realDir);
|
||||
}
|
||||
return rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR)
|
||||
. DIRECTORY_SEPARATOR . 'smarty-tests'
|
||||
. $relative
|
||||
. DIRECTORY_SEPARATOR . self::$tempId
|
||||
. DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup Smarty instance called for each test
|
||||
*
|
||||
@@ -102,47 +196,37 @@ class PHPUnit_Smarty extends PHPUnit\Framework\TestCase
|
||||
*/
|
||||
public function setUpSmarty($dir)
|
||||
{
|
||||
static $s_dir;
|
||||
// set up current working directory
|
||||
chdir($dir);
|
||||
self::$cwd = getcwd();
|
||||
// compute temp base for this test directory
|
||||
self::$tempBase = self::getTempDir($dir);
|
||||
// create missing folders for test
|
||||
if (self::$init) {
|
||||
if (!is_dir($dir . '/templates')) {
|
||||
mkdir($dir . '/templates');
|
||||
if (!is_dir(self::$tempBase . 'templates_c')) {
|
||||
mkdir(self::$tempBase . 'templates_c', 0775, true);
|
||||
}
|
||||
if (!is_dir($dir . '/configs')) {
|
||||
mkdir($dir . '/configs');
|
||||
}
|
||||
if (individualFolders != 'true') {
|
||||
if (!isset($s_dir[ $dir ])) {
|
||||
$this->cleanDir($dir . '/templates_c');
|
||||
$this->cleanDir($dir . '/cache');
|
||||
if (is_dir($dir . '/templates_tmp')) {
|
||||
$this->cleanDir($dir . '/templates_tmp');
|
||||
}
|
||||
$s_dir[ $dir ] = true;
|
||||
}
|
||||
$dir = __DIR__;
|
||||
}
|
||||
if (!is_dir($dir . '/templates_c')) {
|
||||
mkdir($dir . '/templates_c');
|
||||
}
|
||||
chmod($dir . '/templates_c', 0775);
|
||||
if (!is_dir($dir . '/cache')) {
|
||||
mkdir($dir . '/cache');
|
||||
chmod($dir . '/cache', 0775);
|
||||
if (!is_dir(self::$tempBase . 'cache')) {
|
||||
mkdir(self::$tempBase . 'cache', 0775, true);
|
||||
}
|
||||
if (!is_dir(self::$tempBase . 'templates_tmp')) {
|
||||
mkdir(self::$tempBase . 'templates_tmp', 0775, true);
|
||||
}
|
||||
self::$init = false;
|
||||
}
|
||||
clearstatcache();
|
||||
|
||||
// instance Smarty class
|
||||
$this->smarty = new \Smarty\Smarty();
|
||||
if (individualFolders != 'true') {
|
||||
$this->smarty->setCompileDir(__DIR__ . '/templates_c');
|
||||
$this->smarty->setCacheDir(__DIR__ . '/cache');
|
||||
$this->smarty->setCompileDir(self::getTempBase() . 'templates_c');
|
||||
$this->smarty->setCacheDir(self::getTempBase() . 'cache');
|
||||
$this->smarty->addTemplateDir(self::getTempBase() . 'templates_tmp');
|
||||
|
||||
// Clean output dirs once at the start of each test class run
|
||||
if (self::$testNumber === 0) {
|
||||
$this->cleanDirs();
|
||||
}
|
||||
self::$testNumber++;
|
||||
|
||||
}
|
||||
|
||||
@@ -230,24 +314,24 @@ KEY `name` (`name`)
|
||||
{
|
||||
$this->cleanCompileDir();
|
||||
$this->cleanCacheDir();
|
||||
if (is_dir(self::$cwd . '/templates_tmp')) {
|
||||
$this->cleanDir(self::$cwd . '/templates_tmp');
|
||||
$templatesTmpDir = self::getTempBase() . 'templates_tmp';
|
||||
if (is_dir($templatesTmpDir)) {
|
||||
$this->cleanDir($templatesTmpDir);
|
||||
}
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make temporary template file
|
||||
*
|
||||
*/
|
||||
public function makeTemplateFile($name, $code)
|
||||
protected function makeTemplateFile($name, $code)
|
||||
{
|
||||
if (!is_dir(self::$cwd . '/templates_tmp')) {
|
||||
mkdir(self::$cwd . '/templates_tmp');
|
||||
chmod(self::$cwd . '/templates_tmp', 0775);
|
||||
}
|
||||
$fileName = self::$cwd . '/templates_tmp/' . "{$name}";
|
||||
file_put_contents($fileName, $code);
|
||||
file_put_contents(self::getTempBase() . 'templates_tmp' . '/' . $name, $code);
|
||||
}
|
||||
|
||||
protected function removeTemplateFile($name)
|
||||
{
|
||||
unlink(self::getTempBase() . 'templates_tmp' . '/' . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,10 +375,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());
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class TemplateNormalizationTest extends PHPUnit_Smarty
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->setUpSmarty(__DIR__);
|
||||
$this->smarty->setTemplateDir(__DIR__ . '/templates');
|
||||
}
|
||||
|
||||
public function testGetTemplateDir()
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Ignore anything in here, but keep this directory
|
||||
*
|
||||
@@ -1,2 +0,0 @@
|
||||
# Ignore anything in here, but keep this directory
|
||||
*
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user