Skip to content

Backport - #1380

Closed
s2b wants to merge 64 commits into
mainfrom
backport
Closed

Backport#1380
s2b wants to merge 64 commits into
mainfrom
backport

Conversation

@s2b

@s2b s2b commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

No description provided.

s2b and others added 30 commits August 1, 2025 15:31
A code search both for "HtmlspecialcharsViewHelper" and
"htmlspecialchars" confirmed that the ViewHelper is no longer used
internally for escaping. Instead, the `EscapingNode` executes
`htmlspecialchars()` directly on the string, both in uncached and
cached context.

The `@todo` in `HtmlspecialcharsViewHelper` is removed without
changing the return type though, because this would require
a breaking change to the ViewHelper's internal behavior.
If `<f:argument>` is used with the `default` argument, it is now assumed
that the argument is optional without specifying `optional="{true}"`.

Resolves: #1149
A typed array, e.g.: string[] doesn't allow an empty array [] as value until now.

reset() returns false instead of null, that results in the array being invalid.
Backport of test adjustments in Fluid v5.
…) (#1163)

* [FEATURE] RangeViewHelper to return a range of integers as array (#1122)

The RangeViewHelper returns a sequence of integers.

The sequence is increasing if start is less than equal to end.
Otherwise, the sequence is decreasing.

step indicates by how much is the produced sequence progressed
between values of the sequence.

```xml
<f:range start="1" end="3" /> <!--  {0: 1, 1: 2, 2: 3} -->
<f:range start="3" end="1" /> <!--  {0: 3, 1: 2, 2: 1} -->
<f:range start="1" end="5" step="2" /> <!--  {0: 1, 1: 3, 2: 5} -->
```
A new `<f:length>` ViewHelper should be added to Fluid, which calculates the length of a string.

Arguments:

string $value
See also [the php mb_strlen() function](https://www.php.net/manual/en/function.mb-strlen.php).

Examples:

```
<f:variable name="length" value="{f:length(value: 'my string')}" />
<f:variable name="length" value="{myVariable -> f:length()}" />
```
There are two main syntax variants to import a ViewHelper namespace
in a template:

```
{namespace my=Vendor\MyPackage\ViewHelpers}

<html xmlns:my="http://typo3.org/ns/Vendor/MyPackage/ViewHelpers" data-namespace-typo3-fluid="true">
```

The second variant is mainly used/preferred because it provides
autocompletion for ViewHelpers in supporting IDEs. However, a
third variant is implemented, which mixes the two variants:

```
<html xmlns:my="Vendor\MyPackage\ViewHelpers" data-namespace-typo3-fluid="true">
```

This doesn't make much sense because it neither provides
autocompletion nor is it valid XML syntax because it's
not a valid URL.

This patch deprecates that syntax variant, which will no longer
work in Fluid v5.

Resolves: #1019
…1187)

Removed the `get` method entirely and enhanced `getByPath` to handle
both local variables and chain traversal.
Updated tests to cover the new behavior, improving test robustness
by adding cases for nested and array-based paths.

Resolves: #1164

Co-authored-by: Sascha Egerer <sascha.egerer@flowd.de>
Dev dependency.
This has been fixed upstream but not released, yet.

PHP:
php/php-src@5f8d648

React/promise:
reactphp/promise@d3998c8
With TYPO3 v14, there will be a deeper integration of the new
components feature into TYPO3. At that point, TYPO3-specific
documentation of components can be added to the official TYPO3
documentation.

For now, we try to cover TYPO3-specific parts in the Fluid
documentation by adding tips in the relevant places.
The ViewHelper is called with a `string`, but the argument is declared
as `integer`.
Currently, the ViewHelper validation API calls the internal ViewHelper
API to obtain its argument definitions for validation. However, the
proper way is to use the `ViewHelperResolver`, since it is possible
to redefine certain argument definitions there.

This is demonstrated and tested by the `CustomViewHelperResolver`
in the examples. However, this example only worked until now because
of the lenient validation implementation of ViewHelpers.
In #1151, it was already clarified that the `HtmlspecialcharsViewHelper`
is in fact not used internally by Fluid. This means that we can also
get rid of the custom compilation logic, which makes maintainance
unnecessarily hard.
A new <f:merge> ViewHelper is introduced to Fluid, which merges two arrays, optionally recursively.

Arguments:

array $array
array $with
bool $recursive
See also [the php array_merge()](https://www.php.net/manual/en/function.array-merge.php) and [the php array_merge_recursive()](https://www.php.net/manual/en/function.array-merge-recursive.php) functions.

Examples:

```
<f:variable name="merged" value="{f:merge(array: input, with: toMerge)}" />
<f:variable name="merged" value="{input -> f:merge(with: toMerge)}" />
```

Resolves: #1123

Co-authored-by: Michael Staatz <michael@staatzstreich.de>
Two new ViewHelpers `<f:startsWith>` and `<f:endsWith>` are added to Fluid. `<f:startsWith>` checks if the subject string starts with a specified string. `<f:endsWith>` checks if the subject string ends with a specified string. These ViewHelpers extend the AbstractConditionViewHelper.

Arguments:

string $search
string $subject
See also [the php str_starts_with()](https://www.php.net/manual/en/function.str-starts-with.php) and [the php str_ends_with()](https://www.php.net/manual/en/function.str-ends-with.php) functions.

Example:

```
<f:startsWith search="Hello" subject="Hello World">

</f:startsWith>
<f:endsWith search="World" subject="Hello World">

</f:endsWith>

<f:variable name="result" value="{myVariable -> f:startsWith(search: 'Hello')}" />
```

Resolves: #1126
Fluid v5 will have a straightforward way to warmup the cache of
Fluid templates. As preparation for that, all code related to the
old cache warmup is now deprecated.

This includes the `<f:cache.warmup>` ViewHelper, which allowed
to provide special context to templates during cache warmup.
Since all runtime dependencies of Fluid templates have been
deprecated and will no longer be an issue with Fluid v5, this
workaround will no longer be necessary.
Use alias offsetSet() instead: "The SplObjectStorage::contains(),
SplObjectStorage::attach(), and SplObjectStorage::detach() methods
have been deprecated in favour of SplObjectStorage::offsetExists(),
SplObjectStorage::offsetSet(), and SplObjectStorage::offsetUnset()
respectively."

Releases: main, 4.5
The current implementation of `AbstractViewHelper` includes
Fluid's validation logic for ViewHelper arguments in the public
method `validateArguments()`. This method is called internally
by each ViewHelper in `initializeArgumentsAndRender()`, just
before initialization and rendering of the ViewHelper. It is
currently possible to overwrite this method to implement custom
argument validation logic per ViewHelper implementation.

With Fluid v5, we want to centralize Fluid's internal validation
logic in `ViewHelperInvoker`. Thus, the existing public method
`validateArguments()` is now deprecated and will no longer be
called in Fluid v5.

However, there are some valid use cases to extend the default
validation per ViewHelper. This patch introduces a new interface
that can be used to hook into Fluid's ViewHelper rendering
process and to trigger custom validation steps after the internal
validation has been completed successfully. This should allow
users that previously overrode `validateArguments()` to
move their custom logic to the new event.
…1190)" (#1215)

This reverts commit 61477fd.

react/promise 3.3.0 release fixed the issue.
If a component or ViewHelper argument is not required and is not
supplied in the ViewHelper call, Fluid takes care of filling in the
default value. But this also happens if the argument is supplied and
is set to `NULL`. This usually happens if an undefined variable is
provided to the ViewHelper. For ViewHelpers this is done by the
`ViewHelperInvoker`, for components in
`AbstractTemplateView::processAndValidateTemplateVariables()`.

The current implementation of `StrictArgumentProcessor` prevents the
fallback from happening if the argument is supplied, but with `NULL`
as value. This patch adjusts the `StrictArgumentProcessor` to also
pass `NULL` values through.

Related: #1221
This patch addresses various deprecations that were introduced in
recent dev versions of PHP 8.5. All changes should be non-breaking
and thus can be backported to Fluid v4.

* `TemplateParser` now uses empty strings instead of `null` as array
  key for runtime cache. Public API will be adjusted in v5 only.
* `StandardVariableProvider` and `ViewHelperVariableContainer` use
  `__serialize()` instead of `__sleep()` for serialization.
* GroupedForViewHelper ensures to use an empty string instead of
  `null` for array key to avoid the deprecation and considered
  non-breaking because php converted null-index to `""` index
  anyway.
RoundViewHelper, CeilViewHelper and FloorViewHelper provide three different ways of rounding to Fluid template.

Examples

```
<f:round value="123.456" /> <!-- Outputs 123.46 -->
<f:ceil value="123.456" /> <!-- Outputs 124 -->
<f:floor value="123.456" /> <!-- Outputs 123 -->
```

This continues the work of Pullrequest:
[[FEATURE] Add f:round, f:ceil and f:floor ViewHelpers #868](#868)

Co-authored-by: Michael Staatz <michael@staatzstreich.de>
s2b and others added 29 commits November 10, 2025 15:20
This patch adds a new `<f:contains>` ViewHelper to Fluid, which allows
to check if a provided string or array contains a specified value.
Depending on the input, this mimicks PHP's `in_array()` or `str_contains()`.

The initial version of this patch is intended to be backported to Fluid 4.
In a follow-up, the code and argument types can be improved for Fluid 5.

Resolves: #1125

Co-authored-by: Michael Staatz <michael@staatzstreich.de>
The existing `BackedEnumExample` is renamed to `StringBackedEnumExample`
to also cover int-backed enums with `IntBackedEnumExample`. The
existing tests for `<f:constant>` are extended to cover all enum
variants.
…1278)

Due to the way the structure of components is extracted from template
files, it was previously not possible to use a PHP constant or enum
case as default value for a component argument.

This patch adds the `<f:constant>` ViewHelper to the list of allowed
ViewHelpers for that pre-parsing step, which makes it now possible
to use `<f:constant>` from within `<f:argument>`.
Since a Fluid namespace does not necesarily refer to an existing class
(e. g. "TYPO3Fluid\Fluid\ViewHelpers" is not a valid PHP class), the
PHPDoc annotation is removed from `getNamespace()`.

This enables comparing namespace strings with the result of
`getNamespace()` without phpstan issues (e. g. "[...] will always
evaluate to false").
`null` can safely be converted to an empty string, so it's fine to
allow it as a base string for string replacement.

Resolves: #1279
… without being escaped (#1288) (#1301)

Fluid escapes variable output by default to prevent XSS. In some cases,
however, values originate from a trusted/sanitized source (e.g. HTML
generated from a sanitizer, CMS RTE output, a Markdown renderer with
a strict allow-list, …) and should be rendered as HTML without forcing
template authors to opt out of escaping via `f:format.raw` or similar.

This change introduces a marker interface
`TYPO3Fluid\Fluid\Core\Parser\UnsafeHTML` for values that should be
rendered unescaped. Any object implementing this interface will
bypass Fluid’s escaping and will be output as-is via `__toString()`.
For now, this interface is marked as `@internal` to be able to make
adjustments within the Fluid 5 branch, if necessary.

A small helper value object `UnsafeHTMLString` is included for convenience.

Example (PHP):
````php
use TYPO3Fluid\Fluid\Core\Parser\UnsafeHTMLString;

// $safeHtml must already be sanitized/escaped appropriately
$view->assign('content', new UnsafeHTMLString($safeHtml));
````
Template:
````html
{content}
````
Technical notes:

- `EscapingNode` now detects `UnsafeHTML` and returns the value unescaped.
- The compiled escaping closure generated by the `TemplateCompiler`
  includes the same check, so compiled templates behave identically.
- Boolean expression evaluation unwraps `UnsafeHTML` to a string first,
  so empty HTML values behave like empty strings in conditions
  (e.g. `{content}` is false when it’s `''`).

Tests/examples have been adjusted accordingly.

Co-authored-by: Matthias Vogel <m.vogel@andersundsehr.com>
Co-authored-by: Elias Häußler <elias@haeussler.dev>
Fluid's `BooleanParser` attempts to cover a specific edge case: If
two objects should be compared, according to the code it should always
be a strict comparison. However, the condition that should have
accomplished this depends on the variable content during the first
run. If a template is compiled (which is almost always the case), the
comparison is still dependent on the variable content during initial
template compilation.

Above all, this special handling doesn't really make sense, since strict
comparison operators are available as well.

This patch adds test coverage for object comparisons on multiple levels.
Also, the special handling is removed from `BooleanParser` to get the
tests to pass. Without the adjustment, either uncached or cached
templates would be correct, but never both. The new version delivers
the same results as cached templates before this change, so it should
be in line with real-world projects.
…#1305) (#1326)

With #1288, the `UnsafeHTML` interface has been introduced, which prevents
Fluid's default output escaping if implemented by an object. In other Fluid
internals, objects that implement that interface are already stringified in
cases where it increases compatibility with normal strings.

The `BooleanParser` has not been adjusted yet. This patch makes the necessary
adjustments to be able to compare `UnsafeHTML` objects both with strict and
loose comparators (`==` vs. `===`). In order to achieve this, variables in
boolean expressions are now treated differently depending on the strictness
of the context (variables are not auto-converted if they should be compared
strictly).

This is a general enhancement and should not have consequences to loose
comparisons, which are commonly used in templates.

Co-authored-by: Matthias Vogel <m.vogel@andersundsehr.com>
Due to an oversight, each component call from a template created its
own instance of the responsible ViewHelperResolverDelegate. This is
not necessary, since resolver delegates are meant to be sharable.

This patch introduces the new `getResolverDelegate()` method, which
centralizes the already existing runtime cache for resolver delegate
objects. All existing usages of
`createResolverDelegateInstanceFromClassName()` are migrated to the
new method.

Note that the performance impact wasn't relevant in TYPO3, since the
underlying DI implementation already shared the service objects.
To make backports possible, compilation for Fluid cache file is now
handled within `ArgumentDefinition`. This matches the behavior of
Fluid 5, introduced in #1313.
This patch introduces new data structures that represent annotations
for ViewHelpers/Components or their individual arguments. The purpose
is to be able to attach arbitrary information to definitions of
ViewHelpers/Components or arguments, either to pass them along as-is
or to react to them within Fluid. There are places within Fluid where
something like this already exists (e. g. XSD schema), and there are
multiple additional use cases for this, both by Fluid itself and by
third-party libraries that extend Fluid, for example:

* Marking ViewHelpers/Components or individual arguments as deprecated
* Collecting information for documentation from PhpDoc comments
* Attaching additional validations/constraints to component arguments

In this first step, only the low-level API is provided, which
consists of interfaces and a generic implementation. Since this concept
is new to Fluid, the API is still classified as internal and might
change with future revisions.

Since the `ArgumentDefinition` needs to be written to Fluid's template
cache files, each annotation needs to implement the `compile()`
method, which returns the PHP code that re-creates the annotation object
when a template is read from cache.
A new interface ComponentListProviderInterface is added to Fluid,
which allows component collections to not only resolve one specific
component, but also the reverse: When implemented,
getAvailableComponents() returns a list of all components that are
part of the collection. This can be useful for various developer tools,
such as styleguides, documentation generators as well as autocompletion.

This is a partial backport from Fluid 5, which only includes the
interface, but not the default implementation.
Allows phpunit v13 when PHP version fits.

> composer req --dev friendsofphp/php-cs-fixer:^3.94.2
> composer req --dev phpstan/phpstan:^2.1.40
> composer req --dev phpstan/phpstan-phpunit:^2.0.16
> composer req --dev phpunit/phpunit:"^11.5.55 || ^12.5.14 || ^13.0.5"
> composer req --dev psr/container:^2.0.2
> composer req --dev t3docs/fluid-documentation-generator:^4.4.1
This is a partial backport of #1344. Only the exception classes are
backported, which makes it possible to use them in projects that need
to support both Fluid 4 and 5.
Some new ViewHelpers didn't yet cross-reference to the TYPO3
documentation.
All ViewHelper classes now have a `@see` annotation that points to the
ViewHelper reference documentation. Also, the `@api` annotation has been
added where it was missing.
When removing the `<html xmlns` declarations from the template, line
breaks within the `<html>` tags were also removed. This lead to invalid
line numbers reported by the template parser.

With this change, the affected `<html>` tag is replaced with the
matching number of newlines.
symfony/polyfill-php84 1.33.0->1.34.0 adds RoundingMode stub [1].

round() in PHP < 8.4 allows int as third argument only, and
has been extended with php 8.4 to allow int|RoundingMode.

The 'class_exists()' check in RoundViewHelper now suddenly
returns true when latest symfony/polyfill-php84 is loaded,
and then feeds \RoundingMode enum to round() in php < 8.4,
which fails with type error. Yay.

The patch switches to a straight version check to trigger
the PHP < 8.4 callback chain avoiding RoundingMode enum
altogether, to avoid the symfony/polyfill-php84 influence.

[1] symfony/polyfill-php84@e4e3f1c
@s2b s2b closed this Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants