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,76 @@
.. include:: /Includes.rst.txt
.. _deprecation-100887-1774712028:
======================================================================================================
Deprecation: #100887 - Deprecation of useNonce argument in f:asset:css and f:asset:script view helpers
======================================================================================================
See :issue:`100887`
Description
===========
The :html:`useNonce` argument in the :html:`f:asset.script` and
:html:`f:asset.css` ViewHelpers has been renamed to :html:`csp` to better
reflect its purpose (controlling Content-Security-Policy hash/nonce
collection rather than nonce usage specifically).
Similarly, the :php:`'useNonce'` asset option key accepted by
:php:`addJavaScript()` and :php:`addStyleSheet()` in class
:php:`\TYPO3\CMS\Core\Page\AssetCollector` has been
replaced by :php:`'csp'`.
Impact
======
Passing :html:`useNonce` as a ViewHelper argument or as an
:php-short:`\TYPO3\CMS\Core\Page\AssetCollector` option key will trigger
a deprecation-level log entry in TYPO3 v14. This usage is scheduled for
removal in TYPO3 v15.
Affected installations
======================
Installations with Fluid templates using :html:`<f:asset.script useNonce="1">`
or :html:`<f:asset.css useNonce="1">`, and extensions calling
:php:`AssetCollector::addJavaScript()` or :php:`AssetCollector::addStyleSheet()`
with :php:`['useNonce' => true]`.
Migration
=========
Replace the :html:`useNonce` argument with :html:`csp` in Fluid templates:
.. code-block:: html
<!-- Before -->
<f:asset.script identifier="my-script"
src="EXT:my_ext/Resources/Public/JavaScript/foo.js"
useNonce="1" />
<!-- After -->
<f:asset.script identifier="my-script"
src="EXT:my_ext/Resources/Public/JavaScript/foo.js"
csp="1" />
Replace the :php:`'useNonce'` option key with :php:`'csp'` in PHP:
.. code-block:: php
// Before
$assetCollector->addJavaScript('my-script', $src, [], ['useNonce' => true]);
// After
$assetCollector->addJavaScript('my-script', $src, [], ['csp' => true]);
The :php-short:`\TYPO3\CMS\Core\Page\PageRenderer` methods :php:`addJsInlineCode()`,
:php:`addJsFooterInlineCode()`, and :php:`addCssInlineBlock()` retain their
:php:`$useNonce` parameter names for backward compatibility. No migration is
required for callers of these methods.
.. index:: Fluid, PHP-API, PartiallyScanned, ext:core, ext:fluid
@@ -0,0 +1,60 @@
.. include:: /Includes.rst.txt
.. _deprecation-107068-1759214357:
==================================================================
Deprecation: #107068 - Rename fieldExplanationText to description
==================================================================
See :issue:`107068`
Description
===========
The configuration option :yaml:`fieldExplanationText` has been deprecated
in favor of :yaml:`description`. The new name better reflects its purpose
and is easier to understand.
This affects form element type definitions in
:yaml:`prototypes.*.formElementsDefinition.*.formEditor` configuration,
including editors, validators, and finishers in any extension.
Impact
======
Using :yaml:`fieldExplanationText` will trigger a PHP deprecation warning.
The migration service will automatically convert :yaml:`fieldExplanationText`
to :yaml:`description` when form configuration is loaded, ensuring
backward compatibility.
Support for :yaml:`fieldExplanationText` will be removed in TYPO3 v15.0.
Affected installations
======================
Any installations with extensions that provide custom form element type
definitions with the configuration option :yaml:`fieldExplanationText`
in their form prototype YAML files (e.g., :file:`Configuration/Form/*.yaml`
or :file:`Configuration/Yaml/FormSetup.yaml`).
Migration
=========
Rename any occurrence of :yaml:`fieldExplanationText` to :yaml:`description`
in your form element type definition YAML files (typically located in
:file:`Configuration/Yaml/FormElements/*.yaml`).
Example migration:
.. code-block:: diff
# After
formEditor:
editors:
200:
identifier: placeholder
label: Placeholder
- fieldExplanationText: Enter the placeholder text
+ description: Enter the placeholder text
.. index:: Backend, ext:form, NotScanned
@@ -0,0 +1,35 @@
.. include:: /Includes.rst.txt
.. _deprecation-107208-1754387701:
==================================================
Deprecation: #107208 - <f:debug.render> ViewHelper
==================================================
See :issue:`107208`
Description
===========
The `<f:debug.render>` ViewHelper has been deprecated. It was used internally to
render Fluid debug output for the admin panel.
Impact
======
Calling the ViewHelper from a template triggers a deprecation warning. The
ViewHelper will be removed in TYPO3 v15.
Affected installations
======================
Projects and extensions that use `<f:debug.render>` in a template.
Migration
=========
A custom ViewHelper can be created that mimics the behavior of the Core ViewHelper.
.. index:: Fluid, NotScanned, ext:fluid
@@ -0,0 +1,94 @@
.. include:: /Includes.rst.txt
.. _deprecation-107802-1770827443:
=======================================================================================================
Deprecation: #107802 - Deprecate usage of array in password for authentication in Redis session backend
=======================================================================================================
See :issue:`107802`
Description
===========
Since Redis 6.0 it is possible to authenticate against Redis using both a username and
a password. Prior to this version, authentication was only possible via password. With
this patch, you can configure the TYPO3 Redis session backend as follows:
.. code-block:: php
:caption: config/system/additional.php
use TYPO3\CMS\Core\Session\Backend\RedisSessionBackend;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['session']['BE'] = [
'backend' => RedisSessionBackend::class,
'options' => [
'database' => 0,
'hostname' => 'redis',
'port' => 6379,
'username' => 'redis',
'password' => 'redis',
]
];
Impact
======
The "password" configuration option of the Redis session backend is now typed as
:php:`array|string`. Setting this configuration option with an array is deprecated
and will be removed in 15.0.
Affected installations
======================
All installations using a Redis session backend and using the `password` configuration
option to pass an array with a username and password to it.
Migration
=========
Use the configuration options `username` and `password`.
**Before:**
.. code-block:: php
:caption: config/system/additional.php
use TYPO3\CMS\Core\Session\Backend\RedisSessionBackend;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['session']['BE'] = [
'backend' => RedisSessionBackend::class,
'options' => [
'database' => 0,
'hostname' => 'redis',
'port' => 6379,
'username' => 'redis',
'password' =>[
'user' => 'redis',
'pass' => 'redis'
]
]
];
**After:**
.. code-block:: php
:caption: config/system/additional.php
use TYPO3\CMS\Core\Session\Backend\RedisSessionBackend;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['session']['BE'] = [
'backend' => RedisSessionBackend::class,
'options' => [
'database' => 0,
'hostname' => 'redis',
'port' => 6379,
'username' => 'redis',
'password' => 'redis',
]
];
.. index:: LocalConfiguration, NotScanned, ext:core
@@ -0,0 +1,282 @@
.. include:: /Includes.rst.txt
.. _deprecation-108345-1774126701:
====================================================
Deprecation: #108345 - Deprecation of ext_emconf.php
====================================================
See :issue:`108345`
Description
===========
TYPO3 extensions that still ship an `ext_emconf.php` file
**and** do not declare future compatibility to omit this file
will now trigger a deprecation message during cache warm-up.
With TYPO3 v15 the `ext_emconf.php` file is no longer evaluated.
For TYPO3 classic (non-Composer) mode to keep working, TYPO3 then needs to
know the extension version and which `require` and `suggest` entries are not
TYPO3 extensions. The extension version and the `providesPackages` definition
therefore become mandatory fields in `composer.json`, regardless of whether an
`ext_emconf.php` file is still shipped.
In TYPO3 v14 these fields are not yet mandatory, because the deprecated
`ext_emconf.php` can still provide this information. A deprecation message is
triggered only when the `version` or `providesPackages` field is missing, so
that an extension can stay compatible with both TYPO3 v14 and v15 at the same
time.
To avoid this deprecation message, the extension must provide
the required package metadata in `composer.json`.
At minimum, this includes the extension version and the
`providesPackages` definition:
.. code-block:: json
:caption: composer.json for an extension providing Composer packages
{
"name": "vendor/example",
"type": "typo3-cms-extension",
"description": "Example extension",
"license": "GPL-2.0-or-later",
"require": {
"typo3/cms-core": "^14.2",
"vendor/other-example": "*",
"symfony/dotenv": "^8.0"
},
"extra": {
"typo3/cms": {
"extension-key": "example_extension",
"version": "1.0.0",
"Package": {
"providesPackages": {
"symfony/dotenv": "Resources/Private/Php/ComposerVendor"
}
}
}
}
}
.. code-block:: json
:caption: composer.json for an extension not providing Composer packages
{
"name": "vendor/example2",
"type": "typo3-cms-extension",
"description": "Example extension",
"license": "GPL-2.0-or-later",
"require": {
"typo3/cms-core": "^14.2"
},
"extra": {
"typo3/cms": {
"extension-key": "example2_extension",
"version": "1.0.0",
"Package": {
"providesPackages": {}
}
}
}
}
For compatibility with TYPO3 classic mode, third-party extensions
must set the exact extension version in `extra.typo3/cms.version`
or in the top level `version` field of :file:`composer.json`.
This version must match the version previously
defined in :file:`ext_emconf.php` and the released Git tag.
Fixture extensions used in tests can set any version number, for example `1.0.0`,
but a version number must still be provided to avoid deprecation messages.
During testing, the version number is not evaluated.
TYPO3 Core extensions may omit the version number
in :file:`composer.json` because their version number is derived from
:php-short:`\TYPO3\CMS\Core\Information\Typo3Version`.
State migration
---------------
The former `state` field from `ext_emconf.php` is deprecated as a source of
extension metadata and should be set in `composer.json` using
dedicated metadata instead.
Supported stability values should be expressed via the version string:
.. code-block:: json
:caption: composer.json using version stability suffixes
{
"name": "vendor/example",
"type": "typo3-cms-extension",
"description": "Example extension",
"extra": {
"typo3/cms": {
"extension-key": "example_extension",
"version": "1.2.3-beta2",
"Package": {
"providesPackages": {}
}
}
}
}
Supported Composer stability values are:
* `dev`
* `alpha`
* `beta`
* `RC`
* `stable`
State values that are not in the list of supported Composer stability values
can be expressed as build metadata:
.. code-block:: json
:caption: composer.json using build metadata for custom state labels
{
"name": "vendor/example",
"type": "typo3-cms-extension",
"description": "Example extension",
"extra": {
"typo3/cms": {
"extension-key": "example_extension",
"version": "1.0.0+obsolete",
"Package": {
"providesPackages": {}
}
}
}
}
In this example, `obsolete` is preserved as build metadata and can still be displayed
in the TYPO3 Extension Manager.
The former `state = excludeFromUpdates` value should now be expressed via
a dedicated boolean flag:
.. code-block:: json
:caption: composer.json marking an extension as excluded from updates
{
"name": "vendor/example",
"type": "typo3-cms-extension",
"description": "Example extension",
"extra": {
"typo3/cms": {
"extension-key": "example_extension",
"version": "1.0.0",
"exclude-from-updates": true,
"Package": {
"providesPackages": {}
}
}
}
}
PHP constraints
---------------
If an extension declares a PHP version dependency, it should be in
the `require` section of :file:`composer.json`:
.. code-block:: json
:caption: composer.json defining a PHP version constraint
{
"name": "vendor/example",
"version": "1.0.0",
"type": "typo3-cms-extension",
"description": "Example extension",
"require": {
"typo3/cms-core": "^14.2",
"php": "^8.2"
},
"extra": {
"typo3/cms": {
"extension-key": "example_extension",
"Package": {
"providesPackages": {}
}
}
}
}
The PHP dependency remains relevant for metadata and compatibility checks
in TYPO3 classic mode, but it is not used for extension dependency ordering.
If an extension provides regular Composer packages itself in TYPO3 classic mode,
these packages must be declared in
`extra.typo3/cms.Package.providesPackages`.
Packages that are already shipped by TYPO3 or already provided by another loaded
extension do not need to be repeated there.
Entries in `providesPackages` may also associate a provided package with a
relative path to a Composer vendor directory inside the extension. If that
directory contains a Composer-generated `autoload.php`, TYPO3 includes it
early during bootstrap.
If an extension does not provide any regular Composer packages itself,
`providesPackages` must still be present and set to an empty object
to avoid deprecation messages and to declare future compatibility
with TYPO3 classic mode.
If strict :file:`composer.json` validation is required and the extension is published
to Packagist where setting the top level `version` field is not recommended,
it is recommended to set the version via `extra.typo3/cms.version`.
If the `version` field is set anyway, it is recommended to omit `extra.typo3/cms.version`
to avoid redundant data points.
Impact
======
There is no impact on Composer-based TYPO3 installations.
TYPO3 classic installations will trigger a deprecation message
for extensions that ship a :file:`ext_emconf.php` and have not defined
the required metadata in :file:`composer.json`.
Affected installations
======================
TYPO3 classic installations are affected if they use extensions that:
* still ship :file:`ext_emconf.php`
* do not define a `"version"` field or `extra.typo3/cms.version`
* or do not define `extra.typo3/cms.Package.providesPackages`
at all, even as an empty object
Migration
=========
Extension authors should move extension metadata from :file:`ext_emconf.php`
to :file:`composer.json`.
This includes:
* the extension version via `"version"` or `extra.typo3/cms.version`
* `providesPackages` via `extra.typo3/cms.Package.providesPackages`,
using it for packages provided by the extension itself; packages already
shipped by TYPO3 or already provided by another extension do not need
to be repeated
* optional autoload paths for self-provided Composer packages via
`extra.typo3/cms.Package.providesPackages`, pointing to a Composer
vendor directory whose `autoload.php` can be included early
* supported stability via version suffixes such as `-dev`, `-alpha1`,
`-beta2`, or `-RC3`
* custom former state labels via build metadata such as `+obsolete`
* update exclusion via `extra.typo3/cms.exclude-from-updates`
* PHP constraints via the :file:`require.php` entry
For the time being, :file:`ext_emconf.php` may still need to be kept for
third-party tooling such as TYPO3 TER or Tailor. However, once the
required metadata is correctly defined in :file:`composer.json`,
TYPO3 will no longer evaluate :file:`ext_emconf.php`.
.. index:: ext:core, NotScanned
@@ -0,0 +1,77 @@
.. include:: /Includes.rst.txt
.. _deprecation-108557-1768610680:
===================================================================
Deprecation: #108557 - TCA option allowedRecordTypes for Page Types
===================================================================
See :issue:`108557`
Description
===========
The following methods of :php:`TYPO3\CMS\Core\DataHandling\PageDoktypeRegistry`
have been marked as deprecated:
* :php:`PageDoktypeRegistry->add()`
* :php:`PageDoktypeRegistry->addAllowedRecordTypes()`
* :php:`PageDoktypeRegistry->doesDoktypeOnlyAllowSpecifiedRecordTypes()`
Impact
======
Calling any of the above methods will trigger a deprecation-level log
entry and result in a fatal PHP error in TYPO3 v15.0.
Affected installations
======================
All installations using the :php-short:`TYPO3\CMS\Core\DataHandling\PageDoktypeRegistry`
to configure page types using the :php:`add()` method. Also, in some rare cases, using the
methods :php:`addAllowedRecordTypes()` or
:php:`doesDoktypeOnlyAllowSpecifiedRecordTypes()`.
Migration
=========
A new TCA option is introduced to configure allowed record types for pages:
Before:
.. code-block:: php
:caption: EXT:my_extension/ext_tables.php
use TYPO3\CMS\Core\DataHandling\PageDoktypeRegistry;
use TYPO3\CMS\Core\Utility\GeneralUtility;
$dokTypeRegistry = GeneralUtility::makeInstance(PageDoktypeRegistry::class);
$dokTypeRegistry->add(
116,
[
'allowedTables' => '*',
],
);
After:
.. code-block:: php
:caption: EXT:my_extension/Configuration/TCA/Overrides/pages.php
$GLOBALS['TCA']['pages']['types']['116']['allowedRecordTypes'] = ['*'];
The array can contain a list of table names or a single entry with an asterisk `*`
to allow all types. If no second argument was provided to the :php:`add()` method,
then the specific configuration can be omitted, as it will fall back to the
default allowed records.
Also, note that Page Types are registered by TCA types. The former usage of
:php-short:`TYPO3\CMS\Core\DataHandling\PageDoktypeRegistry` was only useful
to define allowed record types different to the default.
The option `allowedRecordType` is only evaluated in the "pages" table.
.. index:: TCA, PartiallyScanned, ext:core
@@ -0,0 +1,86 @@
.. include:: /Includes.rst.txt
.. _deprecation-108568-1734962478:
===========================================================================================
Deprecation: #108568 - BackendUserAuthentication::recordEditAccessInternals() and $errorMsg
===========================================================================================
See :issue:`108568`
Description
===========
The method :php:`BackendUserAuthentication::recordEditAccessInternals()`
and the property :php:`BackendUserAuthentication::$errorMsg` of class
:php:`\TYPO3\CMS\Core\Authentication\BackendUserAuthentication`
have been deprecated.
They represented an anti-pattern in which the method returned
a boolean value but communicated error details through a class property, making
the API difficult to use and test.
A new method :php:`checkRecordEditAccess()` has been introduced. It returns
an :php:`\TYPO3\CMS\Core\Authentication\AccessCheckResult` value object
containing both the access decision and any error messages.
Impact
======
Calling the deprecated method :php:`recordEditAccessInternals()` or accessing
the deprecated property :php:`$errorMsg` will trigger a deprecation-level log
entry and will stop working in TYPO3 v15.0.
The extension scanner reports usages as a **strong** match.
Affected installations
======================
Instances or extensions that directly call
:php:`recordEditAccessInternals()` or access the
:php:`$errorMsg` property.
Migration
=========
Replace calls to :php:`recordEditAccessInternals()` with
:php:`checkRecordEditAccess()`. The new method returns a
:php:`TYPO3\CMS\Core\Authentication\AccessCheckResult` object with two public
properties:
* :php:`isAllowed` - Boolean indicating whether access is granted
* :php:`errorMessage` - String containing the error message. It is empty if
access is granted
Before
------
.. code-block:: php
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
$backendUser = $this->getBackendUser();
if ($backendUser->recordEditAccessInternals($table, $record)) {
// Access granted
} else {
// Access denied, error message is in $backendUser->errorMsg
$errorMessage = $backendUser->errorMsg;
}
After
-----
.. code-block:: php
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
$backendUser = $this->getBackendUser();
$accessResult = $backendUser->checkRecordEditAccess($table, $record);
if ($accessResult->isAllowed) {
// Access granted
} else {
// Access denied
$errorMessage = $accessResult->errorMessage;
}
.. index:: PHP-API, FullyScanned, ext:core
@@ -0,0 +1,141 @@
.. include:: /Includes.rst.txt
.. _deprecation-108653-1741600000:
==========================================================
Deprecation: #108653 - Form file-based storage deprecated
==========================================================
See :issue:`108653`
Description
===========
File-based form storage (YAML files stored via file mounts) in
`EXT:form` has been deprecated in favor of database storage.
Since TYPO3 v14.2, the `EXT:form` module stores form definitions as
records in the :sql:`form_definition` database table. This approach provides
simpler setup, integrates better with the TYPO3 permission system, and
eliminates the need for file mounts and file system configuration.
The following components are deprecated and will be removed in TYPO3 v15.0:
* :php:`\TYPO3\CMS\Form\Storage\FileMountStorageAdapter` the storage
adapter for FAL file mount-based form persistence
* The YAML configuration option :yaml:`persistenceManager.allowedFileMounts`
configuring allowed file mounts for form storage
See :ref:`feature-108653-1767199420` for the new database storage approach.
An upgrade wizard as well as a CLI command are available to migrate existing file-based form definitions
to the database: :guilabel:`System > Upgrade > Upgrade Wizard > Migrate file-based forms to database storage`.
.. note::
YAML form files provided within extension directories
(:yaml:`persistenceManager.allowedExtensionPaths`) still work as before
and are not affected by this deprecation until further concepts are evaluated.
Impact
======
File-based form storage will continue to work without any functional changes
during the deprecation period. However, it will be removed in TYPO3 v15.0.
An upgrade wizard is available to check whether file-based forms exist
and to migrate them to database storage. Run the wizard regularly to verify
your migration status.
Affected installations
======================
All installations that:
* Store form definitions as YAML files in file mounts
(for example, :file:`1:/form_definitions/`)
* Use the :yaml:`persistenceManager.allowedFileMounts` configuration
option in their form setup YAML with one or more mount points configured
Migration
=========
.. warning::
If your installation uses file mountbased permission separation (i.e.,
different backend user groups have access to different form storage
folders to isolate which forms they can see and edit), an equivalent
access control mechanism for database-stored forms is **not yet available**.
In this case, it is recommended to **not migrate at this time**. The
file-based storage will continue to work without functional changes during
the entire deprecation period. A dedicated permission feature for
database storage is planned for a future release.
1. Run the upgrade wizard :guilabel:`Migrate file-based forms to database storage`
in the :guilabel:`System > Upgrade` module. This wizard:
* Copies all file-based form definitions into the :sql:`form_definition`
database table
* Updates all :sql:`tt_content` FlexForm references
(`persistenceIdentifier`) to point to the new database records
* Deletes the original YAML files after successful migration
If the :sql:`form_definition` table does not exist yet, run
:guilabel:`System > Maintenance > Analyze Database` first.
.. important::
The upgrade wizard only updates references in :sql:`tt_content`
(CType `form_formframework`). If your installation stores form
persistence identifiers in **custom database tables** or FlexForm
fields outside :sql:`tt_content` (e.g., through third-party
extensions), these references are **not updated automatically** and
must be migrated manually.
2. After verifying that all forms work correctly from the database, remove
the :yaml:`allowedFileMounts` configuration from your YAML setup:
**Before (deprecated):**
.. code-block:: yaml
:caption: EXT:my_extension/Configuration/Yaml/FormSetup.yaml
persistenceManager:
allowedFileMounts:
10: '1:/form_definitions/'
**After:**
To explicitly disable file mount storage, set :yaml:`allowedFileMounts`
to null (:yaml:`~`):
.. code-block:: yaml
:caption: EXT:my_extension/Configuration/Yaml/FormSetup.yaml
persistenceManager:
allowedFileMounts: ~
3. Optionally, if the upgrade wizard did not delete the YAML files (e.g.,
due to file permission issues), delete them manually from the file system
after confirming that the migration was successful.
Alternatively, the CLI command :bash:`form:definition:transfer` can be used
to transfer forms between storage types:
.. code-block:: bash
# Transfer all file mount forms to database
bin/typo3 form:definition:transfer --source=filemount --target=database
# Move (transfer + delete source) in one step
bin/typo3 form:definition:transfer --source=filemount --target=database --move
# Preview without changes
bin/typo3 form:definition:transfer --source=filemount --target=database --dry-run
.. index:: YAML, NotScanned, ext:form
@@ -0,0 +1,76 @@
.. include:: /Includes.rst.txt
.. _deprecation-108761-1769281290:
==============================================================
Deprecation: #108761 - BackendUtility TSconfig-related methods
==============================================================
See :issue:`108761`
Description
===========
The following methods in :php:`\TYPO3\CMS\Backend\Utility\BackendUtility` have
been deprecated:
* :php:`getTCEFORM_TSconfig()`
* :php:`getTSCpidCached()`
* :php:`getTSCpid()`
A new method :php:`BackendUtility::getRealPageId()` has been introduced. It
returns the real page ID of a given record. Unlike the previous methods which
returned arrays with multiple values or used internal caching, this method
provides a cleaner API that returns either the page ID as an integer or
:php:`null` if the page cannot be determined.
Impact
======
Calling any of the deprecated methods triggers a deprecation-level log entry.
The methods will be removed in TYPO3 v15.0.
The extension scanner reports usages as a **strong** match.
Affected installations
======================
Instances or extensions that call any of the deprecated methods.
Migration
=========
getTCEFORM_TSconfig()
---------------------
This method has been moved to :php:`FormEngineUtility`. If you need TSconfig
for TCEFORM, it is recommended that you rely on FormEngine data providers
instead.
getTSCpidCached() and getTSCpid()
---------------------------------
These methods returned an array with two values: the TSconfig PID and the
real PID. The new :php:`getRealPageId()` method returns only the real page ID.
Before:
.. code-block:: php
// getTSCpidCached returned [$tscPid, $realPid]
[$tscPid, $realPid] = BackendUtility::getTSCpidCached($table, $uid, $pid);
// getTSCpid returned the same structure
[$tscPid, $realPid] = BackendUtility::getTSCpid($table, $uid, $pid);
After:
.. code-block:: php
// getRealPageId() returns int|null
$pageId = BackendUtility::getRealPageId($table, $uid, $pid);
// If you need to ensure an integer (null becomes 0)
$pageId = (int)BackendUtility::getRealPageId($table, $uid, $pid);
.. index:: PHP-API, FullyScanned, ext:backend
@@ -0,0 +1,103 @@
.. include:: /Includes.rst.txt
.. _deprecation-108810-1738253894:
==================================================================
Deprecation: #108810 - BackendUtility localization-related methods
==================================================================
See :issue:`108810`
Description
===========
The following methods in :php:`\TYPO3\CMS\Backend\Utility\BackendUtility` have
been deprecated in favor of new methods in
:php:`\TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository`:
* :php:`BackendUtility::getRecordLocalization()` - use
:php:`LocalizationRepository::getRecordTranslation()` instead
* :php:`BackendUtility::getExistingPageTranslations()` - use
:php:`LocalizationRepository::getPageTranslations()` instead
* :php:`BackendUtility::translationCount()` - use
:php:`LocalizationRepository::getRecordTranslations()` instead
See :ref:`feature-108799-1738094060` for details of the new methods.
Impact
======
Calling any of the deprecated methods triggers a deprecation-level log entry.
The methods will be removed in TYPO3 v15.0 and result in a fatal PHP
error.
The extension scanner reports usages as a **strong** match.
Affected installations
======================
Instances or extensions that directly call any of the deprecated methods are
affected.
Migration
=========
Inject :php-short:`\TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository`
and use the new methods. The new methods return
:php:`\TYPO3\CMS\Core\Domain\RawRecord` objects instead of plain arrays.
getRecordLocalization()
-----------------------
.. code-block:: php
use TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
use TYPO3\CMS\Backend\Utility\BackendUtility;
// Before
$translations = BackendUtility::getRecordLocalization($table, $uid, $languageId);
if (is_array($translations) && !empty($translations)) {
$translation = $translations[0];
}
// After
$translation = $this->localizationRepository->getRecordTranslation($table, $uid, $languageId);
if ($translation !== null) {
// $translation is a RawRecord object
$translatedUid = $translation->getUid();
}
getExistingPageTranslations()
-----------------------------
.. code-block:: php
use TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
use TYPO3\CMS\Backend\Utility\BackendUtility;
// Before
$pageTranslations = BackendUtility::getExistingPageTranslations($pageUid);
// After
// Returns an array of RawRecord objects indexed by language ID
$pageTranslations = $this->localizationRepository->getPageTranslations($pageUid);
translationCount()
------------------
.. code-block:: php
use TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
use TYPO3\CMS\Backend\Utility\BackendUtility;
// Before
$message = BackendUtility::translationCount($table, $uid . ':' . $pid, 'Found %s translation(s)');
// or just counting
$count = (int)BackendUtility::translationCount($table, $uid . ':' . $pid);
// After
$translations = $this->localizationRepository->getRecordTranslations($table, $uid);
$count = count($translations);
$message = sprintf('Found %s translation(s)', $count);
.. index:: PHP-API, FullyScanned, ext:backend
@@ -0,0 +1,99 @@
.. include:: /Includes.rst.txt
.. _deprecation-108843-1738600000:
==========================================================================
Deprecation: #108843 - ExtensionManagementUtility::addFieldsToUserSettings
==========================================================================
See :issue:`108843`
See :issue:`108832`
Description
===========
The method
:php:`\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToUserSettings()`
has been deprecated in favor of the new :php:`addUserSetting()` method.
The legacy method required two separate steps to add a field to user settings:
first, adding the field configuration to the columns array and second, calling
:php:`addFieldsToUserSettings()` to add it to the showitem list. The new
method combines both steps into a single call and uses TCA as the storage
location.
Impact
======
Calling the deprecated method will trigger a deprecation-level log entry.
The method will be removed in TYPO3 v15.0.
The extension scanner reports usages as a **strong** match.
Affected installations
======================
Instances or extensions that use
:php:`ExtensionManagementUtility::addFieldsToUserSettings()` or directly
modify :php:`$GLOBALS['TYPO3_USER_SETTINGS']` to add custom fields to the
backend user profile settings.
Migration
=========
Replace the two-step approach with the new :php:`addUserSetting()` method.
Note that the new method uses TCA-style configuration and should be called
from :file:`Configuration/TCA/Overrides/be_users.php` instead of
:file:`ext_tables.php`.
Before
~~~~~~
.. code-block:: php
// In ext_tables.php
$GLOBALS['TYPO3_USER_SETTINGS']['columns']['myCustomSetting'] = [
'type' => 'check',
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:myCustomSetting',
];
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToUserSettings(
'myCustomSetting',
'after:emailMeAtLogin'
);
After
~~~~~
.. code-block:: php
// In Configuration/TCA/Overrides/be_users.php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserSetting(
'myCustomSetting',
[
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:myCustomSetting',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
],
],
'after:emailMeAtLogin'
);
Field type mapping
------------------
When migrating, use the following type mappings:
============== ==========================================================
Legacy type TCA config
============== ==========================================================
text :php:`['type' => 'input']`
email :php:`['type' => 'email']`
number :php:`['type' => 'number']`
password :php:`['type' => 'password']`
check :php:`['type' => 'check', 'renderType' => 'checkboxToggle']`
select :php:`['type' => 'select', 'renderType' => 'selectSingle']`
language :php:`['type' => 'language']`
============== ==========================================================
.. index:: PHP-API, FullyScanned, ext:core
@@ -0,0 +1,64 @@
.. include:: /Includes.rst.txt
.. _deprecation-108963-1770907005:
==========================================================================
Deprecation: #108963 - Deprecate `PageRenderer->addInlineLanguageDomain()`
==========================================================================
See :issue:`108963`
Description
===========
:php:`\TYPO3\CMS\Core\Page\PageRenderer->addInlineLanguageDomain()` has been
deprecated in favor of importing JavaScript modules, as introduced in
:ref:`feature-108941-1770902109`.
Impact
======
Extension developers can now use labels in JavaScript components without
requiring labels to be preloaded globally or per module. This reduces the risk
of missing labels and simplifies developer workflows.
Affected installations
======================
The deprecated method was introduced in TYPO3 v14.1. This means that only
installations that use :php:`addInlineLanguageDomain()` in TYPO3 v14.1 or later
are affected.
Migration
=========
The call to :php:`PageRenderer::addInlineLanguageDomain()` can be removed. In
the JavaScript code, add a module import that imports from the
:js:`'~labels/'` prefix.
Before:
.. code-block:: php
$pageRenderer->addInlineLanguageDomain('core.bookmarks');
.. code-block:: javascript
import { html } from 'lit';
import { lll } from '@typo3/core/lit-helper.js';
html`<p>{lll('core.bookmarks:groupType.global')}</p>`
After:
.. code-block:: javascript
import { html } from 'lit';
// Import labels from language domain "core.bookmarks"
import labels from '~labels/core.bookmarks';
// Use label
html`<p>{labels.get('groupType.global')}</p>`
.. index:: Backend, JavaScript, FullyScanned, ext:backend
@@ -0,0 +1,113 @@
.. include:: /Includes.rst.txt
.. _deprecation-109027-1771514240:
===============================================================================
Deprecation: #109027 - Move `language:update` command and events to `EXT:core`
===============================================================================
See :issue:`109027`
Description
===========
The `language:update` CLI command and related
:php:`\TYPO3\CMS\Install\Service\LanguagePackService` have been moved from
`EXT:install` to `EXT:core`, allowing installations to update language packs
without `EXT:install` having to be installed.
Since TYPO3 v13 it has been possible to run TYPO3 without `EXT:install` in
Composer-based installations. However, the `language:update` command still
required `EXT:install`, which was impractical for deployments that needed to
update language packs.
The following classes have been moved, and their old class names deprecated:
* :php:`\TYPO3\CMS\Install\Command\LanguagePackCommand` is now
:php:`\TYPO3\CMS\Core\Command\UpdateLanguagePackCommand`
* :php:`\TYPO3\CMS\Install\Service\Event\ModifyLanguagePackRemoteBaseUrlEvent`
is now :php:`\TYPO3\CMS\Core\Localization\Event\ModifyLanguagePackRemoteBaseUrlEvent`
* :php:`\TYPO3\CMS\Install\Service\Event\ModifyLanguagePacksEvent` is now
:php:`\TYPO3\CMS\Core\Localization\Event\ModifyLanguagePacksEvent`
The old class names are registered as aliases via
:php-short:`TYPO3\ClassAliasLoader\ClassAliasMap` and
continue to work in TYPO3 v14. Event listeners registered for the deprecated
event class names are still called when the new event is dispatched, with a
deprecation notice triggered at runtime.
Impact
======
Using the old class names will trigger a deprecation notice. The extension
scanner will report usage of the deprecated class names.
The old class names will be removed in TYPO3 v15.
Affected installations
======================
Extensions that use one or more of the deprecated class names listed above.
Migration
=========
Replace the old class names with the new ones in :php:`use` statements:
.. code-block:: diff
:caption: EXT:my_extension/Classes/EventListener/MyEventListener.php
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
-use TYPO3\CMS\Install\Service\Event\ModifyLanguagePacksEvent;
+use TYPO3\CMS\Core\Localization\Event\ModifyLanguagePacksEvent;
final class MyEventListener
{
#[AsEventListener(
identifier: 'my-extension/modify-language-packs',
)]
public function __invoke(
ModifyLanguagePacksEvent $event,
): void {
// ...
}
}
.. code-block:: diff
:caption: EXT:my_extension/Classes/EventListener/MyOtherEventListener.php
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
-use TYPO3\CMS\Install\Service\Event\ModifyLanguagePackRemoteBaseUrlEvent;
+use TYPO3\CMS\Core\Localization\Event\ModifyLanguagePackRemoteBaseUrlEvent;
final class MyOtherEventListener
{
#[AsEventListener(
identifier: 'my-extension/modify-language-pack-remote-base-url',
)]
public function __invoke(
ModifyLanguagePackRemoteBaseUrlEvent $event,
): void {
// ...
}
}
.. note::
Extensions supporting both TYPO3 v13 and v14 do not need to change
anything. The old class names continue to work in both versions.
Simply update the :php:`use` statements when dropping TYPO3 v13 support.
.. index:: CLI, PHP-API, FullyScanned, ext:install
@@ -0,0 +1,71 @@
.. include:: /Includes.rst.txt
.. _deprecation-109029-1771804800:
=========================================================
Deprecation: #109029 - FormEngine ``doSave`` hidden field
=========================================================
See :issue:`109029`
Description
===========
The :html:`<input type="hidden" name="doSave">` field in FormEngine was a
legacy mechanism where JavaScript set the field value to `1` to
signal to PHP that the submitted form data should be processed as a save
operation.
This indirection is no longer needed. TYPO3 now uses native submit button
values such as `_savedok` directly, which are sufficient to determine
whether a save operation should be performed. The field is **no longer
evaluated internally**.
For backward compatibility, the :html:`doSave` field is still appended to the
form on programmatic saves in TYPO3 v14, but this behavior is deprecated and
will be removed in TYPO3 v15.
Impact
======
Third-party code reading :php:`$request->getParsedBody()['doSave']` to detect
whether a save operation was triggered will stop working in TYPO3 v15.
Affected installations
======================
Installations with custom backend modules or extensions that inspect the
:html:`doSave` POST field to determine whether incoming form data should be
persisted.
Migration
=========
Replace any checking of the :html:`doSave` POST field with a check of
all native submit action fields that FormEngine sends as part of normal
form submission.
Before:
.. code-block:: php
$parsedBody = $request->getParsedBody();
$doSave = (bool)($parsedBody['doSave'] ?? false);
if ($doSave) {
// process data
}
After:
.. code-block:: php
$parsedBody = $request->getParsedBody();
$isSaveAction = !empty($parsedBody['_savedok'])
|| !empty($parsedBody['_saveandclosedok'])
|| !empty($parsedBody['_savedokview'])
|| !empty($parsedBody['_savedoknew']);
if ($isSaveAction) {
// process data
}
.. index:: Backend, JavaScript, NotScanned, ext:backend
@@ -0,0 +1,60 @@
.. include:: /Includes.rst.txt
.. _deprecation-109102-1740480000:
==============================================================
Deprecation: #109102 - FormEngine "additionalHiddenFields" key
==============================================================
See :issue:`109102`
Description
===========
The `additionalHiddenFields` result array key in FormEngine was a legacy
mechanism that stored hidden :html:`<input>` HTML strings separately from the
main `html` key. This indirection is no longer needed. Elements can simply
add their hidden fields to the `html` key.
The following have been deprecated:
* The `additionalHiddenFields` key in FormEngine result arrays
* :php:`FormResult::$hiddenFieldsHtml`
* :php:`FormResultCollection::getHiddenFieldsHtml()`
Impact
======
Third-party FormEngine elements that add entries to
:php:`$resultArray['additionalHiddenFields']` will trigger a PHP
:php:`E_USER_DEPRECATED` level error when their result is merged via
:php:`AbstractNode::mergeChildReturnIntoExistingResult()`.
Affected installations
======================
Installations with custom FormEngine elements or containers that populate the
`additionalHiddenFields` result array key.
Migration
=========
Move hidden field HTML from `additionalHiddenFields` into the `html` key.
Before:
.. code-block:: php
$resultArray = $this->initializeResultArray();
$resultArray['additionalHiddenFields'][] =
'<input type="hidden" name="myField" value="myValue" />';
After:
.. code-block:: php
$resultArray = $this->initializeResultArray();
$resultArray['html'] .=
'<input type="hidden" name="myField" value="myValue" />';
.. index:: Backend, PHP-API, NotScanned, ext:backend
@@ -0,0 +1,76 @@
.. include:: /Includes.rst.txt
.. _deprecation-109152-1741600000:
==============================================
Deprecation: #109152 - Form DatePicker element
==============================================
See :issue:`109152`
Description
===========
The :yaml:`DatePicker` form element type and its associated
:php:`DatePickerViewHelper` and :php:`TimePickerViewHelper` have been
deprecated as part of removing jQuery dependency from
:composer:`typo3/cms-form`. The :yaml:`Date` form element type serves as a
replacement and uses native HTML5 :html:`<input type="date">` without needing
a JavaScript library.
The following components are deprecated:
* :php:`TYPO3\CMS\Form\Domain\Model\FormElements\DatePicker`
* :php:`TYPO3\CMS\Form\ViewHelpers\Form\DatePickerViewHelper`
* :php:`TYPO3\CMS\Form\ViewHelpers\Form\TimePickerViewHelper`
* The :file:`EXT:form/Resources/Public/JavaScript/frontend/date-picker.js`
jQuery initialization script
Impact
======
Using the :yaml:`DatePicker` form element type in a form definition will
trigger a PHP :php:`E_USER_DEPRECATED` level error at runtime. The element,
its ViewHelpers, and the JavaScript file will be removed in TYPO3 v15.
Affected installations
======================
All installations that use the :yaml:`DatePicker` form element type in form
definitions created with the TYPO3 Form Framework.
Migration
=========
Replace :yaml:`DatePicker` with the :yaml:`Date` form element type in your
form definitions.
Before:
.. code-block:: yaml
type: DatePicker
identifier: date-1
label: 'Pick a date'
properties:
dateFormat: Y-m-d
enableDatePicker: true
After:
.. code-block:: yaml
type: Date
identifier: date-1
label: 'Pick a date'
The :yaml:`Date` element uses a native HTML5 date input, which does not
require jQuery or additional JavaScript. The
:yaml:`dateFormat` and :yaml:`enableDatePicker` properties are no longer
needed because the browser handles date formatting and the picker natively.
Alternatively, if the native HTML5 date input does not meet your
requirements, you can create a custom form element with a date picker
JavaScript library of your choice.
.. index:: Frontend, YAML, NotScanned, ext:form
@@ -0,0 +1,78 @@
.. include:: /Includes.rst.txt
.. _deprecation-109171-1741254000:
===========================================
Deprecation: #109171 - Bootstrap tab events
===========================================
See :issue:`109171`
Description
===========
Bootstrap's tab JavaScript has been replaced with a custom implementation
tailored to TYPO3. The Bootstrap tab events :js:`show.bs.tab` and
:js:`shown.bs.tab` are now deprecated and will be removed in TYPO3 v15.
The following new custom events are available as replacements:
* :js:`typo3:tab:show` — dispatched before a tab switch, cancelable via
:js:`event.preventDefault()`
* :js:`typo3:tab:shown` — dispatched after a tab switch
Both events bubble from the tab button and carry a
:js:`detail.relatedTarget` property that points to the previously active tab
button or :js:`null`.
Impact
======
Listening for :js:`show.bs.tab` or :js:`shown.bs.tab` events will continue to
work in TYPO3 v14 but will stop working in TYPO3 v15, when the backward-
compatibility events will be removed.
Affected installations
======================
All extensions that listen to :js:`show.bs.tab` or :js:`shown.bs.tab` events
on tab buttons are affected.
Migration
=========
Replace Bootstrap tab event listeners with the new TYPO3 tab events.
Before:
.. code-block:: js
document.addEventListener('show.bs.tab', (e) => {
console.log(
'Tab is about to show',
e.target,
e.detail.relatedTarget
);
});
document.addEventListener('shown.bs.tab', (e) => {
console.log(
'Tab was shown',
e.target,
e.detail.relatedTarget
);
});
After:
.. code-block:: js
document.addEventListener('typo3:tab:show', (e) => {
console.log('Tab is about to show', e.target, e.detail.relatedTarget);
});
document.addEventListener('typo3:tab:shown', (e) => {
console.log('Tab was shown', e.target, e.detail.relatedTarget);
});
.. index:: Backend, JavaScript, NotScanned, ext:backend
@@ -0,0 +1,67 @@
.. include:: /Includes.rst.txt
.. _deprecation-109192-1741560000:
=====================================================
Deprecation: #109192 - FormEngine OuterWrapContainer
=====================================================
See :issue:`109192`
Description
===========
The :php:`\TYPO3\CMS\Backend\Form\Container\OuterWrapContainer` FormEngine
container has been deprecated in favor
of the new :php:`\TYPO3\CMS\Backend\Form\Container\FormWrapContainer`.
The old container rendered record
headers, type icons, and record identity information inside FormEngine, which
forced controllers to hide redundant elements via CSS hacks.
The new :php-short:`\TYPO3\CMS\Backend\Form\Container\FormWrapContainer`
only handles form wrapping (description,
read-only notice, field information, field wizards, and child HTML).
Rendering record headers and identity information is now the responsibility of
the controllers themselves.
Impact
======
Using the `outerWrapContainer` render type will trigger a PHP
:php:`E_USER_DEPRECATED` level error. The container will still work as before
during the deprecation period.
Affected installations
======================
Installations with custom controllers or FormEngine integrations that set
:php:`$formData['renderType'] = 'outerWrapContainer'`.
Migration
=========
Replace the render type `outerWrapContainer` with
`formWrapContainer`.
Before:
.. code-block:: php
$formData['renderType'] = 'outerWrapContainer';
$formResult = $this->nodeFactory->create($formData)->render();
After:
.. code-block:: php
$formData['renderType'] = 'formWrapContainer';
$formResult = $this->nodeFactory->create($formData)->render();
Note that :php-short:`\TYPO3\CMS\Backend\Form\Container\FormWrapContainer`
no longer renders the record heading
(:html:`<h1>`) or the record identity footer (icon, table title, uid). If
your controller relied on these being rendered by
:php-short:`\TYPO3\CMS\Backend\Form\Container\OuterWrapContainer`, you need to
render them in your controller code.
.. index:: Backend, PHP-API, NotScanned, ext:backend
@@ -0,0 +1,43 @@
.. include:: /Includes.rst.txt
.. _deprecation-109196-1742122800:
==============================================================================
Deprecation: #109196 - Deprecate doktypesToShowInNewPageDragArea user TSconfig
==============================================================================
See :issue:`109196`
Description
===========
The user TSconfig option
:tsconfig:`options.pageTree.doktypesToShowInNewPageDragArea`
has been deprecated and will be removed in TYPO3 v15.0.
The page tree toolbar submenu now automatically determines available doktypes
based on the user's group permissions. Manual TSconfig configuration is no
longer needed.
Impact
======
Using the deprecated user TSconfig option triggers a deprecation-level log
entry and will stop working in TYPO3 v15.0.
Affected installations
======================
TYPO3 installations that set
:tsconfig:`options.pageTree.doktypesToShowInNewPageDragArea` in their user
TSconfig.
Migration
=========
Remove the :tsconfig:`options.pageTree.doktypesToShowInNewPageDragArea` option
from your user TSconfig. The page tree toolbar will then display all
doktypes that the current backend user is allowed to create based on their
group permissions.
.. index:: Backend, TSConfig, NotScanned, ext:backend
@@ -0,0 +1,89 @@
.. include:: /Includes.rst.txt
.. _deprecation-109230-1773404000:
=========================================
Deprecation: #109230 - FormResultCompiler
=========================================
See :issue:`109230`
Description
===========
The class :php:`TYPO3\CMS\Backend\Form\FormResultCompiler` has been
deprecated. The internal implementation of FormEngine has been adjusted to
better separate concerns, especially regarding rendering and asset handling.
This change also removed all internal usages of
:php:`TYPO3\CMS\Backend\Form\FormResultCompiler`,
as it handled more tasks than its name suggested.
Impact
======
Extensions and installations that render FormEngine forms manually rather
than through standard controllers, such as
:php-short:`\TYPO3\CMS\Backend\Controller\EditDocumentController`, and that use
:php-short:`TYPO3\CMS\Backend\Form\FormResultCompiler`, will be affected when the
class is removed in TYPO3 v15.
Affected installations
======================
Installations and extensions using
:php-short:`TYPO3\CMS\Backend\Form\FormResultCompiler` to build FormEngine forms.
Migration
=========
Replace :php-short:`TYPO3\CMS\Backend\Form\FormResultCompiler` with
:php-short:`\TYPO3\CMS\Backend\Form\FormResultFactory` and
:php-short:`\TYPO3\CMS\Backend\Form\FormResultHandler`.
Before:
.. code-block:: php
use TYPO3\CMS\Backend\Form\NodeFactory;
use TYPO3\CMS\Backend\Form\FormResultCompiler;
use TYPO3\CMS\Core\Utility\GeneralUtility;
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
$formResultCompiler = GeneralUtility::makeInstance(
FormResultCompiler::class
);
$formResult = $nodeFactory->create($formData)->render();
$formResultCompiler->mergeResult($formResult);
// Form HTML markup is accessible in the data array
$body = $formResult['html'];
After:
.. code-block:: php
use TYPO3\CMS\Backend\Form\NodeFactory;
use TYPO3\CMS\Backend\Form\FormResultFactory;
use TYPO3\CMS\Backend\Form\FormResultHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;
$nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
$formResultFactory = GeneralUtility::makeInstance(
FormResultFactory::class
);
$formResultHandler = GeneralUtility::makeInstance(
FormResultHandler::class
);
$formResult = $nodeFactory->create($formData)->render();
// Convert the raw result array into a FormResult object
$formResult = $formResultFactory->create($formResult);
// Use FormResultHandler to pass collected assets (JS, CSS, labels) to PageRenderer
$formResultHandler->addAssets($formResult);
// Form HTML markup is accessible in the FormResult DTO
$body = $formResult->html;
.. index:: Backend, FullyScanned, ext:backend
@@ -0,0 +1,135 @@
.. include:: /Includes.rst.txt
.. _deprecation-109280-1742109280:
=================================================================
Deprecation: #109280 - FormEngine TcaDescription fieldInformation
=================================================================
See :issue:`109280`
Description
===========
The :php:`\TYPO3\CMS\Backend\Form\FieldInformation\TcaDescription` field
information render type has been deprecated. Field descriptions configured
via TCA :php:`['columns']['fieldName']['description']` are now rendered
automatically next to the field label by
:php:`\TYPO3\CMS\Backend\Form\Element\AbstractFormElement::renderDescription()`
and
:php:`\TYPO3\CMS\Backend\Form\Container\AbstractContainer::renderDescription()`.
Previously, every FormEngine element and container registered
`tcaDescription` as a default field information node, which rendered the
description inside the element body. The description is now rendered
after the label or legend element, providing more consistent positioning
across all field types.
Additionally, the :php:`$defaultFieldInformation` property has been removed
from all Core FormEngine elements and containers. Custom elements that extend
Core elements and rely on `tcaDescription` being present in
:php:`$defaultFieldInformation` are also affected.
Impact
======
Using the `tcaDescription` render type in a custom `fieldInformation`
configuration will trigger a PHP :php:`E_USER_DEPRECATED` level error. The
render type still exists but will return empty output during the deprecation
period, since descriptions are now rendered at the label level.
Custom FormEngine nodes that extend core elements and override
:php:`$defaultFieldInformation` to include `tcaDescription` will still work,
but the `tcaDescription` entry will trigger a deprecation warning.
Affected installations
======================
* Installations with extensions that explicitly configure `tcaDescription`
as a field information node in TCA:
.. code-block:: php
'fieldInformation' => [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
],
* Custom FormEngine elements and containers that set `tcaDescription` in
their :php:`$defaultFieldInformation` property:
.. code-block:: php
protected $defaultFieldInformation = [
'tcaDescription' => [
'renderType' => 'tcaDescription',
],
];
Extensions that only use the standard TCA `description` property are not
affected — descriptions will continue to be rendered.
Migration
=========
Remove any explicit `tcaDescription` field information configuration from
TCA and from custom FormEngine node classes. Field descriptions are now
rendered automatically next to the label and no longer require a field
information node.
**TCA configuration**
.. code-block:: diff
'columns' => [
'my_field' => [
'label' => 'My field',
'description' => 'Help text for this field',
'config' => [
'type' => 'input',
- 'fieldInformation' => [
- 'tcaDescription' => [
- 'renderType' => 'tcaDescription',
- ],
- ],
],
],
],
**Custom FormEngine nodes with defaultFieldInformation**
If your custom element had a `tcaDescription` in
:php:`$defaultFieldInformation`, remove the property entirely:
.. code-block:: diff
class MyCustomElement extends AbstractFormElement
{
- protected $defaultFieldInformation = [
- 'tcaDescription' => [
- 'renderType' => 'tcaDescription',
- ],
- ];
+ // tcaDescription is no longer needed; descriptions are
+ // rendered automatically next to the label.
}
If your custom element has other field information entries alongside
`tcaDescription`, remove only the `tcaDescription` entry:
.. code-block:: diff
class MyCustomElement extends AbstractFormElement
{
protected $defaultFieldInformation = [
- 'tcaDescription' => [
- 'renderType' => 'tcaDescription',
- ],
'myCustomInfo' => [
'renderType' => 'myCustomInfo',
],
];
}
.. index:: Backend, PHP-API, TCA, NotScanned, ext:backend
@@ -0,0 +1,69 @@
.. include:: /Includes.rst.txt
.. _deprecation-109286-1773844395:
================================================================
Deprecation: #109286 - Explicit request handling in PageRenderer
================================================================
See :issue:`109286`
Description
===========
Since TYPO3 v14.2 some methods of class :php:`TYPO3\CMS\Core\Page\PageRenderer`
require an instance of :php-short:`\Psr\Http\Message\ServerRequestInterface` to
be passed explicitly :
setLanguage()
-------------
* Old: :php:`PageRenderer->setLanguage(Locale $locale, ?ServerRequestInterface $request = null)`
* TYPO3 v14.2: :php:`PageRenderer->setLanguage(Locale $locale, ?ServerRequestInterface $request = null)`
* TYPO3 v15: :php:`PageRenderer->setLanguage(Locale $locale, ServerRequestInterface $request)`
setDocType()
------------
* Old: :php:`PageRenderer->setDocType(DocType $docType)`
* TYPO3 v14.2: :php:`PageRenderer->setDocType(DocType $docType, ?ServerRequestInterface $request = null)`
* TYPO3 v15: :php:`PageRenderer->setDocType(DocType $docType, ServerRequestInterface $request)`
render()
--------
* Old: :php:`PageRenderer->render()`
* TYPO3 v14.2: :php:`PageRenderer->render(?ServerRequestInterface $request = null)`
* TYPO3 v15: :php:`PageRenderer->render(ServerRequestInterface $request)`
renderResponse()
----------------
* Old: :php:`PageRenderer->renderResponse(int $code = 200, string $reasonPhrase = '')`
* TYPO3 v14.2: :php:`PageRenderer->render(ServerRequestInterface|int $requestOrCode = 200, int|string $codeOrReasonPhrase = '', string $reasonPhrase = '')`
* TYPO3 v15: :php:`PageRenderer->render(ServerRequestInterface $request, int $code = 200, string $reasonPhrase = '')`
Impact
======
Request dependencies within
:php-short:`TYPO3\CMS\Core\Page\PageRenderer` are no longer implicit via
:php:`$GLOBALS['TYPO3_REQUEST']` and must now be passed explicitly. Not
passing a request to the methods listed above will trigger a deprecation-level
log entry in TYPO3 v14 and will result in a fatal PHP error in TYPO3 v15.
Affected installations
======================
:php-short:`TYPO3\CMS\Core\Page\PageRenderer` is a low-level Core class. Many
extensions use higher-level APIs and are therefore not directly affected by
this change.
Migration
=========
Adapt method calls to pass the
:php-short:`\Psr\Http\Message\ServerRequestInterface` object explicitly.
.. index:: Backend, Frontend, PHP-API, NotScanned, ext:core
@@ -0,0 +1,115 @@
.. include:: /Includes.rst.txt
.. _deprecation-109295-1742407200:
==================================================================
Deprecation: #109295 - DatabaseWriter::setLogTable()/getLogTable()
==================================================================
See :issue:`109295`
Description
===========
The methods :php:`setLogTable()` and :php:`getLogTable()` in
:php:`\TYPO3\CMS\Core\Log\Writer\DatabaseWriter` have been deprecated.
:php-short:`\TYPO3\CMS\Core\Log\Writer\DatabaseWriter` is a dedicated writer
for the :sql:`sys_log` table. Its :php:`writeLog()` method maps
:php-short:`\TYPO3\CMS\Core\Log\LogRecord` fields to the :sql:`sys_log`
schema (:sql:`request_id`, :sql:`time_micro`, :sql:`component`,
:sql:`level`, :sql:`message`, :sql:`data`, :sql:`tstamp`). Allowing an
arbitrary table to be set via :php:`setLogTable()` created a false sense of
flexibility - any custom table needs to replicate the full
:sql:`sys_log` schema to work correctly.
The long-term goal is to make
:php-short:`\TYPO3\CMS\Core\Log\Writer\DatabaseWriter` :php:`final` and to
remove the :php:`$logTable` property entirely.
Impact
======
Calling :php:`setLogTable()` or :php:`getLogTable()` triggers a PHP
:php:`E_USER_DEPRECATED` error. This also includes passing
:php:`logTable` as a configuration option when
:php-short:`\TYPO3\CMS\Core\Log\Writer\DatabaseWriter` is registered via
:php:`$GLOBALS['TYPO3_CONF_VARS']['LOG']`, since the
:php:`AbstractWriter` constructor resolves options to :php:`set*()` calls.
Support will be removed in TYPO3 v15.0.
Affected installations
======================
Installations that configure
:php-short:`\TYPO3\CMS\Core\Log\Writer\DatabaseWriter` with a custom
:php:`logTable` option, or that call :php:`setLogTable()` or
:php:`getLogTable()` on a
:php-short:`\TYPO3\CMS\Core\Log\Writer\DatabaseWriter` instance.
The extension scanner detects direct calls to :php:`->setLogTable()` and
:php:`->getLogTable()`. The more common case, passing :php:`logTable` as a
configuration option via :php:`$GLOBALS['TYPO3_CONF_VARS']['LOG']`, cannot
be detected automatically and requires a manual search for
:php-short:`\TYPO3\CMS\Core\Log\Writer\DatabaseWriter` usage with a
:php:`logTable` key.
Migration
=========
Replace :php-short:`\TYPO3\CMS\Core\Log\Writer\DatabaseWriter` with a
dedicated writer that extends
:php:`\TYPO3\CMS\Core\Log\Writer\AbstractWriter` and implements
:php:`writeLog()` with explicit field mapping for the custom table.
Before:
.. code-block:: php
use Psr\Log\LogLevel;
use TYPO3\CMS\Core\Log\Writer\DatabaseWriter;
$GLOBALS['TYPO3_CONF_VARS']['LOG']['writerConfiguration'][LogLevel::WARNING] =
[
DatabaseWriter::class => ['logTable' => 'my_custom_log'],
];
After:
.. code-block:: php
:caption: EXT:my_extension/Classes/Log/Writer/MyCustomTableWriter.php
namespace MyVendor\MyExtension\Log\Writer;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Log\LogRecord;
use TYPO3\CMS\Core\Log\Writer\AbstractWriter;
use TYPO3\CMS\Core\Log\Writer\WriterInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class MyCustomTableWriter extends AbstractWriter
{
public function writeLog(LogRecord $record): WriterInterface
{
GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('my_custom_log')
->insert('my_custom_log', [
'created' => (int)$record->getCreated(),
'level' => $record->getLevel(),
'message' => $record->getMessage(),
]);
return $this;
}
}
.. code-block:: php
use Psr\Log\LogLevel;
use MyVendor\MyExtension\Log\Writer\MyCustomTableWriter;
$GLOBALS['TYPO3_CONF_VARS']['LOG']['writerConfiguration'][LogLevel::WARNING] = [
MyCustomTableWriter::class => [],
];
.. index:: PHP-API, PartiallyScanned, ext:core
@@ -0,0 +1,101 @@
.. include:: /Includes.rst.txt
.. _deprecation-109306-1774010043:
===============================================================================
Deprecation: #109306 - Deprecate form editor stage template rendering functions
===============================================================================
See :issue:`109306`
Description
===========
The Form Editor stage component provided a set of JavaScript helper functions
for template-based rendering of form elements in the stage area. These
functions were designed to be called from subscribers to the
:js:`view/stage/abstract/render/template/perform` PubSub event, which is the
extension point for custom form element rendering in the stage.
With the introduction of the
:html:`<typo3-form-form-element-stage-item>` and
:html:`<typo3-form-page-stage-item>` web components (see
:ref:`feature-107058-1769168658`), the built-in template-based helper
functions have been superseded. The
:js:`view/stage/abstract/render/template/perform` event
**remains available**, and extension authors may continue to subscribe to it
to implement fully custom stage rendering logic.
The following exported functions from
:js:`@typo3/form/backend/form-editor/stage-component` are deprecated:
* :js:`eachTemplateProperty()`
* :js:`renderSimpleTemplate()`
* :js:`renderSimpleTemplateWithValidators()`
* :js:`renderCheckboxTemplate()`
* :js:`renderSelectTemplates()`
* :js:`renderFileUploadTemplates()`
* :js:`createAbstractViewFormElementToolbar()` — only used by the legacy
template-based rendering path. Web component-based elements handle
their toolbar via the :js:`toolbarConfig` property of
:html:`<typo3-form-form-element-stage-item>`
In addition, all Fluid partial templates in
:file:`EXT:form/Resources/Private/Backend/Partials/FormEditor/Stage/` are
deprecated, as they were designed for use with the template-based rendering
approach described above:
* :file:`SimpleTemplate.fluid.html`
* :file:`SelectTemplate.fluid.html`
* :file:`FileUploadTemplate.fluid.html`
* :file:`ContentElement.fluid.html`
* :file:`Fieldset.fluid.html`
* :file:`StaticText.fluid.html`
* :file:`Page.fluid.html`
* :file:`SummaryPage.fluid.html`
* :file:`_ElementToolbar.fluid.html`
* :file:`_UnknownElement.fluid.html`
Impact
======
Extensions that call any of the deprecated helper functions will receive IDE
deprecation hints and TypeScript compiler warnings. The deprecated Fluid
templates will emit an HTML comment in the rendered stage area indicating
their deprecation. All deprecated functions and templates will be removed in
TYPO3 v15.
Affected installations
======================
All extensions that:
* call any of the deprecated JavaScript helper functions (including
:js:`createAbstractViewFormElementToolbar()`), typically from a subscriber
of the :js:`view/stage/abstract/render/template/perform` event, or
* reference any of the deprecated Fluid partial templates via
:yaml:`formEditorPartials` in their prototype configuration.
Migration
=========
Two migration paths are available:
**Option 1: Use the built-in web component (recommended)**
Remove the custom JavaScript subscriber and omit the
:yaml:`formEditorPartials` stage partial configuration from your form
element's YAML definition. The Form Editor will then render the element
automatically using the built-in
:html:`<typo3-form-form-element-stage-item>` web component.
See :ref:`feature-107058-1769168658` for full details.
**Option 2: Implement custom rendering logic in the event subscriber**
If you need to keep using the
:js:`view/stage/abstract/render/template/perform` event, replace calls to
the deprecated helper functions with your own DOM manipulation logic.
.. index:: Backend, JavaScript, NotScanned, ext:form
@@ -0,0 +1,88 @@
.. include:: /Includes.rst.txt
.. _deprecation-109329-1774349266:
=================================================
Deprecation: #109329 - PageRenderer get() methods
=================================================
See :issue:`109329`
Description
===========
The following methods have been deprecated:
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getTitle()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getLanguage()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getDocType()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getHtmlTag()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getHeadTag()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getFavIcon()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getIconMimeType()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getTemplateFile()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getMoveJsFromHeaderToFooter()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getBodyContent()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getInlineLanguageLabels()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getInlineLanguageLabelFiles()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->getMetaTag()`
* :php:`TYPO3\CMS\Core\Page\PageRenderer->removeMetaTag()`
* :php:`TYPO3\CMS\Frontend\ContentObject\AbstractContentObject->getPageRenderer()`
Impact
======
Invoking any of the methods listed above will generate a
deprecation-level log entry in TYPO3 v14. These methods are scheduled
for removal in TYPO3 v15.
From an architectural perspective, the
:php-short:`TYPO3\CMS\Core\Page\PageRenderer` singleton represents
a central yet problematic construct, particularly in TYPO3 frontend
rendering. With the deprecation of
these methods, the :php-short:`TYPO3\CMS\Core\Page\PageRenderer` class loses its
ability to serve as a data source - data can still be added but no longer retrieved.
This change paves the way for refactoring the construct in TYPO3 v15,
including the introduction of a compatibility layer to maintain
backward compatibility.
Affected installations
======================
Instances with extensions invoking one of the methods listed above are
affected. The extension scanner is configured to find consumers, apart from the
generic method names :php:`getTitle()`, :php:`getLanguage()`, and :php:`getPageRenderer()`.
Migration
=========
In practice, there is often little reason to rely on the methods
mentioned above. Most data passed to PageRenderer is handled through
mechanisms that can be intercepted and configured, for example title
and meta tag handling. As a result, the deprecated get() methods do
not have a direct replacement.
A commonly used case is :php:`PageRenderer->getDocType()`, which
determines whether self-closing tags should include a trailing slash
(`/`). This is relevant only in the frontend, as the backend
always uses HTML5. The DocType itself is derived from TypoScript
configuration, which is available as a request attribute.
Before:
.. code-block:: php
$needsEndingSlash = GeneralUtility::makeInstance(PageRenderer::class)
->getDocType()
->isXmlCompliant();
After:
.. code-block:: php
$needsEndingSlash = DocType::createFromRequest($request)
->isXmlCompliant();
.. index:: PHP-API, PartiallyScanned, ext:core
@@ -0,0 +1,55 @@
.. include:: /Includes.rst.txt
.. _deprecation-109409-1774787352:
==================================================================
Deprecation: #109409 - Access to arbitrary resources in extensions
==================================================================
See :issue:`109409`
Description
===========
Accessing extension resources outside the configured resource
definitions is deprecated.
By default, extension resources are limited to the following paths:
* :folder:`Configuration`
* :folder:`Resources/Private`
* :folder:`Resources/Public`
If a resource identifier references another extension path, that path
must be configured explicitly in :file:`Configuration/Resources.php`.
See :ref:`feature-109409-1774770383` for information on how to
configure resources for extensions.
Impact
======
TYPO3 installations using resource identifiers that reference extension
folders outside :folder:`Configuration`,
:folder:`Resources/Private`, or :folder:`Resources/Public`
will receive a deprecation message when such a resource is resolved.
Every accessed resource must be configured beforehand as described in
:ref:`feature-109409-1774770383`.
Affected installations
======================
TYPO3 installations using resource identifiers that reference extension
folders outside :folder:`Configuration`,
:folder:`Resources/Private`, or :folder:`Resources/Public`.
Migration
=========
Either configure the referenced paths explicitly in
:file:`Configuration/Resources.php`, as described in
:ref:`feature-109409-1774770383`, or move the resources to a path that
is already configured.
.. index:: PHP-API, NotScanned, ext:core
@@ -0,0 +1,44 @@
.. include:: /Includes.rst.txt
.. _deprecation-109409-1774774806:
================================================================
Deprecation: #109409 - Allowed paths configuration is deprecated
================================================================
See :issue:`109409`
Description
===========
Using :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['addAllowedPaths']`
to configure additional public paths for the `typo3/app` package
has been deprecated.
Configure resources in :file:`config/system/resources.php` instead.
See :ref:`feature-109409-1774770383` for details.
Impact
======
TYPO3 installations that use
:php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['addAllowedPaths']`
will receive a deprecation message whenever resources for the
`typo3/app` package are resolved.
Affected installations
======================
TYPO3 installations that use
:php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['addAllowedPaths']`.
Migration
=========
Configure resources in :file:`config/system/resources.php` instead of
using :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['addAllowedPaths']`.
See :ref:`feature-109409-1774770383` for information on how to
configure resources for the `typo3/app` package.
.. index:: PHP-API, NotScanned, ext:core
@@ -0,0 +1,96 @@
.. include:: /Includes.rst.txt
.. _deprecation-109412-1742000001:
==============================================================
Deprecation: #109412 - TypoScript-based form YAML registration
==============================================================
See :issue:`109412`
Description
===========
The TypoScript-based registration of form YAML configuration files via
:typoscript:`plugin.tx_form.settings.yamlConfigurations` and
:typoscript:`module.tx_form.settings.yamlConfigurations` has been
deprecated in favor of the new auto-discovery mechanism introduced in
TYPO3 v14.2 (see :ref:`Feature-109412 <feature-109412-1742000001>`).
Before TYPO3 v14.2 this was the only way to register `EXT:form` YAML files. It
required separate registration of the frontend and the backend
in TypoScript:
.. code-block:: typoscript
:caption: EXT:my_extension/Configuration/TypoScript/setup.typoscript — deprecated
plugin.tx_form.settings.yamlConfigurations {
1732785702 = EXT:my_extension/Configuration/Form/MySetup.yaml
}
# Backend had to be registered separately:
module.tx_form.settings.yamlConfigurations {
1732785703 = EXT:my_extension/Configuration/Form/MySetup.yaml
}
The TypoScript-based paths will still be loaded during the deprecation
period but will be removed in TYPO3 v15.0.
Impact
======
Extensions that register form YAML files via TypoScript will trigger a
PHP :php:`E_USER_DEPRECATED` error. The registered YAML files are still
loaded and will remain functional during the deprecation period.
Affected installations
======================
All installations where an extension registers form YAML files via:
* :typoscript:`plugin.tx_form.settings.yamlConfigurations`
* :typoscript:`module.tx_form.settings.yamlConfigurations`
Migration
=========
Replace TypoScript registration with the auto-discovery directory
convention introduced in TYPO3 v14.2
(see :ref:`Feature-109412 <feature-109412-1742000001>`).
1. Create directory :file:`EXT:my_extension/Configuration/Form/MySet/`.
2. Add a :file:`config.yaml` file with a unique `name` and, optionally,
a `priority` value (default: 100; the core base set is
priority 10):
.. code-block:: yaml
:caption: EXT:my_extension/Configuration/Form/MySet/config.yaml
name: my-vendor/my-form-set
label: 'My Custom Form Set'
priority: 200
3. Add your existing form configuration to :file:`config.yaml` below
the metadata keys:
.. code-block:: yaml
:caption: EXT:my_extension/Configuration/Form/MySet/config.yaml
name: my-vendor/my-form-set
label: 'My Custom Form Set'
priority: 200
# Content of your former MySetup.yaml
persistenceManager:
allowedExtensionPaths:
10: 'EXT:my_extension/Resources/Private/Forms/'
4. Remove TypoScript registrations from
:file:`setup.typoscript`. PHP or TypoScript registration is
no longer necessary.
The YAML files are picked up automatically for **both** frontend and
backend without any additional registration.
.. index:: YAML, Frontend, Backend, FullyScanned, ext:form
@@ -0,0 +1,93 @@
.. include:: /Includes.rst.txt
.. _deprecation-69190-1770668741:
========================================================================================
Deprecation: #69190 - Deprecate random password generator for frontend and backend users
========================================================================================
See :issue:`69190`
Description
===========
The `passwordRules` option of the `passwordGenerator` field control has been
deprecated. Password generation is now configured through password policies
registered in :php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['passwordPolicies']`.
Each password policy can define a `generator` section with a class implementing
:php:`\TYPO3\CMS\Core\PasswordPolicy\Generator\PasswordGeneratorInterface`.
The field control references a policy by name via the new `passwordPolicy`
option instead of defining rules inline.
Impact
======
Using the `passwordRules` option in TCA field control configuration will
trigger a PHP deprecation warning. Support for `passwordRules` will be
removed in TYPO3 v15.
Affected installations
======================
Installations that use the `passwordGenerator` field control with the
`passwordRules` option in custom TCA configurations, for example in password
or secret token fields.
Migration
=========
Replace the `passwordRules` option with a `passwordPolicy` reference.
.. code-block:: diff
:caption: EXT:my_extension/Configuration/TCA/Overrides/be_users.php
'fieldControl' => [
'passwordGenerator' => [
'renderType' => 'passwordGenerator',
'options' => [
- 'passwordRules' => [
- 'length' => 20,
- 'upperCaseCharacters' => true,
- 'lowerCaseCharacters' => true,
- 'digitCharacters' => true,
- 'specialCharacters' => false,
- ],
+ 'passwordPolicy' => 'myCustomPolicy',
],
],
],
The referenced password policy must be registered in
:php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['passwordPolicies']`:
.. code-block:: php
:caption: config/system/additional.php OR typo3conf/system/additional.php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['passwordPolicies']['myCustomPolicy'] = [
'generator' => [
'className' => \TYPO3\CMS\Core\PasswordPolicy\Generator\PasswordGenerator::class,
'options' => [
'length' => 20,
'upperCaseCharacters' => true,
'lowerCaseCharacters' => true,
'digitCharacters' => true,
'specialCharacters' => false,
],
],
'validators' => [],
];
.. note::
For backend and frontend user password fields, the field control is now
provided by the core automatically. If your TCA override only added the
`passwordGenerator` field control with default rules, you can remove it
entirely. The core uses the password policy configured in
:php:`$GLOBALS['TYPO3_CONF_VARS']['BE']['passwordPolicy']` and
:php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['passwordPolicy']` respectively.
See :ref:`feature-69190-1770137533` for details on password policies and
custom password generators.
.. index:: Backend, Frontend, PHP-API, TCA, NotScanned
@@ -0,0 +1,42 @@
.. include:: /Includes.rst.txt
.. _feature-100254-1742119200:
==================================================================
Feature: #100254 - Support download attribute in file link browser
==================================================================
See :issue:`100254`
Description
===========
The HTML5 :html:`download` attribute can now be configured for a link in
the file link browser. When set, the browser forces a file download
instead of navigating to the file URL.
The link browser renders a `Force download` checkbox for file links. When
enabled, an optional `Custom filename` text field appears, allowing
editors to specify an alternative filename for the downloaded file.
The TypoLink codec supports an optional seventh TypoLink segment for
:html:`download`. The value :php:`true` produces a boolean download
attribute (:html:`<a download>`). Any other string value produces a
named download attribute (:html:`<a download="custom-name.pdf">`).
Example TypoLink strings:
* `t3://file?uid=42 - - - - - true`
* `t3://file?uid=42 - - - - - report.pdf`
Impact
======
Editors can now set whether a file should be downloaded or
displayed in the browser directly in the link. This works in both the RTE and non-RTE link
browser dialogs.
Existing TypoLink values without :html:`download` remain unchanged and
continue to work as before.
.. index:: Backend, Frontend, RTE, ext:backend, ext:frontend
@@ -0,0 +1,149 @@
.. include:: /Includes.rst.txt
.. _feature-100887-1773012077:
===========================================================
Feature: #100887 - Prefer CSP hash values over nonce values
===========================================================
See :issue:`100887`
Description
===========
Content-Security-Policy nonce values are random tokens in each request that prevent
HTTP response caching. By collecting hash values of assets at render time
instead, responses can be cached, for example by using
:composer:`lochmueller/staticfilecache` or reverse proxies, while still
enforcing a strict CSP.
Hash-based CSP is an explicit opt-in configured for a site via :file:`csp.yaml`.
Nonce values remain the default when no behavior is configured.
New `DirectiveHashCollection` service
-------------------------------------
The new
:php:`\TYPO3\CMS\Core\Security\ContentSecurityPolicy\DirectiveHashCollection`
service is a per-request registry that collects CSP hash values for inline and
static assets during page rendering.
Both inline content and static file resources are supported:
* Inline assets: the SHA-256 hash is computed over the content that appears
inside the :html:`<script>` or :html:`<style>` element.
* Static assets: if an :html:`integrity` attribute is already present, its
value is reused; otherwise, the file content is hashed on demand.
* Style attributes: the new :html:`f:asset.styleAttr` ViewHelper hashes
inline style values and covers the :csp:`style-src-attr` directive.
The collected hashes survive the frontend page cache round-trip via
:php:`\TYPO3\CMS\Frontend\Cache\MetaDataState`.
Updated `Behavior` class
------------------------
:php:`\TYPO3\CMS\Core\Security\ContentSecurityPolicy\Configuration\Behavior`
now carries a second nullable boolean property, :php:`$useHash`:
* :php:`true` explicitly enables hash collection and CSP hash sources.
* :php:`null` means off, which is the default. Hashes are not collected or
applied.
* :php:`false` explicitly disables hash collection and CSP hash sources.
Configuring behavior via `csp.yaml`
-----------------------------------
Both :php:`$useNonce` and :php:`$useHash` can be set in a site's
:file:`config/sites/<site>/csp.yaml` under the top-level :yaml:`behavior:` key:
.. code-block:: yaml
behavior:
useNonce: false
useHash: true
enforce:
inheritDefault: true
includeResolutions: true
Setting :yaml:`useHash: true` enables hash-based CSP for that site.
Setting :yaml:`useNonce: false` removes nonce sources from the compiled
policy, which is required for responses to be cacheable by reverse proxies.
Updated `Policy::prepare()` and `Policy::compile()`
---------------------------------------------------
Both methods now accept a
:php:`\TYPO3\CMS\Core\Security\ContentSecurityPolicy\Middleware\PolicyBag`
instead of separate :php:`ConsumableNonce`, :php:`Behavior`, and
:php:`HashCollection` arguments. The :php:`PolicyBag` is forwarded directly
from the CSP middleware, making hash collection visible to PSR-14 event
listeners via
:php:`PolicyPreparedEvent::$policyBag->directiveHashCollection`.
The behavior resolution, applying collected hashes and suppressing nonce
sources, now happens inside :php:`Policy::prepare()`.
New `f:asset.styleAttr` ViewHelper
----------------------------------
A new ViewHelper registers inline style values using the
:csp:`style-src-attr` CSP directive:
.. code-block:: html
<div style="{f:asset.styleAttr(value: 'color: green', csp: true)}"></div>
The :html:`csp` argument defaults to :html:`true` and controls whether the
hash is collected.
Updated `f:asset.script` and `f:asset.css` ViewHelpers
------------------------------------------------------
The :html:`useNonce` argument has been renamed to :html:`csp`
(deprecated, see :ref:`deprecation-100887-1774712028`). The new default is
:html:`true` for external files, that is, static resources, and
:html:`false` for inline content.
.. code-block:: html
<!-- static file: csp=1 by default, hash collected from file content -->
<f:asset.script
identifier="my-script"
src="EXT:my_ext/Resources/Public/JavaScript/foo.js"
/>
<!-- with integrity attribute: hash reused directly, no file read -->
<f:asset.script
identifier="my-script"
src="EXT:my_ext/Resources/Public/JavaScript/foo.js"
integrity="sha256-abc123=="
/>
<!-- inline script: opt in explicitly -->
<f:asset.script identifier="my-inline" csp="1">
document.querySelector('.foo').classList.add('active');
</f:asset.script>
Migration
=========
The :html:`useNonce` ViewHelper argument and :php:`'useNonce'` asset option key
are deprecated and replaced by :html:`csp` and :php:`'csp'`. See
:ref:`deprecation-100887-1774712028`.
The signature of :php:`Policy::prepare()` and :php:`Policy::compile()` has
changed to accept a :php:`PolicyBag`. Code calling these methods directly, as
they are marked :php:`@internal`, must be updated.
Impact
======
Sites that configure :yaml:`behavior.useHash: true`, and optionally
:yaml:`behavior.useNonce: false`, in their :file:`csp.yaml` can use hash-based
CSP sources. This allows HTTP responses to be cached by reverse proxies and
static file cache extensions without sacrificing Content-Security-Policy
enforcement. Sites without this configuration continue to use nonce-based CSP.
.. index:: Backend, Frontend, PHP-API, FluidViewHelpers, ext:core, ext:fluid
@@ -0,0 +1,88 @@
.. include:: /Includes.rst.txt
.. _feature-102079-1756482906:
===========================================================================
Feature: #102079 - Introduce BeforePersistingReportEvent for CSP violations
===========================================================================
See :issue:`102079`
Description
===========
When a Content-Security-Policy violation report needs to be persisted, the
:php:`\TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\BeforePersistingReportEvent`
can be used to provide an alternative report or to prevent a particular report
from being persisted.
Example
-------
.. code-block:: php
<?php
declare(strict_types=1);
namespace Example\Demo\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\BeforePersistingReportEvent;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Reporting\Report;
final class BeforePersistingReportEventListener
{
private const BROWSER_PREFIXES = [
'chrome-extension://',
'moz-extension://',
'safari-extension://',
];
#[AsEventListener('example/security/before-persisting-csp-report')]
public function __invoke(BeforePersistingReportEvent $event): void
{
// Avoid persisting CSP violations caused by browser extensions
$blockedUri = $event->originalReport->details['blocked-uri'] ?? null;
if (is_string($blockedUri) && $this->isBrowserExtensions($blockedUri)) {
$event->report = null;
return;
}
// Otherwise, adjust the report and provide custom metadata
$event->report = new Report(
$event->originalReport->scope,
$event->originalReport->status,
$event->originalReport->requestTime,
array_merge(
$event->originalReport->meta,
['x-example' => '... additional metadata ...']
),
$event->originalReport->details,
$event->originalReport->summary,
$event->originalReport->uuid,
$event->originalReport->created,
$event->originalReport->changed
);
}
private function isBrowserExtensions(string $blockedUri): bool
{
foreach (self::BROWSER_PREFIXES as $prefix) {
if (str_starts_with($blockedUri, $prefix)) {
return true;
}
}
return false;
}
}
Impact
======
The new
:php-short:`\TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\BeforePersistingReportEvent`
allows custom control over whether and how Content-Security-Policy violation
reports are persisted in TYPO3.
.. index:: Backend, Frontend, PHP-API, ext:core
@@ -0,0 +1,66 @@
.. include:: /Includes.rst.txt
.. _feature-102159-1675976883:
=============================================================================
Feature: #102159 - Support additional parameters for TCA slug prefix userFunc
=============================================================================
See :issue:`102159`
Description
===========
TCA slug prefix user functions now receive the full TCA field configuration
and the field name as additional parameters.
User function implementation
============================
The prefix user function receives two additional keys alongside the existing
parameters:
* `fieldName` - The name of the slug field
* `config` - The full TCA configuration array of the slug field
.. code-block:: php
:caption: EXT:my_extension/Classes/Utility/SlugUtility.php
namespace MyExtension\Utility;
class SlugUtility
{
public function generatePrefix(array $parameters): string
{
// Standard parameters (always available)
$site = $parameters['site'];
$languageId = $parameters['languageId'];
$table = $parameters['table'];
$row = $parameters['row'];
$fieldName = $parameters['fieldName'];
$config = $parameters['config'];
return '/default/';
}
}
Available parameters
====================
The user function receives an array with the following keys:
* `site` - The current site object
* `languageId` - The current language ID (int)
* `table` - The table name (string)
* `row` - The current record data (array)
* `fieldName` - The name of the slug field (string)
* `config` - The full TCA configuration array of the slug field
Impact
======
Extension developers can access the complete TCA field configuration and the
field name inside prefix user functions.
.. index:: Backend, TCA, ext:backend
@@ -0,0 +1,73 @@
.. include:: /Includes.rst.txt
.. _feature-102194-1772779432:
==================================================
Feature: #102194 - Introduce QueryBuilderPaginator
==================================================
See :issue:`102194`
Description
===========
A new :php:`\TYPO3\CMS\Core\Pagination\QueryBuilderPaginator` is introduced to
enable pagination of
:php-short:`\TYPO3\CMS\Core\Database\Query\QueryBuilder` instances.
The paginator implements the existing
:php-short:`\TYPO3\CMS\Core\Pagination\PaginatorInterface` and integrates
seamlessly with the existing
:php-short:`\TYPO3\CMS\Core\Pagination\SimplePagination` and
:php-short:`\TYPO3\CMS\Core\Pagination\SlidingWindowPagination` classes.
The paginated items are fetched only once per page request by storing the
result internally, avoiding double execution of the database statement.
The total item count is determined robustly using a common table expression
(CTE) wrapping the passed :php:`QueryBuilder` instance. This approach correctly
handles advanced queries involving `UNION`, nested CTEs, window functions, and
grouping.
.. note::
The :php-short:`\TYPO3\CMS\Core\Pagination\QueryBuilderPaginator` does
**not** handle language overlays. Applying overlays to the result set can
lead to unexpected item count differences between pages when some records
are hidden after overlay processing. Use
:php-short:`\TYPO3\CMS\Extbase\Pagination\QueryResultPaginator` or
:php-short:`\TYPO3\CMS\Core\Pagination\ArrayPaginator` when language
overlay handling is required.
The paginator also takes **full control** over `LIMIT` and `OFFSET`
and does not respect any existing limit or offset constraints on the
passed :php:`QueryBuilder` instance.
Impact
======
A new :php-short:`\TYPO3\CMS\Core\Pagination\QueryBuilderPaginator` is
available to paginate
:php-short:`\TYPO3\CMS\Core\Database\Query\QueryBuilder` result sets using
the TYPO3 pagination API.
Example
-------
.. code-block:: php
:caption: EXT:my_extension/Classes/Controller/MyController.php
use TYPO3\CMS\Core\Pagination\QueryBuilderPaginator;
use TYPO3\CMS\Core\Pagination\SimplePagination;
$paginator = new QueryBuilderPaginator(
queryBuilder: $queryBuilder,
currentPageNumber: $currentPage,
itemsPerPage: 10,
);
$pagination = new SimplePagination($paginator);
// Retrieve the items for the current page
$items = $paginator->getPaginatedItems();
.. index:: Database, PHP-API, ext:core
@@ -0,0 +1,86 @@
.. include:: /Includes.rst.txt
.. _feature-102215-1709554850:
===========================================================================
Feature: #102215 - ViewHelper and data structure to render srcset attribute
===========================================================================
See :issue:`102215`
Description
===========
The `srcset` HTML attribute can be used to provide different image sizes to
the browser. The browser is free to choose which image size to use, which is
why the images must all be scaled versions of the same original image. Each
image in the `srcset` list also has a descriptor which either specifies
the absolute width of the image, for example `400w`, or is a scale factor
relative to the original image size for use on high-density screens, for
example `2x`.
`srcset` attributes are used by various HTML tags:
* :html:`<img srcset="image@500.jpg 500w, image@1000.jpg 1000w" />`
* :html:`<source srcset="image@1x.jpg 1x, image@2x.jpg 2x" />` inside
:html:`<picture>`
* :html:`<link rel="preload" as="image" imagesrcset="image@500.jpg 500w, image@1000.jpg 1000w" />`
.. note::
File name notation like `image@500.jpg` is just a regular file name
used to indicate its pixel dimensions. The `@` notation has no inherent
conversion magic, unlike the descriptors `500w` and `1x`.
To generate `srcset` attributes easily based on input, a new
data structure has been added to calculate the appropriate image sizes from a
list of descriptors. Based on these calculations, image files can be
generated using the image manipulation API.
.. code-block:: php
:caption: EXT:my_ext/Classes/Service/SomeImageService.php
use TYPO3\CMS\Core\Html\Srcset\SrcsetAttribute;
// From width descriptors
$srcset = SrcsetAttribute::createFromDescriptors(['400w', '600w', '800w']);
// Or from pixel density descriptors (a reference width must be supplied)
$srcset = SrcsetAttribute::createFromDescriptors(
['1.5x', '2x', '3x'],
800
);
// Add image URIs
foreach ($srcset->getCandidates() as $candidate) {
// Generate scaled image here using $candidate->getCalculatedWidth()
// Set URI of the generated image
$candidate->setUri($generatedImageUri);
}
// Render srcset attribute
$srcsetString = $srcset->generateSrcset();
To generate `srcset` attributes in Fluid templates, a new ViewHelper has also
been introduced.
.. code-block:: html
<picture>
<source
srcset="{f:image.srcset(image: imageObject, srcset: '400w, 600w, 800w', cropVariant: 'wide')}"
sizes="100vw"
media="(min-width: 1200px)"
/>
<!-- ... -->
</picture>
Impact
======
The new ViewHelper `f:image.srcset` simplifies previous manual implementations
that used :fluid:`f:uri.image` for each image size. This now makes it easier
to provide images in different dimensions based on a single image.
.. index:: Fluid, PHP-API, ext:core, ext:fluid
@@ -0,0 +1,29 @@
.. include:: /Includes.rst.txt
.. _feature-102430-1700581800:
==================================================================
Feature: #102430 - Flush cache tags for file and folder operations
==================================================================
See :issue:`102430`
Description
===========
This feature is guarded by the `frontend.cache.autoTagging` feature toggle and
is currently experimental. The core flushes cache tags automatically for all
kinds of records when they are created, changed, or deleted. This is not the
case for files and folders. This feature adds cache tag handling for file and
folder operations when they are created, changed, or deleted. File metadata
changes are now handled correctly as well. This will lead to a better editor
experience if cache tags are used correctly.
Impact
======
Integrators and extension developers can now add `sys_file_${uid}` and
`sys_file_metadata_${uid}` as cache tags, and they are flushed correctly by
the TYPO3 core when an editor interacts with them in the :guilabel:`Media` module.
.. index:: FAL, PHP-API, ext:core
@@ -0,0 +1,37 @@
.. include:: /Includes.rst.txt
.. _feature-102790-1738838400:
=======================================================
Feature: #102790 - Line wrapping option for code editor
=======================================================
See :issue:`102790`
Description
===========
A new TCA appearance option `lineWrapping` has been added for the
`codeEditor` render type. When enabled, long lines are wrapped inside
the editor instead of requiring horizontal scrolling.
Example:
.. code-block:: php
'config' => [
'type' => 'text',
'renderType' => 'codeEditor',
'format' => 'html',
'appearance' => [
'lineWrapping' => true,
],
],
Impact
======
Code editor fields can now be configured to wrap long lines by setting
`lineWrapping` in the `appearance` array.
.. index:: Backend, TCA, ext:backend
@@ -0,0 +1,188 @@
.. include:: /Includes.rst.txt
.. _feature-104546-1737580000:
==============================================================
Feature: #104546 - Support ICU MessageFormat for plural forms
==============================================================
See :issue:`104546`
Description
===========
TYPO3 now supports ICU MessageFormat for translations. This enables the proper handling
of plural forms, gender-based selections, and other locale-aware formatting
in language labels.
ICU MessageFormat is an internationalization standard that allows messages to
contain placeholders that can vary based on parameters such as quantity,
gender, or other conditions. This is particularly useful for proper
pluralization in languages with complex plural rules.
The format is detected automatically when named arguments, that is,
associative arrays, are used in translation calls. If the message contains ICU
patterns like `{count, plural, ...}` or `{name}`, and named arguments are
provided, the ICU MessageFormatter is used automatically.
Language file format
--------------------
ICU MessageFormat strings are stored as regular translation strings in XLIFF
files:
.. code-block:: xml
:caption: EXT:my_extension/Resources/Private/Language/locallang.xlf
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="locallang.xlf">
<body>
<!-- Simple plural form -->
<trans-unit id="file_count">
<source>{count, plural, one {# file} other {# files}}</source>
</trans-unit>
<!-- Plural with zero case -->
<trans-unit id="item_count">
<source>{count, plural, =0 {no items} one {# item} other {# items}}</source>
</trans-unit>
<!-- Combined placeholder and plural -->
<trans-unit id="greeting">
<source>Hello {name}, you have {count, plural, one {# message} other {# messages}}.</source>
</trans-unit>
<!-- Gender selection -->
<trans-unit id="profile_update">
<source>{gender, select, male {He} female {She} other {They}} updated the profile.</source>
</trans-unit>
<!-- Simple named placeholder -->
<trans-unit id="welcome">
<source>Welcome, {name}!</source>
</trans-unit>
</body>
</file>
</xliff>
PHP usage
---------
Use named arguments in an associative array to trigger ICU
MessageFormat processing:
.. code-block:: php
:caption: Using ICU MessageFormat with LanguageService
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
$languageService = GeneralUtility::makeInstance(LanguageServiceFactory::class)
->createFromUserPreferences($backendUser);
// ICU plural forms: use named arguments
$label = $languageService->translate(
'file_count',
'my_extension.messages',
['count' => 5]
);
// Result: "5 files"
// Combined placeholder and plural
$label = $languageService->translate(
'greeting',
'my_extension.messages',
['name' => 'John', 'count' => 3]
);
// Result: "Hello John, you have 3 messages."
// sprintf-style still works with positional arguments
$label = $languageService->translate(
'downloaded_times', // Label: "Downloaded %d times"
'my_extension.messages',
[42] // Positional arguments use sprintf
);
// Result: "Downloaded 42 times"
.. code-block:: php
:caption: Using ICU MessageFormat with LocalizationUtility
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
// Use named arguments for ICU format
$label = LocalizationUtility::translate(
'file_count',
'MyExtension',
['count' => 1]
);
// Result: "1 file"
Fluid usage
-----------
In Fluid templates use named arguments in the `arguments` attribute:
.. code-block:: html
:caption: EXT:my_extension/Resources/Private/Templates/Example.html
<!-- ICU plural forms with named arguments -->
<f:translate key="file_count" arguments="{count: numberOfFiles}" />
<!-- Combined placeholder and plural -->
<f:translate key="greeting" arguments="{name: userName, count: messageCount}" />
<!-- Gender selection -->
<f:translate key="profile_update" arguments="{gender: userGender}" />
<!-- sprintf-style with positional arguments still works -->
<f:translate key="downloaded_times" arguments="{0: downloadCount}" />
ICU MessageFormat syntax reference
----------------------------------
**Plural forms:**
.. code-block:: text
{variable, plural,
=0 {zero case}
one {singular case}
other {plural case}
}
**Select (gender/choice):**
.. code-block:: text
{variable, select,
male {He}
female {She}
other {They}
}
**Number formatting:**
.. code-block:: text
{count, number} - Basic number
{price, number, currency} - Currency format
The `#` symbol in plural patterns is replaced by the actual number.
Impact
======
This feature provides a standards-based approach to pluralization that:
* uses the well-tested ICU library, via PHP's intl extension
* handles locale-specific plural rules
* supports complex pluralization for languages such as Russian and Arabic
* is backward compatible; existing sprintf-style translations will continue to
work
The system detects which format to use based on arguments:
* **Named arguments** (associative array): Uses ICU MessageFormat
* **Positional arguments** (indexed array): Uses sprintf
.. index:: PHP-API, Fluid, ext:core, ext:extbase, ext:fluid
@@ -0,0 +1,170 @@
.. include:: /Includes.rst.txt
.. _feature-104974-1726401724:
===================================================================
Feature: #104974 - Content area related information in the frontend
===================================================================
See :issue:`104974`
Description
===========
:ref:`feature-103504-1712041725` introduced the :typoscript:`PAGEVIEW` cObject
for frontend rendering. It is a powerful alternative to
the :typoscript:`FLUIDTEMPLATE` cObject, allowing a full page to be rendered with
less configuration.
:typoscript:`PAGEVIEW` has now been extended and provides all
content elements related to a page, grouped by their columns
as defined in the page layout. The elements are provided as fully resolved
:php:`Record` objects (see :ref:`feature-103783-1715113274` and
:ref:`feature-103581-1723209131`).
The content elements are attached to the new
:php:`\TYPO3\CMS\Core\Page\ContentArea` object, which also contains all
column-related information and configuration.
This is useful for frontend rendering because an
element may need to know its rendering context. Knowing
this information, an element can, for example, decide not to render the
:html:`Header` partial if it is in a sidebar content area.
:php-short:`\TYPO3\CMS\Core\Page\ContentArea` objects are added to the
view either by variable name defined in :typoscript:`contentAs` or,
if not defined, `content`. Content elements can then be accessed via the
:html:`records` property.
:php-short:`\TYPO3\CMS\Core\Page\ContentArea` objects contain
backend layout-related configuration, such as
:ref:`content restrictions <feature-108623-1768315053>`. These allow
further validation such as whether a content type is
valid.
Therefore :html:`{content.main.records}` can be used to get content
elements from the `main` content area. `main` is the identifier as defined in
the page layout, and `content` is the default variable name.
.. important::
:php-short:`\TYPO3\CMS\Core\Page\ContentArea` objects are attached in
the :php-short:`\TYPO3\CMS\Core\Page\ContentAreaCollection`, which
implements the PSR-11 :php:`\Psr\Container\ContainerInterface` to allow
access to the content areas using :php:`get()`. To optimize performance
:php-short:`\TYPO3\CMS\Core\Page\ContentArea` objects are
instantiated only when accessed (lazy loading).
Accessing a :php-short:`\TYPO3\CMS\Core\Page\ContentArea` using
:html:`{content.main}` makes the following information available, as defined in
the page layout:
* :html:`identifier` - The column identifier
* :html:`colPos` - The defined `colPos`
* :html:`name` - The descriptive `name`, which might be a locallang key
* :html:`allowedContentTypes` - The defined `allowedContentTypes`
* :html:`disallowedContentTypes` - The defined `disallowedContentTypes`
* :html:`slideMode` - The defined :php:`ContentSlideMode`, which defaults to
:php:`ContentSlideMode::None`
* :html:`configuration` - The complete content area-related configuration
* :html:`records` - The content elements as :php:`Record` objects
The following example renders the content elements of a page which has only
a single column:
.. code-block:: typoscript
mod.web_layout.BackendLayouts {
default {
title = Default
config {
backend_layout {
colCount = 1
rowCount = 1
rows {
1 {
columns {
1 {
name = Main Content Area
colPos = 0
identifier = main
}
}
}
}
}
}
}
}
.. code-block:: typoscript
page = PAGE
page.10 = PAGEVIEW
page.10.paths.10 = EXT:my_site_package/Resources/Private/Templates/
.. code-block:: html
<f:for each="{content.main.records}" as="record">
<f:render partial="ContentElement"
arguments="{record: record, area: content.main}" />
</f:for>
The introduction of the new
:ref:`f:render.contentArea <feature-108726-1769071158>` and
:ref:`f:render.record <feature-108726-1769503907>` ViewHelpers means that manually
iterating over content elements is no longer necessary. All the content elements
in a content area can be rendered with a single ViewHelper call:
.. code-block:: html
<!-- Tag syntax -->
<f:render.contentArea contentArea="{content.main}" />
<!-- Inline syntax -->
{content.main -> f:render.contentArea()}
To render a single record, use the :html:`f:render.record` ViewHelper:
.. code-block:: html
<!-- Tag syntax -->
<f:render.record record="{content.main.records.0}" />
<!-- Inline syntax -->
{content.main.records.0 -> f:render.record()}
.. note::
:php-short:`\TYPO3\CMS\Core\Page\ContentArea` helps the
:php-short:`\TYPO3\CMS\Frontend\Event\AfterContentHasBeenFetchedEvent`
to manipulate content elements in an area by
providing context.
Impact
======
It is now possible to access all the content elements on a page, grouped by their
column, as well as having all the column-related information and
configuration available. In addition to reduced configuration effort,
different rendering is possible for an element depending on context.
Example
=======
A content element template using a `Default` layout that renders the
`Header` partial only if the content element is not in the `sidebar` column.
.. code-block:: html
<f:layout name="Default" />
<f:section name="Main">
<f:if condition="{area.identifier} != 'sidebar'">
<f:render partial="Header" arguments="{_all}" />
</f:if>
<p>{record.text}</p>
<f:image image="{record.image}" width="{area.configuration.imageWidth}" />
</f:section>
.. index:: Frontend, ext:frontend
@@ -0,0 +1,42 @@
.. include:: /Includes.rst.txt
.. _feature-105084-1771501624:
=====================================================================
Feature: #105084 - Add setting to configure indexed_search pagination
=====================================================================
See :issue:`105084`
Description
===========
A new TypoScript setting
:typoscript:`plugin.tx_indexedsearch.settings.pagination_type` has been
introduced to set the pagination implementation used by
`EXT:indexed_search`.
Available values:
* :typoscript:`simple`: uses
:php-short:`\TYPO3\CMS\Core\Pagination\SimplePagination` and renders
all result pages.
* :typoscript:`slidingWindow`: uses
:php-short:`\TYPO3\CMS\Core\Pagination\SlidingWindowPagination` and
limits the displayed page links as set in
:typoscript:`plugin.tx_indexedsearch.settings.page_links`.
The default is :typoscript:`simple` to preserve existing behavior.
Integrators can switch to :typoscript:`slidingWindow` to make
:typoscript:`page_links` effective for indexed_search result browsing.
Impact
======
Integrators can now switch between core pagination implementations using
TypoScript, without having to use custom PHP code.
Advanced, fully-customized pagination logic can still be implemented using
:php-short:`\TYPO3\CMS\IndexedSearch\Event\ModifySearchResultSetsEvent`.
.. index:: Frontend, TypoScript, ext:indexed_search
@@ -0,0 +1,80 @@
.. include:: /Includes.rst.txt
.. _feature-105649-1743710535:
======================================================
Feature: #105649 - New PSR-14 CustomFileSelectorsEvent
======================================================
See :issue:`105649`
Description
===========
A new PSR-14 event :php:`\TYPO3\CMS\Backend\Form\Event\CustomFileSelectorsEvent`
has been added. It is dispatched in
:php-short:`\TYPO3\CMS\Backend\Form\Container\FilesControlContainer`
during the rendering of selectors for relations to `sys_file_references`.
To modify the selectors used to add files, the following methods are
available:
* :php:`getSelectors()`: Get all selectors
* :php:`setSelectors()`: Set all selectors
* :php:`getJavascriptModules()`: Get all JavaScript modules
* :php:`setJavascriptModules()`: Set all JavaScript modules
* :php:`getTableName()`: Get the table name of the current record
* :php:`getFieldName()`: Get the field name of the element
* :php:`getDatabaseRow()`: Get the raw database row
* :php:`getFieldConfig()`: Get the TCA configuration of the current field
* :php:`getFileExtensionFilter()`: Get the allowed and disallowed file
extensions
* :php:`getFormFieldIdentifier()`: Get the DOM object ID used in the form
Example
-------
The corresponding event listener class:
.. code-block:: php
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Backend\Form\Event\CustomFileSelectorsEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'my-extension/custom-file-selector')]
final class CustomFileSelectorEventListener
{
public function __construct(
private CustomDamFileSelector $damFileSelector,
) {}
public function __invoke(CustomFileSelectorsEvent $event): void
{
$result = $this->damFileSelector->renderFileSelector(
$event->getFormFieldIdentifier(),
);
$event->setSelectors(array_merge(
$event->getSelectors(),
$result['control'],
));
$event->setJavascriptModules(array_merge(
$event->getJavascriptModules(),
$result['javaScriptModule'],
));
}
}
Impact
======
It is now possible to modify file selectors using the new PSR-14 event
:php-short:`\TYPO3\CMS\Backend\Form\Event\CustomFileSelectorsEvent`. This is
especially useful for integrating a DAM system.
.. index:: Backend, PHP-API, ext:backend
@@ -0,0 +1,236 @@
.. include:: /Includes.rst.txt
.. _feature-105708-1739721600:
=============================================================
Feature: #105708 - Multiple file upload for EXT:form elements
=============================================================
See :issue:`105708`
Description
===========
The TYPO3 form framework now supports multiple file uploads in the
:yaml:`FileUpload` and :yaml:`ImageUpload` form elements. This allows users
to select and upload multiple files using a single form field.
The implementation follows the same security patterns as Extbase file upload
handling. It uses HMAC-signed deletion requests to ensure secure file removal.
Configuration
-------------
To enable multiple file uploads for a form element, set the :yaml:`multiple`
property to :yaml:`true` in your form definition:
.. code-block:: yaml
:caption: fileadmin/form_definitions/someForm.yaml
:emphasize-lines: 14-15,26-27
type: Form
identifier: contact-form
label: 'Contact Form'
prototypeName: standard
renderables:
- type: Page
identifier: page-1
label: 'Page 1'
renderables:
- type: FileUpload
identifier: attachments
label: 'Attachments'
properties:
multiple: true
allowRemoval: true
saveToFileMount: '1:/user_upload/'
allowedMimeTypes:
- application/pdf
- image/jpeg
- type: ImageUpload
identifier: images
label: 'Images'
properties:
multiple: true
allowRemoval: true
saveToFileMount: '1:/user_upload/'
allowedMimeTypes:
- image/jpeg
- image/png
The :yaml:`multiple` option is also available in the Form Editor backend
module as a checkbox in the element's inspector panel.
The :yaml:`allowRemoval` property enables users to remove previously uploaded
files before submitting the form. When enabled, a `Remove` checkbox is
displayed next to each uploaded file.
File count validation
---------------------
The existing :yaml:`Count` validator can now be used with
:yaml:`FileUpload` and :yaml:`ImageUpload` elements to limit the number of
uploaded files:
.. code-block:: yaml
:caption: fileadmin/form_definitions/someForm.yaml
- type: FileUpload
identifier: attachments
label: 'Attachments'
properties:
multiple: true
validators:
- identifier: Count
options:
minimum: 1
maximum: 5
Frontend rendering
------------------
When :yaml:`multiple` is enabled:
* The file input field renders with the HTML5 :html:`multiple` attribute
* Previously uploaded files are displayed in a list with individual remove
checkboxes
* Users can select multiple files in the browser's file picker dialog
* On the summary page, multiple files are displayed as a list
File deletion
-------------
The implementation uses HMAC-signed deletion requests similar to Extbase file
handling. Each uploaded file displays a checkbox that, when checked, marks the
file for removal on form submission. The deletion data is signed with an HMAC
to prevent manipulation.
A new ViewHelper, :html:`<formvh:form.uploadDeleteCheckbox>`, is available
for custom templates:
.. code-block:: html
<formvh:form.uploadDeleteCheckbox
property="{element.identifier}"
fileReference="{file}"
fileIndex="{iterator.index}"
/>
Adapting custom finishers for multiple file uploads
---------------------------------------------------
When :yaml:`multiple` is enabled on a :yaml:`FileUpload` element, the value
returned by :php:`$formRuntime[$element->getIdentifier()]` is an
:php:`ObjectStorage<FileReference>` instead of a single
:php:`FileReference`. Custom finishers that process file uploads need to be
adapted to handle both cases, single and multiple uploads.
The following example shows the pattern used in the core
:php:`EmailFinisher` and :php:`DeleteUploadsFinisher`:
.. code-block:: php
:caption: EXT:my_extension/Classes/Domain/Finishers/MyFinisher.php
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher;
use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload;
class MyFinisher extends AbstractFinisher
{
protected function executeInternal(): void
{
$formRuntime = $this->finisherContext->getFormRuntime();
foreach (
$formRuntime->getFormDefinition()->getRenderablesRecursively()
as $element
) {
if (!$element instanceof FileUpload) {
continue;
}
$file = $formRuntime[$element->getIdentifier()];
// Single file upload: value is a FileReference
if ($file instanceof FileReference) {
$this->processFile($file->getOriginalResource());
}
// Multiple file upload: value is an ObjectStorage of FileReferences
if ($file instanceof ObjectStorage) {
foreach ($file as $singleFile) {
if ($singleFile instanceof FileReference) {
$this->processFile(
$singleFile->getOriginalResource()
);
}
}
}
}
}
private function processFile(FileInterface $file): void
{
// Your custom logic, e.g. move, copy, attach, etc.
}
}
Per-element validation with ObjectStorageElementValidatorInterface
------------------------------------------------------------------
When a form field value is an
:php-short:`\TYPO3\CMS\Extbase\Persistence\ObjectStorage`, for example, a
multiple-file upload, the :php:`ProcessingRule` must decide how to call each
registered validator:
* **Collection-level validators** (default) receive the entire
:php-short:`\TYPO3\CMS\Extbase\Persistence\ObjectStorage`. Use this for
validators that check the collection as a whole, such as
:php-short:`\TYPO3\CMS\Form\Mvc\Validation\CountValidator` for the minimum
or maximum number of items.
* **Element-level validators** receive each item individually. Use this for
validators that inspect a single item, such as
:php-short:`\TYPO3\CMS\Form\Mvc\Validation\MimeTypeValidator` or
:php-short:`\TYPO3\CMS\Form\Mvc\Validation\FileSizeValidator`.
To mark a validator as element-level, implement the marker interface
:php-short:`\TYPO3\CMS\Form\Mvc\Validation\ObjectStorageElementValidatorInterface`:
.. code-block:: php
:caption: EXT:my_extension/Classes/Validation/MyPerFileValidator.php
use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
use TYPO3\CMS\Form\Mvc\Validation\ObjectStorageElementValidatorInterface;
final class MyFileValidator extends AbstractValidator implements
ObjectStorageElementValidatorInterface
{
public function isValid(mixed $value): void
{
// $value is a single element from the ObjectStorage,
// e.g. a FileReference - not the whole collection.
}
}
For single-value fields, that is, non-
:php-short:`\TYPO3\CMS\Extbase\Persistence\ObjectStorage` values, the
interface has no effect. Validators are always called with the field value
directly.
Impact
======
* Form integrators can now create forms that accept multiple file uploads
without custom extensions
* The :yaml:`FileUpload` and :yaml:`ImageUpload` elements support the new
:yaml:`multiple` property
* All existing finishers, `EmailFinisher`, `SaveToDatabaseFinisher`, and
`DeleteUploadsFinisher`, automatically support multiple file uploads
* Email templates display multiple files as a list of filenames
* The summary page displays multiple images as a gallery and multiple files
as a list of filenames
.. index:: Frontend, ext:form
@@ -0,0 +1,91 @@
.. include:: /Includes.rst.txt
.. _feature-105742-1755084132:
=================================================================
Feature: #105742 - Synchronized manipulation of all crop variants
=================================================================
See :issue:`105742`
Description
===========
The image manipulation wizard allows images to be cropped to multiple
crop variants. When many variants were present, each had to be edited
individually, requiring the same changes to be applied multiple times.
This was particularly tedious when an editor needed identical crop values
across all image variants.
This feature introduces a checkbox that allows editors to crop all image variants
simultaneously. This checkbox is available if all crop variants share
identical aspect ratios and configuration (except for the title).
`excludeFromSync` is a new sub-option of the `cropVariants` TCA/TCEFORM
configuration array which allows developers to exclude specific crop variants
from synchronized cropping.
This is useful, for example, when adding a special crop variant for a list
view that has a different configuration, while still allowing other crop
variants to be synchronized.
Example
=======
The following example defines standard crop variants for a Bootstrap-based
template.
All crop variants are configured identically (except for the title), which
enables synchronized cropping.
An additional crop variant for the `tx_news` list view is defined.
The option `excludeFromSync = 1` ensures that this variant is excluded
from synchronization.
.. code-block:: typoscript
TCEFORM.sys_file_reference.crop.config.cropVariants {
xxl {
title = Very Large Desktop
selectedRatio = NaN
allowedAspectRatios {
# [...] array of defined aspect ratios (identical!)
}
}
xl {
title = Large Desktop
selectedRatio = NaN
allowedAspectRatios {
# [...] array of defined aspect ratios (identical!)
}
}
# [...]
}
# Override for news extension
TCEFORM.tx_news_domain_model_news.fal_media.config {
overrideChildTca.columns.crop.config.cropVariants {
listview {
title = List view
selectedRatio = default
excludeFromSync = 1
allowedAspectRatios {
# [...] array of custom aspect ratio definitions
# (or identical aspect ratios, but not considered for
# synchronized cropping)
}
}
}
}
Impact
======
Editors can now apply changes to image aspect ratios and cropping to
multiple matching crop variants in the image manipulation wizard.
Specific `cropVariants` can be excluded from synchronization.
.. index:: Backend, ext:core
@@ -0,0 +1,73 @@
.. include:: /Includes.rst.txt
.. _feature-105827-1734338503:
===================================================================================================
Feature: #105827 - Search in backend page tree and live search can find pages by their frontend URI
===================================================================================================
See :issue:`105827` and :issue:`105833`
Description
===========
The backend page tree search functionality has been enhanced to allow users to
enter a full URI such as `https://mysite.example.com/de/any/subtree/page/`,
which shows the matching page in the result tree.
Multiple URIs can be separated with commas (`,`), just like multiple page IDs.
It is also possible to combine different search input:
.. code-block::
:caption: Combining multiple search parts
4,8,https://example.com/first,http://sub.example.com/en/second,anyPageTitle
Matches in frontend URIs of translated pages are marked accordingly.
This functionality uses the PSR-14 event
:php-short:`TYPO3\CMS\Backend\Tree\Repository\BeforePageTreeIsFilteredEvent`
(see :ref:`feature-105833-1734420558`) and can serve as
inspiration for custom search variations.
In addition, live search has been enhanced to perform the same lookup based on
a single URI. This is achieved with the new PSR-14 event
:php-short:`TYPO3\CMS\Backend\Search\Event\ModifyConstraintsForLiveSearchEvent`
(see :ref:`feature-105827-1751912675`).
Live search returns both the default language page derived from the URI and the
matching translated page.
Configuration
=============
Search by frontend URI is enabled by default and can be controlled in two ways,
similar to :ref:`search by translation <feature-107961-1762076523>`:
User TSconfig
-------------
Administrators can control the availability of frontend URI search with
user TSconfig:
.. code-block:: typoscript
# Disable searching by frontend URI for specific users/groups
options.pageTree.searchByFrontendUri = 0
User preference
---------------
Individual backend users can toggle this setting in the page tree toolbar menu.
The preference is stored in the backend user's configuration, allowing each
user to customize search behavior.
Impact
======
Editors can now easily locate a backend page when only the frontend URI is
available. Permissions to view or edit the page are respected. Invalid or
non-matching URIs are ignored.
.. index:: Backend, ext:backend
@@ -0,0 +1,94 @@
.. include:: /Includes.rst.txt
.. _feature-105827-1751912675:
=================================================================
Feature: #105827 - New PSR-14 ModifyConstraintsForLiveSearchEvent
=================================================================
See :issue:`105827`, :issue:`105833`, :issue:`93494`
Description
===========
A new PSR-14 event
:php:`\TYPO3\CMS\Backend\Search\Event\ModifyConstraintsForLiveSearchEvent`
has been added to TYPO3 Core. This event is dispatched in the
:php-short:`TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch` class and allows
extensions to modify the
:php-short:`TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression`
constraints collected in an array before execution.
This makes it possible to add additional constraints to the main query
constraints, combined with a logical `OR`. These constraints could
not previously be accessed by the existing event
:php-short:`TYPO3\CMS\Backend\Search\Event\ModifyQueryForLiveSearchEvent`.
The event provides the following methods:
* :php:`getConstraints()`: Returns the current array of query constraints
(composite expressions).
* :php:`addConstraint()`: Adds a single constraint.
* :php:`addConstraints()`: Adds multiple new constraints.
* :php:`getTableName()`: Returns the table for which the query is executed
(for example, `pages` or `tt_content`).
* :php:`getSearchDemand()`: Returns the search demand.
.. hint::
Constraints are intended to be added only. This ensures that
security-related mandatory constraints added by Core or extensions cannot
be negatively affected. For this reason, there is no way to remove a
constraint after it has been added.
Example
=======
The corresponding event listener class:
.. code-block:: php
<?php
namespace Vendor\MyPackage\Backend\EventListener;
use TYPO3\CMS\Backend\Search\Event\ModifyConstraintsForLiveSearchEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Database\ConnectionPool;
final readonly class PageRecordProviderEnhancedSearch
{
public function __construct(
private ConnectionPool $connectionPool,
) {}
#[AsEventListener('my-package/livesearch-enhanced')]
public function __invoke(
ModifyConstraintsForLiveSearchEvent $event,
): void {
if ($event->getTableName() !== 'pages') {
return;
}
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
// Add a constraint so that pages marked with "show_in_all_results=1"
// will always be shown.
$constraints[] = $queryBuilder->expr()->eq(
'show_in_all_results',
1,
);
$event->addConstraints(...$constraints);
}
}
Core itself uses this event to allow searching for frontend URIs in the
backend page tree.
Impact
======
A new PSR-14 event is now available for adding constraints to the live search
query. These constraints are combined with a logical `OR`.
.. index:: Backend, PHP-API, ext:backend
@@ -0,0 +1,57 @@
.. include:: /Includes.rst.txt
.. _feature-106153-1770150965:
========================================================================
Feature: #106153 - Improve DebugExceptionHandler with copy functionality
========================================================================
See :issue:`106153`
Description
===========
The debugging exception handler, which can be configured for backend and
frontend error reporting, provides a large stack trace with details about
an error.
This is often essential when reporting bugs in TYPO3 or debugging custom
code.
The output has now been improved:
* Each stack trace segment's file name and the line number where the error
occurred now has a "Copy path" button. Clicking it copies the full
path, file name, and line number to the browser clipboard.
* The bottom of the page shows two buttons: one toggles the output above
to hide or reveal the file contents, and the other copies the entire
stack trace in plain text format so that it can be forwarded in error
reports.
* A brief section explains what a "stack trace" is, and a jump link is
available to go from the top of the page to the export section.
.. hint::
The "copy to clipboard" functionality is based on JavaScript. Some
browsers, such as Firefox, allow access to the clipboard only when
the site is accessed via `https`. If copying fails, the condensed
output that would have been written to the clipboard is shown instead
in a box below so it can be copied manually.
Thanks to Olivier Dobberkau, whose extension
`https://github.com/dkd-dobberkau/enhanced-error-handler`__ inspired the
rework of this feature.
Impact
======
Errors and their stack traces can now be copied and forwarded much more
easily for support requests, without the need to save an HTML file or take
screenshots.
File names and line numbers of errors can also be copied easily and
inserted into an IDE to jump directly to the relevant code.
.. index:: Backend, ext:core
@@ -0,0 +1,104 @@
.. include:: /Includes.rst.txt
.. _feature-106261-1762614000:
=========================================================================================
Feature: #106261 - Align command line arguments of message consumer with Symfony original
=========================================================================================
See :issue:`106261`
Description
===========
This change aligns the command line arguments of the TYPO3 Console
`messenger:consume` command with the original Symfony Messenger
implementation.
The following new options have been added:
* :bash:`--limit` / :bash:`-l`: Limits the number of received messages.
* :bash:`--failure-limit` / :bash:`-f`: Limits the number of failed
messages the worker can consume.
* :bash:`--memory-limit` / :bash:`-m`: Sets the memory limit available
to the worker.
* :bash:`--time-limit` / :bash:`-t`: Sets the time limit in seconds
during which the worker can handle new messages.
* :bash:`--bus` / :bash:`-b`: Specifies the name of the bus to which
received messages are dispatched.
* :bash:`--all`: Consumes messages from all receivers.
* :bash:`--keepalive`: Uses the transport keepalive mechanism, if
implemented.
Scheduler integration
=====================
The command can be configured as a scheduler task in TYPO3, enabling
automated consumption of messages from the configured transports. This is
particularly useful for processing asynchronous messages in the
background.
This integration helps projects adopt asynchronous message handling by
providing a reliable way to process messages without manual
intervention. Messages can be dispatched asynchronously during normal
request handling and consumed in the background by the scheduler task,
improving application performance and user experience.
.. important::
The :bash:`messenger:consume` command blocks other scheduler tasks
from executing while it is running. It is therefore strongly
recommended to set the :bash:`--time-limit` option to a value lower
than the scheduler's cron interval.
For example, if the scheduler runs every 5 minutes (300 seconds),
set the time limit to 240 seconds (4 minutes) to ensure the task
completes before the next scheduler run and allows other tasks to
execute.
Usage
=====
Consume messages from a specific receiver:
.. code-block:: bash
vendor/bin/typo3 messenger:consume my_receiver
Consume messages with a message limit:
.. code-block:: bash
vendor/bin/typo3 messenger:consume my_receiver --limit=10
Stop the worker after 2 failed messages:
.. code-block:: bash
vendor/bin/typo3 messenger:consume my_receiver --failure-limit=2
Stop the worker when the memory limit is exceeded:
.. code-block:: bash
vendor/bin/typo3 messenger:consume my_receiver --memory-limit=128M
Stop the worker after a time limit:
.. code-block:: bash
vendor/bin/typo3 messenger:consume my_receiver --time-limit=3600
Consume from specific queues only:
.. code-block:: bash
vendor/bin/typo3 messenger:consume my_receiver --queues=fasttrack
Consume from all configured receivers:
.. code-block:: bash
vendor/bin/typo3 messenger:consume --all
.. index:: PHP-API, ext:core
@@ -0,0 +1,85 @@
.. include:: /Includes.rst.txt
.. _feature-106640-1766572100:
====================================================================
Feature: #106640 - Localize enum labels in site settings definitions
====================================================================
See :issue:`106640`
Description
===========
Enum option labels in site settings definitions can now be localized
consistently.
This applies to all common enum declaration styles:
* List-style enum declarations derive localization keys using
:code:`settings.<settingKey>.enum.<enumValue>` in the set labels file.
* Map-style enum declarations are independent of that key schema. Only
the configured label value is evaluated.
* Map-style enum declarations with localization references
(:code:`LLL:...`) resolve these references.
* Map-style enum declarations with literal labels keep these labels
as-is.
* Map-style key-only enum entries fall back to the enum value.
* Map-style empty string labels remain empty strings.
Example
=======
.. code-block:: yaml
:caption: List-style enum declaration in settings.definitions.yaml
settings:
my.enumSetting:
type: string
default: optionA
enum:
- optionA
- optionB
.. code-block:: xml
:caption: Matching labels in labels.xlf
<trans-unit id="settings.my.enumSetting.enum.optionA">
<source>Option A (localized)</source>
</trans-unit>
<trans-unit id="settings.my.enumSetting.enum.optionB">
<source>Option B (localized)</source>
</trans-unit>
.. code-block:: yaml
:caption: Map-style enum declaration in settings.definitions.yaml
settings:
my.enumSetting:
type: string
default: optionA
enum:
optionA: 'LLL:my_extension.labels:settings.custom.optionA' # Explicit LLL reference
optionB: 'Literal Option B' # Literal label
optionC: # Key-only map-style entry, falls back to enum value "optionC"
optionD: '' # Empty label stays empty
.. code-block:: xml
:caption: Referenced label in labels.xlf
<trans-unit id="settings.custom.optionA">
<source>Option A (localized)</source>
</trans-unit>
If you want to work with automatically derived keys in the set
:file:`labels.xlf`, for example
:code:`settings.<settingKey>.enum.<enumValue>`, omit enum labels in YAML
and use list-style enum declarations.
Impact
======
Integrators can localize enum options consistently using the same
resolution behavior as other setting labels.
.. index:: YAML, Backend, ext:core
@@ -0,0 +1,85 @@
.. include:: /Includes.rst.txt
.. _feature-106681-1740000000:
=======================================================================
Feature: #106681 - Support relative date formats in DateRange validator
=======================================================================
See :issue:`106681`
Description
===========
The :yaml:`DateRange` validator of the form extension now supports relative
as well as absolute dates in `Y-m-d` format.
This allows form integrators to define dynamic date constraints that are
evaluated at runtime, such as ensuring a date of birth is at least
18 years in the past or that a date is in the future.
The following relative expressions are supported and follow the syntax of
:php:`strtotime()`:
* Named dates: :yaml:`today`, :yaml:`now`, :yaml:`yesterday`,
:yaml:`tomorrow`
* Relative offsets: :yaml:`-18 years`, :yaml:`+1 month`,
:yaml:`-2 weeks`, :yaml:`+30 days`
These expressions can be used in the :yaml:`options.minimum` and
:yaml:`options.maximum` properties of the :yaml:`DateRange` validator.
Example
=======
Ensure that a date of birth is at least 18 years in the past:
.. code-block:: yaml
type: Date
identifier: date-of-birth
label: 'Date of birth'
validators:
-
identifier: DateRange
options:
maximum: '-18 years'
Ensure that a date is in the future:
.. code-block:: yaml
type: Date
identifier: event-date
label: 'Event date'
validators:
-
identifier: DateRange
options:
minimum: '+1 day'
Mixed absolute and relative dates are also supported:
.. code-block:: yaml
validators:
-
identifier: DateRange
options:
minimum: '2020-01-01'
maximum: 'today'
The form editor in the TYPO3 backend has been updated to accept these
relative expressions in the date range fields. The HTML :html:`min` and
:html:`max` attributes on the rendered
:html:`<input type="date">` element are automatically resolved to
absolute `Y-m-d` dates at render time.
Impact
======
Form integrators can now use relative date expressions in the
:yaml:`DateRange` validator configuration. Existing form definitions
using absolute dates will continue to work without changes.
.. index:: Frontend, Backend, ext:form
@@ -0,0 +1,69 @@
.. include:: /Includes.rst.txt
.. _feature-106828-1751343863:
=========================================================================
Feature: #106828 - Add user TSconfig to define default live search action
=========================================================================
See :issue:`106828`
Description
===========
A new user TSconfig option :typoscript:`options.liveSearch.actions` has
been introduced to allow integrators to define the default behavior of a
search.
Available actions:
* `edit`: Opens the edit form of the record. This is the default for
all tables except `pages`.
* `layout`: Opens the page in the Page module. This is the default for
the `pages` table.
* `list`: Opens the storage page of the record in the Record List
module.
* `preview`: Opens the record in the frontend.
.. important::
The `layout` action can only be used for the :sql:`pages` and
:sql:`tt_content` tables.
Examples
========
Set the default for all tables:
.. code-block:: typoscript
options.liveSearch.actions.default = edit
Set the default for the `tt_content` table:
.. code-block:: typoscript
options.liveSearch.actions.tt_content.default = layout
Set the default for a custom table:
.. code-block:: typoscript
options.liveSearch.actions.my_table.default = preview
.. note::
To use `preview` for a custom record, a valid preview configuration
must exist for the table in `TCEMAIN.preview`.
Impact
======
The default actions of live search results can now be configured with
user TSconfig. Integrators can define global and table-specific behavior
for search results, improving backend workflows.
The default behavior for `pages` has been changed to `layout` to improve
the user workflow.
.. index:: Backend, TSConfig, ext:backend
@@ -0,0 +1,71 @@
.. include:: /Includes.rst.txt
.. _feature-107003-1751223220:
===============================================================
Feature: #107003 - Add event to change record data in list view
===============================================================
See :issue:`107003`
Description
===========
A new PSR-14 event
:php:`\TYPO3\CMS\Backend\RecordList\Event\AfterRecordListRowPreparedEvent`
has been added. This event is dispatched in
:php-short:`TYPO3\CMS\Backend\RecordList\DatabaseRecordList` and allows
extensions to modify the data used to render a single record in the list
view.
The event allows the following properties to be modified:
* `data`: The row fields as an array. The following fields are available:
* `_SELECTOR_`: The checkbox element
* `icon`: The icon
* `__label`: Special field that contains the header
* `_CONTROL_`: The row action buttons
* `_LOCALIZATION_`: The current language
* `_LOCALIZATION_b`: The translated language
* `rowDescription`: The row description
* `header`: The header. This field is used only if `__label` is not
set. Use `__label` instead.
* `uid`: The record UID (read-only)
* `tagAttributes`: The HTML tag attributes of the row. The following
attributes are available:
* `class`
* `data-table`
* `title`
The corresponding event listener class:
.. code-block:: php
use TYPO3\CMS\Backend\RecordList\Event\AfterRecordListRowPreparedEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener('my-package/backend/my-listener-name')]
final class MyEventListener
{
public function __invoke(AfterRecordListRowPreparedEvent $event): void
{
$data = $event->getData();
$tagAttributes = $event->getTagAttributes();
// Modify the row data and tag attributes here.
$event->setData($data);
$event->setTagAttributes($tagAttributes);
}
}
Impact
======
The new PSR-14 event can be used, for example, to modify the title link in
the record list.
.. index:: PHP-API, ext:backend
@@ -0,0 +1,70 @@
.. include:: /Includes.rst.txt
.. _feature-107058-1769168658:
=================================================================
Feature: #107058 - Simplify registration of a custom form element
=================================================================
See :issue:`107058`
Description
===========
The registration of custom form elements in the TYPO3 Form Framework has
been simplified. Previously, registering a custom form
element required subscribing to the JavaScript event
:js:`view/stage/abstract/render/template/perform` to render the element
in the Form Editor stage area.
Custom form elements can now be registered without
custom JavaScript code. The Form Editor automatically uses a generic
Web Component to render form elements in the stage area.
To use this simplified registration method, omit the
:yaml:`formEditorPartials` configuration in your form element's YAML
definition. The Form Editor then automatically renders the element using
the built-in :html:`<typo3-form-form-element-stage-item>` web component,
which provides:
* Element type and identifier display
* Element label with required indicator
* Validator visualization
* Support for select options (`SingleSelect`, `MultiSelect`,
`RadioButton`, `Checkbox`)
* Support for allowed MIME types (`FileUpload`, `ImageUpload`)
* Element toolbar
* Hidden state visualization
The generic rendering automatically extracts and displays relevant
information from the form element configuration without requiring a
custom template or JavaScript code.
Impact
======
Extension developers can now register custom form elements with minimal
configuration. By omitting the :yaml:`formEditorPartials`
configuration, the Form Editor automatically renders the element using a
generic Web Component, eliminating the need for:
* Custom Fluid templates in
:file:`Resources/Private/Backend/Partials/FormEditor/Stage/`
* Custom JavaScript code subscribing to
:js:`view/stage/abstract/render/template/perform`
* Manual element rendering logic
This significantly reduces the complexity and maintenance burden when
creating custom form elements that do not require special visualization
in the Form Editor.
For custom form elements that require specialized rendering or custom
interactions in the stage area, the :yaml:`formEditorPartials`
configuration can still be used to provide custom Fluid templates, which
continue to work as before.
For a complete step-by-step tutorial on creating custom form elements,
see :ref:`Creating a Custom Form Element
<typo3/cms-form:howtos-custom-form-element>`.
.. index:: Backend, ext:form
@@ -0,0 +1,128 @@
.. include:: /Includes.rst.txt
.. _feature-107289-1734172800:
==================================================================
Feature: #107289 - Automatic history tracking for Extbase entities
==================================================================
See :issue:`107289`
Description
===========
TYPO3 now tracks the history of all Extbase domain entities by
listening to Extbase persistence events and storing them in the
:sql:`sys_history` table. This provides a comprehensive audit trail for
all frontend and backend operations on Extbase entities without requiring
any code changes.
The feature leverages TYPO3's existing
:php-short:`TYPO3\CMS\Backend\History\RecordHistoryStore`
infrastructure and integrates seamlessly with the backend record history
functionality.
The history tracking captures:
* Create operations: when entities are persisted for the first time
* Update operations: when existing entities are modified
* Delete operations: when entities are removed from persistence
All operations are tracked with their proper user context (frontend users,
backend users, anonymous operations) and include full entity data
snapshots.
Configuration
=============
History tracking is **disabled** by default. It can be enabled with the
feature toggle `extbase.enableHistoryTracking` (available via
:guilabel:`System > Settings > Feature toggles`).
Once the feature toggle is enabled, history tracking is active for all
Extbase domain model storage tables. It can then be **disabled** via TCA
on a per-table basis:
.. code-block:: php
:emphasize-lines: 11-13
:caption: EXT:my_extension/Configuration/TCA/tx_myextension_domain_model_blog.php
<?php
declare(strict_types=1);
return [
'ctrl' => [
'title' => 'my_extension.messages:my_title',
'label' => 'uid',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'delete' => 'deleted',
// ...
'extbase' => [
'enableHistoryTracking' => false,
],
],
'columns' => [
// ...
],
];
Defining this at the TCA level (instead of TypoScript `persistence`
configuration) means that it can be configured per table and
evaluated consistently in all contexts (backend, frontend, CLI).
If a third-party extension enables history tracking via TCA, it can be
disabled using TCA overrides. Disabling the feature toggle also disables
all history tracking, even for tables configured with
`enableHistoryTracking => true`.
In addition, the following PSR-14 event listeners can be deregistered or
replaced at instance level:
* `extbase-history-tracker-persisted`
* `extbase-history-tracker-updated`
* `extbase-history-tracker-removed`
.. note::
Enabling history tracking can generate a large number of history
entries for Extbase entities. These entries are mixed with regular
editorial changes made in the TYPO3 backend (FormEngine).
.. important::
All changes to Extbase entity data are logged, including full initial
data snapshots. This may have implications for GDPR / DSGVO and other
security-related data handling requirements. Data may need to be
pruned regularly. It is advisable to disable history tracking for
tables containing sensitive data. For this reason, the feature toggle
is disabled by default and requires explicit activation.
Impact
======
Changes to all Extbase domain entities can now be tracked
in the :sql:`sys_history` table, making them visible in the
backend record history. This requires enabling the feature toggle
`extbase.enableHistoryTracking` (default: `false`).
This feature provides administrators and developers with full visibility
into data changes without requiring interface implementations or code
modifications.
Technical details
=================
The implementation consists of a PSR-14 event listener
:php-short:`TYPO3\CMS\Extbase\EventListener\ExtbaseHistoryTracker`
which automatically registers for the following Extbase persistence
events:
* :php-short:`TYPO3\CMS\Extbase\Event\Persistence\EntityAddedToPersistenceEvent`
* :php-short:`TYPO3\CMS\Extbase\Event\Persistence\EntityUpdatedInPersistenceEvent`
* :php-short:`TYPO3\CMS\Extbase\Event\Persistence\EntityRemovedFromPersistenceEvent`
All entities with valid TCA configuration are tracked automatically. This
uses the Extbase DataMap API, TCA Schema API, and RecordHistoryStore API.
.. index:: PHP-API, ext:extbase
@@ -0,0 +1,45 @@
.. include:: /Includes.rst.txt
.. _feature-107771-1760360529:
====================================================================
Feature: #107771 - Make rel attribute in external links configurable
====================================================================
See :issue:`107771`
Description
===========
For security reasons, external links that open in a new window should be
generated with :code:`rel="noopener"` to prevent the opened page from
accessing the originating document via JavaScript's
:code:`Window.opener` object.
TYPO3's default behavior is to add :code:`rel="noreferrer"` to all such
links. This automatically implies :code:`rel="noopener"` but is even more
restrictive, as it also prevents the HTTP `Referer` header from being sent to
the opened page. This may be too strict and therefore undesirable for some
website owners.
This feature introduces a new TypoScript option
:code:`config.linkSecurityRelValue` to define the :code:`rel`
attribute for external links. The default behavior remains
:code:`rel="noreferrer"`, but by setting the TypoScript property to
:code:`noopener`, all external links are generated with
:code:`rel="noopener"` instead.
The feature respects existing the individual settings of a link. Any existing
:code:`rel="noopener"` and :code:`rel="noreferrer"` values from other
sources are preserved.
Impact
======
A new TypoScript configuration option
:code:`config.linkSecurityRelValue` is available and can be set to
`noreferrer` (default) or `noopener`.
This setting affects all external links with :code:`target="_blank"`.
.. index:: Frontend
@@ -0,0 +1,42 @@
.. include:: /Includes.rst.txt
.. _feature-107802-1770827507:
=========================================================================
Feature: #107802 - Support username and password in Redis session backend
=========================================================================
See :issue:`107802`
Description
===========
Since Redis 6.0, it is possible to authenticate against Redis using
both a username and a password. Before that, authentication was possible
by password only. This change means the TYPO3 Redis session backend
can be configured as follows:
.. code-block:: php
:caption: config/system/additional.php
use TYPO3\CMS\Core\Session\Backend\RedisSessionBackend;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['session']['BE'] = [
'backend' => RedisSessionBackend::class,
'options' => [
'database' => 0,
'hostname' => 'redis',
'port' => 6379,
'username' => 'redis',
'password' => 'redis',
],
];
Impact
======
The `password` configuration option of the Redis session backend is now
typed as `array|string`. Setting this configuration option to an array is
deprecated and will be removed in TYPO3 v15.0.
.. index:: LocalConfiguration, ext:core
@@ -0,0 +1,329 @@
.. include:: /Includes.rst.txt
.. _feature-107826-1766220191:
===================================================================
Feature: #107826 - Introduce Extbase action authorization attribute
===================================================================
See :issue:`107826`
Description
===========
A new authorization mechanism has been introduced for Extbase controller
actions using PHP attributes. Extension authors can now implement
declarative access control logic on action methods using the
:php:`#[Authorize]` attribute.
The :php:`#[Authorize]` attribute supports multiple authorization
strategies:
**Built-in checks:**
* Require frontend user login via :php:`requireLogin`
* Require specific frontend user groups via :php:`requireGroups`
**Custom authorization logic:**
* Dedicated authorization class (recommended for complex logic)
* Public controller method (for simple checks)
Multiple :php:`#[Authorize]` attributes can be stacked on a single
action. All authorization checks must pass for access to be granted. If
a check fails, a
:php-short:`\TYPO3\CMS\Core\Http\PropagateResponseException` is thrown with an
HTTP 403 response, which stops the Extbase dispatch process.
Examples
========
Require frontend user login
---------------------------
.. code-block:: php
:caption: EXT:my_extension/Classes/Controller/MyController.php
namespace MyVendor\MyExtension\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Attribute\Authorize;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class MyController extends ActionController
{
#[Authorize(requireLogin: true)]
public function listAction(): ResponseInterface
{
return $this->htmlResponse();
}
}
Require specific user groups
----------------------------
The `requireGroups` parameter accepts an array of frontend user group
identifiers. Groups can be specified either by their UID (recommended) or
by their title. If multiple groups are specified, the user must be a
member of at least one group (OR logic).
.. code-block:: php
:caption: EXT:my_extension/Classes/Controller/MyController.php
namespace MyVendor\MyExtension\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Attribute\Authorize;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class MyController extends ActionController
{
// Recommended: Use group UIDs
#[Authorize(requireGroups: [1, 2])]
public function adminListAction(): ResponseInterface
{
// Only accessible to users in groups 1 or 2
return $this->htmlResponse();
}
// Alternative: Use group titles (not recommended)
#[Authorize(requireGroups: ['administrators', 'editors'])]
public function editorListAction(): ResponseInterface
{
return $this->htmlResponse();
}
// Mixed: UIDs and titles can be combined (not recommended)
#[Authorize(requireGroups: [1, 'editors'])]
public function mixedListAction(): ResponseInterface
{
return $this->htmlResponse();
}
}
.. note::
It is **strongly recommended to use group UIDs** instead of group titles.
Group titles can be changed by editors, which would break the authorization
logic. Group UIDs are stable and should be preferred.
Custom authorization class
--------------------------
For complex authorization logic, create a dedicated authorization class.
This class supports dependency injection and can be reused across
controllers. The class must be publicly available in the DI container,
which can be achieved by annotating it with
:php:`#[Autoconfigure(public: true)]`.
.. code-block:: php
:caption: EXT:my_extension/Classes/Authorization/MyObjectAuthorization.php
namespace MyVendor\MyExtension\Authorization;
use MyVendor\MyExtension\Domain\Model\MyObject;
use TYPO3\CMS\Core\Context\Context;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
#[Autoconfigure(public: true)]
class MyObjectAuthorization
{
public function __construct(
protected readonly Context $context,
) {}
public function checkOwnership(MyObject $myObject): bool
{
$userAspect = $this->context->getAspect('frontend.user');
return $myObject->getOwner()->getUid()
=== $userAspect->get('id');
}
}
.. code-block:: php
:caption: EXT:my_extension/Classes/Controller/MyController.php
namespace MyVendor\MyExtension\Controller;
use MyVendor\MyExtension\Authorization\MyObjectAuthorization;
use MyVendor\MyExtension\Domain\Model\MyObject;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Attribute\Authorize;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class MyController extends ActionController
{
#[Authorize(callback: [MyObjectAuthorization::class, 'checkOwnership'])]
public function editAction(MyObject $myObject): ResponseInterface
{
$this->view->assign('myObject', $myObject);
return $this->htmlResponse();
}
#[Authorize(callback: [MyObjectAuthorization::class, 'checkOwnership'])]
public function deleteAction(MyObject $myObject): ResponseInterface
{
// Delete the object
return $this->htmlResponse();
}
}
Public controller method
------------------------
For simple checks, a public controller method can be used as a callback.
.. code-block:: php
:caption: EXT:my_extension/Classes/Controller/MyController.php
namespace MyVendor\MyExtension\Controller;
use MyVendor\MyExtension\Domain\Model\MyObject;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Extbase\Attribute\Authorize;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class MyController extends ActionController
{
public function __construct(
protected readonly Context $context,
) {}
#[Authorize(callback: 'checkOwnership')]
public function editAction(MyObject $myObject): ResponseInterface
{
$this->view->assign('myObject', $myObject);
return $this->htmlResponse();
}
public function checkOwnership(MyObject $myObject): bool
{
$userAspect = $this->context->getAspect('frontend.user');
return $myObject->getOwner()->getUid() === $userAspect->get('id');
}
}
Combining multiple authorization checks
----------------------------------------
Multiple :php:`#[Authorize]` attributes can be stacked. All checks must
pass.
.. code-block:: php
:caption: EXT:my_extension/Classes/Controller/MyController.php
namespace MyVendor\MyExtension\Controller;
use MyVendor\MyExtension\Authorization\MyObjectAuthorization;
use MyVendor\MyExtension\Domain\Model\MyObject;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Attribute\Authorize;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class MyController extends ActionController
{
#[Authorize(requireLogin: true)]
#[Authorize(requireGroups: [1, 2])]
#[Authorize(callback: [MyObjectAuthorization::class, 'checkOwnership'])]
public function editAction(MyObject $myObject): ResponseInterface
{
// Only accessible to logged-in users in groups 1 or 2 who own the object
return $this->htmlResponse();
}
}
Authorization checks can be combined within a single attribute:
.. code-block:: php
#[Authorize(requireLogin: true, requireGroups: [1, 2])]
public function adminAction(): ResponseInterface
{
return $this->htmlResponse();
}
Customizing the authorization denied response
----------------------------------------------
By default, the authorization check throws a
:php-short:`TYPO3\CMS\Core\Http\PropagateResponseException` with an HTTP
403 response. This response can be handled by the TYPO3 page error
handler configured in site settings.
The PSR-14 event
:php-short:`TYPO3\CMS\Extbase\Event\Mvc\BeforeActionAuthorizationDeniedEvent`
can be used to provide a custom PSR-7 response, which is then returned by
Extbase.
.. code-block:: php
:caption: EXT:my_extension/Classes/EventListener/CustomAuthorizationResponseListener.php
namespace MyVendor\MyExtension\EventListener;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use TYPO3\CMS\Extbase\Authorization\AuthorizationFailureReason;
use TYPO3\CMS\Extbase\Event\Mvc\BeforeActionAuthorizationDeniedEvent;
final class CustomAuthorizationResponseListener
{
public function __construct(
private readonly ResponseFactoryInterface $responseFactory,
private readonly StreamFactoryInterface $streamFactory,
) {}
public function __invoke(
BeforeActionAuthorizationDeniedEvent $event,
): void {
// Customize response based on failure reason
$message = match ($event->getFailureReason()) {
AuthorizationFailureReason::NOT_LOGGED_IN =>
'Please log in to access this page',
AuthorizationFailureReason::MISSING_GROUP =>
'You do not have permission to access this page',
AuthorizationFailureReason::CALLBACK_DENIED =>
'Access to this resource is denied',
};
$response = $this->responseFactory->createResponse()
->withHeader('Content-Type', 'text/html; charset=utf-8')
->withStatus(403)
->withBody($this->streamFactory->createStream($message));
$event->setResponse($response);
}
}
Security considerations
=======================
.. warning::
When using the
:php-short:`TYPO3\CMS\Extbase\Event\Mvc\BeforeActionAuthorizationDeniedEvent`
event:
* Do not perform state changes or modify domain objects in the
event listener. The authorization check happens before the action
is executed, and changes could lead to inconsistent data.
* Do not use Extbase persistence (for example, repository
operations or persist calls) in the event listener, as this may
result in unintended side effects.
* Custom PSR-7 responses should only be used for uncached Extbase
actions. For cached actions, the custom response may be cached
and served to all users regardless of their authorization
status. Ensure proper cache configuration when customizing
authorization responses.
Impact
======
Extension authors can now implement secure, declarative authorization
checks for Extbase controller actions using the
:php:`#[Authorize]` attribute.
.. index:: PHP-API, ext:extbase
@@ -0,0 +1,45 @@
.. include:: /Includes.rst.txt
.. _feature-107887-1761641914:
=====================================================
Feature: #107887 - New "Latest backend logins" widget
=====================================================
See :issue:`107887`
Description
===========
A new dashboard widget :guilabel:`Latest backend logins` has been
introduced to display recent backend user logins in
the TYPO3 Dashboard. This allows administrators to quickly monitor user
activity and track recent backend access patterns without navigating
the system log.
The widget provides a configurable interface where administrators can
set the number of logins to display, offering flexibility in monitoring
scope.
Each login entry shows:
* The backend user avatar and name
* The login time
Key benefits:
* Displays recent backend user logins with user details and
timestamps
* Provides direct access to login monitoring without navigating the
system log
* Offers configurable display limits for different monitoring needs
* Enhances security monitoring with quick access to login patterns
Impact
======
This feature improves administrative oversight by providing immediate
visibility of recent backend user activity in the
dashboard.
.. index:: Backend, ext:dashboard, Security
@@ -0,0 +1,45 @@
.. include:: /Includes.rst.txt
.. _feature-107906-1761739282:
===================================================
Feature: #107906 - Recently opened documents widget
===================================================
See :issue:`107906`
Description
===========
A new `Recently Opened Documents` dashboard widget has been introduced to
display documents that are currently open or recently accessed in the
TYPO3 backend. This allows editors and administrators to quickly return to
their work and access frequently edited content without navigating through
the page tree or search.
The widget provides a configurable interface where users can set the number
of documents to display, offering flexibility based on their workflow
needs. Each document entry shows the record icon and title for easy
identification and quick access.
Key benefits:
* Displays recently opened documents with icons and titles
* Provides quick access to ongoing work without searching
* Offers configurable display limits for different workflow needs
* Shows document type icons for visual identification
* Improves editing efficiency by reducing navigation time
* Displays documents in reverse chronological order (most recent first)
The widget retrieves documents from the FormEngine session data, ensuring
that only currently open or recently accessed documents are displayed.
Deleted records are filtered out to maintain data accuracy.
Impact
======
This feature improves editorial efficiency by providing immediate access to
recently opened documents in the dashboard interface, reducing
the time spent navigating through the backend to resume work.
.. index:: Backend, ext:dashboard, Usability
@@ -0,0 +1,48 @@
.. include:: /Includes.rst.txt
.. _feature-107940-1761853022:
============================================================
Feature: #107940 - Introduce report about content type usage
============================================================
See :issue:`107940`
Description
===========
A new :guilabel:`Content statistics` module has been introduced in the TYPO3
backend under :guilabel:`Reports`. This module provides information about the
usage of content elements in the TYPO3 site.
Overview
========
The overview displays all available content element types along with the number
of times each type is used.
All the associated fields of the element are listed, including key details such
as:
* The field type
* Whether it is marked as required
* Whether it can be configured as excludable via user group permissions
Detail view
===========
The detail view of each content element type lists all the relevant records
that are not marked as deleted.
Impact
======
The new report offers a convenient way to analyze and optimize content
structures within a TYPO3 installation.
It helps administrators and developers to:
* Identify unused content element types
* Understand which fields belong to specific content element types
* Gain insights into the overall configuration and diversity of content
elements
.. index:: Backend, ext:reports
@@ -0,0 +1,301 @@
.. include:: /Includes.rst.txt
.. _feature-108345-1774117214:
==========================================================================
Feature: #108345 - Allow extensions without ext_emconf.php in classic mode
==========================================================================
See :issue:`108345`
Description
===========
Initially :file:`ext_emconf.php` was the only file providing
extension metadata. Since the introduction of :file:`composer.json`,
now mandatory for extensions,
there are now two files containing a lot of redundant data.
This is now resolved by allowing an extension's :file:`composer.json`
to contain information that was previously defined in
:file:`ext_emconf.php`:
1. Extension title and description
2. Extension version
3. Extension state / update exclusion
4. Dependencies on other TYPO3 extensions
5. PHP version constraints
Extension title and description
-------------------------------
See :ref:`feature-108653-1767199420` for how the extension title and description can be set
individually in :file:`composer.json`.
Extension version
-----------------
The version number can be set in `extra.typo3/cms.version` or alternatively
in the `"version"` field in :file:`composer.json`.
For third-party extensions to be compatible with TYPO3 classic mode,
this version must now be set to the same version previously defined in :file:`ext_emconf.php`
and should match the version in the Git tag, for example when publishing to Packagist.
Fixture extensions used in tests can set any version number, for example `1.0.0`,
but a version number must still be provided to avoid deprecation messages.
During testing the version number is not evaluated.
TYPO3 Core extensions may omit the version number
in :file:`composer.json` because their version number is derived via
:php:`TYPO3\CMS\Core\Information\Typo3Version`.
Extension state and update exclusion
------------------------------------
The former `state` property in :file:`ext_emconf.php` was used for multiple purposes.
In :file:`composer.json`, this is now represented by dedicated metadata instead
of a single field.
Supported extension stability values are expressed as version suffixes, for example:
.. code-block:: json
{
"name": "vendor/example",
"type": "typo3-cms-extension",
"description": "Example extension",
"extra": {
"typo3/cms": {
"extension-key": "example_extension",
"version": "1.2.3-alpha4",
"Package": {
"providesPackages": {}
}
}
}
}
Supported Composer stability values are:
* `dev`
* `alpha`
* `beta`
* `RC`
* `stable`
For example:
* `1.2.3-dev`
* `1.2.3-alpha1`
* `1.2.3-beta2`
* `1.2.3-RC3`
* `1.2.3`
Values from the former `state` field that are not supported by Composer stability
can be expressed as build metadata by appending `+...` to the version string.
Example:
.. code-block:: json
{
"name": "vendor/example",
"type": "typo3-cms-extension",
"description": "Example extension",
"extra": {
"typo3/cms": {
"extension-key": "example_extension",
"version": "1.4.2+obsolete",
"Package": {
"providesPackages": {}
}
}
}
}
In this example, TYPO3 will treat the version as `1.0.0`, keep `obsolete`
as build metadata, and expose it in the Extension Manager.
The former `state = excludeFromUpdates` value from :file:`ext_emconf.php`
is now represented by a dedicated boolean flag in :file:`composer.json`:
.. code-block:: json
{
"name": "vendor/example",
"type": "typo3-cms-extension",
"description": "Example extension",
"extra": {
"typo3/cms": {
"extension-key": "example_extension",
"version": "1.2.3",
"exclude-from-updates": true,
"Package": {
"providesPackages": {}
}
}
}
}
This replaces overloading the former `state` field for update handling.
Dependencies on other TYPO3 extensions
--------------------------------------
:file:`ext_emconf.php` had a property for specifying dependencies
on other extensions by referencing the extension key and an optional
range of versions.
:file:`composer.json` also contains a field for specifying dependencies
using a Composer package name with a version range.
However, there is no direct way to distinguish whether such a package name
refers to another TYPO3 extension or to a regular Composer package
that should be installed from Packagist.
TYPO3, however, needs to know which other extensions an extension depends on
in order to resolve the extension loading order correctly.
Therefore, TYPO3 must know which package names refer to TYPO3 extensions
and which refer to regular Composer packages. In Composer mode, this can
be resolved automatically.
In classic mode, TYPO3 now recognizes several categories:
* TYPO3 framework packages shipped by the core
* Composer packages already installed and shipped with TYPO3
* Composer packages provided by other loaded extensions via
`providesPackages`
Because of this, extension authors do not need to repeat such package names
in `providesPackages`.
Extensions still need to declare Composer packages that they themselves provide
when loaded in classic mode. For those entries, `providesPackages` can also
define a relative path to a Composer vendor directory. If that directory contains
a Composer-generated `autoload.php`, TYPO3 includes it early during bootstrap.
This makes it possible to both declare Composer packages and bootstrap
their autoloader in a standardized way.
Here is an example of an extension that ships a local Composer vendor directory:
.. code-block:: json
{
"name": "vendor/example",
"type": "typo3-cms-extension",
"description": "Example extension",
"license": "GPL-2.0-or-later",
"require": {
"typo3/cms-core": "^14.2",
"vendor/other-example": "*",
"symfony/dotenv": "^8.0"
},
"extra": {
"typo3/cms": {
"extension-key": "example_extension",
"version": "1.2.3",
"Package": {
"providesPackages": {
"symfony/dotenv": "Resources/Private/Php/ComposerVendor"
}
}
}
}
}
In this example, the package `symfony/dotenv` is provided by the extension itself
in TYPO3 classic mode, and TYPO3 will include
`Resources/Private/Php/ComposerVendor/autoload.php` early if it is a
Composer-generated autoload file.
The Composer package names `typo3/cms-core` and `vendor/other-example`
are assumed to refer to TYPO3 extensions, and TYPO3 guarantees that `vendor/example`
is loaded after `vendor/other-example`. Otherwise, an error is thrown if
the extension `vendor/other-example` does not exist in the system.
Packages that are already shipped by TYPO3 or already provided by another loaded
extension do not need to be listed in `providesPackages`.
Even if an extension does not depend on any Composer packages,
it is still **required** to specify `providesPackages` in :file:`composer.json`
as an empty object to ensure future compatibility with TYPO3 classic mode
and to avoid deprecation messages in TYPO3 v14.
.. code-block:: json
{
"name": "vendor/example",
"type": "typo3-cms-extension",
"description": "Example extension",
"license": "GPL-2.0-or-later",
"require": {
"typo3/cms-core": "^14.2",
"vendor/other-example": "*"
},
"extra": {
"typo3/cms": {
"extension-key": "example_extension",
"version": "1.2.3",
"Package": {
"providesPackages": {}
}
}
}
}
PHP version constraints
-----------------------
PHP version constraints from :file:`ext_emconf.php` can also be represented in
the `require` section of :file:`composer.json`.
Example:
.. code-block:: json
{
"name": "vendor/example",
"type": "typo3-cms-extension",
"description": "Example extension",
"require": {
"typo3/cms-core": "^14.2",
"php": "^8.2"
},
"extra": {
"typo3/cms": {
"extension-key": "example_extension",
"version": "1.5.6",
"Package": {
"providesPackages": {}
}
}
}
}
The PHP dependency is kept as package metadata so TYPO3 classic mode
can still evaluate PHP version requirements. However, it is ignored for
extension dependency ordering.
Be aware that keeping :file:`ext_emconf.php`, while no longer directly required
by TYPO3, may still be necessary for some tools,
such as Tailor or TYPO3 TER. Therefore, for the time being, it is recommended
to keep the file and ensure that its information stays in sync
with :file:`composer.json` as outlined above.
However, TYPO3 will **not** evaluate :file:`ext_emconf.php` anymore if the required
metadata is correctly defined in :file:`composer.json` and package metadata can be
derived from it.
Impact
======
Extensions can now omit :file:`ext_emconf.php` in TYPO3 classic mode.
A deprecation message is shown during cache warm-up when :file:`ext_emconf.php`
is present and :file:`composer.json` is not yet future-proof
because it does not contain the required metadata definitions.
.. index:: ext:core
@@ -0,0 +1,47 @@
.. include:: /Includes.rst.txt
.. _feature-108557-1768611915:
===============================================================
Feature: #108557 - TCA option allowedRecordTypes for page types
===============================================================
See :issue:`108557`
Description
===========
A new TCA option :php:`allowedRecordTypes` is introduced for page types to
configure which database tables are allowed for specific types (`doktype`).
.. code-block:: php
:caption: EXT:my_extension/Configuration/TCA/Overrides/pages.php
// Allow any record on that page type.
$GLOBALS['TCA']['pages']['types']['116']['allowedRecordTypes'] = ['*'];
// Allow only specific tables on that page type.
$GLOBALS['TCA']['pages']['types']['116']['allowedRecordTypes'] = [
'tt_content',
'my_custom_record',
];
The array can contain a list of table names or a single asterisk entry (`*`)
to allow all record types.
By default, only the tables `pages`, `sys_category`, `sys_file_reference`, and
`sys_file_collection` are allowed if this option is not overridden.
The defaults are extended if TCA tables enable the option
`ctrl.security.ignorePageTypeRestriction`. Again, this is not considered if
:php:`allowedRecordTypes` is set. These tables must then also be configured
there.
Impact
======
The allowed record types for pages can now be configured in TCA. This
centralizes the configuration for page types and further reduces the need for
:file:`ext_tables.php`, which was used previously.
.. index:: TCA, ext:core
@@ -0,0 +1,54 @@
.. include:: /Includes.rst.txt
.. _feature-108580-1734567890:
=======================================================
Feature: #108580 - Improved page module content preview
=======================================================
See :issue:`108580`
Description
===========
The page module's content element preview functionality has been
enhanced to provide editors with a better visual representation of content
elements in the backend.
Sanitized HTML rendering for content
------------------------------------
Content elements with HTML in the bodytext field (such as text, text & images)
now display sanitized HTML in the page module preview instead of plain text.
A new :php:`\TYPO3\CMS\Core\Html\PreviewSanitizerBuilder` has been introduced that creates a sanitizer
specifically for backend previews. This sanitizer:
* Removes clickable links (unwraps :html:`<a>` tags while preserving
their content)
* Removes heading tags (:html:`<h1>` through :html:`<h6>`) while
preserving their content
* Allows safe HTML formatting (bold, italic, lists, etc.)
Enhanced bullet list preview
----------------------------
Content elements of type "bullet list" now render as HTML lists in the preview.
Harmonized menu element rendering
---------------------------------
Preview rendering for menu content elements and "insert records" elements
has been harmonized to match the layout used in the record selector wizard.
This provides a consistent experience across different parts of the backend.
Impact
======
These enhancements improve the editorial experience in the TYPO3
backend by providing clearer, more informative content previews. Editors can now:
* See formatted HTML content as it will appear to users
* Quickly identify bullet list structure and content
.. index:: Backend, HTML, ext:backend, ext:core
@@ -0,0 +1,114 @@
.. include:: /Includes.rst.txt
.. _feature-108581-1735479000:
===========================================================
Feature: #108581 - Record type specific label configuration
===========================================================
See :issue:`108581`
Description
===========
Previously, the TCA label configuration (`ctrl['label']`, `ctrl['label_alt']`,
and `ctrl['label_alt_force']`) applied globally to all record types in a
table. This meant that all content elements in :sql:`tt_content`, regardless of
their `CType`, displayed the same field(s) as their label in the backend.
It is now possible to define type-specific label configuration in the
TCA `types` section. These settings override global `ctrl` label
configuration for a record type:
* `label` - Primary field used for the record title
* `label_alt` - Alternative field(s) used when label is empty (or as
additional fields)
* `label_alt_force` - Force display of alternative fields alongside the
primary label
This is especially useful for tables like :sql:`tt_content` where different
content element types may benefit from showing different fields. For example, an
"Image" content element could display the image caption, while a "Text" element
would show the header field.
Examples
--------
.. code-block:: php
:caption: EXT:my_extension/Configuration/TCA/tx_my_table.php
return [
'ctrl' => [
'label' => 'header',
'type' => 'record_type',
// ... other ctrl configuration
],
'types' => [
'article' => [
'label_alt' => 'teaser',
],
'event' => [
'label_alt' => 'event_date,location',
'label_alt_force' => true,
],
],
// ... columns configuration
];
In this example:
* All types use `header` as the primary label field (from `ctrl['label']`).
* The `article` type displays the `teaser` field if `header` is
empty.
* The `event` type displays `header` together with `event_date` and
`location` (since `label_alt_force` is enabled).
When adding a new record type to a table, label configuration can
be provided as the third argument `$additionalTypeInformation` of
:php-short:`\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addRecordType`.
.. code-block:: php
:caption: EXT:my_extension/Configuration/TCA/Overrides/tx_my_table.php
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
ExtensionManagementUtility::addRecordType(
[
'label' => 'LLL:frontend.ttc:CType.shortcut',
'value' => 'my-type',
'icon' => 'my-icon',
'group' => 'special',
],
'my-header',
[
'label' => 'header',
'label_alt' => 'records',
]
);
Impact
======
Tables with multiple record types can now define more specific and descriptive
labels for each type in the backend user interface. This improves usability and
clarity for editors by making it immediately obvious which type of record is
being displayed.
This feature is especially useful for:
* Content element tables such as :sql:`tt_content` with different `CType`
values
* Tables with different record types serving different purposes
* Plugin records with varying functionality for each type
* Any table where the record type changes the record's purpose or meaning
All occurrences of
:php-short:`\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle()` in TYPO3
automatically benefit from this feature without any code changes. This includes
record lists, page trees, history views, workspaces, and similar backend
modules.
Functionality such as FormEngine records cannot benefit from this option yet,
as FormEngine does not support the Schema API yet.
.. index:: TCA, Backend, ext:core
@@ -0,0 +1,35 @@
.. include:: /Includes.rst.txt
.. _feature-108648-1770305451:
===================================================================
Feature: #108648 - Option to modify src attribute for Vimeo/YouTube
===================================================================
See :issue:`108648`
Description
===========
The new configuration option `srcAttribute` for the `YouTubeRenderer`
and `VimeoRenderer` can be used to modify the previously hard-coded `src`
attribute in the resulting iframe HTML code. This can be useful if
the iframe should not be immediately loaded because of privacy concerns. An
alternative such as `data-src` can be used in the initial
HTML markup.
Example:
.. code-block:: html
<f:media
file="{youtubeVideo}"
additionalConfig="{srcAttribute: 'data-src'}"
/>
Impact
======
The `src` attribute for YouTube and Vimeo embeds can now be renamed.
.. index:: Frontend, ext:core
@@ -0,0 +1,166 @@
.. include:: /Includes.rst.txt
.. _feature-108653-1767199420:
=====================================================
Feature: #108653 - Database storage for form extension
=====================================================
See :issue:`108653`
Description
===========
The :composer:`typo3/cms-form` extension has been extended to include a new database
storage adapter (:php-short:`\TYPO3\CMS\Form\Storage\DatabaseStorageAdapter`),
allowing form definitions to be stored in the database table :sql:`form_definition`
instead of relying on file system storage only.
Form definitions can now be stored in three ways:
* **Database storage** (new, recommended) stored as records in the
:sql:`form_definition` table
* **File mounts (FAL)** stored as :file:`.form.yaml` files in FAL storage
(deprecated, see :ref:`deprecation-108653-1741600000`)
* **Extension paths** shipped with extensions (read-only or configurable)
Storage adapter architecture
----------------------------
The storage layer uses the Chain of Responsibility pattern. Each storage
adapter implements the
:php-short:`\TYPO3\CMS\Form\Storage\StorageAdapterInterface` and declares
which persistence identifiers it can handle via its :php:`supports()` method.
The :php-short:`\TYPO3\CMS\Form\Storage\StorageAdapterFactory` iterates
through all registered adapters sorted by priority and delegates to the first
matching adapter.
Three adapters are shipped:
* :php-short:`\TYPO3\CMS\Form\Storage\DatabaseStorageAdapter` (priority 100)
* :php-short:`\TYPO3\CMS\Form\Storage\ExtensionStorageAdapter` (priority 75)
* :php-short:`\TYPO3\CMS\Form\Storage\FileMountStorageAdapter` (priority 50,
deprecated)
Database table :sql:`form_definition`
-------------------------------------
A new TCA-managed table :sql:`form_definition` stores the form definitions
with the following fields:
* :sql:`label` the human-readable form name
* :sql:`identifier` the unique form identifier (e.g., `contact-form`)
* :sql:`configuration` the full form definition as JSON
Records are read-only in the standard TCA editing interface. All write and
delete operations go through
:php-short:`\TYPO3\CMS\Core\DataHandling\DataHandler`, ensuring proper
permission checks, history tracking, and hook execution.
Form Manager wizard
-------------------
A new **Storage** wizard step lets editors choose the storage type (file
mount, extension, database) when creating or duplicating forms. When only
one storage adapter is accessible, the step auto-advances.
The Form Manager now also shows a **record history** action in the dropdown menu
for database-stored forms, linking to the TYPO3 record history module.
Record list integration
-----------------------
Two event listeners customize the record list of :sql:`form_definition`
records:
* The standard **edit** action is replaced with a link to the Form Editor
module.
* The standard **delete** action is removed. Deletion is only possible
through the Form Manager.
* Clicking the **record title** opens the Form Editor instead of the TCA
editing form.
Creation of :sql:`form_definition` records via the "New Record" wizard
is denied via page TSconfig:
.. code-block:: typoscript
mod.web_list.deniedNewTables := addToList(form_definition)
CLI command: transfer between storages
--------------------------------------
A new CLI command :bash:`form:definition:transfer` allows form
definitions to be transferred between any two storage backends. This is particularly useful for
migrating file-based forms to database storage via the command line.
.. code-block:: bash
# Transfer all forms from file mounts to database
bin/typo3 form:definition:transfer --source=filemount --target=database
# Transfer a specific form by its identifier
bin/typo3 form:definition:transfer --source=extension --target=database --form-identifier=contact
# Move forms (transfer + delete from source)
bin/typo3 form:definition:transfer --source=filemount --target=database --move
# Dry-run: preview what would be transferred without making changes
bin/typo3 form:definition:transfer --source=filemount --target=database --dry-run
# Transfer to a specific target location (PID for database storage)
bin/typo3 form:definition:transfer --source=filemount --target=database --target-location=0
Available options:
* :bash:`--source` source storage type (`database`, `extension`,
`filemount`)
* :bash:`--target` target storage type
* :bash:`--target-location` target storage location
* :bash:`--form-identifier` transfer only a specific form
* :bash:`--move` delete the source form after successful transfer
* :bash:`--dry-run` preview without making changes
Configuration
=============
Backend users must have table access rights for the :sql:`form_definition`
table.
.. important::
**Permission model differences between file-based and database storage**
The file-based storage allows granular access control through TYPO3 file
mounts. Different backend user groups can be restricted to different
storage folders effectively isolating which forms each group can see and
edit.
Database storage currently relies on TCA table permissions
(:sql:`tables_select` / :sql:`tables_modify` for :sql:`form_definition`).
This means that all backend users who have table access can see
**all** database-stored form definitions — there is no
equivalent to the file mountbased isolation yet.
A dedicated access control mechanism (comparable to file mount
isolation) for database-stored forms is planned but not yet implemented.
If your installation depends on separate permission boundaries for
different editor groups, it is recommended to **not migrate** to database
storage at this time and continue using file-based storage until the
permission feature is available.
Impact
======
Editors can store new form definitions in the database by selecting the "Database"
storage type in the Form Manager creation wizard.
File-based storage (file mounts) will remain functional during the deprecation
period but will trigger :php:`E_USER_DEPRECATED` errors. See
:ref:`deprecation-108653-1741600000` for migration instructions. Existing
file-based forms are not affected by this change.
Extension-based storage will continue to work without change.
.. index:: Backend, Database, TCA, ext:form
@@ -0,0 +1,40 @@
.. include:: /Includes.rst.txt
.. _feature-108720-1769035130:
=====================================================
Feature: #108720 - QR code button for frontend preview
=====================================================
See :issue:`108720`
Description
===========
A new QR code button has been added next to the :guilabel:`View` button in various backend
modules. Clicking the button opens a modal displaying a scannable QR code for
the frontend preview URI.
The button is available in the following locations:
* :guilabel:`Content > Web` module (Layout view and Language Comparison view)
* :guilabel:`Web > List` module
* :guilabel:`Web > View` module
* :guilabel:`Web > Workspaces` module
Inside a workspace a QR code contains a special preview URI that will
work without backend authentication. This makes it easy to share workspace
previews with colleagues and clients, or to quickly check draft versions on a
mobile device by scanning the code.
The QR code can be downloaded as PNG or SVG from the modal.
Impact
======
Editors can benefit from a streamlined workflow by sharing page previews and
testing pages on mobile devices. Workspace-aware preview URIs eliminate the
need to be logged in when scanning the QR code, making it particularly useful
for reviewing processes involving external stakeholders.
.. index:: Backend, ext:backend, ext:workspaces
@@ -0,0 +1,64 @@
.. include:: /Includes.rst.txt
.. _feature-108726-1769073579:
=================================================================================================
Feature: #108726 - Add PSR-14 events ModifyRenderedContentAreaEvent and ModifyRenderedRecordEvent
=================================================================================================
See :issue:`108726`
Description
===========
The :php:`\TYPO3\CMS\Fluid\Event\ModifyRenderedRecordEvent` allows developers to
intercept the rendering of individual records in Fluid templates and modify the output.
This depends on records being rendered with the new :html:`<f:render.contentArea>`, see
:ref:`Introduce Fluid f:render.contentArea ViewHelper <feature-108726-1769071158>`, or
:html:`<f:render.record>` ViewHelpers in Fluid templates, see
:ref:`Introduce Fluid f:render.record ViewHelper <feature-108726-1769503907>`.
Note that any alterations will be output as is and will not be escaped. If you
process insecure content inside an event listener, be sure to escape it properly,
for example by applying :php:`htmlspecialchars()` to it.
Example
=======
An example event listener could look like this:
.. code-block:: php
:caption: EXT:my_extension/Classes/EventListener/ModifyRenderedContentEventListener.php
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Fluid\Event\ModifyRenderedContentAreaEvent;
use TYPO3\CMS\Fluid\Event\ModifyRenderedRecordEvent;
final class ModifyRenderedContentEventListener
{
#[AsEventListener]
public function modifyContentArea(ModifyRenderedContentAreaEvent $event): void
{
$content = 'before area<hr />' . $event->getRenderedContentArea()
. '<hr />after area';
$event->setRenderedContentArea($content);
}
#[AsEventListener]
public function modifyRecord(ModifyRenderedRecordEvent $event): void
{
$content = 'before record<hr />' . $event->getRenderedRecord()
. '<hr />after record';
$event->setRenderedRecord($content);
}
}
Impact
======
The new events can be used by extension authors to enhance the output of
content areas and records rendered in themes.
.. index:: Frontend, ext:fluid
@@ -0,0 +1,62 @@
.. include:: /Includes.rst.txt
.. _feature-108726-1769071158:
==================================================================
Feature: #108726 - Introduce Fluid f:render.contentArea ViewHelper
==================================================================
See :issue:`108726`
Description
===========
Instead of using :html:`<f:cObject>` and :html:`<f:for>` ViewHelpers to render content areas,
the new :html:`<f:render.contentArea>` ViewHelper can be used.
It allows content areas to be rendered while enabling other extensions to modify
the output via PSR-14 EventListeners.
This is especially useful for adding debugging wrappers or additional HTML structure
around content areas.
By default, the ViewHelper renders the content area as-is, but EventListeners
can listen to the :php-short:`\TYPO3\CMS\Fluid\Event\ModifyRenderedContentAreaEvent` and modify the output.
You need to use the `PAGEVIEW` config like this:
.. code-block:: typoscript
page = PAGE
page.10 = PAGEVIEW
page.10.paths.10 = EXT:my_site_package/Resources/Private/Templates/
.. code-block:: html
:caption: MyPage.fluid.html
<f:render.contentArea contentArea="{content.left}"/>
or
{content.left -> f:render.contentArea()}
The ViewHelper also supports wrapping each content element with additional markup
if combined with the `<f:render.record> ViewHelper <https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-render-record>`_:
.. code-block:: html
:caption: MyPage.fluid.html
<f:render.contentArea contentArea="{content.main}" recordAs="record">
before {record.fullType}
<f:render.record record="{record}" />
after {record.fullType}
</f:render.contentArea>
Impact
======
Theme creators are encouraged to use the :html:`<f:render.contentArea>` ViewHelper
to allow other extensions to modify the output via EventListeners.
.. index:: Frontend, ext:fluid
@@ -0,0 +1,81 @@
.. include:: /Includes.rst.txt
.. _feature-108726-1769503907:
=============================================================
Feature: #108726 - Introduce Fluid f:render.record ViewHelper
=============================================================
See :issue:`108726`
Description
===========
Instead of using the :html:`<f:cObject>` ViewHelper to render database records,
the new :html:`<f:render.record>` ViewHelper can be used.
It allows records to be rendered while enabling other extensions to modify the
output via PSR-14 event listeners.
This is especially useful for adding debugging wrappers or additional HTML
structure around content elements.
By default, the ViewHelper renders the record as is, but event listeners
can listen to the
:php-short:`\TYPO3\CMS\Fluid\Event\ModifyRenderedRecordEvent` and modify the
output.
Usage with the :typoscript:`record-transformation` data processor:
.. code-block:: typoscript
dataProcessing {
10 = record-transformation
}
.. code-block:: html
:caption: MyContentElement.fluid.html
<f:render.record record="{record}" />
or
{record -> f:render.record()}
You can render not only :sql:`tt_content` records, but any database record by
defining the rendering in TypoScript.
.. code-block:: typoscript
# Example TypoScript configuration for rendering custom records
sys_category = FLUIDTEMPLATE
sys_category {
file = EXT:my_extension/Resources/Private/Templates/Category.html
layoutRootPaths.10 = EXT:my_extension/Resources/Private/Layouts/
partialRootPaths.10 = EXT:my_extension/Resources/Private/Partials/
dataProcessing.1421884800 = record-transformation
}
# Example TypoScript configuration for special record types
tx_myextension_domain_model_product = COA
tx_myextension_domain_model_product.default = FLUIDTEMPLATE
tx_myextension_domain_model_product.default {
templateName >
templateName.ifEmpty.cObject = TEXT
templateName.ifEmpty.cObject {
field = record_type
required = 1
case = uppercamelcase
}
# for record_type = 'mainProduct' the template file my_extension/Resources/Private/Templates/Product/MainProduct.html will be used
layoutRootPaths.10 = EXT:my_extension/Resources/Private/Layouts/
partialRootPaths.10 = EXT:my_extension/Resources/Private/Partials/
templateRootPaths.10 = EXT:my_extension/Resources/Private/Templates/Product/
dataProcessing.1421884800 = record-transformation
}
Impact
======
Theme creators are encouraged to use the :html:`<f:render.record>` ViewHelper
to allow other extensions to modify the output via event listeners.
.. index:: Frontend, ext:fluid
@@ -0,0 +1,181 @@
.. include:: /Includes.rst.txt
.. _feature-108763-1769331943:
=============================================================
Feature: #108763 - Console command to analyze Fluid templates
=============================================================
See :issue:`108763`
Description
===========
The :bash:`typo3 fluid:analyze` console command is introduced, which analyzes
Fluid templates in the current project for correct Fluid syntax and reports
deprecations that are emitted during template parsing.
Usage:
.. code-block:: bash
vendor/bin/typo3 fluid:analyze
Example output:
.. code-block::
[DEPRECATION] packages/myext/Resources/Private/Templates/Test.fluid.html: <my:obsolete> has been deprecated in X and will be removed in Y.
[ERROR] packages/myext/Resources/Private/Templates/Test2.fluid.html: Variable identifiers cannot start with a "_": _temp
In its initial implementation, the command automatically finds all Fluid
templates within the current project based on the `*.fluid.*` file extension
(see
:ref:`Feature: #108166 - Fluid file extension and template resolving <feature-108166-1763400992>`)
and analyzes them. By default, TYPO3 system extensions are skipped. This can
be adjusted by specifying the :bash:`--include-system-extensions` CLI option.
The following errors and deprecations are currently supported:
* Fluid syntax errors (for example, invalid nesting of ViewHelper tags)
* Usage of invalid ViewHelpers or ViewHelper namespaces
* Usage of variable names that start with `_`
(see :ref:`Breaking: #108148 - Disallow Fluid variable names with underscore prefix <breaking-108148-1763288414>`)
* Usage of deprecated ViewHelpers or ViewHelper arguments (if deprecation
is triggered during parse time, see
:ref:`Deprecating ViewHelpers <feature-108763-1769331943-deprecating-viewhelpers>`
and
:ref:`Deprecating ViewHelper arguments <feature-108763-1769331943-deprecating-viewhelper-arguments>`)
If exceptions are caught during the parsing process of at least one template,
the console command will have a return status of 1 (error). Otherwise, it will return 0
(success). This means that deprecations are not interpreted as errors.
This should make it possible to use the command in CI workflows of most
projects, since deprecated functionality used by third-party templates will
not make the pipeline fail.
Verbose output allows users to get feedback on the analyzed templates
and the number of errors and deprecations, or success.
.. _feature-108763-1769331943-tool-integration:
Integration with other tools
----------------------------
The command also supports input of a template string via `STDIN` as well as
machine-readable output as JSON. This enables better integration with other
development-related tools.
Usage:
.. code-block:: bash
echo "<formvh:form.timePicker /> {_invalidVariable}" | vendor/bin/typo3 fluid:analyze --stdin --json
Example output (formatted):
.. code-block:: json
{
"identifier": "template__5adb1a7702b9dcbf",
"path": "php:\/\/stdin",
"errors": [
{
"file": "\/var\/www\/html\/vendor\/typo3fluid\/fluid\/src\/Core\/Parser\/TemplateParser.php",
"line": 130,
"message": "Fluid parse error in template php:\/\/stdin, line 2 at character 27. Error: Variable identifiers cannot start with a \"_\": _invalidVariable (error code 1765900762). Template source chunk: {_invalidVariable}\n",
"templateLocation": {
"identifierOrPath": "php:\/\/stdin",
"line": 2,
"character": 27
}
}
],
"deprecations": [
{
"file": "\/var\/www\/html\/typo3\/sysext\/form\/Classes\/ViewHelpers\/Form\/TimePickerViewHelper.php",
"line": 143,
"message": "The TimePickerViewHelper is deprecated since TYPO3 v14 and will be removed in v15."
}
]
}
.. _feature-108763-1769331943-deprecating-viewhelpers:
Deprecating ViewHelpers
-----------------------
The :bash:`fluid:analyze` console command can catch deprecations of whole
ViewHelpers if the deprecation is emitted during the parse time of a template.
This is possible by implementing the
:php-short:`\TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperNodeInitializedEventInterface`:
.. code-block:: php
:caption: ObsoleteViewHelper.php
use TYPO3Fluid\Fluid\Core\Parser\ParsingState;
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperNodeInitializedEventInterface;
/**
* @deprecated since X, will be removed in Y.
*/
final class ObsoleteViewHelper extends AbstractViewHelper implements ViewHelperNodeInitializedEventInterface
{
// ...
public static function nodeInitializedEvent(ViewHelperNode $node, array $arguments, ParsingState $parsingState): void
{
trigger_error(
'<my:obsolete> has been deprecated in X and will be removed in Y.',
E_USER_DEPRECATED,
);
}
}
.. _feature-108763-1769331943-deprecating-viewhelper-arguments:
Deprecating ViewHelper arguments
--------------------------------
The :php-short:`\TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperNodeInitializedEventInterface`
can be used to deprecate a ViewHelper argument. The deprecation is only
triggered if the argument is actually used in a template.
.. code-block:: php
:caption: SomeViewHelper.php
use TYPO3Fluid\Fluid\Core\Parser\ParsingState;
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperNodeInitializedEventInterface;
final class SomeViewHelper extends AbstractViewHelper implements ViewHelperNodeInitializedEventInterface
{
public function initializeArguments(): void
{
// @deprecated since X, will be removed in Y.
$this->registerArgument('obsoleteArgument', 'string', 'Original description. Deprecated since X, will be removed in Y');
}
public static function nodeInitializedEvent(ViewHelperNode $node, array $arguments, ParsingState $parsingState): void
{
if (array_key_exists('obsoleteArgument', $arguments)) {
trigger_error(
'ViewHelper argument "obsoleteArgument" in <my:some> is deprecated since X and will be removed in Y.',
E_USER_DEPRECATED,
);
}
}
}
Impact
======
The new :bash:`typo3 fluid:analyze` console command can be used to check basic
validity of Fluid templates in projects that use the `*.fluid.*` file
extension and to discover deprecated functionality in template files.
.. index:: CLI, Fluid, ext:fluid
@@ -0,0 +1,34 @@
.. include:: /Includes.rst.txt
.. _feature-108776-1769518546:
=====================================================================================
Feature: #108776 - Allow to set user interface language when using CLI to create user
=====================================================================================
See :issue:`108776`
Description
===========
The CLI command :bash:`typo3 backend:user:create` now supports the
:bash:`--language` option, or :bash:`-l`, that sets the desired user interface
language.
.. code-block:: bash
vendor/bin/typo3 backend:user:create --language=de
User creation using environment variables:
.. code-block:: bash
TYPO3_BE_USER_NAME=username \
TYPO3_BE_USER_EMAIL=admin@example.com \
TYPO3_BE_USER_GROUPS=<comma-separated-list-of-group-ids> \
TYPO3_BE_USER_LANGUAGE=de \
TYPO3_BE_USER_ADMIN=0 \
TYPO3_BE_USER_MAINTAINER=0 \
vendor/bin/typo3 backend:user:create --no-interaction
.. index:: CLI, ext:backend
@@ -0,0 +1,95 @@
.. include:: /Includes.rst.txt
.. _feature-108796-1738078800:
=================================================
Feature: #108796 - Centralize bookmark management
=================================================
See :issue:`108796`
Description
===========
The TYPO3 backend bookmark system has been comprehensively overhauled,
introducing a centralized architecture that replaces the legacy "shortcut"
implementation.
Bookmark groups
---------------
Bookmarks can be organized into three types of groups.
System groups are defined via user TSconfig using
:typoscript:`options.bookmarkGroups` and are available to all users. Global
groups contain bookmarks visible to all backend users, though only
administrators can add bookmarks to these groups. User groups are custom groups
created by individual users for their own personal organization and are stored in
a new database table :sql:`sys_be_shortcuts_group`.
Five default bookmark groups are provided out of the box: Pages, Records,
Files, Tools, and Miscellaneous. Previously these groups were hardcoded in
PHP, but they are now defined via user TSconfig in EXT:backend, making them
fully customizable. The functionality remains the same, but administrators now
have complete control over which groups are available.
The :typoscript:`options.bookmarkGroups` setting should only be modified in
the global scope and not on a per-user basis, as inconsistent group
configuration between users can lead to unexpected behavior:
.. code-block:: typoscript
:caption: EXT:my_ext/Configuration/user.tsconfig
# Remove a specific default group (for example, Files)
options.bookmarkGroups.3 >
# Remove all default groups
options.bookmarkGroups >
# Add a custom group with a static label
options.bookmarkGroups.10 = My Custom Group
# Add a custom group with a translatable label using domain syntax
options.bookmarkGroups.11 = my_extension.messages:bookmark_group.custom
# Disable bookmarks entirely
options.enableBookmarks = 0
Group labels support the TYPO3 translation domain syntax, allowing extensions
to provide translated group names. The format is
:typoscript:`extension_key.messages:translation_key`, which resolves to the
default language file at
:file:`EXT:extension_key/Resources/Private/Language/locallang.xlf`.
As before, group ID :typoscript:`-100` has special behavior as it is a superglobal
group. Bookmarks assigned to this group are visible to all backend users, but
only administrators can add or modify bookmarks in this group. This allows
administrators to provide a shared set of bookmarks across an entire TYPO3
installation.
Bookmark Manager
----------------
A new modal-based :guilabel:`Bookmark Manager` provides a centralized
interface for managing bookmarks. The manager supports drag-and-drop
reordering to reorganize bookmarks within and across groups. Bulk operations
allow selecting multiple bookmarks to move or delete at once. Users can
create, edit, and delete custom bookmark groups through the group management
interface, and rename bookmarks via inline editing.
The :guilabel:`Bookmark Manager` can be accessed via the bookmark icon in the
toolbar dropdown menu.
Impact
======
The bookmark toolbar item now opens a dropdown with quick access to recent
bookmarks and a link to the full :guilabel:`Bookmark Manager`. Users can
create custom bookmark groups to better organize their saved pages,
records, and modules. Administrators can configure global bookmarks visible
to all users. The Bookmarks dashboard widget has been updated to support the
new bookmark system with group filtering and limit options. The legacy
"shortcut" terminology has been replaced with "bookmark" throughout the
backend interface and code base.
.. index:: Backend, TSConfig, ext:backend
@@ -0,0 +1,85 @@
.. include:: /Includes.rst.txt
.. _feature-108799-1738094060:
==================================================================================
Feature: #108799 - LocalizationRepository methods for fetching record translations
==================================================================================
See :issue:`108799`
Description
===========
TYPO3 has historically provided helper methods for localization in various
places in the TYPO3 code. This patch centralizes localization-related functionality by marking
:php:`\TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository`
as public (non-internal) and adding new methods as modern, DI-friendly
alternatives to the static
:php-short:`\TYPO3\CMS\Backend\Utility\BackendUtility` methods.
getRecordTranslation()
----------------------
Fetches a single translated version of a record for a specific language.
.. code-block:: php
public function getRecordTranslation(
string|TcaSchema $tableOrSchema,
int|array|RecordInterface $recordOrUid,
int|LanguageAspect $language,
int $workspaceId = 0,
bool $includeDeletedRecords = false,
): ?RawRecord
getRecordTranslations()
-----------------------
Fetches all translations of a record. This method can also be used to count
translations by using :php:`count()` on the result, replacing the need for
:php:`BackendUtility::translationCount()`.
.. code-block:: php
public function getRecordTranslations(
string|TcaSchema $tableOrSchema,
int|array|RecordInterface $recordOrUid,
array $limitToLanguageIds = [],
int $workspaceId = 0,
bool $includeDeletedRecords = false,
): array
Returns an array of translated :php-short:`\TYPO3\CMS\Core\Domain\RawRecord`
objects indexed by language ID.
getPageTranslations()
---------------------
Fetches all page translations for a page.
.. code-block:: php
public function getPageTranslations(
int $pageUid,
array $limitToLanguageIds = [],
int $workspaceId = 0,
bool $includeDeletedRecords = false,
): array
Returns an array of page translation records as
:php-short:`\TYPO3\CMS\Core\Domain\RawRecord` objects indexed by language ID.
Impact
======
Extension developers working with record translations in the TYPO3 backend now
have access to modern, injectable repository methods that follow current TYPO3
coding practices.
The legacy static methods :php:`BackendUtility::getRecordLocalization()`,
:php:`BackendUtility::getExistingPageTranslations()`, and
:php:`BackendUtility::translationCount()` remain available for backward
compatibility until they are migrated completely.
.. index:: Backend, PHP-API, ext:backend
@@ -0,0 +1,92 @@
.. include:: /Includes.rst.txt
.. _feature-108815-1738249200:
========================================================
Feature: #108815 - CLI commands for system configuration
========================================================
See :issue:`108815`
Description
===========
New CLI commands have been introduced to manage TYPO3 system configuration
(stored in :file:`config/system/settings.php`) directly from the command line.
The following commands are now available:
configuration:show
------------------
Shows a configuration value. By default, if the active value differs from the
local value (for example, due to overrides in
:file:`config/system/additional.php`), both values are displayed with the
difference highlighted.
.. code-block:: bash
# Show configuration (with diff if overridden)
vendor/bin/typo3 configuration:show SYS/sitename
# Show active (effective runtime) value
vendor/bin/typo3 configuration:show SYS/sitename --type=active
# Show local (settings.php) value only
vendor/bin/typo3 configuration:show DB/Connections/Default --type=local
# Output as JSON
vendor/bin/typo3 configuration:show BE/debug --type=active --json
configuration:set
-----------------
Sets a configuration value in :file:`config/system/settings.php`.
.. code-block:: bash
# Set a string value
vendor/bin/typo3 configuration:set SYS/sitename "My Site"
# Set boolean or integer values using --json
vendor/bin/typo3 configuration:set BE/debug true --json
vendor/bin/typo3 configuration:set SYS/displayErrors 1 --json
# Set an array value
vendor/bin/typo3 configuration:set EXTENSIONS/my_extension '{"key": "value"}' --json
The :bash:`--json` option parses the value as JSON, which allows
booleans, integers, and arrays to be set with proper types.
configuration:remove
--------------------
Removes configuration value or values from :file:`config/system/settings.php`.
.. code-block:: bash
# Remove a single path (asks for confirmation)
vendor/bin/typo3 configuration:remove EXTENSIONS/my_extension/setting
# Remove without confirmation
vendor/bin/typo3 configuration:remove EXTENSIONS/my_extension/setting --force
# Remove multiple paths (comma-separated)
vendor/bin/typo3 configuration:remove "EXTCONF/ext1,EXTCONF/ext2" --force
Impact
======
These commands provide a convenient way to manage TYPO3 system configuration
from the command line, which is especially useful for:
* automated deployment and provisioning scripts
* CI/CD pipelines that need to adjust configuration
* quick configuration changes without needing to access the Install Tool
* scripting and automation tasks
The commands respect TYPO3 configuration path restrictions and only allow
writing to paths that are defined in the default configuration or explicitly
allowed (such as :php:`EXTENSIONS`, :php:`EXTCONF`, :php:`DB`).
.. index:: CLI, LocalConfiguration, ext:lowlevel
@@ -0,0 +1,77 @@
.. include:: /Includes.rst.txt
.. _feature-108817-1739494800:
=================================================================
Feature: #108817 - Introduce web component-based form editor tree
=================================================================
See :issue:`108817`
Description
===========
The Form Editor tree component has been completely modernized. It has been migrated from
a legacy jQuery-based implementation to a modern web component architecture
using Lit and the TYPO3 backend tree infrastructure.
Enhanced user experience
------------------------
The new tree component provides a significantly improved user experience with
modern interaction patterns and visual feedback:
**Intuitive drag and drop**
Form elements can now be reorganized using a smooth drag-and-drop interface
with intelligent validation rules. The tree automatically prevents invalid
operations, such as dragging the root form element, moving pages outside
their designated level, or dropping elements into non-composite types.
**Smart element organization**
Only composite elements like grid containers and fieldsets can receive
child elements, while simple form fields remain non-droppable. Pages must
always stay at the top level, ensuring proper form structure. The tree
automatically distinguishes between reordering siblings and changing parent
elements, providing precise control over form organization.
**Visual feedback**
Clear visual indicators show valid drop zones during drag operations.
Selected elements are highlighted with proper styling. The tree provides
immediate feedback for all interactions, making form building more
intuitive.
**Persistent navigation**
The tree remembers expanded and collapsed states. After drag
and drop operations, the tree maintains your current view and selection,
preventing disorientating resets. Navigation feels natural and responsive.
**Integrated search**
A built-in search toolbar allows quick filtering of form elements by name.
The search works client-side for instant results, making it easy to locate
specific elements in complex forms.
**Collapse-all functionality**
The toolbar includes a convenient button to collapse all expanded nodes at
once, helping to get a quick overview of your form structure or reset the
view to a clean state.
Technical implementation
------------------------
The new implementation leverages the proven TYPO3 backend tree infrastructure.
Impact
======
Form editors will immediately notice the improved responsiveness and modern
feel of the tree component. Drag-and-drop operations are smoother and more
predictable. The search functionality makes working with large forms
significantly easier. The tree maintains its state during operations, reducing
friction and improving workflow efficiency.
The new web component-based architecture ensures better maintainability and
extensibility for future enhancements. The component integrates seamlessly with
the existing Form Editor without requiring changes to form definitions or
configuration.
.. index:: Backend, JavaScript, ext:form
@@ -0,0 +1,151 @@
.. include:: /Includes.rst.txt
.. _feature-108819-1738329600:
==========================================================================
Feature: #108819 - RecordFieldPreviewProcessor for custom PreviewRenderers
==========================================================================
See :issue:`108819`
Description
===========
A new service
:php:`\TYPO3\CMS\Backend\Preview\RecordFieldPreviewProcessor`
has been introduced to provide common field rendering helpers for custom
content element preview renderers.
Previously, these helper methods were only available in
:php-short:`\TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer`,
which required custom preview renderers to extend that class to access them.
Instead, this service uses the composition-over-inheritance pattern.
The new service provides the following methods:
prepareFieldWithLabel()
-----------------------
Renders a field value with its TCA label prepended in bold.
.. code-block:: php
use TYPO3\CMS\Core\Domain\RecordInterface;
public function prepareFieldWithLabel(
RecordInterface $record,
string $fieldName,
): ?string
prepareField()
--------------
Renders a processed field value without a label.
.. code-block:: php
use TYPO3\CMS\Core\Domain\RecordInterface;
public function prepareField(
RecordInterface $record,
string $fieldName,
): ?string
prepareText()
-------------
Processes larger text fields (for example, RTE content) with truncation and
HTML stripping.
.. code-block:: php
use TYPO3\CMS\Core\Domain\RecordInterface;
public function prepareText(
RecordInterface $record,
string $fieldName,
int $maxLength = 1500,
): ?string
preparePlainHtml()
------------------
Renders plain HTML content with line limiting.
.. code-block:: php
use TYPO3\CMS\Core\Domain\RecordInterface;
public function preparePlainHtml(
RecordInterface $record,
string $fieldName,
int $maxLines = 100,
): ?string
prepareFiles()
--------------
Renders thumbnails for file references.
.. code-block:: php
use TYPO3\CMS\Core\Resource\FileReference;
public function prepareFiles(
iterable|FileReference $fileReferences,
): ?string
linkToEditForm()
----------------
Wraps content in an edit link if the user has the appropriate permissions.
.. code-block:: php
use TYPO3\CMS\Core\Domain\RecordInterface;
use Psr\Http\Message\ServerRequestInterface;
public function linkToEditForm(
string $linkText,
RecordInterface $record,
ServerRequestInterface $request,
): string
Impact
======
Extension developers implementing custom preview renderers can now inject
:php-short:`\TYPO3\CMS\Backend\Preview\RecordFieldPreviewProcessor`
to access common field rendering helpers without extending
:php-short:`\TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer`.
Example usage:
.. code-block:: php
use TYPO3\CMS\Backend\Preview\PreviewRendererInterface;
use TYPO3\CMS\Backend\Preview\RecordFieldPreviewProcessor;
use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem;
final class MyCustomPreviewRenderer implements PreviewRendererInterface
{
public function __construct(
private readonly RecordFieldPreviewProcessor $fieldProcessor,
) {}
public function renderPageModulePreviewContent(
GridColumnItem $item,
): string {
$record = $item->getRecord();
$content = $this->fieldProcessor->prepareFieldWithLabel(
$record,
'header',
);
$content .= $this->fieldProcessor->prepareFiles($record->get('image'));
return $content;
}
}
.. index:: Backend, PHP-API, ext:backend
@@ -0,0 +1,68 @@
.. include:: /Includes.rst.txt
.. _feature-108826-1770219994:
=======================================
Feature: #108826 - Add Short URL module
=======================================
See :issue:`108826`
Description
===========
A new backend module :guilabel:`Link Management > Short URLs` has been introduced.
It enables editors to create and manage short URLs that redirect visitors to a
configurable target. Short URLs are stored as :sql:`sys_redirect` records with a
dedicated record type :sql:`short_url`, providing a streamlined editing form that
hides redirect-specific fields that are irrelevant to short URL use cases.
Creating short URLs
-------------------
Short URLs can be created in two ways:
* **Manual entry**: Editors type a custom path (for example, `/promo`) into the
source path field.
* **Auto-generation**: Clicking the :guilabel:`Generate Short URL` button
generates a random 8-character path (for example, `/aBcDeFgH`). The
path is guaranteed to be unique due to server-side collision checking.
Uniqueness enforcement
----------------------
Short URL paths must be unique to each source host. Duplicate detection happens at
two levels:
* **Client-side validation**: While editing, the source path and source host
fields are validated against existing records. If a conflict is detected,
both fields are highlighted with an error state together with a notification.
* **Server-side enforcement**: On save, the
:php-short:`\TYPO3\CMS\Core\DataHandling\DataHandler` rejects duplicate short
URLs and displays a flash message, ensuring data integrity even if
client-side validation is bypassed.
Immutability
------------
Once a short URL record has been saved, the source path and source host fields
become read-only. This ensures that published short URLs remain stable and
previously shared links continue to work. The redirect target can be
changed at any time.
Clipboard support
-----------------
The full short URL, including protocol and host, can be copied to the clipboard
from both the list overview and the record editing form.
Impact
======
Editors benefit from a dedicated interface for managing short URLs without
needing to understand redirect configuration details. The module provides a
central location for creating, reviewing, and maintaining short URLs with
built-in safeguards against duplicates and accidental modifications.
.. index:: Backend, ext:redirects
@@ -0,0 +1,51 @@
.. include:: /Includes.rst.txt
.. _feature-108831-1738522284:
================================================================
Feature: #108831 - Extend workspace preview links to all records
================================================================
See :issue:`108831`
Description
===========
The :guilabel:`Workspaces` module previously generated shareable preview links
(with `ADMCMD_prev` token) for pages only. This change extends the functionality
to support any record type.
The preview page for non-page records is determined via existing user TSconfig
options:
* :typoscript:`options.workspaces.previewPageId.<table>`
* :typoscript:`TCEMAIN.preview.<table>.previewPageId`
Additional query parameters can be configured via:
* :typoscript:`TCEMAIN.preview.<table>.fieldToParameterMap`
* :typoscript:`TCEMAIN.preview.<table>.additionalGetParameters`
Example configuration for a custom record type:
.. code-block:: typoscript
TCEMAIN.preview.tx_myext_domain_model_item {
previewPageId = 42
fieldToParameterMap {
uid = tx_myext_pi1[item]
}
additionalGetParameters {
type = 9818
}
}
Impact
======
The QR code and shareable link button in the :guilabel:`Workspaces` module now
work for all record types that have a preview configuration. This allows editors
to share workspace previews of custom records with colleagues or clients without
requiring them to have a backend login.
.. index:: Backend, TSConfig, ext:workspaces
@@ -0,0 +1,70 @@
.. include:: /Includes.rst.txt
.. _feature-108832-1738500000:
==================================================================================
Feature: #108832 - Introduce UserSettings object for backend user profile settings
==================================================================================
See :issue:`108832`
Description
===========
A new :php:`\TYPO3\CMS\Core\Authentication\UserSettings` object provides structured access to backend user
profile settings defined in :php:`$GLOBALS['TYPO3_USER_SETTINGS']`.
UserSettings object
-------------------
The :php-short:`\TYPO3\CMS\Core\Authentication\UserSettings` object can be retrieved via the backend user:
.. code-block:: php
/** @var \TYPO3\CMS\Core\Authentication\UserSettings $userSettings */
$userSettings = $GLOBALS['BE_USER']->getUserSettings();
// Check if a setting exists
if ($userSettings->has('colorScheme')) {
$scheme = $userSettings->get('colorScheme');
}
// Get all settings as an array
$allSettings = $userSettings->toArray();
// Typed access via dedicated methods
$emailOnLogin = $userSettings->isEmailMeAtLoginEnabled();
$showUploadFields = $userSettings->isUploadFieldsInTopOfEBEnabled();
The class implements :php-short:`Psr\Container\ContainerInterface` with :php:`has()`
and :php:`get()` methods. The :php:`get()` method throws
:php-short:`\TYPO3\CMS\Core\Authentication\Exception\UserSettingsNotFoundException`
if the setting does not exist.
New JSON storage with backward compatibility
--------------------------------------------
Profile settings are now stored in a new :sql:`be_users.user_settings` JSON
field, providing a structured and queryable format. For backward compatibility,
the existing serialized :sql:`uc` blob continues to be written alongside:
.. code-block:: php
// Writing still uses the uc mechanism
$GLOBALS['BE_USER']->uc['colorScheme'] = 'dark';
$GLOBALS['BE_USER']->writeUC();
// Both uc (serialized) and user_settings (JSON) are updated
An upgrade wizard, "Migrate user profile settings to JSON format", migrates
existing settings from the :sql:`uc` blob to the new :sql:`user_settings` field.
Impact
======
Backend user profile settings can now be accessed via the
:php-short:`\TYPO3\CMS\Core\Authentication\UserSettings` object, providing
type safety and IDE support. The new JSON storage format improves data
accessibility while maintaining full backward compatibility through dual-write
to both storage formats.
.. index:: Backend, PHP-API, ext:core
@@ -0,0 +1,28 @@
.. include:: /Includes.rst.txt
.. _feature-108842-1770128495:
============================================================
Feature: #108842 - Add badge for slide mode in Layout module
============================================================
See :issue:`108842`
Description
===========
This feature introduces a visual badge in the :guilabel:`Content > Layout` module to
indicate when slide mode is active. The badge is a clear indicator for
editors, enhancing the user experience by providing immediate feedback on the
current mode of operation.
Each slide mode has a corresponding badge and description text:
* For :php:`slideMode = none`, no badge is shown.
* For :php:`slideMode = slide`, a badge with the text "Slide" is shown, but only
if there are no content elements on the current page.
* For :php:`slideMode = collect`, a badge with the text "Collect" is shown.
* For :php:`slideMode = collectReverse`, a badge with the text "CollectReverse"
is shown.
.. index:: Backend, ext:backend, ext:workspaces
@@ -0,0 +1,108 @@
.. include:: /Includes.rst.txt
.. _feature-108843-1738600001:
============================================================
Feature: #108843 - User settings configuration migrated to TCA
============================================================
See :issue:`108843`
See :issue:`108832`
Description
===========
The backend user profile settings configuration that was previously stored in
:php:`$GLOBALS['TYPO3_USER_SETTINGS']` is now available in TCA at
:php:`$GLOBALS['TCA']['be_users']['columns']['user_settings']`.
This allows user settings to benefit from TCA-based tooling and provides
a consistent API that extensions already use for other configuration.
A new method
:php:`ExtensionManagementUtility::addUserSetting()` has been introduced to
simplify adding custom fields to user profile settings.
Impact
======
Extensions can add custom fields to backend user profile settings using
the new :php:`addUserSetting()` method in
:file:`Configuration/TCA/Overrides/be_users.php`:
.. code-block:: php
// Configuration/TCA/Overrides/be_users.php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserSetting(
'myCustomSetting',
[
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:myCustomSetting',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
],
],
'after:emailMeAtLogin'
);
Alternatively, extensions can directly modify the TCA:
.. code-block:: php
// Configuration/TCA/Overrides/be_users.php
$GLOBALS['TCA']['be_users']['columns']['user_settings']['columns']['myCustomSetting'] = [
'label' => 'LLL:my_extension.messages:myCustomSetting',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
],
];
// Add to showitem
$GLOBALS['TCA']['be_users']['columns']['user_settings']['showitem'] .= ',myCustomSetting';
Structure
---------
The :php:`user_settings` TCA column has the following structure:
:php:`columns`
Array of field configurations, each containing:
:php:`label`
The field label (LLL reference or string)
:php:`config`
Standard TCA config array (type, renderType, items, etc.)
:php:`table` (optional)
Set to :php:`'be_users'` if the field is stored in a
:sql:`be_users` table column
:php:`showitem`
Comma-separated list of fields to display; supports
:php:`--div--;` for tabs
Available field types
---------------------
* :php:`input` - Text input field
* :php:`number` - Number input field
* :php:`email` - Email input field
* :php:`password` - Password input field
* :php:`check` with :php:`renderType => 'checkboxToggle'` -
Checkbox or toggle
* :php:`select` with :php:`renderType => 'selectSingle'` -
Select field
* :php:`language` - Language selector
Backward compatibility
----------------------
For backward compatibility, the legacy
:php:`$GLOBALS['TYPO3_USER_SETTINGS']` array is still supported. Third-party
additions are automatically migrated to TCA after all
:file:`ext_tables.php` files have been loaded. However, this approach is
deprecated, and extensions should migrate to the new TCA-based API.
.. index:: Backend, TCA, PHP-API, ext:setup
@@ -0,0 +1,49 @@
.. include:: /Includes.rst.txt
.. _feature-108846-1770196894:
==========================================================================
Feature: #108846 - Console command to inspect global ViewHelper namespaces
==========================================================================
See :issue:`108846`
Description
===========
The new console command `typo3 fluid:namespaces` has been introduced. It
lists all the available global ViewHelper namespaces in the current project and
can be used to verify the current configuration. The `--json` option
can be used to access the information in a machine-readable way.
Usage:
.. code-block:: bash
vendor/bin/typo3 fluid:namespaces
Example output:
.. code-block::
+--------+------------------------------+
| Alias | Namespace(s) |
+--------+------------------------------+
| core | TYPO3\CMS\Core\ViewHelpers |
+--------+------------------------------+
| formvh | TYPO3\CMS\Form\ViewHelpers |
+--------+------------------------------+
| f | TYPO3Fluid\Fluid\ViewHelpers |
| | TYPO3\CMS\Fluid\ViewHelpers |
+--------+------------------------------+
The same information is available in the
:guilabel:`System > Configuration` module in the TYPO3 backend.
Impact
======
The new console command allows developers and integrators to inspect
global ViewHelper namespaces in the current project.
.. index:: CLI, Fluid, ext:fluid
@@ -0,0 +1,158 @@
.. include:: /Includes.rst.txt
.. _feature-108868-1770281522:
===========================================================
Feature: #108868 - Introduce Fluid f:render.text ViewHelper
===========================================================
See :issue:`108868`
Description
===========
A new :html:`<f:render.text>` ViewHelper has been added. It provides a consistent
approach for outputting field values in templates where the field is part of a
record.
The ViewHelper follows the same conventions as other rendering-related
ViewHelpers and can be used wherever a text-based database field needs to be
displayed in the frontend.
The ViewHelper is record-aware. It receives the full record and field name,
and renders the field according to the field's TCA configuration. This includes
both plain text and rich text fields.
By default, accessing a field that is not available in a record
raises an exception. In order to support shared templates that need to be rendered even
if a field is missing, the optional boolean argument :html:`optional` can be set
to :html:`true`. The ViewHelper will then return :html:`null` instead.
The input can be a :php-short:`\TYPO3\CMS\Core\Domain\RecordInterface`,
:php-short:`\TYPO3\CMS\Frontend\Page\PageInformation`, or a
:php-short:`\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface`.
This means records, ContentBlockData objects, PageInformation
objects, and Extbase models can be input. PageInformation objects and Extbase models are
converted internally to a RecordInterface.
Usage
=====
Usage with the :typoscript:`record-transformation` data processor:
.. code-block:: typoscript
dataProcessing {
10 = record-transformation
}
Based on the field's TCA configuration of the record in question, the ViewHelper
chooses the appropriate processing of the field (plain text, multiline text, or
rich text) without needing further configuration in the template.
.. code-block:: html
:caption: MyContentElement.fluid.html
<f:render.text record="{record}" field="title" />
or
<f:render.text field="title">{record}</f:render.text>
or
{f:render.text(record: record, field: 'title')}
or
{record -> f:render.text(field: 'title')}
Usage with optional fields:
.. code-block:: html
:caption: SharedHeader.fluid.html
<f:variable name="header">{record -> f:render.text(field: 'header', optional: true)}</f:variable>
This is useful for shared partials, for example in
:html:`fluid_styled_content`. A header partial can be reused by content
elements whose transformed record does not provide a :html:`header` or
:html:`subheader` field. Without :html:`optional="true"`, rendering such a
partial would raise a :php:`RecordPropertyNotFoundException`. With
:html:`optional="true"`, the ViewHelper returns :html:`null` and the partial can
continue to handle the missing value gracefully.
Usage with an Extbase model (property name differs from database field name):
The :html:`field` argument always refers to the database or TCA column name of
the underlying record, even if your Extbase model maps that column to a
differently named property.
Note that Extbase models need to contain all columns to be rendered and
the record type column (if configured in TCA) for this to work correctly. For
example, an Extbase model that represents `tt_content` must map both `bodytext`
and `CType` to be able to use
:html:`<f:render.text record="{contentModel}" field="bodytext" />`.
.. code-block:: html
:caption: Blog/Templates/Post/Show.fluid.html
<f:render.text record="{post}" field="short_description" />
<!-- Example: Post->shortDescription maps to DB field "short_description";
use field="short_description" here. -->
Previously, you needed to choose different processing for plain text and rich
text fields. You can now use the same ViewHelper for all field types.
**For reference, similar results could previously be achieved using:**
.. code-block:: html
:caption: MyContentElement.fluid.html
{record.title}
or multiline text:
.. code-block:: html
:caption: MyContentElement.fluid.html
<f:format.nl2br>{record.description}</f:format.nl2br>
or
{record.description -> f:format.nl2br()}
or, for rich text:
.. code-block:: html
:caption: MyContentElement.fluid.html
<f:format.html>{record.bodytext}</f:format.html>
or
{record.bodytext -> f:format.html()}
Migration
=========
Extensions that previously accessed field values with
:html:`{record.title}` can continue to do so. However, using
:html:`<f:render.text>` is recommended instead because it renders the field in the
context of the record and applies processing based on the field configuration.
When migrating from formatting ViewHelpers like :html:`<f:format.nl2br>` or
:html:`<f:format.html>` to :html:`<f:render.text>`, the main difference is that
the new ViewHelper is aware of the record it belongs to and renders the field
based on the record's TCA schema.
If a template intentionally accesses fields that might not be available in every
record, for example shared :html:`fluid_styled_content` header partials
used by custom content elements that do not have a visible :html:`header` field, use the
:html:`optional` argument to preserve the previous behavior of treating the
missing field as empty output.
Impact
======
Theme creators are encouraged to use the :html:`<f:render.text>` ViewHelper for
rendering text-based fields (plain text and rich text) as it provides a
standardized, record-aware approach that can be built upon in future versions.
The ViewHelper takes both the record and the field name as arguments so the
rendering process has access to the complete record context. This makes the
ViewHelper more flexible than directly accessing the field value.
.. index:: Frontend, ext:fluid
@@ -0,0 +1,45 @@
.. include:: /Includes.rst.txt
.. _feature-108904-1771065699:
===========================================================================================
Feature: #108904 - Add generic error action for custom HTTP status codes in ErrorController
===========================================================================================
See :issue:`108904`
Description
===========
The :php:`TYPO3\CMS\Frontend\Controller\ErrorController` has been enhanced with
a new method :php:`customErrorAction()` which allows custom error handling
for HTTP status codes.
The new method can be used with TYPO3 site error handling, allowing site
administrators to configure dedicated error handling (for example, rendering a
Fluid template) for a given status code.
Example of usage in an Extbase action:
.. code-block:: php
use TYPO3\CMS\Core\Http\PropagateResponseException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\ErrorController;
$response = GeneralUtility::makeInstance(ErrorController::class)->customErrorAction(
$this->request,
429,
'Rate limit exceeded.',
'You have exceeded the rate limit.'
);
throw new PropagateResponseException($response, 1771065101);
Impact
======
It is now possible to trigger custom error pages with specific HTTP status
codes and messages from within TYPO3 or extensions, while still respecting
the error handling in the main site configuration.
.. index:: Frontend, ext:core
@@ -0,0 +1,53 @@
.. include:: /Includes.rst.txt
.. _feature-108915-1742484720:
===========================================
Feature: #108915 - New page creation wizard
===========================================
See :issue:`108915`
Description
===========
The TYPO3 backend now features a new guided "Page Creation Wizard" designed to
streamline the page creation process. This interface replaces the traditional,
technically complex workflow with a modular and accessible step-by-step
process.
The primary goals of the wizard are to ensure data integrity by enforcing
mandatory fields during creation which improves accessibility, and provides a modern,
responsive user experience that does not require deep TYPO3-specific expertise.
Key features
------------
* **Guided workflow:** A step-by-step process including positioning, type
selection, data entry, and a final review before persistence.
* **Data integrity:** Validation of required fields (for example, page title)
occurs at each step to prevent broken or incomplete page records.
* **Context-aware:** The wizard can be triggered from various entry points
(for example, page tree, :guilabel:`Content > Records` module) and respects
predefined parameters like position and page type.
* **Modular and extensible:** Built using a generic architecture that allows
integrators to add custom steps or extend existing configuration for
specific page types.
* **FormEngine integration:** Dynamic steps are rendered using FormEngine,
ensuring that all TCA-based rules and field configurations are respected.
* **Post-creation actions:** After successful creation, users can choose whether to
jump to the :guilabel:`Content > Layout` module, create another page,
or return to their previous task.
Impact
======
Editors benefit from a faster, less error-prone way to build page structures.
The intuitive interface significantly lowers the barrier to entry for new users
while maintaining the flexibility required by power users.
Developers and integrators can leverage the modular design to customize the
creation process for custom `doktype` values or even adapt the wizard concept
for other TYPO3 workflows in the future.
.. index:: Backend, ext:backend
@@ -0,0 +1,65 @@
.. include:: /Includes.rst.txt
.. _feature-108941-1770902109:
========================================================================
Feature: #108941 - Provide language labels as virtual JavaScript modules
========================================================================
See :issue:`108941`
Description
===========
JavaScript modules can now import language labels as code.
The labels are exposed as an object with a `get()` method
and allows placeholder substitution conforming to the ICU message format.
.. code-block:: javascript
// Import labels from language domain "core.bookmarks"
import { html } from 'lit';
import labels from '~labels/core.bookmarks';
// Use label
html`<p>{labels.get('groupType.global')}</p>`
// Retrieve label and use ICU MessageFormat placeholders
// Example label: <source>File "{filename}" deleted</source>
html`<p>{labels.get('file.deleted', { filename: 'my-file.txt' })}</p>`
// Render a label containing pseudo XML tags
// Example label: "File <bold>{filename}</bold> deleted"
html`<p>{labels.get('file.deleted', {
filename: 'my-file.txt',
// Callback function that renders the contents of <bold>
bold: chunks => html`<strong>${chunks}</strong>`,
})}</p>`
This means controllers do not need to inject arbitrary labels into the
global `TYPO3.lang` configuration, which impeded writing generic web
components.
Virtual JavaScript modules (schema `~labels/{language.domain}`)
are created that resolve the labels for the specified language domain
provided after the `~labels/` prefix. This mapping is
implemented technically by using an import map path prefix which instructs the
JavaScript engine to append a specified suffix to the mapped prefix.
The labels can be cached client-side with a far-future cache lifetime,
similar to static resources. TYPO3 therefore generates version-specific
and locale-specific URLs to ensure labels can be cached by the user
agent without requiring explicit cache invalidation.
Impact
======
Extension developers can now use labels in JavaScript components without
requiring labels to be preloaded globally or per module, reducing the
risk of missing labels and also simplifying developer workflows.
Workarounds such as pushing labels to the top frame,
loading labels globally, and adding labels to component attributes
have previously been used and are replaced by this infrastructure.
.. index:: Backend, JavaScript, ext:backend
@@ -0,0 +1,159 @@
.. include:: /Includes.rst.txt
.. _feature-108966-1738963200:
===============================================================
Feature: #108966 - Rich text editor support in TYPO3 form editor
===============================================================
See :issue:`108966`
Description
===========
The TYPO3 form editor now supports rich text editing for textarea fields with
CKEditor 5. Form elements can be configured to use any available RTE preset,
which provides a consistent editing experience across the TYPO3 backend.
The implementation includes a new
:php:`\TYPO3\CMS\Form\Service\RichTextConfigurationService` that resolves
CKEditor configuration from global TYPO3 RTE presets and prepares it for use
in the form editor context. External plugins, such as the TYPO3 link browser,
are configured automatically.
Impact
======
Form integrators can now enable rich text editing in any textarea field in
the form editor by configuring it in the form YAML configuration.
The following form elements and finishers now support rich text editing out of
the box:
* StaticText element - formatted text in forms
* Checkbox element - labels with links for privacy policies, etc.
* Confirmation finisher - formatted confirmation messages
All textarea fields in custom form elements can be configured to use the RTE.
Basic configuration
-------------------
Enable rich text editing for a form element:
.. code-block:: yaml
:caption: EXT:my_extension/Configuration/Form/MyFormSetup.yaml
prototypes:
standard:
formElementsDefinition:
StaticText:
formEditor:
editors:
300:
identifier: staticText
templateName: Inspector-TextareaEditor
label: formEditor.elements.StaticText.editor.staticText.label
propertyPath: properties.text
enableRichtext: true
richtextConfiguration: form-label
The `richtextConfiguration` option accepts any registered RTE preset name, for
example:
* `form-label` - simple formatting for labels (bold, italic, link) - default
* `form-content` - extended formatting for content fields (includes lists)
* `default` - standard TYPO3 RTE with all features
* `minimal` - minimal feature set
New form RTE presets
--------------------
Two new RTE presets specifically designed for the form extension are now
available:
**form-label**
Essential formatting options for labels and short text fields.
Includes: bold, italic, link
**form-content**
Extended formatting options for content fields like StaticText.
Includes: bold, italic, link, bulleted lists, numbered lists
Configuration options
---------------------
The following options are available for textarea editors in the form editor:
`enableRichtext`
:aspect:`Data type`
boolean
:aspect:`Default`
false
:aspect:`Description`
Enables rich text editing for this textarea field.
`richtextConfiguration`
:aspect:`Data type`
string
:aspect:`Default`
`form-label`
:aspect:`Description`
Name of the RTE preset to use. The preset must be registered in
:php:`$GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']`.
Common presets: `form-label`, `form-content`, `default`, `minimal`,
`full`
Custom sanitizer configuration
------------------------------
The form extension uses a multi-layer sanitization approach for security:
* **Backend**: Content is sanitized using the `htmlSanitize.build` setting
from the RTE preset processing configuration.
* **Frontend**: Content is sanitized again using the `default` sanitizer via
the `f:sanitize.html()` ViewHelper.
To use a custom sanitizer in the backend, configure it in your RTE preset:
.. code-block:: yaml
:caption: EXT:my_extension/Configuration/RTE/MyPreset.yaml
processing:
HTMLparser_db:
htmlSanitize:
build: \MyVendor\MyExtension\Html\MySanitizerBuilder
Frontend customization
----------------------
The frontend templates use `f:sanitize.html()` with the `default` sanitizer for
defense-in-depth security. To customize the frontend sanitization, integrators
have two options:
**Option 1: Override Fluid templates**
Override the form element templates and specify a custom sanitizer build:
.. code-block:: html
:caption: EXT:my_extension/Resources/Private/Frontend/Partials/StaticText.html
{formvh:translateElementProperty(element: element, property: 'text')
-> f:sanitize.html(build: 'myCustomBuild')
-> f:transform.html()}
**Option 2: Register a custom default sanitizer**
Register a custom sanitizer builder as the default sanitizer globally:
.. code-block:: php
:caption: EXT:my_extension/ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['htmlSanitizer']['default']
= \MyVendor\MyExtension\Html\MySanitizerBuilder::class;
.. index:: Backend, RTE, ext:form, ext:rte_ckeditor
@@ -0,0 +1,36 @@
.. include:: /Includes.rst.txt
.. _feature-108975-1770984757:
=============================================================================
Feature: #108975 - Add configuration provider for Extbase class configuration
=============================================================================
See :issue:`108975`
Description
===========
Extbase *class configuration* (persistence mapping) is now exposed in the
backend :guilabel:`System > Configuration` module. The module is available
if the system extension :composer:`typo3/cms-lowlevel` is installed.
The displayed configuration reflects the configured mapping that Extbase uses
at runtime. It is built by collecting and merging all
:file:`EXT:my_extension/Configuration/Extbase/Persistence/Classes.php`
definitions from active packages.
.. seealso::
* :ref:`Connecting the model to the database <t3coreapi:extbase-manual-mapping>`
Impact
======
This is a read-only usability improvement. Developers and integrators can
inspect and verify resolved Extbase persistence class mapping such as
extension overrides in the backend, without having to dump configuration
arrays or manually check each
:file:`EXT:my_extension/Configuration/Extbase/Persistence/Classes.php` file.
.. index:: Backend, ext:extbase, ext:lowlevel
@@ -0,0 +1,111 @@
.. include:: /Includes.rst.txt
.. _feature-108982-1771078311:
==============================================================
Feature: #108982 - Introduce rate limiting for Extbase actions
==============================================================
See :issue:`108982`
Description
===========
Extbase now supports rate limiting for controller actions using the new PHP
attribute :php:`\TYPO3\CMS\Extbase\Attribute\RateLimit`. This feature allows
developers to restrict the number of requests a user can make to a specific
action within a given time frame.
.. note::
Rate limiting only works for uncached Extbase actions. For cached actions,
the TYPO3 frontend cache might return the response before the Extbase
controller is invoked, thus bypassing the rate limiting logic.
Rate limiting is based on the client's IP address and uses Symfony's
RateLimiter component with caching framework storage.
The :php:`#[RateLimit]` attribute supports the following properties:
* :php:`limit`: The maximum number of requests allowed (default: 5).
* :php:`interval`: The time window for the limit (for example,
`15 minutes`, `1 hour`) (default: `15 minutes`).
* :php:`policy`: The rate limiting policy to use (for example,
`sliding_window`, `fixed_window`) (default: `sliding_window`).
* :php:`message`: An optional, localizable translation key for the error
message shown when the limit is reached, for example
`messages.rate_limit_message` (the translation domain, such as
`my_extension`, is added automatically and must not be part of the key),
or
`LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:rate_limit_message`.
When a rate limit is exceeded, Extbase returns a response with HTTP status code
429 by default (:abbr:`Too Many Requests (Too Many Requests)`).
Usage
-----
Apply a rate limit to an Extbase action by adding a :php:`#[RateLimit]` attribute
to the action method:
.. code-block:: php
:caption: EXT:my_extension/Classes/Controller/MyController.php
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Attribute\RateLimit;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class MyController extends ActionController
{
#[RateLimit(limit: 3, interval: '1 minute', message: 'message.ratelimitexceeded')]
public function createAction(): ResponseInterface
{
// Business logic for creating an entity
return $this->redirect('index');
}
}
PSR-14 event: BeforeActionRateLimitResponseEvent
------------------------------------------------
The new PSR-14 event
:php:`\TYPO3\CMS\Extbase\Event\BeforeActionRateLimitResponseEvent`
is dispatched when a rate limit is triggered but before the response is
returned. This allows extension developers to modify the response or perform
additional actions, such as logging, throwing a custom exception, and enqueuing
a flash message.
The following example implementation shows how to throw a custom error if a rate
limit is reached. It is handled by a configured site error handler.
.. code-block:: php
:caption: EXT:my_extension/Classes/EventListener/ModifyRateLimitResponse.php
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Http\PropagateResponseException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Event\BeforeActionRateLimitResponseEvent;
use TYPO3\CMS\Frontend\Controller\ErrorController;
final readonly class MyEventListener
{
#[AsEventListener('my_extension/modify-rate-limit-response')]
public function __invoke(BeforeActionRateLimitResponseEvent $event): void
{
$response = GeneralUtility::makeInstance(ErrorController::class)
->accessDeniedAction(
$event->getRequest(),
$event->getRateLimit()->message,
);
throw new PropagateResponseException($response, 1771077885);
}
}
Impact
======
Developers can now protect sensitive Extbase actions (for example, form
submissions, login attempts, and heavy API endpoints) from abuse, spam, or
brute-force attacks with minimal effort.
.. index:: Frontend, ext:extbase
@@ -0,0 +1,91 @@
.. include:: /Includes.rst.txt
.. _feature-108992-1739706000:
=======================================================================
Feature: #108992 - New PSR-14 event for workspace dependency resolution
=======================================================================
See :issue:`108992`
Description
===========
A new PSR-14 event
:php:`\TYPO3\CMS\Workspaces\Event\IsReferenceConsideredForDependencyEvent`
has been added. It is dispatched for each :sql:`sys_refindex` row when the
workspace dependency resolver evaluates which references constitute structural
dependencies during publish, stage, discard, and display operations.
Listeners decide whether a particular reference should be treated as a workspace
dependency. References are opt-in: the default is "not a dependency", and
listeners must explicitly mark relevant references.
The event has the following methods:
* :php:`getTableName()`: The table owning the field
(:sql:`sys_refindex.tablename`).
* :php:`getRecordId()`: The record owning the field
(:sql:`sys_refindex.recuid`).
* :php:`getFieldName()`: The TCA field name (:sql:`sys_refindex.field`).
* :php:`getReferenceTable()`: The referenced table
(:sql:`sys_refindex.ref_table`).
* :php:`getReferenceId()`: The referenced record ID
(:sql:`sys_refindex.ref_uid`).
* :php:`getAction()`: The
:php:`\TYPO3\CMS\Workspaces\Dependency\DependencyCollectionAction` enum
value (:php:`Publish`, :php:`StageChange`, :php:`Discard`, or
:php:`Display`).
* :php:`getWorkspaceId()`: The current workspace ID.
* :php:`isDependency()` / :php:`setDependency()`: Read or change whether this
reference is a structural dependency.
TYPO3 Core registers a listener that marks :php:`type=inline`,
:php:`type=file` (with :php:`foreign_field`), and :php:`type=flex` fields as
dependencies.
A new enum
:php:`\TYPO3\CMS\Workspaces\Dependency\DependencyCollectionAction`
has been added to represent the action context.
Example
=======
A third-party extension that stores parent-child relationships in a custom
field can register a listener to include those references as workspace
dependencies:
.. code-block:: php
:caption: EXT:my_extension/Classes/EventListener/WorkspaceDependencyListener.php
namespace Vendor\MyPackage\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Workspaces\Event\
IsReferenceConsideredForDependencyEvent;
#[AsEventListener('my-package/workspace-dependency')]
final class WorkspaceDependencyListener
{
public function __invoke(
IsReferenceConsideredForDependencyEvent $event
): void {
if ($event->getFieldName() === 'tx_mypackage_parent') {
$event->setDependency(true);
}
}
}
Impact
======
Extensions can now register custom parent-child relationships as workspace
dependencies via this PSR-14 event. This ensures that structurally dependent
records are published, staged, or discarded together, preventing orphaned
records in workspaces.
The internal pseudo-event mechanism (`EventCallback`,
`ElementEntityProcessor`) that was previously used has been removed. This is an
internal change that does not affect the public API.
.. index:: Backend, PHP-API, ext:workspaces
@@ -0,0 +1,83 @@
.. include:: /Includes.rst.txt
.. _feature-109018-1769714898:
====================================================================
Feature: #109018 - PSR-14 event to modify indexed_search result sets
====================================================================
See :issue:`109018`
Description
===========
A new PSR-14 event
:php:`\TYPO3\CMS\IndexedSearch\Event\AfterSearchResultSetsAreGeneratedEvent`
has been introduced to modify search result sets in
:php-short:`\TYPO3\CMS\IndexedSearch\Controller\SearchController`.
The event is dispatched in :php:`searchAction()` after all the result sets have
been built. Event listeners can manipulate complete result sets, including
pagination, rows, section data, and category metadata.
The event has the following methods:
* :php:`getResultSets()`: Returns all the result sets from the current search.
* :php:`setResultSets(array $resultSets)`: Replaces the result sets.
* :php:`getSearchData()`: Returns the search configuration array.
* :php:`getSearchWords()`: Returns an array of search words.
* :php:`getView()`: Returns the view instance.
* :php:`getRequest()`: Returns the current server request.
Example
=======
The following example replaces every result set pagination with
:php-short:`\TYPO3\CMS\Core\Pagination\SlidingWindowPagination`:
.. code-block:: php
:caption: EXT:my_extension/Classes/EventListener/ModifySearchPaginationListener.php
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Pagination\SimplePagination;
use TYPO3\CMS\Core\Pagination\SlidingWindowPagination;
use TYPO3\CMS\IndexedSearch\Event\AfterSearchResultSetsAreGeneratedEvent;
#[AsEventListener(identifier: 'my-extension/modify-search-result-sets')]
final readonly class ModifySearchPaginationListener
{
public function __invoke(
AfterSearchResultSetsAreGeneratedEvent $event
): void {
$resultSets = $event->getResultSets();
foreach ($resultSets as $key => $resultSet) {
if (($resultSet['pagination'] ?? null)
instanceof SimplePagination
) {
$resultSets[$key]['pagination']
= new SlidingWindowPagination(
$resultSet['pagination']->getPaginator(),
5
);
}
}
$event->setResultSets($resultSets);
}
}
Impact
======
This event allows search result sets to be modified in a single listener
call. It enables custom pagination strategies, as well as advanced search
result transformations.
.. index:: Frontend, PHP-API, ext:indexed_search
@@ -0,0 +1,52 @@
.. include:: /Includes.rst.txt
.. _feature-109031:
=================================================
Feature: #109031 - Page position select component
=================================================
See :issue:`109031`
Description
===========
A new component has been added, based on the `page-browser`, that allows
a page to be selected and an insertion position to be defined. Possible
positions are `inside` and `after`.
Features
--------
* When a page node in the tree is selected insertion options are displayed.
Options include `Insert` and `After`. `After` is applicable to all
child pages.
* On first render, the selected node is scrolled into view
using `scrollNodeIntoViewIfNeeded`.
* The component emits a custom event
`typo3:page-position-select-tree:insert-position-change` whenever the
insertion position changes. The event payload contains `pageUid` (the
selected page ID) and `position` (the chosen insertion position),
allowing other modules to react accordingly.
Example usage
=============
.. code-block:: html
<typo3-backend-component-page-position-select
activePageId="1"
insertPosition="inside"
>
</typo3-backend-component-page-position-select>
Impact
======
This component can be used anywhere in the backend where page selection and
insertion position are needed, replacing previous workflows
with more intuitive controls.
.. index:: Backend
@@ -0,0 +1,177 @@
.. include:: /Includes.rst.txt
.. _feature-109080-1740000001:
==================================================================
Feature: #109080 - Unified RateLimiterFactory with admin overrides
==================================================================
See :issue:`109080`
Description
===========
TYPO3's :php:`\TYPO3\CMS\Core\RateLimiter\RateLimiterFactory` has been
refactored to serve as the single entry point for rate limiting across the
system. A new
:php:`\TYPO3\CMS\Core\RateLimiter\RateLimiterFactoryInterface`
extends Symfony's :php:`RateLimiterFactoryInterface` with additional
convenience methods for request-based and login rate limiting.
Previously, backend and frontend password recovery features and
Extbase rate limiting each created Symfony rate limiter factories,
bypassing TYPO3's factory. All consumers now use the central TYPO3 factory,
which enables a unified admin override mechanism.
Extension developers should type-hint against
:php-short:`\TYPO3\CMS\Core\RateLimiter\RateLimiterFactoryInterface`
when injecting the factory.
Admin overrides via TYPO3_CONF_VARS
-----------------------------------
A new configuration option
:php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['rateLimiter']`
allows administrators to override any rate limiter's settings by its ID. Each
key is a limiter ID, and each value is an array of settings to override:
.. code-block:: php
:caption: config/system/additional.php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['rateLimiter']['login-be'] = [
'limit' => 3,
'interval' => '5 minutes',
];
$GLOBALS['TYPO3_CONF_VARS']['SYS']['rateLimiter']['backend-password-recovery'] = [
'limit' => 1,
'interval' => '1 hour',
];
Known limiter IDs:
* `login-be` — backend login
* `login-fe` — frontend login
* `backend-password-recovery` — backend password reset
* `felogin-password-recovery` — frontend password recovery
* `extbase-<classSlug>-<actionMethod>` — Extbase :php:`#[RateLimit]`
actions
Example limiter ID for Extbase action
-------------------------------------
The limiter ID for an Extbase action with the :php:`#[RateLimit]`
attribute is constructed using the "slugified" class name and the action
method name.
.. code-block:: php
:caption: EXT:my_extension/Classes/Controller/MyController.php
namespace Vendor\MyExtension\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Attribute\RateLimit;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class MyController extends ActionController
{
#[RateLimit(
limit: 5,
interval: '10 minutes',
message: 'ratelimit.dosomething',
)]
public function doSomethingAction(): ResponseInterface
{
return $this->redirect('index');
}
}
The limiter ID for the action is
`extbase-vendor-myextension-controller-mycontroller-dosomethingaction`
General-purpose rate limiting
-----------------------------
Extension developers can now use the factory for custom rate limiting needs.
The :php:`createRequestBasedLimiter()` method is the recommended entry point
for request-scoped rate limiting. It extracts the client's
remote IP from the PSR-7 request and uses it as the limiter key:
.. code-block:: php
:caption: EXT:my_extension/Classes/Service/MyService.php
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\RateLimiter\RateLimiterFactoryInterface;
class MyService
{
public function __construct(
private readonly RateLimiterFactoryInterface $rateLimiterFactory,
) {}
public function doSomething(ServerRequestInterface $request): void
{
$limiter = $this->rateLimiterFactory->createRequestBasedLimiter(
$request,
[
'id' => 'my-extension-action',
'policy' => 'sliding_window',
'limit' => 10,
'interval' => '1 hour',
],
);
$limit = $limiter->consume();
if (!$limit->isAccepted()) {
// Handle rate limit exceeded
}
}
}
In cases where a custom key is needed, for example a user ID instead of the
IP address, the :php:`createLimiter()` method accepts an explicit
configuration array and key:
.. code-block:: php
$limiter = $this->rateLimiterFactory->createLimiter(
[
'id' => 'my-extension-action',
'policy' => 'sliding_window',
'limit' => 10,
'interval' => '1 hour',
],
$userId,
);
Preconfigured named services can also be defined in :file:`Services.yaml`.
They are then injectable with the :php:`create()` method from the
:php:`RateLimiterFactoryInterface`:
.. code-block:: yaml
:caption: EXT:my_extension/Configuration/Services.yaml
myRateLimiter:
class: TYPO3\CMS\Core\RateLimiter\RateLimiterFactory
arguments:
$config:
id: 'my-custom-limiter'
policy: 'sliding_window'
limit: 5
interval: '10 minutes'
Impact
======
All rate limiting in TYPO3 now flows through a single factory that supports
admin-level overrides. Administrators can tune or restrict rate limits for any
component—login, password recovery, Extbase actions, or custom extensions—
without modifying code, using
:php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['rateLimiter']`.
The login rate limiter now uses human-readable IDs (`login-be`, `login-fe`)
instead of SHA1 hashes. Existing cached rate limit state from previous
versions will expire naturally.
.. index:: PHP-API, LocalConfiguration, ext:core
@@ -0,0 +1,61 @@
.. include:: /Includes.rst.txt
.. _feature-109087:
===============================================================================
Feature: #109087 - Introduce BeforeBackendPageRenderEvent for BackendController
===============================================================================
See :issue:`109087`
Description
===========
A new PSR-14 event :php:`\TYPO3\CMS\Backend\Controller\Event\BeforeBackendPageRenderEvent`
has been introduced. It is dispatched in :php-short:`\TYPO3\CMS\Backend\Controller\BackendController`
before the main backend page is rendered. It provides access to:
* :php:`$view` (:php-short:`\TYPO3\CMS\Core\View\ViewInterface`) assign template
variables to the backend top frame view
* :php:`$javaScriptRenderer` (:php-short:`\TYPO3\CMS\Core\Page\JavaScriptRenderer`) add
custom JavaScript modules to the backend top frame
* :php:`$pageRenderer` (:php-short:`\TYPO3\CMS\Core\Page\PageRenderer`) add assets
such as CSS files (marked :php:`@internal`)
Example
=======
.. code-block:: php
:caption: EXT:my_extension/Classes/EventListener/BeforeBackendPageRenderEventListener.php
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Backend\Controller\Event\BeforeBackendPageRenderEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
#[AsEventListener(identifier: 'my-extension/before-backend-page-render')]
final class BeforeBackendPageRenderEventListener
{
public function __invoke(BeforeBackendPageRenderEvent $event): void
{
$event->javaScriptRenderer->addJavaScriptModuleInstruction(
JavaScriptModuleInstruction::create(
'@my-vendor/my-extension/backend-module.js'
)
);
}
}
Impact
======
It is now possible to add custom JavaScript modules and other assets to the
TYPO3 backend top frame using the new PSR-14 event
:php:`\TYPO3\CMS\Backend\Controller\Event\BeforeBackendPageRenderEvent`.
.. index:: Backend, PHP-API, ext:backend
@@ -0,0 +1,75 @@
.. include:: /Includes.rst.txt
.. _feature-109110-1742558400:
====================================================
Feature: #109110 - Introduce scheduler task priority
====================================================
See :issue:`109110`
Description
===========
A new :sql:`priority` column has been added to the
:sql:`tx_scheduler_task` table, allowing administrators to control the
execution order of scheduler tasks. Three levels are available:
* **High** (150)
* **Regular** (100, default)
* **Low** (50)
The scheduler now selects the next executable task ordered by
:sql:`priority DESC` first, using :sql:`nextexecution ASC` as a secondary
tiebreaker. This means a high-priority task is always
executed before a lower-priority task, regardless of how long the
lower-priority task has been waiting.
The priority field is exposed as a select field in the **Timing** tab of
the task editing form in all registered task types. The priority of each
task is also visible in the scheduler backend module list view.
Extending priority levels
=========================
Extensions can add custom priority levels by extending the TCA of
:sql:`tx_scheduler_task`. The :sql:`priority` field is a plain integer
column, so any positive integer value is valid. The scheduler module
automatically resolves the label of any registered TCA item, so that custom
values are displayed correctly in the list view. The TCA item's
:php:`label` key must point to a valid language label. If no matching item
is found, the raw integer is shown.
.. code-block:: php
:caption: EXT:my_extension/Configuration/TCA/Overrides/tx_scheduler_task.php
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
ExtensionManagementUtility::addTcaSelectItem(
'tx_scheduler_task',
'priority',
[
'label' => 'LLL:my_extension.messages:priority.critical',
'value' => 200,
],
150,
'after',
);
Choose integer values that fit naturally into the existing scale (50 /
100 / 150). Values above 150 are executed before **High**, values below
50 after **Low**.
Impact
======
Administrators can now assign a priority to each scheduler task. Tasks
with **High** priority are picked up before **Regular** tasks, and
**Regular** before **Low** tasks. If multiple tasks share the same
priority, the longest-overdue task is still selected first, preserving
the previous behavior as a tiebreaker.
Existing tasks receive the default priority **Regular** (100)
automatically via the schema update — no data migration is required.
.. index:: Backend, Database, TCA, ext:scheduler
@@ -0,0 +1,49 @@
.. include:: /Includes.rst.txt
.. _feature-109114-1772123512:
=============================================================
Feature: #109114 - Autocomplete for components via XSD schema
=============================================================
See :issue:`109114`
Description
===========
The :ref:`existing CLI command <feature-104114-1719419341>`
:bash:`typo3 fluid:schema:generate` has been extended to cover
Fluid components. When executed, the command creates `*.xsd` files in
:path:`var/transient/` for all available ViewHelpers and components,
which can be used by IDEs for autocompletion.
Usage:
.. code-block:: bash
vendor/bin/typo3 fluid:schema:generate
In order to work correctly the responsible component collection
needs to implement the new
:php-short:`TYPO3Fluid\Fluid\Core\Component\ComponentListProviderInterface`.
TYPO3's :ref:`Fluid components integration <feature-108508-1765987901>`
already implements this, so these components are supported out of the box.
Fluid Standalone has a default implementation of custom component
collections that are based on
:php-short:`TYPO3Fluid\Fluid\Core\Component\AbstractComponentCollection`,
which should cover components that were created before the official components
integration (such as those created with TYPO3 v13). However, if a custom
folder structure is used by overriding the default
:php:`resolveTemplateName()`, a custom implementation of
:php:`getAvailableComponents()` must be provided. In most cases, it
is easier to switch to the TYPO3 integration and remove the custom class.
Impact
======
The CLI command :bash:`typo3 fluid:schema:generate` now creates XSD
schema files for Fluid components, enabling autocompletion in supporting
IDEs.
.. index:: CLI, Fluid, ext:fluid
@@ -0,0 +1,45 @@
.. include:: /Includes.rst.txt
.. _feature-109126-1740000000:
=====================================================
Feature: #109126 - Introduce date editor for ext:form
=====================================================
See :issue:`109126`
Description
===========
A new web component :html:`<typo3-form--date-editor>` has been
introduced in the form editor backend. It replaces the plain text
input in the :yaml:`DateRange` validator minimum/maximum fields and the
:yaml:`defaultValue` field in the :yaml:`Date` form element.
Previously editors had to manually type date values or relative expressions
like :yaml:`-18 years` into a plain text field. The new structured editor
provides a user-friendly UI which has five modes:
* **No value** - Clears the constraint (empty value)
* **Today** - Sets the value to :yaml:`today`
* **Absolute date** - A native HTML5 date picker that produces `Y-m-d` values
* **Relative date** - Structured input with direction (past/future), amount
and unit (days, weeks, months, years), which produces expressions like
:yaml:`-18 years` or :yaml:`+1 month`
* **Custom relative expression** - Free-text input for arbitrary relative
date expressions that go beyond the structured input, such as compound
expressions like :yaml:`+1 month +3 days`. The input is validated against
the configured relative date pattern.
Impact
======
The form editor backend now provides a structured, user-friendly editor for
date constraints and default values in :yaml:`Date` form elements. Editors no
longer need to know the PHP relative date syntax - they can simply select
a mode, direction, amount, and unit from dropdown fields. For advanced use
cases, the custom mode allows arbitrary relative date expressions
with real-time validation to be entered. Existing form definitions are not affected and
continue to work without change.
.. index:: Backend, ext:form
@@ -0,0 +1,42 @@
.. include:: /Includes.rst.txt
.. _feature-109130-1772489836:
=============================================================
Feature: #109130 - Context-aware editing in the layout module
=============================================================
See :issue:`109130`
Description
===========
The :guilabel:`Content > Layout` module now features a **context panel**
for editing page properties and content elements. Clicking on an edit
button opens a slide-in panel next to the page layout. The editing form
is displayed inside the panel with the page layout remaining visible in
the background.
The panel supports all FormEngine fields in an improved UI. The panel
header displays the record title as well as **Save** and **Close**
buttons. An **Expand** button allows switching to the full record
editing form in the content area at any time. After saving, the panel
stays open for further edits.
User settings
=============
The context panel is **enabled by default**. It can be disabled per user
in :guilabel:`User Settings` via the
:guilabel:`Use quick editing for records in the page module` option. When
disabled, edit buttons navigate to the full record editing form
as before. The setting takes effect immediately.
Impact
======
Editors can now edit records in the :guilabel:`Content > Layout` module
without leaving the page layout context. The full editing form remains
accessible for more complex editing tasks.
.. index:: Backend, ext:backend
@@ -0,0 +1,157 @@
.. include:: /Includes.rst.txt
.. _feature-109163-1772708896:
===============================================================
Feature: #109163 - Implement public system resources publishing
===============================================================
See :issue:`109163`
Description
===========
When implementing the new system resources API
(:ref:`feature-107537-1759136314`), resource publishing was skipped and has now
been implemented.
The most visible feature of this implementation is the new
`asset:publish` command. This command can publish public
extension resources from their `Resources/Public` folder to the document root
directory (`public` by default in Composer mode).
To maintain backward compatibility for Composer mode installations, this
command is automatically executed during `composer install`. This means
that after Composer has done its job installing packages, extension assets
are already published.
Public extension resources are also published when extensions are set up
with the `extension:setup` command or when an extension is activated in the Extension
Manager. Because of this, and because it might not be desirable or applicable
to publish assets at Composer build time, it is now possible to skip publishing
during `composer install` by setting an environment variable
`TYPO3_SKIP_ASSET_PUBLISH`, for example:
`TYPO3_SKIP_ASSET_PUBLISH=1 composer install`.
Not publishing assets at `composer install` is likely to become default behavior
in future TYPO3 versions.
TYPO3 ships file system-based publishing only. From now on, however, there is
an additional strategy available besides symlink publishing (*nix systems) and
junction publishing (Windows systems). TYPO3 can now copy all files and
folders from their private locations to the document root. This is useful for
many use cases such as container builds, deployments with read-only file
systems, restrictive hosting environments, and others.
By default, the linking strategy is being kept, particularly for backward
compatibility reasons. It is, however, possible to influence the behavior by
setting the following configuration option:
Default behavior: always link:
:php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['SystemResources']['filesystemPublishingType'] = 'link';`
Always copy / mirror files:
:php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['SystemResources']['filesystemPublishingType'] = 'mirror';`
Copy / mirror files in a `Production` context and link folders in a `Development`
context:
:php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['SystemResources']['filesystemPublishingType'] = 'auto';`
Beyond file system publishing
-----------------------------
Although TYPO3 Core only delivers file system-based publishing, third-party
extensions can now implement other ways of publishing public system resources.
By implementing
:php:`\TYPO3\CMS\Core\SystemResource\Publishing\SystemResourcePublisherInterface`
and registering the implementing class as an alias of the interface, TYPO3
will use this not only to publish system resources, but also to generate URIs
that reflect their new location, for example on a CDN.
This also works in TYPO3 classic mode, because publishing is now part of
extension activation.
Simple example of how to generate URIs for a CDN:
.. code-block:: php
:caption: EXT:my_extension/Classes/Service/ExampleResourcePublisher.php
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\Service;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Core\Http\Uri;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Package\PackageInterface;
use TYPO3\CMS\Core\SystemResource\Publishing\DefaultSystemResourcePublisher;
use TYPO3\CMS\Core\SystemResource\Publishing\SystemResourcePublisherInterface;
use TYPO3\CMS\Core\SystemResource\Publishing\UriGenerationOptions;
use TYPO3\CMS\Core\SystemResource\Type\PublicPackageFile;
use TYPO3\CMS\Core\SystemResource\Type\PublicResourceInterface;
#[Autoconfigure(public: true), AsAlias(SystemResourcePublisherInterface::class, public: true)]
final readonly class ExampleResourcePublisher implements SystemResourcePublisherInterface
{
private const CDN_URL = 'https://my.awsome.cdn/files/';
public function __construct(
private DefaultSystemResourcePublisher
$defaultSystemResourcePublisher,
) {}
public function publishResources(
PackageInterface $package,
): FlashMessageQueue {
// Additional logic to publish files to a CDN could be added
// here. For this example, the CDN loads the assets from the
// source automatically, so resources are published as usual.
return $this->defaultSystemResourcePublisher->publishResources(
$package,
);
}
public function generateUri(
PublicResourceInterface $publicResource,
?ServerRequestInterface $request,
?UriGenerationOptions $options = null,
): UriInterface {
$defaultUri = $this->defaultSystemResourcePublisher->generateUri(
$publicResource,
$request,
new UriGenerationOptions(
uriPrefix: '',
absoluteUri: false,
cacheBusting: false,
),
);
if ($publicResource instanceof PublicPackageFile) {
return new Uri(self::CDN_URL . $defaultUri);
}
return $defaultUri;
}
}
Impact
======
There is no apparent impact for any TYPO3 installation, as the changes are mostly internal
and the public API and behavior are the same as before. For deployments, nothing needs to
be changed, as asset publishing is still performed at `composer install`, and also
by the `extension:setup` command, both of which are already part of any deployment workflow.
Users, however, now have more control over when and how publishing is performed, by setting
the environment variable `TYPO3_SKIP_ASSET_PUBLISH=1` for `composer install` or by configuring
the `mirror` strategy for publishing by setting
:php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['SystemResources']['filesystemPublishingType'] = 'mirror';`
in `config/system/additional.php`.
.. index:: ext:core
@@ -0,0 +1,100 @@
.. include:: /Includes.rst.txt
.. _feature-109167-1773174150:
=========================================================
Feature: #109167 - Improved exceptions in Fluid templates
=========================================================
See :issue:`109167`
Description
===========
In an effort to simplify debugging Fluid templates, TYPO3 14 enhances
exception messages thrown by Fluid in several ways:
* Templates that contain invalid syntax or refer to undeclared ViewHelper
arguments now contain both the full path to the template file and the
affected line number in that file.
* Most ViewHelper-related error messages now contain the full path to the
template file.
* Fluid Standalone 5.2 (also backported to Fluid 4.6) introduces more granular
exception classes that can be used by ViewHelpers to classify runtime errors.
These classifications are also part of the error message.
* When a referenced Fluid template cannot be found, the exception message
contains a full list of the candidates that have been tried. Also,
the exception contains the context in which the template file is missing
(for example `FLUIDTEMPLATE` or `PAGEVIEW`).
In order for this to work with custom ViewHelper implementations, ViewHelpers
need to use the base ViewHelper exception class or one of its child classes:
* :php:`\TYPO3Fluid\Fluid\Core\ViewHelper\Exception` for general exceptions
* :php:`\TYPO3Fluid\Fluid\Core\ViewHelper\InvalidArgumentException` for
general exceptions related to ViewHelper arguments
* :php:`\TYPO3Fluid\Fluid\Core\ViewHelper\InvalidArgumentValueException` for
invalid ViewHelper argument values (e.g. wrong type, empty, invalid format)
* :php:`\TYPO3Fluid\Fluid\Core\ViewHelper\MissingArgumentException` if
a required ViewHelper argument has not been supplied
* :php:`\TYPO3Fluid\Fluid\Core\ViewHelper\UndeclaredArgumentException` if
a ViewHelper is called with an argument that has not been defined
If any of these exception classes are used in a ViewHelper, Fluid's internal
error handler automatically adds the full path to the current template file
to the exception. It is not necessary for ViewHelpers to do this themselves.
Note that this leads to nested exceptions. The original exception can be
accessed via :php:`$e->getPrevious()`.
Examples
--------
.. code-block:: plaintext
:caption: Parse error in template
#1238169398 TYPO3Fluid\Fluid\Core\Parser\Exception
Fluid parse error in template /var/www/html/typo3conf/ext/theme/Resources/Private/Components/Test/Test.fluid.html, line 11 at character 15.
Error: Not all tags were closed! (error code 1238169398). Template source chunk: test
.. code-block:: plaintext
:caption: Undeclared ViewHelper argument
#1773227091 TYPO3Fluid\Fluid\Core\ViewHelper\Exception
TYPO3Fluid\Fluid\Core\ViewHelper\UndeclaredArgumentException in /var/www/html/typo3conf/ext/theme/Resources/Private/Components/Test/Test.fluid.html:
Undeclared arguments passed to ViewHelper TYPO3Fluid\Fluid\ViewHelpers\Format\TrimViewHelper: foo. Valid arguments are: value, characters, side
(/var/www/html/vendor/typo3fluid/fluid/src/Core/ViewHelper/AbstractViewHelper.php:314)
.. code-block:: plaintext
:caption: Custom validation by ViewHelper implementation
#1669191560 TYPO3Fluid\Fluid\Core\ViewHelper\Exception
TYPO3Fluid\Fluid\Core\ViewHelper\InvalidArgumentValueException in /var/www/html/typo3conf/ext/theme/Resources/Private/Components/Test/Test.fluid.html:
The side "none" supplied to Fluid's format.trim ViewHelper is not supported.
(/var/www/html/vendor/typo3fluid/fluid/src/ViewHelpers/Format/TrimViewHelper.php:118)
.. code-block:: plaintext
:caption: Missing template file for PAGEVIEW
#1742058289 TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException
PAGEVIEW TypoScript object: Failed to resolve a template file for page layout "default". See also: https://docs.typo3.org/permalink/t3tsref:cobj-pageview@14.2.
The following paths were checked:
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/default.fluid.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/default.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/default",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/Default.fluid.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/Default.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default/Default",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/default.fluid.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/default.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/default",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default.fluid.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default.html",
"/var/www/html/typo3conf/ext/dummy/Resources/Private/Templates/Pages/Default"
Impact
======
To make debugging easier, exceptions that originate from Fluid templates now
contain more context, such as the full path to the template file.
.. index:: Fluid, ext:fluid

Some files were not shown because too many files have changed in this diff Show More