TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
@@ -0,0 +1,89 @@
.. include:: /Includes.rst.txt
.. _feature-105638-1732034075:
==============================================
Feature: #105638 - Modify fetched page content
==============================================
See :issue:`105638`
Description
===========
With :issue:`103894` the new data processor :ref:`PageContentFetchingProcessor <feature-103894-1716544976>`
has been introduced, to allow fetching page content based on the current page
layout, taking the configured :php:`SlideMode` into account.
Fetching content has previously mostly been done via the `Content` content
object. A common example looked like this:
.. code-block:: typoscript
page.20 = CONTENT
page.20 {
table = tt_content
select {
orderBy = sorting
where = colPos=0
}
}
As mentioned in the linked changelog, using the `page-content` data processor,
this can be simplified to:
.. code-block:: typoscript
page.20 = page-content
This however reduces the possibility to modify the select configuration
(SQL statement), used to define which content should be fetched, as this
is automatically handled by the data processor. However, there might be some
use cases in which the result needs to be adjusted, e.g. to hide specific
page content, like it's done by :ref:`EXT:content_blocks <friendsoftypo3/content-blocks:cb-nesting-prevent-output-fe>`
for child elements. For such use cases, the new PSR-14 :php:`AfterContentHasBeenFetchedEvent`
has been introduced, which allows to manipulate the list of fetched page
content.
The following member properties of the event object are provided:
- :php:`$groupedContent`: The fetched page content, grouped by their column - as defined in the page layout
- :php:`$request`: The current request, which can be used to e.g. access the page layout in question
Example
=======
The event listener class, using the PHP attribute :php:`#[AsEventListener]` for
registration, removes some of the fetched page content elements based on
specific field values.
.. code-block:: php
:caption: my_extension/Classes/EventListener/MyEventListener.php
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Frontend\Event\AfterContentHasBeenFetchedEvent;
final class MyEventListener
{
#[AsEventListener]
public function removeFetchedPageContent(AfterContentHasBeenFetchedEvent $event): void
{
foreach ($event->groupedContent as $columnIdentifier => $column) {
foreach ($column['records'] ?? [] as $key => $record) {
if ($record->has('parent_field_name') && (int)($record->get('parent_field_name') ?? 0) > 0) {
unset($event->groupedContent[$columnIdentifier]['records'][$key]);
}
}
}
}
}
Impact
======
Using the new PSR-14 :php:`AfterContentHasBeenFetchedEvent`, it's possible
to manipulate the page content, which has been fetched by the
:php:`PageContentFetchingProcessor`, based on the page layout and
corresponding columns configuration.
.. index:: Frontend, PHP-API, TypoScript, ext:frontend
@@ -0,0 +1,72 @@
.. include:: /Includes.rst.txt
.. _important-103140-1708522119:
=============================================================================================
Important: #103140 - Allow to configure rate limiters in Message consumer (Symfony Messenger)
=============================================================================================
See :issue:`103140`
Description
===========
This change introduces missing configuration options for Symfony Messenger-based
rate limiters.
A **rate limiter** controls how frequently a specific event (e.g., HTTP request
or login attempt) is allowed to occur. It acts as a safeguard to prevent services from
being overwhelmed — either accidentally or intentionally — thus helping
to maintain their availability.
Rate limiters are also useful for controlling internal or outbound
processes, such as limiting the simultaneous processing of messages.
More information about the rate limiter is available in the
`Symfony Rate Limiter component documentation
<https://symfony.com/doc/current/rate_limiter.html>`__.
Usage
=====
Configure a rate limiter per queue
----------------------------------
Rate limiters can be defined in your service configuration
:file:`EXT:yourext/Configuration/Services.yaml`. The name specified
in the settings is resolved to a service tagged with `messenger.rate_limiter`
and the corresponding identifier.
Example Configuration:
.. code-block:: yaml
:caption: EXT:yourext/Configuration/Services.yaml
:emphasize-lines: 10-12,23-25
messenger.rate_limiter.demo:
class: 'Symfony\Component\RateLimiter\RateLimiterFactory'
arguments:
$config:
id: 'demo'
policy: 'sliding_window'
limit: '100'
interval: '60 seconds'
$storage: '@Symfony\Component\RateLimiter\Storage\InMemoryStorage'
tags:
- name: 'messenger.rate_limiter'
identifier: 'demo'
messenger.rate_limiter.default:
class: 'Symfony\Component\RateLimiter\RateLimiterFactory'
arguments:
$config:
id: 'default'
policy: 'sliding_window'
limit: '100'
interval: '60 seconds'
$storage: '@Symfony\Component\RateLimiter\Storage\InMemoryStorage'
tags:
- name: 'messenger.rate_limiter'
identifier: 'default'
.. index:: PHP-API, ext:core
@@ -0,0 +1,24 @@
.. include:: /Includes.rst.txt
.. _important-104477-1722069728:
=========================================================================
Important: #104477 - Remove hyphen prefix from sys_log's data field entry
=========================================================================
See :issue:`104477`
Description
===========
The :php:`\TYPO3\CMS\Core\Log\Writer\DatabaseWriter` is used to write logs into
the database table `sys_log`. Additional log information data is persisted
in the field `data` and has been prefixed with a `-` until now.
As this makes it harder to parse the data, which is JSON-encoded anyway,
the prefix has been removed.
Beware that existing log entries are not migrated automatically.
This leads to a mixed structure in the database table until old records are cleaned.
(TYPO3 itself does not interpret the content of the field.)
.. index:: Database, ext:core
@@ -0,0 +1,63 @@
.. include:: /Includes.rst.txt
.. _important-105007-1728977233:
=============================================================================
Important: #105007 - Manipulation of final search query in EXT:indexed_search
=============================================================================
See :issue:`105007`
Description
===========
By removing the :typoscript:`searchSkipExtendToSubpagesChecking` option in
:issue:`97530`, there might have been performance implications for installations
with a lot of sites. This could be circumvented by adjusting the search query
manually, using available hooks. Since those hooks have also been removed with
:issue:`102937`, developers were no longer be able to handle the query
behaviour.
Therefore, the PSR-14 :php:`BeforeFinalSearchQueryIsExecutedEvent` has been
introduced which allows developers to manipulate the :php:`QueryBuilder`
instance again, just before the query gets executed.
Additional context information, provided by the new event:
* :php:`searchWords` - The corresponding search words list
* :php:`freeIndexUid` - Pointer to which indexing configuration should be searched in.
-1 means no filtering. 0 means only regular indexed content.
.. important::
The provided query (the :php:`QueryBuilder` instance) is controlled by
TYPO3 and is not considered public API. Therefore, developers using this
event need to keep track of underlying changes by TYPO3. Such changes might
be further performance improvements to the query or changes to the
database schema in general.
Example
=======
.. code-block:: php
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\IndexedSearch\Event\BeforeFinalSearchQueryIsExecutedEvent;
final readonly class EventListener
{
#[AsEventListener(identifier: 'manipulate-search-query')]
public function beforeFinalSearchQueryIsExecuted(BeforeFinalSearchQueryIsExecutedEvent $event): void
{
$event->queryBuilder->andWhere(
$event->queryBuilder->expr()->eq('some_column', 'some_value')
);
}
}
.. index:: PHP-API, ext:indexed_search
@@ -0,0 +1,353 @@
.. include:: /Includes.rst.txt
.. _important-105310-1736154830:
===================================================================
Important: #105310 - Create CHAR and BINARY as fixed-length columns
===================================================================
See :issue:`105310`
Description
===========
TYPO3 parses `ext_tables.sql` files into a Doctrine DBAL object schema to define
a virtual database scheme, enriched with :php:`DefaultTcaSchema` information for
TCA-managed tables and fields.
Fixed and variable length variants have been parsed already in the past, but missed
to flag the column as :php:`$fixed = true` for the fixed-length database field types
:sql:`CHAR` and :sql:`BINARY`. This resulted in the wrong creation of these columns as
:sql:`VARCHAR` and :sql:`VARBINARY`, which is now corrected.
+----------------+---------------------+------------------+
| ext_tables.sql | created as (before) | created as (now) |
+================+=====================+==================+
| CHAR(10) | VARCHAR(10) | CHAR(10) |
+----------------+---------------------+------------------+
| VARCHAR(10) | VARCHAR(10) | VARCHAR(10) |
+----------------+---------------------+------------------+
| BINARY(10) | VARBINARY(10) | BINARY(10) |
+----------------+---------------------+------------------+
| VARBINARY(10) | VARBINARY(10) | VARBINARY(10) |
+----------------+---------------------+------------------+
Not all database systems (RDBMS) act the same way for fixed-length columns. Implementation
differences need to be respected to ensure the same query/data behaviour across all supported
database systems.
.. warning::
Using fixed-length :sql:`CHAR` and :sql:`BINARY` column types requires to carefully work
with data being persisted and retrieved from the database due to differently
behaviour specifically of PostgreSQL.
Fixed-length :sql:`CHAR`
------------------------
**Key Difference Between CHAR and VARCHAR**
The main difference between :sql:`CHAR` and :sql:`VARCHAR` is how the database
stores character data in a database. :sql:`CHAR`, which stands for `character`,
is a fixed-length data type, meaning it always reserves a specific amount of
storage space for each value, regardless of whether the actual data occupies
that space entirely. For example, if a column is defined as :sql:`CHAR(10)` and
the word `apple` is stored inside of it, it will still occupy 10 characters worth of
space (not just 5). Unusued characters are padded with extra spaces.
On the other hand, :sql:`VARCHAR`, short for `variable character`, is a
variable-length data type. It only uses as much storage space as needed
to store the actual data without padding. So, storing the word `apple` in a
:sql:`VARCHAR(10)` column will only occupy 5 characters worth of
space, leaving the remaining table row space available for other data.
The main difference from `PostgreSQL` to `MySQL`/`MariaDB`/`SQLite` is:
`PostgreSQL` also returns the filler-spaces for a value not having the
column length (returning `apple[space][space][space][space][space]`).
On top of that, the filled-up spaces are also respected for query conditions, sorting
or data calculations (:sql:`concat()` for example). These two facts makes a huge
difference and **must** be carefully taken into account when using :sql:`CHAR`
field.
**Rule of thumb for fixed-length** :sql:`CHAR` **columns**
* Only use with **ensured fixed-length values** (so that no padding occurs).
* For 255 or more characters :sql:`VARCHAR` or :sql:`TEXT` must be used.
**More hints for fixed-length** :sql:`CHAR` **columns**
* Ensure to write fixed-length values for :sql:`CHAR` (non-space characters),
for example use hash algorithms which produce fixed-length hash identifier
values.
* Ensure to use query statements to `trim` OR `rightPad` the value within
:sql:`WHERE`, :sql:`HAVING` or :sql:`SELECT` operations, when values are
not guaranteed to contain fixed-length values.
.. tip::
Helper :php:`\TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder`
expressions can be used, for example
:php-short:`\TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder->trim()` or
:php-short:`\TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder->rightPad()` to.
* Usage of :sql:`CHAR` **must** be avoided when using the column with the
`Extbase ORM`, because fixed-value length cannot be ensured due to the
lack of using `trim/rightPad` within the ORM generated queries. Only with ensured
fixed-length values, it is usable with `Extbase ORM`.
* Cover custom queries extensively with `functional tests` executed against
all supported database platforms. Code within public extensions **should** ensure to test
queries and their operations against all officially TYPO3-supported database platforms.
Example of difference in behaviour of fixed-length :sql:`CHAR` types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: sql
:caption: Example ext_tables.sql defining a fixed-length tt_content field
CREATE TABLE `tt_content` (
`some_label` CHAR(10) DEFAULT '' NOT NULL,
);
Now, add some data. One row which fits exactly to 10 characters, and one row that only uses
6 characters:
.. code-block:: php
:caption: Adding two example rows
:emphasize-lines: 12,22
<?php
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('tt_content');
// adding a value with 10 chars
$queryBuilder->insert(
'tt_content',
[
'some_label' => 'some-label',
],
[
'some_label' => Connection::PARAM_STR,
],
);
// adding a value with only 6 chars
$queryBuilder->insert(
'tt_content',
[
'some_label' => 'label1',
],
[
'some_label' => Connection::PARAM_STR,
],
);
Now see the difference in retrieving these records:
.. code-block:: php
:caption: Get all records from table
<?php
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
->select('uid', 'some_label')
->from('tt_content')
->executeQuery()
->fetchAllAssociative();
Depending on the used database platform, the retrieved rows would contain these strings:
.. code-block:: php
:caption: Result rows MySQL, MariaDB or SQLite
:emphasize-lines: 6,10
<?php
$rows = [
[
'uid' => 1,
'some_label' => 'some-label',
],
[
'uid' => 2,
'some_label' => 'label1',
],
];
but for PostgreSQL
.. code-block:: php
:caption: Result rows with PostgreSQL
:emphasize-lines: 6,12
<?php
$rows = [
[
'uid' => 1,
'some_label' => 'some-label',
],
[
'uid' => 2,
// PostgreSQL applies the fixed length to the value directly,
// filling it up with spaces
'some_label' => 'label1 ',
],
];
or as a `diff` to make this even more visible:
.. code-block:: diff
:caption: Result rows difference between database platforms (commented)
<?php
$rows = [
[
'uid' => 1,
'some_label' => 'some-label',
],
[
'uid' => 2,
- 'some_label' => 'label1', // MySQL, MariaDB, SQLite
+ 'some_label' => 'label1 ', // PostgreSQL
],
];
.. note::
Because of this, retrieved values need to be trimmed OR padded AFTER
the query results are fetched, to ensure the same retrieved value across all
supported database systems. Another option is to ensure that the persisted
data always has a fixed-value length, like by using the aforementioned hashing
algorithms (making results not human-readable).
To raise the awareness for problems on this topic, using the trimmed value inside
a :sql:`WHERE` condition will match the record, but the returned value will be different
from the value used in the condition:
.. code-block:: php
:caption: Retrieve with trimmed value
:emphasize-lines: 14,21,22
<?php
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
->select('uid', 'some_label')
->from('tt_content')
->where(
$queryBuilder->eq(
'some_label',
$queryBuilder->createNamedParameter('label1'), // trimmed value!
),
)
->executeQuery()
->fetchAllAssociative();
// $rows contains the record for
// PostgreSQL: $rows = [['uid' => 2, 'some_label' => 'label1 ']];
// Others....: $rows = [['uid' => 2, 'some_label' => 'label1']];
.. code-block:: php
:caption: Retrieve with enforced trimmed value.
:emphasize-lines: 13-17,25,31,32,33
<?php
use Doctrine\DBAL\Platforms\TrimMode;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
->select('uid')
->addSelectLiteral(
$queryBuilder->expr()->as(
$queryBuilder->expr()->trim(
'fixed_title',
TrimMode::TRAILING,
' '
),
'fixed_title',
),
)
->from('tt_content')
->where(
$queryBuilder->eq(
'some_label',
$queryBuilder->createNamedParameter('label1'),
),
)
->executeQuery()
->fetchAllAssociative();
// $rows contains the record for
// PostgreSQL: $rows = [['uid' => 2, 'some_label' => 'label1']];
// Others....: $rows = [['uid' => 2, 'some_label' => 'label1']];
// and ensures the same content across all supported database systems.
On PostgreSQL, performing a query for a space-padded value will **not** actually
return the expected row:
.. code-block:: php
:caption: Retrieve with space-padded value for PostgreSQL does not retrieve the record
:emphasize-lines: 16,22
<?php
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
// PostgreSQL specific query!
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
->select('uid', 'some_label')
->from('tt_content')
->where(
$queryBuilder->eq(
'some_label',
$queryBuilder->createNamedParameter('label1 '), // untrimmed value!
),
)
->executeQuery()
->fetchAllAssociative();
// $rows === []
Additional :php-short:`\TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder`
methods can be used to ensure same behaviour on all platforms:
* :php-short:`\TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::trim()`
* :php-short:`\TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::rightPad()`
Recommendation
==============
:sql:`CHAR` and :sql:`BINARY` fields can be used (for storage or performance adjustments),
but only when composed data and queries take care of database-system differences.
Otherwise, the "safe bet" is to consistently utilize :sql:`VARCHAR` and :sql:`VARBINARY`
columns types.
.. index:: Database, ext:core
@@ -0,0 +1,49 @@
.. include:: /Includes.rst.txt
.. _important-105653-1732210472:
=====================================================================================
Important: #105653 - Require a template filename in extbase module template rendering
=====================================================================================
See :issue:`105653`
Description
===========
With the introduction of the FluidAdapter in TYPO3 v13, the dependency between
Fluid and Extbase has been decoupled. As part of this change, the behavior of
the :php:`ModuleTemplate::renderResponse()` and :php:`ModuleTemplate::render()`
methods has been adjusted.
The :php:`$templateFileName` argument is now mandatory for the
:php:`ModuleTemplate::renderResponse()` and :php:`ModuleTemplate::render()`
methods. Previously, if this argument was not provided, the template was
automatically resolved based on the controller and action names. Starting from
TYPO3 13.4, calling these methods with an empty string or without a valid
:php:`$templateFileName` will result in an :php:`InvalidArgumentException`.
Extensions using Extbase backend modules must explicitly provide the
:php:`$templateFileName` when calling these methods. Existing implementations
relying on automatic template resolution need to be updated to prevent
runtime errors.
**Example**:
Before:
.. code-block:: php
$moduleTemplate->renderResponse();
After:
.. code-block:: php
$moduleTemplate->renderResponse('MyController/MyAction');
Note, that it is already possible to explicitly provide the
:php:`$templateFileName` in TYPO3 12.4. It is therefore recommended to
implement the new requirement for websites using TYPO3 12.4.
.. index:: Backend, ext:backend
@@ -0,0 +1,28 @@
.. include:: /Includes.rst.txt
.. _important-105703-1742970227:
==========================================================================
Important: #105703 - Premature end of script headers due to X-TYPO3-Cache-Tags
==========================================================================
See :issue:`105703`
Description
===========
The `X-TYPO3-Cache-Tags` header is now split into multiple lines if it exceeds the maximum
of 8000 characters. This change prevents premature end of script headers and ensures
that the header is sent correctly, even if it contains a large number of cache tags.
Affected installations
----------------------
This change affects all TYPO3 installations that have `$GLOBALS['TYPO3_CONF_VARS']['FE']['debug']`
enabled and misusing the `X-TYPO3-Cache-Tags` header for anything else then debugging.
If you have a large number of cache tags, the header is now split into multiple
lines to avoid exceeding the maximum header size limit imposed by some web servers.
As this header is for debugging purposes only, this does not effect any production
environments.
.. index:: Backend, ext:core
@@ -0,0 +1,59 @@
.. include:: /Includes.rst.txt
.. _important-106401-1742479303:
============================================================================
Important: #106401 - Treat 0 as a defined value for nullable datetime fields
============================================================================
See :issue:`106401`
Description
===========
For nullable integer-based datetime fields, the value `0` now explicitly
represents the Unix epoch time (`1970-01-01T00:00:00Z`) instead of being
interpreted as an empty value by FormEngine.
Only an explicit `null` database value will be considered an empty value.
The default database schema that is generated from TCA has been adapted
to generate datetime columns with :sql:`DEFAULT NULL` instead of
:sql:`DEFAULT 0` if they have been configured to be nullable.
Given the following TCA definition:
.. code-block:: php
'columns' => [
'mydatefield' => [
'config' => [
'type' => 'datetime',
'nullable' => true,
],
],
],
The previously generated SQL statement will be changed from :sql:`DEFAULT 0` to
:sql:`DEFAULT NULL`:
.. code-block:: sql
:caption: Nullable datetime schema before this change
`mydatefield` bigint(20) DEFAULT 0
.. code-block:: sql
:caption: Nullable datetime schema after this change
`mydatefield` bigint(20) DEFAULT NULL
Fields that have not been explicitly configured to be nullable are unaffected
and will default to `0` as before.
.. index:: Backend, Database, JavaScript, ext:backend
@@ -0,0 +1,215 @@
.. include:: /Includes.rst.txt
.. _important-106467-1743452295:
==================================================================================
Important: #106467 - Align Extbase DateTime handling to FormEngine and DataHandler
==================================================================================
See :issue:`106467`
Description
===========
Extbase handling of :php:`\DateTimeInterface` domain model properties has been
aligned with the persistence and database value interpretation behavior of the
TYPO3 Core Engine (FormEngine and DataHandler).
Since this change addresses bugs and value interpretation differences that
existed since the introduction of Extbase and there are many workarounds in use,
a feature flag :php:`'extbase.consistentDateTimeHandling'` is introduced which
allows to enable the new behavior.
Existing TYPO3 v13 instances will use the old behavior by default and are
advised to enable the new feature flag via InstallTool or via:
.. code-block:: php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['extbase.consistentDateTimeHandling'] = true;
TYPO3 v14 (and new v13 instances) enable the consistent DateTime handling
by default, but the feature can still be disabled manually, if needed for the
time being.
There are four different behavioural changes that will be activated and are
explained in the following sections.
Align persistence to database to match DataHandler algorithm
------------------------------------------------------------
Use the DataHandler algorithm for the mapping of DateTime objects
to database values.
This causes non-localtime timezone offsets in :php:`\DateTime` objects
(e.g. supplied by a frontend datepicker) to be respected for native
datetime fields, like already done for integer based datetime fields.
Note that the offset is not stored as-is, but mapped to PHP localtime,
but the offset is no longer cropped off.
That means there is no need to force the server timezone on :php:`\DateTime`
objects before persisting an extbase model, since all dates will be
normalized to localtime (for native datetime fields) or UTC (for integer based
datetime fields) within the persistence layer.
Before:
.. code-block:: php
public function setDatetime(\DateTime $datetime): void
{
// Force local datetime zone in order to avoid
// cropping non localtime offsets during persistence
$datetime->setTimezone(
new\DateTimeZone(date_default_timezone_get())
);
$this->datetime = $datetime;
}
After:
.. code-block:: php
public function setDatetime(\DateTime $datetime): void
{
// No timezone enforcement needed, persistence layer will
// persist correct point in time (UTC for integer, LOCALTIME for native
// fields)
$this->datetime = $datetime;
}
Map date and datetime with named timezone instead of offset
-----------------------------------------------------------
Extbase DataMapper converts dates of integer based database fields to
:php:`\DateTime` instances that use the current server date timezone
(e.g., Europe/Berlin) and not just the time offset of the current server
timezone (e.g., +01:00).
This prevents timezone shifts when modifying the resulting :php:`\DateTime`
object across daylight saving time boundaries.
Previous workarounds that explicitly added the server timezone for properties
can be removed:
Before:
.. code-block:: php
public function getDatetime(): ?\DateTime
{
// object(DateTimeZone)#1 (2) {
// ["timezone_type"]=>
// int(1)
// ["timezone"]=>
// string(6) "+01:00"
// }
var_dump($this->datetime);
$this->datetime->setTimezone(
new\DateTimeZone(date_default_timezone_get())
);
return $this->datetime;
}
After:
.. code-block:: php
public function getDatetime(): ?\DateTime
{
// object(DateTimeZone)#2 (2) {
// ["timezone_type"]=>
// int(3)
// ["timezone"]=>
// string(13) "Europe/Berlin"
// }
var_dump($this->datetime);
// No explicit timezone needed for a proper named timezone
return $this->datetime;
}
Interpret integer based time fields as seconds without timezone offset
----------------------------------------------------------------------
The Extbase DataMapper will interpret `format=time` or `format=timesec`
datetime fields as seconds without timezone offset, like FormEngine and
DataHandler do. The database value is no longer considered as a UNIX timestamp,
but as offset from midnight mapped on 1970-01-01T00:00:00 in PHP localtime.
For european timezones where Central Europe Time (CET) was active on 1970-01-01
that means an integer field value like `7200` (=`02:00`) will be mapped to
`1970-01-01T02:00:00+01:00` instead of `1970-01-01T02:00:00+00:00` and the
:php:`DateTime::$timezone` property of the :php:`DateTime` object will be set to
the named timezone that is configured in PHP ini setting `date.timezone` instead
of UTC.
That means the datetime value can be combined with explicit dates and is always
using the server timezone.
Interpret 00:00:00 as non empty time value for nullable time properties
-----------------------------------------------------------------------
Nullable `format=time`, `format=timesec` or `dbType=time` fields can now
use 00:00:00 to represent midnight (this value has been used in
non-nullable fields to represent an empty value). The DateTime mapper now
understands this value instead of misinterpreting it as an empty value.
This behaviour could not be worked around before, that means existing
implementations do not need to change or remove workarounds, but can basically
support 00:00 as a value time field now.
Construct `format=time` and `dbType=time` properties based on 1970-01-01
------------------------------------------------------------------------
DateTime objects that map to native TIME fields or integer based fields
configured with `format=time` are now initialized with 1970-01-01 as day-part
instead of the current day which results in consistent mapped values independent
from the day where the mapping is performed.
Before:
.. code-block:: php
public function getDatetime(): ?\DateTime
{
//object(DateTime)#2 (3) {
// ["date"]=>
// string(26) "2025-04-11 11:44:00.000000"
// ["timezone_type"]=>
// int(1)
// ["timezone"]=>
// string(6) "+02:00"
//}
var_dump($this->datetime);
return $this->datetime;
}
After:
.. code-block:: php
public function getDatetime(): ?\DateTime
{
//object(DateTime)#2 (3) {
// ["date"]=>
// string(26) "1970-01-01 11:44:00.000000"
// ["timezone_type"]=>
// int(3)
// ["timezone"]=>
// string(13) "Europe/Berlin"
//}
var_dump($this->datetime);
return $this->datetime;
}
.. index:: Database, PHP-API, ext:extbase
@@ -0,0 +1,69 @@
.. include:: /Includes.rst.txt
.. _important-106494-1744372580:
=============================================================================================================================================
Important: #106494 - Adapt custom instances of AbstractFormFieldViewHelper to deal with `persistenceManager->getIdentifierByObject()` methods
=============================================================================================================================================
See :issue:`106494`
Description
===========
When dealing with relations to multilingual Extbase entities, these relations should always store
their reference to the "original" (`sys_language_uid=0`) entity, so that later on,
language record overlays can be properly applied.
For this to work in areas like persistence, internally an "identifier" is established that references
these multilingual objects like `[defaultLanguageRecordUid]_[localizedRecordUid]`.
Internally, this identifier should be converted back to only contain/reference the `defaultLanguageRecordUid`.
A bug has been fixed with #106494 to deal with this inside the `<f:form.select>` ViewHelper, which utilized
an `<option value="11_42">` (defaultLanguageRecordUid=11, localizedRecordUid=42), and when using an `<f:form>`
to edit existing records, the currently attached records would NOT get pre-selected.
When such objects with relations were persisted (in frontend management interfaces with Extbase), if the proper
option had not been selected again, the relation would get lost.
Important: Adapt custom ViewHelpers extended from AbstractFormFieldViewHelper or using persistenceManager->getIdentifierByObject()
----------------------------------------------------------------------------------------------------------------------------------
The bug has been fixed, but it is important that if third-party code created custom ViewHelpers based on
:php:`TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper`, these may need adoption too.
Instead of using code like this:
.. code-block:: php
:caption: Example ViewHelper code utilizing persistenceManager->getIdentifierByObject()
if ($this->persistenceManager->getIdentifierByObject($valueElement) !== null) {
return $this->persistenceManager->getIdentifierByObject($valueElement);
}
the code should be adopted to not rely on `getIdentifierByObject()` but instead:
.. code-block:: php
:caption: Refactored ViewHelper code preferring an object's getUid() method instead
if ($this->persistenceManager->getIdentifierByObject($valueElement) !== null) {
if ($valueElement instanceof DomainObjectInterface) {
return $valueElement->getUid() ?? $this->persistenceManager->getIdentifierByObject($valueElement);
}
return $this->persistenceManager->getIdentifierByObject($valueElement);
}
This code ensures that retrieving the relational object's UID is done with the overlaid record,
and only falls back to the full identifier, if it's not set, or not an object implementing the Extbase
DomainObjectInterface.
Also note that the abstract's method :php:`convertToPlainValue()` has been fixed to no longer return
a value of format `[defaultLanguageRecordUid]_[localizedRecordUid]` but instead always use the
original record's `->getUid()` return value (=`defaultLanguageRecordUid`).
If this method :php:`convertToPlainValue()` is used in 3rd-party code, make sure this is the
expected result, too.
.. index:: Fluid, Frontend, PHP-API, ext:extbase
@@ -0,0 +1,55 @@
.. include:: /Includes.rst.txt
.. _important-106508-1743692685:
=====================================================================================
Important: #106508 - Respect column `CHARACTER SET` and `COLLATE` in `ext_tables.sql`
=====================================================================================
See :issue:`106508`
Description
===========
TYPO3 now reads column based `CHARACTER SET` and `COLLATION` from extension
:file:`ext_tables.sql` files and applies them on column level. This allows
`CHARACTER SET` and `COLLATION` column settings different than defaults defined
on table or schema level. This is limited to `MySQL` and `MariaDB` DBMS.
.. note::
Setting different charset and collation comes with some technical impact
during query time and requires for some queries special handling, for instance
when joining field that have different charsets or collations. Setting special
charsets and collations for single columns should only be used in rare
cases. The support is `@internal` and should be used with care if at all.
For now, :sql:`CHARACTER SET ascii COLLATE ascii_bin` is used for
:sql:`sys_refindex.hash` to reduce required space for the index using
single bytes instead of multiple bytes per character.
The introduced database change is considerable non-breaking, because:
* Not applying the database changes still keeps a fully working state.
* Applying database schema change does not require data migrations.
* Targets only `MySQL` and `MariaDB`.
.. code-block:: sql
:caption: ext_tables.sql example
CREATE TABLE some_table (
col1 CHAR(10) DEFAULT '' NOT NULL CHARACTER SET ascii COLLATE ascii_bin,
col2 CHAR(10) CHARACTER SET ascii COLLATE ascii_bin DEFAULT '' NOT NULL,
col3 VARCHAR(10) DEFAULT '' NOT NULL CHARACTER SET ascii COLLATE ascii_bin,
col4 VARCHAR(10) CHARACTER SET ascii COLLATE ascii_bin DEFAULT '' NOT NULL,
col5 TEXT DEFAULT '' NOT NULL CHARACTER SET ascii COLLATE ascii_bin,
col6 TEXT CHARACTER SET ascii COLLATE ascii_bin DEFAULT '' NOT NULL,
col7 MEDIUMTEXT DEFAULT '' NOT NULL CHARACTER SET ascii COLLATE ascii_bin,
col8 MEDIUMTEXT CHARACTER SET ascii COLLATE ascii_bin DEFAULT '' NOT NULL,
col9 LONGTEXT DEFAULT '' NOT NULL CHARACTER SET ascii COLLATE ascii_bin,
col10 LONGTEXT CHARACTER SET ascii COLLATE ascii_bin DEFAULT '' NOT NULL,
);
.. index:: Database, ext:core
@@ -0,0 +1,75 @@
.. include:: /Includes.rst.txt
.. _important-106894-1750144877:
==============================================================
Important: #106894 - Site settings.yaml is now stored as a map
==============================================================
See :issue:`106894`
Description
===========
Site settings are defined as a map of keys, with a defined
type and default.
The values were previously stored as a tree representation in
:file:`settings.yaml`, e.g.:
.. code-block:: yaml
:caption: EXT:my_extension/Configuration/Sets/MySet/settings.yaml
foo:
bar: 'value'
This tree representation is easier to write, but has the
drawback that arbitrary keys like `foo.bar` and `foo.bar.baz`
exclude each other, as the subkey `baz` would be represented
as a value of `foo.bar` in tree representation.
Note that TypoScript constants can express subkey constants since
TypoScript can store a value and childnodes for every node, which
means that existing extensions that migrate to site sets require
this mixture of setting identifiers to be supported in order to
avoid breaking existing settings.
The storage format of settings.yaml is now changed to use
a map (like settings.definitions.yaml already do) to store
setting values, in order to overcome the mentioned limitation.
It is still supported to *read* from a tree, but the settings editor
will convert the tree to a map when persisting values.
Given the following setting definition:
.. code-block:: yaml
:caption: EXT:my_extension/Configuration/Sets/MySet/settings.definitions.yaml
settings:
foo.bar:
type: string
default: ''
label: FooBar
foo.bar.baz:
type: string
default: ''
label: FooBarBaz
A map will be stored in :file:`settings.yaml` that is able to store values for
both setting identifiers:
.. code-block:: yaml
:caption: typo3conf/sites/mysite/settings.yaml
foo.bar: 'Foo bar value'
foo.bar.baz: 'Foo Bar baz value'
Also site sets are advised to use this format for settings provided in their sets
:file:`settings.yaml` file.
Existing anonymous settings (pre v13 style, e.g. settings without a
matching settings.definitions.yaml definition) will be preserved as
a tree, since it is not known which tree node is key or a value.
.. index:: YAML, ext:core
@@ -0,0 +1,94 @@
.. include:: /Includes.rst.txt
.. _important-107062-1759872067:
===========================================================================================
Important: #107062 - Avoid applying Content-Security-Policy nonce sources when not required
===========================================================================================
See :issue:`107062`
Description
===========
Using nonce sources in a Content-Security-Policy (CSP) HTTP header implicitly leads
to having a `Cache-Control: private, no-store` HTTP response header and internally
requires to renew the nonce value that is present in cached HTML contents, which
has a negative impact on performance.
This change aims for having fully cached pages and tries to avoid nonce sources
in the CSP header when actually feasible.
* The :php:`ConsumableNonce` class was refactored it no longer extends
:php:`ConsumableString`.
* Two new counters are introduced:
:php:`consumeInline()` the nonce is **required** for an inline resource.
:php:`consumeStatic()` the nonce is **optional** for a static resource.
Example usage:
.. code-block:: php
<?php
$nonce = new ConsumableNonce();
$nonce->consumeInline(Directive::ScriptSrcElem); // inline script
$nonce->consumeStatic(Directive::StyleSrcElem); // static style
Nonce sources are removed from the CSP policy in the following
situations, in case the request is supposed to be fully cacheable
(`config.no_cache = 0` and not having any `USER_INT` or `COA_INT` items):
* The response body is readable and contains **no** bytes.
* The nonce consumption counter for **all** usages equals zero.
* A directive contains a sourcekeyword exception (e.g. `'unsafe-inline'`)
that makes a nonce unnecessary.
* The :php:`PolicyPreparedEvent` has been dispatched and explicitly tells
the policy to avoid using nonce sources.
When the nonce should be removed, both the frontend and backend
:php:`ContentSecurityPolicyHeaders` middleware strip the nonce-related
literals from the rendered HTML.
New PSR-14 event
----------------
.. code-block:: php
<?php
declare(strict_types=1);
namespace Example\MyPackage\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Disposition;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\PolicyPreparedEvent;
#[AsEventListener('my-package/content-security-policy/avoid-nonce')]
final class DropNonceEventListener
{
public function __invoke(PolicyPreparedEvent $event): void
{
$policyBag = $event->policyBag;
if (
isset($policyBag->dispositionMap[Disposition::enforce])
&& $policyBag->scope->siteIdentifier === 'my-special-site'
// YOLO: drop nonce sources, even it is consumed
&& $policyBag->nonce->count() > 0
) {
$policyBag->behavior->useNonce = false;
}
}
}
New :php:`useNonce` property
----------------------------
The :php:`\TYPO3\CMS\Core\Security\ContentSecurityPolicy\Configuration\Behavior`
class now contains the nullable boolean property :php:`useNonce`:
* :php:`true` - explicitly allows using nonce sources
* :php:`null` - the default unspecific state (the system will detect and decide automatically)
* :php:`false` - explicitly denies using nonce sources, it also drops constraints like
`'strict-dynamic'` since that source keyword requires a nonce source
.. index:: Backend, Frontend, PHP-API, ext:core
@@ -0,0 +1,78 @@
.. include:: /Includes.rst.txt
.. _important-107063-1752056295:
===========================================================================
Important: #107063 - CKEditor 5 v46.1.0: TypeScript imports and CSS changes
===========================================================================
See :issue:`107063`
Description
===========
With the upgrade to CKEditor 5 v46.1.0, three relevant changes are made:
#. The API naming of TypeScript type imports has changed a lot. Any custom
CKEditor 5 plugin using TypeScript type imports in their build chain will
need to be adapted to match these imports.
See `https://ckeditor.com/docs/ckeditor5/latest/updating/nim-migration/migrating-imports.html`_
for a large table of "before->after" renames.
This is not considered a breaking change in context of TYPO3 integration,
because existing JavaScript modules will continue to work, as TypeScript
type imports are not part of the final output. Runtime imports that are
exposed by the `@ckeditor5/ckeditor-*` modules have not been changed
and will continue to work.
#. A new opinionated default CSS is used by CKEditor to apply some
improved styling over contents displayed within the RTE interface.
Most of these are overruled by TYPO3's default CSS integration though.
Possible customizations need to respect this.
#. A few CSS classes have been renamed, see
`https://ckeditor.com/docs/ckeditor5/latest/updating/guides/update-to-46.html`_.
These are for example referenced in custom CKEditor YAML configurations like
the following diff, and need to replace the `color` subkey:
.. code-block:: diff
:caption: Configuration/RTE/Full.yaml - Before/After
- {
model: 'yellowMarker',
class: 'marker-yellow',
title: 'Yellow marker',
type: 'marker',
- color: 'var(--ck-highlight-marker-yellow)'
+ color: 'var(--ck-content--highlight-marker-yellow)'
}
- {
model: 'greenMarker',
class: 'marker-green',
title: 'Green marker',
type: 'marker',
- color: 'var(--ck-highlight-marker-green)'
+ color: 'var(--ck-content-highlight-marker-green)'
}
- {
model: 'redPen',
class: 'pen-red',
title: 'Red pen',
type: 'pen',
- color: 'var(--ck-highlight-pen-red)'
+ color: 'var(--ck-content-highlight-pen-red)'
}
Affected installations
======================
TYPO3 installation relying on custom or third-party CKEditor 5 TypeScript build chains,
or CSS adaptations that no longer match the CKEditor 5 naming.
Possible Migration
==================
Follow the CKEditor 5 upgrade guide to change CSS class names and TypeScript imports.
.. index:: Backend, RTE, NotScanned
@@ -0,0 +1,31 @@
.. include:: /Includes.rst.txt
.. _important-107342-1761324027:
===============================================================================
Important: #107342 - Extend listForms method in FormPersistenceManagerInterface
===============================================================================
See :issue:`107342`
Description
===========
With this change, the method signature of :php:`listForms()`, defined by the :php:`FormPersistenceManagerInterface`,
has been extended by two arguments: :php:`$orderField` and :php:`$orderDirection`.
The new definition is:
:php:`public function listForms(array $formSettings, string $orderField = '', ?SortDirection $orderDirection = null): array;`
Affected Installations
======================
Some TYPO3 installations may use this interface for their own FormPersistenceManager, even though it is marked as internal.
Possible Migration
==================
If you have implemented your own FormPersistenceManager, you need to update the method signature accordingly.
.. index:: Backend, ext:form, NotScanned
@@ -0,0 +1,88 @@
.. include:: /Includes.rst.txt
.. _important-107594-1759439282:
======================================================
Important: #107594 - Icon overlay for TCA select items
======================================================
See :issue:`107594`
Description
===========
The ability to define an icon overlay for items in the "New Content Element"
wizard was originally introduced in :issue:`92942` using Page TSconfig, but was
accidentally removed during the web-component migration in :issue:`100065` and
then restored in :issue:`105253`.
In the meantime, :issue:`102834` added auto-registration of wizard items
directly from TCA. Since icon overlays defined in Page TSconfig duplicate
configuration that can now be specified in TCA, the recommended approach is to
define icon overlays directly in TCA using the new :php:`iconOverlay` option
for select items.
The :php:`iconOverlay` property is now supported in the :php:`SelectItem`
component, enabling icon overlays for wizard items that are auto-registered
via TCA.
Impact
======
Icon overlays for New Content Element Wizard items can now be defined directly
in TCA alongside other item properties like :php:`icon`, :php:`label`,
:php:`description`, and :php:`group`.
This consolidates configuration in a single location and eliminates the need
for separate Page TSconfig definitions. Page TSconfig icon overlays remain
supported for backward compatibility, but TCA-based configuration is now the
recommended approach.
Migration
=========
**Previous approach using Page TSconfig (still works, but no longer recommended):**
.. code-block:: typoscript
:caption: EXT:my_extension/Configuration/page.tsconfig
mod.wizards.newContentElement.wizardItems {
my_group.elements {
my_element {
iconIdentifier = content-header
iconOverlay = actions-approve
title = LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:my_element_title
description = LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:my_element_description
tt_content_defValues {
CType = my_element
}
}
}
}
**Recommended approach using TCA:**
.. code-block:: php
:caption: EXT:my_extension/Configuration/TCA/Overrides/tt_content.php
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
ExtensionManagementUtility::addRecordType(
[
'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:my_element_title',
'description' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:my_element_description',
'value' => 'my_element',
'icon' => 'content-header',
'iconOverlay' => 'actions-approve',
'group' => 'my_group'
],
'...',
);
.. important::
While Page TSconfig-based icon overlay configuration remains functional for
backward compatibility, it is recommended to migrate to TCA-based
configuration to avoid duplicating configuration across multiple files.
.. index:: Backend, TCA, TSConfig, ext:backend
@@ -0,0 +1,39 @@
.. include:: /Includes.rst.txt
.. _important-107649-1760090777:
============================================================================
Important: #107649 - Dependency Injection cache is now PHP version dependant
============================================================================
See :issue:`107649`
Description
===========
TYPO3 uses the PHP library `symfony/dependency-injection` to build a dependency
injection container that contains class factories for services used by TYPO3 or
by installed extensions.
With the update to `symfony/dependency-injection` v7.3 which may be installed
in TYPO3 v13 composer mode the created factories are optimized to use certain
PHP language level features, if available, which result in a cache that is
incompatible when used with older PHP versions.
In a scenario where the dependency injection cache is created in a CLI PHP
process (e.g. PHP v8.4), this may result in a cache to be created that is
incompatible with a Web PHP process (e.g. PHP v8.2), if the minor versions
of the CLI and Web environments differ.
For this reason the major and minor PHP version numbers are now hashed into the
dependency injection cache identifier, resulting in a possible cache-miss on the
first web-request after a deployment, if the system was prepared via
:bash:`bin/typo3` with a CLI PHP process version that is different to the Web
PHP version.
Make sure to configure the PHP CLI process version :bash:`php -v` to use
the same version number as configured for the Web process. The Web process
version can be introspected in the backend toolbar entry
:guilabel:`System Information > PHP Version`.
.. index:: CLI, ext:core
@@ -0,0 +1,89 @@
.. include:: /Includes.rst.txt
.. _important-107681-1760427687:
=========================================================================
Important: #107681 - Disabled state for ShortcutButton and DropDownButton
=========================================================================
See :issue:`107681`
Description
===========
The :php:`ShortcutButton` and :php:`DropDownButton` classes in the TYPO3
backend button bar system have been enhanced with new methods to support
disabled state functionality. This brings them in line with other button
types that already support the disabled state.
Two new methods have been added to both classes:
- :php:`isDisabled(): bool` - Checks if the button is disabled
- :php:`setDisabled(bool $disabled)` - Sets the disabled state of the button
When a button is disabled, it is rendered with appropriate HTML attributes
and CSS classes to indicate its non-interactive state. For :php:`ShortcutButton`,
the disabled state is applied to both the simple button rendering and the
dropdown rendering modes.
Impact
======
Extension developers can now programmatically disable shortcut and dropdown
buttons in the TYPO3 backend, preventing user interaction when needed. This is
particularly useful for:
- Preventing operations during form initialization
- Disabling buttons during async operations
- Conditional button availability based on application state
- Improving user experience on slow network connections
The disabled state is properly propagated through the rendering process:
- For shortcut buttons rendered as :php:`GenericButton`, the disabled
attribute is added to the button element
- For shortcut buttons rendered as :php:`DropDownButton`, the disabled
state is passed to the dropdown button
Migration
=========
No migration is required. This change is fully backward compatible as it only
adds new optional functionality. Existing code will continue to work without
modifications.
**Example usage for disabling a shortcut button:**
.. code-block:: php
:caption: EXT:my_extension/Classes/Controller/MyController.php
$shortcutButton = $buttonBar->makeShortcutButton()
->setRouteIdentifier('my_module')
->setDisplayName('My Module')
->setArguments(['id' => $pageId])
->setDisabled(true);
$buttonBar->addButton($shortcutButton);
**Example usage for disabling a dropdown button:**
.. code-block:: php
:caption: EXT:my_extension/Classes/Controller/MyController.php
$dropdownButton = GeneralUtility::makeInstance(DropDownButton::class)
->setLabel('Actions')
->setIcon($iconFactory->getIcon('actions-menu'))
->setDisabled(true);
$dropdownButton->addItem($item1);
$dropdownButton->addItem($item2);
$buttonBar->addButton($dropdownButton);
**Example usage for checking disabled state:**
.. code-block:: php
:caption: EXT:my_extension/Classes/Controller/MyController.php
if ($shortcutButton->isDisabled()) {
// Handle disabled state
}
.. index:: Backend, PHP-API, ext:backend
@@ -0,0 +1,120 @@
.. include:: /Includes.rst.txt
.. _important-108604-1780297491:
===================================================
Important: #108604 - Mitigate deserialization flaws
===================================================
See :issue:`108604`
Description
===========
TYPO3 introduces new serialization infrastructure to protect against PHP object
injection attacks. Two complementary strategies are applied depending on how
long the serialized data lives:
**Cache frontend (**:php:`VariableFrontend`**) — HMAC-authenticated serialization**
:php:`\TYPO3\CMS\Core\Cache\Frontend\VariableFrontend` (the default cache
frontend) now uses
:php:`\TYPO3\CMS\Core\Serializer\AuthenticatedMessageDeserializer` for both
writing and reading cache entries. On every :php:`set()` call the payload is
serialized and an HMAC is appended; on every :php:`get()` call the HMAC is
validated before deserialization proceeds.
Because caches are temporary and written exclusively by the server that reads
them, this approach provides a strong integrity guarantee: an attacker cannot
craft a malicious serialized payload that the server would accept, since they
cannot forge the HMAC without knowing the encryption key.
Cache entries written by an older TYPO3 version (without an HMAC) are handled
gracefully: if the payload contains no PHP class tokens it is deserialized
safely with :php:`allowed_classes: false`; if it does contain class tokens it
is discarded and treated as a cache miss, causing the entry to be regenerated
transparently.
**Registry (**:php:`\TYPO3\CMS\Core\Registry`**) — gadget denylist**
:php:`\TYPO3\CMS\Core\Registry` (the persistent key-value store backed by the
:sql:`sys_registry` table) deserializes stored payloads through
:php:`\TYPO3\CMS\Core\Serializer\DenyListDeserializer`. Before deserialization
the class names embedded in the payload are checked against a gadget deny list.
The deny/allow decision for each class is resolved lazily via
:php:`\ReflectionClass` on first encounter and then cached in :php:`cache:core`
(HMAC-signed for integrity) so that reflection is not repeated for the same
class within a cache lifetime. A class is considered a gadget when it carries a
user-defined :php:`__destruct()` or an exploitable :php:`__wakeup()` — that is,
a :php:`__wakeup()` not provided solely by
:php:`\TYPO3\CMS\Core\Security\BlockSerializationTrait`. If any gadget class is
referenced in a payload, a
:php:`\TYPO3\CMS\Core\Serializer\Exception\DeserializerException` is thrown and
deserialization is aborted.
A denylist strategy (block known-bad, allow unknown) is used intentionally for
the registry to avoid breaking changes to long-lived persisted data.
Impact
------
**Cache (**:php:`VariableFrontend`**)**
Existing cache entries that contain serialized PHP objects will be treated as
cache misses on the next read. No exception is thrown; the entry is simply
discarded and the cache is repopulated on the next request. This is transparent
to callers.
**Registry**
Extensions or third-party code that stores serialized PHP objects in
:php:`Registry` may encounter a :php:`DeserializerException` at read time if
the serialized object graph contains a class with a user-defined
:php:`__destruct()` or :php:`__wakeup()` method.
Migration & Insights
--------------------
In most cases no migration is required. The sections below aim to provide
some insights into internal details and general suggestions.
**Cache (**:php:`VariableFrontend`**)**
No migration is required. Stale or legacy cache entries are automatically
treated as misses and regenerated. If code stores object graphs in a
:php:`VariableFrontend`-backed cache it will continue to work as long as the
server reads the entry it wrote (same encryption key).
**Registry — preferred approach: avoid object serialization**
Review what is stored in the registry. Plain PHP arrays and scalar values are
not affected by this protection and should be preferred over serialized object
graphs wherever possible.
**Registry — alternative: restructure the stored value**
If an object must be stored, ensure that neither the object itself nor any
object reachable from it through public or serialized properties carries a
user-defined :php:`__destruct()` or :php:`__wakeup()` method.
**Registry — last resort: explicit class allowlist**
If neither of the above is feasible in the short term, the affected class can be
added to the site-level allowlist in :file:`config/system/additional.php` (or
the legacy :file:`typo3conf/AdditionalConfiguration.php`):
.. code-block:: php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['deserialization']['allowedClassNames'][] =
\Vendor\MyExtension\Domain\Model\MyObject::class;
.. warning::
The allowlist setting bypasses the deserialization gadget protection for
the listed classes. It should only be used as a last resort after carefully
reviewing the class and confirming that its :php:`__destruct()` or
:php:`__wakeup()` implementation cannot be abused in a PHP object injection
attack chain. Remove the entry as soon as the underlying serialization is
refactored.
.. index:: PHP-API, LocalConfiguration, ext:core
@@ -0,0 +1,47 @@
.. include:: /Includes.rst.txt
.. _important-92187-1742812030:
========================================================================
Important: #92187 - Evaluation of incoming HTTP Header X-Forwarded-Proto
========================================================================
See :issue:`92187`
Description
===========
When running TYPO3 behind a reverse proxy, the site owner needs to set two
TYPO3 settings.
.. code-block:: php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue'] = 'first';
$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] = '{ip-of-the-reverse-proxy}';
At this point it is not known if the request between the client (the actual
web browser for example) and the reverse proxy was made via HTTP or HTTPS,
mainly because TYPO3 only evaluated the information from the reverse proxy
to TYPO3 - which was typically faked on the TYPO3's webserver by setting
"HTTPS=on" (for example via :file:`.htaccess` file). In a typical setup, the communication
between the reverse proxy and TYPO3's webserver is done via HTTP and irrelevant
for TYPO3.
When the site owner knows that the reverse proxy acts as a SSL termination point
and only communicates via https to the client, the
`$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxySSL'] <https://docs.typo3.org/permalink/t3coreapi:confval-globals-typo3-conf-vars-sys-reverseproxyprefixssl>`_ option
can be set, to identify all reverse proxy IPs that ensure a secure connection
between client and reverse proxy.
In case, it is not known, and
`reverseProxyPrefixSSL <https://docs.typo3.org/permalink/t3coreapi:confval-globals-typo3-conf-vars-sys-reverseproxyprefixssl>`_
is not in use, but
`reverseProxyIP <https://docs.typo3.org/permalink/t3coreapi:confval-globals-typo3-conf-vars-sys-reverseproxyip>`_
is in use, the incoming HTTP header `X-Forwarded-Proto` is
now evaluated to determine if the request was made, if the header is sent.
If it is **NOT** sent, TYPO3 will assume to detect a secure connection between
SSL information as before via various other HTTP Headers or server configuration
settings.
.. index:: LocalConfiguration, ext:core
+54
View File
@@ -0,0 +1,54 @@
:template: changelogOverview.html
.. include:: /Includes.rst.txt
.. _changelog-13-4-x:
==============
13.4.x Changes
==============
**Table of contents**
.. contents::
:local:
:depth: 1
Breaking Changes
================
None since TYPO3 v13.4.0 LTS release.
.. attention::
Breaking changes are not planned after the TYPO3 v13.4.0 LTS release.
Features
========
.. toctree::
:maxdepth: 1
:titlesonly:
:glob:
Feature-*
Deprecation
===========
.. toctree::
:maxdepth: 1
:titlesonly:
:glob:
Deprecation-*
Important
=========
.. toctree::
:maxdepth: 1
:titlesonly:
:glob:
Important-*