Commit Graph

7583 Commits

Author SHA1 Message Date
Fabien Potencier 2f202b006b bug #4841 Fix Markup truthiness in boolean expressions (xtrime-ru)
This PR was merged into the 3.x branch.

Discussion
----------

Fix Markup truthiness in boolean expressions

## Summary

Fixes Markup truthiness handling in boolean expressions.

`trim` can return a `Twig\Markup` instance for safe strings. Empty `Markup` objects must behave like empty strings in Twig truth tests, but PHP treats all objects as truthy. This caused expressions like this to incorrectly evaluate as true:
```twig
{% set x %}    {% endset %}
{% if x|trim and x|trim %}fail{% else %}ok{% endif %}
```

This case was working properly in https://github.com/twigphp/Twig/releases/tag/v3.14.2 and earlier.

## Related commits
- Bug was introduced in [v3.15.0](https://github.com/twigphp/Twig/releases/tag/v3.15.0) in this commit https://github.com/twigphp/Twig/commit/10c3142d3b036910f63080070c101bfff61e0743
- Partially fixed here:  https://github.com/twigphp/Twig/commit/10c3142d3b036910f63080070c101bfff61e0743

## Changes

- Added `TrueTest::wrap()` to centralize wrapping non-primitive expressions with Twig’s Markup-aware true test.
- Reused `TrueTest::wrap()` in:
  - `IfNode`
  - conditional ternary expressions
  - `and`, `or`, and `xor` binary expressions
  - Elvis expressions
  - unary `not`
- Added regression coverage for boolean operators (`and`, `or`, `xor`, `not`) and ternary/Elvis expressions whose operands evaluate to empty `Markup`.

## Tests
```bash
./vendor/bin/simple-phpunit tests/IntegrationTest.php --filter markup_test
```

Commits
-------

f5afaabf54 Fix Markup truthiness in boolean expressions
2026-06-13 12:28:15 +02:00
Fabien Potencier 8a93950693 bug #4842 Fix a PHP 8.5 chr() deprecation when decoding octal string escapes (austinderrick)
This PR was merged into the 3.x branch.

Discussion
----------

Fix a PHP 8.5 chr() deprecation when decoding octal string escapes

PHP 8.5 deprecates passing a value outside the `[0, 255]` range to `chr()`.

The string-escape decoder in `Lexer::stringEscape()` accepts up to three octal digits, so a template containing an escape such as `"\777"` (= 511) reaches `chr()` out of range and emits:

> `chr(): Providing a value not in-between 0 and 255 is deprecated, this is because a byte value must be in the [0, 255] interval. The value used will be constrained using % 256`

`chr()` already constrains the value with `% 256`, so applying `% 256` explicitly preserves the exact byte that was produced before while silencing the deprecation. The hex-escape branch is unaffected because it is capped at two digits (`\xff` = 255).

Reproducer (PHP 8.5):

```twig
{{ "\777" }}
```

Tests added to `getStringWithEscapedDelimiter()` cover `"\777"` (constrained to `0xff`) and `"\400"` (wraps to a NUL byte). The full suite passes on PHP 8.5; without this change the bridge reports the `chr()` notice as a self-deprecation.

Commits
-------

153094b601 Fix a PHP 8.5 chr() deprecation when decoding octal string escapes
2026-06-13 07:22:34 +02:00
Derrick Austin 153094b601 Fix a PHP 8.5 chr() deprecation when decoding octal string escapes
PHP 8.5 deprecates passing a value outside the [0, 255] range to chr().
The string-escape decoder in the lexer accepts up to three octal digits,
so an escape such as "\777" (= 511) reaches chr() out of range and emits:

    chr(): Providing a value not in-between 0 and 255 is deprecated ...

chr() already constrains the value with "% 256", so applying "% 256"
explicitly preserves the exact byte while silencing the deprecation. The
hex escape branch is unaffected because it is capped at two digits (0xff).
2026-06-13 07:22:26 +02:00
Alexander Pankratov f5afaabf54 Fix Markup truthiness in boolean expressions 2026-06-11 15:33:21 +02:00
Fabien Potencier 7f9714a13e feature #4292 Introduce a CorrectnessNodeVisitor to validate that templates are semantically correct (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Introduce a CorrectnessNodeVisitor to validate that templates are semantically correct

This PR addresses several issues around the correctness of templates.
Being able to parse and compile a template does not mean that it is semantically correct. To enforce correctness, we currently have several places where we deal with it:

 * `Parser::filterBodyNodes()`: This method is a mix of ensuring the correctness of a template, but it also changes the body node of a child template (something that is always needed and not part of the correctness checks)
 * `ExtendsTokenParser`: It checks that an `extend` tag is not embedded into a block or a macro. The `extend` tag is not the only one that must be at the root of a template

This PR introduces a new `CorrectnessNodeVisitor` that has the responsibility to check that a template is semantically correct. It's the continuation of work that started a long time ago in #2687 (where I mentioned the weirdness of some supported templates like those mentioned in #3926 and deprecated by this PR).

Closes #3698: Having a `use` tag embedded in another tag (like `if` in the mentioned PR) is deprecated and will not be possible in 4.0.

Commits
-------

c0504b90c5 Handle single-node child template bodies in cleanup
a69d3dc71e Keep captured block definitions supported
16e5a937ed Clarify captured block deprecation wording
8a0ae2204c Simplify correctness visitor checks
ffcae61b15 Move extends validation into correctness visitor
d96eac3895 Fix correctness visitor regressions
89e8699a73 Fix test assertions that did not verify the intended behavior
4b2e651dd5 Address review: fix block-nesting checks in CorrectnessNodeVisitor
de7bbc7be9 Move the extends-in-block and extends-in-macro errors into the CorrectnessNodeVisitor
c12100525e Introduce a CorrectnessNodeVisitor to validate that templates are semantically correct
2026-06-11 07:44:10 +02:00
Fabien Potencier c0504b90c5 Handle single-node child template bodies in cleanup 2026-06-11 07:44:00 +02:00
Fabien Potencier a69d3dc71e Keep captured block definitions supported 2026-06-11 07:43:59 +02:00
Fabien Potencier 16e5a937ed Clarify captured block deprecation wording 2026-06-11 07:43:47 +02:00
Fabien Potencier 8a0ae2204c Simplify correctness visitor checks 2026-06-11 07:43:47 +02:00
Fabien Potencier ffcae61b15 Move extends validation into correctness visitor 2026-06-11 07:43:47 +02:00
Fabien Potencier d96eac3895 Fix correctness visitor regressions 2026-06-11 07:43:46 +02:00
Fabien Potencier 89e8699a73 Fix test assertions that did not verify the intended behavior 2026-06-11 07:43:36 +02:00
Fabien Potencier 4b2e651dd5 Address review: fix block-nesting checks in CorrectnessNodeVisitor 2026-06-11 07:43:36 +02:00
Fabien Potencier de7bbc7be9 Move the extends-in-block and extends-in-macro errors into the CorrectnessNodeVisitor 2026-06-11 07:43:36 +02:00
Fabien Potencier c12100525e Introduce a CorrectnessNodeVisitor to validate that templates are semantically correct 2026-06-11 07:43:33 +02:00
Fabien Potencier 57e2f5c367 feature #4840 Mark Markup as final (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Mark Markup as final

Commits
-------

f938f6d153 Mark Markup as final
2026-06-09 21:43:35 +02:00
Fabien Potencier f938f6d153 Mark Markup as final 2026-06-07 10:29:17 +02:00
Fabien Potencier 612dc96c18 feature #4838 Allow calling a macro with a dynamic name via the dot operator (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Allow calling a macro with a dynamic name via the dot operator

Closes #4715

Commits
-------

87093aab9e Allow calling a macro with a dynamic name via the dot operator
2026-06-07 09:36:06 +02:00
Fabien Potencier 3f54383f1b bug #4835 Fix markdown_to_html mangling content that starts with a blank line (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Fix markdown_to_html mangling content that starts with a blank line

Closes #3685

Commits
-------

113aec62e7 Fix markdown_to_html mangling content that starts with a blank line
2026-06-07 09:35:24 +02:00
Fabien Potencier 87093aab9e Allow calling a macro with a dynamic name via the dot operator 2026-06-06 17:10:52 +02:00
Fabien Potencier aef7587fc8 feature #4819 Add an allow-list for tests to the sandbox security policy (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Add an allow-list for tests to the sandbox security policy

Commits
-------

416d07da1d Add an allow-list for tests to the sandbox security policy
2026-06-06 17:04:39 +02:00
Fabien Potencier 416d07da1d Add an allow-list for tests to the sandbox security policy 2026-06-06 11:08:56 +02:00
Fabien Potencier fa33c2278e minor #4837 Reduce memory usage of the context restoration compiled at the end of for loops (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Reduce memory usage of the context restoration compiled at the end of for loops

Refs #4021

I benchmarked the new version and it's 10–15% faster CPU and half the allocations.

Commits
-------

0197736dfc Reduce memory usage of the context restoration compiled at the end of for loops
2026-06-06 10:56:19 +02:00
Fabien Potencier 0197736dfc Reduce memory usage of the context restoration compiled at the end of for loops 2026-06-06 09:31:56 +02:00
Fabien Potencier 9a1d3d241f feature #4816 Add an always_allowed_in_sandbox flag for filters, functions, and tags (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Add an always_allowed_in_sandbox flag for filters, functions, and tags

Some filters, functions, and tags are pure and inherently safe to use in sandboxed templates. Forcing every sandbox policy to explicitly allow-list them is noisy.

Introduce an opt-in flag that lets callable and token-parser authors mark their item as always allowed in the sandbox. When set, the sandbox node visitor skips recording the name, so it never reaches the policy's `checkSecurity()` call: zero runtime cost, no allow-list entry required.

Commits
-------

cf4b50d181 Clarify sandbox always-allowed trust boundary
29c4325afd Bump version to 3.28.0 for the always_allowed_in_sandbox feature
1c53b790fb Add regression tests that always-allowed callables still enforce the sandbox __toString policy on arguments
7fd87a381f Document the criteria for always-allowed sandbox items and list 4.0 built-ins
2d75c87d05 Add an always_allowed_in_sandbox flag for filters, functions, and tags
2026-06-06 09:22:57 +02:00
Fabien Potencier cf4b50d181 Clarify sandbox always-allowed trust boundary 2026-06-06 09:19:48 +02:00
Fabien Potencier 29c4325afd Bump version to 3.28.0 for the always_allowed_in_sandbox feature 2026-06-06 08:56:59 +02:00
Fabien Potencier 1c53b790fb Add regression tests that always-allowed callables still enforce the sandbox __toString policy on arguments 2026-06-06 08:56:58 +02:00
Fabien Potencier 7fd87a381f Document the criteria for always-allowed sandbox items and list 4.0 built-ins 2026-06-06 08:56:58 +02:00
Fabien Potencier 2d75c87d05 Add an always_allowed_in_sandbox flag for filters, functions, and tags 2026-06-06 08:56:58 +02:00
Fabien Potencier e3c0c3fe5a feature #4834 Track the source offset of each token and expose it in syntax errors (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Track the source offset of each token and expose it in syntax errors

Commits
-------

3868bac531 Avoid allocating a normalized copy when counting newlines without carriage returns
a82782ac30 Report columns in syntax errors
4943bb405c Track the source offset of each token
2026-06-06 08:56:09 +02:00
Fabien Potencier d0cc1fcd40 Remove issue references in tests 2026-06-06 08:55:16 +02:00
Fabien Potencier 611a5d911a minor #4836 Document how to customize the markdown_to_html converter (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Document how to customize the markdown_to_html converter

Closes #3842
Closes #3211

Commits
-------

36cd35dff4 Document how to customize the markdown_to_html converter
2026-06-06 08:46:50 +02:00
Fabien Potencier 36cd35dff4 Document how to customize the markdown_to_html converter 2026-06-05 21:52:27 +02:00
Fabien Potencier 113aec62e7 Fix markdown_to_html mangling content that starts with a blank line 2026-06-05 21:47:22 +02:00
Fabien Potencier 3868bac531 Avoid allocating a normalized copy when counting newlines without carriage returns 2026-06-05 20:33:28 +02:00
Fabien Potencier a82782ac30 Report columns in syntax errors 2026-06-05 20:28:45 +02:00
Fabien Potencier 4943bb405c Track the source offset of each token 2026-06-05 20:28:45 +02:00
Fabien Potencier afad02326d minor #4662 CoreExtension::getAttribute: small improvement regarding getter/isser/hasser (gharlan)
This PR was merged into the 3.x branch.

Discussion
----------

CoreExtension::getAttribute: small improvement regarding getter/isser/hasser

For a getter method like `getFirstName` it is common to call it in twig via `person.firstName`.
But at the moment twig is adding these variants to the class method cache: `getFirstName`, `getfirstname`, `FirstName` and `firstname`.
So when resolving the name, it uses the first `elseif` here with additional `strtolower` call, because `firstName` is missing:

https://github.com/twigphp/Twig/blob/403bd9d73c2a010e5b26689f2f2eb9d7ddf391af/src/Extension/CoreExtension.php#L1863-L1867

This PR replaces `FirstName` with `firstName` in the method cache.
So `person.firstName` is resolved via first `if` branch (but `person.FirstName` would use the `elseif` with `strtolower` now).

Commits
-------

45cd6ffe80 CoreExtension::getAttribute: small improvement regarding getter/isser/hasser
2026-06-04 22:28:58 +02:00
Fabien Potencier 379df77252 bug #4825 Make the include() function return a Markup object (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Make the include() function return a Markup object

Closes #4754

Commits
-------

a3eda4b1fd Make the include() function return a Markup object
2026-06-04 21:32:19 +02:00
Fabien Potencier a3eda4b1fd Make the include() function return a Markup object 2026-06-04 21:32:12 +02:00
Fabien Potencier ecf0ebf78b bug #4830 Fix nested block() resolution when a directly rendered block calls parent() (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Fix nested block() resolution when a directly rendered block calls parent()

Closes #3321

Commits
-------

279fe13b22 Fix nested block() resolution when a directly rendered block calls parent()
2026-06-04 21:30:37 +02:00
Fabien Potencier 279fe13b22 Fix nested block() resolution when a directly rendered block calls parent() 2026-06-03 22:13:40 +02:00
Fabien Potencier 2171127676 Fix nested block() resolution when a directly rendered block calls parent() 2026-06-03 22:09:09 +02:00
Fabien Potencier a0093cd699 minor #4827 Document {#--#} as the replacement for the deprecated spaceless filter (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Document {#--#} as the replacement for the deprecated spaceless filter

Closes #4442

Commits
-------

c29877f54a Document {#--#} as the replacement for the deprecated spaceless filter
2026-06-03 22:03:07 +02:00
Fabien Potencier 1bdc760cff bug #4828 Stop reporting a skipped test in IntegrationTestCase when there is no legacy test to run (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Stop reporting a skipped test in IntegrationTestCase when there is no legacy test to run

Closes #4635

Commits
-------

86076c87a6 Stop reporting a skipped test in IntegrationTestCase when there is no legacy test to run
2026-06-03 22:02:35 +02:00
Fabien Potencier b52f6d9927 minor #4829 Document storing an enum in a variable to avoid repeating its FQCN (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Document storing an enum in a variable to avoid repeating its FQCN

Closes #4646

Commits
-------

e1178abfa1 Document storing an enum in a variable to avoid repeating its FQCN
2026-06-03 22:00:02 +02:00
Fabien Potencier e1178abfa1 Document storing an enum in a variable to avoid repeating its FQCN 2026-06-03 21:58:29 +02:00
Fabien Potencier 86076c87a6 Stop reporting a skipped test in IntegrationTestCase when there is no legacy test to run 2026-06-03 20:07:23 +02:00
Fabien Potencier c29877f54a Document {#--#} as the replacement for the deprecated spaceless filter 2026-06-03 20:00:30 +02:00