TYPO3 Core Changelog

Extension key

core

Package name

typo3/cms-core

Version

main

Language

en

Author

TYPO3 contributors

License

This document is published under the Creative Commons BY 4.0 license.

Rendered

Wed, 07 May 2025 12:11:41 +0000


This extension provides the TYPO3 API, i.e. the core functionalities, which can be used and extended in any other TYPO3 extension.

Note: Every change to the TYPO3 Core which might affect your site is documented here.


Table of Contents:

ChangeLog v14

Every change to the TYPO3 Core which might affect your site is documented here.

Also available

14.0 Changes

Table of contents

Breaking Changes

Features

Deprecation

Important

Breaking: #68303 - Make tt_content imagewidth/imageheight nullable

See forge#68303

Description

The default value of the fields imagewidth and imageheight of the tt_content table is now null. This removes the awkward UI behavior of the fields being set to 0 if no value is entered.

Impact

Custom queries might fail if they expect the fields to be 0 instead of null.

Affected installations

TYPO3 installation relying on fields imagewidth and imageheight of the tt_content table being always an integer.

Migration

Use the "Media fields zero to null" upgrade wizard to update the field values.

Also modify your queries to handle null values instead of 0.

Breaking: #85323 - Move GET parameters in sitemap into namespace

See forge#85323

Description

The names of the GET parameters used in the sitemap generated by EXT:seo have been changed from page and sitemap to tx_seo[page] and tx_seo[sitemap] respectively.

Impact

Applications, routing configuration and 3rd party tools that rely on the parameters being named page and sitemap break.

Affected installations

This affects installations, which use the arguments page and sitemap or override the sitemap templates of EXT:seo.

Migration

In case the arguments are mapped in the routing configuration, the code needs to be slightly adopted. A working example issue

routeEnhancers:
  Sitemap:
    type: Simple
    routePath: 'sitemap-type/{sitemap}'
    aspects:
      sitemap:
        type: StaticValueMapper
        map:
          pages: pages
          tx_news: tx_news
          my_other_sitemap: my_other_sitemap
    _arguments:
      sitemap: 'tx_seo/sitemap'
Copied!

If the templates of EXT:seo/Resources/Private/Templates/XmlSitemap/Index.xml have been modified, adopt the generated links to fit the original ones.

In case the URL to a single sitemap has been provided to a 3rd party tool like a crawler, search engine, ... it must be added again with the new URL.

Breaking: #97151 - Remove "Database Relations" backend module

See forge#97151

Description

The backend sub-module "Database Relations" within "DB Check" aims to provide information about potentially broken database relations. The information it gives is really sparse and barely helpful, also the whole module and its code received no meaningful updates in the past.

Due to this, the module has been removed.

Impact

The module has been removed. Links and stored bookmarks will not work anymore.

Affected installations

All TYPO3 installations are affected.

Migration

There is no migration available.

Breaking: #101292 - Strong-typed PropertyMappingConfigurationInterface

See forge#101292

Description

Extbase's PropertyMappingConfigurationInterface is now fully typed with native PHP types.

Impact

Existing implementations will fail to work, as per Liskov's Substitution Principle, implementations need follow the contract restrictions.

Affected installations

TYPO3 installations with custom PHP code implementing a custom PropertyMappingConfiguration, which is rather uncommon.

Migration

Add the native PHP types in all custom implementations of the PropertyMappingConfigurationInterface to fulfill the contract again.

Breaking: #101392 - getIdentifier() and setIdentifier() from AbstractFile removed

See forge#101392

Description

When using the PHP API of File Abstraction Layer, there are several classes involved representing a File Object.

Next to the FileInterface there is also the AbstractFile class, where most classes extend from when representing a File.

However, in order to ensure proper code strictness, the Abstract class does not implement the methods getIdentifier() and setIdentifier() anymore, as this is indeed part of the subclasses' job.

They are now implemented in the respective classes inheriting from AbstractFile.

Impact

In an unlikely case that the TYPO3's File Abstraction Layer is extended by adding custom PHP classes extending from AbstractFile, this will result in a fatal PHP error, as the new abstract methods getIdentifier() and setIdentifier() are not implemented.

Affected installations

TYPO3 installations with a custom File Abstraction Layer code extending the actual file abstraction layer, which is highly unlikely.

Migration

Implement the two methods getIdentifier() and setIdentifier() in the custom File class extending AbstractFile.

This can also be done in previous TYPO3 versions to make the code ready for multiple TYPO3 versions.

Breaking: #103910 - Change logout handling in ext:felogin

See forge#103910

Description

The logout handling has been adjusted to correctly dispatch the PSR-14 event LogoutConfirmedEvent when a logout redirect is configured. The actionUri variable has been removed, and the logout template has been updated to reflect the change, including the correct use of the noredirect functionality.

Impact

The PSR-14 event LogoutConfirmedEvent is now correctly dispatched, when a logout redirect is configured. Additionally, the noredirect parameter is now evaluated on logout.

Affected installations

Websites using ext:felogin with a custom Fluid template for the logout form.

Migration

The {actionUri} variable is not available any more and should be removed from the template.

// Before
<f:form action="login" actionUri="{actionUri}" target="_top" fieldNamePrefix="">

// After
<f:form action="login" target="_top" fieldNamePrefix="">
Copied!

The evaluation of the noRedirect variable must be added to the template.

// Before
<div class="felogin-hidden">
    <f:form.hidden name="logintype" value="logout"/>
</div>

// After
<div class="felogin-hidden">
    <f:form.hidden name="logintype" value="logout"/>
    <f:if condition="{noRedirect}!=''">
        <f:form.hidden name="noredirect" value="1" />
    </f:if>
</div>
Copied!

Breaking: #103913 - Do not perform redirect in ext:felogin logoutAction

See forge#103913

Description

Redirect handling for the logoutAction has been removed.

Impact

The logoutAction will not perform a possible configured redirect via plugin or GET parameter.

Affected installations

TYPO3 installation relying on redirect handling in logoutAction.

Migration

There is no migration path, since the redirect handling in the logoutAction was wrong and possible configured redirects were already correctly handled via loginAction and overviewAction.

Breaking: #105377 - Deprecated functionality removed

See forge#105377

Description

The following PHP classes that have previously been marked as deprecated with v13 have been removed:

The following PHP classes have been declared final:

The following PHP interfaces that have previously been marked as deprecated with v13 have been removed:

The following PHP interfaces changed:

  • \TYPO3\CMS\Backend\LoginProvider\LoginProviderInterface->modifyView() added (Deprecation entry)
  • \TYPO3\CMS\Backend\LoginProvider\LoginProviderInterface->render() removed (Deprecation entry)
  • \TYPO3\CMS\Core\PageTitle\PageTitleProviderInterface->setRequest() added forge#102817

The following PHP class aliases that have previously been marked as deprecated with v13 have been removed:

The following PHP class methods that have previously been marked as deprecated with v13 have been removed:

The following PHP static class methods that have previously been marked as deprecated for v13 have been removed:

The following methods changed signature according to previous deprecations in v13 at the end of the argument list:

  • \TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->__construct() - All arguments are now mandatory (Deprecation entry)
  • \TYPO3\CMS\Core\Imaging\IconFactory->getIcon() (argument 4 is now of type \TYPO3\CMS\Core\Imaging\IconState|null) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\AbstractFile->copyTo() (argument 3 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\AbstractFile->moveTo() (argument 3 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\AbstractFile->rename() (argument 2 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\FileInterface->rename() (argument 2 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\FileReference->rename() (argument 2 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\Folder->addFile() (argument 3 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\Folder->addUploadedFile() (argument 2 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\Folder->copyTo() (argument 3 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\Folder->moveTo() (argument 3 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\InaccessibleFolder->addFile() (argument 3 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\InaccessibleFolder->addUploadedFile() (argument 2 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\InaccessibleFolder->copyTo() (argument 3 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\InaccessibleFolder->moveTo() (argument 3 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\ResourceStorage->addFile() (argument 4 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\ResourceStorage->addUploadedFile() (argument 4 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\ResourceStorage->copyFile() (argument 4 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\ResourceStorage->copyFolder() (argument 4 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\ResourceStorage->moveFile() (argument 4 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\ResourceStorage->moveFolder() (argument 4 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Resource\ResourceStorage->renameFile() (argument 3 is now of type \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior ) (Deprecation entry)
  • \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPlugin() (argument 2 $type and 3 $extensionKey have been dropped) (Deprecation entry)

The following public class properties have been dropped:

The following class property has changed/enforced type:

  • \TYPO3\CMS\Extbase\Mvc\Controller\ActionController->view (is now \TYPO3\CMS\Core\View\ViewInterface ) (Deprecation entry)

The following TypoScript options have been dropped or adapted:

The following user TSconfig options have been removed:

The following class constants have been dropped:

The following global option handling have been dropped and are ignored:

The following global variables have been changed:

  • $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['SecondDatabase']['driverMiddlewares']['driver-middleware-identifier'] must be an array, not a class string (Deprecation entry)

The following hooks have been removed:

  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvHeader'] (Deprecation entry)
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvRow'] (Deprecation entry)

The following TCA options are not evaluated anymore:

  • $GLOBALS['TCA'][$table]['types']['subtype_value_field']
  • $GLOBALS['TCA'][$table]['types']['subtypes_addlist']
  • $GLOBALS['TCA'][$table]['types']['subtypes_excludelist']

The following extbase validator options have been removed:

The following fallbacks have been removed:

  • Accepting arrays returned by readFileContent() in Indexed Search external parsers (Deprecation entry)
  • Allowing instantiation of \TYPO3\CMS\Core\Imaging\IconRegistry in ext_localconf.php (Deprecation entry)
  • Accepting a comma-separated list of fields as value for the columnsOnly parameter (Deprecation entry)
  • Support for extbase repository magic findByX(), findOneByX() and countByX() methods (Deprecation entry)
  • Fluid view helpers that extend \TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper should no longer register class attribute and should rely on attribute auto registration for the error class to be added correctly. (Deprecation entry)
  • The legacy backend entry point typo3/index.php has been removed along with handling of composer.json setting extra.typo3/cms.install-deprecated-typo3-index-php (Deprecation entry)

The following upgrade wizards have been removed:

  • Install extension "fe_login_mode" from TER
  • Migrate base and path to the new identifier property of the "sys_filemounts" table
  • Migrate site settings to separate file
  • Set workspace records in table "sys_template" to deleted
  • Migrate backend user and groups to new module names
  • Migrate backend groups "explicit_allowdeny" field to simplified format
  • Migrate sys_log entries to a JSON formatted value
  • Migrate storage and folder to the new folder_identifier property of the "sys_file_collection" table

The following database table fields have been removed:

The following JavaScript modules have been removed:

The following JavaScript method behaviours have changed:

  • FormEngineValidation.markFieldAsChanged() always requires HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement to be passed as first argument (Deprecation entry)
  • FormEngineValidation.validateField() always requires HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement to be passed as first argument (Deprecation entry)

The following JavaScript method has been removed:

The following smooth migration for JavaScript modules have been removed:

  • @typo3/backend/page-tree/page-tree-element to @typo3/backend/tree/page-tree-element (Deprecation entry)

The following localization XLIFF files have been removed:

The following template files have been removed:

The following content element definitions have been removed:

Impact

Using above removed functionality will most likely raise PHP fatal level errors, may change website output or crashes browser JavaScript.

Breaking: #105549 - Improved ISO8601 Date Handling in TYPO3 DataHandler

See forge#105549

Description

The DataHandler PHP API has been extended to support qualified and unqualified ISO8601 date formats in order to correctly process supplied timezone offsets, if supplied.

  • Qualified ISO8601: Includes an explicit timezone offset (e.g., 1999-12-11T10:09:00+01:00 or 1999-12-11T10:09:00Z)
  • Unqualified ISO8601: Omits timezone offsets, representing "LOCALTIME" (e.g., 1999-12-11T10:09:00)

Previously, TYPO3 incorrectly used qualified ISO8601 with Z (UTC+00:00) to denote "LOCALTIME" and applied the server's timezone offset, leading to misinterpretations when any other timezone offset was given, or if real UTC-0 was intended instead of LOCALTIME. Now, timezone offsets are accurately applied if supplied and based on server localtime if omitted.

TYPO3 will internally use unqualified ISO8601 dates for communication between FormEngine and DataHandler API from now on, allowing to correctly process timezone offsets – instead of shifting them – if supplied to the DataHandler PHP API.

In essence this means that existing workarounds for the previously applied timezone offsets need to be revised and removed.

Impact

TYPO3 now provides accurate and consistent handling of ISO8601 dates, eliminating previous issues related to timezone interpretation and LOCALTIME representation.

Affected installations

Installations with custom TYPO3 extensions that invoke DataHandler API with data for type="datetime" fields.

Migration

Qualified ISO8601 dates with intended timezone offsets, and \DateTimeInterface objects can now be passed directly to the DataHandler without requiring manual timezone adjustments.

Example for a previous workaround timezone offsets for DataHandler:

Passing datetime data via DataHandler PHP API
$myDate = new \DateTime('yesterday');
$this->dataHandler->start([
    'tx_myextension_mytable' => [
        'NEW-1' => [
            'pid' => 2,
            // A previous workaround add localtime offset to supplied dates,
            //  as it was subtracted by DataHandler persistence layer
            'mydatefield_1' => gmdate('c', $myDate->getTimestamp() + (int)date('Z')),
        ],
    ],
]);
Copied!

Previous timezone shifting workarounds can be removed and replaced by intuitive formats.

Passing datetime data via DataHandler PHP API
$myDate = new \DateTime('yesterday');
$this->dataHandler->start([
    'tx_myextension_mytable' => [
        'NEW-1' => [
            'pid' => 2,
            // pass \DateTimeInterface object directly
            'mydatefield_1' => $myDate,
            // format as LOCALTIME
            'mydatefield_2' => $myDate->format('Y-m-dTH:i:s'),
            // format with timezone information
            // (offsets will be normalized to persistence timezone format,
            // UTC for integer fields, LOCALTIME for native DATETIME fields)
            'mydatefield_3' => $myDate->format('c'),
        ],
    ],
]);
Copied!

Breaking: #105686 - Avoid obsolete $charset in sanitizeFileName()

See forge#105686

Description

Class \TYPO3\CMS\Core\Resource\Driver\DriverInterface :

public function sanitizeFileName(string $fileName, string $charset = ''): string
Copied!

has been simplified to:

public function sanitizeFileName(string $fileName): string
Copied!

Classes implementing the interface no longer need to take care of a second argument.

Impact

This most likely has little to no impact since the main API caller, the core class ResourceStorage never hands over the second argument. Default implementing class \TYPO3\CMS\Core\Resource\Driver\LocalDriver thus always fell back as if handling utf-8 strings.

Affected installations

Projects with instances implementing own FAL drivers using DriverInterface may be affected.

Migration

Implementing classes should drop support for the second argument. It does not collide with the interface if the second argument is kept, but core code will never call method sanitizeFileName() with handing over a value for a second argument.

Breaking: #105695 - Simplified CharsetConverter

See forge#105695

Description

The following methods have been removed:

  • TYPO3\CMS\Core\Charset\CharsetConverter->conv()
  • TYPO3\CMS\Core\Charset\CharsetConverter->utf8_encode()
  • TYPO3\CMS\Core\Charset\CharsetConverter->utf8_decode()
  • TYPO3\CMS\Core\Charset\CharsetConverter->specCharsToASCII(), use TYPO3\CMS\Core\Charset\CharsetConverter->utf8_char_mapping() instead
  • TYPO3\CMS\Core\Charset\CharsetConverter->sb_char_mapping()
  • TYPO3\CMS\Core\Charset\CharsetConverter->euc_char_mapping()

This removes most helper methods that implemented conversions between different charsets from the core codebase: The vast majority of web sites is based on utf-8 nowadays and needs no expensive conversion provided as core framework functionality anymore.

Impact

Calling one of the above methods will raise fatal PHP errors.

Affected installations

The core does not surface any of this removed low level functionality in upper layers like TypoScript for a while already. The removed methods should have little to no impact to casual instances. The only use cases that may be affected are probably import and export extensions that had to convert between nowadays rather obscure character sets like those of the EUC family. Affected extensions could mitigate the removal by copying the TYPO3 v13 version of class CharsetConverter including affected files from core/Resources/Private/Charsets/csconvtbl/ to their own codebase. The extension scanner will find usages and classify as weak match.

Migration

Avoid calling above methods. Extensions that still need above functionality should copy consumed functionality to their own codebase, or use some third party library.

This detail has a direct substitution:

// Before
$charsetConverter->specCharsToASCII('utf-8', $myString);
// After
$charsetConverter->utf8_char_mapping($myString);
Copied!

Breaking: #105728 - Extbase backend modules not in page context rely on global TypoScript only

See forge#105728

Description

Configuration of extbase based backend modules can be done using frontend TypoScript.

The standard prefix in TypoScript to do this is module.tx_myextension, extbase backend module controllers can typically retrieve their configuration using a call like $configuration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS, 'myextension');.

TypoScript itself is always bound to a page: In the frontend, there must be either some rootline page with a sys_template record, or a page that has a site set. The frontend rendering will otherwise bail out with an error message.

Extbase based backend modules are sometimes bound to pages as well: They can have a rendered page tree configured by their module configuration, and then receive the selected page uid within the request as GET parameter id.

Other extbase based backend modules however are not within page scope and do not render the page tree. Examples of such modules within the core are the backend modules delivered by the form and beuser extension.

Such extbase based backend without page tree modules had a hard time to calculate their relevant frontend TypoScript based configuration: Since TypoScript is bound to pages, they looked for "the first" valid page in the page tree, and the first valid sys_template record to calculate their TypoScript configuration. This dependency and guesswork made final configuration of extbase backend module configuration not in page context brittle, intransparent and clumsy.

TYPO3 v14 puts an end to this: Extbase backend modules without page context compile their TypoScript configuration from "global" TypoScript only and stop calculating TypoScript from guessing "the first valid" page.

The key call to register such "global" TypoScript is ExtensionManagementUtility::addTypoScriptSetup() in ext_localconf.php files.

Impact

Configuration of extbase based backend modules may change if their configuration is defined by the first given valid page in the page tree. Configuration of such backend modules can no longer be changed by including TypoScript on the "first valid" page.

Affected installations

Instances with extbase based backend modules without page tree may be affected.

Migration

Configuration of extbase based backend modules without page tree must be supplied programmatically and "made global" by extending $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup'] using ExtensionManagementUtility::addTypoScriptSetup() within extensions ext_localconf.php files. The backend module of the form extension is a good example. Additional locations of extensions that deliver form yaml definitions are defined like this:

EXT:my_extension/ext_localconf.php
ExtensionManagementUtility::addTypoScriptSetup('
    module.tx_form {
        settings {
            yamlConfigurations {
                1732884807 = EXT:my_extension/Configuration/Yaml/FormSetup.yaml
            }
        }
    }
');
Copied!

Note it is also possible to use ExtensionManagementUtility::addTypoScriptConstants() to declare "global" TypoScrip constants, and to use them in above TypoScript.

Breaking: #105733 - FileNameValidator no longer accepts custom regex in __construct()

See forge#105733

Description

Class \TYPO3\CMS\Core\Resource\Security\FileNameValidator does not handle a custom file deny pattern in __construct() anymore. The service is now stateless and can be injected without side effects.

Impact

A custom partial regex as first constructor argument when instantiating the service is ignored. The service relies on $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] configuration, and a hard coded constant as fallback.

Affected installations

Instances with custom extensions using GeneralUtility::makeInstance(FileNameValidator::class, 'some-custom-pattern'); are affected. This is most likely a very rare case.

Migration

Extensions that need to test with custom patterns that can not be declared globally using $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] should probably switch to an own service implementing the test, or inline the code. The main worker code of the service is just four lines of code.

Breaking: #105809 - AfterMailerInitializationEvent removed

See forge#105809

Description

The event \TYPO3\CMS\Core\Mail\Event\AfterMailerInitializationEvent has been removed. This event became useless with the introduction of the symfony based mailer in TYPO3 v10 and was only able to influence the core handling by calling the @internal marked method injectMailSettings() after the settings have already been determined within the core mailer. The event has been removed since it did not fit a useful use case anymore.

Impact

Event listeners registered for this event will no longer be triggered.

Affected installations

This event did not fit much purpose since the switch to the symfony based mailer anymore and is probably not used in many instances. The extension scanner will find usages.

Migration

Check if this event could be substituted by reconfiguring $GLOBALS['TYPO3_CONF_VARS']['MAIL'] , or by listening on \TYPO3\CMS\Core\Mail\Event\BeforeMailerSentMessageEvent event instead.

Breaking: #105863 - Remove exposeNonexistentUserInForgotPasswordDialog setting in ext:felogin

See forge#105863

Description

The TypoScript setting exposeNonexistentUserInForgotPasswordDialog has been removed in ext:felogin.

Impact

Using the TypoScript setting exposeNonexistentUserInForgotPasswordDialog has no effect any more and the password recovery process in ext:felogin now always shows the same message, when either a username or email address is submitted in the password recovery form.

Affected installations

Websites using the TypoScript setting exposeNonexistentUserInForgotPasswordDialog for ext:felogin.

Migration

The setting has been removed without a replacement. It is possible to use the PSR-14 event \TYPO3\CMS\FrontendLogin\Event\SendRecoveryEmailEvent to implement a similar functionality if really needed. From a security perspective, it is however highly recommended to not expose the existence of email addresses or usernames.

Breaking: #105920 - Folder->getSubFolder() throws FolderDoesNotExistException

See forge#105920

Description

An exception handling detail within FAL/Resource handling has been changed: When calling getSubFolder('mySubFolderName') on a \TYPO3\CMS\Core\Resource\Folder object, and if this sub folder does not exist, the specific \TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException is now raised instead of the global \InvalidArgumentException.

Impact

The change may have impact on extensions that directly or indirectly call Folder->getSubFolder() and expect a \InvalidArgumentException to be thrown.

Affected installations

FolderDoesNotExistException does not extend \InvalidArgumentException. Code that currently expects a \InvalidArgumentException to be thrown, needs adaption.

Migration

The change is breaking for code that takes an "optimistic" approach like "get the sub folder object, and if this throws, create one". Example:

try {
    $mySubFolder = $myFolder->getSubFolder('mySubFolder');
} catch (\InvalidArgumentException) {
    $mySubFolder = $myFolder->createFolder('mySubFolder');
}
Copied!

This should be changed to catch a FolderDoesNotExistException instead:

Extensions that need to stay compatible with both TYPO3 v13 and v14 should catch both exceptions and should later avoid catching \InvalidArgumentException when v13 compatibility is dropped:

Breaking: #106041 - TypoScript Extbase toggle config.tx_extbase.persistence.updateReferenceIndex removed

See forge#106041

Description

Extbase had the TypoScript toggle config.tx_extbase.persistence.updateReferenceIndex whether the reference index shall be updated when records are persisted.

It becomes more and more important the reference index is always up to date since an increasing number of core code relies on current reference index data. Using the reference index at key places can improve read and rendering performance significantly.

This toggle has been removed, reference index updating is now always enabled.

Impact

The change may increase database load slightly which may become noticeable when Extbase changes many records at once.

Affected installations

Instances with extensions writing many records using the Extbase persistence layer may be affected.

Migration

TypoScript toggle config.tx_extbase.persistence.updateReferenceIndex should be removed from any extensions codebase, it is ignored by Extbase.

Breaking: #106056 - Add setRequest and getRequest to extbase ValidatorInterface

See forge#106056

Description

Custom validators implementing \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface must now implement the methods setRequest() and getRequest().

Impact

Missing implementation of the methods setRequest() and getRequest() will now result in a PHP fatal error.

Affected installations

TYPO3 websites implementing \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface .

Migration

The methods setRequest() and getRequest() must be implemented in affected validators.

If no direct implementation of \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface is required, it is recommended to extend \TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator , where both methods already are implemented.

Breaking: #106118 - Property DataHandler->storeLogMessages removed

See forge#106118

Description

Public property TYPO3\CMS\Core\DataHandling\DataHandler->storeLogMessages has been removed without substitution. It should not be used by extensions anymore.

Impact

Setting or reading the property in extension raises a PHP warning level error.

Affected installations

Instances with extensions dealing with the property. This should be a very rare use case, no TER extension was affected when looking this up. The extension scanner is configured to find usages as weak match.

Migration

The property has been removed, setting or reading it from DataHandler instances should be removed. The DataHandler->log() method now always writes given $details to table sys_log.

Breaking: #106412 - TCA interface settings for list view removed

See forge#106412

Description

Each TCA definition has had an optional entry named interface to define relevant information for displaying the TCA records.

The TCA options ['interface']['maxSingleDBListItems'] and ['interface']['maxDBListItems'] are removed and not evaluated anymore.

These options have been used for defining the amount of table rows to show within TYPO3's Web>List module.

Impact

Setting these values in custom extensions will have no effect anymore, as they are automatically removed during build-time.

Affected installations

TYPO3 installations with custom TCA settings from third-party-extensions.

Migration

Overriding visual settings can still be done on a per - User TSconfig or per - Page TSconfig level, which is much more flexible anyways, as it allows for rendering different amount of values per site / pagetree or usergroup.

The TCA option ['interface']['maxSingleDBListItems'] is removed in favor of mod.web_list.itemsLimitSingleTable.

The TCA option php:['interface']['maxDBListItems']is removed in favor of mod.web_list.itemsLimitPerTable.

Breaking: #106503 - Removal of field from sys_file_metadata

See forge#106503

Description

The following database fields and TCA definitions have been removed from the DB table sys_file_metadata without substitution:

  • visible
  • fe_groups

These fields are added to the DB table when the system extension filemetadata is installed.

Both fields seemed like being fields known from any other table which restrict access to the content but they have never been configured so, which is also depending on the use-case and not possible from TYPO3 Core usages.

Additionally, the field status has been moved from the tab Access to Metadata to avoid the notion that this field has any restrictive behaviour.

Impact

The fields visible and fe_groups of the database table sys_file_metadata are not used anymore. After a Database Compare in the Install Tool, the columns are removed and its content lost. When accessing the DB fields via PHP or TypoScript, a PHP warning might exist.

Affected installations

Any TYPO3 installation using the mentioned fields by having EXT:filemetadata installed.

Migration

No migration is available.

If the fields are in use, it is recommended to re-add the TCA definitions in a custom extension.

Feature: #92760 - Configurable timezone for DateViewHelper

See forge#92760

Description

A new option timezone has been added to the DateViewHelper to render a date with the provided time zone.

<f:format.date format="d.m.Y g:i a" date="1640995200" /><br>
<f:format.date format="d.m.Y g:i a" date="1640995200" timezone="America/Phoenix" /><br>
<f:format.date format="d.m.Y g:i a" date="1640995200" timezone="Indian/Mayotte" />
Copied!

will render:

01.01.2022 12:00 am
31.12.2021 5:00 pm
01.01.2022 3:00 am
Copied!

Impact

Using the new timezone option, it's now possible to set a specific time zone for the the DateViewHelper.

Feature: #99409 - New PSR-14 BeforeLiveSearchFormIsBuiltEvent

See forge#99409

Description

A new PSR-14 event \TYPO3\CMS\Backend\Search\Event\BeforeLiveSearchFormIsBuiltEvent has been added.

To modify the live search form data, the following methods are available:

  • addHint(): Add a single hint.
  • addHints(): Add one or multiple hints.
  • setHints(): Allows to set hints. Can be used to reset or overwrite current hints.
  • getHints(): Returns all hints.
  • getRequest(): Returns the current PSR-7 Request.
  • getSearchDemand(): Returns the SearchDemand, used by the live search.
  • setSearchDemand(): Allows to set a custom SearchDemand object.
  • :php:`getAdditionalViewData(): Returns the additional view data set to be used in the template.
  • :php:`setAdditionalViewData(): Set the additional view data to be used in the template.

Example

The corresponding event listener class:

<?php

namespace MyVendor\MyPackage\Backend\Search\EventListener;

use TYPO3\CMS\Backend\Search\Event\BeforeLiveSearchFormIsBuiltEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

final class BeforeLiveSearchFormIsBuiltEventListener
{
    #[AsEventListener('my-package/backend/search/modify-live-search-form-data')]
    public function __invoke(BeforeLiveSearchFormIsBuiltEvent $event): void
    {
        $event->addHints(...[
            'LLL:EXT:my-package/Resources/Private/Language/locallang.xlf:identifier',
        ]);
    }
}
Copied!

Impact

It's now possible to modify the form data for the backend live search, using the new PSR-14 event BeforeLiveSearchFormIsBuiltEvent.

Feature: #99911 - New TCA type "country"

See forge#99911

Description

A new TCA field type called country has been added to TYPO3 Core. Its main purpose is to use the newly introduced Country API to provide a country selection in the backend and use the stored representation in Extbase or TypoScript output.

TCA Configuration

The new TCA type displays all filtered countries including the configurable name and the corresponding flag.

Configuration/TCA/tx_myextension_mymodel.php
'country' => [
    'label' => 'Country',
    'config' => [
        'type' => 'country',
        // available options: name, localizedName, officialName, localizedOfficialName, iso2, iso3
        'labelField' => 'localizedName',
        // countries which are listed before all others
        'prioritizedCountries' => ['AT', 'CH'],
        // sort by the label
        'sortItems' => [
            'label' => 'asc'
        ],
        'filter' => [
            // restrict to the given country ISO2 or ISO3 codes
            'onlyCountries' => ['DE', 'AT', 'CH', 'FR', 'IT', 'HU', 'US', 'GR', 'ES'],
            // exclude by the given country ISO2 or ISO3 codes
            'excludeCountries' => ['DE', 'ES'],
        ],
        'default' => 'HU',
        // When required=false, an empty selection ('') is possible
        'required' => false,
    ],
],
Copied!

Note that extra items / countries should be added via the new PSR-14 event BeforeCountriesEvaluatedEvent.

Flexform Configuration

Similar keys work for FlexForms:

Configuration/FlexForms/example.xml
<settings.country>
    <label>My Label</label>
    <config>
        <type>country</type>
        <labelField>officialName</labelField>
        <prioritizedCountries>
            <numIndex index="0">AT</numIndex>
            <numIndex index="1">CH</numIndex>
        </prioritizedCountries>
        <filter>
            <onlyCountries>
                <numIndex index="0">DE</numIndex>
                <numIndex index="1">AT</numIndex>
                <numIndex index="2">CH</numIndex>
                <numIndex index="1">FR</numIndex>
                <numIndex index="3">IT</numIndex>
                <numIndex index="4">HU</numIndex>
                <numIndex index="5">US</numIndex>
                <numIndex index="6">GR</numIndex>
                <numIndex index="7">ES</numIndex>
            </onlyCountries>
            <excludeCountries>
                <numIndex index="0">DE</numIndex>
                <numIndex index="1">ES</numIndex>
            </excludeCountries>
        </filter>
        <sortItems>
            <label>asc</label>
        </sortItems>
        <default>HU</default>
        <required>1</required>
    </config>
</settings.country>
Copied!

Available config keys

The TCA type country features the following column configuration:

  • filter (array): onlyCountries (array), excludeCountries (array) - filter/reduce specific countries
  • prioritizedCountries (array) - items put first in the list
  • default (string) - default value
  • labelField (string) - display label (one of localizedName, name, iso2, iso3, officialName, localizedOfficialName)
  • sortItems (string) - sort order (asc, desc)
  • required (bool) - whether an empty selection can be made or not

Extbase usage

When using Extbase Controllers to fetch Domain Models containing properties declared with the Country type, these models can be used with their usual getters, and passed along to Fluid templates as usual.

Extbase Domain Model example
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Core\Country\Country;

class SomeDomainModel extends AbstractEntity
{
    protected ?Country $country = null;

    public function setCountry(?Country $country): void
    {
        $this->country = $country;
    }

    public function getCountry(): ?Country
    {
        return $this->country;
    }
}
Copied!
Extbase Controller usage
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Core\Country\Country;

class ItemController extends ActionController {
    // ...

    public function __construct(
        private readonly CountryProvider $countryProvider,
    ) {}

    public function singleAction(SomeDomainModel $model): ResponseInterface
    {
        // Do something in PHP, using the Country API
        if ($model->getCountry()->getAlpha2IsoCode() == 'DE') {
            $this->loadGermanLanguage();
        }
        $this->view->assign('model', $model);

        // You can access the `CountryProvider` API for additional country-related
        // operations, too (ideally use Dependency Injection for this):
        $this->view->assign('countries', $this->countryProvider->getAll());

        return $this->htmlResponse();
    }
}
Copied!
Fluid Template example
Country: {model.country.flag}
 - <span title="{f:translate(key: model.country.localizedOfficialNameLabel)}">
     {model.country.alpha2IsoCode}
   </span>
Copied!

You can use any of the getXXX() methods available from the Country API via the Fluid {model.country.XXX} accessors.

If you use common Extbase CRUD (Create/Read/Update/Delete) with models using a Country type, you can utilize the existing ViewHelper f:form.countrySelect within your <f:form> logic.

Please keep in mind that Extbase by default has no coupling (in terms of validation) to definitions made in the TCA for the properties, as with other types like file uploads or select items.

That means, if you restrict the allowed countries via filter.onlyCountries on the backend (TCA) side, you also need to enforce this in the frontend.

It is recommended to use Extbase Validators for this task. If you want to share frontend-based validation and TCA-based validation non-redundantly, you could use data objects (DO/DTO) or ENUMs for returning the list of allowed countries:

EXT:my_extension/Classes/Domain/Validator/CountryValidator.php
namespace MyExtension\Domain\Validator;

use TYPO3\CMS\Extbase\Validation\Error;
use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;

class ItemValidator extends AbstractValidator
{
    /**
     * @param MyModel $value
     */
    protected function isValid(mixed $value): void
    {
        if ($value->getCountry() === null) {
            $error = new Error('Valid country (alpha2) must be set.', 4815162343);
            $this->result->forProperty('country')->addError($error);
        } else {
            $allowedCountries = ['DE', 'EN'];
            if (!in_array($value->getCountry()->getAlpha2IsoCode(), $allowedCountries)) {
                $error = new Error('Country ' . $value->getCountry()->getAlpha2IsoCode() . ' not allowed.', 4815162344);
                $this->result->forProperty('country')->addError($error);
            }
        }
    }
}
Copied!
EXT:my_extension/Classes/Controller/ItemController.php (excerpt)
namespace MyExtension\Controller;

use TYPO3\CMS\Extbase\Annotation\Validate;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use MyExtension\Domain\Model\Item;
use MyExtension\Domain\Validator\ItemValidator;

final class ItemController extends ActionController
{
    // Excerpt ...

    #[Validate([
        'param' => 'item',
        'validator' => CountryValidator::class,
    ])]
    public function createAction(Item $item): ResponseInterface
    {
        $this->itemRepository->add($item);
        return $this->htmlResponse();
    }

    // ...
}
Copied!

A fleshed-out example for this (along with Extbase CRUD implementation) can be found in EXT:tca_country_example Demo Extension.

Extbase / Fluid localization

The type Country does not point to a real Extbase model, and thus has no inherent localization or query-logic based on real records. It is just a pure PHP data object with some getters, and a magic __toString() method returning a LLL:... translation key for the name of the country ( Country->getLocalizedNameLabel()).

Here are some examples how to access them and provide localization:

EXT:my_extension/Resources/Private/Templates/Show.html
<f:comment>Will show something like "AT" or "DE"</f:comment>
Country ISO2:
    {item.country.alpha2IsoCode}

<f:comment>Will show something like "CHE"</f:comment>
Country ISO3:
    {item.country.alpha3IsoCode}

<f:comment>Will show something a flag (UTF-8 character)</f:comment>
Country flag:
    {item.country.flag}

<f:comment>Will show something like "LLL:EXT:core/Resources/Private/Language/Iso/countries.xlf:AT.name"</f:comment>
Country LLL label:
    {item.country}
Actual localized country:
    <f:translate key="{item.country}" />

<f:comment>Will show something like "LLL:EXT:core/Resources/Private/Language/Iso/countries.xlf:AT.official_name"</f:comment>
Country LLL label:
    {item.country.localizedOfficialNameLabel}
Actual localized official country name:
    <f:translate key="{item.country.localizedOfficialNameLabel}" />

<f:comment>Will show something like "Germany" (always english)</f:comment>
    {item.country.name}
Copied!

You can use the Extbase \TYPO3\CMS\Extbase\Utility\LocalizationUtility in PHP-scope (Controllers, Domain Model) to create a custom getter in your Domain Model to create a shorthand method:

EXT:my_extension/Domain/Model/Item.php
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3\CMS\Core\Country\Country;

class SomeDomainModel extends AbstractEntity
{
    protected ?Country $country = null;

    public function setCountry(?Country $country): void
    {
        $this->country = $country;
    }

    public function getCountry(): ?Country
    {
        return $this->country;
    }

    // Special getter to easily access `{item.localizedCountry}` in Fluid
    public function getLocalizedCountry(): string
    {
        return (string) LocalizationUtility::translate(
            (string) $this->getCountry()?->getLocalizedNameLabel()
        );
    }
}
Copied!

Extbase Repository access

As mentioned above, since Country has no database-record relations. The single-country relation always uses the 2-letter ISO alpha2 key (respectively custom country keys, when added via the PSR-14 event BeforeCountriesEvaluatedEvent). Thus, queries need to utilize them as string comparisons:

EXT:my_extension/Classes/Domain/Repository/ItemRepository.php
namespace MyExtension\Domain\Repository;

use TYPO3\CMS\Extbase\Persistence\Repository;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;

class ItemRepository extends Repository
{
    public function findByGermanMarkets(): QueryResultInterface {
        $query = $this->createQuery();
        $query->matching(
            $query->in('country', ['DE', 'AT', 'CH'])
        );
        return $query->execute();
    }
}
Copied!

The default Extbase repository magic method $repository->findBy(['country' => 'DE']) will work, too.

TypoScript rendering usage via record-transformation

Database records using 'country' type fields can be rendered with the TypoScript-based record-transformation rendering (data processor).

You can specify how a field containing a country is rendered in the output (using the name, the flag icon, specific ISO keys) with regular fluid logic then:

Step 1: TypoScript utilizing record-transformation, defining a Homepage.html Fluid template
page = PAGE
page {
  # Just an example basic template for your site. The important section starts with `dataProcessing`!
  100 = FLUIDTEMPLATE
  100 {
    templateName = Homepage
    templateRootPaths {
      0 = EXT:myextension/Resources/Private/Templates/
    }
    dataProcessing {
      10 = database-query
      10 {
        as = mainContent
        # This table holds for example a TCA type=country definition for a field "country"
        table = tx_myextension_domain_model_mycountries
        # An extra boolean field "show_on_home_page" would indicate whether these
        # records are fetched and displayed on the home page
        where = show_on_home_page=1
        # Depending on your table storage you may need to set a proper pidInList constraint.
        #pidInList = 4711
        dataProcessing {
          # Makes all records available as `{mainContent.[0..].myRecord}` in the
          # Fluid file EXT:myextension/Resources/Private/Templates/Homepage.html
          10 = record-transformation
          10 {
            as = myRecord
          }
        }
      }
    }
  }
}
Copied!
Step 2: Fluid template EXT:myextension/Resources/Private/Templates/Homepage.html
<f:if condition="{mainContent}">
  <f:for each="{mainContent}" as="element">
    <!-- given that your 'tx_myextension_domain_model_mycountries' has a TCA field called "storeCountry":
    Selected Country:
      <f:translate key="{element.myRecord.storeCountry.localizedOfficialNameLabel}" />
  </f:for>

  <!-- note that you can access any transformed record type object via 'element', also multiple country
       elements could be contained in 'element.myRecord'. -->
</f:if>
Copied!

Impact

It is now possible to use a dedicated TCA type for storing a relation to a country in a record.

Using the new TCA type, corresponding database columns are added automatically. Country-annotated properties of Extbase Domain Models can be evaluated in Extbase and via TypoScript.

Feature: #103740 - Language selection for backend module "Info - Pagetree Overview"

See forge#103740

Description

The backend module Web > Info > Pagetree Overview is enriched by a language selection.

This allows to switch the displayed Pagetree to the selected language, and adjust all labels, edit and view links accordingly.

The language selection dropdown is right next to the other filter possibilities (recursion depth, information type) and compliments the Web > Info > Localization Overview with a page/record-focussed view.

Impact

The Web > Info backend module is more useful on sites with multiple languages, to have a quick overview about information of the selected page and its subpages.

Feature: #105549 - Support qualified and unqualified ISO8601 dates in DataHandler

See forge#105549

Description

The DataHandler PHP API has been extended to support qualified and unqualified ISO8601 date formats in order to correctly process supplied timezone offsets, if supplied.

  • Qualified ISO8601: Includes an explicit timezone offset (e.g., 1999-12-11T10:09:00+01:00 or 1999-12-11T10:09:00Z)
  • Unqualified ISO8601: Omits timezone offsets, representing "LOCALTIME" (e.g., 1999-12-11T10:09:00)

TYPO3 DataHandler now accepts five different formats:

Format Examples
Unqualified ISO8601 (LOCALTIME) 'Y-m-d\\TH:i:s' 1999-11-11T11:11:11
Qualified ISO8601 'Y-m-d\\TH:i:sP'

1999-11-11T10:11:11Z

1999-11-11T11:11:11+01:00

DateTime objects \DateTimeInterface

new \DateTime('yesterday')

new \DateTimeImmutable()

SQL flavored dates (internal) 'Y-m-d H:i:s' 1999-11-11 11:11:11
Unix timestamps (internal) 'U' 942315071

The ISO8601 variants and \DateTimeInterface objects are intended to be used as API. The SQL flavored variant and unix timestamps are mainly targeted for copy and import operations of native datetime and unix timestamp database fields and are considered internal API.

Passing datetime data via DataHandler PHP API
$myDate = new \DateTime('yesterday');
$this->dataHandler->start([
    'tx_myextension_mytable' => [
        'NEW-1' => [
            'pid' => 2,
            // Format as LOCALTIME
            'mydatefield_1' => $myDate->format('Y-m-dTH:i:s'),
            // Format with timezone information
            // (offsets will be normalized to persistence timezone format,
            // UTC for integer fields, LOCALTIME for native DATETIME fields)
            'mydatefield_2' => $myDate->format('c'),
            // Pass \DateTimeInterface objects directly
            'mydatefield_3' => $myDate,
        ],
    ],
]);
Copied!

Impact

TYPO3 now provides accurate and consistent handling of ISO8601 dates, eliminating previous issues related to timezone interpretation and LOCALTIME representation.

Feature: #105624 - PSR-14 event after a Backend user password has been reset

See forge#105624

Description

A new PSR-14 event \TYPO3\CMS\Backend\Authentication\Event\PasswordHasBeenResetEvent has been introduced which is raised right after a Backend user reset their password and it has been hashed and persisted to the database.

The event contains the corresponding Backend user UID.

Example

The corresponding event listener class:

<?php

namespace Vendor\MyPackage\Backend\EventListener;

use TYPO3\CMS\Backend\Authentication\Event\PasswordHasBeenResetEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

final class PasswordHasBeenResetEventListener
{
    #[AsEventListener('my-package/backend/password-has-been-reset')]
    public function __invoke(PasswordHasBeenResetEvent $event): void
    {
        $userUid = $event->userUid;
        // Do something with the be_user UID
    }
}
Copied!

Impact

It's now possible to add custom business logic after a Backend user reset their password using the new PSR-14 event PasswordHasBeenResetEvent.

Feature: #105783 - Notify backend user on failed MFA verification attempts

See forge#105783

Description

TYPO3 now notifies backend users via email when a failed MFA (Multi-Factor Authentication) verification attempt occurs. The notification is sent only if an MFA provider is configured and the user has a valid email address in their profile.

Impact

TYPO3 backend users benefit from enhanced security awareness through immediate email notifications about failed MFA verification attempts. This is especially useful in scenarios where backend accounts with active MFA setup are targeted by unauthorized access attempts.

Feature: #105833 - Extended page tree filter functionality

See forge#105833

Description

The page tree is one of the central components in the TYPO3 backend, particularly for editors. However, in large installations, the page tree can quickly become overwhelming and difficult to navigate. To maintain a clear overview, the page tree can be filtered using basic terms, such as the page title or ID.

To enhance the filtering capabilities, the new PSR-14 \TYPO3\CMS\Backend\Tree\Repository\BeforePageTreeIsFilteredEvent has been introduced. This event allows developers to extend the filter's functionality and process the given search phrase in more advanced ways.

Using the Event, it is for example possible to evaluate a given URL or to add additional field matchings, like filter pages by their doktype or their configured backend layout.

The event provides the following member properties:

$searchParts:
The search parts to be used for filtering
$searchUids:
The uids to be used for filtering by a special search part, which
is added by Core always after listener evaluation
$searchPhrase
The complete search phrase, as entered by the user
$queryBuilder:
The current QueryBuilder
instance to provide context and to be used to create search parts

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration, adds additional conditions to the filter.

my_extension/Classes/EventListener/MyEventListener.php
use TYPO3\CMS\Backend\Tree\Repository\BeforePageTreeIsFilteredEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Database\Connection;

final class MyEventListener
{
    #[AsEventListener]
    public function removeFetchedPageContent(BeforePageTreeIsFilteredEvent $event): void
    {
        // Adds an additional UID to the filter
        $event->searchUids[] = 123;

        // Adds evaluation of doktypes to the filter
        if (preg_match('/doktype:([0-9]+)/i', $event->searchPhrase, $match)) {
            $doktype = $match[1];
            $event->searchParts = $event->searchParts->with(
                $event->queryBuilder->expr()->eq('doktype', $event->queryBuilder->createNamedParameter($doktype, Connection::PARAM_INT))
            );
        }
    }
}
Copied!

Impact

With the new PSR-14 event \BeforePageTreeIsFilteredEvent, custom functionality and advanced evaluations can now be added to enhance the page tree filter.

Feature: #106072 - Introduce regex based replacements for slugs

See forge#106072

Description

Adds a second replacement config array to provide regex based definitions. This way it's possible to define case-insensitive or wildcard replacements.

Impact

Slug fields have now a new regexReplacements configuration array inside generatorOptions.

'generatorOptions' => [
  'regexReplacements' => [
      '/foo/i' => 'bar', // case-insensitive replace of Foo, foo, FOO,... with "bar", ignoring casing
      '/\(.*\)/' => '',  // Remove string wrapped in parentheses
      '@\(.*\)@' => '',  // Remove string wrapped in parentheses with custom regex delimiter
   ],
],
Copied!

Feature: #106092 - Associative array keys for TCA valuePicker items

See forge#106092

Description

It is now possible to define associative array keys for the items configuration of TCA configuration valuePicker. The new keys are called: label and value.

This follows the change done already to the items configuratio of the TCA types select, radio and check. See forge#99739

Impact

It is now much easier and clearer to define the TCA items configuration with associative array keys. The struggle to remember which option is first, label or value, is now over. In addition, optional keys like icon and group can be omitted, for example, when one desires to set the description option.

Feature: #106232 - Provide SEO record title provider

See forge#106232

Description

The class \TYPO3\CMS\Seo\PageTitle\RecordTitleProvider is a new page title provider with the identifier recordTitle which is called before \TYPO3\CMS\Seo\PageTitle\SeoTitlePageTitleProvider with the TypoScript identifier seo.

This provider can be used by 3rd party extensions to set the page title.

my_extension/Classes/Controller/ItemController.php
use MyVendor\MyExtension\Domain\Model\Item;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Seo\PageTitle\RecordTitleProvider;

final class ItemController extends ActionController
{
    public function __construct(
        private readonly RecordTitleProvider $recordTitleProvider
    ) {
    }

    public function showAction(Item $item): ResponseInterface
    {
        $this->recordTitleProvider->setTitle($item->getTitle());
        $this->view->assign('item', $item);
        return $this->htmlResponse();
    }
}
Copied!

Impact

Ease the life of extension developers by providing a dedicated provider instead of forcing them to provide a provider in every extension.

Feature: #106363 - PSR-14 event for modifying URLs for redirects:integritycheck

See forge#106363

Description

A new PSR-14 event \AfterPageUrlsForSiteForRedirectIntegrityHaveBeenCollectedEvent is added which allows TYPO3 Extensions to register event listeners to modify the list of URLs that are being processed by the CLI command redirects:checkintegrity.

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration, adds the URLs found in a sites XML sitemap to the list of URLs.

my_extension/Classes/EventListener/MyEventListener.php
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Http\RequestFactory;
use TYPO3\CMS\Redirects\Event\AfterPageUrlsForSiteForRedirectIntegrityHaveBeenCollectedEvent;

final class MyEventListener
{
    public function __construct(
        private RequestFactory $requestFactory,
    ) {}

    #[AsEventListener]
    public function __invoke(AfterPageUrlsForSiteForRedirectIntegrityHaveBeenCollectedEvent $event): void
    {
        $pageUrls = $event->getPageUrls();

        $additionalOptions = [
            'headers' => ['Cache-Control' => 'no-cache'],
            'allow_redirects' => false,
        ];

        $site = $event->getSite();
        foreach ($site->getLanguages() as $siteLanguage) {
            $sitemapIndexUrl = rtrim((string)$siteLanguage->getBase(), '/') . '/sitemap.xml';
            $response = $this->requestFactory->request(
                $sitemapIndexUrl,
                'GET',
                $additionalOptions,
            );
            $sitemapIndex = simplexml_load_string($response->getBody()->getContents());
            foreach ($sitemapIndex as $sitemap) {
                $sitemapUrl = (string)$sitemap->loc;
                $response = $this->requestFactory->request(
                    $sitemapUrl,
                    'GET',
                    $additionalOptions,
                );
                $sitemap = simplexml_load_string($response->getBody()->getContents());
                foreach ($sitemap as $url) {
                    $pageUrls[] = (string)$url->loc;
                }
            }
        }

        $event->setPageUrls($pageUrls);
    }
}
Copied!

Feature: #106415 - Add stdWrap to config.htmlTag.attributes.[attr]

See forge#106415

Description

Each attribute within the TypoScript option config.htmlTag.attributes.[attr] now has all stdWrap possibilities available.

This option is used to control the attributes of the single <html> tag of a rendered page.

Impact

It is now possible to e.g. use a custom userFunc, override or getData via TypoScript:

config.htmlTag.attributes.my-attribute = 123
config.htmlTag.attributes.my-attribute.override = 456
Copied!
config.htmlTag.attributes.my-attribute = 123
config.htmlTag.attributes.my-attribute.userFunc = MyVendor\\MyExtension\\HtmlTagEnhancer->overrideMyAttribute
Copied!

Feature: #106510 - Added PSR-14 events to Extbase Backend::getObjectCountByQuery method

See forge#106510

Description

The class \TYPO3\CMS\Extbase\Persistence\Generic\Backend is the covering entity to retrieve data from the database within the Extbase persistence framework.

Already in 2013 the getObjectDataByQuery method got equipped with signals (later migrated to events) in order to modify data retrieval.

Especially when used in combination with Extbase's QueryResult there is also a second important method getObjectCountByQuery, which is often used in combination with Fluid.

Extensions, or any other code using the existing events for data retrieval, have not been able to consistently modify queries, such that results returned by QueryResult were consistent.

The getObjectCountByQuery method is now enhanced with events as well. This finally allows extensions to modify all parts of query usage within Extbase's generic Backend to achieve consistent results.

The new events are:

  • \TYPO3\CMS\Extbase\Event\Persistence\ModifyQueryBeforeFetchingObjectCountEvent may be used to modify the query before being passed on to the actual storage backend.
  • \TYPO3\CMS\Extbase\Event\Persistence\ModifyResultAfterFetchingObjectCountEvent may be used to adjust the result.

Typically, an extension will want to implement events pair-wise: ModifyQueryBeforeFetchingObjectCountEvent together with ModifyQueryBeforeFetchingObjectDataEvent, and ModifyResultAfterFetchingObjectCountEvent together with ModifyResultAfterFetchingObjectDataEvent

Deprecation: #106393 - Various methods in BackendUtility

See forge#106393

Description

Due to the use of the Schema API the following methods of \TYPO3\CMS\Backend\Utility\BackendUtility which are related to retrieving information from :php:$GLOBALS['TCA']` have been deprecated:

  • \TYPO3\CMS\Backend\Utility\BackendUtility::isWebMountRestrictionIgnored()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::resolveFileReferences()

Impact

Calling one of the following methods raises deprecation level log errors and will stop working in TYPO3 v15.0.

Affected installations

Instances using the mentioned methods directly.

Migration

isWebMountRestrictionIgnored

// Before
return BackendUtility::isWebMountRestrictionIgnored($table);

// After
// Retrieve in instance of tcaSchemaFactory with Dependency Injection of TYPO3\CMS\Core\Schema\TcaSchemaFactory
$schema = $this->tcaSchemaFactory->get('pages');
return $schema->hasCapability(TcaSchemaCapability::RestrictionWebMount);
Copied!

resolveFileReferences

No substitution is available. Please copy the method to your own codebase and adjust it to your needs.

Deprecation: #106527 - markFieldAsChanged() moved to FormEngine main module

See forge#106527

Description

The static method markFieldAsChanged() in the module @typo3/backend/form-engine-validation is used to modify the markup in the DOM to mark a field as changed. Technically, this is unrelated to validation at all, therefore the method has been moved to @typo3/backend/form-engine.

Impact

Calling markFieldAsChanged() from @typo3/backend/form-engine-validation will trigger a deprecation notice in the browser console.

Affected installations

All extensions using the deprecated code are affected.

Migration

If not already done, import the main FormEngine module and call markFieldAsChanged() from that.

Example:

- import FormEngineValidation from '@typo3/backend/form-engine-validation.js';
+ import FormEngine from '@typo3/backend/form-engine.js';

- FormEngineValidation.markFieldAsChanged(fieldReference);
+ FormEngine.markFieldAsChanged(fieldReference);
Copied!

Example compatibility layer:

import FormEngine from '@typo3/backend/form-engine.js';
import FormEngineValidation from '@typo3/backend/form-engine-validation.js';

if (typeof FormEngine.markFieldAsChanged === 'function') {
  FormEngine.markFieldAsChanged(fieldReference);
} else {
  FormEngineValidation.markFieldAsChanged(fieldReference);
}
Copied!

Deprecation: #106618 - GeneralUtility::resolveBackPath

See forge#106618

Description

The method GeneralUtility::resolveBackPath has been marked as deprecated and will be removed in TYPO3 v15.0.

It served as a possibility to remove any relative path segments such as ".." when referencing files or directories, and was especially important before TYPO3 v7, where every TYPO3 Backend had their own route and entrypoint PHP file, but nowadays has been a relict from the past.

Since TYPO3 v13, this method has been even more unrelated as the main entrypoint file for TYPO3 Backend is now the same as the frontend ("htdocs/index.php").

Impact

TYPO3 does not resolve the back path of a reference to a resource and does not "normalize" the path anymore when rendering or referencing the resource in the HTML output - neither in the frontend or backend. It will however continue to work.

Affected installations

TYPO3 installations with custom inclusions in TypoScript, or backend modules referencing files with relative paths, which is very uncommon in the current web era.

Migration

Referencing resources should now be done with the EXT prefix, or relative to the public web path of the TYPO3 installation.

Referencing JavaScript modules (ES6 modules) should be handled via import maps and the module names.

Important: #105244 - Updated default .htaccess template

See forge#105244

Description

When installing TYPO3 for the first time, a .htaccess file is added to the htdocs / public path, when running TYPO3 via Apache webserver.

Next to some TYPO3 optimizations, this file mainly contains rules (via the "mod_rewrite" Apache2 module) for pointing all URL requests to non-existent files within a TYPO3 project to the main index.php entry point file.

For new installations, this file has some changed configuration, which can be adapted to existing TYPO3 installations reflecting a default setup:

URL requests within /_assets/ and /fileadmin/ are not redirected, as they contain resources either managed by editors or TYPO3 itself. The directory /_assets/ is added now, as it has been in place since TYPO3 v12 for Composer-based installations.

The folder /uploads/ is officially not needed anymore since TYPO3 v11, and not maintained by TYPO3 anymore. This folder is now removed from the .htaccess configuration as well, so TYPO3 pages can officially have the URL path /uploads now.

It is recommended to change this in existing TYPO3 installations as well - also with other server configurations such as nginx or IIS - in case no custom usage of /_assets/ or "/uploads/ is in effect, like via a PSR-15 middleware, custom extensions, or custom routing.

In Apache-based setups, look for this line:

RewriteRule ^(?:fileadmin/|typo3conf/|typo3temp/|uploads/) - [L]
Copied!

and replace it with

RewriteRule ^(?:fileadmin/|typo3conf/|typo3temp/|_assets/) - [L]
Copied!

Important: #105310 - Create CHAR and BINARY as fixed-length columns

See forge#105310

Description

TYPO3 parses ext_tables.sql files into a Doctrine DBAL object schema to define a virtual database scheme, enriched with DefaultTcaSchema information for TCA-managed tables and fields.

Fixed and variable length variants have been parsed already in the past, but missed to flag the column as $fixed = true for the fixed-length database field types CHAR and BINARY. This resulted in the wrong creation of these columns as VARCHAR and VARBINARY, which is now corrected.

ext_tables.sql created as (before) created as (now)
CHAR(10) VARCHAR(10) CHAR(10)
VARCHAR(10) VARCHAR(10) VARCHAR(10)
BINARY(10) VARBINARY(10) BINARY(10)
VARBINARY(10) VARBINARY(10) VARBINARY(10)

Not all database systems (RDBMS) act the same way for fixed-length columns. Implementation differences need to be respected to ensure the same query/data behaviour across all supported database systems.

Fixed-length CHAR

Key Difference Between CHAR and VARCHAR

The main difference between CHAR and VARCHAR is how the database stores character data in a database. CHAR, which stands for character, is a fixed-length data type, meaning it always reserves a specific amount of storage space for each value, regardless of whether the actual data occupies that space entirely. For example, if a column is defined as CHAR(10) and the word apple is stored inside of it, it will still occupy 10 characters worth of space (not just 5). Unusued characters are padded with extra spaces.

On the other hand, VARCHAR, short for variable character, is a variable-length data type. It only uses as much storage space as needed to store the actual data without padding. So, storing the word apple in a VARCHAR(10) column will only occupy 5 characters worth of space, leaving the remaining table row space available for other data.

The main difference from PostgreSQL to MySQL/MariaDB/SQLite is: PostgreSQL also returns the filler-spaces for a value not having the column length (returning apple[space][space][space][space][space]).

On top of that, the filled-up spaces are also respected for query conditions, sorting or data calculations ( concat() for example). These two facts makes a huge difference and must be carefully taken into account when using CHAR field.

Rule of thumb for fixed-length CHAR columns

  • Only use with ensured fixed-length values (so that no padding occurs).
  • For 255 or more characters VARCHAR or TEXT must be used.

More hints for fixed-length CHAR columns

  • Ensure to write fixed-length values for CHAR (non-space characters), for example use hash algorithms which produce fixed-length hash identifier values.
  • Ensure to use query statements to trim OR rightPad the value within WHERE, HAVING or SELECT operations, when values are not guaranteed to contain fixed-length values.

    Helper \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder expressions can be used, for example \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder->trim() or \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder->rightPad() to.

  • Usage of CHAR must be avoided when using the column with the Extbase ORM, because fixed-value length cannot be ensured due to the lack of using trim/rightPad within the ORM generated queries. Only with ensured fixed-length values, it is usable with Extbase ORM.
  • Cover custom queries extensively with functional tests executed against all supported database platforms. Code within public extensions should ensure to test queries and their operations against all officially TYPO3-supported database platforms.

Example of difference in behaviour of fixed-length CHAR types

Example ext_tables.sql defining a fixed-length tt_content field
CREATE TABLE `tt_content` (
    `some_label` CHAR(10) DEFAULT '' NOT NULL,
);
Copied!

Now, add some data. One row which fits exactly to 10 characters, and one row that only uses 6 characters:

Adding two example rows
<?php

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getConnectionForTable('tt_content');
// adding a value with 10 chars
$queryBuilder->insert(
    'tt_content',
    [
        'some_label' => 'some-label',
    ],
    [
        'some_label' => Connection::PARAM_STR,
    ],
);
// adding a value with only 6 chars
$queryBuilder->insert(
    'tt_content',
    [
        'some_label' => 'label1',
    ],
    [
        'some_label' => Connection::PARAM_STR,
    ],
);
Copied!

Now see the difference in retrieving these records:

Get all records from table
<?php

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
    ->select('uid', 'some_label')
    ->from('tt_content')
    ->executeQuery()
    ->fetchAllAssociative();
Copied!

Depending on the used database platform, the retrieved rows would contain these strings:

Result rows MySQL, MariaDB or SQLite
<?php

$rows = [
    [
        'uid' => 1,
        'some_label' => 'some-label',
    ],
    [
        'uid' => 2,
        'some_label' => 'label1',
    ],
];
Copied!

but for PostgreSQL

Result rows with PostgreSQL
<?php

$rows = [
    [
        'uid' => 1,
        'some_label' => 'some-label',
    ],
    [
        'uid' => 2,
        // PostgreSQL applies the fixed length to the value directly,
        // filling it up with spaces
        'some_label' => 'label1    ',
    ],
];
Copied!

or as a diff to make this even more visible:

Result rows difference between database platforms (commented)
 <?php

 $rows = [
     [
         'uid' => 1,
         'some_label' => 'some-label',
     ],
     [
         'uid' => 2,
-        'some_label' => 'label1',      // MySQL, MariaDB, SQLite
+        'some_label' => 'label1    ',  // PostgreSQL
     ],
 ];
Copied!

To raise the awareness for problems on this topic, using the trimmed value inside a WHERE condition will match the record, but the returned value will be different from the value used in the condition:

Retrieve with trimmed value
<?php

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
    ->select('uid', 'some_label')
    ->from('tt_content')
    ->where(
        $queryBuilder->eq(
            'some_label',
            $queryBuilder->createNamedParameter('label1'), // trimmed value!
        ),
    )
    ->executeQuery()
    ->fetchAllAssociative();

// $rows contains the record for
// PostgreSQL: $rows = [['uid' => 2, 'some_label' => 'label1    ']];
// Others....: $rows = [['uid' => 2, 'some_label' => 'label1']];
Copied!
Retrieve with enforced trimmed value.
<?php

use Doctrine\DBAL\Platforms\TrimMode;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
    ->select('uid')
    ->addSelectLiteral(
        $queryBuilder->expr()->as(
            $queryBuilder->expr()->trim(
                'fixed_title',
                TrimMode::TRAILING,
                ' '
            ),
            'fixed_title',
        ),
    )
    ->from('tt_content')
    ->where(
        $queryBuilder->eq(
            'some_label',
            $queryBuilder->createNamedParameter('label1'),
        ),
    )
    ->executeQuery()
    ->fetchAllAssociative();

// $rows contains the record for
// PostgreSQL: $rows = [['uid' => 2, 'some_label' => 'label1']];
// Others....: $rows = [['uid' => 2, 'some_label' => 'label1']];
// and ensures the same content across all supported database systems.
Copied!

On PostgreSQL, performing a query for a space-padded value will not actually return the expected row:

Retrieve with space-padded value for PostgreSQL does not retrieve the record
<?php

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

// PostgreSQL specific query!

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
    ->select('uid', 'some_label')
    ->from('tt_content')
    ->where(
        $queryBuilder->eq(
            'some_label',
            $queryBuilder->createNamedParameter('label1    '), // untrimmed value!
        ),
    )
    ->executeQuery()
    ->fetchAllAssociative();

// $rows === []
Copied!

Additional ExpressionBuilder methods can be used to ensure same behaviour on all platforms:

  • \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::trim()
  • \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::rightPad()

Recommendation

CHAR and BINARY fields can be used (for storage or performance adjustments), but only when composed data and queries take care of database-system differences.

Otherwise, the "safe bet" is to consistently utilize VARCHAR and VARBINARY columns types.

Important: #105538 - list_type and sub types

See forge#105538

Description

Due to the removal of the plugin content element ( list) and the corresponding plugin subtype field list_type the fifth parameter $pluginType of ExtensionUtility::configurePlugin() is now unused and can be omitted. It is only kept for backwards compatibility. However, be aware that passing any value other than CType will trigger a \InvalidArgumentException.

Please also note that due to the removal of the list_type field in tt_content, passing list_type as second parameter $field to ExtensionManagementUtility::addTcaSelectItemGroup() will now - as for any other non-existent field - trigger a \RuntimeException.

Important: #106192 - Add 'center' and 'font' to YAML processing removeTags

See forge#106192

Description

The HTML tags <font> and <center> are officially deprecated for some time: see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/font and https://developer.mozilla.org/en-US/docs/Web/HTML/Element/center.

The default YAML processing configuration file EXT:rte_ckeditor/Configuration/RTE/Processing.yaml has been changed to remove these HTML tags <font> and <center> by default when saving a RTE field content to the database.

This new default is adjusted with the option processing.HTMLparser_db.removeTags, which now also lists these two tags.

A stored input like <p><font face="Arial">My text</font></p> will - when saved - be changed to <p>My text</p>.

Affected installations

All installations having <font> and :html<center> stored in their database fields, and where no custom RTE YAML configuration is in place that allows these tags.

Please note that due issue forge#104839, this removeTags option was never properly applied previously, so the chances are that an installation never had output for font and center properly working anyways.

Also take note that the CKEditor by default uses <span style="..."> tags to apply font formatting when using the Full preset.

Thus, real-life impact should be low, but for legacy installations you may want to convert existing data to replace font/html tags with their appropriate modern counterparts.

Migration

Either accept the removal of these tags, and use specific HTML tags like <span> and <div> to apply formatting.

Or adapt the RTE Processing via TypoScript/YAML configuration to not have center and font to the processing.HTMLparser_db.removeTags list.

If the tags center and font have been configured via the editor.conf.style.definitions YAML option (not set by default), CKEditor would allow to use these tags, but they will now be removed both when saving, or when being rendered in the frontend. So these style definitions should be removed and/or adapted to <span style="..."> configurations.

Important: #106532 - Changed database storage format for Scheduler Tasks

See forge#106532

Description

TYPO3's system extension scheduler has stored its tasks to be executed with PHP-serialized storage format in the database since its inception.

This has led to many problems, e.g. when changing a class property to be fully typed, or when a class name has changed to use PHP 5 namespaces back then, or when renaming a class or a class property.

This has now changed, where as the task object now stores the "tasktype" (typically the class name) and its options in a "parameters" as JSON-encoded value as well as the execution details (DB field execution_details) in separate fields of the database table tx_scheduler_task. This way, the task object can be re-constituted with a properly defined API, avoiding pains in the future.

All existing tasks are compatible with the new internal format. An upgrade wizard ensures that the previously serialized objects are now transferred into the new format. If this wizard does not disappear after being executed, it means there are tasks that failed to be migrated and may need manual inspection or re-creation. Inspect all tasks of tx_scheduler_task where the "tasktype" column is empty. The old serialized data format is somewhat human-readable (or can be inspected with PHP deserializers), so re-creating a task with its former configuration options should be possible.

Please note that this upgrade step needs to be performed in the context of TYPO3 v14; performing the wizard in future TYPO3 versions may not succeed due to changes in the Task objects.

Important: #106649 - Default Language Binding in page module

See forge#106649

Description

The Page module now always uses default language binding ( mod.web_layout.defLangBinding) when displaying localized content in the language comparion mode.

Default language binding makes editing translations easier: Editors can see what they’re translating next to the default language. It additionally prevents confusion when localizations are incomplete. Editors directly see which content is not translated yet. This improves UX in the backend for multilingual sites, which was also a result of recent JTBD interviews.

Impact

Editors will now always see the content elements next to each other within the page module, when in language comparison mode.

Migration

Because default language binding is now always enabled, the previous Page TSconfig setting mod.web_layout.defLangBinding is not evaluated and can therefore be removed

Important: #106656 - Allow DEFAULT NULL for varchar fields

See forge#106656

Description

In TCA, if an input field is configured to be nullable via 'nullable' => true, the database migration now respects this and creates new or updates existing fields with DEFAULT NULL.

In Extbase, this may cause issues if properties and their accessor methods are not properly declared to be nullable, therefore this change is introduced to TYPO3 v14 only.

Example:

Example properly implementing a nullable property
<?php

declare(strict_types=1);

namespace Vendor\myExtension\Domain\Model;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class MyExtbaseEntity extends AbstractEntity
{
    protected ?string $title;

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(?string $title): void
    {
        $this->title = $title;
    }
}
Copied!

As stated above, this automatic detection is not provided in TYPO3 versions older than 14.0. Using DEFAULT NULL can be enforced via an extension's ext_tables.sql instead:

CREATE TABLE tx_myextension_table (
    title varchar(255) DEFAULT NULL
);
Copied!

14.x Changes by type

This lists all changes to the TYPO3 Core of minor versions grouped by their type.

Table of contents

Breaking Changes

Features

Deprecations

Important notes

ChangeLog v13

Every change to the TYPO3 Core which might affect your site is documented here.

Also available

13.4.x Changes

Table of contents

Breaking Changes

None since TYPO3 v13.4.0 LTS release.

Features

Deprecation

Important

Feature: #105638 - Modify fetched page content

See forge#105638

Description

With forge#103894 the new data processor PageContentFetchingProcessor has been introduced, to allow fetching page content based on the current page layout, taking the configured SlideMode into account.

Fetching content has previously mostly been done via the Content content object. A common example looked like this:

page.20 = CONTENT
page.20 {
    table = tt_content
    select {
        orderBy = sorting
        where = colPos=0
    }
}
Copied!

As mentioned in the linked changelog, using the page-content data processor, this can be simplified to:

page.20 = page-content
Copied!

This however reduces the possibility to modify the select configuration (SQL statement), used to define which content should be fetched, as this is automatically handled by the data processor. However, there might be some use cases in which the result needs to be adjusted, e.g. to hide specific page content, like it's done by EXT:content_blocks for child elements. For such use cases, the new PSR-14 AfterContentHasBeenFetchedEvent has been introduced, which allows to manipulate the list of fetched page content.

The following member properties of the event object are provided:

  • $groupedContent: The fetched page content, grouped by their column - as defined in the page layout
  • $request: The current request, which can be used to e.g. access the page layout in question

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration, removes some of the fetched page content elements based on specific field values.

my_extension/Classes/EventListener/MyEventListener.php
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Frontend\Event\AfterContentHasBeenFetchedEvent;

final class MyEventListener
{
    #[AsEventListener]
    public function removeFetchedPageContent(AfterContentHasBeenFetchedEvent $event): void
    {
        foreach ($event->groupedContent as $columnIdentifier => $column) {
            foreach ($column['records'] ?? [] as $key => $record) {
                if ($record->has('parent_field_name') && (int)($record->get('parent_field_name') ?? 0) > 0) {
                    unset($event->groupedContent[$columnIdentifier]['records'][$key]);
                }
            }
        }
    }
}
Copied!

Impact

Using the new PSR-14 AfterContentHasBeenFetchedEvent, it's possible to manipulate the page content, which has been fetched by the PageContentFetchingProcessor, based on the page layout and corresponding columns configuration.

Important: #92187 - Evaluation of incoming HTTP Header X-Forwarded-Proto

See forge#92187

Description

When running TYPO3 behind a reverse proxy, the site owner needs to set two TYPO3 settings.

$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue'] = 'first'; $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] = '{ip-of-the-reverse-proxy}';

At this point it is not known if the request between the client (the actual web browser for example) and the reverse proxy was made via HTTP or HTTPS, mainly because TYPO3 only evaluated the information from the reverse proxy to TYPO3 - which was typically faked on the TYPO3's webserver by setting "HTTPS=on" (e.g. via .htaccess file). In a typical setup, the communication between the reverse proxy and TYPO3's webserver is done via HTTP and irrelevant for TYPO3.

When the site owner knows that the reverse proxy acts as a SSL termination point and only communicates via https to the client, the $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxySSL'] option can be set, to identify all reverse proxy IPs that ensure a secure connection between client and reverse proxy.

In case, it is not known, and reverseProxySSL is not in use, but reverseProxyIP is in use, the incoming HTTP header X-Forwarded-Proto is now evaluated to determine if the request was made, if the header is sent.

If it is NOT sent, TYPO3 will assume to detect a secure connection between SSL information as before via various other HTTP Headers or server configuration settintgs.

Important: #103140 - Allow to configure rate limiters in Message consumer (Symfony Messenger)

See forge#103140

Description

This change introduces missing configuration options for Symfony Messenger-based rate limiters.

A rate limiter controls how frequently a specific event (e.g., HTTP request or login attempt) is allowed to occur. It acts as a safeguard to prevent services from being overwhelmed — either accidentally or intentionally — thus helping to maintain their availability.

Rate limiters are also useful for controlling internal or outbound processes, such as limiting the simultaneous processing of messages.

More information about the rate limiter is available in the Symfony Rate Limiter component documentation.

Usage

Configure a rate limiter per queue

Rate limiters can be defined in your service configuration EXT:yourext/Configuration/Services.yaml. The name specified in the settings is resolved to a service tagged with messenger.rate_limiter and the corresponding identifier.

Example Configuration:

EXT:yourext/Configuration/Services.yaml
messenger.rate_limiter.demo:
  class: 'Symfony\Component\RateLimiter\RateLimiterFactory'
  arguments:
    $config:
      id: 'demo'
      policy: 'sliding_window'
      limit: '100'
      interval: '60 seconds'
    $storage: '@Symfony\Component\RateLimiter\Storage\InMemoryStorage'
  tags:
    - name: 'messenger.rate_limiter'
      identifier: 'demo'

messenger.rate_limiter.default:
  class: 'Symfony\Component\RateLimiter\RateLimiterFactory'
  arguments:
    $config:
      id: 'default'
      policy: 'sliding_window'
      limit: '100'
      interval: '60 seconds'
    $storage: '@Symfony\Component\RateLimiter\Storage\InMemoryStorage'
  tags:
    - name: 'messenger.rate_limiter'
      identifier: 'default'
Copied!

Important: #104477 - Remove hyphen prefix from sys_log's data field entry

See forge#104477

Description

The \TYPO3\CMS\Core\Log\Writer\DatabaseWriter is used to write logs into the database table sys_log. Additional log information data is persisted in the field data and has been prefixed with a - until now. As this makes it harder to parse the data, which is JSON-encoded anyway, the prefix has been removed.

Beware that existing log entries are not migrated automatically. This leads to a mixed structure in the database table until old records are cleaned. (TYPO3 itself does not interpret the content of the field.)

Important: #105310 - Create CHAR and BINARY as fixed-length columns

See forge#105310

Description

TYPO3 parses ext_tables.sql files into a Doctrine DBAL object schema to define a virtual database scheme, enriched with DefaultTcaSchema information for TCA-managed tables and fields.

Fixed and variable length variants have been parsed already in the past, but missed to flag the column as $fixed = true for the fixed-length database field types CHAR and BINARY. This resulted in the wrong creation of these columns as VARCHAR and VARBINARY, which is now corrected.

ext_tables.sql created as (before) created as (now)
CHAR(10) VARCHAR(10) CHAR(10)
VARCHAR(10) VARCHAR(10) VARCHAR(10)
BINARY(10) VARBINARY(10) BINARY(10)
VARBINARY(10) VARBINARY(10) VARBINARY(10)

Not all database systems (RDBMS) act the same way for fixed-length columns. Implementation differences need to be respected to ensure the same query/data behaviour across all supported database systems.

Fixed-length CHAR

Key Difference Between CHAR and VARCHAR

The main difference between CHAR and VARCHAR is how the database stores character data in a database. CHAR, which stands for character, is a fixed-length data type, meaning it always reserves a specific amount of storage space for each value, regardless of whether the actual data occupies that space entirely. For example, if a column is defined as CHAR(10) and the word apple is stored inside of it, it will still occupy 10 characters worth of space (not just 5). Unusued characters are padded with extra spaces.

On the other hand, VARCHAR, short for variable character, is a variable-length data type. It only uses as much storage space as needed to store the actual data without padding. So, storing the word apple in a VARCHAR(10) column will only occupy 5 characters worth of space, leaving the remaining table row space available for other data.

The main difference from PostgreSQL to MySQL/MariaDB/SQLite is: PostgreSQL also returns the filler-spaces for a value not having the column length (returning apple[space][space][space][space][space]).

On top of that, the filled-up spaces are also respected for query conditions, sorting or data calculations ( concat() for example). These two facts makes a huge difference and must be carefully taken into account when using CHAR field.

Rule of thumb for fixed-length CHAR columns

  • Only use with ensured fixed-length values (so that no padding occurs).
  • For 255 or more characters VARCHAR or TEXT must be used.

More hints for fixed-length CHAR columns

  • Ensure to write fixed-length values for CHAR (non-space characters), for example use hash algorithms which produce fixed-length hash identifier values.
  • Ensure to use query statements to trim OR rightPad the value within WHERE, HAVING or SELECT operations, when values are not guaranteed to contain fixed-length values.

    Helper \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder expressions can be used, for example \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder->trim() or \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder->rightPad() to.

  • Usage of CHAR must be avoided when using the column with the Extbase ORM, because fixed-value length cannot be ensured due to the lack of using trim/rightPad within the ORM generated queries. Only with ensured fixed-length values, it is usable with Extbase ORM.
  • Cover custom queries extensively with functional tests executed against all supported database platforms. Code within public extensions should ensure to test queries and their operations against all officially TYPO3-supported database platforms.

Example of difference in behaviour of fixed-length CHAR types

Example ext_tables.sql defining a fixed-length tt_content field
CREATE TABLE `tt_content` (
    `some_label` CHAR(10) DEFAULT '' NOT NULL,
);
Copied!

Now, add some data. One row which fits exactly to 10 characters, and one row that only uses 6 characters:

Adding two example rows
<?php

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getConnectionForTable('tt_content');
// adding a value with 10 chars
$queryBuilder->insert(
    'tt_content',
    [
        'some_label' => 'some-label',
    ],
    [
        'some_label' => Connection::PARAM_STR,
    ],
);
// adding a value with only 6 chars
$queryBuilder->insert(
    'tt_content',
    [
        'some_label' => 'label1',
    ],
    [
        'some_label' => Connection::PARAM_STR,
    ],
);
Copied!

Now see the difference in retrieving these records:

Get all records from table
<?php

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
    ->select('uid', 'some_label')
    ->from('tt_content')
    ->executeQuery()
    ->fetchAllAssociative();
Copied!

Depending on the used database platform, the retrieved rows would contain these strings:

Result rows MySQL, MariaDB or SQLite
<?php

$rows = [
    [
        'uid' => 1,
        'some_label' => 'some-label',
    ],
    [
        'uid' => 2,
        'some_label' => 'label1',
    ],
];
Copied!

but for PostgreSQL

Result rows with PostgreSQL
<?php

$rows = [
    [
        'uid' => 1,
        'some_label' => 'some-label',
    ],
    [
        'uid' => 2,
        // PostgreSQL applies the fixed length to the value directly,
        // filling it up with spaces
        'some_label' => 'label1    ',
    ],
];
Copied!

or as a diff to make this even more visible:

Result rows difference between database platforms (commented)
 <?php

 $rows = [
     [
         'uid' => 1,
         'some_label' => 'some-label',
     ],
     [
         'uid' => 2,
-        'some_label' => 'label1',      // MySQL, MariaDB, SQLite
+        'some_label' => 'label1    ',  // PostgreSQL
     ],
 ];
Copied!

To raise the awareness for problems on this topic, using the trimmed value inside a WHERE condition will match the record, but the returned value will be different from the value used in the condition:

Retrieve with trimmed value
<?php

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
    ->select('uid', 'some_label')
    ->from('tt_content')
    ->where(
        $queryBuilder->eq(
            'some_label',
            $queryBuilder->createNamedParameter('label1'), // trimmed value!
        ),
    )
    ->executeQuery()
    ->fetchAllAssociative();

// $rows contains the record for
// PostgreSQL: $rows = [['uid' => 2, 'some_label' => 'label1    ']];
// Others....: $rows = [['uid' => 2, 'some_label' => 'label1']];
Copied!
Retrieve with enforced trimmed value.
<?php

use Doctrine\DBAL\Platforms\TrimMode;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
    ->select('uid')
    ->addSelectLiteral(
        $queryBuilder->expr()->as(
            $queryBuilder->expr()->trim(
                'fixed_title',
                TrimMode::TRAILING,
                ' '
            ),
            'fixed_title',
        ),
    )
    ->from('tt_content')
    ->where(
        $queryBuilder->eq(
            'some_label',
            $queryBuilder->createNamedParameter('label1'),
        ),
    )
    ->executeQuery()
    ->fetchAllAssociative();

// $rows contains the record for
// PostgreSQL: $rows = [['uid' => 2, 'some_label' => 'label1']];
// Others....: $rows = [['uid' => 2, 'some_label' => 'label1']];
// and ensures the same content across all supported database systems.
Copied!

On PostgreSQL, performing a query for a space-padded value will not actually return the expected row:

Retrieve with space-padded value for PostgreSQL does not retrieve the record
<?php

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

// PostgreSQL specific query!

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
    ->select('uid', 'some_label')
    ->from('tt_content')
    ->where(
        $queryBuilder->eq(
            'some_label',
            $queryBuilder->createNamedParameter('label1    '), // untrimmed value!
        ),
    )
    ->executeQuery()
    ->fetchAllAssociative();

// $rows === []
Copied!

Additional ExpressionBuilder methods can be used to ensure same behaviour on all platforms:

  • \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::trim()
  • \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::rightPad()

Recommendation

CHAR and BINARY fields can be used (for storage or performance adjustments), but only when composed data and queries take care of database-system differences.

Otherwise, the "safe bet" is to consistently utilize VARCHAR and VARBINARY columns types.

Important: #105653 - Require a template filename in extbase module template rendering

See forge#105653

Description

With the introduction of the FluidAdapter in TYPO3 v13, the dependency between Fluid and Extbase has been decoupled. As part of this change, the behavior of the ModuleTemplate::renderResponse() and ModuleTemplate::render() methods has been adjusted.

The $templateFileName argument is now mandatory for the ModuleTemplate::renderResponse() and ModuleTemplate::render() methods. Previously, if this argument was not provided, the template was automatically resolved based on the controller and action names. Starting from TYPO3 13.4, calling these methods with an empty string or without a valid $templateFileName will result in an InvalidArgumentException.

Extensions using Extbase backend modules must explicitly provide the $templateFileName when calling these methods. Existing implementations relying on automatic template resolution need to be updated to prevent runtime errors.

Example:

Before:

$moduleTemplate->renderResponse();
Copied!

After:

$moduleTemplate->renderResponse('MyController/MyAction');
Copied!

Note, that it is already possible to explicitly provide the $templateFileName in TYPO3 12.4. It is therefore recommended to implement the new requirement for websites using TYPO3 12.4.

Important: #105703 - Premature end of script headers due to X-TYPO3-Cache-Tags

See forge#105703

Description

The X-TYPO3-Cache-Tags header is now split into multiple lines if it exceeds the maximum of 8000 characters. This change prevents premature end of script headers and ensures that the header is sent correctly, even if it contains a large number of cache tags.

Affected installations

This change affects all TYPO3 installations that have $GLOBALS['TYPO3_CONF_VARS']['FE']['debug'] enabled and misusing the X-TYPO3-Cache-Tags header for anything else then debugging. If you have a large number of cache tags, the header is now split into multiple lines to avoid exceeding the maximum header size limit imposed by some web servers. As this header is for debugging purposes only, this does not effect any production environments.

Important: #106401 - Treat 0 as a defined value for nullable datetime fields

See forge#106401

Description

For nullable integer-based datetime fields, the value 0 now explicitly represents the Unix epoch time (1970-01-01T00:00:00Z) instead of being interpreted as an empty value by FormEngine.

Only an explicit null database value will be considered an empty value.

The default database schema that is generated from TCA has been adapted to generate datetime columns with DEFAULT NULL instead of DEFAULT 0 if they have been configured to be nullable.

Given the following TCA definition:

'columns' => [
    'mydatefield' => [
        'config' => [
            'type' => 'datetime',
            'nullable' => true,
        ],
    ],
],
Copied!

The previously generated SQL statement will be changed from DEFAULT 0 to DEFAULT NULL:

Nullable datetime schema before this change
`mydatefield` bigint(20) DEFAULT 0
Copied!
Nullable datetime schema after this change
`mydatefield` bigint(20) DEFAULT NULL
Copied!

Fields that have not been explicitly configured to be nullable are unaffected and will default to 0 as before.

Important: #106494 - Adapt custom instances of AbstractFormFieldViewHelper to deal with persistenceManager->getIdentifierByObject() methods

See forge#106494

Description

When dealing with relations to multilingual Extbase entities, these relations should always store their reference to the "original" (sys_language_uid=0) entity, so that later on, language record overlays can be properly applied.

For this to work in areas like persistence, internally an "identifier" is established that references these multilingual objects like [defaultLanguageRecordUid]_[localizedRecordUid].

Internally, this identifier should be converted back to only contain/reference the defaultLanguageRecordUid.

A bug has been fixed with #106494 to deal with this inside the <f:form.select> ViewHelper, which utilized an <option value="11_42"> (defaultLanguageRecordUid=11, localizedRecordUid=42), and when using an <f:form> to edit existing records, the currently attached records would NOT get pre-selected.

When such objects with relations were persisted (in frontend management interfaces with Extbase), if the proper option had not been selected again, the relation would get lost.

Important: Adapt custom ViewHelpers extended from AbstractFormFieldViewHelper or using persistenceManager->getIdentifierByObject()

The bug has been fixed, but it is important that if third-party code created custom ViewHelpers based on \TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper , these may need adoption too.

Instead of using code like this:

Example ViewHelper code utilizing persistenceManager->getIdentifierByObject()
if ($this->persistenceManager->getIdentifierByObject($valueElement) !== null) {
    return $this->persistenceManager->getIdentifierByObject($valueElement);
}
Copied!

the code should be adopted to not rely on getIdentifierByObject() but instead:

Refactored ViewHelper code preferring an object's getUid() method instead
if ($this->persistenceManager->getIdentifierByObject($valueElement) !== null) {
    if ($valueElement instanceof DomainObjectInterface) {
        return $valueElement->getUid() ?? $this->persistenceManager->getIdentifierByObject($valueElement);
    }
    return $this->persistenceManager->getIdentifierByObject($valueElement);
}
Copied!

This code ensures that retrieving the relational object's UID is done with the overlaid record, and only falls back to the full identifier, if it's not set, or not an object implementing the Extbase DomainObjectInterface.

Also note that the abstract's method convertToPlainValue() has been fixed to no longer return a value of format [defaultLanguageRecordUid]_[localizedRecordUid] but instead always use the original record's ->getUid() return value (=defaultLanguageRecordUid).

If this method convertToPlainValue() is used in 3rd-party code, make sure this is the expected result, too.

Important: #106508 - Respect column CHARACTER SET and COLLATE in ext_tables.sql

See forge#106508

Description

TYPO3 now reads column based CHARACTER SET and COLLATION from extension ext_tables.sql files and applies them on column level. This allows CHARACTER SET and COLLATION column settings different than defaults defined on table or schema level. This is limited to MySQL and MariaDB DBMS.

For now, CHARACTER SET ascii COLLATE ascii_bin is used for sys_refindex.hash to reduce required space for the index using single bytes instead of multiple bytes per character.

The introduced database change is considerable non-breaking, because:

  • Not applying the database changes still keeps a fully working state.
  • Applying database schema change does not require data migrations.
  • Targets only MySQL and MariaDB.
ext_tables.sql example
CREATE TABLE some_table (

    col1    CHAR(10) DEFAULT ''             NOT NULL CHARACTER SET ascii COLLATE ascii_bin,
    col2    CHAR(10) CHARACTER SET ascii COLLATE ascii_bin DEFAULT '' NOT NULL,
    col3    VARCHAR(10) DEFAULT ''          NOT NULL CHARACTER SET ascii COLLATE ascii_bin,
    col4    VARCHAR(10) CHARACTER SET ascii COLLATE ascii_bin DEFAULT '' NOT NULL,
    col5    TEXT DEFAULT ''                 NOT NULL CHARACTER SET ascii COLLATE ascii_bin,
    col6    TEXT CHARACTER SET ascii COLLATE ascii_bin DEFAULT '' NOT NULL,
    col7    MEDIUMTEXT DEFAULT ''           NOT NULL CHARACTER SET ascii COLLATE ascii_bin,
    col8    MEDIUMTEXT CHARACTER SET ascii COLLATE ascii_bin DEFAULT '' NOT NULL,
    col9    LONGTEXT DEFAULT ''             NOT NULL CHARACTER SET ascii COLLATE ascii_bin,
    col10   LONGTEXT CHARACTER SET ascii COLLATE ascii_bin DEFAULT '' NOT NULL,
Copied!

);

13.4 Changes

Table of contents

Breaking Changes

None since TYPO3 v13.0 release.

Features

None since TYPO3 v13.3 release.

Deprecation

Important

Deprecation: #105076 - Plugin content element and plugin sub types

See forge#105076

Description

Historically, plugins have been registered using the list content element and the plugin subtype list_type field. This functionality has been kept for backwards compatibility reasons. However, since the release of TYPO3 v12.4, the recommended way to create a plugin is by using a dedicated content type (CType) for each plugin.

This old "General Plugin" approach has always been ugly from a UX perspective point of view since it hides plugin selection behind "General plugin" content element, forcing a second selection step and making such plugins something special.

Therefore, the plugin content element (list) and the plugin sub types field ( list_type) have been marked as deprecated in TYPO3 v13.4 and will be removed in TYPO3 v14.0.

Additionally, the related PHP constant TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_PLUGIN has been deprecated as well.

Impact

Plugins added using TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPlugin() where the second parameter is list_type (which is still the default) will trigger a deprecation level log entry in TYPO3 v13 and will fail in v14.

Therefore, the same applies on using TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin() (to configure the plugin for frontend rendering), where no fifth parameter is provided or where the fifth parameter is list_type ( ExtensionUtility::PLUGIN_TYPE_PLUGIN), which is still the default.

The extension scanner will report any usage of configurePlugin(), where less than the required five arguments are provided. Actually, the only valid value for the fifth parameter $pluginType is CType, for which the ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT constant can be used.

Affected installations

Extensions registering plugins as list_type plugin sub type.

Migration

Existing plugins must be migrated to use the CType record type. Extension authors must implement the following changes:

  • Register plugins using the CType record type
  • Create update wizard which extends \TYPO3\CMS\Install\Updates\AbstractListTypeToCTypeUpdate and add list_type to CType mapping for each plugin to migrate. The migration wizard for indexed_search in class IndexedSearchCTypeMigration can be used as reference example.
  • Migrate possible FlexForm registration and add dedicated showitem TCA configuration
  • Migrate possible PreviewRenderer registration in TCA
  • Adapt possible content element wizard items in Page TSConfig, where list_type is used
  • Adapt possible content element restrictions in backend layouts or container elements defined by third-party extensions like ichhabrecht/content-defender .

Common example

// Before

$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['my_plugin'] = 'pi_flexform';
$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_excludelist']['my_plugin'] = 'pages,layout,recursive';

// After
$GLOBALS['TCA']['tt_content']['types']['my_plugin']['showitem'] = '<Some Fields>,pi_flexform,<Other Fields>';
Copied!

Deprecation: #105171 - INCLUDE_TYPOSCRIPT TypoScript syntax

See forge#105171

Description

The old TypoScript syntax to import external TypoScript files based on <INCLUDE_TYPOSCRIPT: has been marked as deprecated with TYPO3 v13 and will be removed in v14.

Integrators should switch to @import.

There are multiple reasons to finally phase out this old construct:

  • The <INCLUDE_TYPOSCRIPT: syntax is clumsy and hard to grasp, especially when combining multiple options. It is hard to learn for integrators new to the project.
  • The implementation has a high level of complexity, is only partially tested and consists of edge cases that are hard to decide on and even harder to change since that may break existing usages in hard to debug ways.
  • The syntax can have negative security impact when not used wisely by loading TypoScript from editor related folders like fileadmin/. The syntax based on @import has been designed more thoughtful in this regard.
  • Loading TypoScript files from folders relative to the public web folder is unfortunate and can have negative side effects when switching "legacy" based instances to composer.
  • The syntax based on @import has been introduced in TYPO3 v9 already and has been designed to stay at this point in time. With TYPO3 v12, the last missing feature of <INCLUDE_TYPOSCRIPT: has been made possible for @import as well: @import can be loaded conditionally by putting them into the body of a casual TypoScript condition.
  • TYPO3 v12 discouraged using <INCLUDE_TYPOSCRIPT: within the documentation and already anticipated a future deprecation and removal.

Impact

When using TypoScript <INCLUDE_TYPOSCRIPT: syntax a deprecation level log entry in TYPO3 v13 is emitted. The syntax will stop working with TYPO3 v14 and will be detected as an "invalid line" in the TypoScript related Backend modules.

Affected installations

Instances TypoScript syntax based on <INCLUDE_TYPOSCRIPT:.

Migration

Most usages of <INCLUDE_TYPOSCRIPT: can be turned into @import easily. A few examples:

# Before
<INCLUDE_TYPOSCRIPT: source="FILE:EXT:my_extension/Configuration/TypoScript/myMenu.typoscript">
# After
@import 'EXT:my_extension/Configuration/TypoScript/myMenu.typoscript'

# Before
# Including .typoscript files in a single (non recursive!) directory
<INCLUDE_TYPOSCRIPT: source="DIR:EXT:my_extension/Configuration/TypoScript/" extensions="typoscript">
# After
@import 'EXT:my_extension/Configuration/TypoScript/*.typoscript'

# Before
# Including .typoscript and .ts files in a single (non recursive!) directory
<INCLUDE_TYPOSCRIPT: source="DIR:EXT:my_extension/Configuration/TypoScript/" extensions="typoscript,ts">
# After
@import 'EXT:my_extension/Configuration/TypoScript/*.typoscript'
# Rename all files ending on .ts to .typoscript

# Before
# Including a file conditionally
<INCLUDE_TYPOSCRIPT: source="FILE:EXT:my_extension/Configuration/TypoScript/user.typoscript" condition="[frontend.user.isLoggedIn]">
# After
[frontend.user.isLoggedIn]
    @import 'EXT:my_extension/Configuration/TypoScript/user.typoscript'
[END]
Copied!

There are a few more use cases that cannot be transitioned so easily since @import is a bit more restrictive.

As one restriction @import cannot include files from arbitrary directories like fileadmin/, but only from extensions by using the EXT: prefix. Instances that use <INCLUDE_TYPOSCRIPT: with source="FILE:./someDirectory/..." should move this TypoScript into a project or site extension. Such instances are also encouraged to look into the TYPO3 v13 "Site sets" feature and eventually transition towards it along the way.

@import allows to import files with the file ending .typoscript and .tsconfig. If you used any of the outdated file endings like .ts or .txt rename those files before switching to the @import syntax.

The @import feature does not support recursive directory inclusion, as it does not allow wildcards in directory paths. Having directories like TypoScript/foo and TypoScript/bar, each having .typoscript files, could be included using <INCLUDE_TYPOSCRIPT: source=DIR:EXT:my_extension/Configuration/TypoScript extensions="typoscript">, which would find such files in foo and bar, and any other directory. This level of complexity was not wished to allow in the @import syntax since it can make file includes more intransparent with too much attached magic. Instances using this should either reorganize their files, or have multiple dedicated @import statements. The need for recursive includes may also be mitigated by restructuring TypoScript based functionality using "Site sets".

The transition from <INCLUDE_TYPOSCRIPT: can be often further relaxed with these features in mind:

Deprecation: #105213 - TCA sub types

See forge#105213

Description

One of the main features of TCA are the record types. This allows to use a single table for different purposes and in different contexts. The most known examples of using record types are the "Page Types" of pages and the "Content Types" of tt_content. For every specific type of such table, it's possible to define the fields to be used and even manipulate them e.g. change their label.

A special case since ever has been the plugin registration. This for a long time has been done using the so called "sub types" feature of TCA. This is another layer below record types and allows to further customize the behaviour of a record type using another select field, defined via subtype_value_field as well as defining fields to be added - subtypes_addlist - or excluded - subtypes_excludelist - for the record type, depending on the selected sub type.

For a couple of version now, it's encouraged to register plugins just as standard content elements via the tt_content type field CType. Therefore, the special registration via the combination of the list record type and the selection of a sub type via the list_type field has already been deprecated with Deprecation: #105076 - Plugin content element and plugin sub types.

Since the "sub types" feature was mainly used for this scenario only, it has now been deprecated as well. Registration of custom types should therefore always be done by using record types. This makes configuration much cleaner and more comprehensible.

Impact

Using subtype_value_field in a TCA types configurations will lead to a deprecation log entry containing information about where adaptations need to take place.

Affected installations

All installations using the sub types feature by defining a subtype_value_field in a TCA types configuration, which is really uncommon as the feature was mainly used for plugin registration in the tt_content table only.

Migration

Replace any subtype_value_field configuration with dedicated record types. Please also consider migrating corresponding subtypes_addlist and subtypes_excludelist definitions accordingly.

Before

'ctrl' => [
    'type' => 'type',
],
'columns' => [
    'type' => [
        'config' => [
            'type' => 'select',
            'renderType' => 'selectSingle',
            'items' => [
                [
                    'label' => 'A record type',
                    'value' => 'a_record_type'
                ]
            ]
        ]
    ],
    'subtype' => [
        'config' => [
            'type' => 'select',
            'renderType' => 'selectSingle',
            'items' => [
                [
                    'label' => 'A sub type',
                    'value' => 'a_sub_type'
                ]
            ]
        ]
    ],
],
'types' => [
    'a_record_type' => [
        'showitem' => 'aField,bField',
        'subtype_value_field' => 'subtype',
        'subtypes_addlist' => [
            'a_sub_type' => 'pi_flexform'
        ],
        'subtypes_excludelist' => [
            'a_sub_type' => 'bField'
        ]
    ]
]
Copied!

After

'ctrl' => [
    'type' => 'type',
],
'columns' => [
    'type' => [
        'config' => [
            'type' => 'select',
            'renderType' => 'selectSingle',
            'items' => [
                [
                    'label' => 'A record type',
                    'value' => 'a_record_type'
                ],
                [
                    'label' => 'A sub type',
                    'value' => 'a_sub_type'
                ]
            ]
        ]
    ],
],
'types' => [
    'a_record_type' => [
        'showitem' => 'aField,bField'
    ],
    'a_sub_type' => [
        'showitem' => 'aField,pi_flexform'
    ]
]
Copied!

Deprecation: #105230 - TypoScriptFrontendController and $GLOBALS['TSFE']

See forge#105230

Description

Class \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController and its global instance $GLOBALS['TSFE'] have been marked as deprecated. The class will be removed with TYPO3 v14.

Impact

Calling TypoScriptFrontendController methods, or accessing state from $GLOBALS['TSFE'] is considered deprecated.

Affected installations

Various instances may still retrieve information from $GLOBALS['TSFE'] . Remaining uses should be adapted. The extension scanner will find possible matches.

To keep backwards compatibility in TYPO3 v13, some calls can not raise deprecation level log messages.

Migration

See Breaking: #102621 - Most TSFE members marked internal or read-only for details on substitutions. In general, most state used by extensions has been turned into request attributes.

Deprecation: #105252 - DataProviderContext getters and setters

See forge#105252

Description

The backend layout related data object class \TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext has been turned into a data object using public constructor property promotion (PCPP). All setX() and getX() methods have been marked as deprecated in TYPO3 v13.4 and will be removed with TYPO3 v14.0. The class will be declared readonly in TYPO3 v14.0 which will enforce instantiation using PCPP. The class has been declared final since it is an API contract that must never be changed or extended. The constructor arguments will be declared non-optional in TYPO3 v14.0.

  • TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->setPageId()
  • TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->setTableName()
  • TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->setFieldName()
  • TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->setData()
  • TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->setPageTsConfig()
  • TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->getPageId()
  • TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->getTableName()
  • TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->getFieldName()
  • TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->getData()
  • TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext->getPageTsConfig()

Impact

Calling the getters or setters raises deprecation level log errors and will stop working in TYPO3 v14.0.

Affected installations

This data object is only relevant for instances with extensions that add custom backend layout data providers using $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider'] . There are few known extensions that do this. The extension scanner is not configured to find possible usages since the method names are too generic and would lead to too many false positives.

Migration

Create new objects using PCPP with named arguments instead of the setters. Instances should be created using new():

// Before
$dataProviderContext = GeneralUtility::makeInstance(DataProviderContext::class);
$dataProviderContext
    ->setPageId($pageId)
    ->setData($parameters['row'])
    ->setTableName($parameters['table'])
    ->setFieldName($parameters['field'])
    ->setPageTsConfig($pageTsConfig);

// After
$dataProviderContext = new DataProviderContext(
    pageId: $pageId,
    tableName: $parameters['table'],
    fieldName: $parameters['field'],
    data: $parameters['row'],
    pageTsConfig: $pageTsConfig,
);
Copied!

Use the properties instead of the getters, example:

// Before
$pageId = $dataProviderContext->getPageId()
// After
$pageId = $dataProviderContext->pageId
Copied!

Deprecation: #105279 - Replace TYPO3 EnumType with Doctrine DBAL EnumType

See forge#105279

Description

TYPO3 did provide a custom Doctrine DBAL column type implementation for the native SQL type ENUM that was only compatible with MySQL and MariaDB connections.

The Doctrine DBAL Team implemented ENUM support with Release 4.2.0 in class \Doctrine\DBAL\Types\EnumType, only supporting MySQL and MariaDB as well.

TYPO3 removed its custom implementation with TYPO3 v13.4.0.

Class \TYPO3\CMS\Core\Database\Schema\Types\EnumType has been marked as deprecated and is replaced with an class alias of \EnumType. The alias will be removed with TYPO3 v14.

See Release 4.2.0

Impact

doctrine/dbal >= 4.2.0 is incompatible with TYPO3 versions before v13.4.0. Composer-based instances using TYPO3 v13.3 or older should add an according conflict to their composer.json.

Affected installations

Instances using the the ENUM type directly or by any third party extension using TYPO3 13.0 to 13.3 in Composer mode will break, when the doctrine/dbal Composer packages is updated to version 4.2.0 or newer.

Migration

Upgrade (directly) to TYPO3 v13.4 or ensure to avoid updating Doctrine DBAL to 4.2.x or newer versions in Composer-based instances.

Replace \TYPO3\CMS\Core\Database\Schema\Types\EnumType type declarations with \Doctrine\DBAL\Types\EnumType.

Deprecation: #105297 - tableoptions and collate connection configuration

See forge#105297

Description

The possibility to configure default table options like charset and collation for the database analyzer has been introduced using the array $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['tableoptions'] with sub-array keys charset and collate. These were only used for MySQL and MariaDB connections.

Since TYPO3 v11 the tableoptions keys were silently migrated to defaultTableOptions, which is the proper Doctrine DBAL connection option for for MariaDB and MySQL.

Furthermore, Doctrine DBAL 3.x switched from using they array key collate to collation, ignoring the old array key with Doctrine DBAL 4.x. This was silently migrated by TYPO3, too.

These options and migration are now deprecated in favor of using the final array keys and will be removed with TYPO3 v15 (or later) as breaking change.

Impact

Instances using the database connection options in $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['tableoptions'] array, or using the collate key will trigger a E_USER_DEPRECATED notification.

Affected installations

All instances using the mentioned options.

Migration

Review settings.php and additional.php and adapt the deprecated configuration by renaming affected array keys.

# before
'DB' => [
    'Connections' => [
        'Default' => [
            'tableoptions' => [
                'collate' => 'utf8mb4_unicode_ci',
            ],
        ],
    ],
],

# after
'DB' => [
    'Connections' => [
        'Default' => [
            'defaultTableOptions' => [
                'collation' => 'utf8mb4_unicode_ci',
            ],
        ],
    ],
],
Copied!

Important: #105175 - Move FrontendBackendUserAuthentication into EXT:frontend

See forge#105175

Description

The internal \TYPO3\CMS\Frontend\Authentication\FrontendBackendUserAuthentication class, used for frontend requests while being logged in the backend has been moved from EXT:backend to EXT:frontend, since its dependencies are limited to EXT:core and EXT:frontend.

While for v13 a class alias mapping and a legacy notation for IDE's is available, the class is marked as @internal and therefore does not fall under TYPO3's Core API deprecation policy.

13.3 Changes

Table of contents

Breaking Changes

None since TYPO3 v13.0 release.

Features

Deprecation

Important

Feature: #93100 - Allow to directly declare static route variables

See forge#93100

Description

Instead of having to use custom route aspect mappers, implementing \TYPO3\CMS\Core\Routing\Aspect\StaticMappableAspectInterface , to avoid having &cHash= signatures being applied to the generated URL, variables now can be simply declared static in the corresponding route enhancer configuration.

Impact

By using the new static route configuration directive, custom aspect mapper implementations can be avoided. However, static route variables are only applied for a particular variable name if

  • there is no aspect mapper configured - aspect mappers are considered more specific and will take precedence
  • there is a companion requirements definition which narrows the set of possible values, and should be as restrictive as possible to avoid potential cache flooding - static routes variables are ignored, if there is no corresponding requirements definition

Example

routeEnhancers:
  Verification:
    type: Simple
    routePath: '/verify/{code}'
    static:
      code: true
    requirements:
      # only allows SHA1-like hex values - which still allows lots
      # of possible combinations - thus, for this particular example
      # the handling frontend controller should be uncached as well
      #
      # hint: if `static` is set, `requirements` must be set as well
      code: '[a-f0-9]{40}'
Copied!

As a result, using the URI query parameters &code=11f6ad8ec52a2984abaafd7c3b516503785c2072 would generate the URL https://example.org/verify/11f6ad8ec52a2984abaafd7c3b516503785c2072.

Feature: #99418 - Enable recycler by default

See forge#99418

Description

The TYPO3 system extension typo3/cms-recycler is now enabled by default for new TYPO3 installations.

Impact

New composer-based TYPO3 installations based on the TYPO3 CMS Base Distribution, and new legacy installations (tarball / zip download) have the system extension recycler enabled by default.

Feature: #99510 - Add file embedding option to asset ViewHelpers

See forge#99510

Description

The ViewHelpers <f:asset.css> and <f:asset.script> have been extended with a new argument inline. If this argument is set, the referenced asset file is rendered inline.

Setting the argument will therefore load the file content of the defined href / src as inline style or script. This is especially useful for content elements which are used as first element on a page and need some custom CSS to improve the Cumulative Layout Shift (CLS).

Impact

To add inline styles and scripts from a referenced file, the new inline argument can be set. For example, to add above-the-fold styles, the priority option can be set, which will put the file contents of EXT:sitepackage/Resources/Public/Css/my-hero.css as inline styles to the <head> section.

<f:asset.css identifier="my-hero" href="EXT:sitepackage/Resources/Public/Css/my-hero.css" inline="1" priority="1"/>
Copied!

To add JavaScript:

<f:asset.script identifier="my-hero" src="EXT:sitepackage/Resources/Public/Js/my-hero.js" inline="1" priority="1"/>
Copied!

Feature: #101252 - Introduce ErrorHandler for 403 errors with redirect option

See forge#101252

Description

The new error handler \TYPO3\CMS\Core\Error\PageErrorHandler\RedirectLoginErrorHandler has been added, which makes it possible to redirect the user to a configurable page.

Requesting a login-protected URL would usually return a generic HTTP 403 error in case of a missing fulfilled access permissions and the configuration typolinkLinkAccessRestrictedPages = NONE (default) is set.

By enabling this new handler via the site settings, the 403 response can be handled and a custom redirect can be performed.

The RedirectLoginErrorHandler allows to define a loginRedirectTarget, which must be configured to the page, where the login process is handled. Additionally, the loginRedirectParameter must be set to the URL parameter that will be used to hand over the original URL to the target page.

The redirect ensures that the original URL is added to the configured GET parameter loginRedirectParameter, so that the user can be redirected back to the original page after a successful login.

The error handler allows return_url or redirect_url as values for loginRedirectParameter. Those values are used in extensions like EXT:felogin or EXT:oidc.

The new error handler works (with some minor exceptions) similar to the "Forbidden (HTTP Status 403)" handler in TYPO3 extension plan2net/sierrha . It will still emit generic 403 HTTP error messages in certain scenarios, like when a user is already logged in, but the permissions are not satisfied.

Impact

It is now possible to configure a login redirection process when a user has no access to a page and a 403 error is thrown, so that after login the originating URL is requested again. Previously, this required custom Middlewares or implementations of PageErrorHandlerInterface .

Feature: #101391 - Add base64 attribute to ImageViewHelper

See forge#101391

Description

The ViewHelpers <f:image> and <f:uri.image> now support the attribute base64="true" that will provide a possibility to return the value of the image's src attribute encoded in base64.

<f:image base64="true" src="EXT:backend/Resources/Public/Images/typo3_logo_orange.svg" height="20" class="pr-2" />
<img src="{f:uri.image(base64: 'true', src:'EXT:backend/Resources/Public/Images/typo3_logo_orange.svg')}">
Copied!

Will result in the according HTML tag providing the image encoded in base64.

<img class="pr-2" src="data:image/svg+xml;base64,PHN2...cuODQ4LTYuNzU3Ii8+Cjwvc3ZnPgo=" alt="" width="20" height="20">
<img src="data:image/svg+xml;base64,PHN2...cuODQ4LTYuNzU3Ii8+Cjwvc3ZnPgo=">
Copied!

This can be particularly useful inside \TYPO3\CMS\Core\Mail\FluidEmail or to prevent unneeded HTTP calls.

Feature: #101472 - Allow static routes to assets

See forge#101472

Description

It is now possible to configure static routes with the type asset to link to resources which are typically located in the directory EXT:my_extension/Resources/Public/.

config/sites/my-site/config.yaml
routes:
  -
    route: example.svg
    type: asset
    asset: 'EXT:backend/Resources/Public/Icons/Extension.svg'
Copied!

Note that the asset URL can be configured on a per-site basis. This allows to deliver site-dependent custom favicon or manifest assets, for example.

Impact

Static routes to files shipped with extensions can now be configured in the site configuration.

Feature: #102255 - Option to skip URL processing in AssetRenderer

See forge#102255

Description

The \TYPO3\CMS\Core\Page\AssetCollector options have been extended to include an external flag. When set for asset files using $assetCollector->addStyleSheet() or $assetCollector->addJavaScript(), all processing of the asset URI (like the addition of the cache busting parameter) is skipped and the input path will be used as-is in the resulting HTML tag.

Example

The following code skips the cache busting parameter ?1726090820 for the supplied CSS file:

$assetCollector->addStyleSheet(
    'myCssFile',
    PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName('EXT:my_extension/Resources/Public/MyFile.css')),
    [],
    ['external' => true]
);
Copied!

Resulting in the following HTML output:

<link rel="stylesheet" href="/_assets/<hash>/myFile.css" />
Copied!

Impact

Developers can now use the AssetCollector API to embed JavaScript or CSS files without any processing of the supplied asset URI.

Feature: #102353 - AVIF support for images generated by GIFBUILDER

See forge#102353

Description

GIFBUILDER, the image manipulation library for TypoScript based on GDlib, a PHP extension bundled into PHP, now also supports generating resulting files of type "avif".

AVIF is an image format, that is supported by most modern browsers, and usually has a better compression (= smaller file size) than jpg files.

Impact

If defined via format=avif within a GifBuilder setup, the generated files are now AVIF files instead of png (the default).

It is possible to define the quality of a AVIF image similar to jpg images globally via $TYPO3_CONF_VARS['GFX']['avif_quality'] or via TypoScript's "quality" property on a per-image basis. Via TypoScript it is also possible to use the new property "speed" - see https://www.php.net/manual/en/function.imageavif.php for more details.

Example

page.10 = IMAGE
page.10 {
  file = GIFBUILDER
  file {
    backColor = yellow
    XY = 1024,199
    format = avif
    quality = 44
    speed = 1

    10 = IMAGE
    10.offset = 10,10
    10.file = 1:/my-image.jpg
  }
}
Copied!

A new test in the Environment module / Install Tool can be used to check if the bundled GDlib extension of your PHP version supports the AVIF image format.

Feature: #102422 - Introduce CacheDataCollector Api

See forge#102422

Description

A new API has been introduced to collect cache tags and their corresponding lifetime. This API is used in TYPO3 to accumulate cache tags from page cache and content object cache.

The API is implemented as a new PSR-7 request attribute 'frontend.cache.collector', which makes this API independent from TSFE.

Every cache tag has a lifetime. The minimum lifetime is calculated from all given cache tags. By default, the lifetime of a cache tag is set to PHP_INT_MAX, so it expires many years in the future. API users must therefore define the lifetime of a cache tag individually.

The current TSFE API is deprecated in favor of the new API, as the current cache tag API implementation does not allow to set lifetime and extension authors had to work around it.

Example

Add a single cache tag with 24 hours lifetime
use TYPO3\CMS\Core\Cache\CacheTag;

$cacheDataCollector = $request->getAttribute('frontend.cache.collector');
$cacheDataCollector->addCacheTags(
    new CacheTag('tx_myextension_mytable', 86400)
);
Copied!
Add multiple cache tags with different lifetimes
use TYPO3\CMS\Core\Cache\CacheTag;

$cacheDataCollector = $request->getAttribute('frontend.cache.collector');
$cacheDataCollector->addCacheTags(
    new CacheTag('tx_myextension_mytable_123', 3600),
    new CacheTag('tx_myextension_mytable_456', 2592000)
);
Copied!
Remove a cache tag
use TYPO3\CMS\Core\Cache\CacheTag;

$cacheDataCollector = $request->getAttribute('frontend.cache.collector');
$cacheDataCollector->removeCacheTags(
    new CacheTag('tx_myextension_mytable_123')
);
Copied!
Remove multiple cache tags
use TYPO3\CMS\Core\Cache\CacheTag;

$cacheDataCollector = $request->getAttribute('frontend.cache.collector');
$cacheDataCollector->removeCacheTags(
    new CacheTag('tx_myextension_mytable_123'),
    new CacheTag('tx_myextension_mytable_456')
);
Copied!
Get minimum lifetime, calculated from all cache tags
$cacheDataCollector = $request->getAttribute('frontend.cache.collector');
$cacheDataCollector->resolveLifetime();
Copied!
Get all cache tags
$cacheDataCollector = $request->getAttribute('frontend.cache.collector');
$cacheDataCollector->getCacheTags();
Copied!

The following event should only be used in code that has no access to the request attribute 'frontend.cache.collector', it is marked @internal and may vanish: It designed to allow passive cache-data signaling, without exactly knowing the current context and not having the current request at hand. It is not meant to allow for cache tag interception or extension.

Add cache tag without access to the request object
$this->eventDispatcher->dispatch(
    new AddCacheTagEvent(
        new CacheTag('tx_myextension_mytable_123', 3600)
    )
);
Copied!

Feature: #103511 - Introduce Extbase file upload and deletion handling

See forge#103511

Description

TYPO3 now provides an API for file upload- and deletion-handling in Extbase extensions, which allows extension developers to implement file uploads more easily into Extbase Domain Models.

The scope of this API is to cover some of the most common use cases and to keep the internal file upload and deletion process in Extbase as simple as possible.

The API supports mapping and handling of file uploads and deletions for the following scenarios:

  • Property of type FileReference in a domain model
  • Property of type \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> in a domain model

File uploads can be validated by the following rules:

  • minimum and maximum file count
  • minimum and maximum file size
  • allowed MIME types
  • image dimensions (for image uploads)

Additionally, it is ensured, that the filename given by the client is valid, meaning that no invalid characters (null-bytes) are added and that the file does not contain an invalid file extension. The API has support for custom validators, which can be created on demand.

To avoid complexity and maintain data integrity, a file upload is only processed if the validation of all properties of a domain model is successful. In this first implementation, file uploads are not persisted/cached temporarily, so this means in any case of a validation failure ("normal" validators and file upload validation) a file upload must be performed again by users.

Possible future enhancements of this functionality could enhance the existing #[FileUpload] attribute/annotation with configuration like a temporary storage location, or specifying additional custom validators (which can be done via the PHP-API as described below)

Nesting of domain models

File upload handling for nested domain models (e.g. modelA.modelB.fileReference) is not supported.

File upload configuration with the FileUpload attribute

File upload for a property of a domain model can be configured using the newly introduced \TYPO3\CMS\Extbase\Annotation\FileUpload attribute.

Example:

#[FileUpload([
    'validation' => [
        'required' => true,
        'maxFiles' => 1,
        'fileSize' => ['minimum' => '0K', 'maximum' => '2M'],
        'mimeType' => ['allowedMimeTypes' => ['image/jpeg', 'image/png']],
    ],
    'uploadFolder' => '1:/user_upload/files/',
])]
protected ?FileReference $file = null;
Copied!

All configuration settings of the \TYPO3\CMS\Extbase\Mvc\Controller\FileUploadConfiguration object can be defined using the FileUpload attribute. It is however not possible to add custom validators using the FileUpload attribute, which you can achieve with a manual configuration as shown below.

The currently available configuration array keys are:

  • validation ( array with keys required, maxFiles, minFiles, fileSize, allowedMimeTypes, mimeType, imageDimensions, see File upload validation)
  • uploadFolder ( string, destination folder)
  • duplicationBehavior ( object, behaviour when file exists)
  • addRandomSuffix ( bool, suffixing files)
  • createUploadFolderIfNotExist ( bool, whether to create missing directories)

It is also possible to use the FileUpload annotation to configure file upload properties, but it is recommended to use the FileUpload attribute due to better readability.

Manual file upload configuration

A file upload configuration can also be created manually and should be done in the initialize*Action.

Example:

public function initializeCreateAction(): void
{
    $mimeTypeValidator = GeneralUtility::makeInstance(MimeTypeValidator::class);
    $mimeTypeValidator->setOptions(['allowedMimeTypes' => ['image/jpeg']]);

    $fileHandlingServiceConfiguration = $this->arguments->getArgument('myArgument')->getFileHandlingServiceConfiguration();
    $fileHandlingServiceConfiguration->addFileUploadConfiguration(
        (new FileUploadConfiguration('myPropertyName'))
            ->setRequired()
            ->addValidator($mimeTypeValidator)
            ->setMaxFiles(1)
            ->setUploadFolder('1:/user_upload/files/')
    );

    $this->arguments->getArgument('myArgument')->getPropertyMappingConfiguration()->skipProperties('myPropertyName');
}
Copied!

Configuration options for file uploads

The configuration for a file upload is defined in a FileUploadConfiguration object.

This object contains the following configuration options.

Property name:

Defines the name of the property of a domain model to which the file upload configuration applies. The value is automatically retrieved when using the FileUpload attribute. If the FileUploadConfiguration object is created manually, it must be set using the $propertyName constructor argument.

Validation:

File upload validation is defined in an array of validators in the FileUploadConfiguration object. The validator \TYPO3\CMS\Extbase\Validation\Validator\FileNameValidator , which ensures that no executable PHP files can be uploaded, is added by default if the file upload configuration object is created using the FileUpload attribute.

In addition, Extbase includes the following validators to validate an UploadedFile object:

  • \TYPO3\CMS\Extbase\Validation\Validator\FileSizeValidator
  • \TYPO3\CMS\Extbase\Validation\Validator\MimeTypeValidator
  • \TYPO3\CMS\Extbase\Validation\Validator\ImageDimensionsValidator

Those validators can either be configured with the FileUpload attribute or added manually to the configuration object with the addValidator method.

Required:

Defines whether a file must be uploaded. If it is set to true, the minFiles configuration is set to 1.

Minimum files:

Defines the minimum amount of files to be uploaded.

Maximum files:

Defines the maximum amount of files to be uploaded.

Upload folder:

Defines the upload path for the file upload. This configuration expects a storage identifier (e.g. 1:/user_upload/folder/). If the given target folder in the storage does not exist, it is created automatically.

Upload folder creation, when missing:

The default creation of a missing storage folder can be disabled via the configuration attribute createUploadFolderIfNotExist ( bool, default true).

Add random suffix:

When enabled, the filename of an uploaded and persisted file will contain a random 16 char suffix. As an example, an uploaded file named job-application.pdf will be persisted as job-application-<random-hash>.pdf in the upload folder.

The default value for this configuration is true and it is recommended to keep this configuration active.

This configuration only has an effect when uploaded files are persisted.

Duplication behavior:

Defines the FAL behavior, when a file with the same name exists in the target folder. Possible values are DuplicationBehavior::RENAME (default), DuplicationBehavior::REPLACE and DuplicationBehavior::CANCEL.

Modifying existing configuration

File upload configuration defined by the FileUpload attribute can be changed in the initialize*Action.

Example:

public function initializeCreateAction(): void
{
    $validator = GeneralUtility::makeInstance(MyCustomValidator::class);

    $argument = $this->arguments->getArgument('myArgument');
    $configuration = $argument->getFileHandlingServiceConfiguration()->getFileUploadConfigurationForProperty('file');
    $configuration?->setMinFiles(2);
    $configuration?->addValidator($validator);
    $configuration?->setUploadFolder('1:/user_upload/custom_folder');
}
Copied!

The example shows how to modify the file upload configuration for the argument item and the property file. The minimum amount of files to be uploaded is set to 2 and a custom validator is added.

To remove all defined validators except the DenyPhpUploadValidator, use the resetValidators() method.

Using TypoScript configuration for file uploads configuration

When a file upload configuration for a property has been added using the FileUpload attribute, it may be required make the upload folder or other configuration options configurable with TypoScript.

Extension authors should use the initialize*Action to apply settings from TypoScript to a file upload configuration.

Example:

public function initializeCreateAction(): void
{
    $argument = $this->arguments->getArgument('myArgument');
    $configuration = $argument->getFileHandlingServiceConfiguration()->getConfigurationForProperty('file');
    $configuration?->setUploadFolder($this->settings['uploadFolder'] ?? '1:/fallback_folder');
}
Copied!

File upload validation

Each uploaded file can be validated against a configurable set of validators. The validation section of the FileUpload attribute allows to configure commonly used validators using a configuration shorthand.

The following validation rules can be configured in the validation section of the FileUpload attribute:

  • required
  • minFiles
  • maxFiles
  • fileSize (for \TYPO3\CMS\Extbase\Validation\Validator\FilesizeValidator)
  • imageDimensions (for \TYPO3\CMS\Extbase\Validation\Validator\ImageDimensionsValidator )
  • mimeType (for \TYPO3\CMS\Extbase\Validation\Validator\MimeTypeValidator )
  • allowedMimeTypes (shorthand notation for configuration option allowedMimeTypes of the MimeTypeValidator)

Example:

#[FileUpload([
    'validation' => [
        'required' => true,
        'maxFiles' => 1,
        'fileSize' => ['minimum' => '0K', 'maximum' => '2M'],
        'mimeType' => ['allowedMimeTypes' => ['image/jpeg']],
        'imageDimensions' => ['maxWidth' => 4096, 'maxHeight' => 4096]
    ],
    'uploadFolder' => '1:/user_upload/extbase_single_file/',
])]
Copied!

Extbase will internally use the Extbase file upload validators for fileSize, mimeType and imageDimensions validation.

Custom validators can be created according to project requirements and must extend the Extbase AbstractValidator . The value to be validated is always a PSR-7 UploadedFile object. Custom validators can however not be used in the FileUpload attribute and must be configured manually.

Shorthand notation for allowedMimeTypes

Using the mimeType configuration array, all options of the MimeTypeValidator can be set as sub-keys (since TYPO3 13.4.1):

#[FileUpload([
    'validation' => [
        'required' => true,
        'mimeType' => [
            'allowedMimeTypes' => ['image/jpeg'],
            'ignoreFileExtensionCheck' => false,
            'notAllowedMessage' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:validation.mimetype.notAllowedMessage',
            'invalidExtensionMessage' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:validation.mimetype.invalidExtensionMessage',
        ],
    ],
    'uploadFolder' => '1:/user_upload/files/',
])]
Copied!

The shorthand notation via 'allowedMimeTypes' continues to exist, in case only the mime type validation is needed. However, it is recommended to utilize the full 'mimeType' configuration array.

Deletion of uploaded files and file references

The new Fluid ViewHelper Form.uploadDeleteCheckbox ViewHelper <f:form.uploadDeleteCheckbox> can be used to show a "delete file" checkbox in a form.

Example for object with FileReference property:

<f:form.uploadDeleteCheckbox property="file" fileReference="{object.file}" />
Copied!

Example for an object with an TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> property, containing multiple files and allowing to delete the first one (iteration is possible within Fluid, to do that for every object of the collection):

<f:form.uploadDeleteCheckbox property="file.0" fileReference="{object.file}" />
Copied!

Extbase will then handle file deletion(s) before persisting a validated object. It will:

  • validate that minimum and maximum file upload configuration for the affected property is fulfilled (only if the property has a FileUpload )
  • delete the affected sys_file_reference record
  • delete the affected file

Internally, Extbase uses FileUploadDeletionConfiguration objects to track file deletions for properties of arguments. Files are deleted directly without checking whether the current file is referenced by other objects.

Apart from using this ViewHelper, it is of course still possible to manipulate FileReference properties with custom logic before persistence.

New PSR-14 events

The following new PSR-14 event has been added to allow customization of file upload related tasks:

ModifyUploadedFileTargetFilenameEvent

The ModifyUploadedFileTargetFilenameEvent allows event listeners to alter a filename of an uploaded file before it is persisted.

Event listeners can use the method getTargetFilename() to retrieve the filename used for persistence of a configured uploaded file. The filename can then be adjusted via setTargetFilename(). The relevant configuration can be retrieved via getConfiguration().

Impact

Extension developers can use the new feature to implement file uploads and file deletions in Extbase extensions easily with commonly known Extbase property attributes/annotations.

Feature: #103521 - Change table restrictions UI to combine read and write permissions

See forge#103521

Description

The tables_select and tables_modify fields of the be_groups table store information about permissions to read and write into selected database tables.

Due to TYPO3's internal behavior, when write permissions are granted for some tables, those tables are also automatically available for reading.

To make managing table permissions much easier and more efficient for integrators, the separate form fields for Tables (listing) [tables_select] and Tables (modify) [tables_modify] have been combined into a single UI element. This field now offers separate radio buttons to define which tables the backend user group should have permission to read and / or write. This is done by selecting one of the "No Access", "Read" or "Read & Write" options.

To further improve the user experience, it is also possible to use the "Check All", "Uncheck All" and "Toggle Selection" options for each permission.

Under the hood, when these permissions are processed, they are still saved separately in the tables_select and tables_modify columns in the be_groups table, as they were before.

To render this new table view and handle its behavior, a dedicated form renderType tablePermission has been introduced, which is now set for the tables_modify column. The tables_select column has been changed to TCA type passthrough.

The new form element is defined through: \TYPO3\CMS\Backend\Form\Element\TablePermissionElement . It uses a dedicated data provider defined in: \TYPO3\CMS\Backend\Form\FormDataProvider\TcaTablePermission . The JavaScript code is handled by a new web component: @typo3/backend/form-engine/element/table-permission-element.js.

When the TcaTablePermission data provider handles the configuration, it reads table lists from both the tables_select and tables_modify columns and combines them into a single array with unique table names.

Impact

Managing table permissions for backend user groups has been improved by visually combining the Tables (listing) [tables_select] and Tables (modify) [tables_modify] options, as well as by adding the multi record selection functionality.

Feature: #103576 - Allow defining opacity in TCA type=color element

See forge#103576

Description

A new boolean property opacity has been added to the TCA configuration of a TCA type color element to allow defining colors with an opacity using the RRGGBBAA color notation.

'my_color' => [
    'label' => 'My Color',
    'config' => [
        'type' => 'color',
        'opacity' => true,
    ],
],
Copied!

Impact

If opacity is enabled, editors can select not only a color but also its opacity in a corresponding color element.

Feature: #103581 - Automatically transform TCA field values for record objects

See forge#103581

Description

With forge#103783 the new \TYPO3\CMS\Core\Domain\Record object has been introduced. It is an object representing a raw database record, based on TCA and is usually used in the frontend (via Fluid Templates), when fetching records with the RecordTransformationProcessor ( record-transformation) or by collecting content elements with the PageContentFetchingProcessor ( page-content).

The Records API - introduced together with the Schema API in forge#104002 - now expands the record's values for most common field types (known from the TCA Schema) from their raw database value into "rich-flavored" values, which might be Record , FileReference , \TYPO3\CMS\Core\Resource Folder or \DateTimeImmutable objects.

This works for the following "relation" TCA types:

  • category
  • file
  • folder
  • group
  • inline
  • select with MM and foreign_table

In addition, the values of following TCA types are also resolved and expanded automatically:

  • datetime
  • flex
  • json
  • link
  • select with a static list of entries

Each of the fields receives a full-fledged resolved value, based on the field configuration from TCA.

In case of relations ( category, group, inline, select with MM and foreign_table), a collection ( LazyRecordCollection) of new Record objects is attached as value. In case of file, a collection ( LazyFileReferenceCollection) of FileReference objects and in case of type folder, a collection ( LazyFolderCollection) of Folder objects are attached.

Example

<f:for each="{myContent.main.records}" as="record">
    <f:for each="{record.image}" as="image">
        <f:image image="{image}" />
    </f:for>
</f:for>
Copied!

New TCA option relationship

In order to define cardinality on TCA level, the option relationship is introduced for all "relation" TCA types listed above. If this option is set to oneToOne or manyToOne, then relations are resolved directly without being wrapped into collection objects. In case the relation can not be resolved, NULL is returned.

'image' => [
    'config' => [
        'type' => 'file',
        'relationship' => 'manyToOne',
    ]
]
Copied!
<f:for each="{myContent.main.records}" as="record">
    <f:image image="{record.image}" />
</f:for>
Copied!

Field expansion

For TCA type flex, the corresponding FlexForm is resolved and therefore all values within this FlexForm are processed and expanded as well.

Fields of TCA type datetime will be transformed into a full \DateTimeInterface object.

Fields of TCA type json will provide the decoded JSON value.

Fields of TCA type link will provide the \TYPO3\CMS\Core\LinkHandling\TypolinkParameter object, which is an object oriented representation of the corresponding TypoLink parameter configuration.

Fields of TCA type select without a relationship will always provide an array of static values.

Impact

When using Record objects through the \TYPO3\CMS\Core\Domain\RecordFactory API, e.g. via RecordTransformationProcessor ( record-transformation) or PageContentFetchingProcessor (page-content), the corresponding Record objects are now automatically processed and enriched.

Those can not only be used in the frontend but also for Backend Previews in the page module. This is possible by configuring a Fluid Template via Page TSconfig to be used for the page preview rendering:

mod.web_layout.tt_content.preview {
    textmedia = EXT:site/Resources/Private/Templates/Preview/Textmedia.html
}
Copied!

In such template the newly available variable {record} can be used to access the resolved field values. It is advised to migrate existing preview templates to this new object, as the former values will probably vanish in the next major version.

By utilizing the new API for fetching records and content elements, the need for further data processors, e.g. FilesProcessor ( files), becomes superfluous since all relations are resolved automatically when requested.

Feature: #103789 - Add "close"-button to page layout, if returnUrl is set

See forge#103789

Description

A "close"-button is now displayed in the page module, if the returnUrl argument is set. When this button is clicked, the previous module leading to the page module (or a custom link defined in returnUrl) will be displayed again.

In order to utilize this, backend module links set in extensions must pass the returnUrl argument. If returnUrl is not set, the "close"-button will not be displayed.

Examples

Here is an example, using the Fluid <be:moduleLink> ViewHelper:

Fluid example
<a href="{be:moduleLink(route:'web_layout', arguments:'{id:pageUid, returnUrl: returnUrl}')}"
   class="btn btn-default"
   title="{f:translate(key: 'LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:title')}">
    <core:icon identifier="actions-document" size="small"/>
</a>
Copied!

The behaviour is similar to the <be:uri.editRecord> ViewHelper, where setting the returnUrl argument will also cause a "close"-button to be displayed.

The returnUrl should usually return to the calling (originating) module.

You can build the returnUrl with the Fluid ViewHelper be:uri:

Fluid example for building returnUrl to module "linkvalidator"
<f:be.uri route="web_linkvalidator" parameters="{id: pageUid}"/>
Copied!

Here is an example for building the returnUrl via PHP:

Backend module controller
use TYPO3\CMS\Backend\Routing\UriBuilder;

public function __construct(
    protected readonly UriBuilder $uriBuilder
) {}

protected function generateModuleUri(array $parameters = []): string
{
    return $this->uriBuilder->buildUriFromRoute('web_linkvalidator',  $parameters);
}

public function __invoke(ServerRequestInterface $request): ResponseInterface
{
    // ...
    $this->view->assign('returnUrl', $this->generateModuleUri(['pageUid' => $this->id]));
    // ...
}
Copied!

Impact

The change has no impact, unless the functionality is being used. Extension authors can make use of the new functionality to also conveniently link back to an originating or custom module for a streamlined linear backend user-experience.

Feature: #104126 - Add configuration setting to define backend-locking file

See forge#104126

Description

TYPO3 supports the ability to lock the backend for maintenance reasons. This is controlled with a LOCK_BACKEND file that was previously stored in typo3conf/.

With Important: #104126 - Drop "typo3conf" directory from system status check and backend locking this directory is no longer needed, so now the location to this file can be adjusted via the new configuration setting $GLOBALS['TYPO3_CONF_VARS']['BE']['lockBackendFile'] .

When empty, it falls back to a file LOCK_BACKEND, which is now stored by default in:

  • var/lock/ for Composer Mode
  • config/ for Legacy Mode

If you previously manually maintained the LOCK_BACKEND file (for example via deployment or other maintenance automation), please either adjust your automations to the new file location, or change the setting to the desired file location, or at best use the CLI commands vendor/bin/typo3 backend:lock and vendor/bin/typo3 backend:unlock.

The backend locking functionality is now contained in a distinct service class \TYPO3\CMS\Backend\Authentication\BackendLocker to allow future flexibility.

When upgrading an installation to Composer Mode with a locked backend in effect, please ensure your backend can remain locked by moving (or copying) the file to the new location var/lock/.

Remember, if you want locked backend state to persist between deployments, ensure that the used directory (var/lock by default) is shared between deployment releases.

Impact

The location for LOCK_BACKEND to lock (and unlock) the backend can now be controlled by maintainers of a TYPO3 installation, and has moved outside of typo3conf/ by default to either var/lock/ (Composer) or config/ (Legacy).

Feature: #104168 - PSR-14 event for modifying countries

See forge#104168

Description

A new PSR-14 event \TYPO3\CMS\Core\Country\Event\BeforeCountriesEvaluatedEvent has been introduced to modify the list of countries provided by \TYPO3\CMS\Core\Country\CountryProvider .

This event allows to add, remove and alter countries from the list used by the provider class itself and ViewHelpers like <f:form.countrySelect />.

Example

An example corresponding event listener class:

<?php
declare(strict_types=1);

namespace MyVendor\MyExtension\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Country\Country;
use TYPO3\CMS\Core\Country\Event\BeforeCountriesEvaluatedEvent;

final readonly class EventListener
{
    #[AsEventListener(identifier: 'my-extension/before-countries-evaluated')]
    public function __invoke(BeforeCountriesEvaluatedEvent $event): void
    {
        $countries = $event->getCountries();
        unset($countries['BS']);
        $countries['XX'] = new Country(
            'XX',
            'XYZ',
            'Magic Kingdom',
            '987',
            '🔮',
            'Kingdom of Magic and Wonders'
        );
        $event->setCountries($countries);
    }
}
Copied!
EXT:my_extension/ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['locallangXMLOverride']
    ['EXT:core/Resources/Private/Language/Iso/countries.xlf'][]
        = 'EXT:my_extension/Resources/Private/Language/countries.xlf';
Copied!
EXT:my_extension/Resources/Private/Language/countries.xlf
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<xliff version="1.0">
    <file source-language="en" datatype="plaintext" date="2024-01-08T18:44:59Z" product-name="my_extension">
        <body>
            <trans-unit id="XX.name" approved="yes">
                <source>Magic Kingdom</source>
            </trans-unit>
            <trans-unit id="XX.official_name" approved="yes">
                <source>Kingdom of Magic and Wonders</source>
            </trans-unit>
        </body>
    </file>
</xliff>
Copied!

Impact

Using the PSR-14 event BeforeCountriesEvaluatedEvent allows modification of countries provided by CountryProvider .

Feature: #104221 - PSR-14 events for RTE <-> Persistence transformations

See forge#104221

Description

When using an RTE HTML content element, two transformations take place within the TYPO3 backend:

  • From database: Fetching the current content from the database (persistence) and preparing it to be displayed inside the RTE HTML component.
  • To database: Retrieving the data returned by the RTE and preparing it to be persisted into the database.

This takes place in the \TYPO3\CMS\Core\Html\RteHtmlParser class, by utilizing the methods transformTextForRichTextEditor and transformTextForPersistence.

With forge#96107 and forge#92992, the former hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['transformation'] was removed, which took care of applying custom user-transformations. The suggested replacement for this was to use the actual RTE YAML configuration and API like allowAttributes.

Now, four PSR-14 Events are introduced to allow more granular control over data for persistence -> RTE and RTE -> persistence. This allows developers to apply more customized transformations, apart from the internal and API ones:

Modify data when saving RTE content to the database (persistence):

  • \TYPO3\CMS\Core\Html\Event\BeforeTransformTextForPersistenceEvent
  • \TYPO3\CMS\Core\Html\Event\AfterTransformTextForPersistenceEvent

Modify data when retrieving content from the database and pass to the RTE:

  • \TYPO3\CMS\Core\Html\Event\BeforeTransformTextForRichTextEditorEvent
  • \TYPO3\CMS\Core\Html\Event\AfterTransformTextForRichTextEditorEvent

All four events have the same structure (for now):

  • getHtmlContent() - retrieve the current HTML content
  • setHtmlContent() - used to set modifications of the HTML content
  • getInitialHtmlContent() - retrieve the untampered initial HTML content
  • getProcessingConfiguration() - retrieve processing configuration array

The event is meant to be used so that developers can change the HTML content either before the internal TYPO3 modifications, or after those.

The before events are executed before TYPO3 applied any kind of internal transformations, like for links. Event Listeners that want to modify output so that TYPO3 additionally operates on that, should listen to those before-Events.

When Event Listeners want to perform on the final result, the corresponding after-Events should be utilized.

Event listeners can use $value = $event->getHtmlContent() to get the current contents, apply changes to $value and then store the manipulated data via $event->setHtmlContent($value), see example:

Example

An event listener class is constructed which will take an RTE input TYPO3 and internally store it in the database as [tag:typo3]. This could allow a content element data processor in the frontend to handle this part of the content with for example internal glossary operations.

The workflow would be:

  • Editor enters "TYPO3" in the RTE instance.
  • When saving, this gets stored as "[tag:typo3]".
  • When the editor sees the RTE instance again, "[tag:typo3]" gets replaced to "TYPO3" again.
  • So: The editor will always only see "TYPO3" and not know how it is internally handled.
  • The frontend output receives "[tag:typo3]" and could do its own content element magic, other services accessing the database could also use the parseable representation.

The corresponding event listener class:

EXT:MyExtension/Classes/EventListener/TransformListener.php
<?php
declare(strict_types=1);

namespace MyVendor\MyExtension\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;

class TransformListener
{
    /**
     * Transforms the current value the RTE delivered into a value that is stored (persisted) in the database.
     */
    #[AsEventListener('rtehtmlparser/modify-data-for-persistence')]
    public function modifyPersistence(AfterTransformTextForPersistenceEvent $event): void
    {
        $value = $event->getHtmlContent();
        $value = str_replace('TYPO3', '[tag:typo3]', $value);
        $event->setHtmlContent($value);
    }

    /**
     * Transforms the current persisted value into something the RTE can display
     */
    #[AsEventListener('rtehtmlparser/modify-data-for-richtexteditor')]
    public function modifyRichTextEditor(AfterTransformTextForRichTextEditorEvent $event): void
    {
        $value = $event->getHtmlContent();
        $value = str_replace('[tag:typo3]', 'TYPO3', $value);
        $event->setHtmlContent($value);
    }
}
Copied!

Impact

Using the new PSR-14 events

  • \TYPO3\CMS\Core\Html\Event\BeforeTransformTextForPersistenceEvent
  • \TYPO3\CMS\Core\Html\Event\AfterTransformTextForPersistenceEvent
  • \TYPO3\CMS\Core\Html\Event\BeforeTransformTextForRichTextEditorEvent
  • \TYPO3\CMS\Core\Html\Event\AfterTransformTextForRichTextEditorEvent

allows to apply custom transformations for database <-> RTE content transformations.

Feature: #104311 - Auto created system TCA columns

See forge#104311

Description

Introduction

There are various TCA table ctrl settings that define fields used to enable certain TYPO3 table capabilities and to specify the database column to store this row state.

An example is $GLOBALS['TCA']['ctrl']['enablecolumns']['starttime'] = 'starttime' , which makes the table "start time aware", resulting in the automatic exclusion of a record if the given start time is in the future, when rendered in the frontend.

Such ctrl settings require TCA columns definitions. Default definitions of such columns are now automatically added to TCA if not manually configured. Extension developers can now remove and avoid a significant amount of boilerplate field definitions in columns and rely on TYPO3 Core to create them automatically. Note the Core does not automatically add such columns to TCA types or palettes definitions: Developers still need to place them, to show the columns when editing record rows, and need to add according access permissions.

Let us have a quick look on what happened within TCA and its surrounding code lately, to see how this feature embeds within the general TYPO3 Core strategy in this area and why the above feature has been implemented at this point in time:

TCA has always been a central cornerstone of TYPO3. The TYPO3 Core strives to maintain this central part while simplifying and streamlining less desirable details.

TYPO3 version v12 aimed to simplify single column definitions by implementing new column types like file, category, email, and more. These are much easier to understand and require far fewer single property definitions than previous solutions. With this in place, auto-creation of database column definitions derived from TCA has been established with TYPO3 v13, making the manual definition of database table schemas in ext_tables.sql largely unnecessary. Additionally, an object-oriented approach called TcaSchema has been introduced to harmonize and simplify information retrieval from TCA.

With the step described in this document - the auto-creation of TCA columns from ctrl properties - the amount of manual boilerplate definitions is significantly reduced, and the TYPO3 Core gains more control over these columns to harmonize these fields throughout the system. Note that the TYPO3 Core has not yet altered the structure of TCA types and palettes. This will be one of the next steps in this area, but details have not been decided upon yet.

All these steps streamline TCA and its surrounding areas, simplify the system, and reduce the amount of details developers need to be aware of when defining their own tables and fields.

This document details the "column auto-creation from 'ctrl' fields" feature: It first lists all affected settings with their derived default definitions. It concludes with a section relevant for instances that still need to override certain defaults of these columns by explaining the order of files and classes involved in building TCA and the available options to change defaults and where to place these changes.

Auto-created columns from 'ctrl'

The configuration settings below enable single table capabilities. Their values are a database column name responsible for storing the row data of the capability.

If a setting is defined in a "base" TCA table file (Configuration/TCA, not in Configuration/TCA/Overrides), the TYPO3 Core will add default columns definition for this field name if no definition exists in a base file.

$GLOBALS['TCA']['ctrl']['enablecolumns']['disabled']

This setting makes database table rows "disable aware": A row with this flag being set to 1 is not rendered in the frontend to casual website users.

Typical usage:

'ctrl' => [
    'enablecolumns' => [
        'disabled' => 'disabled',
    ],
],
Copied!

Default configuration added by the TYPO3 Core:

'disabled' => [
    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.enabled',
    'exclude' => true,
    'config' => [
        'type' => 'check',
        'renderType' => 'checkboxToggle',
        'default' => 0,
        'items' => [
            [
                'label' => '',
                'invertStateDisplay' => true,
            ],
        ],
    ],
],
Copied!

$GLOBALS['TCA']['ctrl']['enablecolumns']['starttime']

This setting makes database table rows "starttime aware": A row having a start time in the future is not rendered in the frontend.

Typical usage:

'ctrl' => [
    'enablecolumns' => [
        'starttime' => 'starttime',
    ],
],
Copied!

Default configuration added by the TYPO3 Core:

'starttime' => [
    'exclude' => true,
    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
    'config' => [
        'type' => 'datetime',
        'default' => 0,
    ],
],
Copied!

$GLOBALS['TCA']['ctrl']['enablecolumns']['endtime']

This setting makes database table rows "endtime aware": A row having an end time in the past is not rendered in the frontend.

Typical usage:

'ctrl' => [
    'enablecolumns' => [
        'endtime' => 'endtime',
    ],
],
Copied!

Default configuration added by the TYPO3 Core:

'endtime' => [
    'exclude' => true,
    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
    'config' => [
        'type' => 'datetime',
        'default' => 0,
        'range' => [
            'upper' => mktime(0, 0, 0, 1, 1, 2106),
        ],
    ],
],
Copied!

$GLOBALS['TCA']['ctrl']['enablecolumns']['fe_group']

This setting makes database table rows "frontend group aware": A row can be defined to be shown only to frontend users who are a member of selected groups.

Typical usage:

'ctrl' => [
    'enablecolumns' => [
        'fe_group' => 'fe_group',
    ],
],
Copied!

Default configuration added by the TYPO3 Core:

'fe_group' => [
    'exclude' => true,
    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.fe_group',
    'config' => [
        'type' => 'select',
        'renderType' => 'selectMultipleSideBySide',
        'size' => 5,
        'maxitems' => 20,
        'items' => [
            [
                'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hide_at_login',
                'value' => -1,
            ],
            [
                'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.any_login',
                'value' => -2,
            ],
            [
                'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.usergroups',
                'value' => '--div--',
            ],
        ],
        'exclusiveKeys' => '-1,-2',
        'foreign_table' => 'fe_groups',
    ],
],
Copied!

$GLOBALS['TCA']['ctrl']['editlock']

This setting makes database table rows "backend lock aware": A row with this being flag enabled can only be edited by backend administrators.

Typical usage:

'ctrl' => [
    'editlock' => 'editlock',
],
Copied!

Default configuration added by the TYPO3 Core:

'endtime' => [
    'displayCond' => 'HIDE_FOR_NON_ADMINS',
    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:editlock',
    'config' => [
        'type' => 'check',
        'renderType' => 'checkboxToggle',
    ],
],
Copied!

$GLOBALS['TCA']['ctrl']['descriptionColumn']

This setting makes database table rows "description aware": Backend editors have a database field to add row specific notes.

Typical usage:

'ctrl' => [
    'descriptionColumn' => 'description',
],
Copied!

Default configuration added by the TYPO3 Core:

'description' => [
    'exclude' => true,
    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.description',
    'config' => [
        'type' => 'text',
        'rows' => 5,
        'cols' => 30,
        'max' => 2000,
    ],
],
Copied!

$GLOBALS['TCA']['ctrl']['languageField'] and transOrigPointerField`

These setting make database table rows "localization aware": Backend editors can create localized versions of a record. Note when languageField is set, and transOrigPointerField is not, the TYPO3 Core will automatically set transOrigPointerField to l10n_parent since both fields must be always set in combination.

Typical usage:

'ctrl' => [
    'languageField' => 'sys_language_uid',
    'transOrigPointerField' => 'l10n_parent',
],
Copied!

Default configuration added by the TYPO3 Core, note string $table corresponds to the current table name.

'sys_language_uid' => [
    'exclude' => true,
    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
    'config' => [
        'type' => 'language',
    ],
],
'l10n_parent' => [
    'displayCond' => 'FIELD:sys_language_uid:>:0',
    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
    'config' => [
        'type' => 'select',
        'renderType' => 'selectSingle',
        'items' => [
            [
                'label' => '',
                'value' => 0,
            ],
        ],
        'foreign_table' => $table,
        'foreign_table_where' => 'AND {#' . $table . '}.{#pid}=###CURRENT_PID### AND {#' . $table . '}.{#' . $languageFieldName . '} IN (-1,0)',
        'default' => 0,
    ],
],
Copied!

$GLOBALS['TCA']['ctrl']['transOrigDiffSourceField']

This setting makes database table rows "parent language record change aware": Backend editors can have an indicator when the parent column has been changed.

Typical usage:

'ctrl' => [
    'transOrigDiffSourceField' = 'l10n_diffsource',
],
Copied!

Default configuration added by the TYPO3 Core:

'l10n_diffsource' => [
    'config' => [
        'type' => 'passthrough',
        'default' => '',
    ],
],
Copied!

$GLOBALS['TCA']['ctrl']['translationSource']

This setting makes database table rows "parent language source aware" to determine the difference between "connected mode" and "free mode".

Typical usage:

'ctrl' => [
    'translationSource' = 'l10n_source',
],
Copied!

Default configuration added by the TYPO3 Core:

'l10n_source' => [
    'config' => [
        'type' => 'passthrough',
        'default' => '',
    ],
],
Copied!

Load order when building TCA

To understand if and when TCA column auto-creation from ctrl definitions kicks in, it is important to have an overview of the order of the single loading steps:

  1. Load single files from extension Configuration/TCA files
  2. NEW - Enrich columns from ctrl settings
  3. Load single files from extension Configuration/TCA/Overrides files
  4. Apply TCA migrations
  5. Apply TCA preparations

As a result of this strategy, columns fields are not auto-created, when a ctrl capability is added in a Configuration/TCA/Overrides file, and not in a Configuration/TCA "base" file. In general, such capabilities should be set in base files only: Adding them at a later point - for example in a different extension - is brittle and there is a risk the main extension can not deal with such an added capability properly.

Overriding definitions from auto-created TCA columns

I most cases, developers do not need to change definitions of columns auto-created by the TYPO3 Core. In general, it is advisable to not actively do this. Developers who still want to change detail properties of such columns should generally stick to "display" related details only.

There are two options to have own definitions: When a column is already defined in a "base" TCA file (Configuration/TCA), the TYPO3 Core will not override it. Alternatively, a developer can decide to let the TYPO3 Core auto-create a column, to then override single properties in Configuration/TCA/Overrides files.

As example, "base" pages file defines this (step 1 above):

'ctrl' => [
    'enablecolumns' => [
        'disabled' => 'disabled',
    ],
],
Copied!

The TYPO3 Core thus creates this columns definition (step 2 above):

'columns' => [
    'disabled' => [
        'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.enabled',
        'exclude' => true,
        'config' => [
            'type' => 'check',
            'renderType' => 'checkboxToggle',
            'default' => 0,
            'items' => [
                [
                    'label' => '',
                    'invertStateDisplay' => true,
                ],
            ],
        ],
    ],
],
Copied!

When an editor creates a new page, it should be "disabled" by default to avoid having a new page online in the website before it is set up completely. A Configuration/TCA/Overrides/pages.php file does this:

<?php
// New pages are disabled by default
$GLOBALS['TCA']['pages']['columns']['hidden']['config']['default'] = 1;
Copied!

Impact

Extension developers can typically remove columns definitions of all the above fields and rely on TYPO3 Core creating them with a good default definition.

It is only required to define the desired table capabilities in ctrl with its field names, and the system will create the according columns definitions automatically.

Feature: #104321 - Allow handling of argument mapping exceptions in ActionController

See forge#104321

Description

A new method handleArgumentMappingExceptions has been introduced in Extbase \TYPO3\CMS\Extbase\Mvc\Controller\ActionController to improve handling of exceptions that occur during argument mapping.

The new method supports optional handling of the following exceptions:

  • \TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException , which occurs, when a given object UID can not be resolved to an existing record.
  • \TYPO3\CMS\Extbase\Mvc\Controller\Exception\RequiredArgumentMissingException , which occurs, when a required action argument is missing.

Handling of the exceptions can be enabled globally with the following TypoScript configuration.

  • config.tx_extbase.mvc.showPageNotFoundIfTargetNotFoundException = 1
  • config.tx_extbase.mvc.showPageNotFoundIfRequiredArgumentIsMissingException = 1

The exception handling can also be configured on extension level with the following TypoScript configuration.

  • plugin.tx_yourextension.mvc.showPageNotFoundIfTargetNotFoundException = 1
  • plugin.tx_yourextension.mvc.showPageNotFoundIfRequiredArgumentIsMissingException = 1
  • plugin.tx_yourextension_plugin1.mvc.showPageNotFoundIfTargetNotFoundException = 1
  • plugin.tx_yourextension_plugin1.mvc.showPageNotFoundIfRequiredArgumentIsMissingException = 1

By default, these options are set to 0, which will lead to exceptions being thrown (and would lead to errors, if not caught). This is the current behavior of TYPO3.

When setting one of these values to 1, the configured exceptions will not be thrown. Instead, a pageNotFound response is propagated, resulting in a 404 error being shown.

Additionally, extension authors can extend or override the method handleArgumentMappingExceptions in relevant Controllers in order to implement custom argument mapping exception handling.

Impact

Extension authors can now handle exceptions in implementations of a ActionController , which are thrown during argument mapping.

Feature: #104451 - Redis backends support for key prefixing

See forge#104451

Description

It is now possible to add a dedicated key prefix for all invocations of a Redis cache or session backend. This allows to use the same Redis database for multiple caches or even for multiple TYPO3 instances if the provided prefix is unique.

Possible use cases are:

  • Using Redis caching for multiple caches, if only one Redis database is available
  • Pre-fill caches upon deployments using a new prefix (zero downtime deployments)
additional.php example for using Redis as session backend
$GLOBALS['TYPO3_CONF_VARS']['SYS']['session']['BE'] = [
    'backend' => \TYPO3\CMS\Core\Session\Backend\RedisSessionBackend::class,
    'options' => [
        'hostname' => 'redis',
        'database' => '11',
        'compression' => true,
        'keyPrefix' => 'be_sessions_',
    ],
];
$GLOBALS['TYPO3_CONF_VARS']['SYS']['session']['FE'] = [
    'backend' => \TYPO3\CMS\Core\Session\Backend\RedisSessionBackend::class,
    'options' => [
        'hostname' => 'redis',
        'database' => '11',
        'compression' => true,
        'keyPrefix' => 'fe_sessions_',
        'has_anonymous' => true,
    ],
];
Copied!
additional.php example for pages cache
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['pages'] = [
    'backend' => \TYPO3\CMS\Core\Cache\Backend\RedisBackend::class,
    'options' => [
        'hostname' => 'redis',
        'database' => 11,
        'compression' => true,
        'keyPrefix' => 'pages_';
    ],
];

$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['rootline'] = [
    'backend' => \TYPO3\CMS\Core\Cache\Backend\RedisBackend::class,
    'options' => [
        'hostname' => 'redis',
        'database' => 11,
        'compression' => true,
        'keyPrefix' => 'rootline_';
    ],
];
Copied!

Impact

The new feature allows to use the same Redis database for multiple caches or even for multiple TYPO3 instances while having no impact on existing configuration.

Feature: #104482 - Add if() support to ExpressionBuilder

See forge#104482

Description

The TYPO3 \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder provides a new method to phrase "if-then-else" expressions. Those are translated into IF or CASE statements depending on the used database engine.

ExpressionBuilder::if()

Creates an IF-THEN-ELSE expression.

Method signature
/**
 * Creates IF-THEN-ELSE expression construct compatible with all supported database vendors.
 * No automatic quoting or escaping is done, which allows to build up nested expression statements.
 *
 * **Example:**
 * ```
 * $queryBuilder
 *   ->selectLiteral(
 *     $queryBuilder->expr()->if(
 *       $queryBuilder->expr()->eq('hidden', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)),
 *       $queryBuilder->quote('page-is-visible'),
 *       $queryBuilder->quote('page-is-not-visible'),
 *       'result_field_name'
 *     ),
 *   )
 *   ->from('pages');
 * ```
 *
 * **Result with MySQL:**
 * ```
 * SELECT (IF(`hidden` = 0, 'page-is-visible', 'page-is-not-visible')) AS `result_field_name` FROM `pages`
 * ```
 */
public function if(
    CompositeExpression|\Doctrine\DBAL\Query\Expression\CompositeExpression|\Stringable|string $condition,
    \Stringable|string $truePart,
    \Stringable|string $falsePart,
    \Stringable|string|null $as = null
): string {
    $platform = $this->connection->getDatabasePlatform();
    $pattern = match (true) {
        $platform instanceof DoctrineSQLitePlatform => 'IIF(%s, %s, %s)',
        $platform instanceof DoctrinePostgreSQLPlatform => 'CASE WHEN %s THEN %s ELSE %s END',
        $platform instanceof DoctrineMariaDBPlatform,
        $platform instanceof DoctrineMySQLPlatform => 'IF(%s, %s, %s)',
        default => throw new \RuntimeException(
            sprintf('Platform "%s" not supported for "%s"', $platform::class, __METHOD__),
            1721806463
        )
    };
    $expression = sprintf($pattern, $condition, $truePart, $falsePart);
    if ($as !== null) {
        $expression = $this->as(sprintf('(%s)', $expression), $as);
    }
    return $expression;
}
Copied!

Impact

Extension authors can use the new expression method to build more advanced queries without the requirement to deal with the correct implementation for all supported database vendors.

Feature: #104493 - Add castText() expression support to ExpressionBuilder

See forge#104493

Description

The TYPO3 \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder provides a new method to cast expression results to text like datatypes. This is done to large VARCHAR/CHAR types using the CAST/CONVERT or similar methods based on the used database engine.

ExpressionBuilder::castText()

Creates a CAST expression.

Method signature
/**
 * Creates a cast for the `$expression` result to a text datatype depending on the database management system.
 *
 * Note that for MySQL/MariaDB the corresponding CHAR/VARCHAR types are used with a length of `16383` reflecting
 * 65554 bytes with `utf8mb4` and working with default `max_packet_size=16KB`. For SQLite and PostgreSQL the text
 * type conversion is used.
 *
 * Main purpose of this expression is to use it in a expression chain to convert non-text values to text in chain
 * with other expressions, for example to {@see self::concat()} multiple values or to ensure the type,  within
 * `UNION/UNION ALL` query parts for example in recursive `Common Table Expressions` parts.
 *
 * This is a replacement for {@see QueryBuilder::castFieldToTextType()} with minor adjustments like enforcing and
 * limiting the size to a fixed variant to be more usable in sensible areas like `Common Table Expressions`.
 *
 * Alternatively the {@see self::castVarchar()} can be used which allows for dynamic length setting per expression
 * call.
 *
 * **Example:**
 * ```
 * $queryBuilder->expr()->castText(
 *    '(' . '1 * 10' . ')',
 *    'virtual_field'
 * );
 * ```
 *
 * **Result with MySQL:**
 * ```
 * CAST((1 * 10) AS CHAR(16383) AS `virtual_field`
 * ```
 *
 * @throws \RuntimeException when used with a unsupported platform.
 */
public function castText(CompositeExpression|\Stringable|string $expression, string $asIdentifier = ''): string
{
    $platform = $this->connection->getDatabasePlatform();
    if ($platform instanceof DoctrinePostgreSQLPlatform) {
        return $this->as(sprintf('((%s)::%s)', $expression, 'text'), $asIdentifier);
    }
    if ($platform instanceof DoctrineSQLitePlatform) {
        return $this->as(sprintf('(CAST((%s) AS %s))', $expression, 'TEXT'), $asIdentifier);
    }
    if ($platform instanceof DoctrineMariaDBPlatform) {
        // 16383 is the maximum for a VARCHAR field with `utf8mb4`
        return $this->as(sprintf('(CAST((%s) AS %s(%s)))', $expression, 'VARCHAR', '16383'), $asIdentifier);
    }
    if ($platform instanceof DoctrineMySQLPlatform) {
        // 16383 is the maximum for a VARCHAR field with `utf8mb4`
        return $this->as(sprintf('(CAST((%s) AS %s(%s)))', $expression, 'CHAR', '16383'), $asIdentifier);
    }
    throw new \RuntimeException(
        sprintf(
            '%s is not implemented for the used database platform "%s", yet!',
            __METHOD__,
            get_class($this->connection->getDatabasePlatform())
        ),
        1722105672
    );
}
Copied!

Impact

Extension authors can use the new expression method to build more advanced queries without the requirement to deal with the correct implementation for all supported database vendors - at least to some grade.

Feature: #104526 - Provide validators for PSR-7 UploadedFile objects in Extbase

See forge#104526

Description

4 new Extbase validators have been added to allow common validation tasks of a PSR-7 \TYPO3\CMS\Core\Http\UploadedFile object or an \TYPO3\CMS\Extbase\Persistence\ObjectStorage containing PSR-7 UploadedFile objects.

Note, that the new validators can only be applied to the TYPO3 implementation of the PSR-7 \Psr\Http\Message\UploadedFileInterface because they validate the uploaded files before it has been moved.

Custom implementations of the \UploadedFileInterface must continue to implement their own validators.

FileNameValidator

This validator ensures, that files with PHP executable file extensions can not be uploaded. The validator has no options.

FileSizeValidator

This validator can be used to validate an uploaded file against a given minimum and maximum file size.

Validator options:

  • minimum - The minimum size as string (e.g. 100K)
  • maximum - The maximum size as string (e.g. 100K)

MimeTypeValidator

This validator can be used to validate an uploaded file against a given set of accepted MIME types. The validator additionally verifies, that the given file extension of the uploaded file matches allowed file extensions for the detected mime type.

Validator options:

  • allowedMimeTypes - An array of allowed MIME types
  • ignoreFileExtensionCheck - If set to "true", it is checked, the file extension check is disabled

ImageDimensionsValidator

This validator can be used to validate an uploaded image for given image dimensions. The validator must only be used, when it is ensured, that the uploaded file is an image (e.g. by validating the MIME type).

Validator options:

  • width - Fixed width of the image as integer
  • height - Fixed height of the image as integer
  • minWidth - Minimum width of the image as integer. Default is 0
  • maxWidth - Maximum width of the image as integer. Default is PHP_INT_MAX
  • minHeight - Minimum height of the image as integer. Default is 0
  • maxHeight - Maximum height of the image as integer. Default is PHP_INT_MAX

Impact

TYPO3 extension autors can now use the new validators to validate a given UploadedFile object.

Feature: #104631 - Add UNION Clause support to the QueryBuilder

See forge#104631

Description

The UNION clause is used to combine the result sets of two or more SELECT statements, which all database vendors support, each with their own specific variations.

However, there is a commonly shared subset that works across all of them:

SELECT column_name(s) FROM table1
WHERE ...

UNION <ALL | DISTINCT>

SELECT column_name(s) FROM table2
WHERE ...

ORDER BY ...
LIMIT x OFFSET y
Copied!

with shared requirements:

  • Each SELECT must return the same fields in number, naming and order.
  • Each SELECT must not have ORDER BY, expect MySQL allowing it to be used as sub query expression encapsulated in parentheses.

Generic UNION clause support has been contributed to Doctrine DBAL and is included since Release 4.1.0 which introduces two new API method on the \QueryBuilder:

  • union(string|QueryBuilder $part) to create first UNION query part
  • addUnion(string|QueryBuilder $part, UnionType $type = UnionType::DISTINCT) to add additional UNION (ALL|DISTINCT) query parts with the selected union query type.

TYPO3 decorates the Doctrine DBAL \QueryBuilder to provide for most API methods automatic quoting of identifiers and values and to apply database restrictions automatically for SELECT queries.

The Doctrine DBAL API has been adopted now to provide the same surface for the TYPO3 \TYPO3\CMS\Core\Database\Query\QueryBuilder and the intermediate \TYPO3\CMS\Core\Database\Query\ConcreteQueryBuilder to make it easier to create UNION clause queries. The API on both methods allows to provide dedicated QueryBuilder instances or direct queries as strings in case it is needed.

In queries containing subqueries, only named placeholders (such as :username) can be used and must be registered on the outermost QueryBuilder object, similar to advanced query creation with SUB QUERIES.

UnionType::DISTINCT and UnionType::ALL

Each subsequent part needs to be defined either as UNION DISTINCT or UNION ALL which could have not so obvious effects.

For example, using UNION ALL for all parts in between except for the last one would generate larger result sets first, but discards duplicates when adding the last result set. On the other side, using UNION ALL tells the query optimizer not to scan for duplicates and remove them at all which can be a performance improvement - if you can deal with duplicates it can be ensured that each part does not produce same outputs.

Example: Compose a UNION clause query

Custom service class using a UNION query to retrieve data.
use Doctrine\DBAL\Query\UnionType;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;

final readonly class MyService {
  public function __construct(
    private ConnectionPool $connectionPool,
  ) {}

  public function executeUnionQuery(
    int $pageIdOne,
    int $pageIdTwo,
  ): ?array {
    $connection = $this->connectionPool->getConnectionForTable('pages');
    $unionQueryBuilder = $connection->createQueryBuilder();
    $firstPartQueryBuilder = $connection->createQueryBuilder();
    $secondPartQueryBuilder = $connection->createQueryBuilder();
    // removing automatic TYPO3 restriction for the sake of the example
    // to match the PLAIN SQL example when executed. Not removing them
    // will generate corresponding restriction SQL code for each part.
    $firstPartQueryBuilder->getRestrictions()->removeAll();
    $secondPartQueryBuilder->getRestrictions()->removeAll();
    $expr = $unionQueryBuilder->expr();

    $firstPartQueryBuilder
      // The query parts **must** have the same column counts, and these
      // columns **must** have compatible types
      ->select('uid', 'pid', 'title')
      ->from('pages')
      ->where(
        $expr->eq(
          'pages.uid',
          // !!! Ensure to use most outer / top / main QueryBuilder
          //   instance for creating parameters and the complete
          //   query can be executed in the end.
          $unionQueryBuilder->createNamedParameter($pageIdOne, Connection::PARAM_INT),
        )
      );
    $secondPartQueryBuilder
      ->select('uid', 'pid', 'title')
      ->from('pages')
      ->where(
        $expr->eq(
          'pages.uid',
          // !!! Ensure to use most outer / top / main QueryBuilder instance
          $unionQueryBuilder->createNamedParameter($pageIdTwo, Connection::PARAM_INT),
        )
      );

    // Set first and second union part to the main (union)
    // QueryBuilder and return the retrieved rows.
    return $unionQueryBuilder
      ->union($firstPartQueryBuilder)
      ->addUnion($secondPartQueryBuilder, UnionType::DISTINCT)
      ->orderBy('uid', 'ASC')
      ->executeQuery()
      ->fetchAllAssociative();
  }
}
Copied!

This would create the following query for MySQL with $pageIdOne = 100 and $pageIdTwo = 10:

    (SELECT `uid`, `pid`, `title` FROM pages WHERE `pages`.`uid` = 100)
UNION
    (SELECT `uid`, `pid`, `title` FROM pages WHERE `pages`.`uid` = 10)
ORDER BY `uid` ASC
Copied!

Impact

Extension authors can use the new QueryBuilder methods to build more advanced queries.

Feature: #104655 - Add console command to mark upgrade wizards as undone

See forge#104655

Description

A new CLI command typo3 upgrade:mark:undone has been introduced. It allows to mark a previously executed upgrade wizard as "undone", so it can be run again.

This makes the existing functionality from the install tool also available on CLI.

Impact

You can now mark an already executed upgrade wizard as "undone" with typo3 upgrade:mark:undone <wizardIdentifier>

Feature: #104773 - Generic view factory

See forge#104773

Description

Class \TYPO3\CMS\Core\View\ViewFactoryInterface has been added as a generic view interface to create views that return an instance of \TYPO3\CMS\Core\View\ViewInterface . This implements the "V" of "MVC" in a generic way and is used throughout the TYPO3 Core.

This obsoletes all custom view instance creation approaches within the TYPO3 Core and within TYPO3 extensions. Extensions should retrieve view instances based on this ViewFactoryInterface .

Impact

Instances of this interface should be injected using dependency injection. The default injected implementation is a Fluid view, and can be reconfigured using dependency injection configuration, typically in a Services.yaml file.

A casual example to create and render a view looks like this.

use TYPO3\CMS\Core\View\ViewFactoryInterface;

class MyController
{
    public function __construct(
        private readonly ViewFactoryInterface $viewFactory,
    ) {}

    public function myAction(ServerRequestInterface $request): string
    {
        $viewFactoryData = new ViewFactoryData(
            templateRootPaths: ['EXT:myExt/Resources/Private/Templates'],
            partialRootPaths: ['EXT:myExt/Resources/Private/Partials'],
            layoutRootPaths: ['EXT:myExt/Resources/Private/Layouts'],
            request: $request,
        );
        $view = $this->viewFactory->create($viewFactoryData);
        $view->assign('myData', 'myData');
        return $view->render('path/to/template');
    }
}
Copied!

Note Extbase-based extensions create a view instance based on this factory by default and are accessible as $this->view.

Feature: #104789 - Support for contentArgumentName in AbstractViewHelper

See forge#104789

Description

ContentArgumentName has been a feature on Fluid ViewHelpers for some time now. It allows ViewHelpers to link a ViewHelper argument to the children of the ViewHelper name. As a result, an input value can either be specified as an argument or as the ViewHelper's children, leading to the same result.

Example:

<!-- Tag syntax -->
<f:format.json value="{data}" />
<f:format.json>{data}</f:format.json>

<!-- Inline syntax -->
{f:format.json(value: data)}
{data -> f:format.json()}
Copied!

Previously, this feature was only available to ViewHelpers using the trait \CompileWithContentArgumentAndRenderStatic . It is now available to all ViewHelpers since it has been integrated into the \AbstractViewHelper . The trait is no longer necessary.

To use the new feature, all the ViewHelper implementation needs to do is to define a method getContentArgumentName() which returns the name of the argument to be linked to the ViewHelper's children:

Example:

public function getContentArgumentName(): string
{
    return 'value';
}
Copied!

Impact

ViewHelpers using the trait \CompileWithContentArgumentAndRenderStatic should be migrated to the new feature.

\CompileWithContentArgumentAndRenderStatic will continue to work in Fluid v4, but will log a deprecation level error message. It will be removed in Fluid v5.

Feature: #104794 - Introduce Site Settings Editor

See forge#104794

Description

A new Site Settings editor has been introduced that allows to configure per-site settings in file:config/sites/*/settings.yaml.

The new backend module Site Management > Settings provides an overview of sites which offer configurable settings and makes them editable based on Site Set provided Settings Definitions.

The editor shows a list of settings categories and respective settings. It will persist all settings into config/sites/*/settings.yaml. The module will only persist settings that deviate from the site-scoped default value. That means it will only change the minimal difference to the settings set defined by the active sets for the respective site.

The backend module is currently available for administrators only, but will likely be extended to be made available for editors in future.

Anonymous (undefined) site settings – as supported since TYPO3 v10 – will not be made editable, but will be preserved as-is when persisting changes through the settings editor.

Categorization

Sets can define categories and settings definitions can reference the category by ID in order to assign a setting to a specific category. These definitions are placed in settings.definitions.yaml next to the site set file config.yaml.

EXT:my_extension/Configuration/Sets/MySet/settings.definitions.yaml
categories:
  myCategory:
    label: 'My Category'

settings:
  my.example.setting:
    label: 'My example setting'
    category: myCategory
    type: string
    default: ''

  my.seoRelevantSetting:
    label: 'My SEO relevant setting'
    # show in EXT:seo provided category "seo"
    category: seo
    type: int
    default: 5
Copied!

The settings ordering is defined through the loading order of extensions and by the order of categories. Uncategorized settings will be grouped into a virtual "Other" category and shown at the end of the list of available settings.

Readonly

Site settings can be made readonly. They can be overridden only by editing the config/sites/my-site/settings.yaml but not from within the editor.

The value of the field is displayed in a readonly field in the settings editor.

EXT:my_extension/Configuration/Sets/MySet/settings.definitions.yaml
settings:
  my.readonlySetting:
    label: 'My readonly setting'
    type: int
    default: 5
    readonly: true
Copied!

Enumeration of strings

Site settings can provide possible options via the enum specifier, that will be selectable in the editor GUI:

EXT:my_extension/Configuration/Sets/MySet/settings.definitions.yaml
settings:
  my.enumSetting:
    label: 'My setting with options'
    type: string
    enum:
      valueA: 'Label of value A'
      valueB: 'Label of value B'
Copied!

Impact

Site-scoped settings will most likely be the place to configure site-wide configuration, which was previously only possible to modify via Constant Editor, modifying TypoScript constants.

It is recommended to use site-sets and their UI configuration in favor of TypoScript Constants in the future.

Feature: #104814 - Automatically add system fields to content types

See forge#104814

Description

All content elements types ( CType) are usually equipped with the same system fields (language, hidden, etc.) - see also Feature: #104311 - Auto created system TCA columns. Adding them to the editor form has previously been done by adding those fields to each content types' showitem definition.

In the effort to simplify content element creation, to unify the available fields and position for the editor and to finally reduce configuration effort for integrators, those system fields are now added automatically based on the ctrl definition.

The following tabs / palettes are now added automatically:

  • The General tab with the general palette at the very beginning
  • The Language tab with the language palette after custom fields
  • The Access tab with the hidden and access palettes
  • The Notes tab with the rowDescription field

As mentioned, in case one of those palettes has been changed to no longer include the corresponding system fields, those fields are added individually depending on their definition in the table's ctrl section:

  • The ctrl[type] field (usually CType)
  • The colPos field
  • The ctrl[languageField] (usually sys_language_uid)
  • The ctrl[editlock] field (usually editlock)
  • The ctrl[enablecolumns][disabled] field (usually hidden)
  • The ctrl[enablecolumns][starttime] field (usually starttime)
  • The ctrl[enablecolumns][endtime] field (usually endtime)
  • The ctrl[enablecolumns][fe_group] field (usually fe_group)
  • The ctrl[descriptionColumn] field (usually rowDescription)

By default, all custom fields - the ones still defined in showitem - are added after the general palette and are therefore added to the General tab, unless a custom tab (e.g. Plugin, or Categories) is defined in between. It is also possible to start with a custom tab by defining a --div-- as the first item in the showitem. In this case, the General tab will be omitted.

All those system fields, which are added based on the ctrl section are also automatically removed from any custom palette and from the customized type's showitem definition.

If the content element defines the Extended tab, it will be inserted at the end, including all fields added to the type via API methods, without specifying a position, e.g. via ExtensionManagementUtility::addToAllTcaTypes().

Impact

Creating content elements has been simplified by removing the need to define the system fields for each element again and again. This shrinks down a content element's showitem to just the element specific fields.

A usual migration will therefore look like the following:

Before:

'slider' => [
    'showitem' => '
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
            --palette--;;general,
            --palette--;;headers,
            slider_elements,
            bodytext;LLL:EXT:awesome_slider/Resources/Private/Language/locallang_ttc.xlf:bodytext.ALT.slider_description,
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:appearance,
            --palette--;;frames,
            --palette--;;appearanceLinks,
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
            --palette--;;language,
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
            --palette--;;hidden,
            --palette--;;access,
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories,
            categories,
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes,
            rowDescription,
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
    ',
],
Copied!

After:

'slider' => [
    'showitem' => '
            --palette--;;headers,
            slider_elements,
            bodytext;LLL:EXT:awesome_slider/Resources/Private/Language/locallang_ttc.xlf:bodytext.ALT.slider_description,
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories,
            categories,
        --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
    ',
],
Copied!

Since all fields, palettes and tabs, which are defined in the showitem are added after the general palette, also the Categories tab - if defined - is displayed before the system tabs / fields. The only special case is the Extended tab, which is always added at the end.

Feature: #104832 - PSR-14 Event to alter the results of PageTreeRepository

See forge#104832

Description

Until TYPO3 v9, it was possible to alter the rendering of one of TYPO3's superpowers — the page tree in the TYPO3 Backend User Interface.

This was done via a "Hook", but was removed due to the migration towards an SVG-based tree rendering.

As the Page Tree Rendering has evolved, and the hook system has been replaced in favor of PSR-14 Events, a new event \TYPO3\CMS\Backend\Tree\Repository\AfterRawPageRowPreparedEvent has been introduced.

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration, will remove any children for displaying for the page with the UID 123:

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Backend\Tree\Repository\AfterRawPageRowPreparedEvent;

final class MyEventListener
{
    #[AsEventListener]
    public function __invoke(AfterRawPageRowPreparedEvent $event): void
    {
        $rawPage = $event->getRawPage();
        if ((int)$rawPage['uid'] === 123) {
            $rawPage['_children'] = [];
            $event->setRawPage($rawPage);
        }
    }
}
Copied!

Impact

Using the new PSR-14 event, it is now possible to modify the populated page properties or its children records.

Feature: #104844 - Add widgets for listing all the sys_notes inside the TYPO3 system

See forge#104844

Description

To make it easier for TYPO3 users to view all the internal notes (EXT:sys_note) in their TYPO3 system, TYPO3 now offers dashboard widgets for each internal note type. The backend user must have access to the sys_note table and view permission to the page where the record is located.

Impact

TYPO3 users who have access to the Dashboard module and are granted access to the new widgets can now add and use these widgets.

Feature: #104846 - Custom field transformations for new records

See forge#104846

Description

With forge#103783 the new \TYPO3\CMS\Core\Domain\Record object has been introduced, which is an object representing a raw database record based on TCA and is usually used in the frontend (via Fluid Templates).

Since Feature: #103581 - Automatically transform TCA field values for record objects the properties of those Record objects are transformed / expanded from their raw database value into "rich-flavored" values. Those values might be relations to e.g. Record , FileReference , Folder or \DateTimeImmutable objects.

However, TYPO3 does not know about custom field meanings, e.g. latitude and longitude information, stored in an input field or user settings stored as JSON in an TCA type json field. For such custom needs, the new PSR-14 \TYPO3\CMS\Core\Domain\Event\RecordCreationEvent has been introduced. It is dispatched right before a Record is created and therefore allows to fully manipulate any property, even the ones already transformed by TYPO3.

The new event is stoppable (implementing \StoppableEventInterface ), which allows listeners to actually create a Record object completely on their own.

The new event features the following methods:

  • setRecord() - Manually adds a RecordInterface object (stops the event propagation)
  • hasProperty() - Whether a property exists
  • setProperty() - Add or overwrite a property
  • setProperties() - Set properties for the RecordInterface
  • unsetProperty() - Unset a single property
  • getProperty() - Get the value for a single property
  • getProperties() - Get all properties
  • getRawRecord() - Get the RawRecord object
  • getSystemProperties() - Get the calculated SystemProperties
  • getContext() - Get the current Context (used to fetch the raw database row)
  • isPropagationStopped() - Whether the event propagation is stopped

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration, creates a Coordinates object based on the field value of the coordinates field for the custom maps content type.

final class RecordCreationEventListener
{
    #[AsEventListener]
    public function __invoke(\TYPO3\CMS\Core\Domain\Event\RecordCreationEvent $event): void
    {
        $rawRecord = $event->getRawRecord();

        if ($rawRecord->getMainType() === 'tt_content' && $rawRecord->getRecordType() === 'maps' && $event->hasProperty('coordinates')) {
            $event->setProperty(
                'coordinates',
                new Coordinates($event->getProperty('coordinates'))
            );
        }
    }
}
Copied!

Impact

Using the new PSR-14 RecordCreationEvent , extension authors are able to apply any field transformation to any property before a Record is created.

It is even possible to completely create a new RecordInterface object on their own.

Feature: #104868 - Add color scheme switching

See forge#104868

Description

Options have been added to switch between the available color schemes in TYPO3. A set of buttons for each available color scheme in the user dropdown at the top right and a setting in User Settings.

As the dark color scheme is currently regarded experimental until further notice, color scheme switching logic is currently hidden behind the UserTS setting setup.fields.colorScheme.disabled.

Impact

It is now possible to switch to an automatic, light or dark color scheme for use in the backend.

Feature: #104878 - Introduce dashboard widget for pages with latest changes

See forge#104878

Description

To make it easier for TYPO3 users to view the latest changed pages in their TYPO3 system, TYPO3 now offers a dashboard widget that lists the latest changed pages.

Widget Options: - limit The limit of pages, displayed in the widget, default is 10 - historyLimit The maximum number of history records to check, default 1000

Impact

TYPO3 users who have access to the Dashboard module and are granted access to the new widgets can now add and use this widget.

Feature: #104896 - Raise Fluid Standalone to 4.0

See forge#104896

Description

TYPO3 13 now uses Fluid 4 as the new base version. Old TYPO3 versions will keep using Fluid 2, which will still receive bugfixes if necessary. For detailed information about this release, please refer to the dedicated release notes on GitHub.

With the update to Fluid 4, tag-based ViewHelpers now have proper support for boolean attributes. Before this change, it was very cumbersome to generate these with Fluid, now it is implemented similar to popular JavaScript frameworks by using the newly introduced boolean literals:

<my:viewhelper async="{true}" />
Result: <tag async="async" />

<my:viewhelper async="{false}" />
Result: <tag />
Copied!

Of course, any variable containing a boolean can be supplied as well:

<my:viewhelper async="{isAsync}" />
Copied!

This can also be used in combination with variable casting:

<my:viewhelper async="{myString as boolean}" />
Copied!

For compatibility reasons empty strings still lead to the attribute being omitted from the tag.

Impact

For existing installations, negative consequences of this update should be minimal as deprecated features will still work. Users are however advised to look into the already announced deprecations and to update their code accordingly. This update helps with this by now writing log messages to the deprecation log (if activated) if any deprecated feature is used in the TYPO3 instance.

Feature: #104904 - Ignore Fluid syntax error in <f:comment>

See forge#104904

Description

Fluid 4 brings a new template processor \TYPO3Fluid\Fluid\Core\Parser\TemplateProcessor\RemoveCommentsTemplateProcessor which removes Fluid comments created with the Debug ViewHelper <f:debug> from the template source string before the parsing process starts. It retains the original line breaks to ensure that error messages still refer to the correct line in the template.

By applying this template processor to all Fluid instances in the Core, it is now possible to use invalid Fluid code inside <f:comment> without triggering a Fluid error.

Impact

This feature is helpful during template development because developers don't need to take care for commented-out code being valid Fluid code.

Feature: #104914 - Updated HTTP headers for frontend rendering and new TypoScript setting for proxies

See forge#104914

Description

In a typical frontend rendering scenario, TYPO3 sends HTTP response headers to deny caching to clients (= browsers) when e.g. a frontend user is logged in, a backend user is previewing a page, or a non-cacheable plugin is on a page.

When a frontend page is "client-cacheable", TYPO3 does not send any HTTP headers by default, but only when config.sendCacheHeaders = 1 is set via TypoScript.

In this case, TYPO3 sends the following HTTP Headers (example):

Expires: Thu, 26 Aug 2024 08:52:00 GMT
ETag: "d41d8cd98f00b204ecs00998ecf8427e"
Cache-Control: max-age=86400
Pragma: public
Copied!

However, in the past, this could lead to problems, because recurring website users might see outdated content for up to 24 hours (by default) or even longer, even if other website visitors already see new content, depending on various cache_timeout settings.

Thus, config.sendCacheHeaders = 1 should be used with extreme care.

However, this option was also used when a proxy / CDN / shared cache such as Varnish was put in between TYPO3 / the webserver and the client. The reverse proxy can then evaluate the HTTP Response Headers sent by TYPO3 frontend, put the TYPO3 response from the actual webserver into its "shared cache" and send a manipulated / adapted response to the client.

However, when working with proxies, it is much more helpful to take load off of TYPO3 / the webserver by keeping a cached version for a period of time and answering requests from the client, while still telling the client to not cache the response inside the browser cache.

This is now achieved with a new option config.sendCacheHeadersForSharedCaches = auto.

With this option enabled, TYPO3 now evaluates if the current TYPO3 frontend request is executed behind a Reverse Proxy, and if so, TYPO3 sends the following HTTP Response Headers at a cached response:

Expires: Thu, 26 Aug 2024 08:52:00 GMT ETag: "d41d8cd98f00b204ecs00998ecf8427e" Cache-Control: max-age=0, s-maxage=86400 Pragma: public

With config.sendCacheHeadersForSharedCaches = force the reverse proxy evaluation can be omitted, which can be used for local webserver internal caches.

"s-maxage" is a directive to tell shared caches - CDNs and reverse proxies - to keep a cached version of the HTTP response for a period of time (based on various cache settings) in their shared cache, while max-age=0 is evaluated at the client level. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control for more details and if your reverse proxy supports this directive.

The new option takes precedence over config.sendCacheHeaders = 1 if running behind a reverse proxy.

Impact

By utilizing the new TypoScript setting, TYPO3 caches cacheable contents, and also instructs shared caches such as reverse proxies or CDNs to cache the HTTP Response, while always delivering fresh content to the client, if certain routines for cache invalidation are in place. The latter is typically handled by TYPO3 extensions which hook into the cache invalidation process of TYPO3 to also invalidate cache entries in the reverse proxies.

In addition, compared to previous TYPO3 versions, client-cacheable HTTP Responses now send "Cache-Control: private, no-store" if no option applies.

Feature: #104935 - Allow moving content elements via page tree

See forge#104935

Description

To make managing content across pages easier, a backend user may now drag content elements from the Web > Page module into a page in the page tree.

Once dropped, a modal window opens, allowing the backend user to select the position for placing the content element and to change the target page if needed.

Impact

Content elements can be moved from the Web > Page module into the page tree to initiate the moving process.

Feature: #104973 - Activate the shipped LintYaml executable for TYPO3

See forge#104973

Description

The typo3 executable received a new command lint:yaml to ease and encourage linting of YAML files before deploying to production and therefore avoid failures.

Usage as follows:

# Validates a single file
bin/typo3 lint:yaml path/to/file.yaml

# Validates multiple files
bin/typo3 lint:yaml path/to/file1.yaml path/to/file2.yaml

# Validates all files in a directory (also in sub-directories)
bin/typo3 lint:yaml path/to/directory

# Validates all files in multiple directories (also in sub-directories)
bin/typo3 lint:yaml path/to/directory1 path/to/directory2

# Exclude one or more files from linting
bin/typo3 lint:yaml path/to/directory --exclude=path/to/directory/foo.yaml --exclude=path/to/directory/bar.yaml

# Show help
bin/typo3 lint:yaml --help
Copied!

The help argument will list possible usage elements.

Impact

Integrate easy made linting of YAML files from Core, custom extensions or any other source into your quality assurance workflow in the known format of the typo3 executable.

Feature: #104990 - Automatic frontend cache tagging

See forge#104990

Description

When database records are used in the frontend, and the rendered result is put into caches like the page cache, the TYPO3 frontend now automatically tags cache entries with lists of used records.

When changing such records in the backend, affected cache entries are dropped, leading to automatic cache eviction.

This is a huge improvement to previous TYPO3 versions where tagging and cache eviction had to configured manually.

This feature - automatically tagging cache entries - is the final solution to consistent caches at any point in time. It is however a bit tricky to get right in a performant way: There are still details to rule out, and the core will continue to improve in this area. The basic implementation in TYPO3 v13 however already resolves many use cases. Core development now goes ahead to see how this features behaves in the wild.

This feature is encapsulated in the feature toggle frontend.cache.autoTagging: It is enabled by default with new instances based on TYPO3 v13, and needs to be manually enabled for instances being upgrades from previous versions.

Impact

Instances configured with the feature toggle automatically tag caches. Affected cache entries will be removed when changing records.

Deprecation: #101559 - Extbase uses ext:core ViewInterface

See forge#101559

Description

The default view of ext:extbase now returns a view that implements \TYPO3\CMS\Core\View\ViewInterface and not only \TYPO3Fluid\Fluid\View\ViewInterface anymore. This allows implementing any view that implements ViewInterface , and frees the direct dependency to Fluid.

The default return object is an instance of \TYPO3\CMS\Fluid\View\FluidViewAdapter which implements all special methods tailored for Fluid. Extbase controllers should check for instance of this object before calling these methods, especially:

  • getRenderingContext()
  • setRenderingContext()
  • renderSection()
  • renderPartial()

Method calls not being part of ViewInterface or the above listed method names have been marked as deprecated and will be removed in TYPO3 v14.

Impact

Extbase controllers that extend ActionController and call methods not part of ViewInterface , should test for $view instanceof FluidViewAdapter before calling getRenderingContext(), setRenderingContext(), php:renderSection() and renderPartial().

All other Fluid related methods called on $view have been marked as deprecated and will log a deprecation level error message.

Affected installations

Instances with Extbase based extensions that call $view methods without testing for FluidViewAdapter .

Migration

Methods on "old" Fluid instances were wrapper methods for RenderingContext . Controllers should call $view->getRenderingContext() to perform operations instead.

Deprecation: #102422 - TypoScriptFrontendController->addCacheTags() and ->getPageCacheTags()

See forge#102422

Description

The methods \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->addCacheTags() and \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPageCacheTags() have been marked as deprecated.

Impact

Calling the methods \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->addCacheTags() and \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPageCacheTags() will trigger a PHP deprecation warning.

Affected installations

TYPO3 installations calling \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->addCacheTags() or \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPageCacheTags().

Migration

// Before
$GLOBALS['TSFE']->addCacheTags([
    'tx_myextension_mytable_123',
    'tx_myextension_mytable_456'
]);

// After
use TYPO3\CMS\Core\Cache\CacheTag;

$request->getAttribute('frontend.cache.collector')->addCacheTags(
    new CacheTag('tx_myextension_mytable_123', 3600),
    new CacheTag('tx_myextension_mytable_456', 3600)
);
Copied!
// Before
$GLOBALS['TSFE']->getPageCacheTags();

// After
$request->getAttribute('frontend.cache.collector')->getCacheTags();
Copied!

Deprecation: #102821 - ExtensionManagementUtility::addPItoST43()

See forge#102821

Description

The method \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPItoST43() has been marked as deprecated in TYPO3 v13 and will be removed with TYPO3 v14.

Impact

Using the ExtensionManagementUtility::addPItoST43() will raise a deprecation level log entry and a fatal error in TYPO3 v14.

Affected installations

Extensions using ExtensionManagementUtility::addPItoST43() are affected: Using ExtensionManagementUtility::addPItoST43() triggers a deprecation level log message. The extension scanner will find usages of ExtensionManagementUtility::addPItoST43() as strong match.

Migration

// Before:
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPItoST43('my_extkey', '', '_pi1');
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScript(
    'tx_myextkey',
    'setup',
    'plugin.tx_myextkey_pi1.userFunc = MY\MyExtkey\Plugins\Plugin->main'
);

// After:
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScript(
    'my_extkey',
    'setup',
    'plugin.tx_myextkey_pi1 = USER_INT
     plugin.tx_myextkey_pi1.userFunc = MY\MyExtkey\Plugins\Plugin->main'
);
Copied!

Deprecation: #104223 - Fluid standalone methods

See forge#104223

Description

Some methods in Fluid standalone v2 have been marked as deprecated:

  • \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper->registerUniversalTagAttributes()
  • \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper->registerTagAttribute()

Impact

Calling these methods is discouraged. They will log a deprecation level error when used with Fluid standalone v4.

Affected installations

Instances with extensions calling above methods.

Migration

registerUniversalTagAttributes()

Within tag based ViewHelpers, calls to registerUniversalTagAttributes() should be removed. This method has been marked as @deprecated with Fluid standalone 2.12, will log a deprecation level error with Fluid standalone v4, and will be removed with v5.

When removing the call, attributes registered by the call are now available in $this->additionalArguments, and no longer in $this->arguments. This may need adaption within single ViewHelpers, if they handle such attributes on their own. For example, the common ViewHelper f:image was affected within the TYPO3 Core. The following attributes may need attention when removing registerUniversalTagAttributes(): class, dir, id, lang, style, title, accesskey, tabindex, onclick.

registerTagAttribute()

Within tag based ViewHelpers, calls to registerTagAttribute() should be removed. This method has been marked as @deprecated with Fluid standalone 2.12, will log a deprecation level error with Fluid standalone v4, and will be removed with v5.

The call be often simply removed since arbitrary attributes not specifically registered are just added as-is by \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper . This only needs attention if single view helpers deal with such attributes within the render() method: When removing the call, those arguments are no longer available in $this->arguments, but in $this->additionalArguments. Additional attention is needed with attributes registered with type boolean: Those usually have some handling within render(). To stay compatible, it can be helpful to not simply remove the registerTagAttribute() call, but to turn it into a call to registerArgument().

Deprecation: #104304 - BackendUtility::getTcaFieldConfiguration

See forge#104304

Description

The method \TYPO3\CMS\Backend\Utility\BackendUtility::getTcaFieldConfiguration was introduced back in 2010 to add a simple abstraction to access "TCA" definitions of a field.

However, apart from the set up that it is not part of a flexible API without knowing the context, it was used seldom in TYPO3 Core.

The method has now been deprecated, as one could and can easily write the same PHP code with $GLOBALS['TCA'] in mind already (which the TYPO3 Core already did in several other places).

Now that Schema API was introduced, the last parts have been migrated to use the new API.

Impact

Calling the PHP method BackendUtility::getTcaFieldConfiguration will trigger a PHP deprecation warning.

Affected installations

TYPO3 installations with custom extensions using this method.

Migration

Either access $GLOBALS['TCA'] directly (in order to support TYPO3 v12 and TYPO3 v13), or migrate to the new Schema API:

public function __construct(
    private readonly TcaSchemaFactory $tcaSchemaFactory
) {}

private function getFieldConfiguration(string $table, string $fieldName): array
{
    return $this->tcaSchemaFactory
        ->get($table)
        ->getField($fieldName)
        ->getConfiguration();
}
Copied!

Deprecation: #104325 - DiffUtility->makeDiffDisplay()

See forge#104325

Description

Method \TYPO3\CMS\Core\Utility\DiffUtility->makeDiffDisplay() and class property DiffUtility->stripTags have been deprecated in favor of new method DiffUtility->diff(). The new method no longer applies strip_tags() to the input strings.

This change makes class DiffUtility stateless: Property $stripTags will vanish in v14.

Impact

Using method DiffUtility->makeDiffDisplay() will trigger a deprecation level error message.

Affected installations

Instances with extensions calling DiffUtility->makeDiffDisplay().

Migration

If DiffUtility->stripTags is not explicitly set to false, a typical migration looks like this:

// before
$diffUtility->DiffUtility->makeDiffDisplay($from, $to);

// after
$diffUtility->DiffUtility->diff(strip_tags($from), stripTags($to));
Copied!

If DiffUtility->stripTags = false is set before calling DiffUtility->makeDiffDisplay(), method diff() can be called as before, and DiffUtility->stripTags = false can be removed.

Deprecation: #104463 - Fluid standalone overrideArgument

See forge#104463

Description

Fluid standalone method \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper->overrideArgument() has been marked as deprecated.

Impact

Using overrideArgument() in ViewHelpers logs a deprecation level error message in Fluid standalone v4, and will be removed with Fluid standalone v5. The method continues to work without deprecation level error message in Fluid standalone v2.

With Fluid standalone v2.14, registerArgument() no longer throws an exception if an argument is already registered. This allows to override existing arguments transparently without using overrideArgument().

Affected installations

Instances with custom ViewHelpers using overrideArgument() are affected.

Migration

Update typo3fluid/fluid to at least 2.14 and use registerArgument().

Deprecation: #104607 - BackendUserAuthentication:returnWebmounts()

See forge#104607

Description

Method \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::returnWebmounts() has been marked as deprecated and will be removed with TYPO3 v14.

Method BackendUserAuthentication::getWebmounts() was introduced as substitution. It returns a unique list of integer uids instead of a list of strings, which is more type safe. Superfluous calls to array_unique() can be removed since the uniqueness is now guaranteed by BackendUserAuthentication::getWebmounts().

Impact

Calling BackendUserAuthentication::returnWebmounts() will trigger a PHP deprecation warning.

Affected installations

All installations using BackendUserAuthentication::returnWebmounts() are affected.

Migration

Existing calls to BackendUserAuthentication::returnWebmounts() should be replaced by BackendUserAuthentication::getWebmounts().

If third party extensions convert the previous result array from an array of strings to an array of integers, this can be skipped. In addition superfluous calls to array_unique() can be removed since the uniqueness is now guaranteed by BackendUserAuthentication::getWebmounts().

Deprecation: #104662 - BackendUtility thumbCode

See forge#104662

Description

The method \TYPO3\CMS\Backend\Utility\BackendUtility::thumbCode() has been deprecated since the method is no longer used in TYPO3 anymore. Additionally, due to multiple changes to file processing over the years, e.g. introducing of FAL, the method's signature changed a couple of times leading to a couple of method arguments are being unused, which is quite a bad API.

Impact

Calling the PHP method BackendUtility::thumbCode() will trigger a PHP deprecation warning.

Affected installations

TYPO3 installations with custom extensions using this method. The extension scanner will report any usage as strong match.

Migration

Remove any usage of this method. In case you currently rely on the functionality, you can copy it to your custom extension. However, you might want to consider refactoring the corresponding code places.

The method basically resolved given FileReference objects. In case a file could not be resolved, a special icon has been rendered. Otherwise, the cropping configuration has been applied and the file's process() has been called to get the thumbnail, which has been wrapped in corresponding thumbnail markup. This might has been extended to also open the information modal on click.

This means the relevant parts are:

// Get file references
$fileReferences = BackendUtility:resolveFileReferences($table, $field, $row);

// Check for existence of the file
$fileReference->getOriginalFile()->isMissing()

// Render special icon if missing
$iconFactory
    ->getIcon('mimetypes-other-other', IconSize::MEDIUM, 'overlay-missing')
    ->setTitle(static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing') . ' ' . $fileObject->getName())
    ->render()

// Process file with cropping configuration if not missing
$fileReference->getOriginalFile()->process(
    ProcessedFile::CONTEXT_IMAGEPREVIEW,// ProcessedFile::CONTEXT_IMAGECROPSCALEMASK if cropArea is defiend
    [
        'width' => '...',
        'height' => '...',
        'crop' // If cropArea is defined
    ]
)

// Use cropped file and create <img> tag
<img src="' . $fileReference->getOriginalFile()->process()->getPublicUrl() . '"/>

// Wrap the info popup via <a> around the thumbnail
<a href="#" data-dispatch-action="TYPO3.InfoWindow.showItem" data-dispatch-args-list="_FILE,' . (int)$fileReference->getOriginalFile()->getUid() . '">
Copied!

Example of the HTML markup for a thumbnail:

<div class="preview-thumbnails" style="--preview-thumbnails-size: 64px">
    <div class="preview-thumbnails-element">
        <div class="preview-thumbnails-element-image">
            <img src="' . $fileReference->getOriginalFile()->process()->getPublicUrl() . '" width="64px" height="64px" alt="' . $fileReference->getAlternative() ?: $fileReference->getName()  . '" loading="lazy"/>
        </div>
    </div>
</div>
Copied!

Deprecation: #104684 - Fluid RenderingContext->getRequest()

See forge#104684

Description

The following methods have been marked as deprecated in TYPO3 v13 and will be removed with TYPO3 v14:

  • \TYPO3\CMS\Fluid\Core\Rendering\RenderingContext->setRequest()
  • \TYPO3\CMS\Fluid\Core\Rendering\RenderingContext->getRequest()

Impact

Calling above methods triggers a deprecation level log entry in TYPO3 v13 and will trigger a fatal PHP error with TYPO3 v14.

Affected installations

RenderingContext->getRequest() is a relatively common call in custom view helpers. Instances with extensions that deliver custom view helpers may be affected. The extension scanner is not configured to find potential places since the method names are common and would lead to too many false positives.

Migration

Class \TYPO3\CMS\Fluid\Core\Rendering\RenderingContext of the Core extension Fluid extends class \TYPO3Fluid\Fluid\Core\Rendering\RenderingContext of Fluid standalone and adds the methods setRequest() and getRequest(). These methods are however not part of \TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface .

Fluid standalone will not add these methods, since the view of this library should stay free from direct PSR-7 \ServerRequestInterface dependencies. Having those methods in ext:fluid RenderingContext however collides with \RenderingContextInterface , which is type hinted in Fluid view helper method signatures.

Fluid standalone instead added three methods to handle arbitrary additional data in \RenderingContextInterface : setAttribute(), hasAttribute() and getAttribute(). Those should be used instead.

A typical usage in a view helper before:

/** @var \TYPO3Fluid\Fluid\Core\Rendering\RenderingContext $renderingContext */
$renderingContext = $this->renderingContext;
$request = $renderingContext->getRequest();
Copied!

After:

// use Psr\Http\Message\ServerRequestInterface

$request = null;
if ($renderingContext->hasAttribute(ServerRequestInterface::class)) {
    $request = $renderingContext->getAttribute(ServerRequestInterface::class);
}
Copied!

To stay compatible to previous TYPO3 versions while avoiding deprecation notices, the following code can be used:

// use Psr\Http\Message\ServerRequestInterface

if (
    method_exists($renderingContext, 'getAttribute') &&
    method_exists($renderingContext, 'hasAttribute') &&
    $renderingContext->hasAttribute(ServerRequestInterface::class)
) {
    $request = $renderingContext->getAttribute(ServerRequestInterface::class);
} else {
    $request = $renderingContext->getRequest();
}
Copied!

Deprecation: #104764 - Fluid TemplatePaths->fillDefaultsByPackageName

See forge#104764

Description

Method \TYPO3\CMS\Fluid\View\TemplatePaths->fillDefaultsByPackageName() has been marked as deprecated in TYPO3 v13 and will be removed in TYPO3 v14.

Fluid template paths should be set directly using the methods setTemplateRootPaths(), setLayoutRootPaths() and setPartialRootPaths(), or - even better - be handled by ViewFactoryInterface, which comes as new feature in TYPO3 v13.

See Feature: #104773 - Generic view factory for more details of the generic view interface.

Impact

Calling fillDefaultsByPackageName() triggers a deprecation level log level entry in TYPO3 v13 and will be removed in TYPO3 v14.

Affected installations

The method is relatively rarely used by extensions directly, a usage in Extbase ActionController has been refactored away. The extension scanner will find candidates.

Note class TemplatePaths is marked @internal and should not be used by extensions at all.

Migration

Use \TYPO3\CMS\Core\View\ViewFactoryInterface to create views with proper template paths instead. The TYPO3 system extensions come with plenty of examples on how to do this.

Deprecation: #104773 - Custom Fluid views and Extbase

See forge#104773

Description

These classes have been marked as deprecated in TYPO3 v13 and will be removed in v14:

  • \TYPO3\CMS\Fluid\View\StandaloneView
  • \TYPO3\CMS\Fluid\View\TemplateView
  • \TYPO3\CMS\Fluid\View\AbstractTemplateView
  • \TYPO3\CMS\Extbase\Mvc\View\ViewResolverInterface
  • \TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver

This change is related to the general View refactoring.

Impact

Using one of the above classes triggers a deprecation level log entry.

Affected installations

Instances with extensions that create view instances of \StandaloneView or \TemplateView are affected. The extension scanner will find possible candidates.

Migration

Extensions should no longer directly instantiate own views, but should get \TYPO3\CMS\Core\View\ViewFactoryInterface injected and use create() to retrieve a view.

Within Extbase, ActionController->defaultViewObjectName should only be set to Extbase JsonView if needed, or not set at all. Custom view implementations should implement an own ViewFactoryInterface and configure controllers to inject an instance, or can set $this->defaultViewObjectName = JsonView::class in a custom __construct().

Deprecation: #104773 - ext:backend LoginProviderInterface changes

See forge#104773

Description

Method \TYPO3\CMS\Backend\LoginProvider\LoginProviderInterface->render() has been marked as deprecated and is substituted by LoginProviderInterface->modifyView() that will be added to the interface in TYPO3 v14, removing render() from the interface in v14.

Related to this, event \TYPO3\CMS\Backend\LoginProvider\Event\ModifyPageLayoutOnLoginProviderSelectionEvent has been changed to deprecate getController() and getPageRenderer(), while getRequest() has been added. getView() now typically returns an instance of ViewInterface.

This change is related to the general View refactoring.

Impact

The default LoginProviderInterface implementation is UsernamePasswordLoginProvider provided by ext:core. This consumer has been adapted.

Using LoginProviderInterface->render() in TYPO3 v13 will trigger a deprecation level log entry and will fail in v14.

Affected installations

Instances with custom login providers that change the TYPO3 backend login field rendering may be affected. The extension scanner is not configured to find usages, since method name render() is too common. A deprecation level log message is triggered upon use of the old method.

Migration

Consumers of LoginProviderInterface should implement modifyView() instead, the transition should be smooth. Consumers that need the PageRenderer for JavaScript magic, should use dependency injection to receive an instance.

The default implementation in UsernamePasswordLoginProvider is a good example. Extensions that need to configure additional template, layout or partial lookup paths can extend them:

if ($view instanceof FluidViewAdapter) {
    $templatePaths = $view->getRenderingContext()->getTemplatePaths();
    $templateRootPaths = $templatePaths->getTemplateRootPaths();
    $templateRootPaths[] = 'EXT:my_extension/Resources/Private/Templates';
    $templatePaths->setTemplateRootPaths($templateRootPaths);
}
Copied!

Consumers of ModifyPageLayoutOnLoginProviderSelectionEvent should use the request instead, and/or should get an instance of PageRenderer injected as well.

Deprecation: #104778 - Instantiation of IconRegistry in ext_localconf.php

See forge#104778

Description

Since TYPO3 v11 it is possible to automatically register own icons via Configuration/Icons.php. Prior to this, extension authors used to register icons manually via instantiating the php:\TYPO3\CMS\Core\Imaging\IconRegistry in their ext_localconf.php files. This method has now been deprecated. It is recommended to switch to the newer method introduced with forge#94692.

Impact

Instantiating IconRegistry inside ext_localconf.php files will trigger a deprecation-level log entry.

Affected installations

All installations, which instantiate IconRegistry before the \TYPO3\CMS\Core\Core\Event\BootCompletedEvent . This includes ext_localconf.php files as well as TCA/Overrides.

Migration

The most common use-cases can be accomplished via the Configuration/Icons.php file.

Before:

EXT:example/ext_localconf.php
<?php

$iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
  \TYPO3\CMS\Core\Imaging\IconRegistry::class,
);
$iconRegistry->registerIcon(
    'example',
    \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
    [
        'source' => 'EXT:example/Resources/Public/Icons/example.svg'
    ],
);
Copied!

After:

EXT:example/Configuration/Icons.php
<?php

return [
    'example' => [
        'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
        'source' => 'EXT:example/Resources/Public/Icons/example.svg',
    ],
];
Copied!

For more complex tasks, it is recommended to register an event listener for the BootCompletedEvent . At this stage the system is fully booted and you have a completely configured IconRegistry at hand.

In case the registry was used in TCA/Overrides files to retrieve icon identifiers, then this should be replaced completely with static identifiers. The reason behind this is, that the registry isn't even fully usable at this stage. TCA isn't fully built yet and icons can still be registered at a later point.

Deprecation: #104789 - Fluid variables true, false, null

See forge#104789

Description

Fluid standalone will add proper language syntax for booleans and null with Fluid v4, which will be used in TYPO3 v13. Thus, user-defined variables named true, false and null are no longer allowed.

Impact

Assigning variables with name true, false or null will throw an exception in Fluid v4. In preparation of this change, Fluid v2.15 logs a deprecation level error message if any of these variable names are used.

Affected installations

Instances with Fluid templates using true, false or null as user-defined variable names. This should rarely happen, as it would involve using $view->assign('true', $someVar).

Migration

Template code using these variables should be adjusted to use different variable names. In Fluid v4, the variables will contain their matching PHP counterparts.

Deprecation: #104789 - renderStatic() for Fluid ViewHelpers

See forge#104789

Description

The usage of renderStatic() for Fluid ViewHelpers has been deprecated. Also, Fluid standalone traits \TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic and \TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic have been marked as deprecated.

Impact

Using one of the mentioned traits or renderStatic() in ViewHelpers logs a deprecation level error message in Fluid standalone v4. renderStatic() will no longer be called in Fluid standalone v5. renderStatic() and both traits continue to work without deprecation level error message in Fluid standalone v2.

Affected installations

Instances with custom ViewHelpers using any variant of renderStatic() are affected.

Migration

ViewHelpers should always use render() as their primary rendering method.

ViewHelpers using just renderStatic() without any trait or with the trait \CompileWithRenderStatic can be migrated by converting the static rendering method to a non-static method:

Before:

class MyViewHelper extends AbstractViewHelper
{
    use CompileWithRenderStatic;

    public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string
    {
        return $renderChildrenClosure();
    }
}
Copied!

After:

class MyViewHelper extends AbstractViewHelper
{
    public function render(): string
    {
        return $this->renderChildren();
    }
}
Copied!

ViewHelpers using \TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic can use the new contentArgumentName feature added with Fluid v2.15:

Before:

class MyViewHelper extends AbstractViewHelper
{
    use CompileWithContentArgumentAndRenderStatic;

    public function initializeArguments(): void
    {
        $this->registerArgument('value', 'string', 'a value');
    }

    public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string
    {
        return $renderChildrenClosure();
    }

    public function resolveContentArgumentName(): string
    {
        return 'value';
    }
}
Copied!

After:

class MyViewHelper extends AbstractViewHelper
{
    public function initializeArguments(): void
    {
        $this->registerArgument('value', 'string', 'a value');
    }

    public function render(): string
    {
        return $this->renderChildren();
    }

    public function getContentArgumentName(): string
    {
        return 'value';
    }
}
Copied!

Here is a basic recipe to perform this migration, preferably utilizing statical code analysis/replacement tools on your *ViewHelper.php files:

  • Find definitions of renderStatic
  • Rename method to render(), remove the arguments, remove static declaration
  • Within that method:

    • Replace $arguments with $this->arguments
    • Replace $renderingContext with $this->renderingContext
    • Replace $renderChildrenClosure() with $this->renderChildren()
    • Replace remaining $renderChildrenClosure usages with proper closure handling, like $this->renderChildren(...).
  • Replace resolveContentArgumentName( with getContentArgumentName(
  • Remove the mentioned definitions:

    • use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
    • use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
    • use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic;
    • use CompileWithRenderStatic; (class trait)
    • use CompileWithContentArgumentAndRenderStatic; (class trait)
  • (Optionally remove custom phpdoc annotations to the renderStatic parameters)
  • If you previously called ViewHelper's renderStatic methods in other places, you may utilize something like:

    $this->renderingContext->getViewHelperInvoker()->invoke(
        MyViewHelper::class,
        $arguments,
        $this->renderingContext,
        $this->renderChildren(...),
    );
    Copied!

Important: #101535 - Unused DB fields from tt_content removed

See forge#101535

Description

The database table tt_content contains all necessary fields for rendering content elements.

Back with TYPO3 v4.7, a major feature to render certain Content Types in a more accessible way, funded by the German Government (BLE_) with the "Konjunkturpaket II" was merged into CSS Styled Content.

In this procedure, certain Content Types received new fields and rendering definitions, which were stored in the database fields accessibility_title, accessibility_bypass and accessibility_bypass_text.

When CSS Styled Content was removed in favor of Fluid Styled Content in TYPO3 v8, the DB fields continued to exist in TYPO3 Core, so a migration from CSS Styled Content was possible.

However, the DB fields are not evaluated anymore since then, and are removed, along with their TCA definition in tt_content.

If these fields are still relevant for a custom legacy installation, these DB fields need to be re-created via TCA for further use in a third-party extension.

Important: #104126 - Drop "typo3conf" directory from system status check and backend locking

See forge#104126

Description

The directory typo3conf is no longer needed in Composer Mode. Checking for the existence of this directory is no longer performed in the Environment and Install Tool.

Previously it contained:

  • extensions (which are now Composer packages stored in vendor/),
  • the configuration files (which are now part of the config/ tree)
  • language labels and some artifact states (now part of var/)
  • a "backend lock" file (LOCK_BACKEND)

The location to this file can be adjusted via the new configuration setting $GLOBALS['TYPO3_CONF_VARS']['BE']['lockBackendFile'] . See Feature: #104126 - Add configuration setting to define backend-locking file for details on this setting and location.

By default, LOCK_BACKEND is now located here:

  • var/lock/ for Composer Mode
  • config/ for Legacy Mode

13.2 Changes

Table of contents

Breaking Changes

None since TYPO3 v13.0 release.

Features

Deprecation

Important

Feature: #91783 - Allow system maintainer to mute disable_functions error

See forge#91783

Description

Adds a configuration option to adapt the environment check in the Install Tool for a list of sanctioned disable_functions.

With the new configuration option $GLOBALS['TYPO3_CONF_VARS']['SYS']['allowedPhpDisableFunctions'] , a system maintainer can add native PHP function names to this list, which are then reported as environment warnings instead of errors.

Configuration example in additional.php:

$GLOBALS['TYPO3_CONF_VARS']['SYS']['allowedPhpDisableFunctions']
  = ['set_time_limit', 'set_file_buffer'];
Copied!

You can also define this in your settings.php file manually or via Admin Tools > Settings > Configure options.

Impact

Native php function names can be added as an array of function names, which will not trigger an error but only a warning, if they can also be found in the php.ini setting disable_functions.

Feature: #92009 - Provide backend modules in LiveSearch

See forge#92009

Description

The backend LiveSearch is now capable of listing backend modules, a user has access to, offering the possibility of alternative navigation to different parts of the backend.

Impact

Backend users now have another possibility to quickly access a backend module.

Feature: #99203 - Streamline FE/versionNumberInFilename to 'EXT:' resources

See forge#99203

Description

Local resources are currently not "cache-busted", for example, have no version in URL. TypoScript has no possibility to add the cache buster. When replacing them a new filename must be used (which feels little hacky).

getText "asset" to cache-bust assets in TypoScript

EXT:my_extension/Configuration/TypoScript/setup.typoscript
page.20 = TEXT
page.20 {
    value = { asset : EXT:core/Resources/Public/Icons/Extension.svg }
    insertData = 1
}
Copied!
Result
typo3/sysext/core/Resources/Public/Icons/Extension.svg?1709051481
Copied!

Cache-busted assets with the <f:uri.resource> ViewHelper

EXT:my_extension/Resources/Private/Template/MyTemplate.html
<f:uri.resource
    path="EXT:core/Resources/Public/Icons/Extension.svg"
    useCacheBusting="true"
/>
Copied!
Comparison
Before: typo3/sysext/core/Resources/Public/Icons/Extension.svg
Now: typo3/sysext/core/Resources/Public/Icons/Extension.svg?1709051481
Copied!

The ViewHelper argument useCacheBusting is enabled by default.

Depending on $GLOBALS['TYPO3_CONF_VARS']['FE']['versionNumberInFilename'] the cache buster is applied as query string or embedded in the filename.

Impact

Local resources now can have a cache buster to easily replace them without changing the filename.

Feature: #102155 - User TSconfig option for default resources ViewMode

See forge#102155

Description

The listing of resources in the TYPO3 backend, e.g. in the File > Filelist module or the FileBrowser can be switched between list and tiles. TYPO3 serves tiles by default.

A new User TSconfig option options.defaultResourcesViewMode has been introduced, which allows the initial display mode to be defined. Valid values are therefore list and tiles, e.g.:

options.defaultResourcesViewMode = list
Copied!

Impact

Integrators can now define the default display mode for resources via User TSconfig.

Feature: #102326 - Allow custom translations for Extbase validators

See forge#102326

Description

All validation messages from Extbase validators can now be overwritten using validator options. It is possible to provide either a translation key or a custom message as string.

Extbase validators providing only one validation message can be overwritten by a translation key or message using the validator option message. Validators providing multiple validation messages (e.g. Boolean, NotEmpty or NumberRange) use different validator options keys. In general, translation keys or messages for validators are registered in the validator property translationOptions.

Example with translations

use TYPO3\CMS\Extbase\Annotation as Extbase;

#[Extbase\Validate([
    'validator' => 'NotEmpty',
    'options' => [
        'nullMessage' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:validation.myProperty.notNull',
        'emptyMessage' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:validation.myProperty.notEmpty',
    ],
])]
protected string $myProperty = '';
Copied!

In this example, translation option keys for the NotEmptyValidator are overwritten for the property $myProperty. The locallang.xlf translation file from the extension my_extension will be used to lookup translations for the newly provided translation key options.

Example with a custom string

use TYPO3\CMS\Extbase\Annotation as Extbase;

#[Extbase\Validate([
    'validator' => 'Float',
    'options' => [
        'message' => 'A custom, non translatable message',
    ],
])]
protected float $myProperty = 0.0;
Copied!

In this example, translation option keys for the FloatValidator are overwritten for the property $myProperty. The message string is shown if validation fails.

Impact

The new validator translation option keys allow developers to define unique validation messages for TYPO3 Extbase validators on validator usage basis. This may result in a better user experience, since validation messages now can refer to the current usage scope (e.g. "The field 'Title' is required" instead of "The given subject was empty.").

Feature: #102337 - PSR-14 event for modifying record list export data

See forge#102337

Description

A new PSR-14 event \TYPO3\CMS\Backend\RecordList\Event\BeforeRecordDownloadIsExecutedEvent has been introduced to modify the result of a download / export initiated in the Web > List module.

This replaces the $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvHeader'] and $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvRow'] , hooks, which have been deprecated.

The event allows body and header sections of the data dump to be modified, so that they can e.g. be used to redact specific data for GDPR compliance, transform / translate specific data, trigger creation of archives or web hooks, log export access and more.

The event offers the following methods:

  • getHeaderRow(): Return the current header row of the dataset.
  • setHeaderRow(): Sets the modified header row of the dataset.
  • getRecords(): Returns the current body rows of the dataset.
  • setRecords(): Sets the modified body rows of the dataset.
  • getRequest(): Returns the PSR request context.
  • getTable(): Returns the name of the database table of the dataset.
  • getFormat(): Returns the format of the download action (CSV/JSON).
  • getFilename(): Returns the name of the download filename (for browser output).
  • getId(): Returns the page UID of the download origin.
  • getModTSconfig(): Returns the active module TSconfig of the download origin.
  • getColumnsToRender(): Returns the list of header columns for the triggered download.
  • isHideTranslations(): Returns whether translations are hidden or not.

Example

The corresponding event listener class:

<?php

declare(strict_types=1);

namespace Vendor\MyPackage\Core\EventListener;

use TYPO3\CMS\Backend\RecordList\Event\BeforeRecordDownloadIsExecutedEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener(identifier: 'my-package/record-list-download-data')]
final readonly class DataListener
{
    public function __invoke(BeforeRecordDownloadIsExecutedEvent $event): void
    {
        // List of redactable fields.
        $gdprFields = ['title', 'author'];

        $headerRow = $event->getHeaderRow();
        $records = $event->getRecords();

        // Iterate header to mark redacted fields...
        foreach ($headerRow as $headerRowKey => $headerRowValue) {
            if (in_array($headerRowKey, $gdprFields, true)) {
                $headerRow[$headerRowKey] .= ' (REDACTED)';
            }
        }

        // Redact actual content...
        foreach ($records as $uid => $record) {
            foreach ($gdprFields as $gdprField) {
                if (isset($record[$gdprField])) {
                    $records[$uid][$gdprField] = '(REDACTED)';
                }
            }
        }

        $event->setHeaderRow($headerRow);
        $event->setRecords($records);
    }
}
Copied!

Migration

The functionality of both hooks customizeCsvHeader and customizeCsvRow are now handled by the new PSR-14 event.

Migrating customizeCsvHeader

The prior hook parameter/variable fields is now available via $event->getColumnsToRender(). The actual record data (previously $this->recordList, submitted to the hook as its object reference) is accessible via $event->getHeaderRow().

Migrating customizeCsvRow

The prior hook parameters/variables have the following substitutes:

  • databaseRow is now available via $event->getRecords() (see note below).
  • tableName is now available via $event->getTable().
  • pageId is now available via $event->getId().

The actual record data (previously $this->recordList, submitted to the hook as its object reference) is accessible via $event->getRecords().

Please note that the hook was previously executed once per row retrieved from the database. The PSR-14 event however - due to performance reasons - is only executed for the full record list after database retrieval, thus allowing post-processing on the whole dataset.

Impact

Using the PSR-14 event BeforeRecordDownloadIsExecutedEvent it is now possible to modify all of the data available when downloading / exporting a list of records via the Web > List module.

Feature: #102337 - PSR-14 event for modifying record list download presets

See forge#102337

Description

A new PSR-14 event \TYPO3\CMS\Backend\RecordList\Event\BeforeRecordDownloadPresetsAreDisplayedEvent has been introduced to manipulate the list of available download presets in the Web > List module.

See Feature: #102337 - Presets for download of record lists for a detailed description of how to utilize presets when downloading a set of records from the backend in CSV or JSON format.

The event class offers the following methods:

  • getPresets(): Returns a list of presets set via TSconfig
  • setPresets(): Sets a modified list of presets.
  • getDatabaseTable(): Returns the database table name that a preset applies to.
  • getRequest(): Returns the PSR Request object for the context of the request.
  • getId(): Returns the page ID of the originating page.

Note that the event is dispatched for one specific database table. If an event listener is created to attach presets to different tables, the listener method must check for the table name, as shown in the example below.

If no download presets exist for a given table, the PSR-14 event can still be used to modify and add presets to it via the setPresets() method.

The array passed from getPresets() to setPresets() can contain an array collection of \TYPO3\CMS\Backend\RecordList\DownloadPreset objects with the array key using the preset label. The existing presets can be retrieved with these getters:

  • $preset->getLabel(): Name of the preset (can utilize LLL translations), optional.
  • $preset->getColumns(): Array of database table column names.
  • $preset->getIdentifier(): Identifier of the preset (manually set or calculated based on label and columns)

The event listener can also remove array indexes or columns of existing array entries by passing a newly constructed DownloadPreset object with the changed label and columns constructor properties.

Example

The corresponding event listener class:

<?php

declare(strict_types=1);

namespace Vendor\MyPackage\RecordList\EventListener;

use TYPO3\CMS\Backend\RecordList\Event\BeforeRecordDownloadPresetsAreDisplayedEvent;
use TYPO3\CMS\Backend\RecordList\DownloadPreset;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener(identifier: 'my-package/modify-record-list-preset')]
final readonly class PresetListener
{
    public function __invoke(BeforeRecordDownloadPresetsAreDisplayedEvent $event): void
    {
        $presets = $event->getPresets();

        switch ($event->getDatabaseTable()) {
            case 'be_users':
                $presets[] = new DownloadPreset('PSR-14 preset', ['uid','email']);
                break;

            case 'pages':
                $presets[] = new DownloadPreset('PSR-14 preset', ['title']);
                $presets[] = new DownloadPreset('Another PSR-14 preset', ['title', 'doktype']);
                break;

            case 'tx_myvendor_myextension':
                $presets[] = new DownloadPreset('PSR-14 preset', ['uid', 'something']);
                break;
        }

        $presets[] = new DownloadPreset('Available everywhere, simple UID list', ['uid']);

        $presets['some-identifier'] = new DownloadPreset('Overwrite preset', ['uid, pid'], 'some-identifier');

        $event->setPresets($presets);
    }
}
Copied!

Impact

Using the PSR-14 event BeforeRecordDownloadPresetsAreDisplayedEvent it is now possible to modify the presets of each table for downloading / exporting a list of such records via the Web > List module.

Feature: #102337 - Presets for download of record lists

See forge#102337

Description

In the Web > List backend module, the data of records for each database table (including pages and content records) can be downloaded.

This export takes the currently selected list of columns into consideration and alternatively allows all columns to be selected.

A new feature has been introduced adding the ability to pick the exported data columns from a list of configurable presets.

Those presets can be configured via page TSconfig, and can also be overridden via user TSconfig (for example, to make certain presets only available to specific users).

EXT:my_extension/Configuration/page.tsconfig
mod.web_list.downloadPresets {
    pages {
        10 {
            label = Quick overview
            columns = uid, title, crdate, slug
        }

        20 {
            identifier = LLL:EXT:myext/Resources/Private/Language/locallang.xlf:preset2.label
            label = UID and titles only
            columns = uid, title
        }
    }
}
Copied!

Each entry of mod.web_list.downloadPresets defines the table name on the first level (in this case pages), followed by any number of presets.

Each preset contains a label (the displayed name of the preset, which can be a locallang key), a comma-separated list of each column that should be included in the export as columns and optionally an identifier. If identifier is not provided, the identifier is generated as a hash of the label and columns.

This can be manipulated with user TSConfig by adding the page. prefix. User TSConfig is loaded after page TSConfig, so you can overwrite existing keys or replace the whole list of keys:

EXT:my_extension/Configuration/user.tsconfig
page.mod.web_list.downloadPresets {
    pages {
        10 {
            label = Quick overview (customized)
            columns = uid, title, crdate, slug
        }

        30 {
            label = Short with URL
            columns = uid, title, slug
        }
    }
}
Copied!

Since any table can be configured for a preset, any extension can deliver a defined set of presets through the EXT:my_extension/Configuration/page.tsconfig file and their table name(s).

Additionally, the list of presets can be manipulated via the new BeforeRecordDownloadPresetsAreDisplayedEvent.

Impact

Editors can now export data with specific presets as required as identified by the website maintainer or extension developer(s).

It is no longer required to pick specific columns to export over and over again, and the list of presets is controlled by the website maintainer.

Two new PSR-14 Events have been added to allow further manipulation:

Feature: #102869 - List workspaces in LiveSearch

See forge#102869

Description

The backend LiveSearch is now able to list workspaces a backend user has access to, offering the possibility to switch to a workspace quickly outside the Workspaces module.

Impact

Backend users now have another, quickly accessible way to access a workspace. With proper permissions, a backend user may also switch to the edit interface of a workspace to configure its settings.

Feature: #102951 - Provide PSR-7 request in Extbase validators

See forge#102951

Description

Extbase abstractValidator now provides a getter and a setter for the PSR-7 Request object. Validators extending AbstractValidator will include the PSR-7 request object, if the validator has been instantiated by Extbase ValidationResolver.

Impact

Extension developers can now create custom validators which consume data from the PSR-7 request object (e.g. request attribute frontend.user).

Feature: #103019 - ModifyRedirectUrlValidationResultEvent PSR-14 event

See forge#103019

Description

This feature introduces the new PSR-14 event ModifyRedirectUrlValidationResultEvent in the felogin extension to provide developers the possibility and flexibility to implement custom validation for the redirect URL. This may be useful if TYPO3 frontend login acts as an SSO system or if users should be redirected to an external URL after login.

Example

<?php

namespace Vendor\MyExtension\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\FrontendLogin\Event\ModifyRedirectUrlValidationResultEvent;

class ValidateRedirectUrl
{
    #[AsEventListener('validate-custom-redirect-url')]
    public function __invoke(ModifyRedirectUrlValidationResultEvent $event): void
    {
        $parsedUrl = parse_url($event->getRedirectUrl());
        if ($parsedUrl['host'] === 'trusted-host-for-redirect.tld') {
            $event->setValidationResult(true);
        }
    }
}
Copied!

Impact

Developers now have the possibility to modify the validation results for the redirect URL, allowing redirects to URLs not matching existing validation constraints.

Feature: #103783 - RecordTransformation Data Processor

See forge#103783

Description

A new TypoScript data processor for FLUIDTEMPLATE and PAGEVIEW has been added.

The record-transformation Data Processor can typically be used in conjunction with the DatabaseQuery Data Processor. The DatabaseQuery Data Processor typically fetches records from the database, and the record-transformation will take the result and transform the objects into Record objects, which contain relevant data from the TCA table, which has been configured in the TCA columns fields for this record.

This is especially useful for TCA tables, which contain "types" (such as pages or tt_content database tables), where only relevant fields are added to the record object. In addition, special fields from "enableColumns" or deleted fields, as well as language and version information are extracted so they can be dealt with in a unified way.

The "type" property contains the database table name and the actual type based on the record, such as "tt_content.textmedia" for Content Elements.

Impact

Example of the data processor being used in conjunction with DatabaseQuery processor.

page = PAGE
page {
  10 = PAGEVIEW
  10 {
    paths.10 = EXT:my_extension/Resources/Private/Templates/
    dataProcessing {
      10 = database-query
      10 {
        as = mainContent
        table = tt_content
        select.where = colPos=0
        dataProcessing.10 = record-transformation
      }
    }
  }
}
Copied!

Transform the current data array of FLUIDTEMPLATE to a Record object. This can be used for Content Elements of Fluid Styled Content or custom ones. In this example the FSC element "Text" has its data transformed for easier and enhanced usage.

tt_content.text {
  templateName = Text
  dataProcessing {
    10 = record-transformation
    10 {
      as = data
    }
  }
}
Copied!

Usage in Fluid templates

The f:debug output of the Record object is misleading for integrators, as most properties are accessed differently than would be expected. The debug view is a better organized overview of all the available information. E.g. the property properties lists all relevant fields for the current Content Type.

We are dealing with an object here. You can, however, access your record properties as you are used to with {record.title} or {record.uid}. In addition, you gain special, context-aware properties like the language {record.languageId} or workspace {data.versionInfo.workspaceId}.

Overview of all possibilities:

<!-- Any property, which is available in the Record (like normal) -->
{record.title}
{record.uid}
{record.pid}

<!-- Language related properties -->
{record.languageId}
{record.languageInfo.translationParent}
{record.languageInfo.translationSource}

<!-- The overlaid uid -->
{record.overlaidUid}

<!-- Types are a combination of the table name and the Content Type name. -->
<!-- Example for table "tt_content" and CType "textpic": -->

<!-- "tt_content" (this is basically the table name) -->
{record.mainType}

<!-- "textpic" (this is the CType) -->
{record.recordType}

<!-- "tt_content.textpic" (Combination of mainType and record type, separated by a dot) -->
{record.fullType}

<!-- System related properties -->
{data.systemProperties.isDeleted}
{data.systemProperties.isDisabled}
{data.systemProperties.isLockedForEditing}
{data.systemProperties.createdAt}
{data.systemProperties.lastUpdatedAt}
{data.systemProperties.publishAt}
{data.systemProperties.publishUntil}
{data.systemProperties.userGroupRestriction}
{data.systemProperties.sorting}
{data.systemProperties.description}

<!-- Computed properties depending on the request context -->
{data.computedProperties.versionedUid}
{data.computedProperties.localizedUid}
{data.computedProperties.requestedOverlayLanguageId}
{data.computedProperties.translationSource} <!-- Only for pages, contains the Page model -->

<!-- Workspace related properties -->
{data.versionInfo.workspaceId}
{data.versionInfo.liveId}
{data.versionInfo.state.name}
{data.versionInfo.state.value}
{data.versionInfo.stageId}
Copied!

Available options

The variable that contains the record(s) from a previous data processor,
or from a FLUIDTEMPLATE view. Default is :typoscript:`data`.
variableName = items

# the name of the database table of the records. Leave empty to auto-resolve
# the table from context.
table = tt_content

# the target variable where the resolved record objects are contained
# if empty, "record" or "records" (if multiple records are given) is used.
as = myRecords
Copied!

Feature: #103894 - Additional properties for columns in Page Layouts

See forge#103894

Description

Backend Layouts were introduced in TYPO3 v6 in order to customize the view of the Page module in TYPO3 backend for pages, but has since grown, also in frontend rendering, to select e.g. Fluid template files via TypoScript for a page, commonly used via data:pagelayout.

In order to use a single source for backend and frontend representation, the definition of a "Backend Layout" or "Page Layout" is expanded to also include more information for a specific content area. The Content Area was previously defined via "name" (for the label in the Page module) and "colPos", the numeric database field in which content is grouped in.

A definition can now optionally also contain a "slideMode" property and an "identifier" property next to each colPos, in order to simplify frontend rendering.

Whereas "identifier" is a speaking representation for the colPos, such as "main", "sidebar" or "footerArea", the "slideMode" can be set to one of the three options:

  • slideMode = slide - if no content is found, check the parent pages for more content
  • slideMode = collect - use all content from this page, and the parent pages as one collection
  • slideMode = collectReverse- same as "collect" but in the opposite order

With this information added, a new DataProcessor page-content ( \TYPO3\CMS\Frontend\DataProcessing\PageContentFetchingProcessor ) is introduced for the frontend rendering, which fetches all content for a page and respecting the settings from the page layout.

The new data processor allows to manipulate the fetched page content via the PSR-14 AfterContentHasBeenFetchedEvent.

Impact

Enriching the backend layout information for each colPos enables a TYPO3 integrator to write less TypoScript in order to render content on a page.

The DataProcessor fetches all content elements from all defined columns with an included "identifier" in the selected backend layout and makes the resolved record objects available in the Fluid template via {content."myIdentifier".records}.

Example of an enriched backend layout definition:

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
                slideMode = slide
              }
            }
          }
        }
      }
    }
  }
}
Copied!

Example of the frontend output:

page = PAGE
page.10 = PAGEVIEW
page.10.paths.10 = EXT:my_site_package/Tests/Resources/Private/Templates/
page.10.dataProcessing.10 = page-content
page.10.dataProcessing.10.as = myContent
Copied!
<main>
    <f:for each="{myContent.main.records}" as="record">
        <f:cObject typoscriptObjectPath="{record.mainType}" table="{record.mainType}" data="{record}"/>
    </f:for>
</main>
Copied!

The f:cObject ViewHelper above uses the rendering definition of the tt_content table {record.mainType} to render the Content Element from the list. The attribute data expects the raw database record, which is retrieved from {record}.

Feature: #104002 - Schema API

See forge#104002

Description

A new Schema API is introduced to access information about TCA structures in a unified way.

The main goal of this architecture is to reduce direct access to $GLOBALS['TCA'] after the Bootstrap process is completed.

The Schema API implements the following design goals:

  1. An object-oriented approach to access common TCA information such as if a database table is localizable or workspace-aware, if it has a "deleted" field ("soft-delete"), or other common functionality such as "enableFields" / "enablecolumns", which can be accessed via "Capabilities" within a Schema.
  2. A unified way to access information which "types" a TCA table has available, such as "tt_content", where the "CType" field is the divisor for types, thus, allowing a Schema to have sub-schemata for a TCA Table.

    The API in turn then handles which fields are available for a specific "CType". An example is "tt_content" with type "textpic": The sub-schema "tt_content.textpic" only contains the fields that are registered of that "CType", such as "bodytext", which then knows it is a Rich Text Field (the default column does not have this information), or "image" (a file relation field), but the sub-schema does not contain fields that are irrelevant for this type, such as "assets" (also a file relation field).

  3. An abstracted approach to available TCA field types such as "input" or "select", which also takes information into account, if a select field is a selection of a static list (such as "pages.layout") or if it contains a relation to another schema or field (based on "foreign_table"). Previously, this was evaluated in many places in TYPO3 Core, and can now be reduced.

    Thus, Schema API can now be utilized to determine the RelationshipType of a relational field type in a unified way without having to deal with deeply nested arrays.

  4. Information about relations to other database tables or fields. This is especially useful when dealing with Inline elements or category selection fields.

    Schema API can find out, which fields of other schemata are pointing to one-self. Schema API differentiates between an "Active Relation" and a "Passive Relation". An Active Relation is the information that a field such as "pages.media" (a field of type "file") contains a reference to the "sys_file_reference.uid_foreign" field. Active Relations in consequence are connected to a specific field (of type RelationalFieldTypeInterface).

    In turn, a "Passive Relation" is the information what other schemata/fields are pointing to a specific table or field.

    A common example of a "Passive Relation" is "sys_workspace_stage": The information stored in $GLOBALS[TCA][sys_workspace_stage] does not contain the information that this table is actually used as a reference from the database field sys_workspace.custom_stages, the sys_workspace_stage Schema now contains this information directly via TcaSchema->getPassiveRelations(). This is possible as TcaSchemaFactory is evaluating all TCA information and holistically as a graph. Passive Relations are currently only connected to a Schema, and Active Relations to a Field or a Schema.

    As the Schema API fetches information solely based on the TCA, an Active Relation only points to possible references, however, the actual reference (does a record really have a connection to another database table) would require an actual Record instance (a database row) to evaluate this information.

    Relations do not know about the "Type" or "Quantity" (many-to-many etc) as this information is already stored in the Field information. For this reason, the "Relations" currently only contain a flat information structure of the table (and possibly a field) pointing TO another schema name (Active Relation) or FROM another schema name / field (Passive Relation).

    Schema API also parses all available FlexForm data structures in order to resolve relations. As a result, a field of type FlexFormField contains a list of possible "FlexFormSchema" instances, which resolve all fields, sheets and section containers within each data structure.

  5. Once built, the Schema can never be changed. Whereas the TCA could be overridden at runtime, all TCA is now evaluated once and then cached. This is a consequence of working with an object-oriented approach.

    If the TCA is changed after the Bootstrap process is completed, the Schema needs to be rebuilt manually, which TYPO3 Core currently does, for example, in some Functional Testing Scenarios.

    All key objects (Schema, FieldType, Capabilities) are treated as immutable DTOs and never contain cross-references to their parent objects (Sub schemata do not know information about their parent schema, a field does not know which schema it belongs to), so the only entry point is always the TcaSchemaFactory object.

    This design allows the API to be fully cacheable at PHP level as a nested tree.

  6. Low-level, not full-fledged but serves as a basis.

    Several API decisions were made in order to let Schema API keep only its original purpose, but can be encapsulated further in other APIs:

    • Schema API is not available during Bootstrap as it needs TCA to be available and fully finished.
    • Schema API does not contain all available TCA properties for each field type. An example is "renderType" for select fields. This information is not relevant when querying records in the frontend, and mainly relevant for FormEngine - it is not generic enough to justify a getter method.
    • Extensibility: custom field types are currently not available until TYPO3 Core as fully migrated to Schema API.
    • User Permissions: Evaluating if a user has access to "tt_content.bodytext" requires information about the currently logged in user, thus it is not part of the Schema API. A "Permission API" should evaluate this information in the future.
    • Available options for a field. As an example, a common scenario is to find out which possible options are available for "pages.backend_layout". In TYPO3 Core an itemsProcFunc is connected to that field in TCA. Whether there is an itemsProcFunc is stored, but Schema API is not designed to actually execute the itemsProcFunc as it is dependent on various factors evaluated during runtime, such as the page it resides on, user permissions or PageTsConfig overrides.

    Schema API is currently marked as internal, as it might be changed during TYPO3 v13 development, because more parts of TYPO3 will be migrated towards Schema API.

    DataHandler and the Record Factory already utilize Schema API in order to reduce direct access to $GLOBALS[TCA].

    In the future Schema API might be used to evaluate information for Site Configurations, like TCA and FlexForms.

Impact

Reading and writing $GLOBALS[TCA] within Configuration/TCA/* and via TCA Overrides is untouched, as the API is meant for reading the information there in a unified way.

Usage

The API can now be used to find out information about TCA fields.

public function __construct(
    protected readonly PageRepository $pageRepository,
    protected readonly TcaSchemaFactory $tcaSchemaFactory
) {}

public function myMethod(string $tableName): void
{
    if (!$this->tcaSchemaFactory->has($tableName)) {
        // this table is not managed via TYPO3's TCA API
        return;
    }
    $schema = $this->tcaSchemaFactory->get($tableName);

    // Find out if a table is localizable
    if ($schema->isLocalizable()) {
        // do something
    }

    // Find all registered types
    $types = $schema->getSubSchemata();

}
Copied!

Using the API improves handling for parts such as evaluating columnsOverrides, foreign field structures, FlexForm Schema parsing, and evaluating type fields for database fields.

Feature: #104020 - ViewHelper to check feature flags

See forge#104020

Description

The <f:feature> ViewHelper allows integrators to check for feature flags from within Fluid templates. The ViewHelper follows the same rules as the underlying TYPO3 api, which means that undefined flags will be treated as false.

Examples

Basic usage

<f:feature name="myFeatureFlag">
   This is being shown if the flag is enabled
</f:feature>
Copied!

feature / then / else

<f:feature name="myFeatureFlag">
   <f:then>
      Flag is enabled
   </f:then>
   <f:else>
      Flag is undefined or not enabled
   </f:else>
</f:feature>

Copied!

Impact

Feature flags can now be checked from within Fluid templates.

Feature: #104067 - Sorting of forms in the form module

See forge#104067

Description

The listing of existing forms in the Form backend module has been extended to provide sorting functionality.

Impact

It's now possible to sort the existing forms in the Form backend module.

Feature: #104069 - Improved backend notifications display and handling

See forge#104069

Description

The notifications shown on the lower right now have a "Clear all" button to allow the user to clear all notifications with a single click. This button is only displayed when two or more notifications are on screen.

In case the height of the notification container exceeds the viewport, a scroll bar will allow the user to navigate through the notifications.

Impact

Handling of multiple notifications has been improved by allowing to scroll and clear all notifications at once.

Feature: #104085 - Edit specific columns of multiple records in List module

See forge#104085

Description

Using the "Show columns" button on a record table in the Web > List backend module allows to select the columns to be displayed for the corresponding table listing.

When selecting multiple records, it has already been possible to edit all those records at once, using the "Edit" button in the table header.

Now, a new button "Edit columns" has been introduced, which additionally allows to access the editing form for the selected records with just the columns of the current selection (based on "Show columns"). This improves the usability when doing mass editing of specific columns.

Impact

It's now possible to edit the columns of multiple records in the Web > List backend module, using the new "Edit columns" button.

Feature: #104095 - Edit specific columns of multiple files in Filelist module

See forge#104095

Description

Using the "Show columns" action in the File > Filelist backend module allows to select the columns to be displayed file and folder listing.

When selecting multiple files, it has already been possible to edit the metadata of all those records at once, using the "Edit Metadata" button above the listing.

Now, a new button "Edit selected columns" has been introduced, which additionally allows to access the editing form for the selected files with just the columns of the current selection (based on "Show columns"). This improves the usability when doing mass editing of specific columns.

Impact

It's now possible to edit selected columns of multiple file metadata in the File > Filelist backend module, using the new "Edit selected columns" button.

Feature: #104114 - Command to generate Fluid schema files

See forge#104114

Description

With Fluid Standalone 2.12, a new implementation of the XSD schema generator has been introduced, which was previously a separate composer package. These XSD files allow IDEs to provide autocompletion for ViewHelper arguments in Fluid templates, provided that they are included in the template by using the xmlns syntax:

<html
    xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
    xmlns:my="http://typo3.org/ns/Vendor/MyPackage/ViewHelpers"
    data-namespace-typo3-fluid="true"
>
Copied!

A new CLI command has been defined to apply Fluid's new schema generator to TYPO3's Fluid integration. New Fluid APIs are used to find all ViewHelpers that exist in the current project (based on the composer autoloader). Then, TYPO3's configuration is checked for any merged Fluid namespaces (like f:, which consists of both Fluid Standalone and EXT:fluid ViewHelpers which in some cases override each other).

After that consolidation, *.xsd files are created in var/transient/ using another API from Fluid Standalone. These files which will automatically get picked up by supporting IDEs (like PhpStorm) to provide autocompletion in template files.

Impact

To get autocompletion for all available ViewHelpers in supporting IDEs, the following CLI command can be executed in local development environments:

vendor/bin/typo3 fluid:schema:generate
Copied!

Feature: #104220 - Make parseFunc allowTags and denyTags optional

See forge#104220

Description

Defining the TypoScript properties allowTags or denyTags for the HTML processing via stdWrap.parseFunc is now optional.

Besides that, it is now possible to use allowTags = *.

Impact

By omitting allowTags or denyTags, the corresponding rendering instructions can be simplified. Security aspects are considered automatically by the HTML sanitizer, unless htmlSanitize is disabled explicitly.

Examples

10 = TEXT
10.value = <p><em>Example</em> <u>underlined</u> text</p>
10.parseFunc = 1
10.parseFunc {
  allowTags = *
  denyTags = u
}
Copied!

The example above allows any tag, except <u> which will be encoded.

10 = TEXT
10.value = <p><em>Example</em> <u>underlined</u> text</p>
10.parseFunc = 1
10.parseFunc {
  allowTags = u
}
Copied!

The example above only allows <u> and encodes any other tag.

10 = TEXT
10.value = <p><em>Example</em> <u>underlined</u> text</p>
10.parseFunc = 1
10.parseFunc {
  allowTags = *
  denyTags = *
}
Copied!

The example above allows all tags, the new allowTags = * takes precedence over denyTags = *.

Feature: #104223 - Update Fluid Standalone to 2.12

See forge#104223

Description

Fluid Standalone has been updated to version 2.12. This version adds new capabilities for tab based ViewHelpers and adds the new ViewHelper f:constant.

Also see this deprecation document for information on deprecated functionality.

Impact

Arbitrary tags with tag based view helpers

Tag based view helpers (such as <f:image /> or <f:form.*>) can now receive arbitrary tag attributes which will be appended to the resulting HTML tag, without dedicated registration.

<f:form.textfield inputmode="tel" />
<f:image image="{image}" hidden="hidden" />
Copied!

New f:constant ViewHelper

A <f:constant> ViewHelper has been added to be able to access PHP constants from Fluid templates:

{f:constant(name: 'PHP_INT_MAX')}
{f:constant(name: '\Vendor\Package\Class::CONSTANT')}
{f:constant(name: '\Vendor\Package\Enum::CASE')}
Copied!

Deprecation: #102326 - RegularExpressionValidator validator option "errorMessage"

See forge#102326

Description

The errorMessage validator option provides a custom string as error message for validation failures of the RegularExpressionValidator. In order to streamline error message translation keys with other validators, the errorMessage validator option has been marked as deprecated in TYPO3 v13 and will be removed in TYPO3 v14.

Impact

Using the errorMessage validator option with the RegularExpressionValidator will trigger a PHP deprecation warning.

Affected installations

TYPO3 installations using the validator option errorMessage with the RegularExpressionValidator.

Migration

The new message validator option should be used to provide a custom translatable error message for failed validation.

Before:

use TYPO3\CMS\Extbase\Annotation as Extbase;

#[Extbase\Validate([
    'validator' => 'RegularExpression',
    'options' => [
        'regularExpression' => '/^simple[0-9]expression$/',
        'errorMessage' => 'Error message or LLL schema string',
    ],
])]
protected string $myProperty = '';
Copied!

After:

use TYPO3\CMS\Extbase\Annotation as Extbase;

#[Extbase\Validate([
    'validator' => 'RegularExpression',
    'options' => [
        'regularExpression' => '/^simple[0-9]expression$/',
        'message' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:my.languageKey'
    ],
])]
protected string $myProperty = '';
Copied!

Deprecation: #102337 - Deprecate hooks for record download

See forge#102337

Description

The previously used hooks $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvHeader'] and $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvRow'] , used to manipulate the download / export configuration of records, triggered in the Web > List backend module, have been deprecated in favor of a new PSR-14 event \TYPO3\CMS\Backend\RecordList\Event\BeforeRecordDownloadIsExecutedEvent .

Details for migration and functionality can be found in Feature: #102337 - PSR-14 event for modifying record list export data

Impact

When the hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvHeader'] or $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvRow'] is executed, this will trigger a PHP deprecation warning.

The extension scanner will find possible usages with a weak match.

Affected installations

All installations using $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvHeader'] or $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList']['customizeCsvRow'] are affected.

Migration

The new PSR-14 event \TYPO3\CMS\Backend\RecordList\Event\BeforeRecordDownloadIsExecutedEvent can be used as a near drop-in replacement.

Deprecation: #103752 - Obsolete $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields']

See forge#103752

Description

Configuration option $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'] is obsolete and has been removed in TYPO3 Core v13.2.

This option is well-known to integrators who add relations to the TCA pages table. It triggers relation resolving of page relations for additional fields when rendering a frontend request in the default language. The most common usage is TypoScript "slide".

Impact

Integrators can simply forget about this option: relations of table pages are now resolved with nearly no performance penalty in comparison to not having them resolved.

Affected installations

Many instances add additional relations to the pages table then add this field in addRootLineFields. This option is no longer evaluated. Relation fields attached to pages are always resolved in frontend.

There should be hardly any extensions using this option, since it was an internal option of class RootlineUtility. Extensions using $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'] may trigger a PHP warning level error because the array key has been removed. The extension scanner is configured to locate such usages.

Migration

The option is no longer evaluated in TYPO3 Core. It is removed from settings.php during upgrade, if given.

Deprecation: #103785 - Deprecate MathUtility::convertToPositiveInteger()

See forge#103785

Description

TYPO3 has the method MathUtility::convertToPositiveInteger() to ensure that an integer is always positive. However, the method is rather "heavy" as it calls MathUtility::forceIntegerInRange() internally and therefore misuses a clamp mechanism to convert the integer to a positive number.

Also, the method name doesn't reflect what the method actually does. Negative numbers are not converted to their positive counterpart, but are swapped with 0. Due to the naming issue and the fact that the method can be replaced by a simple max() call, the method is therefore deprecated.

Impact

Calling MathUtility::convertToPositiveInteger() will trigger a PHP deprecation warning.

Affected installations

All installations using MathUtility::convertToPositiveInteger().

Migration

To recover the original behavior of the deprecated method, its call can be replaced with max(0, $number). To actually convert negative numbers to their positive counterpart, call abs($number).

Deprecation: #103965 - Deprecate namespaced shorthand validator usage in Extbase

See forge#103965

Description

It is possible to use undocumented namespaced shorthand notation in Extbase to add validators to properties or arguments. For example, TYPO3.CMS.Extbase:NotEmpty will be resolved as \TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator and Vendor.Extension:Custom will be resolved as \Vendor\MyExtension\Validation\Validator\CustomValidator.

The namespaced shorthand notation for Extbase validators has been marked as deprecated and will be removed in TYPO3 v14.

Impact

Using namespaced shorthand notation in Extbase will trigger a PHP deprecation warning.

Affected installations

All installations using namespaced shorthand notation in Extbase.

Migration

Extensions using the namespaced shorthand notation must use the FQCN of the validator instead. For Extbase core validators, the well known shorthand validator name can be used.

Before

/**
 * @Extbase\Validate("TYPO3.CMS.Extbase:NotEmpty")
 */
protected $myProperty1;

/**
 * @Extbase\Validate("Vendor.Extension:Custom")
 */
protected $myProperty2;
Copied!

After

/**
 * @Extbase\Validate("NotEmpty")
 */
protected $myProperty1;

/**
 * @Extbase\Validate("Vendor\Extension\Validation\Validator\CustomValidator")
 */
protected $myProperty2;
Copied!

or

#[Extbase\Validate(['validator' => \TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator::class])]
protected $myProperty1;

#[Extbase\Validate(['validator' => \Vendor\Extension\Validation\Validator\CustomValidator::class])]
protected $myProperty2;
Copied!

Deprecation: #104108 - Table dependant definition of columnsOnly

See forge#104108

Description

When linking to the edit form it's possible to instruct the EditDocumentController to only render a subset of available fields for relevant records using the columnsOnly functionality, by adding the fields to be rendered as a comma-separated list.

However, the edit form can render records from many tables, and not just a single table, in the same request.

Therefore, the limit fields functionality has been extended to allow setting the fields to be rendered on a per-table basis. This means that passing a comma-separated list of fields as a value for columnsOnly has been deprecated.

Impact

Passing a comma-separated list of fields as value for columnsOnly will trigger a PHP deprecation warning. A compatibility layer will automatically set the field list for the required tables.

Affected installations

All installations passing a comma-separated list of fields as a value for columnsOnly.

Migration

The fields to be rendered have to be passed as an array under the corresponding table name.

An example, building such link using the UriBuilder:

$urlParameters = [
    'edit' => [
        'pages' => [
            1 => 'edit',
        ],
    ],
    'columnsOnly' => 'title,slug'
    'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(),
];

GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute('record_edit', $urlParameters);
Copied!

Above example has to be migrated to:

$urlParameters = [
    'edit' => [
        'pages' => [
            1 => 'edit',
        ],
    ],
    'columnsOnly' => [
        'pages' => [
            'title',
            'slug'
        ]
    ],
    'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(),
];

GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute('record_edit', $urlParameters);
Copied!

Additionally, when rendering records from many tables, a configuration could look like the following:

$urlParameters = [
    'edit' => [
        'pages' => [
            1 => 'edit',
        ],
        'tt_content' => [
            2 => 'edit',
        ],
    ],
    'columnsOnly' => [
        'pages' => [
            'title',
            'slug'
        ],
        'tt_content' => [
            'header',
            'subheader'
        ]
    ],
    'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri(),
];

// https:://example.com/typo3/record/edit?edit[pages][1]=edit&edit[tt_content][2]=edit&columnsOnly[pages][0]=title&columnsOnly[pages][1]=slug&columnsOnly[tt_content][0]=header&columnsOnly[tt_content][1]=subheader
$link = GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute('record_edit', $urlParameters);
Copied!

Deprecation: #104154 - Deprecate Utility.updateQueryStringParameter()

See forge#104154

Description

The Utility.updateQueryStringParameter() method in the @typo3/backend/utility.js module was introduced in TYPO3 v8 as a bugfix for highlighting in the old ExtJS-based page tree. Since removal of ExtJS in TYPO3 v9 the method has been unused.

Because safe removal of the method cannot be guaranteed as this point, it is therefore deprecated.

Impact

Calling Utility.updateQueryStringParameter() will result in a JavaScript warning.

Affected installations

All 3rd party extensions using the deprecated method.

Migration

Now, JavaScript supports the URL and its related URLSearchParams object that can be used to achieve the same result:

const url = new URL('http://localhost?baz=baz');
url.searchParams.set('baz', 'bencer');
const urlString = url.toString(); // http://localhost?baz=bencer
Copied!

Important: #101621 - Changed default value for twitter_card field

See forge#101621

Description

The default value of the twitter_card field of a page is now an empty string instead of summary.

Meta tag <meta name="twitter:card"> is only rendered if one of the following fields is filled in,

  • twitter_title
  • twitter_description
  • twitter_image
  • twitter_card
  • og_title
  • og_description
  • og_image

If no twitter card is selected, the fallback value is summary.

Important: #103485 - Provide lib.parseFunc via ext:frontend

See forge#103485

Description

The lib.parseFunc and lib.parseFunc_RTE functions render HTML from Rich Text Fields in TYPO3. Direct interaction with these libraries is now uncommon, but they control the output of the <f:format.html> ViewHelper.

Previously, the libraries were available only through content rendering definitions like fluid_styled_content, the bootstrap_package or your own packages.

The <f:format.html> ViewHelper requires a parseFunc to function and will throw an exception if none is provided. With the shift towards self-contained content elements, also known as content blocks, there is no need to include a separate rendering definition. The frontend provides a base version of the libraries, which are now available in the frontend context.

The libraries are loaded early in the TypoScript chain, ensuring that all existing overrides continue to work as before, without the need for a basic parseFunc definition.

Important: #103748 - Reference index rebuild required

See forge#103748

Description

A series of new columns has been added to the reference index table sys_refindex. This requires a rebuild of the table. All instances must update the reference index when upgrading.

In TYPO3 v13 the reference index has become more important. Most notably, it is used in the frontend for performance improvements. This requires a valid index and keeping it up-to-date after deployments is mandatory to avoid incorrect data during frontend and backend processing.

After deployment and initial rebuild, the index is kept up-to-date automatically by the DataHandler when changing records in the backend .

In general, updating the reference index is required when database relations that are defined in TCA change - typically when adding, removing or changing extensions, and after TYPO3 Core updates (also patch level).

It is strongly recommended to update the reference index after deployments. Note TYPO3 v13 has optimized this operation - a full update is usually much quicker than with previous versions.

The recommended way to rebuild and fully update the reference index is the CLI command:

bin/typo3 referenceindex:update
Copied!

If CLI can not be used, the reference index can be updated in the backend using the "DB check" module in the "typo3/cms-lowlevel" extension. Since the update process may take a while, PHP web processes may time out during this operation, which makes this backend interface suitable for small sized instances only.

Important: #103915 - Adjust database field defaults for "check" TCA types

See forge#103915

Description

TYPO3 v13.0 has introduced automatic database field creation for TCA fields configured as type "check" (if not explicitly defined in ext_tables.sql), via https://review.typo3.org/c/Packages/TYPO3.CMS/+/80513.

This conversion applied a default 0 to all fields, and did not evaluate the actual TCA definition for the ['config']['default'] setting.

This bug has been fixed, and the DB schema analyzer will now convert all the following fields to their proper default settings:

  • be_users.options (0->3)
  • sys_file_storage.is_browsable (0->1)
  • sys_file_storage.is_writable (0->1)
  • sys_file_storage.is_online (0->1)
  • sys_file_storage.auto_extract_metadata (0->1)
  • sys_file_metadata.visible (0->1)
  • tt_content.sectionIndex (0->1)
  • tx_styleguide_palette.palette_1_1 (0->1)
  • tx_styleguide_palette.palette_1_3 (0->1)
  • tx_styleguide_valuesdefault.checkbox_1 (0->1)
  • tx_styleguide_valuesdefault.checkbox_2 (0->1)
  • tx_styleguide_valuesdefault.checkbox_3 (0->5)
  • tx_styleguide_valuesdefault.checkbox_4 (0->5)
  • sys_workspace.edit_allow_notificaton_settings (0->3)
  • sys_workspace.edit_notification_preselection (0->2)
  • sys_workspace.publish_allow_notificaton_settings (0->3)
  • sys_workspace.publish_notification_preselection (0->1)
  • sys_workspace.execute_allow_notificaton_settings (0->3)
  • sys_workspace.execute_notification_preselection (0->3)
  • sys_workspace_stage.allow_notificaton_settings (0->3)
  • sys_workspace_stage.notification_preselection (0->8)

All these records, created via DataHandler calls, actually evaluate the TCA default for record insertion and do not rely on SQL database field defaults.

Only records created using the QueryBuilder or other "raw" database calls would apply the wrong values.

An example of this is TYPO3\CMS\Core\Resource\StorageRepository->createLocalStorage() which creates a default fileadmin record via the QueryBuilder and then sets the field auto_extract_metadata to 0, instead of 1 as would be expected in the TCA. This would mean YouTube files would not automatically fetch metadata on creation.

This means, for all custom extension code that

  • removed the column definition in ext_tables.sql to enforce automatic database field creation,
  • and did not use the recommended DataHandler for record insertion (so, any code that is not executed in the backend context, using QueryBuilder or Extbase repository methods),
  • and expects a different default than 0 for newly created records,
  • and relied on the database field definition default

this code may have created incorrect database records for versions between TYPO3 v13.0 and 13.2.

For TYPO3 Core code, this has only affected:

  • Default file storage creation, field sys_file_metadata.auto_extract_metadata
  • Default backend user creation (admin) property be_users.options

In these rare case, the database record integrity needs to be checked manually, because there are no automated tools to see if a record has used SQL default values or specifically defined values.

Important: #104037 - Backend module "Access" renamed to "Permissions"

See forge#104037

Description

The TYPO3 backend module "Access" has been renamed to "Permissions".

This accurately reflects the purpose of this module and improves consistency in the TYPO3 backend.

Important: #104153 - About database error "Row size too large"

See forge#104153

Description

Introduction

MySQL and MariaDB database engines sometimes generate a "Row size too large" error when modifying the schema of tables with many columns. This document aims to provide a detailed explanation of this error and presents solutions for TYPO3 instance maintainers to fix it.

Note that TYPO3 Core v13 has implemented measures to mitigate this error in most scenarios. Therefore, instance maintainers typically do not need to be aware of the specific details outlined below.

Preface

Firstly, it is important to recognize that there are two different error messages that appear similar but have distinct root causes and potentially opposite solution strategies. This will be elaborated on later in this document.

Secondly, we will not cover all possible variations of these errors, but will focus on a subset most relevant to TYPO3. Therefore, later sections of the document are very specific. Correctly following the instructions may already resolve the issue for instances running a different setup.

The issue is most likely to occur with the database table tt_content, as this table is often extended with many additional columns, increasing the likelihood of encountering the error. This document uses table tt_content in code examples. However, the solution strategies are applicable to other tables as well by adjusting the code examples below.

Ensure storage engine is 'InnoDB'

TYPO3 typically utilizes the InnoDB storage engine for tables in MySQL / MariaDB databases. However, instances upgraded from older TYPO3 Core versions might still employ different storage engines for some tables.

TYPO3 Core provides an automatic migration within Admin Tools > Maintenance > Analyze Database Structure and will suggest to migrate all tables to InnoDB.

You can manually verify the engine currently in use:

SELECT `TABLE_NAME`,`ENGINE`
FROM `information_schema`.`TABLES`
WHERE `TABLE_SCHEMA`='my_database'
AND `TABLE_NAME`='tt_content';
Copied!

Tables not using InnoDB should be converted via Admin Tools > Maintenance > Analyze Database Structure or manually via SQL:

USE `my_database`;
ALTER TABLE `tt_content` ENGINE=InnoDB;
Copied!

Ensure InnoDB row format is 'Dynamic'

The InnoDB row format dictates how data is physically stored. The Dynamic row format provides better support for tables with many variable-length columns and has been the default format for some time. However, instances upgraded from older TYPO3 Core versions and older MySQL / MariaDB engines might still use the previous default format Compact.

TYPO3 Core provides an automatic migration within Admin Tools > Maintenance > Analyze Database Structure and will suggest to migrate all tables to ROW_FORMAT=DYNAMIC.

You can manually verify the row format currently in use:

SELECT `TABLE_NAME`,`Row_format`
FROM `information_schema`.`TABLES`
WHERE `TABLE_SCHEMA`='my_database'
AND `TABLE_NAME`='tt_content';
Copied!

Tables not using Dynamic should be converted via Admin Tools > Maintenance > Analyze Database Structure or manually via SQL:

USE 'my_database`;
ALTER TABLE `tt_content` ROW_FORMAT=DYNAMIC;
Copied!

Database, table and column charset

The column charset impacts length calculations. This document assumes utf8mb4 for columns, aligning with the default TYPO3 setup. Converting an existing instance to utf8mb4 can be a complex task depending on the currently used charset and is beyond the scope of this document.

A key point about utf8mb4 is that when dealing with the utf8mb4 charset for VARCHAR() columns, storage and index calculations need to be multiplied by four (4). For example, a VARCHAR(20) can take up to eighty (80) bytes since each of the twenty (20) characters can use up to four (4) bytes. In contrast, a VARCHAR(20) in a latin1 column will consume only twenty (20) bytes, as each character is only one byte long.

The TYPO3 Core may set individual columns to a charset like latin1 in the future, which will optimize storage for ASCII-character only columns, but most content-related columns should be utf8mb4 to avoid issues with multi-byte characters.

Note that column types that do not store characters (like INT) do not have a charset. An overview of current charsets can be retrieved:

# Default charset of the database, new tables use this charset when no
# explicit charset is given with a "CREATE TABLE" statement:
SELECT `SCHEMA_NAME`, `DEFAULT_CHARACTER_SET_NAME` FROM `INFORMATION_SCHEMA`.`SCHEMATA`
WHERE `SCHEMA_NAME`='my_database';

# Default charset of a table, new columns use this charset when no
# explicit charset is given with a "ALTER TABLE" statement:
SELECT `table`.`table_name`,`charset`.`character_set_name`
FROM `information_schema`.`TABLES` AS `table`,`information_schema`.`COLLATION_CHARACTER_SET_APPLICABILITY` AS `charset`
WHERE `charset`.`collation_name`=`table`.`table_collation`
AND `table`.`table_schema`='my_database'
AND `table`.`table_name`='tt_content';

# List table columns, their column types with length and selected charsets:
SELECT `column_name`,`column_type`,`character_set_name`
FROM `information_schema`.`COLUMNS`
WHERE `table_schema`='my_database'
AND `table_name`='tt_content';
Copied!

Ensure innodb_page_size is 16384

Few instances modify the MySQL / MariaDB innodb_page_size system variable, and it is advisable to keep the default value of 16384. Verify the current value:

SHOW variables WHERE `Variable_name`='innodb_page_size';
Copied!

Row size too large

This document now assumes that MySQL / MariaDB is used, the table in question uses the InnoDB storage engine with Dynamic row format (please check Admin Tools > Maintenance > Analyze Database Structure which provides automatic migrations), innodb_page_size default 16384 is set, and that a system maintainer is aware of specific column charsets.

Error "Row size too large 65535"

ERROR 1118 (42000): Row size too large. The maximum row size for the used table type,
not counting BLOBs, is 65535. This includes storage overhead, check the manual. You
have to change some columns to TEXT or BLOBs
Copied!

Explanation

When altering the database schema of a table, such as adding or increasing the size of a VARCHAR column, the above error might occur.

Note the statement: "The maximum row size [...] is 65535".

MySQL / MariaDB impose a global maximum size per table row of 65kB. The combined length of all column types contribute to this limit, except for TEXT and BLOB types, which are stored "off row" where only a "pointer" to the actual storage location counts.

However, standard VARCHAR fields contribute their full maximum byte length towards this 65kB limit. For instance, a VARCHAR(2048) column with the utf8mb4 character set (4 bytes per character) requires 4 * 2048 = 8192 bytes. Therefore, only 65535 - 8192 = 57343 bytes remain available for the storage of all other table columns.

As another example, consider the query below which creates a table with a VARCHAR(16383) column alongside an INT column:

# ERROR 1118 (42000): Row size too large. The maximum row size [...] is 65535
CREATE TABLE test (c1 varchar(16383), c2 int) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Copied!

Let's break down the calculation:

varchar 16383 characters = 16383 * 4 bytes = 65532 bytes
int = 4 bytes
Total: 65532 + 4 = 65536 bytes
Copied!

This exceeds the maximum limit by one byte, causing the query to fail.

Mitigation

The primary strategy to mitigate the 65kB limit is to minimize the use of lengthy VARCHAR columns.

For instance, in the tt_content table of a default Core instance, there are approximately a dozen VARCHAR(255) columns, totaling about 12kB, alongside smaller INT and similar fields. This leaves ample room for additional custom VARCHAR() columns.

TYPO3 v13 has introduced improvements in two key areas:

Firstly, TCA fields with type='link' and type='slug' have been converted from VARCHAR(2048) (requiring 8kB of row space) to TEXT. The tt_content table was affected by this change in at least one column ( header_link). This adjustment provides more space by default for custom columns.

Additionally, the TYPO3 Core now defaults to using TEXT instead of VARCHAR() for TCA fields with type='input' when the TCA property max is set to a value greater than 255 and extension authors utilize the column auto creation feature.

Instances encountering the 65kB limit can consider adjusting fields with these considerations in mind:

  • Priority should be given to reconsidering long VARCHAR() columns first. Changing a single utf8mb4 VARCHAR(2048) column to TEXT can free enough space for up to eight (8) utf8mb4 VARCHAR(255) columns.
  • Consider reducing the length of VARCHAR() columns. For instance, columns containing database table or column names can be limited to VARCHAR(64), as MySQL / MariaDB restricts table and column names to a maximum of 64 characters. Similar considerations apply to "short" content fields, such as a column storing an author's name or similar potentially limited length information.

    However, be cautious, as setting VARCHAR() columns to "too short" lengths may impose a different limit, as discussed below.

  • Consider removing entries from ext_tables.sql with TYPO3 Core v13: the column auto creation feature generally provides better-defined column definitions and ensures columns stay synchronized with TCA definitions automatically. The TYPO3 Core aims to provide sensible default definitions, often superior to a potentially imprecise definition by extension authors.
  • Note that individual column definitions in ext_tables.sql always override TYPO3 Core v13's column auto creation feature. In rare cases where TYPO3 Core's definition is inappropriate, extension authors can always override these details.
  • Note utf8mb4 VARCHAR(255) and TINYTEXT are not the same: a VARCHAR(255) size limit is 255 characters, while a TINYTEXT is 255 bytes. The proper substitution for a (4 bytes per character) utf8mb4 VARCHAR(255) field is TEXT, which allows for 65535 bytes.
  • TEXT may negatively impact performance as it forces additional Input/Output operations in the database. This is typically not a significant issue with standard TYPO3 queries, as various other operations in TYPO3 have a greater impact on overall performance. However, indiscriminately changing all fields from VARCHAR() to TEXT or similar is not advisable.
  • Be mindful of indices. When VARCHAR() columns that are part of an index are changed to TEXT or similar, these indexes may require adjustment. Ensure they are properly restricted in length to avoid a "Specified key was too long" error. The InnoDB key length limit with row format Dynamic is 3072 bytes (not characters). In general, indexes on VARCHAR() and all other "longish" columns should be set with care and only if really needed since long indexes can negatively impact database performance as well, especially when a table has many write operations in production.

Error "Row size too large (> 8126)"

ERROR 1118 (42000): Row size too large (> 8126). Changing some columns to TEXT
or BLOB may help. In current row format, BLOB prefix of 0 bytes is stored inline.
Copied!

Sometimes there is also an error similar to this in MySQL / MariaDB logs:

[Warning] InnoDB: Cannot add field col1 in table db1.tab because after adding it,
the row size is 8478 which is greater than maximum allowed size (8126) for a record
on index leaf page.
Copied!

Explanation

This error may occur when adding or updating table rows, not only when altering table schema.

Note the statement: "Row size too large (> 8126)". This differs from the previous error message. This error is not about a general row size limit of 65535 bytes, but a limit imposed by InnoDB tables.

The root cause is that InnoDB has a maximum row size equivalent to half of the innodb_page_size system variable value of 16384 bytes, which is 8192 bytes.

InnoDB mitigates this by storing certain variable-length columns on "overflow pages". The decision regarding which columns are actually stored on overflow pages is made dynamically when adding or changing rows. This is why the error can be raised at runtime and not only when altering the schema. Additionally, it makes accurately predicting whether the error will occur challenging. Furthermore, not all variable-length columns can be stored on overflow pages. This is why the error can be raised when altering table schema.

Variable-length columns of type TEXT and BLOB can always be stored on overflow pages, thus minimally impacting the main data page limit of 8192 bytes. However, VARCHAR columns can only be stored on overflow pages if their maximum length exceeds 255 bytes. Therefore, an unexpected solution to the "Row size too large 8192" error in many cases is to increase the length of some variable-length columns, enabling InnoDB to store them on overflow pages.

Mitigation

TYPO3 Core v13 has modified several default columns to mitigate the issue for instances with many custom columns. The TYPO3 Core maintainers expect this issue to occur infrequently in practice.

Instances encountering the 8192 bytes limit can consider adjusting fields with these considerations in mind:

  • The calculation determining if a column can be stored on overflow pages is based on a minimum of 256 bytes, not characters. A typical utf8mb4 VARCHAR(255) equates to 1020 bytes, which can be stored on overflow pages. Changing such fields makes no difference.
  • Changing a utf8mb4 VARCHAR(63) (or smaller) to VARCHAR(64) (64 characters utf8mb4 = 256 bytes) allows this column to be stored on overflow pages and does make a difference.
  • Changing a utf8mb4 VARCHAR(63) (or smaller) to TINYTEXT should allow this column to be stored on overflow pages as well. However, this may not be the optimal solution due to potential performance penalties, as discussed earlier. Similarly, indiscriminately increasing the length of multiple variable-length columns is not advisable. Columns should ideally be kept as small as possible, only exceeding the 255-byte limit or converting to TEXT types if absolutely necessary. Also, refer to the note on indexes above when single columns are part of indexes.
  • Columns using utf8mb4 that are smaller or equal to VARCHAR(63) and only store ASCII characters can be downsized by changing the charset to latin1. For instance, a VARCHAR(60) column occupies 4 * 60 = 240 bytes in row size, but only 60 bytes when using the latin1 charset. Currently, TYPO3 Core does not interpret charset definitions for individual columns from ext_tables.sql. The Core Team anticipates implementing this feature in the future.
  • Note that increasing the length of VARCHAR columns can potentially conflict with the 65kB limit mentioned earlier. This is another reason to avoid indiscriminately increasing the length of variable-length columns.

Further reading

This document is based on information from database vendors and other sites found online. The following links may provide further insights:

Final words

Navigating the two limits in MySQL / MariaDB requires a deep understanding of database engine internals to manage them effectively. The TYPO3 Core Team is confident that version 13 has effectively mitigated the issue, ensuring that typical instances will rarely encounter it. We trust this document remains helpful and welcome any feedback in case something crucial has been overlooked.

13.1 Changes

Table of contents

Breaking Changes

None since TYPO3 v13.0 release.

Features

Deprecation

Important

Feature: #93942 - Crop SVG images natively

See forge#93942

Description

Cropping SVG images via backend image editing or specific Fluid ViewHelper via <f:image> or <f:uri.image> (via crop attribute) now outputs native SVG files by default - which are processed but again stored as SVG, instead of rasterized PNG/JPG images like before.

Impact

Editors and integrators can now crop SVG assets without an impact to their output quality.

Forced rasterization of cropped SVG assets can still be performed by setting the fileExtension="png" Fluid ViewHelper attribute or the TypoScript file.ext = png property.

<f:image> ViewHelper example:

<f:image image="{image}" fileExtension="png" />
Copied!

This keeps forcing images to be generated as PNG image.

file.ext = png TypoScript example:

page.10 = IMAGE
page.10.file = 2:/myfile.svg
page.10.file.crop = 20,20,500,500
page.10.file.ext = png
Copied!

If no special hard-coded option for the file extension is set, SVGs are now processed and stored as SVGs again.

Feature: #102836 - Allow deleting IRRE elements via postMessage()

See forge#102836

Description

To invoke a deletion on items in FormEngine's Inline Relation container API-wise, a new message identifier typo3:foreignRelation:delete has been introduced.

Example usage:

import { MessageUtility } from '@typo3/backend/utility/message-utility.js';

MessageUtility.send({
    actionName: 'typo3:foreignRelation:delete',
    objectGroup: 'data-<page_id>-<parent_table>-<parent_uid>-<reference_table>',
    uid: '<reference_uid>'
});
Copied!

Impact

Extension developers are now able to trigger the deletion of IRRE elements via API.

Feature: #103043 - Modernize tree rendering and implement RTL and dark mode

See forge#103043

Description

The Tree feature in TYPO3 stands as one of its most iconic and widely used components, offering a visual representation of site structures to editors globally. Serving various purposes, such as file management, category/record selection, navigation, and more, the Tree has been a cornerstone for content handling.

Originally introduced in Version 8 with a performance-oriented approach, the SVG tree, powered by d3js, has faithfully served the community for the past seven years. While it excelled in providing a fast and efficient experience, it had its share of challenges, particularly due to its reliance on SVG and d3js.

Challenges with the SVG tree:

  • Limited functionality due to SVG constraints
  • Maintenance complexities with code-built SVG
  • Accessibility challenges
  • Difficulty in extension and innovation
  • Lack of native drag and drop
  • Complexity hindering understanding for many

Recognizing these challenges, we embarked on a journey to reimagine the Tree component, paving the way for a more adaptable and user-friendly experience.

Introducing the Modern Reactive Tree:

The new Tree, built on contemporary web standards, bids farewell to the SVG tree's limitations. Embracing native drag and drop APIs, standard HTML markup, and CSS for styling, the Modern Reactive Tree promises improved maintainability and accessibility.

Key enhancements:

  • Unified experience: All features are now consolidated into the base tree, ensuring a seamless and consistent user experience. This encompasses data loading and processing, selection, keyboard navigation, drag and drop, and basic node editing.
  • User preferences: The tree now dynamically adjusts to user preferences, supporting both light/dark mode and left-to-right (LTR) or right-to-left (RTL) writing modes.
  • Reactive rendering: Adopting a modern reactive rendering approach, the tree and its nodes now autonomously redraw themselves based on property changes, ensuring a smoother and more responsive interface.
  • Native drag and drop: Leveraging native drag and drop functionality opens up avenues for future enhancements, such as dragging content directly onto a page or seamlessly moving elements between browser windows.
  • Improved API endpoints: All endpoints delivering data for the tree now adhere to a defined API definition, enhancing consistency and compatibility with existing integrations.
  • Unified dragging tooltip handling: The dragging tooltip handling has been adjusted to a unified component that can be utilized across all components, ensuring synchronization across browser windows.
  • Dynamic tree status storage: The Pagetree status is no longer stored in the database. Instead, it is now stored in the local storage of the user's browser. This change empowers the browser to control the tree status, making it more convenient for users to transition between multiple browsers or machines.
  • Enhanced virtual scroll: The virtual scroll of the tree has been improved, ensuring that only nodes currently visible to the user are rendered to the DOM. Additionally, the focus on selected nodes is maintained even when scrolled out of view, providing a smoother and more user-friendly experience.

As we transition to this Modern Reactive Tree, we anticipate a renewed era of flexibility, ease of use, and potential for exciting future features.

Impact

The TYPO3 CMS Tree modernization brings:

  • Personalization: Adapts to user preferences for a tailored interface.
  • Reactive design: Ensures smoother interactions.
  • Efficient integration: Improved API endpoints for seamless data exchange.
  • Consistency across devices: Unified dragging and dynamic storage for a consistent experience.
  • Enhanced performance: Optimal rendering during navigation.

These changes collectively enhance usability, adaptability, and performance, elevating the TYPO3 CMS Tree experience.

Feature: #103147 - Provide full userdata in password recovery email in ext:backend

See forge#103147

Description

A new array variable {userData} has been added to the password recovery FluidEmail object. It contains the values of all fields from the be_users table belonging to the affected backend user.

Impact

It is now possible to use the {userData} variable in the password recovery FluidEmail to access data from the affected backend user.

Feature: #103186 - Introduce tree node status information

See forge#103186

Description

We've enhanced the backend tree component by extending tree nodes to incorporate status information. These details serve to indicate the status of nodes and provide supplementary information.

For instance, if a page undergoes changes within a workspace, it will now display an indicator on the respective tree node. Additionally, the status is appended to the node's title. This enhancement not only improves visual clarity but also enhances information accessibility.

Each node can accommodate multiple status information, prioritized by severity and urgency. Critical messages take precedence over other status notifications.

For example, status information can be added by using the event \TYPO3\CMS\Backend\Controller\Event\AfterPageTreeItemsPreparedEvent :

EXT:my_extension/Classes/Backend/EventListener/ModifyPageTreeItems.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Backend\EventListener;

use TYPO3\CMS\Backend\Controller\Event\AfterPageTreeItemsPreparedEvent;
use TYPO3\CMS\Backend\Dto\Tree\Label\Label;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener(
    identifier: 'my-extension/backend/modify-page-tree-items',
)]
final readonly class ModifyPageTreeItems
{
    public function __invoke(AfterPageTreeItemsPreparedEvent $event): void
    {
        $items = $event->getItems();
        foreach ($items as &$item) {
            if ($item['_page']['uid'] === 123) {
                $item['statusInformation'][] = new StatusInformation(
                    label: 'A warning message',
                    severity: ContextualFeedbackSeverity::WARNING,
                    priority: 0,
                    icon: 'actions-dot',
                    overlayIcon: '',
                );
            }
        }
        $event->setItems($items);
    }
}
Copied!

Impact

Tree nodes can now have status information. Workspace changes are now reflected in the title of the node in addition to the indicator.

Feature: #103187 - Introduce CLI command to create backend user groups

See forge#103187

Description

A new CLI command ./bin/typo3 setup:begroups:default has been introduced as an alternative to the existing backend module. This command automates the creation of backend user groups, enabling the creation of two pre-configured backend user groups with permission presets applied.

Impact

You can now use ./bin/typo3 setup:begroups:default to create pre-configured backend user groups without touching the GUI.

Example

Interactive / guided setup (questions/answers):

Basic command
./bin/typo3 setup:begroups:default
Copied!

The backend user group can be set via the --groups|-g option. Allowed values for groups are Both, Editor and Advanced Editor:

Command examples
./bin/typo3 setup:begroups:default --groups Both
./bin/typo3 setup:begroups:default --groups Editor
./bin/typo3 setup:begroups:default --groups "Advanced Editor"
Copied!

When using the --no-interaction option, this defaults to Both.

Feature: #103211 - Introduce tree node labels

See forge#103211

Description

We've upgraded the backend tree component by extending tree nodes to incorporate labels, offering enhanced functionality and additional information.

Before the implementation of labels, developers and integrators relied on pageTree.backgroundColor.<pageid> for visual cues, which has been deprecated with TYPO3 v13. However, these background colors lacked accessibility and meaningful context, catering only to users with perfect eyesight and excluding those dependent on screen readers or contrast modes.

With labels, we now cater to all editors. These labels not only offer customizable color markings for tree nodes but also require an associated label for improved accessibility.

Each node can support multiple labels, sorted by priority, with the highest priority label taking precedence over others. Users can assign a label to a node via user TSconfig, noting that only one label can be set through this method.

EXT:my_extension/Configuration/user.tsconfig
options.pageTree.label.<pageid> {
    label = Campaign A
    color = #ff8700
}
Copied!

The labels can also be added by using the event \TYPO3\CMS\Backend\Controller\Event\AfterPageTreeItemsPreparedEvent .

EXT:my_extension/Classes/Backend/EventListener/ModifyPageTreeItems.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Backend\EventListener;

use TYPO3\CMS\Backend\Controller\Event\AfterPageTreeItemsPreparedEvent;
use TYPO3\CMS\Backend\Dto\Tree\Label\Label;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener(
    identifier: 'my-extension/backend/modify-page-tree-items',
)]
final readonly class ModifyPageTreeItems
{
    public function __invoke(AfterPageTreeItemsPreparedEvent $event): void
    {
        $items = $event->getItems();
        foreach ($items as &$item) {
            // Add special label for all pages with parent page ID 123
            if (($item['_page']['pid'] ?? null) === 123) {
                $item['labels'][] = new Label(
                    label: 'Campaign B',
                    color: '#00658f',
                    priority: 1,
                );
            }
        }
        $event->setItems($items);
    }
}
Copied!

Please note that only the marker for the label with the highest priority is rendered. All additional labels will only be added to the title of the node.

Impact

Labels are now added to the node and their children, significantly improving the clarity and accessibility of the tree component.

Feature: #103220 - Support comma-separated lists in page tree filter

See forge#103220

Description

The page tree has been enhanced to enable the user to not only search for strings and single page IDs, but for comma-separated lists of page IDs as well.

Feature: #103255 - Native support for language Scottish Gaelic added

See forge#103255

Description

TYPO3 now supports Scottish Gaelic. Scottish Gaelic language is spoken in Scotland.

The ISO 639-1 code for Scottish Gaelic is "gd", which is how TYPO3 accesses the language internally.

Impact

It is now possible to

  • Fetch translated labels from translations.typo3.org / CrowdIn automatically within the TYPO3 backend.
  • Switch the backend interface to Scottish Gaelic language.
  • Create a new language in a site configuration using Scottish Gaelic.
  • Create translation files with the "gd" prefix (such as gd.locallang.xlf) to create your own labels.

TYPO3 will pick Scottish Gaelic as a language just like any other supported language.

Feature: #103309 - Add more expression methods to ExpressionBuilder

See forge#103309

Description

The TYPO3 \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder provides a relatively conservative set of database query expressions since a couple of TYPO3 and Doctrine DBAL versions now.

Additional expression methods are now available to build more advanced database queries that ensure compatibility across supported database vendors.

ExpressionBuilder::as()

Creates a statement to append a field alias to a value, identifier or sub-expression.

Method signature
/**
 * @param string $expression Value, identifier or expression which
 *                           should be aliased.
 * @param string $asIdentifier Used to add a field identifier alias
 *                             (`AS`) if non-empty string (optional).
 *
 * @return string   Returns aliased expression.
 */
public function as(
    string $expression,
    string $asIdentifier = '',
): string {}

// use TYPO3\CMS\Core\Database\Connection;
// use TYPO3\CMS\Core\Database\ConnectionPool;
// use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('some_table');
$expressionBuilder = $queryBuilder->expr();

$queryBuilder->selectLiteral(
  $queryBuilder->quoteIdentifier('uid'),
  $expressionBuilder->as('(1 + 1 + 1)', 'calculated_field'),
);

$queryBuilder->selectLiteral(
  $queryBuilder->quoteIdentifier('uid'),
  $expressionBuilder->as(
    $expressionBuilder->concat(
        $expressionBuilder->literal('1'),
        $expressionBuilder->literal(' '),
        $expressionBuilder->literal('1'),
    ),
    'concatenated_value'
  ),
);
Copied!

ExpressionBuilder::concat()

Can be used to concatenate values, row field values or expression results into a single string value.

Method signature
/**
 * @param string ...$parts      Unquoted value or expression parts to
 *                              concatenate with each other
 * @return string  Returns the concatenation expression compatible with
 *                 the database connection platform.
 */
public function concat(string ...$parts): string {}
Copied!
Usage example
// use TYPO3\CMS\Core\Database\Connection;
// use TYPO3\CMS\Core\Database\ConnectionPool;
// use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('pages');
$expressionBuilder = $queryBuilder->expr();
$result = $queryBuilder
    ->select('uid', 'pid', 'title')
    ->addSelectLiteral(
        $expressionBuilder->concat(
            $queryBuilder->quoteIdentifier('title'),
            $queryBuilder->quote(' - ['),
            $queryBuilder->quoteIdentifier('uid'),
            $queryBuilder->quote('|'),
            $queryBuilder->quoteIdentifier('pid'),
            $queryBuilder->quote(']'),
        ) . ' AS ' . $queryBuilder->quoteIdentifier('page_title_info')
    )
    ->where(
        $expressionBuilder->eq(
            'pid',
            $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
        ),
    )
    ->executeQuery();

while ($row = $result->fetchAssociative()) {
    // $row = array{
    //  'uid' => 1,
    //  'pid' => 0,
    //  'title' => 'Site Root Page',
    //  'page_title_info' => 'Site Root Page - [1|0]',
    // }
}
Copied!

ExpressionBuilder::castVarchar()

Can be used to create an expression which converts a value, row field value or the result of an expression to varchar type with dynamic length.

Method signature
/**
 * @param string    $value          Unquoted value or expression,
 *                                  which should be casted.
 * @param int       $length         Dynamic varchar field length.
 * @param string    $asIdentifier   Used to add a field identifier alias
 *                                  (`AS`) if non-empty string (optional).
 * @return string   Returns the cast expression compatible for the database platform.
 */
public function castVarchar(
    string $value,
    int $length = 255,
    string $asIdentifier = '',
): string {}
Copied!
Usage example
// use TYPO3\CMS\Core\Database\ConnectionPool;
// use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('some_table');

$fieldVarcharCastExpression = $queryBuilder->expr()->castVarchar(
    $queryBuilder->quote('123'), // integer as string
    255,                         // convert to varchar(255) field - dynamic length
    'new_field_identifier',
);

$fieldExpressionCastExpression = $queryBuilder->expr()->castVarchar(
    '(100 + 200)',           // calculate a integer value
    100,                     // dynamic varchar(100) field
    'new_field_identifier',
);
Copied!

ExpressionBuilder::castInt()

Can be used to create an expression which converts a value, row field value or the result of an expression to signed integer type.

Method signature
/**
 * @param string    $value         Quoted value or expression result which
 *                                 should be casted to integer type.
 * @param string    $asIdentifier  Used to add a field identifier alias
 *                                 (`AS`) if non-empty string (optional).
 * @return string   Returns the integer cast expression compatible with the
 *                  connection database platform.
 */
public function castInt(string $value, string $asIdentifier = ''): string {}
Copied!
Usage example
// use TYPO3\CMS\Core\Database\ConnectionPool;
// use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('pages');
$queryBuilder
    ->select('uid')
    ->from('pages');

// simple value (quoted) to be used as sub-expression
$expression1 = $queryBuilder->expr()->castInt(
    $queryBuilder->quote('123'),
);

// simple value (quoted) to return as select field
$queryBuilder->addSelectLiteral(
    $queryBuilder->expr()->castInt(
        $queryBuilder->quote('123'),
        'virtual_field',
    ),
);

$expression3 = queryBuilder->expr()->castInt(
  $queryBuilder->quoteIdentifier('uid'),
);

// expression to be used as sub-expression
$expression4 = $queryBuilder->expr()->castInt(
    $queryBuilder->expr()->castVarchar('(1 * 10)'),
);

// expression to return as select field
$queryBuilder->addSelectLiteral(
    $queryBuilder->expr()->castInt(
        $queryBuilder->expr()->castVarchar('(1 * 10)'),
        'virtual_field',
    ),
);
Copied!

ExpressionBuilder::repeat()

Create a statement to generate a value repeating defined $value for $numberOfRepeats times. This method can be used to provide the repeat number as a sub-expression or calculation.

Method signature
/**
 * @param int|string    $numberOfRepeats    Statement or value defining
 *                                          how often the $value should
 *                                          be repeated. Proper quoting
 *                                          must be ensured.
 * @param string        $value              Value which should be repeated.
 *                                          Proper quoting must be ensured.
 * @param string        $asIdentifier       Provide `AS` identifier if not
 *                                          empty.
 * @return string   Returns the platform compatible statement to create the
 *                  x-times repeated value.
 */
public function repeat(
    int|string $numberOfRepeats,
    string $value,
    string $asIdentifier = '',
): string {}
Copied!
Usage example
// use TYPO3\CMS\Core\Database\ConnectionPool;
// use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('some_table');

$expression1 = $queryBuilder->expr()->repeat(
    10,
    $queryBuilder->quote('.'),
);

$expression2 = $queryBuilder->expr()->repeat(
    20,
    $queryBuilder->quote('0'),
    $queryBuilder->quoteIdentifier('aliased_field'),
);

$expression3 = $queryBuilder->expr()->repeat(
    20,
    $queryBuilder->quoteIdentifier('table_field'),
    $queryBuilder->quoteIdentifier('aliased_field'),
);

$expression4 = $queryBuilder->expr()->repeat(
    $queryBuilder->expr()->castInt(
        $queryBuilder->quoteIdentifier('repeat_count_field')
    ),
    $queryBuilder->quoteIdentifier('table_field'),
    $queryBuilder->quoteIdentifier('aliased_field'),
);

$expression5 = $queryBuilder->expr()->repeat(
    '(7 + 3)',
    $queryBuilder->quote('.'),
);

$expression6 = $queryBuilder->expr()->repeat(
  '(7 + 3)',
  $queryBuilder->concat(
    $queryBuilder->quote(''),
    $queryBuilder->quote('.'),
    $queryBuilder->quote(''),
  ),
  'virtual_field_name',
);
Copied!

ExpressionBuilder::space()

Create statement containing $numberOfSpaces spaces.

Method signature
/**
 * @param int|string    $numberOfSpaces Expression or value defining how
 *                                      many spaces should be created.
 * @param string        $asIdentifier   Provide result as identifier field
 *                                      (AS), not added if empty string.
 * @return string   Returns the platform compatible statement to create the
 *                  x-times repeated space(s).
 */
public function space(
    string $numberOfSpaces,
    string $asIdentifier = '',
): string {}
Copied!
Usage example
// use TYPO3\CMS\Core\Database\ConnectionPool;
// use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('some_table');

$expression1 = $queryBuilder->expr()->space(
    '10'
);

$expression2 = $queryBuilder->expr()->space(
    '20',
    $queryBuilder->quoteIdentifier('aliased_field'),
);

$expression3 = $queryBuilder->expr()->space(
    '(210)'
);

$expression3 = $queryBuilder->expr()->space(
    '(210)',
    $queryBuilder->quoteIdentifier('aliased_field'),
);

$expression5 = $queryBuilder->expr()->space(
    $queryBuilder->expr()->castInt(
        $queryBuilder->quoteIdentifier('table_repeat_number_field'),
    ),
);

$expression6 = $queryBuilder->expr()->space(
    $queryBuilder->expr()->castInt(
        $queryBuilder->quoteIdentifier('table_repeat_number_field'),
    ),
    $queryBuilder->quoteIdentifier('aliased_field'),
);
Copied!

ExpressionBuilder::left()

Extract $length character of $value from the left side.

Method signature
/**
 * @param int|string    $length         Integer value or expression
 *                                      providing the length as integer.
 * @param string        $value          Value, identifier or expression
 *                                      defining the value to extract from
 *                                      the left.
 * @param string        $asIdentifier   Provide `AS` identifier if not empty.
 * @return string   Return the expression to extract defined substring
 *                  from the right side.
 */
public function left(
    int|string $length,
    string $value,
    string $asIdentifier = '',
): string {}
Copied!
Usage example
// use TYPO3\CMS\Core\Database\ConnectionPool;
// use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('some_table');

$expression1 = $queryBuilder->expr()->left(
    6,
    $queryBuilder->quote('some-string'),
);

$expression2 = $queryBuilder->expr()->left(
    '6',
    $queryBuilder->quote('some-string'),
);

$expression3 = $queryBuilder->expr()->left(
    $queryBuilder->castInt('(23)'),
    $queryBuilder->quote('some-string'),
);

$expression4 = $queryBuilder->expr()->left(
    $queryBuilder->castInt('(23)'),
    $queryBuilder->quoteIdentifier('table_field_name'),
);
Copied!

ExpressionBuilder::right()

Extract $length character of $value from the right side.

Method signature
/**
 * @param int|string    $length         Integer value or expression
 *                                      providing the length as integer.
 * @param string        $value          Value, identifier or expression
 *                                      defining the value to extract from
 *                                      the right.
 * @param string        $asIdentifier   Provide `AS` identifier if not empty.
 *
 * @return string   Return the expression to extract defined substring
 *                  from the right side.
 */
public function right(
    int|string $length,
    string $value,
    string $asIdentifier = '',
): string {}
Copied!
Usage example
// use TYPO3\CMS\Core\Database\ConnectionPool;
// use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('some_table');

$expression1 = $queryBuilder->expr()->right(
    6,
    $queryBuilder->quote('some-string'),
);

$expression2 = $queryBuilder->expr()->right(
    '6',
    $queryBuilder->quote('some-string'),
);

$expression3 = $queryBuilder->expr()->right(
    $queryBuilder->castInt('(23)'),
    $queryBuilder->quote('some-string'),
);

$expression4 = $queryBuilder->expr()->right(
    $queryBuilder->castInt('(23)'),
    $queryBuilder->quoteIdentifier('table_field_name'),
);
Copied!

ExpressionBuilder::leftPad()

Left-pad the value or sub-expression result with $paddingValue, to a total length of $length.

Method signature
/**
 * @param string        $value          Value, identifier or expression
 *                                      defining the value which should
 *                                      be left padded.
 * @param int|string    $length         Value, identifier or expression
 *                                      defining the padding length to
 *                                      fill up on the left or crop.
 * @param string        $paddingValue   Padding character used to fill
 *                                      up if characters are missing on
 *                                      the left side.
 * @param string        $asIdentifier   Used to add a field identifier alias
 *                                      (`AS`) if non-empty string (optional).
 * @return string   Returns database connection platform compatible
 *                  left-pad expression.
 */
public function leftPad(
    string $value,
    int|string $length,
    string $paddingValue,
    string $asIdentifier = '',
): string {}
Copied!
Usage example
// use TYPO3\CMS\Core\Database\ConnectionPool;
// use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('some_table');

$expression1 = $queryBuilder->expr()->leftPad(
    $queryBuilder->quote('123'),
    10,
    '0',
);

$expression2 = $queryBuilder->expr()->leftPad(
    $queryBuilder->expr()->castVarchar($queryBuilder->quoteIdentifier('uid')),
    10,
    '0',
);

$expression3 = $queryBuilder->expr()->leftPad(
    $queryBuilder->expr()->concat(
        $queryBuilder->quote('1'),
        $queryBuilder->quote('2'),
        $queryBuilder->quote('3'),
    ),
    10,
    '0',
);

$expression4 = $queryBuilder->expr()->leftPad(
    $queryBuilder->castVarchar('( 1123 )'),
    10,
    '0',
);

$expression5 = $queryBuilder->expr()->leftPad(
    $queryBuilder->castVarchar('( 1123 )'),
    10,
    '0',
    'virtual_field',
);
Copied!

ExpressionBuilder::rightPad()

Right-pad the value or sub-expression result with $paddingValue, to a total length of $length.

Method signature
/**
 * @param string        $value          Value, identifier or expression
 *                                      defining the value which should be
 *                                      right padded.
 * @param int|string    $length         Value, identifier or expression
 *                                      defining the padding length to
 *                                      fill up on the right or crop.
 * @param string        $paddingValue   Padding character used to fill up
 *                                      if characters are missing on the
 *                                      right side.
 * @param string        $asIdentifier   Used to add a field identifier alias
 *                                      (`AS`) if non-empty string (optional).
 * @return string   Returns database connection platform compatible
 *                  right-pad expression.
 */
public function rightPad(
    string $value,
    int|string $length,
    string $paddingValue,
    string $asIdentifier = '',
): string {}
Copied!
Usage example
// use TYPO3\CMS\Core\Database\ConnectionPool;
// use TYPO3\CMS\Core\Utility\GeneralUtility;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('some_table');

$expression1 = $queryBuilder->expr()->rightPad(
    $queryBuilder->quote('123'),
    10,
    '0',
);

$expression2 = $queryBuilder->expr()->rightPad(
    $queryBuilder->expr()->castVarchar($queryBuilder->quoteIdentifier('uid')),
    10,
    '0',
 );

$expression3 = $queryBuilder->expr()->rightPad(
    $queryBuilder->expr()->concat(
        $queryBuilder->quote('1'),
        $queryBuilder->quote('2'),
        $queryBuilder->quote('3'),
    ),
    10,
    '0',
);

$expression4 = $queryBuilder->expr()->rightPad(
    $queryBuilder->castVarchar('( 1123 )'),
    10,
    '0',
);

$expression5 = $queryBuilder->expr()->rightPad(
    $queryBuilder->quote('123'),
    10,
    '0',
    'virtual_field',
);
Copied!

Impact

Extension authors can use the new expression methods to build more advanced queries without the requirement to deal with the correct implementation for all supported database vendors.

Feature: #103331 - Native support for language Maltese added

See forge#103331

Description

TYPO3 now supports Maltese. Maltese language is spoken in Malta.

The ISO 639-1 code for Maltese is "mt", which is how TYPO3 accesses the language internally.

Impact

It is now possible to

  • Fetch translated labels from translations.typo3.org / CrowdIn automatically within the TYPO3 backend.
  • Switch the backend interface to Maltese language.
  • Create a new language in a site configuration using Maltese.
  • Create translation files with the "mt" prefix (such as mt.locallang.xlf) to create your own labels.

TYPO3 will pick Maltese as a language just like any other supported language.

Feature: #103372 - Native support for language Irish Gaelic added

See forge#103372

Description

TYPO3 now supports Irish Gaelic. Irish Gaelic language is spoken in Ireland.

The ISO 639-1 code for Irish Gaelic is "ga", which is how TYPO3 accesses the language internally.

Impact

It is now possible to

  • Fetch translated labels from translations.typo3.org / CrowdIn automatically within the TYPO3 backend.
  • Switch the backend interface to Irish Gaelic language.
  • Create a new language in a site configuration using Irish Gaelic.
  • Create translation files with the "ga" prefix (such as ga.locallang.xlf) to create your own labels.

TYPO3 will pick Irish Gaelic as a language just like any other supported language.

Feature: #103437 - Introduce site sets

See forge#103437

Description

Site sets ship parts of site configuration as composable pieces. They are intended to deliver settings, TypoScript, TSconfig and reference enabled content blocks for the scope of a site.

Extensions can provide multiple sets in order to ship presets for different sites or subsets (think of frameworks) where selected features are exposed as a subset (example: typo3/seo-xml-sitemap).

A set is defined in an extension's subfolder in Configuration/Sets/, for example EXT:my_extension/Configuration/Sets/MySet/config.yaml.

The folder name in Configuration/Sets/ is arbitrary, significant is the name defined in config.yaml. The name uses a vendor/name scheme by convention, and should use the same vendor as the containing extension. It may differ if needed for compatibility reasons (e.g. when sets are moved to other extensions). If an extension provides exactly one set that should have the same name as defined in composer.json.

The config.yaml for a set that is composed of three subsets looks as follows:

EXT:my_extension/Configuration/Sets/MySet/config.yaml
name: my-vendor/my-set
label: My Set

# Load TypoScript, TSconfig and settings from dependencies
dependencies:
  - some-namespace/slider
  - other-namespace/fancy-carousel
Copied!

Sets are applied to sites via dependencies array in site configuration:

config/sites/my-site/config.yaml
base: 'http://example.com/'
rootPageId: 1
dependencies:
  - my-vendor/my-set
Copied!

Site sets can also be edited via the backend module Site Management > Sites.

A list of available site sets can be retrieved with the console command bin/typo3 site:sets:list.

Settings definitions

Sets can define settings definitions which contain more metadata than just a value: They contain UI-relevant options like label, description, category and tags and types like int, bool, string, stringlist, text or color. These definitions are placed in settings.definitions.yaml next to the site set file config.yaml.

The description can make use of markdown syntax for richtext formatting.

EXT:my_extension/Configuration/Sets/MySet/settings.definitions.yaml
settings:
  foo.bar.baz:
    label: 'My example baz setting'
    description: 'Configure `baz` to be used in `bar`.'
    type: int
    default: 5
Copied!

Settings for subsets

Settings for subsets (e.g. to configure settings in declared dependencies) can be shipped via settings.yaml when placed next to the set file config.yaml.

Note that default values for settings provided by the set do not need to be defined here, as defaults are to be provided within settings.definitions.yaml.

Here is an example where the setting styles.content.defaultHeaderType — as provided by typo3/fluid-styled-content — is configured via settings.yaml:

EXT:my_extension/Configuration/Sets/MySet/settings.yaml
styles:
  content:
    defaultHeaderType: 1
Copied!

This setting will be exposed as site setting whenever the set my-vendor/my-set is applied to a site configuration.

Hidden sets

Sets may be hidden from the backend set selection in Site Management > Sites and the console command bin/typo3 site:sets:list by adding a hidden flag to the config.yaml definition:

EXT:my_extension/Configuration/Sets/MyHelperSet/config.yaml
name: my-vendor/my-helperset
label: A helper Set that is not visible inside the GUI
hidden: true
Copied!

Integrators may choose to hide existing sets from the list of available sets for backend users via User TSConfig, in case only a curated list of sets shall be selectable:

EXT:my_extension/Configuration/user.tsconfig
options.sites.hideSets := addToList(typo3/fluid-styled-content)
Copied!

The Site Management > Sites GUI will not show hidden sets, but makes one exception if a hidden set has already been applied to a site (e.g. by manual modification of config.yaml). In this case a set marked as hidden will be shown in the list of currently activated sets (that means it can be introspected and removed via backend UI).

Impact

Sites can be composed of sets where relevant configuration, templates, assets and setting definitions are combined in a central place and applied to sites as one logical volume.

Sets have dependency management and therefore allow sharing code between multiple TYPO3 sites and extensions in a flexible way.

Feature: #103439 - TypoScript provider for sites and sets

See forge#103439

Description

TYPO3 sites have been enhanced to be able to operate as TypoScript template provider. They act similar to sys_template records with "clear" and "root" flags set. By design a site TypoScript provider always defines a new scope ("root" flag) and does not inherit from parent sites (for example, sites up in the root line). That means it behaves as if the "clear" flag is set in a sys_template record. This behavior is not configurable by design, as TypoScript code sharing is intended to be implemented via sharable sets (Feature: #103437 - Introduce site sets).

Note that sys_template records will still be loaded, but they are optional now, and applied after TypoScript provided by the site.

TypoScript dependencies can be included via set dependencies. This mechanism is much more effective than the previous static_file_include's or manual @import statements (they are still fine for local includes, but should be avoided for cross-set/extensions dependencies), as sets are automatically ordered and deduplicated.

Site TypoScript

The files setup.typoscript and constants.typoscript (placed next to the site's config.yaml file) will be loaded as TypoScript setup and constants, if available.

Site dependencies (sets) will be loaded first, that means setup and constants can be overridden on a per-site basis.

Set TypoScript

Set-defined TypoScript can be shipped within a set. The files setup.typoscript and constants.typoscript (placed next to the config.yaml file) will be loaded, if available. They are inserted (similar to static_file_include) into the TypoScript chain of the site TypoScript that will be defined by a site that is using sets.

Set constants will always be overruled by site settings. Since site settings always provide a default value, a constant will always be overruled by a defined setting. This can be used to provide backward compatibility with TYPO3 v12 in extensions, where constants shall be used in v12, while v13 will always prefer defined site settings.

In contrast to static_file_include, dependencies are to be included via sets. Dependencies are included recursively. This mechanism supersedes the previous include via static_file_include or manual @import statements as sets are automatically ordered and deduplicated. That means TypoScript will not be loaded multiple times, if a shared dependency is required by multiple sets.

Note that @import statements are still fine to be used for local includes, but should be avoided for cross-set/extensions dependencies.

Global TypoScript

Site sets introduce reliable dependencies in order to replace the need for globally provided TypoScript. It is therefore generally discouraged to use global TypoScript in an environment using TypoScript provided by site sets. TypoScript should only be provided globally if absolutely needed.

It has therefore been decided that ext_typoscript_setup.typoscript and ext_typoscript_constants.typoscript are not autoloaded in site set provided TypoScript.

These files can still be used to provide global TypoScript for traditional sys_template setups. Existing setups do not need to be adapted and extensions can still ship globally defined TypoScript via ext_typoscript_setup.typoscript for these cases, but should provide explicitly dependable sets for newer site set setups.

If global TypoScript is still needed and is unavoidable, it can be provided for site sets and sys_template setups in ext_localconf.php via:

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptSetup(
    'module.tx_foo.settings.example = 1'
);
Copied!

There are some cases where globally defined TypoScript configurations are needed because backend modules rely on their availability. One such case is the form framework backend module which uses module.tx_form.settings.yamlConfigurations as a registry for extension-provided form configuration. Global form configuration can be loaded via ExtensionManagementUtility as described in YAML registration for the backend via addTypoScriptSetup() Please make sure to only load backend-related form TypoScript globally and to provide TypoScript related to frontend rendering via site sets.

Impact

Sites and sets can ship TypoScript without the need for sys_template records in database, and dependencies can be expressed via sets, allowing for automatic ordering and deduplication.

Feature: #103441 - Request ID as public visible error reference in error handlers output

See forge#103441

Description

The ProductionExceptionHandler in EXT:core outputs error details, but not for everyone. As a normal visitor you don't see any traceable error information.

The ProductionExceptionHandler in EXT:frontend outputs "Oops, an error occurred!" followed by a timestamp and a hash. This is part of log messages.

Whenever an error/exception is logged, the log message contains the request ID.

With this the request ID is also shown in web output of error/exception handlers as public visible error reference.

Impact

Everyone sees a request id as traceable error information.

Feature: #103504 - New ContentObject PAGEVIEW

See forge#103504

Description

A new content object for TypoScript PAGEVIEW has been added.

This cObject is mainly intended for rendering a full page in the TYPO3 frontend with fewer configuration options over the generic FLUIDTEMPLATE cObject.

A basic usage of the PAGEVIEW cObject is as follows:

page = PAGE
page.10 = PAGEVIEW
page.10.paths.100 = EXT:mysite/Resources/Private/Templates/
Copied!

PAGEVIEW wires certain parts automatically:

  1. The name of the used page layout (backend layout) is resolved automatically.

    If a page has a layout named "with_sidebar", the template file is then resolved to EXT:mysite/Resources/Private/Templates/Pages/With_sidebar.html.

  2. Fluid features for layouts and partials are wired automatically. They can be placed into EXT:mysite/Resources/Private/Templates/Layouts/ and EXT:mysite/Resources/Private/Templates/Partials/ with above example.
  3. Default variables are available in the Fluid template:

    • settings - contains all TypoScript settings (= constants)
    • site - the current Site object
    • language - the current SiteLanguage object
    • page - the current page record as object

There is no special Extbase resolving done for the templates.

Migration

Before

page = PAGE
page {
    10 = FLUIDTEMPLATE
    10 {
        templateName = TEXT
        templateName {
            stdWrap {
                cObject = TEXT
                cObject {
                    data = levelfield:-2, backend_layout_next_level, slide
                    override {
                        field = backend_layout
                    }
                    split {
                        token = pagets__
                        1 {
                            current = 1
                            wrap = |
                        }
                    }
                }
                ifEmpty = Standard
            }
        }

        templateRootPaths {
            100 = {$plugin.tx_mysite.templateRootPaths}
        }

        partialRootPaths {
            100 = {$plugin.tx_mysite.partialRootPaths}
        }

        layoutRootPaths {
            100 = {$plugin.tx_mysite.layoutRootPaths}
        }

        variables {
            pageUid = TEXT
            pageUid.data = page:uid

            pageTitle = TEXT
            pageTitle.data = page:title

            pageSubtitle = TEXT
            pageSubtitle.data = page:subtitle

            parentPageTitle = TEXT
            parentPageTitle.data = levelfield:-1:title
        }

        dataProcessing {
            10 = menu
            10.as = mainMenu
        }
    }
}
Copied!

After

page = PAGE
page {
    10 = PAGEVIEW
    10 {
        paths {
            100 = {$plugin.tx_mysite.templatePaths}
        }
        variables {
            parentPageTitle = TEXT
            parentPageTitle.data = levelfield:-1:title
        }
        dataProcessing {
            10 = menu
            10.as = mainMenu
        }
    }
}
Copied!

In Fluid, the pageUid is available as {page.uid} and pageTitle as {page.title}. The page layout identifier can be accessed using {page.pageLayout.identifier}.

Impact

Creating new page templates based on Fluid follows conventions in order to reduce the amount of TypoScript needed to render a page in the TYPO3 frontend.

Sane defaults are applied, variables and settings are available at any time.

Feature: #103522 - Page TSconfig provider for sites and sets

See forge#103522

Description

TYPO3 sites have been enhanced to be able to provide page TSconfig on a per-site basis.

Site page TSconfig is loaded from page.tsconfig, if placed next to the site configuration file config.yaml and is scoped to pages within that site.

Impact

Sites and sets can ship page TSconfig without the need for database entries or by polluting global scope when registering page TSconfig globally via ext_localconf.php or Configuration/page.tsconfig. Dependencies can be expressed via sets, allowing for automatic ordering and deduplication.

Feature: #103529 - Introduce hotkey for "Save and Close"

See forge#103529

Description

A new hotkey is introduced in the FormEngine scope that lets editors invoke "Save and Close" via Ctrl/Cmd + Shift + S.

Impact

Next to the existing Ctrl/Cmd + s hotkey (Save), the hotkey Ctrl/Cmd + Shift + S (Save and Close) became available.

Feature: #103560 - Update Fluid Standalone to version 2.11

See forge#103560

Description

Fluid Standalone has been updated to version 2.11. This version includes new ViewHelpers that cover common tasks in Fluid templates. More ViewHelpers will be added with future minor releases.

A full documentation of the new ViewHelper's arguments is available in the ViewHelper reference <https://docs.typo3.org/other/typo3/view-helper-reference/main/en-us/>.

Impact

The following ViewHelpers are now included and can be used in all Fluid templates:

<f:split> ViewHelper:

The SplitViewHelper splits a string by the specified separator, which results in an array.

<f:split value="1,5,8" separator="," /> <!-- Output: {0: '1', 1: '5', 2: '8'} -->
<f:split separator="-">1-5-8</f:split> <!-- Output: {0: '1', 1: '5', 2: '8'} -->
<f:split value="1,5,8" separator="," limit="2" /> <!-- Output: {0: '1', 1: '5,8'} -->
Copied!

<f:join> ViewHelper:

The JoinViewHelper combines elements from an array into a single string.

<f:join value="{0: '1', 1: '2', 2: '3'}" /> <!-- Output: 123 -->
<f:join value="{0: '1', 1: '2', 2: '3'}" separator=", " /> <!-- Output: 1, 2, 3 -->
<f:join value="{0: '1', 1: '2', 2: '3'}" separator=", " separatorLast=" and " /> <!-- Output: 1, 2 and 3 -->
Copied!

<f:replace> ViewHelper:

The ReplaceViewHelper replaces one or multiple strings with other strings.

<f:replace value="Hello World" search="World" replace="Fluid" /> <!-- Output: Hello Fluid -->
<f:replace value="Hello World" search="{0: 'World', 1: 'Hello'}" replace="{0: 'Fluid', 1: 'Hi'}" /> <!-- Output: Hi Fluid -->
<f:replace value="Hello World" replace="{'World': 'Fluid', 'Hello': 'Hi'}" /> <!-- Output: Hi Fluid -->
Copied!

<f:first> and <f:last> ViewHelpers:

The FirstViewHelper and LastViewHelper return the first or last item of a specified array, respectively.

<f:first value="{0: 'first', 1: 'second', 2: 'third'}" /> <!-- Outputs "first" -->
<f:last value="{0: 'first', 1: 'second', 2: 'third'}" /> <!-- Outputs "third" -->
Copied!

Feature: #103578 - Add database default value support for TEXT, BLOB and JSON field types

See forge#103578

Description

Database default values for TEXT, JSON and BLOB fields could not be used in a cross-database, vendor-compatible manner, for example in ext_tables.sql, or as default database scheme generation for TCA-managed tables and types.

Direct default values are still unsupported, but since MySQL 8.0.13+ this is possible by using default value expressions, albeit in a slightly differing syntax.

Example

EXT:my_extension/ext_tables.sql
CREATE TABLE `tx_myextension_domain_model_entity` (
  `some_field` TEXT NOT NULL DEFAULT 'default-text',
  `json_field` JSON NOT NULL DEFAULT '{}'
);
Copied!
Insert a new record using the defined default values
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getConnectionByName(ConnectionPool::DEFAULT_NAME);
$connection->insert(
    'tx_myextension_domain_model_entity',
    [
        'pid' => 123,
    ]
);
Copied!

Advanced example with value quoting

EXT:my_extension/ext_tables.sql
CREATE TABLE a_textfield_test_table
(
    # JSON object default value containing single quote in json field
    field1 JSON NOT NULL DEFAULT '{"key1": "value1", "key2": 123, "key3": "value with a '' single quote"}',

    # JSON object default value containing double-quote in json field
    field2 JSON NOT NULL DEFAULT '{"key1": "value1", "key2": 123, "key3": "value with a \" double quote"}',
);
Copied!

Impact

Database INSERT queries that do not provide values for fields with defined default values, and that do not use TCA-powered TYPO3 APIs, can now be used, and will receive default values defined at databaselevel. This also accounts for dedicated applications operating directly on the database table.

Feature: #103671 - Provide null coalescing operator for TypoScript constants

See forge#103671

Description

TypoScript constants expressions have been extended to support a null coalescing operator (??) as a way for providing a migration path from a legacy constant name to a newer name, while providing full backwards compatibility for the legacy constant name, if still defined.

Example that evaluates to $config.oldThing if set, otherwise the newer setting $myext.thing would be used:

plugin.tx_myext.settings.example = {$config.oldThing ?? $myext.thing}
Copied!

Impact

Since Feature: #103439 - TypoScript provider for sites and sets it is suggested to define site settings via settings.definitions.yaml in site sets instead of TypoScript constants. Migration of TYPO3 Core extensions revealed that such migration is a good time to revisit constant names and the null coalescing operator helps to switch to a new setting identifier without breaking backwards-compatibility with previous constant names.

Deprecation: #102762 - Deprecate GeneralUtility::hmac()

See forge#102762

Description

The method \TYPO3\CMS\Core\Utility\GeneralUtility::hmac() has been deprecated in TYPO3 v13 and will be removed with v14 in favor of Feature: #102761 - Introduce class to generate/validate HMAC hashes.

Impact

Usage of the method will raise a deprecation level log entry in TYPO3 v13 and a fatal error in TYPO3 v14.

Affected installations

All third-party extensions using \TYPO3\CMS\Core\Utility\GeneralUtility::hmac().

Migration

All usages of \TYPO3\CMS\Core\Utility\GeneralUtility::hmac() must be migrated to use the hmac() method in the class \TYPO3\CMS\Core\Crypto\HashService .

Before

//use TYPO3\CMS\Core\Utility\GeneralUtility;

$hmac = GeneralUtility::hmac('some-input', 'some-secret');
Copied!

After

Using GeneralUtility::makeInstance()
//use TYPO3\CMS\Core\Crypto\HashService;
//use TYPO3\CMS\Core\Utility\GeneralUtility;

$hashService = GeneralUtility::makeInstance(HashService::class);
$hmac = $hashService->hmac('some-input', 'some-secret');
Copied!
Using dependency injection
namespace MyVendor\MyExt\Services;

use TYPO3\CMS\Core\Crypto\HashService;
use TYPO3\CMS\Core\Utility\GeneralUtility;

final readonly class MyService
{
    public function __construct(
        private HashService $hashService,
    ) {}

    public function someMethod(): void
    {
        $hmac = $this->hashService->hmac('some-input', 'some-secret');
    }
}
Copied!

If possible, use dependency injection to inject HashService into your class.

Deprecation: #103211 - Deprecate pageTree.backgroundColor

See forge#103211

Description

The user TSconfig option options.pageTree.backgroundColor has been deprecated and will be removed in TYPO3 v14 due to its lack of accessibility. It is being replaced with a new label system for tree nodes.

Impact

During v13, options.pageTree.backgroundColor will be migrated to the new label system. Since the use case is unknown, the generated label will be "Color: <value>". This information will be displayed on all affected nodes.

Affected installations

All installations that use the user TSconfig option options.pageTree.backgroundColor are affected.

Migration

Before:

EXT:my_extension/Configuration/user.tsconfig
options.pageTree.backgroundColor.<pageid> = #ff8700
Copied!

After:

EXT:my_extension/Configuration/user.tsconfig
options.pageTree.label.<pageid> {
    label = Campaign A
    color = #ff8700
}
Copied!

Deprecation: #103230 - Deprecate @typo3/backend/wizard.js

See forge#103230

Description

The TYPO3 backend module @typo3/backend/wizard.js that offers simple wizards has been marked as deprecated in favor of the richer @typo3/backend/multi-step-wizard.js module.

Impact

Using the deprecated module will trigger a browser console warning.

Affected installations

All installations using @typo3/backend/wizard.js are affected.

Migration

Migrate to the module @typo3/backend/multi-step-wizard.js. There are two major differences:

  • The class name changes to MultiStepWizard.
  • The method addSlide() receives an additional argument for the step title in the progress bar.

Example

-import Wizard from '@typo3/backend/wizard.js';
+import MultiStepWizard from '@typo3/backend/multi-step-wizard.js';

-Wizard.addSlide(
+MultiStepWizard.addSlide(
     'my-slide-identifier',
     'Slide title',
     'Content of my slide',
     SeverityEnum.notice,
+    'My step',
     function () {
         // callback executed after displaying the slide
     }
);
Copied!

Deprecation: #103244 - Class SlugEnricher

See forge#103244

Description

Class \TYPO3\CMS\Core\DataHandling\SlugEnricher has been marked as deprecated in TYPO3 v13 and will be removed with v14.

The class was used as a helper for \TYPO3\CMS\Core\DataHandling\DataHandler , which now inlines the code in a simplified variant.

Impact

Using the class will raise a deprecation level log entry and a fatal error in TYPO3 v14.

Affected installations

There is little to no reason to use this class in custom extensions, very few instances should be affected by this. The extension scanner will find usages with a strong match.

Migration

No migration available.

Deprecation: #103528 - Deprecated DocumentSaveActions module

See forge#103528

Description

The JavaScript module @typo3/backend/document-save-actions.js was introduced in TYPO3 v7 to add some interactivity in FormEngine context. At first it was only used to disable the submit button and render a spinner icon instead. Over the course of some years, the module got more functionality, for example to prevent saving when validation fails.

Since some refactorings within FormEngine, the module rather became a burden. This became visible with the introduction of the Hotkeys API, as the @typo3/backend/document-save-actions.js reacts on explicit click events on the save icon, that is not triggered when FormEngine invokes a save action via keyboard shortcuts. Adjusting document-save-actions.js's behavior is necessary, but would become a breaking change, which is unacceptable after the 13.0 release. For this reason, said module has been marked as deprecated and its usages are replaced by its successor @typo3/backend/form/submit-interceptor.js.

Impact

Using the JavaScript module @typo3/backend/document-save-actions.js will render a deprecation warning in the browser's console.

Affected installations

All installations relying on @typo3/backend/document-save-actions.js are affected.

Migration

To migrate the interception of submit events, the successor module @typo3/backend/form/submit-interceptor.js shall be used instead.

The usage is similar to @typo3/backend/document-save-actions.js, but requires the form HTML element in its constructor.

Example

import '@typo3/backend/form/submit-interceptor.js';

// ...

const formElement = document.querySelector('form');
const submitInterceptor = new SubmitInterceptor(formElement);
submitInterceptor.addPreSubmitCallback(function() {
    // the same handling as in @typo3/backend/document-save-actions.js
});
Copied!

Deprecation: #103850 - Renamed Page Tree Navigation Component ID

See forge#103850

Description

When registering a module in the TYPO3 Backend, using the page tree as navigation component, the name of the page tree navigation component has been renamed in TYPO3 v13.

Previously, the navigation component was called @typo3/backend/page-tree/page-tree-element, now it is named @typo3/backend/tree/page-tree-element.

Impact

Using the old navigation ID will trigger a PHP deprecation warning.

Affected installations

TYPO3 installations with custom backend modules utilizing the page tree navigation component.

Migration

Instead of writing this snippet in your Configuration/Backend/Modules.php:

'mymodule' => [
    'parent' => 'web',
    ...
    'navigationComponent' => '@typo3/backend/page-tree/page-tree-element',
],
Copied!

It is now called:

'mymodule' => [
    'parent' => 'web',
    ...
    'navigationComponent' => '@typo3/backend/tree/page-tree-element',
],
Copied!

Important: #103165 - Database table cache_treelist removed

See forge#103165

Description

Database table cache_treelist has been removed, the database analyzer will suggest to drop it if it exists.

That cache table was unused since a TYPO3 v12 patch level release, v13 removed leftover handling throughout the Core and removed the table itself.

13.0 Changes

Table of contents

Breaking Changes

Features

Deprecation

Important

Breaking: #97330 - FormEngine element classes must create label or legend

See forge#97330

Description

When editing records in the backend, the FormEngine class structure located within EXT:backend/Classes/Form/ handles the generation of the editing view.

A change has been applied related to the rendering of single field labels, which is no longer done automatically by "container" classes: Single elements have to create the label themselves.

Extension that add own elements to FormEngine must be adapted, otherwise the element label is no longer rendered.

Impact

When the required changes are not applied to custom FormEngine element classes, the value of the TCA "label" property is not rendered.

Affected installations

Instances with custom FormEngine elements are affected. Custom elements need to be registered to the FormEngine's NodeFactory, candidates are found by looking at the $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine'] array (for instance using the System > Configuration backend module provided by EXT:lowlevel). Classes registered using the sub keys nodeRegistry and nodeResolver may be affected. The extension scanner does not find affected classes.

Migration

Custom elements must take care of creating a <label> or <legend> tag on their own: If the element creates an <input>, or <select> tag, the <label> should have a for attribute that points to a field having an id attribute. This is important especially for accessibility. When no such target element exists, a <legend> embedded in a <fieldset> can be used. There are two helper methods in \TYPO3\CMS\Backend\Form\Element\AbstractFormElement to help with this: renderLabel() and wrapWithFieldsetAndLegend().

In practice, an element having an <input>, or <select> field should essentially look like this:

$resultArray = $this->initializeResultArray();
// Next line is only needed for extensions that need to keep TYPO3 v12 compatibility
$resultArray['labelHasBeenHandled'] = true;
$fieldId = StringUtility::getUniqueId('formengine-input-');
$html = [];
$html[] = $this->renderLabel($fieldId);
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] =     '<div class="form-wizards-wrap">';
$html[] =         '<div class="form-wizards-element">';
$html[] =             '<div class="form-control-wrap">';
$html[] =                 '<input class="form-control" id="' . htmlspecialchars($fieldId) . '" value="..." type="text">';
$html[] =             '</div>';
$html[] =         '</div>';
$html[] =     '</div>';
$html[] = '</div>';
$resultArray['html'] = implode(LF, $html);
return $resultArray;
Copied!

The renderLabel() is a helper method to generate a <label> tag with a for attribute, and the same fieldId is used as id attribute in the <input> field to connect <label> and <input> with each other.

If there is no such field, a <legend> tag should be used:

$resultArray = $this->initializeResultArray();
// Next line is only needed for extensions that need to keep TYPO3 v12 compatibility
$resultArray['labelHasBeenHandled'] = true;
$html = [];
$html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
$html[] =     '<div class="form-wizards-wrap">';
$html[] =         '<div class="form-wizards-element">';
$html[] =             '<div class="form-control-wrap">';
$html[] =                 Some custom element html
$html[] =             '</div>';
$html[] =         '</div>';
$html[] =     '</div>';
$html[] = '</div>';
$resultArray['html'] = $this->wrapWithFieldsetAndLegend(implode(LF, $html));
return $resultArray;
Copied!

Breaking: #97664 - FormPersistenceManagerInterface modified

See forge#97664

Description

The PHP interface \TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface now requires PHP classes to implement an additional method hasForms() in order to fulfill the API.

Impact

TYPO3 projects with extensions using implementations of the \TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface will now break with a fatal PHP error.

Affected installations

Extensions using implementations of the \TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface .

Migration

Add the new method to your extension's implementation of this interface, which also makes it compatible with TYPO3 v12 and TYPO3 v13 at the same time.

Breaking: #99323 - Removed hook for modifying records after fetching content

See forge#99323

Description

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content_content.php']['modifyDBRow'] has been removed in favor of the more powerful PSR-14 \TYPO3\CMS\Frontend\ContentObject\Event\ModifyRecordsAfterFetchingContentEvent .

Impact

Any hook implementation registered is not executed anymore in TYPO3 v13.0+.

Affected installations

TYPO3 installations with custom extensions using this hook.

Migration

The hook is removed without deprecation in order to allow extensions to work with TYPO3 v12 (using the hook) and v13+ (using the new event) when implementing the event as well without any further deprecations. Use the PSR-14 event to allow greater influence in the functionality.

Breaking: #99807 - Relocated ModifyUrlForCanonicalTagEvent

See forge#99807

Description

The \TYPO3\CMS\Seo\Event\ModifyUrlForCanonicalTagEvent has been improved.

Therefore, the event is now always dispatched after the standard functionality has been executed, such as fetching the URL from the page properties.

The event is furthermore also dispatched in case the canonical tag generation has been disabled via TypoScript or the page properties. This allows greater influence in the generation process, but might break existing setups, which rely on listeners are being called before standard functionality has been executed or only in case generation is enabled.

Effectively, this also means that getUrl() might already return a non-empty string.

Impact

The \TYPO3\CMS\Seo\Event\ModifyUrlForCanonicalTagEvent is now always dispatched when generating canonical tags, just before the final tag markup is being built.

Affected installations

TYPO3 installations with custom extensions, whose event listeners rely on the event being dispatched before standard functionality has been executed or only in case generation has not been disabled.

Migration

Adjust your listeners by respecting the new execution order. Therefore, the event contains the new getCanonicalGenerationDisabledException() method, which can be used to determine whether generation is disabled and the reason for it.

Breaking: #99898 - Continuous array keys from GeneralUtility::intExplode

See forge#99898

Description

When the method \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode() is called with the parameter $removeEmptyEntries set to true, the array keys are now continuous.

Previously, the array had gaps in the keys in the places where empty values were removed. This behavior had been an undocumented side-effect of the implementation. It is now changed to always return an array with continuous integer array keys (i.e., a list) to reduce surprising behavior.

Before this change (TYPO3 v12):

GeneralUtility::intExplode(',', '1,,3', true);
// Result: [0 => 1, 2 => 3]
Copied!

After this change (TYPO3 v13):

GeneralUtility::intExplode(',', '1,,3', true);
// Result: [0 => 1, 1 => 3]
Copied!

Impact

Calling GeneralUtility::intExplode() with the parameter $removeEmptyEntries set to true and relying on gaps in the keys of the resulting array keys may lead to unexpected results.

Affected installations

Custom extensions that rely on the array keys of the result of GeneralUtility::intExplode() to have gaps in the keys.

Migration

Adapt your code to not rely on gaps in the keys anymore.

Breaking: #99937 - Utilize BIGINT database column type for datetime TCA

See forge#99937

Description

The TCA 'type' => 'datetime' attribute previously created integer signed types as per the new auto-creation of table columns, if not specified differently via ext_tables.sql or listed as an exception ( starttime, endtime, tstamp, crdate).

A datetime field created without an exception would allow date ranges from 1901 to 2038. While that allows dates before 1970 (usual birthdays), sadly this field would "end" in 2038.

Because of this, the exceptions ( starttime, endtime, tstamp, crdate) already are created as integer unsigned, which puts them from 1970 to 2106. Dates before 1970 are not needed, because you will not publish or create anything in the past, but maybe after 2038.

However, there are many use cases where datetime TCA fields should have a much broader time span, or at least past 2038.

Now, all these fields are changed to use the bigint signed data type. This allows to define ranges far into the future and past. It uses a few more bytes within the database, for the benefit of being a unified solution that can apply to every use case.

Impact

All extensions that previously declared datetime columns should remove the column definition from ext_tables.sql to utilize the type bigint signed. This will allow to store timestamps after 2038 (and before 1970).

A future implementation may change from integer-based columns completely to a native datetime database field.

When executing the database compare utility, the column definitions for a few Core fields are changed and their storable range increases.

These fields are now able to hold a timestamp beyond 2038 (and also before 1970):

  • be_users.lastlogin
  • fe_users.lastlogin
  • pages.lastUpdated
  • pages.newUntil
  • sys_redirect.lasthiton
  • index_config.timer_next_indexing
  • tx_extensionmanager_domain_model_extension.last_updated
  • sys_workspace.publish_time
  • sys_file_metadata.content_creation_date
  • tt_content.date

Breaking: #100224 - MfaViewType migrated to backed enum

See forge#100224

Description

The class \TYPO3\CMS\Core\Authentication\Mfa\MfaViewType has been migrated to a native PHP backed enum.

Impact

Since MfaViewType is no longer a class, the existing class constants are no longer available, but are enum instances instead.

In addition, it's not possible to instantiate the class anymore or call the equals() method.

The \TYPO3\CMS\Core\Authentication\Mfa\MfaProviderInterface , which all MFA providers need to implement, does now require the third argument $type of the handleRequest() method to be a MfaViewType instead of a string.

Affected installations

All installations directly using the class constants, instantiating the class or calling the equals() method.

All extensions with custom MFA providers, which therefore implement the handleRequest() method.

Migration

To access the string representation of a MfaViewType, use the corresponding value property, e.g. \TYPO3\CMS\Core\Authentication\Mfa\MfaViewType::SETUP->value or on a variable, use $type->value.

Replace class instantiation by \TYPO3\CMS\Core\Authentication\Mfa\MfaViewType::tryFrom('setup').

Adjust your MFA providers handleRequest() method to match the interface:

public function handleRequest(
    ServerRequestInterface $request,
    MfaProviderPropertyManager $propertyManager,
    MfaViewType $type
): ResponseInterface;
Copied!

Breaking: #100229 - Convert JSConfirmation to a BitSet

See forge#100229

Description

The class \TYPO3\CMS\Core\Type\Bitmask\JSConfirmation is replaced by \TYPO3\CMS\Core\Authentication\JsConfirmation . The new class is extending the \TYPO3\CMS\Core\Type\BitSet class instead of \TYPO3\CMS\Core\TypeEnumeration\Enumeration.

Impact

Since JSConfirmation is now extending the class \TYPO3\CMS\Core\Type\BitSet it's no longer possible to call the following public methods:

  • matches()
  • setValue()
  • isValid()

The only static method left is: compare()

Affected installations

Custom TYPO3 extensions calling public methods:

  • matches()
  • setValue()
  • isValid()

Custom TYPO3 extensions calling static methods in \TYPO3\CMS\Core\Type\Bitmask\JSConfirmation except for the method \TYPO3\CMS\Core\Type\Bitmask\JSConfirmation::compare().

Custom TYPO3 extensions calling \TYPO3\CMS\Core\Authentication\BackendUserAuthentication->jsConfirmation(), if first argument passed is not an int.

Migration

Replace existing usages of \TYPO3\CMS\Core\Type\Bitmask\JSConfirmation with \TYPO3\CMS\Core\Authentication\JsConfirmation .

There is no migration for the methods:

  • matches()
  • setValue()
  • isValid()

Remove existing calls to static methods \TYPO3\CMS\Core\Type\Bitmask\JSConfirmation::method() and where JSConfirmation::compare() is used, replace the namespace from \TYPO3\CMS\Core\Type\Bitmask\JSConfirmation to \TYPO3\CMS\Core\Authentication\JsConfirmation .

Ensure an int value is passed to:

  • \TYPO3\CMS\Core\Authentication\BackendUserAuthentication->jsConfirmation()

Breaking: #100963 - Deprecated functionality removed

See forge#100963

Description

The following PHP classes that have previously been marked as deprecated with v12 have been removed:

  • \TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher
  • \TYPO3\CMS\Backend\EventListener\SilentSiteLanguageFlagMigration
  • \TYPO3\CMS\Backend\Template\Components\Buttons\Action\HelpButton
  • \TYPO3\CMS\Backend\Tree\View\BrowseTreeView
  • \TYPO3\CMS\Backend\Tree\View\ElementBrowserPageTreeView
  • \TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader
  • \TYPO3\CMS\Core\Configuration\PageTsConfig
  • \TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser
  • \TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractConditionMatcher
  • \TYPO3\CMS\Core\Configuration\TypoScript\Exception\InvalidTypoScriptConditionException
  • \TYPO3\CMS\Core\Controller\RequireJsController
  • \TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction
  • \TYPO3\CMS\Core\Database\Query\Restriction\FrontendWorkspaceRestriction
  • \TYPO3\CMS\Core\Exception\MissingTsfeException
  • \TYPO3\CMS\Core\ExpressionLanguage\DeprecatingRequestWrapper
  • \TYPO3\CMS\Core\Resource\Service\MagicImageService
  • \TYPO3\CMS\Core\Resource\Service\UserFileInlineLabelService
  • \TYPO3\CMS\Core\Resource\Service\UserFileMountService
  • \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
  • \TYPO3\CMS\Core\TypoScript\TemplateService
  • \TYPO3\CMS\Core\Utility\ResourceUtility
  • \TYPO3\CMS\Dashboard\Views\Factory
  • \TYPO3\CMS\Fluid\ViewHelpers\Be\Buttons\CshViewHelper
  • \TYPO3\CMS\Fluid\ViewHelpers\Be\Labels\CshViewHelper
  • \TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher
  • \TYPO3\CMS\Frontend\Plugin\AbstractPlugin

The following PHP classes have been declared final:

  • \TYPO3\CMS\Core\Database\Driver\PDOMySql\Driver
  • \TYPO3\CMS\Core\Database\Driver\PDOPgSql\Driver
  • \TYPO3\CMS\Core\Database\Driver\PDOSqlite\Driver

The following PHP interfaces that have previously been marked as deprecated with v12 have been removed:

  • \TYPO3\CMS\Backend\Form\Element\InlineElementHookInterface
  • \TYPO3\CMS\Backend\RecordList\RecordListGetTableHookInterface
  • \TYPO3\CMS\Backend\Wizard\NewContentElementWizardHookInterface
  • \TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\ConditionMatcherInterface
  • \TYPO3\CMS\Core\Domain\Repository\PageRepositoryGetPageOverlayHookInterface
  • \TYPO3\CMS\Core\Domain\Repository\PageRepositoryGetRecordOverlayHookInterface
  • \TYPO3\CMS\Dashboard\Widgets\RequireJsModuleInterface
  • \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuFilterPagesHookInterface
  • \TYPO3\CMS\Frontend\ContentObject\TypolinkModifyLinkConfigForPageLinksHookInterface
  • \TYPO3\CMS\Frontend\Http\UrlProcessorInterface

The following PHP interfaces changed:

  • \TYPO3\CMS\Adminpanel\ModuleApi\ShortInfoProviderInterface method setModuleData() added
  • \TYPO3\CMS\Backend\Form\NodeInterface method setData() added
  • \TYPO3\CMS\Backend\Form\NodeInterface method render() must return array
  • \TYPO3\CMS\Backend\Form\NodeResolverInterface method setData() added
  • \TYPO3\CMS\Backend\Form\NodeResolverInterface method resolve() must return ?string
  • \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface method getContentObject() removed
  • \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface method isFeatureEnabled() removed
  • \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface method setContentObject() removed
  • \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface method setRequest() added

The following PHP class aliases that have previously been marked as deprecated with v12 have been removed:

  • \TYPO3\CMS\Backend\ElementBrowser\FileBrowser
  • \TYPO3\CMS\Backend\ElementBrowser\FolderBrowser
  • \TYPO3\CMS\Backend\Form\Element\InputColorPickerElement
  • \TYPO3\CMS\Backend\Form\Element\InputDateTimeElement
  • \TYPO3\CMS\Backend\Form\Element\InputLinkElement
  • \TYPO3\CMS\Backend\Provider\PageTsBackendLayoutDataProvider
  • \TYPO3\CMS\Frontend\Service\TypoLinkCodecService
  • \TYPO3\CMS\Frontend\Typolink\LinkResultFactory
  • \TYPO3\CMS\Recordlist\Browser\AbstractElementBrowser
  • \TYPO3\CMS\Recordlist\Browser\DatabaseBrowser
  • \TYPO3\CMS\Recordlist\Browser\ElementBrowserInterface
  • \TYPO3\CMS\Recordlist\Browser\ElementBrowserRegistry
  • \TYPO3\CMS\Recordlist\Browser\FileBrowser
  • \TYPO3\CMS\Recordlist\Browser\FolderBrowser
  • \TYPO3\CMS\Recordlist\Controller\AbstractLinkBrowserController
  • \TYPO3\CMS\Recordlist\Controller\AccessDeniedException
  • \TYPO3\CMS\Recordlist\Controller\ClearPageCacheController
  • \TYPO3\CMS\Recordlist\Controller\ElementBrowserController
  • \TYPO3\CMS\Recordlist\Controller\RecordDownloadController
  • \TYPO3\CMS\Recordlist\Controller\RecordListController
  • \TYPO3\CMS\Recordlist\Event\ModifyRecordListHeaderColumnsEvent
  • \TYPO3\CMS\Recordlist\Event\ModifyRecordListRecordActionsEvent
  • \TYPO3\CMS\Recordlist\Event\ModifyRecordListTableActionsEvent
  • \TYPO3\CMS\Recordlist\Event\RenderAdditionalContentToRecordListEvent
  • \TYPO3\CMS\Recordlist\LinkHandler\AbstractLinkHandler
  • \TYPO3\CMS\Recordlist\LinkHandler\FileLinkHandler
  • \TYPO3\CMS\Recordlist\LinkHandler\FolderLinkHandler
  • \TYPO3\CMS\Recordlist\LinkHandler\LinkHandlerInterface
  • \TYPO3\CMS\Recordlist\LinkHandler\MailLinkHandler
  • \TYPO3\CMS\Recordlist\LinkHandler\PageLinkHandler
  • \TYPO3\CMS\Recordlist\LinkHandler\RecordLinkHandler
  • \TYPO3\CMS\Recordlist\LinkHandler\TelephoneLinkHandler
  • \TYPO3\CMS\Recordlist\LinkHandler\UrlLinkHandler
  • \TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList
  • \TYPO3\CMS\Recordlist\RecordList\DownloadRecordList
  • \TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface
  • \TYPO3\CMS\Recordlist\View\FolderUtilityRenderer
  • \TYPO3\CMS\Recordlist\View\RecordSearchBoxComponent

The following PHP class methods that have previously been marked as deprecated with v12 have been removed:

  • \TYPO3\CMS\Backend\Template\Components\ButtonBar->makeHelpButton()
  • \TYPO3\CMS\Backend\Template\ModuleTemplate->getBodyTag()
  • \TYPO3\CMS\Backend\Template\ModuleTemplate->getDynamicTabMenu()
  • \TYPO3\CMS\Backend\Template\ModuleTemplate->getView()
  • \TYPO3\CMS\Backend\Template\ModuleTemplate->header()
  • \TYPO3\CMS\Backend\Template\ModuleTemplate->isUiBlock()
  • \TYPO3\CMS\Backend\Template\ModuleTemplate->registerModuleMenu()
  • \TYPO3\CMS\Backend\Template\ModuleTemplate->renderContent()
  • \TYPO3\CMS\Backend\Template\ModuleTemplate->setContent()
  • \TYPO3\CMS\Backend\Tree\View\AbstractTreeView->addTagAttributes()
  • \TYPO3\CMS\Backend\Tree\View\AbstractTreeView->determineScriptUrl()
  • \TYPO3\CMS\Backend\Tree\View\AbstractTreeView->getRootIcon()
  • \TYPO3\CMS\Backend\Tree\View\AbstractTreeView->getRootRecord()
  • \TYPO3\CMS\Backend\Tree\View\AbstractTreeView->getThisScript()
  • \TYPO3\CMS\Core\Authentication\BackendUserAuthentication->modAccess()
  • \TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools->removeElementTceFormsRecursive()
  • \TYPO3\CMS\Core\Database\Driver\PDOMySql\Driver->getName()
  • \TYPO3\CMS\Core\Database\Driver\PDOPgSql\Driver->getName()
  • \TYPO3\CMS\Core\Database\Driver\PDOSqlite\Driver->getName()
  • \TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression->add()
  • \TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression->addMultiple()
  • \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder->andX()
  • \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder->orX()
  • \TYPO3\CMS\Core\Database\Query\QueryBuilder->execute()
  • \TYPO3\CMS\Core\Domain\Repository\PageRepository->getExtURL()
  • \TYPO3\CMS\Core\Environment->getBackendPath()
  • \TYPO3\CMS\Core\Localization\LanguageService->getLL()
  • \TYPO3\CMS\Core\Localization\Locales->getIsoMapping()
  • \TYPO3\CMS\Core\Page\JavaScriptModuleInstruction->shallLoadRequireJs()
  • \TYPO3\CMS\Core\Page\PageRenderer->loadRequireJs()
  • \TYPO3\CMS\Core\Page\PageRenderer->loadRequireJsModule()
  • \TYPO3\CMS\Core\Page\PageRenderer->setRenderXhtml()
  • \TYPO3\CMS\Core\Page\PageRenderer->getRenderXhtml()
  • \TYPO3\CMS\Core\Page\PageRenderer->setCharSet()
  • \TYPO3\CMS\Core\Page\PageRenderer->getCharSet()
  • \TYPO3\CMS\Core\Page\PageRenderer->setMetaCharsetTag()
  • \TYPO3\CMS\Core\Page\PageRenderer->getMetaCharsetTag()
  • \TYPO3\CMS\Core\Page\PageRenderer->setBaseUrl()
  • \TYPO3\CMS\Core\Page\PageRenderer->getBaseUrl()
  • \TYPO3\CMS\Core\Page\PageRenderer->enableRemoveLineBreaksFromTemplate()
  • \TYPO3\CMS\Core\Page\PageRenderer->disableRemoveLineBreaksFromTemplate()
  • \TYPO3\CMS\Core\Page\PageRenderer->getRemoveLineBreaksFromTemplate()
  • \TYPO3\CMS\Core\Page\PageRenderer->enableDebugMode()
  • \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter->filterInlineChildren()
  • \TYPO3\CMS\Core\Session\UserSessionManager->createFromGlobalCookieOrAnonymous()
  • \TYPO3\CMS\Core\Site\Entity\SiteLanguage->getTwoLetterIsoCode()
  • \TYPO3\CMS\Core\Site\Entity\SiteLanguage->getDirection()
  • \TYPO3\CMS\Core\Type\DocType->getXhtmlDocType()
  • \TYPO3\CMS\Dashboard\DashboardInitializationService->getRequireJsModules()
  • \TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager->getContentObject()
  • \TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager->setContentObject()
  • \TYPO3\CMS\Extbase\Configuration\ConfigurationManager->getContentObject()
  • \TYPO3\CMS\Extbase\Configuration\ConfigurationManager->isFeatureEnabled()
  • \TYPO3\CMS\Extbase\Configuration\ConfigurationManager->setContentObject()
  • \TYPO3\CMS\Extbase\Configuration\FrontendConfigurationManager->getContentObject()
  • \TYPO3\CMS\Extbase\Configuration\FrontendConfigurationManager->setContentObject()
  • \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder->getRequest()
  • \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings->setLanguageOverlayMode()
  • \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings->getLanguageOverlayMode()
  • \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings->setLanguageUid()
  • \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings->getLanguageUid()
  • \TYPO3\CMS\Extbase\Property\AbstractTypeConverter->canConvertFrom()
  • \TYPO3\CMS\Extbase\Property\AbstractTypeConverter->getPriority()
  • \TYPO3\CMS\Extbase\Property\AbstractTypeConverter->getSupportedTargetType()
  • \TYPO3\CMS\Extbase\Property\AbstractTypeConverter->getSupportedSourceTypes()
  • \TYPO3\CMS\Fluid\View\StandaloneView->getFormat()
  • \TYPO3\CMS\Fluid\View\StandaloneView->getRequest()
  • \TYPO3\CMS\Fluid\View\StandaloneView->getTemplatePathAndFilename()
  • \TYPO3\CMS\FrontendLogin\Event\PasswordChangeEvent->getErrorMessage()
  • \TYPO3\CMS\FrontendLogin\Event\PasswordChangeEvent->isPropagationStopped()
  • \TYPO3\CMS\FrontendLogin\Event\PasswordChangeEvent->setAsInvalid()
  • \TYPO3\CMS\FrontendLogin\Event\PasswordChangeEvent->setHashedPassword()
  • \TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication->getUserTSconf()
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->baseUrlWrap()
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->checkEnableFields()
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->doWorkspacePreview()
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPagesTSconfig()
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->initUserGroups()
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->isBackendUserLoggedIn()
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->isUserOrGroupSet()
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->whichWorkspace()
  • \TYPO3\CMS\Frontend\Typolink\LinkFactory->createFromUriString()
  • \TYPO3\CMS\Frontend\Typolink\LinkFactory->getATagParams()
  • \TYPO3\CMS\Frontend\Typolink\LinkFactory->getMailTo()
  • \TYPO3\CMS\Frontend\Typolink\LinkFactory->getQueryArguments()
  • \TYPO3\CMS\Frontend\Typolink\LinkFactory->getTreeList()
  • \TYPO3\CMS\Frontend\Typolink\LinkFactory->getTypoLink_URL()
  • \TYPO3\CMS\Frontend\Typolink\LinkFactory->getTypoLink()
  • \TYPO3\CMS\Frontend\Typolink\LinkFactory->getUrlToCurrentLocation()
  • \TYPO3\CMS\Scheduler\Scheduler->addTask()
  • \TYPO3\CMS\Scheduler\Scheduler->fetchTaskRecord()
  • \TYPO3\CMS\Scheduler\Scheduler->fetchTaskWithCondition()
  • \TYPO3\CMS\Scheduler\Scheduler->fetchTask()
  • \TYPO3\CMS\Scheduler\Scheduler->isValidTaskObject()
  • \TYPO3\CMS\Scheduler\Scheduler->removeTask()
  • \TYPO3\CMS\Scheduler\Scheduler->saveTask()
  • \TYPO3\CMS\Scheduler\Task\AbstractTask->isExecutionRunning()
  • \TYPO3\CMS\Scheduler\Task\AbstractTask->markExecution()
  • \TYPO3\CMS\Scheduler\Task\AbstractTask->remove()
  • \TYPO3\CMS\Scheduler\Task\AbstractTask->unmarkAllExecutions()
  • \TYPO3\CMS\Scheduler\Task\AbstractTask->unmarkExecution()
  • \TYPO3\CMS\Setup\Event\AddJavaScriptModulesEvent->addModule()
  • \TYPO3\CMS\Setup\Event\AddJavaScriptModulesEvent->getModules()

The following PHP static class methods that have previously been marked as deprecated for v12 have been removed:

  • \TYPO3\CMS\Backend\Utility\BackendUtility::ADMCMD_previewCmds()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::cshItem()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::getClickMenuOnIconTagParameters()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::getDropdownMenu()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::getFuncCheck()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::getFuncMenu()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::getLinkToDataHandlerAction()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::getPreviewUrl()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordToolTip()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::getThumbnailUrl()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::getUpdateSignalCode()
  • \TYPO3\CMS\Backend\Utility\BackendUtility::isModuleSetInTBE_MODULES()
  • \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get()
  • \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::purgeInstances()
  • \TYPO3\CMS\Core\Page\JavaScriptModuleInstruction::forRequireJS()
  • \TYPO3\CMS\Core\Type\ContextualFeedbackSeverity::transform()
  • \TYPO3\CMS\Core\Utility\DebugUtility::debugInPopUpWindow()
  • \TYPO3\CMS\Core\Utility\DebugUtility::debugRows()
  • \TYPO3\CMS\Core\Utility\DebugUtility::printArray()
  • \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addCoreNavigationComponent()
  • \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addLLrefForTCAdescr()
  • \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule()
  • \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addNavigationComponent()
  • \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::allowTableOnStandardPages()
  • \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig()
  • \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::insertModuleFunction()
  • \TYPO3\CMS\Core\Utility\GeneralUtility::_GET()
  • \TYPO3\CMS\Core\Utility\GeneralUtility::_GP()
  • \TYPO3\CMS\Core\Utility\GeneralUtility::_GPmerged()
  • \TYPO3\CMS\Core\Utility\GeneralUtility::_POST()
  • \TYPO3\CMS\Core\Utility\GeneralUtility::linkThisScript()
  • \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule()
  • \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter()

The following methods changed signature according to previous deprecations in v12 at the end of the argument list:

  • \TYPO3\CMS\Backend\Form\FormDataCompiler->compile() (argument 2 is now required)
  • \TYPO3\CMS\Core\Messaging\AbstractMessage->setSeverity() (argument 1 is now of type ContextualFeedbackSeverity)
  • \TYPO3\CMS\Core\Messaging\FlashMessageQueue->clear() (argument 1 is now of type ContextualFeedbackSeverity|null)
  • \TYPO3\CMS\Core\Messaging\FlashMessageQueue->getAllMessagesAndFlush() (argument 1 is now of type ContextualFeedbackSeverity|null)
  • \TYPO3\CMS\Core\Messaging\FlashMessageQueue->getAllMessages() (argument 1 is now of type ContextualFeedbackSeverity|null)
  • \TYPO3\CMS\Core\Messaging\FlashMessageQueue->removeAllFlashMessagesFromSession() (argument 1 is now of type ContextualFeedbackSeverity|null)
  • \TYPO3\CMS\Core\Messaging\FlashMessages->__construct() (argument 3 is now of type ContextualFeedbackSeverity)
  • \TYPO3\CMS\Core\Page\PageRenderer->setLanguage() (argument 1 is now of type Locale)
  • \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility->addMessageToFlashMessageQueue() (argument 2 is now of type ContextualFeedbackSeverity|null)
  • \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode() (argument 4 $limit has been removed)
  • \TYPO3\CMS\Extbase\Mvc\Controller\ActionController->addFlashMessage() (argument 2 is now of type ContextualFeedbackSeverity)
  • \TYPO3\CMS\Extbase\Utility\LocalizationUtility::translate() (argument 4 has been removed)
  • \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer->start() (argument 3 $request has been removed)
  • \TYPO3\CMS\Reports\Status->__construct() (argument 4 is now of type ContextualFeedbackSeverity)
  • \TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider->addMessage() (argument 2 is now of type ContextualFeedbackSeverity)

The following public class properties have been dropped:

  • \TYPO3\CMS\Backend\Tree\View\AbstractTreeView->BE_USER
  • \TYPO3\CMS\Backend\Tree\View\AbstractTreeView->thisScript
  • \TYPO3\CMS\Core\Localization\LanguageService->debugKey
  • \TYPO3\CMS\Core\Security\ContentSecurityPolicy\ConsumableNonce->b64
  • \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer->lastTypoLinkLD
  • \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer->lastTypoLinkTarget
  • \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer->lastTypoLinkUrl
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->baseUrl
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->extTarget
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->fileTarget
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->intTarget
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->spamProtectEmailAddresses
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->tmpl
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->xhtmlDoctype
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->xhtmlVersion

The following class method visibility has been changed to protected:

  • \TYPO3\CMS\Core\Domain\Repository\PageRepository->getRecordOverlay()

The following class methods are now marked as internal:

  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->isSetSessionCookie()
  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->isRefreshTimeBasedCookie()
  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->removeCookie()
  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->isCookieSet()
  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->unpack_uc()
  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->appendCookieToResponse()

The following class methods now have a native return type and removed the #[\ReturnTypeWillChange] attribute:

  • \TYPO3\CMS\Core\Collection\AbstractRecordCollection->current()
  • \TYPO3\CMS\Core\Collection\AbstractRecordCollection->key()
  • \TYPO3\CMS\Core\Log\LogRecord->offsetGet()
  • \TYPO3\CMS\Core\Messaging\FlashMessageQueue->dequeue()
  • \TYPO3\CMS\Core\Resource\Collection\AbstractFileCollection->key()
  • \TYPO3\CMS\Core\Resource\MetaDataAspect->offsetGet()
  • \TYPO3\CMS\Core\Resource\MetaDataAspect->current()
  • \TYPO3\CMS\Core\Resource\Search\Result\EmptyFileSearchResult->current()
  • \TYPO3\CMS\Core\Resource\Search\Result\EmptyFileSearchResult->key()
  • \TYPO3\CMS\Core\Routing\SiteRouteResult->offsetGet()
  • \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy->current()
  • \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy->key()
  • \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage->current()
  • \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage->offsetGet()
  • \TYPO3\CMS\Extbase\Persistence\Generic\QueryResult->offsetGet()
  • \TYPO3\CMS\Extbase\Persistence\Generic\QueryResult->current()
  • \TYPO3\CMS\Extbase\Persistence\Generic\QueryResult->key()
  • \TYPO3\CMS\Extbase\Persistence\ObjectStorage->current()
  • \TYPO3\CMS\Extbase\Persistence\ObjectStorage->offsetGet()
  • \TYPO3\CMS\Filelist\Dto\ResourceCollection->current()
  • \TYPO3\CMS\Filelist\Dto\ResourceCollection->key()

The following class properties visibility have been changed to protected:

  • \TYPO3\CMS\Core\Domain\Repository\PageRepository->where_hid_del
  • \TYPO3\CMS\Core\Domain\Repository\PageRepository->where_groupAccess
  • \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->type

The following class property visibility has been changed to private:

  • \TYPO3\CMS\Core\Type\DocType->getXhtmlVersion

The following class properties have been marked as internal:

  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->lastLogin_column
  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->formfield_uname
  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->formfield_uident
  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->formfield_status
  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->loginSessionStarted
  • \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication->dontSetCookie
  • \TYPO3\CMS\Core\Authentication\FrontendUserAuthentication->formfield_permanent
  • \TYPO3\CMS\Core\Authentication\FrontendUserAuthentication->is_permanent

The following class property has changed/enforced type:

  • \TYPO3\CMS\Core\Page\PageRenderer->endingSlash (is now string)

The following eID entry point has been removed:

  • requirejs

The following ViewHelpers have been changed or removed:

  • <f:be.buttons.csh> removed
  • <f:be.labels.csh> removed
  • <f:translate> Argument "alternativeLanguageKeys" has been removed

The following TypoScript options have been dropped or adapted:

  • config.baseURL
  • config.removePageCss
  • config.spamProtectEmailAddresses (only ascii value)
  • config.xhtmlDoctype
  • plugin.[pluginName]._CSS_PAGE_STYLE
  • [ip()] condition function must be used in a context with request
  • [loginUser()] condition function removed
  • [usergroup()] condition function removed
  • constants setup top-level-object and constants property of parseFunc
  • plugin.tx_felogin_login.settings.passwordValidators has been removed

The following constant has been dropped:

  • TYPO3_mainDir

The following class constants have been dropped:

  • \TYPO3\CMS\Core\Messaging\AbstractMessage::ERROR
  • \TYPO3\CMS\Core\Messaging\AbstractMessage::INFO
  • \TYPO3\CMS\Core\Messaging\AbstractMessage::NOTICE
  • \TYPO3\CMS\Core\Messaging\AbstractMessage::OK
  • \TYPO3\CMS\Core\Messaging\AbstractMessage::WARNING
  • \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR
  • \TYPO3\CMS\Core\Messaging\FlashMessage::INFO
  • \TYPO3\CMS\Core\Messaging\FlashMessage::NOTICE
  • \TYPO3\CMS\Core\Messaging\FlashMessage::OK
  • \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING
  • \TYPO3\CMS\Core\Page\JavaScriptModuleInstruction::FLAG_LOAD_REQUIRE_JS
  • \TYPO3\CMS\Reports\Status::ERROR
  • \TYPO3\CMS\Reports\Status::INFO
  • \TYPO3\CMS\Reports\Status::NOTICE
  • \TYPO3\CMS\Reports\Status::OK
  • \TYPO3\CMS\Reports\Status::WARNING

The following global option handling have been dropped and are ignored:

  • $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultUserTSconfig']
  • $GLOBALS['TYPO3_CONF_VARS']['FE']['versionNumberInFilename'] only accepts a boolean value now

The following global variables have been removed:

  • $GLOBALS['TBE_STYLES']
  • $GLOBALS['TBE_STYLES']['stylesheet']
  • $GLOBALS['TBE_STYLES']['stylesheet2']
  • $GLOBALS['TBE_STYLES']['skins']
  • $GLOBALS['TBE_STYLES']['admPanel']
  • $GLOBALS['TCA_DESCR']

The following hooks have been removed:

  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['RequireJS']['postInitializationModules']
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/cache/frontend/class.t3lib_cache_frontend_abstractfrontend.php']['flushByTag']
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass']
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing']
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing']
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing']
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['backendUserLogin']
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getDefaultUploadFolder']
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Lowlevel\Controller\ConfigurationController']['modifyBlindedConfigurationOptions']

The following single field configuration has been removed from TCA:

  • MM_insert_fields (for TCA fields with MM configuration)

The following event has been removed:

  • \TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent

The following fallbacks have been removed:

  • Usage of the ext_icon.* file locations for extension icons
  • Usage of the result property additionalJavaScriptPost of the form engine result array
  • Using chart.js v3 compatible widgets in ext:dashboard
  • Usage of .t3js-contextmenutrigger to trigger and configure context menus
  • Usage of the jsonArray property scriptCall for AjaxController's
  • Binding the selected menu items to callback actions in context menus
  • Checking for \TYPO3\CMS\Core\Site\SiteLanguageAwareTrait is removed in \TYPO3\CMS\Core\Routing\Aspect\AspectFactory
  • f:format.html ViewHelper no longer works in BE context
  • Usage of JScode containing inline JavaScript for handing custom signals
  • Usage property $resultArray['requireJsModules'] of the form engine result array
  • Using backend FormEngine, the current ServerRequestInterface request must be provided in key "request" as initialData to FormDataCompiler, the fallback to $GLOBALS['TYPO3_REQUEST'] has been removed.
  • Compatibility layer for "TCEforms" key in FlexFormTools has been removed
  • Compatibility layer for using array parameters for files in extbase (use UploadedFile instead)

The following upgrade wizards have been removed:

  • Wizard for migrating backend user languages
  • Wizard for installing the extension "legacy_collections" from TER
  • Wizard for migrating the transOrigDiffSourceField field to a json encoded string
  • Wizard for cleaning up workspace new placeholders
  • Wizard for cleaning up workspace move placeholders
  • Wizard for migrating shortcut records
  • Wizard for sanitizing existing SVG files in the fileadmin folder
  • Wizard for populating a new channel column of the sys_log table

The following features are now always enabled:

  • security.backend.enforceContentSecurityPolicy

The following feature has been removed:

  • Regular expression based validators in ext:form backend UI

The following database table fields have been removed:

  • fe_users.TSconfig
  • fe_groups.TSconfig

The following backend route identifier has been removed:

  • ajax_core_requirejs

The following global JavaScript variable has been removed:

  • TYPO3.Tooltip

The following global JavaScript function has been removed:

  • Global_JavaScript_Function_Name

The following JavaScript module has been removed:

  • tooltip

The following JavaScript method behaviour has changed:

  • ColorPicker.initialize() always requires an HTMLInputElement to be passed as first argument

The following JavaScript method has been removed:

  • getParameterFromUrl() of @typo3/backend/utility

The following CKEditor plugin has been removed:

  • SoftHyphen

The following dependency injection service aliase has been removed:

  • @dashboard.views.widget

Impact

Using above removed functionality will most likely raise PHP fatal level errors, may change website output or crashes browser JavaScript.

Breaking: #100966 - Remove jquery-ui

See forge#100966

Description

The NPM package jquery-ui has been removed completely for TYPO3 v13 without any substitute.

According to the TYPO3 Deprecation Policy, JavaScript code and packages used only in the TYPO3 backend are not considered to be part of that policy:

The deprecation policy does not cover the deprecations of backend components such as JavaScript code, CSS code, HTML code, and backend templates.

Impact

TYPO3 does not ship the NPM package jquery-ui any longer. Third-party extensions that rely on this package will be broken and need to be adjusted.

Since TYPO3 exposed only parts of jquery-ui, only the components core, draggable, droppable, mouse, resizable, selectable, sortable and widget are affected - other components simply did not exist.

Affected installations

Those having custom or third-party extensions using jquery-ui from typo3/sysext/core/Resources/Public/JavaScript/Contrib/jquery-ui/.

Migration

TYPO3 does not provide any substitute. In TYPO3 the draggable and resizable features of jquery-ui have been reimplemented in the new custom element <typo3-backend-draggable-resizable>.

Breaking: #101129 - Convert Action to native backed enum

See forge#101129

Description

The class \TYPO3\CMS\Scheduler\Task\Enumeration\Action is now converted to a native backed enum. In addition the class is moved to the namespace \TYPO3\CMS\Scheduler and renamed to SchedulerManagementAction.

Impact

Since \TYPO3\CMS\Scheduler\Task\Enumeration\Action is no longer a class, the existing class constants are no longer available. In addition it's not possible to instantiate it anymore.

Affected installations

Third-party extensions using the following class constants:

  • \TYPO3\CMS\Scheduler\Task\Enumeration\Action::ADD
  • \TYPO3\CMS\Scheduler\Task\Enumeration\Action::EDIT
  • \TYPO3\CMS\Scheduler\Task\Enumeration\Action::LIST

Class instantiation:

  • new Action('a-string')

Migration

Include the enum SchedulerManagementAction from namespace \TYPO3\CMS\Scheduler as a replacement for Action.

Use the new syntax

  • \TYPO3\CMS\Scheduler\SchedulerManagementAction::ADD
  • \TYPO3\CMS\Scheduler\SchedulerManagementAction::EDIT
  • \TYPO3\CMS\Scheduler\SchedulerManagementAction::LIST

as well as the tryFrom($aString) static method of the backed enum.

Breaking: #101131 - Convert LoginType to native backed enum

See forge#101131

Description

The class \TYPO3\CMS\Core\Authentication\LoginType is now converted to a native backed enum.

Impact

Since \TYPO3\CMS\Core\Authentication\LoginType is no longer a class, the existing class constants are no longer available.

Affected installations

Custom authenticators using the following class constants:

  • \TYPO3\CMS\Core\Authentication\LoginType::LOGIN
  • \TYPO3\CMS\Core\Authentication\LoginType::LOGOUT

Migration

Use the new syntax: \TYPO3\CMS\Core\Authentication\LoginType::LOGIN->value \TYPO3\CMS\Core\Authentication\LoginType::LOGOUT->value

Alternatively, use the enum method tryFrom to convert a value to an enum. For direct comparison of two enums, the null-coalescing operator shall be used to ensure that the parameter is a string:

<?php

use TYPO3\CMS\Core\Authentication\LoginType;

if (LoginType::tryFrom($value ?? '') === LoginType::LOGIN) {
    // Do login stuff
}
if (LoginType::tryFrom($value ?? '') === LoginType::LOGOUT) {
    // Do logout stuff
}
Copied!

Breaking: #101133 - IconFactory->getIcon() signature change

See forge#101133

Description

The public method getIcon() in \TYPO3\CMS\Core\Imaging\IconFactory has changed its 4th parameter, in order to prepare the removal of class \TYPO3\CMS\Core\Type\Icon\IconState.

Impact

Custom extensions extending the getIcon() method of class \TYPO3\CMS\Core\Imaging\IconFactory not having the same signature will fail with a PHP fatal error.

Affected installations

Custom extensions extending the getIcon() method from class \TYPO3\CMS\Core\Imaging\IconFactory .

Migration

Adapt the 4th parameter of getIcon() to be of type \TYPO3\CMS\Core\Type\Icon\IconState|IconState $state = null

In addition, adapt the code in the body of the method.

Breaking: #101133 - Icon->state changed type

See forge#101133

Description

The protected property \TYPO3\CMS\Core\Imaging\Icon->state holds now a native enum \TYPO3\CMS\Core\Imaging\IconState instead of an instance of \TYPO3\CMS\Core\Type\Icon\IconState.

Impact

Custom extensions calling \TYPO3\CMS\Core\Imaging\Icon->getState() will receive an enum now, which will most probably lead to PHP errors in the runtime.

Custom extensions calling \TYPO3\CMS\Core\Imaging\Icon->setState() with an instance of \TYPO3\CMS\Core\Type\Icon\IconState will receive a PHP TypeError.

Affected installations

Custom extensions calling \TYPO3\CMS\Core\Imaging\Icon->getState() or \TYPO3\CMS\Core\Imaging\Icon->setState().

Migration

Adapt your code to handle the native enum \TYPO3\CMS\Core\Imaging\IconState .

use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Type\Icon\IconState;
use TYPO3\CMS\Core\Utility\GeneralUtility;

// Before
$icon = GeneralUtility::makeInstance(Icon::class);
$icon->setState(IconState::cast(IconState::STATE_DEFAULT));
$state = $icon->getState();
$stateValue = (string)$state;

// After
$icon = GeneralUtility::makeInstance(Icon::class);
$icon->setState(IconState::STATE_DEFAULT);

$state = $icon->getState();
$stateValue = $state->value;
Copied!

Breaking: #101137 - Page Doktype "Recycler" removed

See forge#101137

Description

TYPO3 had multiple concepts of a recycler / trash bin. One of the oldest concepts was the ability to create a manual page of the type "Recycler" (page records with doktype=255 set) where editors could manually move content to such a page instead of deleting it. One other option is to use the Web > Recycler backend module (available with the shipped recycler system extension). This process is much more user-friendly: Any kind of record which has been (soft-)deleted can be viewed and re-added via this module, no manual process during the deletion process is needed.

For reasons of consistency and de-cluttering the UI, the former functionality has been removed from TYPO3 Core, along with the PHP class constant \TYPO3\CMS\Domain\Repository\PageRepository::DOKTYPE_RECYCLER.

Impact

The recycler doktype has been removed and cannot be selected or used anymore. Any existing recycler pages are migrated to a page of type "Backend User Section" which is also not accessible, if there is no valid backend user with permission to see this page.

Affected installations

TYPO3 installations using this special page doktype "Recycler".

Migration

A migration is in place, it is recommended to use the Recycler module with soft-deleting records.

Breaking: #101143 - Strict typing in LinktypeInterface

See forge#101143

Description

All methods in the interface \TYPO3\CMS\Linkvalidator\Linktype\LinktypeInterface are now strictly typed.

Impact

Classes implementing the interface must now ensure all methods are strictly typed.

Affected installations

Custom classes implementing \TYPO3\CMS\Linkvalidator\Linktype\LinktypeInterface

Migration

Ensure that classes that implement \TYPO3\CMS\Linkvalidator\Linktype\LinktypeInterface have the following signatures:

public function checkLink(string $url, array $softRefEntry, LinkAnalyzer $reference): bool;
public function fetchType(array $value, string $type, string $key): string;
public function getErrorParams(): array;
public function getBrokenUrl(array $row): string;
public function getErrorMessage(array $errorParams): string;
Copied!

Breaking: #101149 - Mark PageTsBackendLayoutDataProvider as final

See forge#101149

Description

The class \TYPO3\CMS\Backend\View\BackendLayout\PageTsBackendLayoutDataProvider is marked as final.

Impact

It is no longer possible to extend the class \TYPO3\CMS\Backend\View\BackendLayout\PageTsBackendLayoutDataProvider .

Affected installations

Classes extending \TYPO3\CMS\Backend\View\BackendLayout\PageTsBackendLayoutDataProvider .

Migration

Instead of extending the data provider, it is recommended to register a custom DataProvider for backend layouts, which can already be used since TYPO3 v7.

Breaking: #101175 - Convert VersionState to native backed enum

See forge#101175

Description

The class \TYPO3\CMS\Core\Versioning\VersionState is now converted to a native PHP backed enum.

Impact

Since \TYPO3\CMS\Core\Versioning\VersionState is no longer a class, the existing class constants are no longer available, but are enum instances instead.

In addition it's not possible to instantiate it anymore or call the equals() method.

Affected installations

TYPO3 code using the following code:

Using the following class constants:

  • \TYPO3\CMS\Core\Versioning\VersionState::DEFAULT_STATE
  • \TYPO3\CMS\Core\Versioning\VersionState::NEW_PLACEHOLDER
  • \TYPO3\CMS\Core\Versioning\VersionState::DELETE_PLACEHOLDER
  • \TYPO3\CMS\Core\Versioning\VersionState::MOVE_POINTER

Class instantiation:

  • new \TYPO3\CMS\Core\Versioning\(VersionState::*->value)

where * denotes one of the enum values.

Method invocation:

  • \TYPO3\CMS\Core\Versioning\VersionState::cast()
  • \TYPO3\CMS\Core\Versioning\VersionState::cast()->equals()

Migration

Use the new syntax for getting the values:

  • \TYPO3\CMS\Core\Versioning\VersionState::DEFAULT_STATE->value
  • \TYPO3\CMS\Core\Versioning\VersionState::NEW_PLACEHOLDER->value
  • \TYPO3\CMS\Core\Versioning\VersionState::DELETE_PLACEHOLDER->value
  • \TYPO3\CMS\Core\Versioning\VersionState::MOVE_POINTER->value

Class instantiation should be replaced by:

  • \TYPO3\CMS\Core\Versioning\VersionState::tryFrom($row['t3ver_state'])

Method invocation of cast()/ equals() should be replaced by:

  • \TYPO3\CMS\Core\Versioning\VersionState::tryFrom(...)
  • \TYPO3\CMS\Core\Versioning\VersionState::tryFrom(...) === VersionState::MOVE_POINTER

Breaking: #101186 - Strict typing in UnableToLinkException

See forge#101186

Description

The class constructor in \TYPO3\CMS\Frontend\Exception\UnableToLinkException is now strictly typed. In addition, the variable $linkText has type string.

Impact

The class constructor is now strictly typed.

Affected installations

TYPO3 sites using the \TYPO3\CMS\Frontend\Exception\UnableToLinkException exception.

Migration

Ensure that the class constructor is called properly, according to the changed signature:

public function __construct(
    string $message = '',
    int $code = 0,
    ?\Throwable $previous = null,
    string $linkText = ''
);
Copied!

Breaking: #101192 - Remove fallback for CKEditor removePlugins

See forge#101192

Description

Remove fallback for CKEditor configuration removePlugins as a string.

Impact

Runtime Javascript errors can occur if the CKEditor configuration removePlugins isn't an array.

Affected installations

TYPO3 installation which have CKEditor configuration removePlugins configured as a string.

Migration

Adjust your CKEditor configuration and pass removePlugins as array.

Before

editor:
    config:
        removePlugins: image
Copied!

After

editor:
    config:
        removePlugins:
            - image
Copied!

Breaking: #101266 - Remove RequireJS

See forge#101266

Description

The RequireJS project has been discontinued and was therefore deprecated in TYPO3 v12 with forge#96510 in favor of native ECMAScript v6/v11 modules (added in forge#96510).

The infrastructure for configuration and loading of RequireJS modules is now removed.

Impact

Registering FormEngine JavaScript modules via 'requireJsModules' will have no effect. The PageRenderer endpoints \TYPO3\CMS\Core\Page\PageRenderer->loadRequireJs() and \TYPO3\CMS\Core\Page\PageRenderer->loadRequireJsModule() have been removed and must no longer be called. The respective includeJavaScriptModules property of the ViewHelper <f:be.pageRenderer> ViewHelper has also been removed.

Affected installations

TYPO3 installations using RequireJS modules to provide JavaScript in the TYPO3 backend, or – less common – use PageRenderer RequireJS infrastructure for frontend JavaScript module loading.

Migration

Migrate your JavaScript from the AMD module format to native ES6 modules and register your configuration in Configuration/JavaScriptModules.php, also see forge#96510 and ES6 in the TYPO3 Backend for more information:

# Configuration/JavaScriptModules.php
<?php

return [
    'dependencies' => ['core', 'backend'],
    'imports' => [
        '@vendor/my-extension/' => 'EXT:my_extension/Resources/Public/JavaScript/',
    ],
];
Copied!

Then use \TYPO3\CMS\Core\Page\PageRenderer->loadJavaScriptModule() instead of \TYPO3\CMS\Core\Page\PageRenderer->loadRequireJsModule() to load the ES6 module:

// via PageRenderer
$this->pageRenderer->loadJavaScriptModule('@vendor/my-extension/example.js');
Copied!

In Fluid templates includeJavaScriptModules is to be used instead of includeRequireJsModules:

In Fluid template the includeJavaScriptModules property of the <f:be.pageRenderer> ViewHelper may be used:

<f:be.pageRenderer
   includeJavaScriptModules="{
      0: '@vendor/my-extension/example.js'
   }"
/>
Copied!

Breaking: #101281 - Introduce type declarations in ResourceInterface

See forge#101281

Description

The following methods of interface \TYPO3\CMS\Core\Resource\ResourceInterface have been given return type declarations:

public function getIdentifier(): string;
public function getName(): string;
public function getStorage(): ResourceStorage;
public function getHashedIdentifier(): string;
public function getParentFolder(): FolderInterface;
Copied!

Impact

This affects many classes due to the following implementation rules:

  • \TYPO3\CMS\Core\Resource\Folder , because it implements \TYPO3\CMS\Core\Resource\FolderInterface which extends \TYPO3\CMS\Core\Resource\ResourceInterface
  • \TYPO3\CMS\Core\Resource\FileReference , and \TYPO3\CMS\Core\Resource\AbstractFile because both implement \TYPO3\CMS\Core\Resource\FileInterface which extends \TYPO3\CMS\Core\Resource\ResourceInterface
  • \TYPO3\CMS\Core\Resource\File and \TYPO3\CMS\Core\Resource\ProcessedFile because both extend \TYPO3\CMS\Core\Resource\AbstractFile

In consequence, the following methods are affected:

  • \TYPO3\CMS\Core\Resource\Folder::getIdentifier()
  • \TYPO3\CMS\Core\Resource\Folder::getName()
  • \TYPO3\CMS\Core\Resource\Folder::getStorage()
  • \TYPO3\CMS\Core\Resource\Folder::getHashedIdentifier()
  • \TYPO3\CMS\Core\Resource\Folder::getParentFolder()
  • \TYPO3\CMS\Core\Resource\FileReference::getIdentifier()
  • \TYPO3\CMS\Core\Resource\FileReference::getName()
  • \TYPO3\CMS\Core\Resource\FileReference::getStorage()
  • \TYPO3\CMS\Core\Resource\FileReference::getHashedIdentifier()
  • \TYPO3\CMS\Core\Resource\FileReference::getParentFolder()
  • \TYPO3\CMS\Core\Resource\AbstractFile::getIdentifier()
  • \TYPO3\CMS\Core\Resource\AbstractFile::getName()
  • \TYPO3\CMS\Core\Resource\AbstractFile::getStorage()
  • \TYPO3\CMS\Core\Resource\AbstractFile::getHashedIdentifier()
  • \TYPO3\CMS\Core\Resource\AbstractFile::getParentFolder()
  • \TYPO3\CMS\Core\Resource\File::getIdentifier()
  • \TYPO3\CMS\Core\Resource\File::getName()
  • \TYPO3\CMS\Core\Resource\File::getStorage()
  • \TYPO3\CMS\Core\Resource\File::getHashedIdentifier()
  • \TYPO3\CMS\Core\Resource\File::getParentFolder()
  • \TYPO3\CMS\Core\Resource\ProcessedFile::getIdentifier()
  • \TYPO3\CMS\Core\Resource\ProcessedFile::getName()
  • \TYPO3\CMS\Core\Resource\ProcessedFile::getStorage()
  • \TYPO3\CMS\Core\Resource\ProcessedFile::getHashedIdentifier()
  • \TYPO3\CMS\Core\Resource\ProcessedFile::getParentFolder()

Affected installations

Affected installations are those which either implement the ResourceInterface directly (very unlikely) or those that extend any of mentioned implementations (Core classes).

The usage (the API) of those implementation itself has not changed!

Migration

Use the same return type declarations as ResourceInterface does.

Breaking: #101291 - Introduce capabilities bit set

See forge#101291

Description

The capabilities property of the ResourceStorage and drivers ( LocalDriver/ AbstractDriver) have been converted from an integer (holding a bit value) to an instance of a new BitSet class \TYPO3\CMS\Core\Resource\Capabilities .

This affects the public API of the following interface methods:

  • \TYPO3\CMS\Core\Resource\Driver\DriverInterface::getCapabilities()
  • \TYPO3\CMS\Core\Resource\Driver\DriverInterface::mergeConfigurationCapabilities()

In consequence, all mentioned methods of implementations are affected as well, those of:

  • \TYPO3\CMS\Core\Resource\Driver\AbstractDriver::getCapabilities()
  • \TYPO3\CMS\Core\Resource\Driver\LocalDriver::mergeConfigurationCapabilities()

Also the following constants have been removed:

  • \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_BROWSABLE
  • \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_PUBLIC
  • \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_WRITABLE
  • \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_HIERARCHICAL_IDENTIFIERS

Impact

The return type of the following methods, respective their implementations have changed from int to \TYPO3\CMS\Core\Resource\Capabilities :

  • \TYPO3\CMS\Core\Resource\Driver\DriverInterface::getCapabilities()
  • \TYPO3\CMS\Core\Resource\Driver\DriverInterface::mergeConfigurationCapabilities()

The type of the parameter $capabilities of the method mergeConfigurationCapabilities() has been changed from int to \TYPO3\CMS\Core\Resource\Capabilities .

The usage of the mentioned, removed constants of \TYPO3\CMS\Core\Resource\ResourceStorageInterface will lead to errors.

Affected installations

Installations that implement custom drivers and therefore directly implement \TYPO3\CMS\Core\Resource\Driver\DriverInterface or extend \TYPO3\CMS\Core\Resource\Driver\AbstractDriver .

Also, installations that use the removed constants of \TYPO3\CMS\Core\Resource\ResourceStorageInterface .

Migration

When using mentioned methods that formerly returned the bit value as integer or expected the bit value as integer parameter need to use the Capabilities class instead. It behaves exactly the same as the plain integer. If the plain integer value needs to be retrieved, __toInt() can be called on Capabilities instances.

The following removed constants

  • \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_BROWSABLE
  • \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_PUBLIC
  • \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_WRITABLE
  • \TYPO3\CMS\Core\Resource\ResourceStorageInterface::CAPABILITY_HIERARCHICAL_IDENTIFIERS

can be replaced with public constants of the new Capabilities class:

  • \TYPO3\CMS\Core\Resource\Capabilities::CAPABILITY_BROWSABLE
  • \TYPO3\CMS\Core\Resource\Capabilities::CAPABILITY_PUBLIC
  • \TYPO3\CMS\Core\Resource\Capabilities::CAPABILITY_WRITABLE
  • \TYPO3\CMS\Core\Resource\Capabilities::CAPABILITY_HIERARCHICAL_IDENTIFIERS

Breaking: #101294 - Introduce type declarations in FileInterface

See forge#101294

Description

Return and param type declarations have been introduced for all methods stubs of \TYPO3\CMS\Core\Resource\FileInterface .

Impact

In consequence, all implementations of \TYPO3\CMS\Core\Resource\FileInterface need to reflect those changes and add the same return and param type declarations.

In case, any of the Core implementations are extended, overridden methods might need to be adjusted. The Core classes, implementing \TYPO3\CMS\Core\Resource\FileInterface , are:

  • \TYPO3\CMS\Core\Resource\AbstractFile
  • \TYPO3\CMS\Core\Resource\File
  • \TYPO3\CMS\Core\Resource\FileReference
  • \TYPO3\CMS\Core\Resource\ProcessedFile

Affected installations

Only those installations that implement \TYPO3\CMS\Core\Resource\FileInterface directly or that extend any of those mentioned core implementations.

Migration

Return and param type declarations have to be synced with the ones of the interface.

Breaking: #101305 - Introduce type declarations for some methods in GeneralUtility

See forge#101305, forge#101453

Description

Native return and param type declarations have been introduced for the following methods of \TYPO3\CMS\Core\Utility\GeneralUtility :

  • addInstance()
  • array2xml()
  • callUserFunction()
  • cmpFQDN()
  • cmpIP()
  • cmpIPv4()
  • cmpIPv6()
  • copyDirectory()
  • createDirectoryPath()
  • createVersionNumberedFilename()
  • explodeUrl2Array()
  • fixPermissions()
  • flushInternalRuntimeCaches()
  • getAllFilesAndFoldersInPath()
  • getBytesFromSizeMeasurement()
  • getClassName()
  • getFileAbsFileName()
  • getFilesInDir()
  • getIndpEnv()
  • getInstances()
  • getLogger()
  • getSingletonInstances()
  • getUrl()
  • get_dirs()
  • get_tag_attributes()
  • implodeArrayForUrl()
  • implodeAttributes()
  • intExplode()
  • isAllowedAbsPath()
  • isOnCurrentHost()
  • isValidUrl()
  • jsonEncodeForHtmlAttribute()
  • jsonEncodeForJavaScript()
  • locationHeaderUrl()
  • makeInstanceForDi()
  • mkdir_deep()
  • mkdir()
  • normalizeIPv6()
  • purgeInstances()
  • quoteJSvalue()
  • removePrefixPathFromList()
  • removeSingletonInstance()
  • resetSingletonInstances()
  • resolveBackPath()
  • revExplode()
  • rmdir()
  • sanitizeLocalUrl()
  • setIndpEnv()
  • setSingletonInstance()
  • split_tag_attributes()
  • tempnam()
  • trimExplode()
  • unlink_tempfile()
  • upload_copy_move()
  • upload_to_tempfile()
  • validEmail()
  • validIP()
  • validIPv4()
  • validIPv6()
  • validPathStr()
  • webserverUsesHttps()
  • wrapJS()
  • writeFileToTypo3tempDir()
  • writeFile()
  • writeJavaScriptContentToTemporaryFile()
  • writeStyleSheetContentToTemporaryFile()
  • xml2arrayProcess()
  • xml2array()
  • xml2tree()
  • xmlRecompileFromStructValArray()

Impact

Calling any of the mentioned methods with invalid types will result in a PHP error.

Affected installations

Only those installations that use the mentioned methods with invalid types.

Migration

Make sure to pass parameters of the required types to the mentioned methods.

Breaking: #101309 - Introduce type declarations in DriverInterface

See forge#101309

Description

Return and param type declarations have been introduced for all methods stubs of \TYPO3\CMS\Core\Resource\Driver\DriverInterface .

Also, method \TYPO3\CMS\Core\Resource\Driver\AbstractDriver::sanitizeFileName() has been removed.

Impact

In consequence, all implementations of \TYPO3\CMS\Core\Resource\Driver\DriverInterface need to reflect those changes and add the same return and param type declarations.

In case, any of the Core implementations are extended, overridden methods might need to be adjusted. The Core classes, implementing \TYPO3\CMS\Core\Resource\DriverInterface, are:

  • \TYPO3\CMS\Core\Resource\Driver\AbstractDriver
  • \TYPO3\CMS\Core\Resource\Driver\AbstractHierarchicalFilesystemDriver
  • \TYPO3\CMS\Core\Resource\Driver\LocalDriver

Concerning removed method \TYPO3\CMS\Core\Resource\Driver\AbstractDriver::sanitizeFileName():

Said method didn't sanitize at all, it didn't respect the given $charset param and simply returned the input string. Abstract classes MAY fulfill the interface contract but if they do so, they MUST do it right. There is no benefit in fulfilling it just signature wise, it MUST fulfill it functional wise and in this case it didn't. That's why LocalDriver reimplements sanitizeFileName() completely.

As a consequence of this removal, all classes that extend either \TYPO3\CMS\Core\Resource\Driver\AbstractDriver or \TYPO3\CMS\Core\Resource\Driver\AbstractHierarchicalFilesystemDriver , need to implement method sanitizeFileName().

Affected installations

All installations that implement \TYPO3\CMS\Core\Resource\DriverInterface or that extend either \TYPO3\CMS\Core\Resource\Driver\AbstractDriver or \TYPO3\CMS\Core\Resource\Driver\AbstractHierarchicalFilesystemDriver .

Migration

As for the type declarations: Add the same param and return type declarations the interface does.

As for the removed method \TYPO3\CMS\Core\Resource\Driver\AbstractDriver::sanitizeFileName():

Implement the method according to your driver capabilities.

Breaking: #101311 - Make the parameter for GeneralUtility::sanitizeLocalUrl required

See forge#101311

Description

The (only) parameter for \TYPO3\CMS\Core\Utility\GeneralUtility::sanitizeLocalUrl() is now required.

Impact

Calling GeneralUtility::sanitizeLocalUrl() without an argument will result in a PHP error.

Affected installations

Only those installations that call GeneralUtility::sanitizeLocalUrl() without an argument.

The extension scanner will detect affected usages as a strong match.

Migration

Make sure to pass an argument to GeneralUtility::sanitizeLocalUrl().

Breaking: #101327 - Harden FileInterface::getSize()

See forge#101327

Description

A return type declaration has been added to the method stub \TYPO3\CMS\Core\Resource\FileInterface::getSize(). As a consequence, implementations of said method, \TYPO3\CMS\Core\Resource\AbstractFile::getSize() and \TYPO3\CMS\Core\Resource\FileReference::getSize() received return type declarations as well.

Also, \TYPO3\CMS\Core\Resource\AbstractFile::getSize() has been adjusted to actually just return an integer. Previously, it returned null, if the actual size could not be gathered. It now returns 0 in that case.

Impact

Code, that calls \TYPO3\CMS\Core\Resource\AbstractFile::getSize() through derivatives like \TYPO3\CMS\Core\Resource\File::getSize() might be adjusted to not respect null any more.

Implementations (classes) that implement \TYPO3\CMS\Core\Resource\FileInterface , have to adjust the return type of the method getSize() to match the contract.

Affected installations

Installations that implement \TYPO3\CMS\Core\Resource\FileInterface or that call \TYPO3\CMS\Core\Resource\FileInterface::getSize() via derivatives.

Migration

Adjust the return type and possible null checks.

Breaking: #101398 - Remove leftover $fetchAllFields in RelationHandler

See forge#101398

Description

The \TYPO3\CMS\Core\Database\RelationHandler had an unused property $fetchAllFields since TYPO3 v11.5.0. The related method setFetchAllFields() has been removed with it.

Impact

Custom extensions calling \TYPO3\CMS\Core\Database\RelationHandler->setFetchAllFields() will result in a PHP Fatal error.

Affected installations

All installations with custom extensions calling \TYPO3\CMS\Core\Database\RelationHandler->setFetchAllFields().

Migration

Remove the affected line of code. This method has had no effect since TYPO3 v11.5.0.

Breaking: #101469 - Introduce type declarations in FolderInterface

See forge#101469

Description

Return and param type declarations have been introduced for all methods stubs of \TYPO3\CMS\Core\Resource\FolderInterface .

Impact

In consequence, all implementations of \TYPO3\CMS\Core\Resource\FolderInterface need to reflect those changes and add the same return and param type declarations.

In case, any of the Core implementations are extended, overridden methods might need to be adjusted. The Core classes, implementing \TYPO3\CMS\Core\Resource\FolderInterface are:

  • \TYPO3\CMS\Core\Resource\Folder
  • \TYPO3\CMS\Core\Resource\InaccessibleFolder

Affected installations

All installations that implement \TYPO3\CMS\Core\Resource\FolderInterface or that extend either \TYPO3\CMS\Core\Resource\Folder or \TYPO3\CMS\Core\Resource\InaccessibleFolder .

Migration

Add the same param and return type declarations the interface does.

Breaking: #101471 - Introduce type declarations in AbstractDriver

See forge#101471

Description

Return and param type declarations have been introduced for all methods and method stubs of \TYPO3\CMS\Core\Resource\Driver\AbstractDriver and \TYPO3\CMS\Core\Resource\Driver\AbstractHierarchicalFilesystemDriver

Impact

In consequence, all classes, extending any of those abstract classes and overriding any of those affected methods need to reflect those changes and add the same return and param type declarations.

Affected methods are:

  • \TYPO3\CMS\Core\Resource\Driver\AbstractDriver::isValidFilename()
  • \TYPO3\CMS\Core\Resource\Driver\AbstractDriver::getTemporaryPathForFile()
  • \TYPO3\CMS\Core\Resource\Driver\AbstractDriver::canonicalizeAndCheckFilePath()
  • \TYPO3\CMS\Core\Resource\Driver\AbstractDriver::canonicalizeAndCheckFileIdentifier()
  • \TYPO3\CMS\Core\Resource\Driver\AbstractDriver::canonicalizeAndCheckFolderIdentifier()
  • \TYPO3\CMS\Core\Resource\Driver\AbstractHierarchicalFilesystemDriver::isPathValid()
  • \TYPO3\CMS\Core\Resource\Driver\AbstractHierarchicalFilesystemDriver::canonicalizeAndCheckFilePath()
  • \TYPO3\CMS\Core\Resource\Driver\AbstractHierarchicalFilesystemDriver::canonicalizeAndCheckFileIdentifier()
  • \TYPO3\CMS\Core\Resource\Driver\AbstractHierarchicalFilesystemDriver::canonicalizeAndCheckFolderIdentifier()

Affected installations

Installations that extend any of those abstract classes might be affected.

Migration

Add the same param and return type declarations the interface does.

Breaking: #101519 - Remove immediate flag in DebounceEvent

See forge#101519

Description

With the introduction in TYPO3 v10, the DebounceEvent module had the possibility to shift the event handler execution to the beginning of the debounce sequence, enabled via the optional immediate parameter.

The parameter is unused in TYPO3 and using this feature has a negative impact on UX. If used, the event handler is directly executed and the user has to wait a specific time after the last event was triggered, before any further execution is possible.

The flag immediate has been therefore removed.

Impact

The DebounceEvent module now always waits until a certain time has passed after the last trigger of the event happened before executing the event handler. This is mostly used in potential heavy tasks, for example, an Ajax request that is sent depending on the content of a search field.

Affected installations

All extensions using the removed flag are affected.

Migration

There is no direct migration possible. An extension author either may re-implement the removed behavior manually, or use the ThrottleEvent module, providing a similar behavior.

Breaking: #101603 - Removed hook for overriding icon overlay identifier

See forge#101603

Description

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Core\Imaging\IconFactory']['overrideIconOverlay'] has been removed in favor of a new PSR-14 event \TYPO3\CMS\Core\Imaging\Event\ModifyRecordOverlayIconIdentifierEvent .

Impact

Any hook implementation registered is not executed anymore in TYPO3 v13.0+.

Affected Installations

TYPO3 installations with custom extensions using this hook. The extension scanner will report usages as strong match.

Migration

The hook is removed without deprecation in order to allow extensions to work with TYPO3 v12 (using the hook) and v13+ (using the new event) when implementing the event as well without any further deprecations.

Replace any hook usage with the new PSR-14 event.

Breaking: #101612 - LinkParameterProviderInterface changed

See forge#101612

Description

The PHP interface \TYPO3\CMS\Backend\Tree\View\LinkParameterProviderInterface has changed. The interface is used to generate URLs with query parameters for links within element browsers or link browsers in the TYPO3 backend.

The methods getScriptUrl() and isCurrentlySelectedItem() have been removed from the interface, as the implementing link browsers do not need this information anymore due to simplification in routing.

The method getUrlParameters() now has a native return type array, whereas previously this was only type-hinted.

Impact

When accessing implementing PHP objects, it should be noted that these methods do not exist anymore. When called this might result in fatal PHP errors.

When implementing the PHP interface, the implementing code will fail due to missing return types.

Affected installations

TYPO3 installations with custom implementations of this interface.

Migration

For extensions implementing the interface, the return type for getUrlParameters() can be added in order to be TYPO3 v12+ compatible. For v13-only compatibility, it is recommended to remove the superfluous methods.

Breaking: #101647 - Unused graphical assets removed from EXT:backend

See forge#101647

Description

The TYPO3 system extension "backend" accumulated many graphical assets over the years that became unused piece by piece.

The following icons have been removed from the Icon Registry:

  • status-edit-read-only
  • warning-in-use
  • warning-lock

The following assets have been removed from the directory EXT:backend/Resources/Public/Images/:

  • FormFieldWizard/wizard_forms.gif
  • clear.gif
  • filetree-folder-default.png
  • filetree-folder-opened.png
  • Logo.png
  • pages.gif
  • tt_content.gif

Impact

Calling any of the removed icons from the Icon Registry will render the default icon. Accessing any of the removed files directly will lead to a 404 error.

Affected installations

All extensions using the removed icons and assets are affected.

Migration

No direct migration is available.

Breaking: #101671 - Disable external linktypes by default in EXT:linkvalidator

See forge#101671

Description

There are several known problems with external link checking in Linkvalidator, such as:

  • "False positives": Some links are reported broken, but are not broken, see forge#101670.
  • External sites are checked without rate limit which may cause sites which perform link checking to be blocked, see forge#89287.
  • No caching of results (except for a runtime cache during link checking which will be invalid on next run)

These issues are currently not easily solvable and should also be addressed specifically for the site concerned.

We now deactivate checking external link types by default in the configuration: TSconfig Reference. The "external" link types checking still works but must be enabled explicitly.

This will make administrators more aware of problems and the specific problems can be addressed, for example, by providing a custom class to replace \TYPO3\CMS\Linkvalidator\Linktype\ExternalLinktype . Additionally, a page Known Problems was already added to the documentation in a previous patch.

Impact

External links will no longer be checked by default in EXT:linkvalidator unless mod.linkvalidator.linktypes is specifically set via page TSconfig.

Affected installations

Installations using EXT:linkvalidator.

Migration

Either leave external link checking deactivated or find ways to mitigate the problems with external link checking.

Solutions:

  • do not use external link checking
  • or, create a custom linktype class to replace ExternalLinktype

    • the custom link type should rate limit when checking external links, for example, by adding a crawl delay in the link targets with the same domain
    • the custom link type should find a way to handle possible false positives
    • alternatively the external link type should restrict link checking to known domains without problems
    • alternatively, there should be a method to exclude specific URLs or domains from link checking
    • excessive checking of external links should be avoided, for example, by using a link target cache

More information is available in the Linkvalidator documentation:

Example for activating external linktype

EXT:my_sitepackage/Configuration/page.tsconfig
mod.linkvalidator.linktypes = db,file,external
Copied!

Breaking: #101820 - Remove bootstrap jQuery interface and window.jQuery

See forge#101820

Description

The bootstrap jQuery interfaces required a global window.jQuery variable to be set. The jquery drop-in is dropped in order to remove this non-optional jQuery dependency.

As a side effect the window.jQuery global is removed as well. Note that global jQuery usage has already been deprecated in forge#86438 and removed in forge#97243 with the suggestion to use JavaScript modules instead. window.jQuery was basically left in place for bootstrap to operate and therefore only window.$ was removed back then.

Impact

Loading the ES6 'bootstrap' module no longer has side effects, as the global scope window is no longer polluted by writing to the property jQuery. This also means jQuery will no longer be loaded when it is not actually needed.

Affected Installations

All installations that use bootstrap's jQuery interface or applications that use window.jQuery to invoke jQuery.

Following method calls are affected:

  • $(…).alert()
  • $(…).button()
  • $(…).carousel()
  • $(…).collapse()
  • $(…).dropdown()
  • $(…).tab()
  • $(…).modal()
  • $(…).offcanvas()
  • $(…).popover()
  • $(…).scrollspy()
  • $(…).toast()
  • $(…).tooltip()

Migration

Use bootstrap's ES6 exports import { Carousel } from 'bootstrap'; instead.

Breaking: #101822 - Change callback interruption in @typo3/backend/document-save-actions

See forge#101822

Description

The JavaScript module @typo3/backend/document-save-actions is used in FormEngine and Scheduler context mainly to disable the submit button in the according forms, where also a spinner is rendered within the button to visualize a running action.

Over the time, the module took over some tasks that logically belong to FormEngine, which lead to slimming down the module. In a further effort, jQuery has been removed from said module, leading to a change in behavior how the callback chain can be aborted.

Native JavaScript events cannot get asked whether event propagation has been stopped, making changes in the callbacks necessary. All callbacks registered via DocumentSaveActions.getInstance().addPreSubmitCallback() now need to return a boolean value.

Impact

Using stop[Immediate]Propagation() on events passed into registered callbacks is now unsupported and may lead to undefined behavior.

Affected installations

All extensions using DocumentSaveActions.getInstance().addPreSubmitCallback() are affected.

Migration

Callbacks now need to return a boolean value, where returning false will abort the callback execution chain.

Breaking: #101933 - Dispatch AfterUserLoggedInEvent for frontend user login

See forge#101933

Description

The \TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent PSR-14 event is now also dispatched for a successful frontend user login.

Impact

Listeners to the AfterUserLoggedInEvent event should evaluate the implementation type of the $user property, if custom functionality after a user login should be executed for backend login only.

Affected installations

Installations with an event listener to the php:AfterUserLoggedInEvent PSR-14 event.

Migration

If custom functionality in a listener to the AfterUserLoggedInEvent event should be executed for the backend user login only, a type check for the $user property must be added.

// Before
public function __invoke(AfterUserLoggedInEvent $afterUserLoggedInEvent): void
{
    // custom logic after backend user login
}

// After
public function __invoke(AfterUserLoggedInEvent $afterUserLoggedInEvent): void
{
    if ($afterUserLoggedInEvent->getUser() instanceof BackendUserAuthentication) {
        // custom logic after backend user login
    }
}
Copied!

Breaking: #101948 - File-based AbstractRepository class removed

See forge#101948

Description

When the base architecture of File Abstraction Layer (FAL) was introduced in TYPO3 v6.0, various functionality was based on concepts based on Extbase's architecture. Some concepts never flourished. One of them being the \TYPO3\CMS\Core\Resource\AbstractRepository class from FAL.

This PHP class served as a basis for 2 PHP classes, \TYPO3\CMS\Core\Resource\FileRepository and \TYPO3\CMS\Core\Resource\ProcessedFileRepository .

Nowadays, it is obvious that some decisions in this area were not useful:

1. The coupling to Extbase's repository architecture does not work out, as the manual database queries that return objects should not be bound to Extbase's QueryRestrictions.

These never worked and were never implemented in the mentioned repository classes from FAL.

It becomes abundantly clear that the concepts do not match by looking at the AbstractRepository class which even had exceptions for methods that were not compatible with Extbase.

2. The concept of inheritance did not work out for Dependency Injection introduced in TYPO3 v10, and with PHP 8.x which reveals various typing problems that arose around AbstractRepository.

AbstractRepository is thus removed, and the implementing classes do not extend from this class anymore, as they only include the methods required for their purpose, and are now completely strictly typed.

Additionally, FileRepository has been cleaned up by removing findFileReferenceByUid() as it is only a wrapper to ResourceFactory::getFileReferenceObject()

Impact

Code that uses the three classes in a third-party extension might fail as the implementing PHP repositories FileRepository and ProcessedFileRepository have only necessary methods available.

PHP extensions that derive from the AbstractRepository will stop working.

Code that used FileRepository::findFileReferenceByUid() will break.

Affected installations

As all three PHP classes are low-level in the FAL API, the impact for regular installations will be rather low. Third-party extensions that extend from the AbstractRepository of FAL, which is a wild use-case will stop working. It is safe to say, that only edge-case extensions that worked with the FAL API might be affected, but regular installations will see no difference.

Migration

Only extension authors working with the low-level API of File Abstraction Layer would need to adapt their code to be type-safe. Extensions that extend from the AbstractRepository class of FAL should implement the necessary methods themselves and remove the dependency from AbstractRepository.

It is highly recommended to not use any of these classes, but rather stick to high-level API of FAL, such as ResourceFactory, File or ResourceStorage.

Replace former calls to FileRepository::findFileReferenceByUid() like:

$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
$reference = $fileRepository->findFileReferenceByUid($referenceUid);
Copied!

by using the ResourceFactory with new code like:

$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$reference = $resourceFactory->getFileReferenceObject($referenceUid);
Copied!

Breaking: #101950 - Removed legacy setting 'GFX/processor_allowTemporaryMasksAsPng'

See forge#101950

Description

GFX/processor_allowTemporaryMasksAsPng is a setting that stems from an even older setting called im_mask_temp_ext_gif. This setting was added because generally PNG generation of Image/GraphicsMagick is always faster than generating GIF files, but there were issues with PNG files in earlier versions of ImageMagick 5.

TYPO3 requires newer versions of GraphicsMagick and at least ImageMagick version 6, in which the above reported behaviours couldn't be replicated anymore, obsoleting the need for a non-PNG setting entirely.

The following global settings have no effect anymore and are automatically removed if still in use:

  • $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_allowTemporaryMasksAsPng'] (removed)

Impact

Temporarily saved masking images are now saved as PNG files rather than GIF images.

Testing has revealed no visual changes between this setting being turned on or off, with both ImageMagick or GraphicsMagick.

Affected installations

Every instance that already didn't set processor_allowTemporaryMasksAsPng to true.

Migration

The configuration value has been removed without replacement. No migration is necessary.

Breaking: #102009 - imagesizes cache removed

See forge#102009

Description

A cache layer called "imagesizes" was added in 2004 (= TYPO3 3.x) to cache width and height of any kind of images - mostly generated by GifBuilder or ImageMagick. It was using the PHP function getimagesizes() or, if this failed, ImageMagick identify command, which is costly.

In 2012, the new processing layer for File Abstraction Layer (FAL - via sys_file_processedfile) was introduced with a more modern API, which persists final information about processed images in a separate database table.

Any kind of information processing then is first checked in FAL and then stored again in cache_imagesizes. Some more files, which do not use FAL still utilize this functionality, but the second level cache layer (by default in the database), is unneeded nowadays.

For this reason, the lowlevel cache "imagesizes" is removed along with some methods which were not marked as internal, but marked public:

  • \TYPO3\CMS\Core\Imaging\GraphicalFunctions->cacheImageDimensions()
  • \TYPO3\CMS\Core\Imaging\GraphicalFunctions->getCachedImageDimensions()

The main entry point \TYPO3\CMS\Core\Imaging\GraphicalFunctions->getImageDimensions() is still available but does not use a cache layer anymore.

Impact

Calling the removed public methods will result in a fatal PHP error. In addition, accessing the database table directly or via TYPO3's CacheManager is not possible anymore, as the "imagesizes" cache is removed.

Affected installations

TYPO3 installations which use the cache directly or access these methods directly, which is very very unlikely.

Migration

The now unused database tables are automatically removed when using the database compare update is called. When trying to retrieve image dimensions, the \TYPO3\CMS\Core\Type\File\Imageinfo PHP class should be used instead in favor of the main GraphicalFunctions method.

The removed methods should be moved also towards the Imageinfo PHP class.

Breaking: #102020 - Removed legacy setting 'GFX/gdlib_png'

See forge#102020

Description

GFX/gdlib_png is a setting that adjusted rendering of temporary images used by GDLib to be PNG files instead of GIF files.

PNG files offer many benefits over GIF files, one of them being faster processing times using Image/GraphicsMagick.

In line with this change, the property GraphicalFunctions::$gifExtension has been removed, as it mainly was used by this class and GifBuilder to determine if a temporary PNG or GIF image should be rendered.

GFX/processor_colorspace now defaults to an empty value and is migrated to one if you use the recommended colorspace for the given processor (sRGB for ImageMagick, RGB for GraphicsMagick). Image processing now will pick the recommended colorspace unless you configure it to be another one.

Additionally, all GIF assets that are now not shown anymore due to those changes have been removed as well:

  • EXT:core/Resources/Public/Images/NotFound.gif
  • EXT:install/Resources/Public/Images/TestReference/Gdlib-*.gif

Impact

Temporary layers/masks are now saved as PNG files instead of GIF files.

Affected installations

Every instance that already didn't set gdlib_png to true. Output differences may only occur on instances that use GIFBUILDER functionality (see Migration section for more information).

Migration

The configuration value has been removed without replacement. GFX/processor_colorspace is automatically migrated to the recommended value for setups using the default configuration.

GraphicalFunctions::$gifExtension has been removed without replacement. If this has been used to determine what type of file should be rendered using GraphicalFunctions::imageMagickConvert, please specify the filetype manually now.

Breaking: #102023 - Remove security.usePasswordPolicyForFrontendUsers

See forge#102023

Description

The feature toggle security.usePasswordPolicyForFrontendUsers has been removed, because TypoScript-based password validation in ext:felogin has been removed, too.

Impact

The password policy configured in $GLOBALS['TYPO3_CONF_VARS']['FE']['passwordPolicy'] is now always active for frontend user records in DataHandler and for the password recovery functionality in ext:felogin.

Affected installations

Installations, where security.usePasswordPolicyForFrontendUsers is deactivated.

Migration

To disable the password policy for frontend users, $GLOBALS['TYPO3_CONF_VARS']['FE']['passwordPolicy'] must be set to an empty string. Note, that it is not recommended to disable the password policy on production websites.

Breaking: #102108 - TCA [types][bitmask_*] settings

See forge#102108

Description

Handling of two settings has been removed from the TYPO3 Core codebase:

  • $GLOBALS['TCA']['someTable']['types']['bitmask_excludelist_bits']
  • $GLOBALS['TCA']['someTable']['types']['bitmask_value_field']

Impact

These two fields allowed to set record "sub types" based on a record bitmask field, typically 'type' => 'check' or 'type' => 'radio'.

This has been removed, the settings are not considered anymore when rendering records in the backend record editing interface.

Affected installations

Both settings have been used very rarely: Neither Core nor published TER extensions revealed a single usage. The extension scanner will find affected extensions.

Migration

In case extensions still use these two rather obscure settings, they should switch to casual $GLOBALS['TCA']['someTable']['ctrl']['type'] fields instead, which can be powered by columns based on string values.

Note the overall "subtype" record logic of TCA is within an ongoing process to be removed in TYPO3 v13, so the basic thinking should be: There is a record, and its details can be configured using $GLOBALS['TCA']['someTable']['ctrl']['type'] , and that's it. Extensions using "sub types" or this bitmask detail need to simplify and eventually deliver according upgrade wizards to adapt existing records.

Breaking: #102113 - Removed legacy setting 'GFX/gdlib'

See forge#102113

Description

'GFX/gdlib' is a setting that enables or disables image manipulation using GDLib, functionality used in GIFBUILDER, depending if the host system did not provide GDLib functionality.

With this change, the configuration value 'GFX/gdlib' has been removed, and TYPO3 will simply check for the GdImage PHP class being available to determine if it can be used.

Impact

TYPO3 now always enables GDLib functionality as soon as relevant GDLib classes are found.

Migration

The configuration value has been removed without replacement.

Custom code that relied on $GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib'] should instead adopt the simpler check if (class_exists(\GdImage::class)).

Breaking: #102146 - Removed legacy setting 'BE/flexformForceCDATA'

See forge#102146

Description

The TYPO3 configuration option $GLOBALS['TYPO3_CONF_VARS']['BE']['flexformForceCDATA'] has been removed without substitution.

This setting was an ancient workaround for an issue in libxml in old PHP versions that has been resolved long ago.

This was the last usage of useCDATA option in FlexForm-related XML methods in the Core, so that option is removed along the way. Values of XML data should still be encoded properly when dealing with related methods like GeneralUtility::array2xml().

Impact

There should be no impact on casual instances, except if single extensions tamper with the useCDATA options when dealing with XML data.

Affected installations

Instances with extensions that explicitly call XML-related transformations methods provided by the Core that tamper with useCDATA may need a look. Chances are everything is ok, though.

Migration

No direct migration possible.

Breaking: #102151 - XML prologue always added in flexArray2Xml()

See forge#102151

Description

The second argument $addPrologue = false on \TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools->flexArray2Xml() has been dropped: When imploding a FlexForm array to an XML string using this method, the "XML prologue" is always added.

Impact

This should have no impact for consumers of this method. The counterpart method \TYPO3\CMS\Core\Utility\GeneralUtility::xml2array() happily deals with this.

Affected installations

Instances with extensions using FlexFormTools->flexArray2Xml() can drop the second argument. The extension scanner will find usages with a weak match.

Since this is a detail method of the TYPO3 Core FlexForm handling, not often handled by extensions themselves, few instances will be affected in the first place.

Migration

No data migration needed, PHP consumers should drop the second argument when calling the method.

Breaking: #102165 - File Abstraction Layer: Processing APIs and interface changed

See forge#102165

Description

The Task API for processing files (mainly images) in the File Abstraction Layer (FAL) has been reworked. This mainly accommodates to the fact, that the API was revisited, the functionality has been updated to be up-to-date with PHP standards and further adaptions.

The PHP interface \TYPO3\CMS\Core\Resource\Processing\TaskInterface has lost the __construct() method as part of the interface, as the constructor is an implementation detail and should not be part of an interface definition. In addition, the method sanitizeConfiguration() has been added to clean and sort the properties required for a task. All other methods have been fully typed.

The PHP class \TYPO3\CMS\Core\Resource\Processing\AbstractGraphicalTask has been removed in order to reduce complexity, as all of the methods have been moved into the respective subclasses.

The PHP class \TYPO3\CMS\Core\Resource\Processing\Task now has two abstract methods getName() and getType() in favor of the protected properties $name and $type.

The PHP class \TYPO3\CMS\Core\Resource\ProcessedFile is now fully typed.

Impact

Custom FAL processing tasks will result in a fatal error if not adapted to the new interface.

If an extension was depending on AbstractGraphicalTask, calling this code will now result in a PHP fatal error.

Affected installations

TYPO3 installations working with the internals of the processing part of the File Abstraction Layer, e.g. when extensions add custom FAL processors or custom tasks.

Migration

Implementing a custom FAL processing task will require the extension author to adapt to the new interface requirements.

When a custom task was built on top of the AbstractGraphicalTask, this now needs to be removed and be compliant with the TaskInterface, optionally inheriting from the AbstractTask class. This can already be achieved for TYPO3 v12 to make an implementation compatible with TYPO3 v12 and TYPO3 v13.

Breaking: #102181 - Removed CLI options using bin/typo3 cleanup:flexforms

See forge#102181

Description

The CLI command bin/typo3 cleanup:flexforms of extension lowlevel can be used to clean up database record type="flex" fields that contain values not reflected in the current FlexForm data structure anymore.

The command has been changed slightly: The CLI options -p / --pid and -d / --depth have been removed.

The "dry run" CLI option --dry-run is kept.

The command implementation has been rewritten in TYPO3 v13 and is some orders of magnitudes quicker than before: While the command could easily run hours for a seasoned instance, it is now usually a matter of seconds. The "pid" and "depth" options were a hindrance to this drastic performance improvement and have been removed.

Impact

The command exits with an error when called with one of -p, --pid, -d or --depth option. It is no longer possible to restrict the command to single page tree sections, the command always checks all (not soft-deleted) records.

Affected installations

The command is not very well known and - if ever - often only used when deploying major upgrades of TYPO3 instances. Instances using one of the above options should remove them from their deployment scripts, and enjoy the massive speed improvement.

Migration

No migration, remove the above mentioned options.

Breaking: #102229 - Removed FlexFormTools->traverseFlexFormXMLData()

See forge#102229

Description

Class \TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools got a series of cleanup and removal patches.

The following public class properties have been removed:

  • $reNumberIndexesOfSectionData
  • $flexArray2Xml_options
  • $callBackObj
  • $cleanFlexFormXML

The following public methods have been removed:

  • traverseFlexFormXMLData()
  • traverseFlexFormXMLData_recurse()
  • cleanFlexFormXML_callBackFunction()

The following public methods have been marked @internal:

  • cleanFlexFormXML()
  • flexArray2Xml()
  • migrateFlexFormTcaRecursive()

The class is now a stateless service and can be injected as shared service without any risk of triggering side effects.

Impact

In general, these changes should have relatively low impact on extensions, if they don't build additional low level functionality on top of the general TYPO3 Core FlexForm related features. Extensions like the TemplaVoila forks may need to have a look for required adaptions, though.

Using the removed methods or properties in TYPO3 v13 will of course trigger PHP fatal errors.

Affected installations

Instances that extend functionality of FlexForm handling may be affected if they use methods of class FlexFormTools. This is a relatively rare case, most instances will not be affected when they just provide and use casual FlexForm definitions in extensions.

The extension scanner will find possible extensions that consume the methods or properties as a weak match.

Migration

If at all, method traverseFlexFormXMLData() is probably the one used in extensions. The easiest way is to copy the method and it's recursive worker method to an own class.

Extension developers are however encouraged to refactor their code since traverseFlexFormXMLData() with its callback logic was ugly, hard to follow and maintain. The Core switched away from the method by implementing own traversers that match the specific use cases. Method cleanFlexFormXML() is an example of such an implementation. Note FlexForms are not recursive since section containers can not be nested since TYPO3 v8 anymore. The Core thus uses some nested foreach loops instead of a recursive approach.

Breaking: #102260 - Removed TCA ['softref'] = 'notify'

See forge#102260

Description

TCA columns type fields like input and text obey the config key softref. One of the allowed soft reference parsers is notify implemented by class \TYPO3\CMS\Core\DataHandling\SoftReference\NotifySoftReferenceParser.

This soft reference parser fits no apparent use case and has been removed.

Impact

Involving the notify key in the comma-separated list of TCA columns config softref or a flex form data structure column definition does not trigger any action anymore and may log a warning this parser hasn't been found.

Affected installations

There was little reason to activate this soft reference parser in the first place since it essentially did nothing. Instances with extensions having TCA column config softref set to a value including notify will be affected. That's a very rare use case. The extension scanner will not notify about this, but the SoftReferenceParserFactory will add a log entry this parser was not found upon using an affected record.

Migration

Remove key notify from TCA columns softref list.

Breaking: #102440 - EXT:t3editor merged into EXT:backend

See forge#102440

Description

TYPO3 comes with a code editor extension called "t3editor" for a long time. Since then, the extension was always optional. When the extension is installed, selected text areas are converted to code editors based on CodeMirror.

The optional extension has been merged into EXT:backend, making the code editor always available.

Impact

An integrator cannot optionally install the code editor anymore as it's part of the mandatory "backend" extension now.

By default, this affects the following occurrences:

  • TCA: be_groups.TSconfig
  • TCA: be_users.TSconfig
  • TCA: pages.TSconfig
  • TCA: sys_template.constants
  • TCA: sys_template.config
  • TCA: tt_content.bodytext, if the content element is of type "HTML"
  • EXT:filelist: edit file content
  • Composer status view in Extension Manager

Also, checks whether the extension is installed via \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('t3editor') are now obsolete.

Affected installations

All installations are affected.

Migration

Extension checks using \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('t3editor') don't have an effect anymore and must get removed.

In addition, please see Deprecation: #102440 - EXT:t3editor merged into EXT:backend.

Breaking: #102499 - User TSconfig setting "overridePageModule" removed

See forge#102499

Description

The user TSconfig setting options.overridePageModule has been removed.

This option allowed to change links within some modules to be redirected to an alternative page module, mainly introduced in TYPO3 4.x to allow to link to the TemplaVoila page module.

However, as this has never been applied consistently across all modules provided by TYPO3 Core, it has been removed. The only few places within TYPO3 Core where this option was still evaluated was within the Workspaces Administration and the Info module.

The alternative, using a different routing endpoint and support for module aliases via the introduced Module API in TYPO3 v12, is much more robust and consistent.

Impact

Setting the user TSconfig option options.overridePageModule has no effect anymore.

Affected installations

TYPO3 installations using this setting in user TSconfig, mainly when used in conjunction with TemplaVoila and having mixed installations where both TemplaVoila page module and the default Page module are used for different editors.

Migration

In order to replace the Page module within a third-party extension such as TemplaVoila, it is possible to create a custom module entry in an extensions' Configuration/Backend/Modules.php with the following entry:

return [
    'my_module' => [
        'parent' => 'web',
        'position' => ['before' => '*'],
        'access' => 'user',
        'aliases' => ['web_layout'],
        'path' => '/module/my_module',
        'iconIdentifier' => 'module-page',
        'labels' => 'LLL:EXT:backend/Resources/Private/Language/locallang_mod.xlf',
        'routes' => [
            '_default' => [
                'target' => \MyVendor\MyPackage\Controller\MyController::class . '::mainAction',
            ],
        ],
    ],
];
Copied!

Breaking: #102518 - Database engine version requirements

See forge#102518 and forge#102594

Description

TYPO3 v13 supports these database products and versions:

  • MySQL 8.0.17 or higher
  • MariaDB 10.4.3 or higher
  • PostgresSQL 10.0 or higher
  • SQLite 3.8.3 or higher

Impact

Environments with older MariaDB or MySQL database engines will report an unsupported database version and stop working properly with the upcoming Doctrine DBAL v4 upgrade.

Affected installations

Hosting a TYPO3 instance based on version 13 may require an update of the MariaDB or MySQL database engine.

Migration

TYPO3 v12 supports MariaDB 10.4.3 or MySQL 8.0.17 and higher database engines required by v13. This allows upgrading the platform in a first step and upgrading to TYPO3 v13 in a second step.

Breaking: #102581 - Removed hook for manipulating ContentObjectRenderer

See forge#102581

Description

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] has been removed in favor of the new PSR-14 event \TYPO3\CMS\Frontend\ContentObject\Event\AfterContentObjectRendererInitializedEvent .

Impact

Any hook implementation registered is not executed anymore in TYPO3 v13+.

Affected installations

TYPO3 installations with custom extensions using this hook.

Migration

The hook is removed without deprecation in order to allow extensions to work with TYPO3 v12 (using the hook) and v13+ (using the new event) when implementing the event as well without any further deprecations. Use the PSR-14 event to allow greater influence in the functionality.

Breaking: #102583 - Removed context aspect typoscript

See forge#102583

Description

The \TYPO3\CMS\Core\Context\Context aspect typoscript has been removed without direct substitution. This aspect was implemented by now removed class \TYPO3\CMS\Core\Context\TypoScriptAspect, handling the EXT:adminpanel-related property forcedTemplateParsing.

Impact

The following calls will throw PHP exceptions:

/** @var \TYPO3\CMS\Core\Context\Context $context */
$context->getPropertyFromAspect('typoscript', 'forcedTemplateParsing');
$context->getAspect('typoscript');
// Returns false
$context->hasAspect('typoscript');
Copied!

Affected installations

Extensions typically do not use this context aspect since it only carried an EXT:adminpanel-related information.

Migration

No direct migration possible. There should be little reason for extensions to work with this EXT:adminpanel related detail.

Breaking: #102590 - TSFE->generatePage_preProcessing() removed

See forge#102590

Description

Frontend-related method TypoScriptFrontendController->generatePage_preProcessing() has been removed without substitution.

Impact

Calling the methods will raise a fatal PHP error.

Affected installations

There is little to no need for extensions to call or override this method and it should have been marked as @internal already. It was part of a removed "safety net" when extensions did set TypoScriptFrontendController->no_cache to false after it has been set to true already, which is not allowed.

Migration

No migration, do not call the method anymore.

Breaking: #102600 - TSFE->applicationData removed

See forge#102600

Description

Frontend-related property TypoScriptFrontendController->applicationData has been removed without substitution.

This property has been used by a few rather old-school extensions to park and communicate state using this global "extension-specific state array".

When looking at the TYPO3 frontend rendering chain, class TypoScriptFrontendController is by far the biggest technical debt: It mixes a lot of concerns and carries tons of state and functionality that should be modeled differently, which leads to easier to understand and more flexible code. The class is shrinking since various major versions already and will ultimately dissolve entirely at some point. Changes in this area are becoming more aggressive with TYPO3 v13. Any code using the class will need adaptions at some point, single patches will continue to communicate alternatives.

In case of the applicationData property, this is simply a misuse of the class instance to park arbitrary state in a global object. This is why it needs to fall and why there is no direct substitution.

Impact

Using TypoScriptFrontendController->applicationData (or $GLOBALS['TSFE']->applicationData ) will raise a PHP fatal error.

Affected installations

Instances with extensions that use applicationData to store and communicate state.

Migration

There are various solutions to communicate state to avoid applicationData:

In some cases, an extension could establish a frontend middleware and attach a request attribute that carries the state.

In other cases an event could be fired to gather information from other extensions. One example is the indexed_search extension which dispatches the new event EnableIndexingEvent to get know if indexing should be performed. The third-party crawler extension should use this instead of setting that information on $GLOBALS['TSFE'] .

Breaking: #102605 - TSFE->fe_user removed

See forge#102605

Description

Frontend-related property TypoScriptFrontendController->fe_user has been removed.

When looking at the TYPO3 frontend rendering chain, class TypoScriptFrontendController is by far the biggest technical debt: It mixes a lot of concerns and carries tons of state and functionality that should be modeled differently, which leads to easier to understand and more flexible code. The class is shrinking since various major versions already and will ultimately dissolve entirely at some point. Changes in this area are becoming more aggressive with TYPO3 v13. Any code using the class will need adaptions at some point, single patches will continue to communicate alternatives.

In case of the fe_user property, two alternatives exist: The frontend user can be retrieved from the PSR-7 request attribute frontend.user, and basic frontend user information is available using the Context aspect frontend.user.

Note accessing TypoScript TSFE:fe_user details continues to work for now, using for example lib.foo.data = TSFE:fe_user|user|username to retrieve the username of a logged in user is still ok.

Impact

Using TypoScriptFrontendController->fe_user (or $GLOBALS['TSFE']->fe_user ) will raise a PHP fatal error.

Affected installations

Instances with extensions dealing with frontend user details may be affected, typically custom login extensions or extensions consuming detail data of logged in users.

Migration

There are two possible migrations.

First, a limited information list of frontend user details can be retrieved using the Context aspect frontend.user in frontend calls. See class \TYPO3\CMS\Core\Context\UserAspect for a full list. The current context can retrieved using dependency injection. Example:

use TYPO3\CMS\Core\Context\Context;

final class MyExtensionController {
    public function __construct(
        private readonly Context $context,
    ) {}

    public function myAction() {
        $frontendUserUsername = $this->context->getPropertyFromAspect('frontend.user', 'username', ''));
    }
}
Copied!

Additionally, the full \TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication object is available as request attribute frontend.user in the frontend. Note some details of that object are marked @internal, using the context aspect is thus the preferred way. Example of an extension using Extbase's ActionController:

final class MyExtensionController extends ActionController {
    public function myAction() {
        // Note the 'user' property is marked @internal.
        $frontendUserUsername = $this->request->getAttribute('frontend.user')->user['username'];
    }
}
Copied!

Breaking: #102614 - Removed Hook for manipulating GetData result

See forge#102614

Description

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'] has been removed in favor of the new PSR-14 event \TYPO3\CMS\Frontend\ContentObject\Event\AfterGetDataResolvedEvent .

Impact

Any hook implementation registered is not executed anymore in TYPO3 v13.0+.

Affected installations

TYPO3 installations with custom extensions using this hook.

Migration

The hook is removed without deprecation in order to allow extensions to work with TYPO3 v12 (using the hook) and v13+ (using the new event) when implementing the event as well without any further deprecations. Use the PSR-14 event to allow greater influence in the functionality.

Breaking: #102621 - Most TSFE members marked internal or read-only

See forge#102621

Description

Most properties and methods of class TypoScriptFrontendController have been marked @internal or "read-only".

TypoScriptFrontendController ("TSFE") is a god object within the TYPO3 frontend rendering chain: It is used by multiple middlewares that call TSFE methods to create state in it, it is used within ContentObjectRenderer and various other classes to update and retrieve state. It is also registered as $GLOBALS['TSFE'] at some point and thus available as global state object.

This makes the class the biggest anti-pattern we have within the frontend - class ContentObjectRenderer is problematic as well, but that is a different story. The current role of TypoScriptFrontendController leads to very complex and opaque state handling within the frontend rendering, is the true source of many hard to fix issues and prevents Core development from implementing cool new features.

The TYPO3 Core strives to resolve large parts of this with TYPO3 v13: State needed by lower level code is being modeled as request attributes or handled locally in middlewares, methods are moved out of the class into middlewares to improve encapsulation and code flow.

To do that within continued TYPO3 v13 development, the Core needs to mark various methods and properties @internal, and needs to mark more strict access patterns on others.

The solution is to look at public properties of TypoScriptFrontendController, and to declare those as @internal, which extensions typically should not need to deal with at all. Others (like for instance id) are actively used by extensions and will be substituted by something different later, and are thus marked as "allowed to read, but never write" for extensions. This allows implementation of a deprecation layer for those "read-only" properties later, while those marked @internal can vanish without further notice. A similar strategy is added for methods, leaving only a few not marked @internal, which the Core will deprecate with a compatibility layer later.

The following public class properties have been marked "read-only", and have later been deprecated with the full deprecation of TypoScriptFrontendController in TYPO3 v13.4, see Deprecation: #105230 - TypoScriptFrontendController and $GLOBALS['TSFE'].

  • TypoScriptFrontendController->id - Use $request->getAttribute('frontend.page.information')->getId() instead
  • TypoScriptFrontendController->rootLine - Use $request->getAttribute('frontend.page.information')->getRootLine() instead
  • TypoScriptFrontendController->page - Use $request->getAttribute('frontend.page.information')->getPageRecord() instead
  • TypoScriptFrontendController->contentPid - Avoid usages altogether, available as @internal call using $request->getAttribute('frontend.page.information')->getContentFromPid()
  • TypoScriptFrontendController->sys_page - Avoid altogether, create own instance using GeneralUtility::makeInstance(PageRepository::class)
  • TypoScriptFrontendController->config - Use $request->getAttribute('frontend.typoscript')->getConfigArray() instead of config['config']
  • TypoScriptFrontendController->cObj - Create an own ContentObjectRenderer instance, call setRequest($request) and start($request->getAttribute('frontend.page.information')->getPageRecord(), 'pages')
  • TypoScriptFrontendController->config['rootLine'] - Use $request->getAttribute('frontend.page.information')->getLocalRootLine() instead

The following public class properties have been marked @internal - in general all properties not listed above. They contain information usually not relevant within extensions. The TYPO3 core will model them differently.

  • TypoScriptFrontendController->absRefPrefix
  • TypoScriptFrontendController->no_cache - Use request attribute frontend.cache.instruction instead
  • TypoScriptFrontendController->additionalHeaderData
  • TypoScriptFrontendController->additionalFooterData
  • TypoScriptFrontendController->register
  • TypoScriptFrontendController->registerStack
  • TypoScriptFrontendController->recordRegister
  • TypoScriptFrontendController->currentRecord
  • TypoScriptFrontendController->content
  • TypoScriptFrontendController->lastImgResourceInfo

The following methods have been marked @internal and may vanish anytime:

  • TypoScriptFrontendController->__construct() - extensions should not create own instances of TSFE
  • TypoScriptFrontendController->determineId()
  • TypoScriptFrontendController->getPageAccessFailureReasons()
  • TypoScriptFrontendController->calculateLinkVars()
  • TypoScriptFrontendController->isGeneratePage()
  • TypoScriptFrontendController->preparePageContentGeneration()
  • TypoScriptFrontendController->generatePage_postProcessing()
  • TypoScriptFrontendController->generatePageTitle()
  • TypoScriptFrontendController->INTincScript()
  • TypoScriptFrontendController->INTincScript_loadJSCode()
  • TypoScriptFrontendController->isINTincScript()
  • TypoScriptFrontendController->applyHttpHeadersToResponse()
  • TypoScriptFrontendController->isStaticCacheble()
  • TypoScriptFrontendController->newCObj()
  • TypoScriptFrontendController->logDeprecatedTyposcript()
  • TypoScriptFrontendController->uniqueHash()
  • TypoScriptFrontendController->set_cache_timeout_default()
  • TypoScriptFrontendController->set_no_cache() - Use $request->getAttribute('frontend.cache.instruction')->disableCache() instead
  • TypoScriptFrontendController->sL() - Use GeneralUtility::makeInstance(LanguageServiceFactory::class)->createFromSiteLanguage($request->getAttribute('language'))->sL()` instead
  • TypoScriptFrontendController->get_cache_timeout()
  • TypoScriptFrontendController->getRequestedId() - Use $request->getAttribute('routing')->getPageId() instead
  • TypoScriptFrontendController->getLanguage() - Use $request->getAttribute('site')->getDefaultLanguage() instead
  • TypoScriptFrontendController->getSite() - Use $request->getAttribute('site') instead
  • TypoScriptFrontendController->getContext() - Use dependency injection or GeneralUtility::makeInstance() instead
  • TypoScriptFrontendController->getPageArguments() - Use $request->getAttribute('routing') instead

Impact

Writing to the listed read-only properties may break the frontend rendering, using the properties or methods marked as @internal may raise fatal PHP errors.

Affected installations

The majority of extensions should already use the above properties that are marked read-only for reading only: Updating their state can easily lead to unexpected behavior. Most extensions also don't consume the properties or methods marked as @internal.

Extension developers should watch out for usages of TypoScriptFrontendController in general and reduce usages as much as possible.

Migration

The migration strategy depends on the specific use case. The frontend rendering chain continues to add state that is needed by extensions as PSR-7 request attributes. Debugging the incoming request within an extension often reveals a proper alternative.

Breaking: #102624 - PSR-14 Event for modifying image source collection

See forge#102624

Description

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] has been removed in favor of the new PSR-14 event \TYPO3\CMS\Frontend\ContentObject\Event\ModifyImageSourceCollectionEvent .

Impact

Any hook implementation registered is not executed anymore in TYPO3 v13.0+.

Affected installations

TYPO3 installations with custom extensions using this hook.

Migration

The hook is removed without deprecation in order to allow extensions to work with TYPO3 v12 (using the hook) and v13+ (using the new event) when implementing the event as well without any further deprecations. Use the PSR-14 event to allow greater influence in the functionality.

Breaking: #102627 - Removed special properties of page arrays in PageRepository

See forge#102627

Description

When requesting a page with a translation, the following special properties of an overlaid page have been removed:

_PAGES_OVERLAY_UID: The property denounced the UID of the overlaid database record entry, keeping "uid" as the original uid field.

_PAGES_OVERLAY: A boolean flag being set to true if a page was actually overlaid with a found record.

_PAGES_OVERLAY_LANGUAGE: The value of the database record's "sys_language" field value (the "language ID" of the overlaid record)

_PAGES_OVERLAY_REQUESTEDLANGUAGE: A special property used to set the actual requested language when having multi-level fallbacks while overlaying a record. When a requested overlay of language=5 is not available, but its fallback to language=2 is available, this property is set to "5" even though the page records' "sys_language_uid" field is set to 2.

These special properties have been relevant especially when generating menus, or when fetching overlays for Extbase domain models, and have been used due to historical reasons, because translations of pages have been set in "pages_language_overlay" instead of the database table "pages" until TYPO3 v9.0.

Any other record, where translations have been stored in the database, received the special property "_LOCALIZED_UID".

Impact

When calling PageRepository->getPage() or PageRepository->getLanguageOverlay() these special page-related properties are not set anymore when overlaying a page.

Affected installations

TYPO3 installations with custom extensions working on the low-level API using these properties.

Migration

The value of the previous _PAGES_OVERLAY_UID property is now available in _LOCALIZED_UID making it consistent with all database record overlays across the system.

The property _PAGES_OVERLAY is removed in favor of a isset($page['_LOCALIZED_UID') check instead.

The property _PAGES_OVERLAY_LANGUAGE is removed in favor of the property $page['sys_language_uid'] which holds the same value.

The property _PAGES_OVERLAY_REQUESTEDLANGUAGE is moved to a new property called _REQUESTED_OVERLAY_LANGUAGE which is available now for any kind of overlaid record, and not just pages.

Breaking: #102632 - Use strict types in Extbase

See forge#102632, forge#102878, forge#102879, forge#102885, forge#102954, forge#102956, forge#102966, forge#102969

Description

All properties, except the $view property, in \TYPO3\CMS\Extbase\Mvc\Controller\ActionController are now strictly typed. In addition, all function arguments and function return types are now strictly typed.

Also, the properties in the \TYPO3\CMS\Extbase\Annotation\Annotation namespace now have native PHP types for their properties.

In summary, the following classes have received strict types:

  • \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Annotation\IgnoreValidation
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Annotation\ORM\Cascade
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Annotation\Required\Validate
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Domain\Model\Category
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Domain\Model\FileReference
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Domain\Model\File
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Persistence\ObjectStorage
  • \TYPO3\CMS\Extbase\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface

Impact

Classes extending the changed classes must now ensure that overwritten properties and methods are all are strictly typed.

Affected installations

Custom classes extending the changed classes.

Migration

Ensure classes that extend the changed classes use strict types for overwritten properties, function arguments and return types.

Extensions supporting multiple TYPO3 versions (for example, v12 and v13) must not overwrite properties of the changed classes. Instead, it is recommended to set values of overwritten properties in the constructor of the extending class.

Before

<?php

namespace MyVendor\MyExtension\Controller;

use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class MyController extends ActionController
{
    public string $errorMethodName = 'myAction';
}
Copied!

After

<?php

namespace MyVendor\MyExtension\Controller;

use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class MyController extends ActionController
{
    public function __construct()
    {
        $this->errorMethodName = 'myAction';
    }
}
Copied!

Breaking: #102645 - More strict Context handling

See forge#102645

Description

Class \TYPO3\CMS\Core\Context\Context is a stateful singleton class set up pretty early by the frontend or backend application after the request object has been created. Its state is then further changed by various frontend and backend middlewares. It can be retrieved using dependency injection or GeneralUtility::makeInstance() in consuming classes.

To clean up Context-related code a bit, the following changes have been made:

  • Method __construct() removed from \TYPO3\CMS\Core\Context\Context
  • Class \TYPO3\CMS\Core\Context\ContextAwareInterface removed
  • Trait \TYPO3\CMS\Core\Context\ContextAwareTrait removed

Impact

Handing over manual arguments to the constructor of __construct() does not have an effect anymore, and using the interface or the trait will raise a fatal PHP error.

Affected installations

Most likely, not too many instances are affected: An instance of Context is typically created by Core bootstrap and retrieved using dependency injection, extensions usually do not need to create own instances.

There are also not many routing aspects with context dependencies that may use the interface or the trait. If so, they can adapt easily and stay compatible with older versions.

Migration

The constructor of the Context class was bogus. Since the class is an injectable singleton that should be available through the container, it must not have manual constructor arguments since this would shut down the container registration. Extensions typically did not create own instances of Context, using the constructor argument was - if at all - only done in tests. Unit tests should typically create own instances using new and hand them over to classes that get the context injected.

Adaption to the interface and trait removal is straight forward as well: Get the context injected into the aspect, or retrieve the instance using GeneralUtility::makeInstance().

Breaking: #102745 - Removed ContentObject stdWrap hook

See forge#102745

Description

The ContentObject stdWrap hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] has been removed in favor of the more powerful PSR-14 events:

  • \TYPO3\CMS\Frontend\ContentObject\Event\BeforeStdWrapFunctionsInitializedEvent
  • \TYPO3\CMS\Frontend\ContentObject\Event\AfterStdWrapFunctionsInitializedEvent
  • \TYPO3\CMS\Frontend\ContentObject\Event\BeforeStdWrapFunctionsExecutedEvent
  • \TYPO3\CMS\Frontend\ContentObject\Event\AfterStdWrapFunctionsExecutedEvent

Impact

Any hook implementation registered is not executed anymore in TYPO3 v13.0+. The extension scanner will report usages.

Affected installations

TYPO3 installations with custom extensions using the hook.

Migration

The hook is removed without deprecation in order to allow extensions to work with TYPO3 v12 (using the hook) and v13+ (using the new events) when implementing the events as well without any further deprecations. Use the PSR-14 events to allow greater influence in the functionality.

Breaking: #102755 - Improved getImageResource functionality

See forge#102755

Description

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] has been removed in favor of the new PSR-14 event \TYPO3\CMS\Frontend\ContentObject\Event\AfterImageResourceResolvedEvent .

The new event is using the new \TYPO3\CMS\Core\Imaging\ImageResource DTO, which allows an improved API as developers do no longer have to deal with unnamed array keys but benefit from the object-oriented approach, using corresponding getter and setter. Therefore, the return types of the following methods have been changed to ?ImageResource:

  • \TYPO3\CMS\Frontend\Imaging\GifBuilder->gifBuild()
  • \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer->getImgResource()

Impact

Any registered hook implementation is not executed anymore in TYPO3 v13.0+.

Calling the mentioned methods do now return either null or an instance of the ImageResource DTO.

The new Event is also using the new DTO instead of an array.

Affected Installations

TYPO3 installations with custom extensions using this hook or calling mentioned methods directly.

Migration

The hook is removed without deprecation in order to allow extensions to work with TYPO3 v12 (using the hook) and v13+ (using the new event) when implementing the event as well without any further deprecations. Use the PSR-14 event to allow greater influence in the functionality.

Additionally, adjust your code to handle the new return types appropriately.

Breaking: #102763 - Extbase HashService usage replaced with Core HashService

See forge#102763

Description

All usages of the @internal class \TYPO3\CMS\Extbase\Security\Cryptography\HashService in TYPO3 have been removed and replaced by \TYPO3\CMS\Core\Crypto\HashService .

Impact

Custom extensions expecting \TYPO3\CMS\Extbase\Security\Cryptography\HashService as type of the property '$hashService of various @internal classes will result in a PHP Fatal error.

Affected installations

TYPO3 installations with custom extensions using one of the following:

  • Property $hashService of \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper
  • @internal property $hashService of class \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
  • Property $hashService of internal class \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService
  • Property $hashService of internal class \TYPO3\CMS\FrontendLogin\Configuration\RecoveryConfiguration
  • Property $hashService of internal class \TYPO3\CMS\FrontendLogin\Configuration\RecoveryConfiguration
  • Property $hashService of internal class \TYPO3\CMS\FrontendLogin\Controller\PasswordRecoveryController
  • Property $hashService of internal class \TYPO3\CMS\Form\Domain\Runtime\FormRuntime
  • Property $hashService of internal class \TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter

Migration

Custom extensions must be adapted to use methods of class \TYPO3\CMS\Core\Crypto\HashService .

Breaking: #102763 - Frontend user password recovery hashes invalidated

See forge#102763

Description

The replacement of deprecated class \TYPO3\CMS\Extbase\Security\Cryptography\HashService results in existing password recovery hashes of frontend users being invalid.

Impact

A frontend user with a valid and unexpired password recovery link created with a TYPO3 version < 13 can not use the password recovery link to reset the password.

These hashes have a limited lifetime already (12 hours). On large installations that require hashes to survive a major update, you could write a small CLI task that re-adds missing hashes created in the maintenance time window of the upgrade.

Affected installations

TYPO3 installations which use the "Display Password Recovery Link" option of ext:fe_login.

Migration

Frontend users need to request a new password recovery link to reset the password.

Breaking: #102775 - PageRepository methods with native PHP types

See forge#102775

Description

Various methods in of the main TYPO3 Core classes \TYPO3\CMS\Core\Domain\Repository\PageRepository now have native PHP types in their method signature, requiring the caller code to use exactly the required PHP types for the corresponding method arguments.

The following methods are affected:

  • PageRepository->getPage()
  • PageRepository->getPage_noCheck()
  • PageRepository->getPageOverlay()
  • PageRepository->getPagesOverlay()
  • PageRepository->checkRecord()
  • PageRepository->getRawRecord()
  • PageRepository->enableFields()
  • PageRepository->getMultipleGroupsWhereClause()
  • PageRepository->versionOL()

Impact

Calling the affected methods now requires the passed arguments to be of the specified PHP type. Otherwise a PHP TypeError is triggered.

Affected installations

TYPO3 installations with third-party extensions utilizing the PageRepository PHP class.

Migration

Extension authors need to adapt their PHP code to use ensure passed arguments are of the required PHP type when calling corresponding methods of the PageRepository PHP class. Using proper type casts would is a possible migration strategy.

Breaking: #102779 - TYPO3 v13 System Requirements

See forge#102779

Description

The minimum PHP version required to run TYPO3 version v13 has been defined as 8.2.

TYPO3 v13 supports these database products and versions:

  • MySQL 8.0.17 or higher
  • MariaDB 10.4.3 or higher
  • PostgresSQL 10.0 or higher
  • SQLite 3.8.3 or higher

Impact

The TYPO3 Core codebase and extensions tailored for v13 and above can use features implemented with PHP up to and including 8.2. Running TYPO3 v13 with older PHP versions or database engines will trigger fatal errors.

Affected installations

Hosting a TYPO3 instance based on version 13 may require an update of the PHP platform and the database engine.

Migration

TYPO3 v11 / v12 supports PHP 8.2 and database engines required by v13. This allows upgrading the platform in a first step and upgrading to TYPO3 v13 in a second step.

Breaking: #102793 - PageRepository->enableFields hook removed

See forge#102793

Description

One of the common PHP APIs used in TYPO3 Core for fetching records is \TYPO3\CMS\Core\Domain\Repository\PageRepository . The method enableFields() is marked as deprecated, and the according hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['addEnableColumns'] has been removed.

Impact

Hook listeners will not be executed anymore.

Affected installations

TYPO3 installations with custom extensions using the mentioned hook.

Migration

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['addEnableColumns'] can be replaced by a listener to the newly introduced PSR-14 event.

Breaking: #102806 - Hooks in PageRepository removed

See forge#102806

Description

The following hooks in TYPO3's Core API class \TYPO3\CMS\Core\Domain\PageRepository have been removed:

  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\TYPO3\CMS\Core\Domain\PageRepository::class]['init']
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPage']

Later hook has been replaced by the new PSR-14 event \TYPO3\CMS\Core\Domain\Event\BeforePageIsRetrievedEvent .

Impact

Any hook implementation registered is not executed anymore in TYPO3 v13.0+.

Affected installations

TYPO3 installations with custom extensions using these hooks.

Migration

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\TYPO3\CMS\Core\Domain\PageRepository::class]['init'] is removed without substitution. Back in TYPO3 v4.x this hook was useful to modify public properties after everything was initialized. Nowadays, this is not necessary anymore, as the properties are not public anymore and calculated based on the Context API when instantiated.

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPage'] is removed without deprecation in order to allow extensions to work with TYPO3 v12 (using the hook) and v13+ (using the new Event) when implementing the event as well without any further deprecations. Use the PSR-14 event to allow greater influence in the functionality.

Breaking: #102834 - Remove items from New Content Element Wizard

See forge#102834

Description

The configuration of the New Content Element Wizard has been improved by automatically registering the groups and elements from the TCA configuration.

The previously used option to show / hide elements mod.wizards.newContentElement.wizardItems.<group>.show is therefore not evaluated anymore.

All configured groups and elements are automatically shown. Removing these groups and elements from the New Content Element Wizard has to be done via the new mod.wizards.newContentElement.wizardItems.removeItems and mod.wizards.newContentElement.wizardItems.<group>.removeItems options.

Impact

Using the page TSconfig option mod.wizards.newContentElement.wizardItems.<group>.show to show / hide elements is not evaluated anymore.

Affected installations

TYPO3 installations with custom extensions using the page TSconfig option mod.wizards.newContentElement.wizardItems.<group>.show to show / hide elements in the New Content Element Wizard.

Migration

To hide elements, migrate your page TSconfig from mod.wizards.newContentElement.wizardItems.<group>.show := removeFromList(html) to mod.wizards.newContentElement.wizardItems.<group>.removeItems := addToList(html).

Breaking: #102835 - Strict typing in final TypoLinkCodecService

See forge#102835

Description

The \TYPO3\CMS\Core\LinkHandling\TypoLinkCodecService , used to encode and decode TypoLinks, has been declared readonly and set final. Additionally, the class does now use strict typing and the decode() method's first parameter $typoLink is now a type hinted string.

This has been done in combination with the introduction of the two new PSR-14 events BeforeTypoLinkEncodedEvent and AfterTypoLinkDecodedEvent, which allow to fully influence the encode and decode functionality, making any cross classing superfluous.

Impact

Extending / cross classing TypoLinkCodecService does no longer work and will lead to PHP errors.

Calling decode() with the first parameter $typolink being not a string will lead to a PHP TypeError.

Affected installations

All installations extending / cross classing TypoLinkCodecService or calling decode() with the first parameter $typolink not being a string.

Migration

Instead of extending / cross classing TypoLinkCodecService use the new PSR-14 events to modify the functionality.

Ensure to always provide a string as first parameter $typolink, when calling decode() in your extension code.

Breaking: #102849 - Removed ContentObject stdWrap cacheStore hook

See forge#102849

Description

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] has been removed in favor of the new PSR-14 event \TYPO3\CMS\Frontend\ContentObject\Event\BeforeStdWrapContentStoredInCacheEvent .

Impact

Any hook implementation registered is not executed anymore in TYPO3 v13.0+.

Affected installations

TYPO3 installations with custom extensions using this hook.

Migration

The hook is removed without deprecation in order to allow extensions to work with TYPO3 v12 (using the hook) and v13+ (using the new event) when implementing the event as well without any further deprecations. Use the PSR-14 event to allow greater influence in the functionality.

Breaking: #102855 - Removed LinkService resolveByStringRepresentation hook

See forge#102855

Description

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['Link']['resolveByStringRepresentation'] has been removed in favor of the new PSR-14 event \TYPO3\CMS\Core\LinkHandling\Event\AfterLinkResolvedByStringRepresentationEvent event.

Impact

Any hook implementation registered is not executed anymore in TYPO3 v13.0+.

Affected installations

TYPO3 installations with custom extensions using this hook.

Migration

The hook is removed without deprecation in order to allow extensions to work with TYPO3 v12 (using the hook) and v13+ (using the new event) when implementing the event as well without any further deprecations. Use the PSR-14 event to allow greater influence in the functionality.

Breaking: #102875 - Changed Connection method signatures and behaviour

See forge#102875

Description

Signature and behaviour of following methods has been changed:

  • lastInsertId() no longer accepts the sequence and field name.
  • quote() no longer has a type argument and the value must be a string.

Public Connection::PARAM_* class constants has been replaced with the Doctrine DBAL 4 ParameterType and ArrayParameterType enum definitions.

Impact

Calling quote() with a non-string as first argument will result in a PHP error. Still providing the second argument will not emit an error, but may be detected by static code analysers.

Calling lastInsertId() not directly after the record insert or inserting records in another table in between will return the incorrect value.

Affected installations

Only installations calling quote() with a non-string as first argument or not using lastInsertId() directly after the record insert.

Migration

lastInsertId()

Returns the last inserted ID (auto-created) on the connection.

BEFORE

use TYPO3\CMS\Core\Database\Connection as Typo3Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/** @var Typo3Connection $connection */
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getConnectionForTable('tx_myextension_mytable');

$connection->insert(
    'tx_myextension_mytable',
    [
        'pid' => $pid,
        'some_string' => $someString,
    ],
    [
        Typo3Connection::PARAM_INT,
        Typo3Connection::PARAM_STR,
    ]
);
$uid = $connection->lastInsertId('tx_myextension_mytable');
Copied!

AFTER

use TYPO3\CMS\Core\Database\Connection as Typo3Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/** @var Typo3Connection $connection */
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getConnectionForTable('tx_myextension_mytable');

$connection->insert(
    'tx_myextension_mytable',
    [
        'pid' => $pid,
        'some_string' => $someString,
    ],
    [
        Typo3Connection::PARAM_INT,
        Typo3Connection::PARAM_STR,
    ]
);
$uid = $connection->lastInsertId();
Copied!

Breaking: #102875 - ExpressionBuilder changes

See forge#102875

Description

Signature changes for following methods

  • ExpressionBuilder::literal(string $value): Value must be a string now.
  • ExpressionBuilder::trim(): Only \Doctrine\DBAL\Platforms\TrimMode enum for $position argument.

Following class constants have been removed

  • QUOTE_NOTHING: Not used since already TYPO3 v12 and Doctrine DBAL 3.x.
  • QUOTE_IDENTIFIER: Not used since already TYPO3 v12 and Doctrine DBAL 3.x.
  • QUOTE_PARAMETER: Not used since already TYPO3 v12 and Doctrine DBAL 3.x.

Impact

Calling any of the mentioned methods with invalid type will result in a PHP error.

Affected installations

Only those installations that uses one of the mentioned methods with invalid type(s).

Migration

ExpressionBuilder::literal()

Extension author need to ensure that a string is passed to literal().

ExpressionBuilder::trim()

Extension author need to pass the Doctrine DBAL enum TrimMode instead of an integer.

TRIM_LEADING

integer enum
0 TrimMode::UNSPECIFIED
1 TrimMode::LEADING
2 TrimMode::TRAILING
3 TrimMode::BOTH
EXT:my_extension/Classes/Domain/Repository/MyTableRepository.php
use Doctrine\DBAL\Platforms\TrimMode;
use TYPO3\CMS\Core\Database\Connection
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;

// before
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
$queryBuilder->expr()->comparison(
    $queryBuilder->expr()->trim($fieldName, 1),
    ExpressionBuilder::EQ,
    $queryBuilder->createNamedParameter('', Connection::PARAM_STR)
);

// after
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
$queryBuilder->expr()->comparison(
    $queryBuilder->expr()->trim($fieldName, TrimMode::LEADING),
    ExpressionBuilder::EQ,
    $queryBuilder->createNamedParameter('', Connection::PARAM_STR)
);

// example for dual version compatible code
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
$queryBuilder->expr()->comparison(
    $queryBuilder->expr()->trim($fieldName, TrimMode::LEADING),
    ExpressionBuilder::EQ,
    $queryBuilder->createNamedParameter('', Connection::PARAM_STR)
);
Copied!

Breaking: #102875 - QueryBuilder changes

See forge#102875

Description

Doctrine DBAL 4 removed methods from the QueryBuilder which has been adopted to the extended \TYPO3\CMS\Core\Database\Query\QueryBuilder .

Removed methods:

  • QueryBuilder::add(): Use new reset methods and normal set methods instead.
  • QueryBuilder::getQueryPart($partName): No replacement, internal state.
  • QueryBuilder::getQueryParts(): No replacement, internal state.
  • QueryBuilder::resetQueryPart($partName): Replacement methods has been added, see list.
  • QueryBuilder::resetQueryParts(): Replacement methods has been added, see list.
  • QueryBuilder::execute(): Use QueryBuilder::executeQuery() or QueryBuilder::executeStatement() directly.
  • QueryBuilder::setMaxResults(): Using (int)0 as max result will no longer work and retrieve no records. Use NULL instead to allow all results.

Signature changes:

  • QueryBuilder::quote(string $value): Second argument has been dropped and the value must now be of type string.

Impact

Calling any of the mentioned removed methods will result in a PHP error. Also signature changes introducing type hint will result in a PHP error if called with an invalid type.

Affected installations

Only those installations that use the mentioned methods.

Migration

Extension author need to replace the removed methods with the alternatives which

QueryBuilder::add('query-part-name')

Use the direct set/select methods instead:

before after
->add('select', $array) ->select(...$array)
->add('where', $wheres) ->where(...$wheres)
->add('having', $havings) ->having(...$havings)
->add('orderBy', $orderBy) ->orderBy($orderByField, $orderByDirection)->addOrderBy($orderByField2)
->add('groupBy', $groupBy) ->groupBy($groupField)->addGroupBy($groupField2)

QueryBuilder::resetQueryParts() and QueryBuilder::resetQueryPart()

However, several replacements have been put in place depending on the $queryPartName parameter:

before after
'select' Call ->select() with a new set of columns
'distinct' ->distinct(false)
'where' ->resetWhere()
'having' ->resetHaving()
'groupBy' ->resetGroupBy()
'orderBy ->resetOrderBy()
'values' Call ->values() with a new set of values.

QueryBuilder::execute()

Doctrine DBAL 4 removed QueryBuilder::execute() in favour of the two methods QueryBuilder::executeQuery() for select/count and QueryBuilder::executeStatement() for insert, delete and update queries.

Before

use TYPO3\CMS\Core\Database\ConnectionPool;

// select query
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('pages');
$rows = $queryBuilder
    ->select('*')
    ->from('pages')
    ->execute()
    ->fetchAllAssociative();

// delete query
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('pages');
$deletedRows = (int)$queryBuilder
    ->delete('pages')
    ->where(
      $queryBuilder->expr()->eq('pid', $this->createNamedParameter(123),
    )
    ->execute();
Copied!

After

use TYPO3\CMS\Core\Database\ConnectionPool;

// select query
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('pages');
$rows = $queryBuilder
    ->select('*')
    ->from('pages')
    ->executeQuery()
    ->fetchAllAssociative();

// delete query
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  ->getQueryBuilderForTable('pages');
$deletedRows = (int)$queryBuilder
    ->delete('pages')
    ->where(
      $queryBuilder->expr()->eq('pid', $this->createNamedParameter(123),
    )
    ->executeStatement();
Copied!

QueryBuilder::quote(string $value)

quote() uses Connection::quote() and therefore adopts the changed signature and behaviour.

Before

use TYPO3\CMS\Core\Database\Connection as Typo3Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

// select query
$pageId = 123;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('pages');
$rows = $queryBuilder
    ->select('*')
    ->from('pages')
    ->where(
        $queryBuilder->expr()->eq(
            'uid',
            $queryBuilder->quote($pageId, Typo3Connection::PARAM_INT)
        ),
    )
    ->executeQuery()
    ->fetchAllAssociative();
Copied!

After

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

// select query
$pageId = 123;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('pages');
$rows = $queryBuilder
    ->select('*')
    ->from('pages')
    ->where(
        $queryBuilder->expr()->eq(
            'uid',
            $queryBuilder->quote((string)$pageId)
        ),
    )
    ->executeQuery()
    ->fetchAllAssociative();
Copied!

QueryBuilder::setMaxResults()

Using (int)0 as max result will no longer work and retrieve no records. Use NULL instead to allow all results.

Before

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

// select query
$pageId = 123;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('pages');
$rows = $queryBuilder
    ->select('*')
    ->from('pages')
    ->setFirstResult(0)
    ->setMaxResults(0)
    ->executeQuery()
    ->fetchAllAssociative();
Copied!

After

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

// select query
$pageId = 123;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable('pages');
$rows = $queryBuilder
    ->select('*')
    ->from('pages')
    ->setFirstResult(0)
    ->setMaxResults(null)
    ->executeQuery()
    ->fetchAllAssociative();
Copied!

Breaking: #102895 - PackageInterface modified

See forge#102895

Description

The PHP interface \TYPO3\CMS\Core\Package\PackageInterface has been modified.

All methods of the interface now have proper types in the method signature.

In addition, the method getPackageIcon(): ?string is added to define whether the package has an icon which is shipped with the package.

Impact

Although the interface exists primarily because of the original implementation from Flow Framework in TYPO3 v6.0 in order to differentiate between TYPO3 extensions and Flow packages, the interface only has one implementation: \TYPO3\CMS\Core\Package\Package .

Thus, it does not impact any extension or installation directly.

However, projects might be affected if there is a custom implementation of the PackageInterface, which is highly unlikely.

Affected installations

TYPO3 installations in very rare cases where there is a custom implementation of the interface, which is unknown at the time of writing.

Migration

Extend the custom implementation to reflect the updated PackageInterface.

Breaking: #102907 - Indexed Search TypoScript settings removed

See forge#102907

Description

Indexed Search previously used a custom link building to generate links and their targets for linking to search results that are of type "page", instead of the native "typolink" ( LinkFactory) system, automatically detecting links to other sites of the same installation and using the proper extTarget setting in TypoScript for creating the target attribute for the link.

For this reason, the two TypoScript settings are removed:

plugin.tx_indexedsearch.settings.detectDomainRecords
plugin.tx_indexedsearch.settings.detectDomainRecords.target
Copied!

Impact

Setting these options have no effect anymore.

Affected installations

TYPO3 installations using indexed search using these options.

Migration

Remove the lines, and adapt config.extTarget accordingly if needed in such cases, as the links are now generated through TYPO3's native link building APIs.

Breaking: #102921 - Remove several outdated indexed search features

See forge#102921

Description

The internal search of TYPO3, Indexed Search exists since over 20 years. Some functionality that is shipped with the search form is not considered up-to-date anymore, in regard to templating, as Indexed Search has an Extbase and Fluid-based plugin since TYPO3 v6.2 (10 years).

Some functionality was never removed, which is now the case:

  • The ability to customize the styling of a specific page via plugin.tx_indexedsearch.settings.specialConfiguration
  • The ability to customize a result icon (used as Gif images) based on the type via plugin.tx_indexedsearch.settings.iconRendering
  • The ability to customize a result language symbol icon (used as Gif images) based on the page language via plugin.tx_indexedsearch.settings.flagRendering

In addition, the possibility for visitors to change only search for results in a language other than the current language is removed. It proved little sense to search for e.g. Japanese content on a French websites.

Impact

All of the TypoScript settings are not evaluated anymore. The Fluid variables {allLanguageUids}, {row.language} and {row.icon} are not filled anymore.

Search only shows results in the language of the currently active language of the website.

Affected installations

TYPO3 installations using these options or features with Indexed Search.

Migration

Adapt your TypoScript settings, and remove the TypoScript settings and Fluid variables.

If you still need specific rendering of icons for pages, or customized CSS for result pages, it is recommended to use Fluid conditions adapted in your custom template, which is usually not necessary.

Breaking: #102924 - Single Table Inheritance from fe_groups removed

See forge#102924

Description

Extbase ships with a feature called "Single Table Inheritance", to allow multiple Extbase domain models reflecting one database table depending on a specific value of a database field.

TYPO3 has the functionality enabled for the database tables fe_users and fe_groups.

The respective default models, which do not make a lot of sense as models depend on a specific domain, have been removed in previous TYPO3 versions.

For frontend user groups, the usage and the usefulness for TYPO3 to ship this out of the box, has shown little impact. For this reason, the functionality has been removed. Along with that, the database field fe_groups.tx_extbase_type and its TCA definition as well as the Extbase configuration as a single table inheritance option, has been removed.

The functionality for Single Table Inheritance in Extbase and also for frontend users is working as before without any changes.

Impact

Using the database field in custom code, or using Single Table Inheritance in Extbase for frontend user groups will result in SQL and PHP errors.

Affected installations

TYPO3 installations with custom extensions using Single Table Inheritance in Extbase with frontend usergroups.

Migration

If necessary, extension authors can add Single Table Inheritance in their own extension for fe_groups by themselves.

  • Add a database field fe_groups.tx_extbase_type in ext_tables.sql
  • Add TCA information in Configuration/TCA/Overrides/fe_groups.php for the database field

Breaking: #102931 - Removed hook in GifBuilder

See forge#102931

Description

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_gifbuilder.php']['gifbuilder-ConfPreProcess'] has been removed.

This hook was solely introduced in TYPO3 v3.8.0 for a specific use case which isn't needed anymore, and thus removed.

At the same time the whole GifBuilder class is now strictly typed.

Impact

PHP code utilizing this hook will not be executed anymore.

Affected installations

TYPO3 installations with extensions utilizing this hook, which is highly unlikely.

Any usages can be found with the Extension Scanner in the Install Tool.

Migration

It is recommended to hand in custom configuration already into GifBuilder directly, and remove any usages to the hook in custom extension code.

Breaking: #102932 - Removed TypoScriptFrontendController hooks

See forge#102932

Description

The following frontend TypoScript and page rendering related hooks have been removed:

  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['configArrayPostProc'] , substituted by event ModifyTypoScriptConfigEvent.
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['pageLoadedFromCache'] , no direct substitution, use event AfterTypoScriptDeterminedEvent or an own middleware after typo3/cms-frontend/prepare-tsfe-rendering instead.
  • $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['createHashBase']', substituted by event :php: BeforePageCacheIdentifierIsHashedEvent`.

Impact

Any such hook implementation registered is not executed anymore with TYPO3 v13.0+.

Affected installations

TYPO3 installations with custom extensions using above listed hooks.

Migration

See PSR-14 event for substitutions. The new events are tailored for more restricted use cases and can be used when existing hook usages have not been "side" usages. Any "off label" hook usages should be converted to custom middlewares instead.

Breaking: #102935 - Overhauled extension installation in Extension Manager

See forge#102935

Description

Installing extensions via the extension manager is only used for non-Composer-based installations. However, there have been a couple of dependencies to the EXT:extensionmanager, which required even Composer-based installations to have this extension installed. This has now been resolved. The EXT:extensionmanager extension is now optional.

The public \TYPO3\CMS\Extensionmanager\Utility\InstallUtility->processExtensionSetup() method has therefore been removed. It has previously been used to execute a couple of "import" tasks, such as import site configurations or media assets to the fileadmin/. However those tasks had dependencies to other optional core extensions, such as EXT:impexp. Therefore the new PSR-14 event PackageInitializationEvent has been introduced and the functionality has been split into corresponding event listeners, which are added to their associated Core extensions.

The PSR-14 events, dispatched by those "tasks" have been removed:

  • \TYPO3\CMS\Extensionmanager\Event\AfterExtensionDatabaseContentHasBeenImportedEvent
  • \TYPO3\CMS\Extensionmanager\Event\AfterExtensionFilesHaveBeenImportedEvent
  • \TYPO3\CMS\Extensionmanager\Event\AfterExtensionSiteFilesHaveBeenImportedEvent
  • \TYPO3\CMS\Extensionmanager\Event\AfterExtensionStaticDatabaseContentHasBeenImportedEvent

The information, provided by those events can now be accessed by fetching the corresponding storage entry from the new \TYPO3\CMS\Core\Package\Event\PackageInitializationEvent .

Using before and after keywords in the listener registration, custom extensions can ensure to be executed, once the corresponding information is available.

It's even possible to manually execute those "tasks" by dispatching the PackageInitializationEvent in custom extension code. This can be used as replacement for the InstallUtility->processExtensionSetup() call.

Impact

Using one of the removed PSR-14 events or calling the removed method will lead to a PHP error. The extension scanner will report any usages.

Affected installations

TYPO3 installations with extensions registering listeners to the removed events or calling the removed method in their extension code.

Migration

Instead of registering listeners for the removed events, developers can now just register a listener to the new PackageInitializationEvent, which contains the listeners result as storage entry:

// Before

#[AsEventListener]
public function __invoke(AfterExtensionSiteFilesHaveBeenImportedEvent $event): void
{
    foreach ($event->getSiteIdentifierList() as $siteIdentifier) {
        $configuration = $this->siteConfiguration->load($siteIdentifier);
        $configuration = $this->extendSiteConfiguration($configuration);
        $this->siteConfiguration->write($siteIdentifier, $configuration);
    }
}

// After

#[AsEventListener(after: ImportSiteConfigurationsOnPackageInitialization::class)]
public function __invoke(PackageInitializationEvent $event): void
{
    foreach ($event->getStorageEntry(ImportSiteConfigurationsOnPackageInitialization::class)->getResult() as $siteIdentifier) {
        $configuration = $this->siteConfiguration->load($siteIdentifier);
        $configuration = $this->extendSiteConfiguration($configuration);
        $this->siteConfiguration->write($siteIdentifier, $configuration);
    }
}
Copied!

Instead of calling InstallUtility->processExtensionSetup(), extensions can just dispatch the PackageInitializationEvent on their own.

Breaking: #102945 - Pagination of Indexed Search replaced

See forge#102945

Description

Indexed Search used a custom crafted pagination, implemented with several ViewHelpers known as is:pageBrowsingResults and is:pageBrowsing. These ViewHelpers have been removed in favor of the existing Pagination API, leading to template changes.

Impact

In case Fluid templates of EXT:indexed_search are overridden, the frontend will render an exception due to the missing ViewHelpers.

Affected installations

All installations overriding the Fluid template Templates/Search/Search.html of EXT:indexed_search are affected.

Migration

is:pageBrowsingResults has been replaced with a short HTML snippet:

<f:sanitize.html>
    <f:translate key="displayResults" arguments="{0: result.pagination.startRecordNumber, 1: result.pagination.endRecordNumber, 2: result.count}" />
</f:sanitize.html>
Copied!

is:pageBrowsing has been replaced with a new Fluid partial file:

<f:render partial="Pagination" arguments="{pagination: result.pagination, searchParams: searchParams, freeIndexUid: freeIndexUid}" />
Copied!

Breaking: #102968 - FormEngine itemFormElID removed

See forge#102968

Description

When dealing with custom FormEngine elements in the backend record editing interface, the infrastructure prepares a huge data array and hands it over to single element classes for rendering.

The specific data key $this->data['parameterArray']['itemFormElID'] has been removed. The intention of that key was to prepare some unique id to be used as id attribute. This never made a lot of sense, single element classes can easily take care of this on their own if needed.

Since the Core can't actively deprecate and log access to members of the main data array as such, there is no point in declaring a deprecation for it, and the array entry has been removed directly.

Impact

Extensions with custom backend FormEngine elements may raise an "undefined array key" PHP warning, or may create empty id attributes in their HTML output if accessing itemFormElID.

Affected installations

Instances with extensions that deliver custom FormEngine elements may be affected.

Migration

A typical use case for a unique id attribute on a form element is to connect it with a label element. Accessing itemFormElID can usually be easily avoided by creating a unique string using StringUtility::getUniqueId(), with a custom prefix:

// Before
$attributeId = htmlspecialchars($this->data['parameterArray']['itemFormElID']);
$html[] = '<input id="' . $attributeId . '">';

// After
$attributeId = htmlspecialchars(StringUtility::getUniqueId('formengine-my-custom-element-'));
$html[] = '<input id="' . $attributeId . '">';
Copied!

Breaking: #102970 - No database relations in FlexForm container sections

See forge#102970

Description

FlexForm handling details can be troublesome in certain scenarios. The Core suffers from some nasty issues in this area, especially when relations to other tables are used in FlexForms - the system for instance tends to mix up things with language and workspace on this level.

The Core strives to get these scenarios sorted out, and a couple of patches to prepare towards better flex form handling have been done with v13.0 already.

To unblock further development in this area, one detail is restricted a bit more than with previous versions: FlexForm container section data structures must no longer contain fields that configure relations to other database tables.

This has already been restricted since TYPO3 v8 for TCA type="inline" and has been partially extended to type="category" and others later, if they configured MM relations in FlexForm sections containers. Now, especially type="select" with foreign_table will also throw an exception.

In general, anonymous FlexForm container section data can and should not point to database entities. Their use is tailored for "simple" types like input, email and similar, support of those will not be restricted.

Note this does not restrict using casual FlexForms without containers sections, like FlexForm data structures that rely on casual fields in sheets: Those can continue to work with TCA types like inline, group and select, and the Core development tries to actively fix existing problematic scenarios in this area.

Impact

When editing records that configure FlexForms with container sections that use database relation-aware TCA types, an exception will be thrown by FormEngine. The related code may later be relocated to a lower level place that can be triggered by DataHandler as well.

Affected installations

Instances with extensions that use FlexForm container sections configuring database relations to tables.

Since previous core versions restricted database relations within FlexForm container sections already, and since container sections are a relatively rarely used feature in the first place, we don't expect too many extensions to be affected by this.

You can easily spot custom usage of FlexForm sections by searching for a <section> tag within your FlexForm .xml files, or within a TCA definition with type="flex". These will be the instances you need to migrate, when those sections contain type="select" fields (or others mentioned above).

Affected FlexForm XML

EXT:my_extension/Configuration/FlexForms/Example.xml
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<T3DataStructure>
    <sheets>
        <sSection>
            <ROOT>
                <sheetTitle>section</sheetTitle>
                <type>array</type>
                <el>
                    <section_1>
                        <title>section_1</title>
                        <type>array</type>
                        <!-- this is what to look out for: -->
                        <section>1</section>
                        <el>
                            <container_1>
                                <type>array</type>
                                <title>container_1</title>
                                <el>
                                    <select_tree_1>
                                        <label>select_tree_1 pages description</label>
                                        <description>field description</description>
                                        <config>
                                            <type>select</type>
                                            <renderType>selectTree</renderType>
                                            <foreign_table>pages</foreign_table>
                                            <foreign_table_where>ORDER BY pages.sorting</foreign_table_where>
                                            <size>20</size>
                                            <treeConfig>
                                                <parentField>pid</parentField>
                                                <appearance>
                                                    <expandAll>true</expandAll>
                                                    <showHeader>true</showHeader>
                                                </appearance>
                                            </treeConfig>
                                        </config>
                                    </select_tree_1>
                                </el>
                            </container_1>
                        </el>
                    </section_1>
                </el>
            </ROOT>
        </sSection>
    </sheets>
</T3DataStructure>
Copied!

Affected FlexForm TCA

EXT:my_extension/Configuration/TCA/tx_myextension_flex.php
[
    'columns' => [
        'flex_2' => [
            'label' => 'flex section container',
            'config' => [
                'type' => 'flex',
                'ds' => [
                    'default' => '
                        <T3DataStructure>
                            <sheets>
                                <sSection>
                                    <ROOT>
                                        <sheetTitle>section</sheetTitle>
                                        <type>array</type>
                                        <el>
                                            <section_1>
                                                <title>section_1</title>
                                                <type>array</type>
                                                <!-- this is what to look out for: -->
                                                <section>1</section>
                                                <el>
                                                    <container_1>
                                                        <type>array</type>
                                                        <title>container_1</title>
                                                        <el>
                                                            <select_tree_1>
                                                                <label>select_tree_1 pages description</label>
                                                                <description>field description</description>
                                                                <config>
                                                                    <type>select</type>
                                                                    <renderType>selectTree</renderType>
                                                                    <foreign_table>pages</foreign_table>
                                                                    <foreign_table_where>ORDER BY pages.sorting</foreign_table_where>
                                                                    <size>20</size>
                                                                    <treeConfig>
                                                                        <parentField>pid</parentField>
                                                                        <appearance>
                                                                            <expandAll>true</expandAll>
                                                                            <showHeader>true</showHeader>
                                                                        </appearance>
                                                                    </treeConfig>
                                                                </config>
                                                            </select_tree_1>
                                                        </el>
                                                    </container_1>
                                                </el>
                                            </section_1>
                                        </el>
                                    </ROOT>
                                </sSection>
                            </sheets>
                        </T3DataStructure>
                    ',
                ],
            ],
        ],
    ],
]
Copied!

Migration

Some extensions tried to work around existing restrictions by switching from type="inline" to type="group" or type="select", ending up with the same problematic scenario.

The basic issue is still, that binding database entities to anonymous data structures is a problematic approach in the first place: A container section that can be repeated often, combined with the additional built-in feature to have multiple different sections at the same time, is close to impossible to manage in a way that does not easily destroy data integrity.

Extensions that rely on this feature need to get rid of this approach: It typically means rewriting the extension to model relations using type="inline" bound to database columns directly.

Breaking: #102971 - Most classes of EXT:workspaces declared internal

See forge#102971

Description

A few additional classes of extension "workspaces" have been declared @internal. With this, most of the classes are now considered internal handling, except, of course, dispatched events.

Impact

Extensions extending or using workspace classes as PHP API that are now marked @internal may break, when the Core changes such classes. This will not be considered breaking.

Affected installations

Few extensions extend workspaces as such, and the backend workspaces application in particular.

Migration

Extension authors who need to extend from classes within EXT:workspaces should reconsider on why this needs to be done. They should expect these may break without further notice.

Legit use cases can often be moved towards some additional event instead. Extension authors are encouraged to come up with specific solutions in those cases.

Breaking: #102976 - TimeTracker read API internal

See forge#102976

Description

Class \TYPO3\CMS\Core\TimeTracker is used in the TYPO3 frontend rendering. It allows tracking time consumed by single code sections. The admin panel uses gathered data and renders a "time elapsed" overview from it.

All methods and properties that enable or disable tracking details and return the gathered data have been marked @internal and partially moved to EXT:adminpanel.

Extensions should only write data to TimeTracker, methods that are considered API are these:

  • TimeTracker->push() (second argument may vanish)
  • TimeTracker->pull()
  • TimeTracker->setTSlogMessage()

Impact

Extensions using methods other than the ones listed above may raise PHP fatal errors or different result structures when the underlying code is further refactored.

Affected installations

Most extensions in the wild use only the above listed methods. There is little reason to use other methods, except for extension that mimic or extend functionality of EXT:adminpanel. Instances with such extensions need to follow changes of class TimeTracker.

Migration

No direct migration possible.

Breaking: #102980 - getAllPageNumbers() in PaginationInterface

See forge#102980

Description

A method has been added to \TYPO3\CMS\Core\Pagination\PaginationInterface with this signature: public function getAllPageNumbers(): array;. It should return a list of all available page numbers.

The method has already been implemented in \TYPO3\CMS\Core\Pagination\SimplePagination and \TYPO3\CMS\Core\Pagination\SlidingWindowPagination .

Impact

Custom implementations of PaginationInterface must implement the method.

Affected installations

Instances with extensions that provide own pagination classes that implement PaginationInterface may be affected.

Migration

See the two Core classes SimplePagination and SlidingWindowPagination for examples on how the method is implemented.

Breaking: #102985 - Declare Indexed Search as Content Type

See forge#102985

Description

The plugin configuration of the "Indexed Search" plugin has been changed. The plugin is now configured as a proper "content element" using the CType plugin type. This allows to further shrink down the CType=list and list_type=<plugin_name> combination, like it has already been done with other plugins, e.g. the "Frontend Login" plugin.

Impact

The "Indexed Search" plugin is now configured as a content element, using CType=indexedsearch_pi2 instead of the CType=list and list_type=indexedsearch_pi2 combination.

An upgrade wizard is in place, migrating existing content elements as well as corresponding backend user group permissions.

Affected installations

All installations with extensions, relying on the "Indexed Search" plugin using the CType=list and list_type=indexedsearch_pi2 combination. This might be done in custom database queries, frontend data providers or in TSconfig. Also in cases where the corresponding backend user group permissions ( be_groups.explicit_allowdeny) are manually evaluated.

Migration

Execute the Migrate "Indexed Search" plugins to content elements. upgrade wizard to automatically migrate existing records. Make sure to have the Migrate backend groups "explicit_allowdeny" field to simplified format. upgrade wizard executed beforehand.

Additionally, adjust any place relying on the plugin using the CType=list and list_type=indexedsearch_pi2 combination.

Example SQL migrations:

-- Before
SELECT * FROM tt_content WHERE CType = 'list' AND list_type = 'indexedsearch_pi2';

-- After
SELECT * FROM tt_content WHERE CType = 'indexedsearch_pi2';
Copied!
-- Before
SELECT * FROM be_groups WHERE explicit_allowdeny LIKE '%tt_content:list_type:indexedsearch_pi2%';

-- After
SELECT * FROM be_groups WHERE explicit_allowdeny LIKE '%tt_content:CType:indexedsearch_pi2%';
Copied!

Feature: #82855 - Update Metadata of online media assets

See forge#82855

Description

A new action Reload Metadata has been added to the secondary options' dropdown of online media assets, such as YouTube and Vimeo videos, in the File > Filelist module. Additionally, also the context menu of such files has been extended for the new action.

The action allows to reload the corresponding information from the external service. That information is, for example, the preview image, the author or the dimensions of the online media asset.

Impact

It's now possible to reload the Metadata of an online media asset using the new Reload Metadata action.

Feature: #87889 - Configurable TYPO3 backend URL

See forge#87889

Description

The TYPO3 backend URL is made configurable in order to enable optional protection against application admin interface infrastructure enumeration (WSTG-CONF-05). Both, frontend and backend requests are now handled by the PHP script /index.php to enable virtual admin interface URLs.

The default TYPO3 backend entry point path /typo3 can be changed by specifying a custom URL path or domain name in $GLOBALS['TYPO3_CONF_VARS']['BE']['entryPoint'] .

This change requires web server adaption. A silent migration and according documentation for custom web server configurations is added. A deprecation layer (for non-adapted systems) is in place that rewrites the server environment variables passed to /typo3/index.php as if /index.php was used directly. This layer will be removed in TYPO3 v14.

This change does not take assets into account, only routing is adapted. That means Composer mode will use assets provided via /_assets as before and TYPO3 classic mode will serve backend assets from /typo3/* even if another backend URL is used and configured.

Configure to a specific path

$GLOBALS['TYPO3_CONF_VARS']['BE']['entryPoint'] = '/admin';
Copied!

Now point your browser to https://example.com/admin to log into the TYPO3 backend.

Configure to use a distinct (sub)domain

$GLOBALS['TYPO3_CONF_VARS']['BE']['entryPoint'] = 'https://backend.example.com';
$GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'] = '.example.com';
Copied!

Now point your browser to https://backend.example.com/ to log into the TYPO3 backend.

Legacy-Free installation

The legacy entry point /typo3/index.php is no longer needed and deprecated in favor of handling all backend and frontend requests with /index.php. The entry point is still in place, in case webserver configuration has not been adapted yet. The maintenance and emergency tool is still available via /typo3/install.php in order to work in edge cases like broken web server routing.

In Composer mode there is an additional opt-out for the installation of the legacy entrypoint that can be defined in your project's composer.json file:

"extra": {
  "typo3/cms": {
    "install-deprecated-typo3-index-php": false
  }
}
Copied!

Impact

The TYPO3 backend route path is made configurable in order to protected against application admin interface infrastructure enumeration (WSTG-CONF-05). Therefore, all requests are handled by the PHP script /index.php in order to allow for variable admin interface URLs.

Feature: #88537 - WebP image format support for Image Processing

See forge#88537

Description

WebP [https://en.wikipedia.org/wiki/WebP] is a modern image format for the web that comes with several advantages over PNG or JPEG image files:

  • WebP images have roughly 30% smaller file size compared to JPEG or PNG files
  • WebP images support an alpha channel (transparency) which JPEG files do not support

WebP is support by all modern browsers [https://caniuse.com/webp], and is available for processing / generation in most ImageMagick / GraphicsMagick versions.

TYPO3 can now generate WebP images, if the underlying ImageMagick / GraphicsMagick library supports WebP.

Impact

By default, WebP images can now be generated, as TYPO3's configuration setting $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] is now extended with "webp".

Integrators can now use the file extension webp in their Fluid template or Fluid templates or PHP code when interacting with the underlying Processing API or the Graphical Functions API.

The Install Tool / Environment Module displays if support for generating WebP image files is possible. In addition, a new report in the System > Reports module of TYPO3 backend shows, if TYPO3 is properly configured for generating WebP image files.

If the underlying ImageMagick / GraphicsMagick library is not built with WebP support, the server administrators can install or recompile the library with WebP support by installing the cwebp or dwebp libraries.

The default quality of generated WebP image files can be defined via $GLOBALS['TYPO3_CONF_VARS']['GFX']['webp_quality'] which requires a value between 1 (low quality, small file size) and 100 (best quality, large file size), or set to lossless which uses the lossless compression format. Even lossless compression for converting, for example, PNG files will result in smaller file sizes as WebP [https://developers.google.com/speed/webp/gallery2].

Depending on the target audience of the TYPO3 Frontend, it may be valid to disable WebP support by removing "webp" from the imagefile_ext setting.

Feature: #88817 - Make autocomplete selectable in EXT:form backend

See forge#88817

Description

Autocomplete options can already be added to input fields in EXT:form via editing the YAML. This was hard or impossible to do for editors.

The autocomplete tag has to be set for form fields to be accessibility compliant, where applicable.

The parameter can have arbitrary content according to the HTML standard. However, assistive technology only supports a finite number of values, of which only few are commonly used in contact forms.

Projects that desire additional parameter values can set them via YAML.

Impact

Editors can now select the autocomplete input purpose when editing forms.

In installations that extended the form YAML configuration with keys that are used in this change, the autocomplete field might be overridden and not be displayed in the editor by default.

Feature: #94501 - FAL support for FlexFormProcessor

See forge#94501

Description

The FlexFormProcessor is now able to resolve FAL references by its own.

Each FlexForm field, which should be resolved, needs a reference definition to the foreign_match_fields. This reference is later used in the FilesProcessor to resolve the correct FAL resource.

Example of an advanced TypoScript configuration, which processes the field my_flexform_field, resolves its FAL references and assigns the array to the myOutputVariable variable:

10 = TYPO3\CMS\Frontend\DataProcessing\FlexFormProcessor
10 {
  fieldName = my_flexform_field
  references {
    my_flex_form_group.my_flex_form_field = my_field_reference
  }
  as = myOutputVariable
}
Copied!
<my_flex_form_group.my_flex_form_field>
   <label>LLL:EXT:sitepackage/Resources/Private/Language/locallang_be.xlf:my_flex_form_field</label>
   <config>
      <type>file</type>
      <maxitems>9</maxitems>
      <foreign_selector_fieldTcaOverride>
         <config>
            <appearance>
               <elementBrowserType>file</elementBrowserType>
               <elementBrowserAllowed>gif,jpg,jpeg,png,svg</elementBrowserAllowed>
            </appearance>
         </config>
      </foreign_selector_fieldTcaOverride>
      <foreign_types type="array">
         <numIndex index="0">
            <showitem>--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,--palette--;;filePalette</showitem>
         </numIndex>
         <numIndex index="2">
            <showitem>--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,--palette--;;filePalette</showitem>
         </numIndex>
      </foreign_types>
      <appearance type="array">
         <headerThumbnail>
            <height>64</height>
            <width>64</width>
         </headerThumbnail>
         <enabledControls>
            <info>1</info>
            <dragdrop>0</dragdrop>
            <sort>1</sort>
            <hide>0</hide>
            <delete>1</delete>
            <localize>1</localize>
         </enabledControls>
         <createNewRelationLinkTitle>LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference</createNewRelationLinkTitle>
      </appearance>
      <behaviour>
         <localizationMode>select</localizationMode>
         <localizeChildrenAtParentLocalization>1</localizeChildrenAtParentLocalization>
      </behaviour>
      <overrideChildTca>
         <types type="array">
            <numIndex index="2">
               <showitem>--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,--palette--;;filePalette</showitem>
            </numIndex>
         </types>
      </overrideChildTca>
      <allowed>jpg,png,svg,jpeg,gif</allowed>
   </config>
</my_flex_form_group.my_flex_form_field>
Copied!

Impact

FAL references within a FlexForm can now be resolved for the direct usage in Fluid templates. This makes resolving the file references by using additional data processors obsolete.

Feature: #95808 - Enable item groups from foreign tables

See forge#95808

Description

A new TCA option foreign_table_item_group has been introduced for the TCA types select and category. It allows extension authors to define a specific field in the foreign table, holding an item group identifier. As described in the TCA reference, this needs to be a string.

Therefore, it's now possible to also use the item groups feature, introduced with forge#91008, for TCA columns with a foreign table lookup.

Example

'select_field' => [
    'label' => 'select_field',
    'config' => [
        'type' => 'select',
        'renderType' => 'selectSingle',
        'items' => [
            ['label' => 'static item 1', 'value' => 'static-1', 'group' => 'group1'],
        ],
        'itemGroups' => [
            'group1' => 'Group 1 with items',
            'group2' => 'Group 2 from foreign table',
        ],
        'foreign_table' => 'tx_extension_foreign_table',
        'foreign_table_item_group' => 'itemgroup'
    ],
],
Copied!

In case the foreign_table_item_group field of a foreign record contains an item group identifier, not set in the local itemGroups configuration, the database value will be used as label in the select box, as it's also the case for static items with a group set to a value, which is not configured in itemGroups.

Impact

Using the new foreign_table_item_group TCA config option, it's now possible to use the items group feature even for items from foreign tables.

Feature: #97664 - Add search functionality to form manager

See forge#97664

Description

It can be difficult for an editor to find the right form definition to edit, if there are many form definitions in the system. To make it easier, a search field is displayed in the form manager. This allows a case-insensitive search by name or persistenceIdentifier.

Impact

It's now possible to search for a form definition inside the form manager backend module.

Feature: #99165 - Add "Edit Metadata" button to element information view

See forge#99165

Description

A new button Edit Metadata has been added to the element information view of files. It allows to directly edit corresponding Metadata without leaving the context. The element information view for files can be accessed via the context menu or via the secondary action of files in any file listing, for example, in the File > Filelist module or the File selector.

Impact

It's now possible to edit a file's Metadata directly in the element information view, which improves the UX as the context is not lost.

Feature: #99323 - PSR-14 event for modifying records after fetching content

See forge#99323

Description

A new PSR-14 event \TYPO3\CMS\Frontend\ContentObject\Event\ModifyRecordsAfterFetchingContentEvent has been introduced which serves as a more powerful replacement for the now removed $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content_content.php']['modifyDBRow'] hook.

The event allows to modify the fetched records next to the possibility to manipulate most of the options, such as slide. Listeners are also able to set the final content and change the whole TypoScript configuration, used for further processing.

This can be achieved with the following methods:

  • getRecords()
  • getFinalContent()
  • getSlide()
  • getSlideCollect()
  • getSlideCollectReverse()
  • getSlideCollectFuzzy()
  • getConfiguration()
  • setRecords()
  • setFinalContent()
  • setSlide()
  • setSlideCollect()
  • setSlideCollectReverse()
  • setSlideCollectFuzzy()
  • setConfiguration()

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration:

use TYPO3\CMS\Frontend\ContentObject\Event\ModifyRecordsAfterFetchingContentEvent;

final class ModifyRecordsAfterFetchingContentEventListener
{
    #[AsEventListener]
    public function __invoke(ModifyRecordsAfterFetchingContentEvent $event): void
    {
        if ($event->getConfiguration()['table'] !== 'tt_content') {
            return;
        }

        $records = array_reverse($event->getRecords());
        $event->setRecords($records);
    }
}
Copied!

Impact

Using the new PSR-14 event, it's now possible to modify the records fetched by the "Content" ContentObject, before they are being further processed, or even skip TYPO3's default processing of records by setting an empty array for the records to be rendered.

Additionally, next to the final content, also most of the options and the whole TypoScript configuration can be modified.

Feature: #99485 - Show the redirect integrity status

See forge#99485

Description

The integrity check command checks redirects and displays the information in the CLI or in the status report. This status information is now used and stored on the redirect.

The command redirects:cleanup has been extended by the option integrityStatus. This allows you to remove specific redirects according to status.

The event \TYPO3\CMS\Redirects\Event\ModifyRedirectManagementControllerViewDataEvent has been extended by two new functions:

  • setIntegrityStatusCodes(): Allows to set integrityStatusCodes. Can be used to filter for integrityStatusCodes
  • getIntegrityStatusCodes(): Returns all integrityStatusCodes.

Impact

In the redirect module, the conflicting redirects are now marked. In addition, you can now filter for conflicting redirects.

In the redirects:checkintegrity command, the type of conflict is now displayed in the table.

Feature: #99807 - Improve ModifyUrlForCanonicalTagEvent

See forge#99807

Description

The \TYPO3\CMS\Seo\Event\ModifyUrlForCanonicalTagEvent , used by listeners to manipulate the URL of the canonical tag, has been improved. The event is now being dispatched after the standard functionality, such as fetching the URL from the page properties, has been executed.

Additionally, the event is now even dispatched, in case the canonical tag generation is disabled via TypoScript disableCanonical or via page properties no_index. If disabled, the new \TYPO3\CMS\Seo\Exception\CanonicalGenerationDisabledException is being thrown in the CanonicalGenerator. The exception is caught and transferred to the event, allowing listeners to determine whether generation is disabled, using the new getCanonicalGenerationDisabledException() method, which either returns the exception with the corresponding reason or null.

Impact

By relocating and extending the ModifyUrlForCanonicalTagEvent, listeners are now able to fully manipulate the canonical tag generation, even if the generation is disabled and after the standard functionality has been executed.

Feature: #100268 - Provide full userdata in password recovery email

See forge#100268

Description

A new array variable userData has been added to the password recovery \TYPO3\CMS\Core\Mail\FluidEmail object. It contains the values of all fields belonging to the affected frontend user.

Impact

It is now possible to use the {userData} variable in the Fluid template of the password recovery to access data from the affected frontend user.

Feature: #100926 - Introduce RotatingFileWriter for log rotation

See forge#100926

Description

TYPO3 log files tend to grow over time, if not manually cleaned on a regular basis, potentially leading to full disks. Also, reading its contents may be hard when several weeks of log entries are printed as a wall of text.

To circumvent such issues, established tools like logrotate are available for a long time already. However, TYPO3 may be installed on a hosting environment where logrotate is not available and cannot be installed by the customer. To cover such cases, a simple log rotation approach has been implemented, following the "copytruncate" approach: when rotating files, the currently opened log file is copied (for example, to typo3_[hash].log.20230616094812) and the original log file is emptied. This saves the hassle with properly closing and re-creating open file handles.

A new file writer \TYPO3\CMS\Core\Log\Writer\RotatingFileWriter has been added, which extends the already existing \TYPO3\CMS\Core\Log\Writer\FileWriter class. The RotatingFileWriter accepts all options of FileWriter in addition of the following:

  • interval - how often logs should be rotated, can be any of

    • daily or \TYPO3\CMS\Core\Log\Writer\Enum\Interval::DAILY (default)
    • weekly or \TYPO3\CMS\Core\Log\Writer\Enum\Interval::WEEKLY
    • monthly or \TYPO3\CMS\Core\Log\Writer\Enum\Interval::MONTHLY
    • yearly or \TYPO3\CMS\Core\Log\Writer\Enum\Interval::YEARLY
  • maxFiles - how many files should be retained (by default 5 files, 0 never deletes any file)

The RotatingFileWriter is configured like any other log writer.

Example

The following example introduces log rotation for the "main" log file.

system/additional.php
$GLOBALS['TYPO3_CONF_VARS']['LOG']['TYPO3']['CMS']['Core']['Resource']['ResourceStorage']['writerConfiguration'][\Psr\Log\LogLevel::ERROR] = [
    \TYPO3\CMS\Core\Log\Writer\RotatingFileWriter::class => [
        'interval' => \TYPO3\CMS\Core\Log\Writer\Enum\Interval::DAILY,
        'maxFiles' => 5,
    ],
    \TYPO3\CMS\Core\Log\Writer\DatabaseWriter::class => [], // this is part of the default configuration
];
Copied!

The following example introduces log rotation for the "deprecation" log file.

system/additional.php
$GLOBALS['TYPO3_CONF_VARS']['LOG']['TYPO3']['CMS']['deprecations']['writerConfiguration'][\Psr\Log\LogLevel::NOTICE] = [
    \TYPO3\CMS\Core\Log\Writer\RotatingFileWriter::class => [
        'logFileInfix' => 'deprecations',
        'interval' => \TYPO3\CMS\Core\Log\Writer\Enum\Interval::WEEKLY,
        'maxFiles' => 4,
        'disabled' => false,
    ],
    \TYPO3\CMS\Core\Log\Writer\DatabaseWriter::class => [], // this is part of the default configuration
];
Copied!

Impact

When configured, log files may be rotated before writing a new log entry, depending on the configured interval, where Interval::DAILY is the default. When rotating, the log files are suffixed with a rotation incremental value.

Example:

Directory listing of var/log/ with rotated logs
$ ls -1 var/log
typo3_[hash].log
typo3_[hash].log.20230613065902
typo3_[hash].log.20230614084723
typo3_[hash].log.20230615084756
typo3_[hash].log.20230616094812
Copied!

If maxFiles is configured with a value greater than 0, any exceeding log file is removed.

Feature: #101113 - Show if redirects were checked in report

See forge#101113

Description

In the status report is an entry to show, if redirect conflicts have been found.

However, there was previously no indication if / when the last check was run. If there were no conflicts, a status of "ok" was reported which could be quite misleading, if a redirects check was not performed lately.

Now we write a timestamp into the registry when checking redirects, along with the result of "checkintegrity". These are 2 separate registry entries.

The timestamp is queried in the report generation and an additional "info" status is displayed, if the timestamp indicates that the check was run more than 24 hours ago or never run at all:

List of conflicting redirects may not be up to date!
Regularly run the console command redirects:checkintegrity.
Copied!

This can be deactivated in the extension configuration and the time (24 hours) can be changed as well.

Impact

  • An additional informational message will appear in the system report, if checkintegrity was not run within the last 24 hours
  • This can be configured in the extension configuration of EXT:redirects
  • If extensions provide other means to check redirects, they should write the entries to the registry as well as the timestamp (see \TYPO3\CMS\Redirects\Command\CheckIntegrityCommand ):
/** @var \TYPO3\CMS\Core\Registry $registry */
$registry->set('tx_redirects', 'conflicting_redirects', $list);
$registry->set('tx_redirects', 'redirects_check_integrity_last_check', time());
Copied!

Feature: #101133 - Native enum IconState

See forge#101133

Description

A new native backed enum \TYPO3\CMS\Core\Imaging\IconState has been introduced for streamlined usage within \TYPO3\CMS\Core\Imaging\Icon and \TYPO3\CMS\Core\Imaging\IconFactory .

Impact

The new \TYPO3\CMS\Core\Imaging\IconState native backed enum is meant to be a drop-in replacement for the former \TYPO3\CMS\Core\Type\Icon\IconState class.

Example

<?php

use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconState;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$icon = $iconFactory->getIcon(
    'my-icon',
    TYPO3\CMS\Core\Imaging\Icon::SIZE_SMALL,
    null,
    IconState::STATE_DISABLED
);
Copied!

Feature: #101151 - Native enum DuplicationBehavior

See forge#101151

Description

A new native backed enum \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior has been introduced for streamlined usage within:

  • \TYPO3\CMS\Backend\Controller\File\FileController
  • \TYPO3\CMS\Core\Resource\AbstractFile
  • \TYPO3\CMS\Core\Resource\FileInterface
  • \TYPO3\CMS\Core\Resource\FileReference
  • \TYPO3\CMS\Core\Resource\Folder
  • \TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\AbstractOnlineMediaHelper
  • \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility
  • \TYPO3\CMS\Filelist\Controller\FileListController
  • \TYPO3\CMS\Impexp\Controller\ImportController

Impact

The new \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior native backed enum is meant to be a drop-in replacement for the former \TYPO3\CMS\Core\Resource\DuplicationBehavior class.

Feature: #101174 - Native enum InformationStatus

See forge#101174

Description

A new native backed enum \TYPO3\CMS\Backend\Toolbar\InformationStatus has been introduced as a drop-in replacement for the for former \TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus class. It is used to specify the severity of a system information, displayed in the backend toolbar.

The new enum features the following values:

  • NOTICE
  • INFO
  • OK
  • WARNING
  • ERROR

Additionally, the isGreaterThan() method is available to compare severities.

Impact

It's now possible to use the native \TYPO3\CMS\Backend\Toolbar\InformationStatus enum to describe the severity of the system information for the backend toolbar.

Feature: #101396 - Let Extbase handle native enums

See forge#101396

Description

With PHP 8.1, native support for enums has been introduced. This is quite handy if a database field has a specific set of values which can be represented by a PHP enum.

It is now possible to use backed enums in entities like this:

<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model\Enum;

enum Level: string
{
    case INFO = 'info';
    case ERROR = 'error';
}
Copied!
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class LogEntry extends AbstractEntity
{
    protected Enum\Level $level;
}
Copied!

Impact

To implement enums, it is no longer necessary to extend the now deprecated TYPO3 Core class \TYPO3\CMS\Core\Type\Enumeration.

Feature: #101475 - IconSizeEnum

See forge#101475

Description

A new backed enum \TYPO3\CMS\Core\Imaging\IconSize is introduced to be used in conjunction with the Icon API.

Impact

The introduced enum acts as a streamlined drop-in replacement for the existing \TYPO3\CMS\Core\Imaging\Icon::SIZE_* string constants.

Feature: #101507 - Hotkey API

See forge#101507

Description

TYPO3 provides the @typo3/backend/hotkeys.js module that allows developers to register custom keyboard shortcuts in the TYPO3 backend.

It is also possible and highly recommended to register hotkeys in a dedicated scope to avoid conflicts with other hotkeys, perhaps registered by other extensions.

The module provides an enum with common modifier keys (Ctrl, Meta, Alt, and Shift), and also a public property describing the common hotkey modifier based on the user's operating system: Cmd (Meta) on macOS, Ctrl on anything else. Using any modifier is optional, but highly recommended.

A hotkey is registered with the register() method. The method takes three arguments:

  • hotkey - An array defining the keys that must be pressed
  • handler - A callback that is executed when the hotkey is invoked
  • options - Object that configured a hotkey's behavior.

    • scope - The scope a hotkey is registered in
    • allowOnEditables - If false (default), handlers are not executed when an editable element is focussed
    • allowRepeat - If false (default), handlers are not executed when the hotkey is pressed for a long time
    • bindElement - If given, an aria-keyshortcuts attribute is added to the element. This is recommended for accessibility reasons.
import Hotkeys, {ModifierKeys} from '@typo3/backend/hotkeys.js';

Hotkeys.register(
    [Hotkeys.normalizedCtrlModifierKey, ModifierKeys.ALT, 'e'],
    function (keyboardEvent) => {
        console.log('Triggered on Ctrl/Cmd+Alt+E');
    },
    {
        scope: 'my-extension/module',
        bindElement: document.querySelector('.some-element')
    }
);

// Get the currently active scope
const currentScope = Hotkeys.getScope();

// Make use of registered scope
Hotkeys.setScope('my-extension/module');
Copied!

Impact

If properly used, common functionality is easier to access with hotkeys. The following hotkeys are configured within TYPO3:

  • Ctrl/Cmd + K - open LiveSearch
  • Ctrl/Cmd + S - save current document opened in FormEngine

Feature: #101544 - Introduce PHP attribute to autoconfigure event listeners

See forge#101544

Description

A new custom PHP attribute \TYPO3\CMS\Core\Attribute\AsEventListener has been added in order to autoconfigure a class as a PSR-14 event listener.

The attribute supports the following properties, which are all optional, as if you would register the listener by manually tagging it in the Configuration/Services.yaml or Configuration/Services.php file:

  • identifier - Event listener identifier (unique) - uses the service name, if not provided
  • event - Fully-qualified class name of the PSR-14 event to listen to
  • method - Method to be called - if omitted, __invoke() is called by the listener provider.
  • before - List of listener identifiers
  • after - List of listener identifiers

The attribute can be used on class and method level. Additionally, the new attribute is repeatable, which allows to register the same class to listen for different events.

Migration example

Before:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\EventListener\AddMailMessageBcc:
  tags:
    - name: event.listener
      identifier: 'my-extension/add-mail-message-bcc'
Copied!
EXT:my_extension/Classes/EventListener/AddMailMessageBcc.php
<?php

namespace MyVendor\MyExtension\EventListener;

use TYPO3\CMS\Core\Mail\Event\BeforeMailerSentMessageEvent;
use TYPO3\CMS\Core\Mail\MailMessage;

final readonly class AddMailMessageBcc
{
    public function __invoke(BeforeMailerSentMessageEvent $event): void
    {
        $message = $event->getMessage();
        if ($message instanceof MailMessage) {
            $message->addBcc('me@example.com');
        }
        $event->setMessage($message);
    }
}
Copied!

After:

The configuration is removed from the Services.yaml file and the attribute is assigned to the class instead:

EXT:my_extension/Classes/EventListener/AddMailMessageBcc.php
<?php

namespace MyVendor\MyExtension\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Mail\Event\BeforeMailerSentMessageEvent;
use TYPO3\CMS\Core\Mail\MailMessage;

#[AsEventListener(
    identifier: 'my-extension/add-mail-message-bcc'
)]
final readonly class AddMailMessageBcc
{
    public function __invoke(BeforeMailerSentMessageEvent $event): void
    {
        $message = $event->getMessage();
        if ($message instanceof MailMessage) {
            $message->addBcc('me@example.com');
        }
        $event->setMessage($message);
    }
}
Copied!

Repeatable example

EXT:my_extension/Classes/EventListener/MailerEventListener.php
<?php

namespace MyVendor\MyExtension\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Mail\Event\AfterMailerSentMessageEvent;
use TYPO3\CMS\Core\Mail\Event\BeforeMailerSentMessageEvent;

#[AsEventListener(
    identifier: 'my-extension/mailer-after-sent-message',
    event: AfterMailerSentMessageEvent::class
)]
#[AsEventListener(
    identifier: 'my-extension/mailer-before-sent-message',
    event: BeforeMailerSentMessageEvent::class
)]
final readonly class MailerEventListener
{
    public function __invoke(
        AfterMailerSentMessageEvent | BeforeMailerSentMessageEvent $event
    ): void {
        // do something
    }
}
Copied!

Method level example

EXT:my_extension/Classes/EventListener/MailerEventListener.php
<?php

namespace MyVendor\MyExtension\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Mail\Event\AfterMailerSentMessageEvent;
use TYPO3\CMS\Core\Mail\Event\BeforeMailerSentMessageEvent;

final readonly class MailerEventListener
{
    #[AsEventListener(
        identifier: 'my-extension/mailer-after-sent-message',
        event: AfterMailerSentMessageEvent::class
    )]
    #[AsEventListener(
        identifier: 'my-extension/mailer-before-sent-message',
        event: BeforeMailerSentMessageEvent::class
    )]
    public function __invoke(
        AfterMailerSentMessageEvent | BeforeMailerSentMessageEvent $event
    ): void {
        // do something
    }
}
Copied!

Impact

Using the PHP attribute \TYPO3\CMS\Core\Attribute\AsEventListener , it is now possible to tag any PHP class as an event listener. By adding the attribute the class is automatically tagged as event.listener and is therefore autoconfigured by the \TYPO3\CMS\Core\DependencyInjection\ListenerProviderPass .

Feature: #101553 - Auto-create DB fields from TCA columns

See forge#101553

Description

The TYPO3 v13 Core strives to auto-create database columns derived from TCA columns definitions without explicitly declaring them in ext_tables.sql.

Creating "management" fields like uid, pid automatically derived from TCA ctrl settings is available for a couple of Core versions already, the Core now extends this to single TCA columns.

As a goal, extension developers should not need to maintain a ext_tables.sql definition for casual table columns anymore, the file can vanish from extensions and the Core takes care of creating fields with sensible defaults.

Of course, it is still possible for extension authors to override single definitions in ext_tables.sql files in case they feel the Core does not define them in a way the extension author wants: Explicit definition in ext_tables.sql always take precedence over auto-magic.

New TCA config option dbFieldLength

For fields of type select a new TCA config option dbFieldLength has been introduced. It contains an integer value that is applied to varchar fields (not text) and defines the length of the database field. It will not be respected for fields that resolve to an integer type. Developers who wish to optimize field length can use dbFieldLength for type=select fields to increase or decrease the default length the Core comes up with.

Example:

// will result in SQL text field
'config' => [
    'itemsProcFunc => 'something',
],

// will result in SQL varchar field length for 200 characters
'config' => [
    'itemsProcFunc => 'something',
    'dbFieldLength' => 200,
],
Copied!

Impact

Extension authors should start removing single column definitions from ext_tables.sql for extensions being compatible with TYPO3 v13 and up.

If all goes well, the database analyzer will not show any changes since the Core definition is identical to what has been defined in ext_tables.sql before.

In various cases though, the responsible class DefaultTcaSchema may come to different conclusions than the extension author. Those cases should be reviewed by extension authors one-by-one: Most often, the Core declares a more restricted field, which is often fine. In some cases though, the extension author may know the particular field definition better than the Core default, and may decide to keep the field definition within ext_tables.sql.

Columns are auto-created for these TCA columns types:

  • type = 'category' - Core v12 already
  • type = 'datetime' - Core v12 already
  • type = 'slug' - Core v12 already
  • type = 'json' - Core v12 already
  • type = 'uuid' - Core v12 already
  • type = 'file' - new with Core v13
  • type = 'email' - new with Core v13
  • type = 'check' - new with Core v13
  • type = 'folder' - new with Core v13
  • type = 'imageManipulation' - new with Core v13
  • type = 'language' - new with Core v13
  • type = 'group' - new with Core v13
  • type = 'flex' - new with Core v13
  • type = 'text' - new with Core v13
  • type = 'password' - new with Core v13
  • type = 'color' - new with Core v13
  • type = 'radio' - new with Core v13
  • type = 'link' - new with Core v13
  • type = 'inline' - new with Core v13
  • type = 'number' - new with Core v13
  • type = 'select' - new with Core v13
  • type = 'input' - new with Core v13

See Breaking: DateTime column definitions for a change in the datetime column definition calculation.

Also see Important: About database error "row size too large" for limits imposed by MySQL / MariaDB on table length.

Migration of NULL to NOT NULL definitions, data truncation

As mentioned, the automatic database schema migration is based on TCA configuration, and will also take the nullable TCA definition of a field into consideration.

This can lead to scenarios in which a field (from both the TYPO3 Core or third party extension table definitions) will be converted in both type and attributes, and where data conversion might lead to error message like:

MySQL/MariaDB error message
Error: Data truncated for column 'image' at row 1
Copied!

This can happen if previously a field was defined via ext_tables.sql, and then the definition was removed so that the TCA automatism could take over, but the definition mismatches the TCA definition (which might have changed as well).

This can best be showcased with the following example:

Previous EXT:frontend/ext_tables.sql definition from TYPO3 v12
CREATE TABLE fe_users (
  # ...
 image tinytext,
 # ...
)
Copied!

With the TCA definition for the column fe_users.image set to type=file, the TYPO3 schema migration will decide to set this field to:

New automatically deduced SQL definition since TYPO3 v13
CREATE TABLE fe_users (
  # ...
  image INT UNSIGNED DEFAULT 0 NOT NULL,
  # ...
)
Copied!

Then, this executed SQL statement:

SQL statement as executed by the Database Compare tool on MySQL/MariaDB
ALTER TABLE `fe_users` CHANGE `image` `image` INT UNSIGNED DEFAULT 0 NOT NULL
Copied!

would lead to the error mentioned above, because any row that currently contains a NULL value would no longer be allowed. The solution for this is to fix these records before the schema migration is executed, by setting all currently existing NULL values to the new schema's DEFAULT value (here: 0).

This solution is provided by the TYPO3 Core via the migration wizard Migrate NULL field values to DEFAULT values.

The wizard looks for all existing records of a table where a schema conversion of NULL to NOT NULL would take place, iterates all rows of the table, and applies the default like this:

SQL command to fix database records NULL/NOT NULL state as executed by the upgrade wizard
UPDATE `fe_users` SET `image` = '0' WHERE `image` IS NULL;
Copied!

Feature: #101603 - PSR-14 event for modifying record overlay icon identifier

See forge#101603

Description

A new PSR-14 event \TYPO3\CMS\Core\Imaging\Event\ModifyRecordOverlayIconIdentifierEvent has been introduced which serves as a direct replacement for the now removed $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Core\Imaging\IconFactory']['overrideIconOverlay'] hook.

To modify the overlay icon identifier, the following methods are available:

  • setOverlayIconIdentifier(): Allows to set the overlay icon identifier
  • getOverlayIconIdentifier(): Returns the overlay icon identifier
  • getTable(): Returns the record's table name
  • getRow(): Returns the record's database row
  • getStatus(): Returns the record's visibility status

Example

The corresponding event listener class:

<?php

namespace Vendor\MyPackage\Core\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Imaging\Event\ModifyRecordOverlayIconIdentifierEvent;

final class ModifyRecordOverlayIconIdentifierEventListener
{
    #[AsEventListener('my-package/core/modify-record-overlay-icon-identifier')]
    public function __invoke(ModifyRecordOverlayIconIdentifierEvent $event): void
    {
        $event->setOverlayIconIdentifier('my-overlay-icon-identifier');
    }
}
Copied!

Impact

It's now possible to modify the overlay icon identifier of any record icon, using the new PSR-14 event ModifyRecordOverlayIconIdentifierEvent.

Feature: #101612 - UriBuilder->buildUriFromRequest

See forge#101612

Description

A new method within \TYPO3\CMS\Backend\Routing\UriBuilder named buildUriFromRequest is added which allows to generate a URL to a backend route of this request.

Impact

This is typically useful when linking to the current route or module in the TYPO3 backend for extension authors to avoid internals with any PSR-7 request attribute.

Usage within a PHP module controller in the TYPO3 backend context:

$this->uriBuilder->buildUriFromRequest($request, ['id' => $id]);

Feature: #101700 - Use Symfony attribute to autoconfigure message handlers

See forge#101700

Description

The symfony PHP attribute \Symfony\Component\Messenger\Attribute\AsMessageHandler is now respected and allows to register services as message handlers by setting the attribute on the class or the method.

Before:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\Queue\Handler\DemoHandler:
  tags:
    - name: 'messenger.message_handler'
Copied!

After:

The registration can be removed from the Configuration/Services.yaml file and the attribute is assigned to the handler class instead:

EXT:my_extension/Classes/Queue/Handler.php
<?php

namespace MyVendor\MyExtension\Queue\Handler;

use MyVendor\MyExtension\Queue\Message\DemoMessage;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler]
final class DemoHandler
{
    public function __invoke(DemoMessage $message): void
    {
        // do something with $message
    }
}
Copied!

It's also possible to set the attribute on the method:

EXT:my_extension/Classes/Queue/Handler.php
<?php

namespace MyVendor\MyExtension\Queue\Handler;

use MyVendor\MyExtension\Queue\Message\DemoMessage;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

final class DemoHandler
{
    #[AsMessageHandler]
    public function __invoke(DemoMessage $message): void
    {
        // do something with $message
    }
}
Copied!

Impact

The registration of services as message handlers has been simplified by respecting the \Symfony\Component\Messenger\Attribute\AsMessageHandler attribute. When using this attribute, there is no need to register such service in the Configuration/Services.yaml file anymore. Existing configuration will work as before.

Feature: #101807 - Automatic inclusion of user TSconfig of extensions

See forge#101807

Description

Extension authors can now put a file named Configuration/user.tsconfig in their extension folder.

This file is then recognized to load the contents as global user TSconfig for the whole TYPO3 installation during build-time. This is more performant than the existing solution using ExtensionManagementUtility::addUserTSConfig() in ext_localconf.php, which is added to $TYPO3_CONF_VARS[SYS][defaultUserTSconfig] during runtime.

Impact

When a file is created, the user TSconfig is loaded automatically without a custom registration, cached within the Core caches, and more performant than the existing registration format. The old registration format has been marked as deprecated, see Deprecated ExtensionManagementUtility::addUserTSConfig() for more details.

Feature: #101818 - BeforeLoadedPageTsConfigEvent

See forge#101818

Description

The PSR-14 event \TYPO3\CMS\Core\TypoScript\IncludeTree\Event\BeforeLoadedPageTsConfigEvent can be used to add global static page TSconfig before anything else is loaded. This is especially useful, if page TSconfig is generated automatically as a string from a PHP function.

It is important to understand that this config is considered static and thus should not depend on runtime / request.

Example

<?php

namespace Vendor\MyExtension\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\TypoScript\IncludeTree\Event\BeforeLoadedPageTsConfigEvent;

#[AsEventListener(identifier: 'vendor/my-extension/global-pagetsconfig')]
final class AddGlobalPageTsConfig
{
    public function __invoke(BeforeLoadedPageTsConfigEvent $event): void
    {
        $event->addTsConfig('global = a global setting');
    }
}
Copied!

Impact

Developers are able to define an event listener which is dispatched before any other page TSconfig is loaded.

Feature: #101838 - BeforeLoadedUserTsConfigEvent

See forge#101838

Description

The PSR-14 event \TYPO3\CMS\Core\TypoScript\IncludeTree\Event\BeforeLoadedUserTsConfigEvent can be used to add global static user TSconfig before anything else is loaded. This is especially useful, if user TSconfig is generated automatically as a string from a PHP function.

It is important to understand that this config is considered static and thus should not depend on runtime / request.

Example

<?php

namespace Vendor\MyExtension\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\TypoScript\IncludeTree\Event\BeforeLoadedUserTsConfigEvent;

#[AsEventListener(identifier: 'vendor/my-extension/global-usertsconfig')]
final class AddGlobalUserTsConfig
{
    public function __invoke(BeforeLoadedUserTsConfigEvent $event): void
    {
        $event->addTsConfig('global = a global setting');
    }
}
Copied!

Impact

Developers are able to define an event listener which is dispatched before any other user TSconfig is loaded.

Feature: #101843 - Allow configuration of color palettes in FormEngine

See forge#101843

Description

TYPO3 uses a color picker component that already supports color palettes, or swatches. Integrators are now able to configure colors and assign colors to palettes. Palettes then may be used within FormEngine.

Impact

In case commonly used colors or, for example, colors defined in a corporate design should be made accessible in an easy way, integrators may configure multiple color palettes to be used in FormEngine via page TSconfig.

EXT:my_sitepackage/Configuration/page.tsconfig
# Configure colors and assign colors to palettes
colorPalettes {
  colors {
    typo3 {
      value = #ff8700
    }
    blue {
      value = #0080c9
    }
    darkgray {
      value = #515151
    }
    valid {
      value = #5abc55
    }
    error {
      value = #dd123d
    }
  }
  palettes {
    main = typo3
    key_colors = typo3, blue, darkgray
    messages = valid, error
  }
}

# Assign palette to a specific field
TCEFORM.[table].[field].colorPalette = messages

# Assign palette to all color pickers used in a table
TCEFORM.[table].colorPalette = key_colors

# Assign global palette
TCEFORM.colorPalette = main
Copied!

Configuration allows to define the color palette either on a specific field of a table, for all fields within a table or a global configuration affecting all color pickers within FormEngine. If no palette is defined, FormEngine falls back to all configured colors.

Feature: #101933 - Dispatch AfterUserLoggedInEvent for frontend user login

See forge#101933

Description

The AfterUserLoggedInEvent PSR-14 event, which has been introduced with TYPO3 12.3, is now also triggered, when a frontend user has successfully been authenticated.

Impact

It is now possible to modify and adapt user functionality based on successful frontend user login.

Feature: #101970 - Ajax API accepts native URL and URLSearchParams objects as arguments

See forge#101970

Description

The Ajax API ( @typo3/core/ajax/ajax-request) has been enhanced to accept native URL-related objects.

Impact

The constructor now accepts a URL object as argument, along with the already established string type. Also, the withQueryArguments() method accepts an object of type URLSearchParams as argument.

Example

import AjaxRequest from '@typo3/core/ajax/ajax-request.js';

const url = new URL('https://example.com/page/1/2/');
const queryArguments = new URLSearchParams({
    foo: 'bar',
    baz: 'bencer'
});

const request = new AjaxRequest(url).withQueryArguments(queryArguments);
request.get().then(/* ... */);
Copied!

Feature: #102032 - Native enum FileType

See forge#102032

Description

A new native backed enum \TYPO3\CMS\Core\Resource\FileType is introduced as a replacement for the public FILETYPE_* constants in \TYPO3\CMS\Core\Resource\AbstractFile

Impact

The new \TYPO3\CMS\Core\Resource\FileType native backed enum is meant to be a drop-in replacement for the former public FILETYPE_* constants:

  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_UNKNOWN
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_TEXT
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_IMAGE
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_AUDIO
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_VIDEO
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_APPLICATION

Feature: #102067 - PSR-14 BeforeTcaOverridesEvent

See forge#102067

Description

A new PSR-14 event \TYPO3\CMS\Core\Configuration\Event\BeforeTcaOverridesEvent has been introduced, enabling developers to listen to the state between loaded base TCA and merging of TCA overrides.

Example

<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\EventListener;

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Configuration\Event\BeforeTcaOverridesEvent;

final class AddTcaBeforeTcaOverrides
{
    #[AsEventListener('my-vendor/my-extension/before-tca-overrides')]
    public function __invoke(BeforeTcaOverridesEvent $event): void
    {
        $tca = $event->getTca();
        $tca['tt_content']['columns']['header']['config']['max'] = 100;
        $event->setTca($tca);
    }
}
Copied!

Impact

The new PSR-14 event can be used to dynamically generate TCA and add it as additional base TCA. This is especially useful for "TCA generator" extensions, which add TCA based on another resource, while still enabling users to override TCA via the known TCA overrides API.

Note $GLOBALS['TCA'] is not set at this point. Event listeners can only work on the TCA coming from $event->getTca() and must not access $GLOBALS['TCA'] .

Feature: #102072 - Allow redirect filtering by "protected" state

See forge#102072

Description

The Redirects administration module now allows to filter redirects based on the protected state. Additionally, protected redirects now show a lock icon in the list of redirects, so it is better visualized, that a redirect is protected and excluded from automatic deletion (for example, with redirects:cleanup).

Impact

The administration of redirects has become more user-friendly, because users can now easily filter protected redirects in the Redirects administration module.

Feature: #102077 - Allow custom default value in getFormValue() conditions function

See forge#102077

Description

The getFormValue() function can be used in conditions of form variants to safely retrieve form values. Before, null was returned as default value. This made it impossible to use this, for example, with the in operator to check values in multi-value form fields. An additional check was necessary to avoid type issues:

variants:
  - identifier: variant-1
    condition: 'getFormValue("multiCheckbox") && "foo" in getFormValue("multiCheckbox")'
Copied!

A second argument has been added to this function to set a custom default value. This allows shortening conditions accordingly:

variants:
  - identifier: variant-1
    condition: '"foo" in getFormValue("multiCheckbox", [])'
Copied!

Impact

Form variant conditions can be shortened.

Feature: #102177 - WebP support for images generated by GIFBUILDER

See forge#102177

Description

GIFBUILDER, the image manipulation library for TypoScript based on GDlib, a PHP extension bundled into PHP, now also supports generating resulting files of type "webp".

WebP is an image format, that is supported by all modern browsers, and usually has a better compression (= smaller file size) than jpg files.

Impact

If defined via format=webp within a GifBuilder setup, the generated files are now webp instead of png (the default).

It is possible to define the quality of a webp image similar to jpg images globally via $TYPO3_CONF_VARS['GFX']['webp_quality'] or via TypoScript's "quality" property on a per-image basis. Setting the quality to "101" equivalents to "lossless" compression.

Example

page.10 = IMAGE
page.10 {
  file = GIFBUILDER
  file {
    backColor = yellow
    XY = 1024,199
    format = webp
    quality = 44

    10 = IMAGE
    10.offset = 10,10
    10.file = 1:/my-image.jpg
  }
}
Copied!

A new test in the Environment module / Install Tool can be used to check if the bundled GDlib extension of your PHP version supports the WebP image format.

Feature: #102496 - Introduce global Doctrine DBAL driver middlewares

See forge#102496

Description

Since v3, Doctrine DBAL supports adding custom driver middlewares. These middlewares act as a decorator around the actual Driver component. Subsequently, the Connection, Statement and Result components can be decorated as well. These middlewares must implement the \Doctrine\DBAL\Driver\Middleware interface. A common use case would be a middleware for implementing SQL logging capabilities.

For more information on driver middlewares, see https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/architecture.html. Furthermore, you can look up the implementation of the \TYPO3\CMS\Adminpanel\Log\DoctrineSqlLoggingMiddleware in ext:adminpanel as an example.

With Feature: #100089 - Introduce Doctrine DBAL v3 driver middlewares this has been introduced as a configuration per connection.

Now it's also possible to register global driver middlewares once, which are applied to all configured connections and then the specific connection middlewares.

See sortable Doctrine DBAL middleware registration feature changelog for further details about the middleware configuration block.

Registering a new global driver middleware

use MyVendor\MyExt\Doctrine\Driver\CustomGlobalDriverMiddleware;

// Register a global middleware
$GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares']['my-ext/custom-global-driver-middleware'] = [
  'target' => CustomGlobalDriverMiddleware::class,
  'after' [
    // NOTE: Custom driver middleware should be registered after essential
    //       TYPO3 Core driver middlewares. Use the following identifiers
    //       to ensure that.
    'typo3/core/custom-platform-driver-middleware',
    'typo3/core/custom-pdo-driver-result-middleware',
  ],
];
Copied!

Disable a global middleware for a specific connection

use MyVendor\MyExt\Doctrine\Driver\CustomGlobalDriverMiddleware;

// Register a global middleware
$GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares']['my-ext/custom-global-driver-middleware'] = [
  'target' => CustomGlobalDriverMiddleware::class,
  'after' [
    // NOTE: Custom driver middleware should be registered after essential
    //       TYPO3 Core driver middlewares. Use the following identifiers
    //       to ensure that.
    'typo3/core/custom-platform-driver-middleware',
    'typo3/core/custom-pdo-driver-result-middleware',
  ],
];

// Disable a global driver middleware for a specific connection
$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['SecondDatabase']['driverMiddlewares']['my-ext/custom-global-driver-middleware']['disabled'] = true;
Copied!

Impact

Using custom global middlewares allows to enhance the functionality of Doctrine components for all connections.

Feature: #102581 - PSR-14 event for modifying ContentObjectRenderer

See forge#102581

Description

A new PSR-14 event \TYPO3\CMS\Frontend\ContentObject\Event\AfterContentObjectRendererInitializedEvent has been introduced which serves as a drop-in replacement for the now removed hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] .

The event is being dispatched after ContentObjectRenderer has been initialized in its start() method. The ContentObjectRenderer instance can be accessed using the getContentObjectRenderer() method.

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration:

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Frontend\ContentObject\Event\AfterContentObjectRendererInitializedEvent;

final class AfterContentObjectRendererInitializedEventListener
{
    #[AsEventListener]
    public function __invoke(AfterContentObjectRendererInitializedEvent $event): void
    {
        $event->getContentObjectRenderer()->setCurrentVal('My current value');
    }
}
Copied!

Impact

Using the new PSR-14 event, it's now possible to modify the ContentObjectRenderer instance, after it has been initialized.

Feature: #102582 - Allow CLI command cleanup:localprocessedfiles to reset all records

See forge#102582

Description

A new CLI Symfony command option --all is added to the CLI command bin/typo3 cleanup:localprocessedfiles that allows to reset all entries in the database to force re-creating processed files.

When developing FAL features or updating installations with large user-generated content in fileadmin storages, it may be helpful to clean the sys_file_processedfile database table completely to force a rebuild (i.e. when new FAL processors are added).

This table holds all locally generated processed files with specific crop or size variants (or references to unaltered originals, or "proxy" entries).

The new command option will also report the numbers of deleted records before execution, and allows you to review execution. It is not set by default.

Impact

It is now possible to use the --all CLI command option for bin/typo3 cleanup:localprocessedfiles that allows to not only clear missing processed files, but also existing ones.

Feature: #102586 - Introduce sortable Doctrine DBAL middleware registration

See forge#102586

Description

TYPO3 v12 introduced the ability to register Doctrine DBAL driver middlewares for connections, using a simple 'identifier' => MyClass::class,' configuration schema.

TYPO3 v13 introduces Doctrine DBAL driver middleware registration on a global configuration level, allow extension authors to register middleware once but using it for all connections.

Global driver middlewares and connection driver middlewares are combined for a connection. The simple configuration approach introduced for the connection driver middlewares is no longer suitable for an easy dependency configuration or disabling a global driver middleware by connection.

The way to register and order PSR-15 middlewares has proven to be a reliable way, and understood by extension authors and integrators.

TYPO3 makes the global and connection driver middleware configuration sortable using the DependencyOrderingService ( \TYPO3\CMS\Core\Service\DependencyOrderingService ) similar to the PSR-15 middleware stack. Available structure for a middleware configuration is:

Basic driver middleware configuration array and PHPStan doc-block definition
/** @var array{target: string, disabled?: bool, after?: string[], before?: string[]} $middlewareConfiguration */
$middlewareConfiguration = [
  // target is required - for example use MyDriverMiddleware::class
  'target' => 'class fqdn',
  // disabled can be used to disable a global middleware for a specific
  // connection. This is optional and defaults to `false` if not provided
  'disabled' => false,
  // list of middleware identifiers, the current middleware should be registered after
  'after' => [
    // NOTE: Custom driver middleware should be registered after essential
    //       TYPO3 Core driver middlewares. Use the following identifiers
    //       to ensure that.
    'typo3/core/custom-platform-driver-middleware',
    'typo3/core/custom-pdo-driver-result-middleware',
  ],
  // list of middleware identifiers, the current middleware should be registered before
  'before' => [
    'some-driver-middleware-identifier',
  ],
];

// Register global driver middleware
$GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares']['global-driver-middleware-identifier']
  = $middlewareConfiguration;

// Register connection driver middleware
$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['SecondDatabase']['driverMiddlewares']['connection-driver-middleware-identifier']
  = $middlewareConfiguration;

// Simple disable a global driver middleware for a connection
$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['SecondDatabase']['driverMiddlewares']['global-driver-middleware-identifier'] = [
  // to disable a global driver middleware, setting disabled to true for a connection
  // is enough. Repeating target, after and/or before configuration is not required.
  'disabled' => false,
];
Copied!

Impact

Using custom driver middlewares allows to enhance the functionality of Doctrine components for all connections or a specific connection and have control over the sorting configuration - and it's now also possible to disable global driver middleware for a specific connection.

Feature: #102587 - Introduce driver middleware interface UsableForConnectionInterface

See forge#102587

Description

Since v3, Doctrine DBAL supports adding custom driver middlewares. These middlewares act as a decorator around the actual Driver component. Subsequently, the Connection, Statement and Result components can be decorated as well. These middlewares must implement the \Doctrine\DBAL\Driver\Middleware interface.

Global driver middlewares and connection driver middlewares are available and configuration has been enhanced with the DependencyOrderingService.

That means, that Doctrine DBAL driver middlewares can be registered globally for all connections or for specific connections. Due to the nature of the decorator pattern it may become hard to determine for specific configuration or drivers, if a middleware needs only be executed for a subset, for example only specific drivers.

TYPO3 now provides a custom \TYPO3\CMS\Core\Database\Middleware\UsableForConnectionInterface driver middleware interface which requires the implementation of the method

public function canBeUsedForConnection(
    string $identifier,
    array $connectionParams
): bool {}
Copied!

This allows to decide if a middleware should be used for specific connection, either based on the $connectionName or the $connectionParams, for example the concrete $connectionParams['driver'].

Custom driver middleware example using the interface

my_extension/Classes/DoctrineDBAL/CustomDriver.php (driver decorator)
namespace MyVendor\MyExt\DoctrineDBAL;

use Doctrine\DBAL\Driver\Connection as DriverConnection;
// Using the abstract class minimize the methods to implement and therefore
// reduces a lot of boilerplate code. Override only methods needed to be
// customized.
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;

final class CustomDriver extends AbstractDriverMiddleware
{
  public function connect(#[\SensitiveParameter] array $params): DriverConnection
  {
    $connection = parent::connect($params);

    // @todo Do something custom on connect, for example wrapping the driver
    //       connection class or executing some queries on connect.

    return $connection;
  }
}
Copied!
my_extension/Classes/DoctrineDBAL/CustomMiddleware.php (driver middleware)
namespace MyVendor\MyExt\DoctrineDBAL;

use Doctrine\DBAL\Driver as DoctrineDriverInterface;
use MyVendor\MyExt\DoctrineDBAL\CustomDriver as MyCustomDriver;
use TYPO3\CMS\Core\Database\Middleware\UsableForConnectionInterface;

final class CustomMiddleware implements UsableForConnectionInterface
{
  public function wrap(DoctrineDriverInterface $driver): DoctrineDriverInterface
  {
    // Wrap the original or already wrapped driver with our custom driver
    // decoration class to provide additional features.
    return new MyCustomDriver($driver);
  }

  public function canBeUsedForConnection(
    string $identifier,
    array $connectionParams
  ): bool {
     // Only use this driver middleware, if the configured connection driver
     // is 'pdo_sqlite' (sqlite using php-ext PDO).
     return ($connectionParams['driver'] ?? '') === 'pdo_sqlite';
  }
}
Copied!
my_extension/ext_localconf.php (Register custom driver middleware)
use MyVendor\MyExt\DoctrineDBAL\CustomMiddleware;

$middlewareConfiguration = [
  'target' => CustomMiddleware::class,
  'after' => [
    // NOTE: Custom driver middleware should be registered after essential
    //       TYPO3 Core driver middlewares. Use the following identifiers
    //       to ensure that.
    'typo3/core/custom-platform-driver-middleware',
    'typo3/core/custom-pdo-driver-result-middleware',
  ],
];

// Register middleware globally, to include it for all connection which
// uses the 'pdo_sqlite' driver.
$GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares']['myvendor/myext/custom-pdosqlite-driver-middleware']
  = $middlewareConfiguration;
Copied!

Impact

Extension author can provide conditional-based Doctrine driver middlewares by implementing the \TYPO3\CMS\Core\Database\Middleware\UsableForConnectionInterface along with the canBeUsedForConnection() method.

Feature: #102614 - PSR-14 event for modifying GetData result

See forge#102614

Description

A new PSR-14 event \TYPO3\CMS\Frontend\ContentObject\Event\AfterGetDataResolvedEvent has been introduced which serves as a drop-in replacement for the now removed hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'] .

The event is being dispatched just before ContentObjectRenderer->getData() is about to return the resolved "data". The event is therefore in comparison to the removed hook not dispatched for every section of the parameter string, but only once, making the former $secVal superfluous.

To modify the getData() result, the following methods are available:

  • setResult(): Allows to set the "data" to return
  • getResult(): Returns the resolved "data"
  • getParameterString(): Returns the parameter string, e.g. field : title
  • getAlternativeFieldArray(): Returns the alternative field array, if provided
  • getContentObjectRenderer(): Returns the current ContentObjectRenderer instance

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration:

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Frontend\ContentObject\Event\AfterGetDataResolvedEvent;

final class AfterGetDataResolvedEventListener
{
    #[AsEventListener]
    public function __invoke(AfterGetDataResolvedEvent $event): void
    {
        $event->setResult('modified-result');
    }
}
Copied!

Impact

Using the new PSR-14 event, it's now possible to modify the resolved getData() result.

Feature: #102624 - PSR-14 event for modifying image source collection

See forge#102624

Description

A new PSR-14 event \TYPO3\CMS\Frontend\ContentObject\Event\ModifyImageSourceCollectionEvent has been introduced which serves as a drop-in replacement for the now removed hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] .

The event is being dispatched in ContentObjectRenderer->getImageSourceCollection() for each configured sourceCollection and allows to enrich the final source collection result.

To modify getImageSourceCollection() result, the following methods are available:

  • setSourceCollection(): Allows to modify a source collection based on the corresponding configuration
  • getSourceCollection(): Returns the source collection
  • getFullSourceCollection(): Returns the current full source collection, being enhanced by the current sourceCollection
  • getSourceConfiguration(): Returns the current sourceCollection configuration
  • getSourceRenderConfiguration(): Returns the corresponding renderer configuration for the source collection
  • getContentObjectRenderer(): Returns the current ContentObjectRenderer instance

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration:

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Frontend\ContentObject\Event\ModifyImageSourceCollectionEvent;

final class ModifyImageSourceCollectionEventListener
{
    #[AsEventListener]
    public function __invoke(ModifyImageSourceCollectionEvent $event): void
    {
        $event->setSourceCollection('<source src="bar-file.jpg" media="(max-device-width: 600px)">');
    }
}
Copied!

Impact

Using the new PSR-14 event, it's now possible to manipulate the sourceCollection's, used for an ImageContentObject.

Feature: #102628 - Cache instruction middleware

See forge#102628

Description

TYPO3 v13 introduces the new frontend-related PSR-7 request attribute frontend.cache.instruction implemented by class \TYPO3\CMS\Frontend\Cache\CacheInstruction . This replaces the previous TyposcriptFrontendController->no_cache property and boolean noCache request attribute.

Impact

The attribute can be used by middlewares to disable cache mechanics of the frontend rendering.

In early middlewares before typo3/cms-frontend/tsfe, the attribute may or may not exist already. A safe way to interact with it is like this:

$cacheInstruction = $request->getAttribute('frontend.cache.instruction', new CacheInstruction());
$cacheInstruction->disableCache('EXT:my-extension: My-reason disables caches.');
$request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
Copied!

Extension with middlewares or other code after typo3/cms-frontend/tsfe can assume the attribute to be set already. Usage example:

$cacheInstruction = $request->getAttribute('frontend.cache.instruction');
$cacheInstruction->disableCache('EXT:my-extension: My-reason disables caches.');
Copied!

Feature: #102631 - Introduce AsController attribute to autoconfigure backend controllers

See forge#102631

Description

A new custom PHP attribute \TYPO3\CMS\Core\Attribute\AsController has been introduced in order to automatically tag backend controllers, making them available in the service container and enabling dependency injection.

Instead of adding the backend.controller manually, the attribute can be set on the class:

use TYPO3\CMS\Backend\Attribute\AsController;

#[AsController]
class MyBackendController {

}
Copied!

Impact

It's now possible to automatically tag a backend controller by adding the new PHP attribute \TYPO3\CMS\Backend\Attribute\AsController .

Feature: #102715 - New frontend.page.information request attribute

See forge#102715

Description

TYPO3 v13 introduces the new frontend-related PSR-7 request attribute frontend.page.information implemented by class \TYPO3\CMS\Frontend\Page\PageInformation . The object aims to replace various page related properties of \TYPO3\CMS\Frontend\Controller\TyposcriptFrontendController.

Note the class is currently still marked as experimental. Extension authors are however encouraged to use information from this request attribute instead of the TyposcriptFrontendController properties already: TYPO3 Core v13 will try to not break especially the getters / properties not marked as @internal.

Impact

There are three properties in TyposcriptFrontendController frequently used by extensions, which are now modeled in \TYPO3\CMS\Frontend\Page\PageInformation . The attribute is attached to the PSR-7 frontend request by middleware TypoScriptFrontendInitialization, middlewares below can rely on existence of that attribute. Examples:

$pageInformation = $request->getAttribute('frontend.page.information');
// Formerly $tsfe->id
$id = $pageInformation->getId();
// Formerly $tsfe->page
$page = $pageInformation->getPageRecord();
// Formerly $tsfe->rootLine
$rootLine = $pageInformation->getRootLine();
Copied!

Feature: #102745 - PSR-14 events for modifying ContentObject stdWrap functionality

See forge#102745

Description

Four new PSR-14 events have been introduced:

  • \TYPO3\CMS\Frontend\ContentObject\Event\BeforeStdWrapFunctionsInitializedEvent
  • \TYPO3\CMS\Frontend\ContentObject\Event\AfterStdWrapFunctionsInitializedEvent
  • \TYPO3\CMS\Frontend\ContentObject\Event\BeforeStdWrapFunctionsExecutedEvent
  • \TYPO3\CMS\Frontend\ContentObject\Event\AfterStdWrapFunctionsExecutedEvent

They serve as more powerful replacement of the removed, $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap'] hook.

Instead of registering one hook class, implementing four different methods - due to the deprecated interface - extension authors are now able to register dedicated listeners. Next to the individual events, it's also possible to register listeners to listen on the parent \TYPO3\CMS\Frontend\ContentObject\Event\EnhanceStdWrapEvent . Since this event is extended by all other events, registered listeners are called on each occurrence.

All events provide the same functionality. The difference is only the execution order in which they are called in the stdWrap processing chain.

Available methods:

  • getContent() - Returns the current content (stdWrap result)
  • setContent() - Allows to modify the final content (stdWrap result)
  • getConfiguration() - Returns the corresponding TypoScript configuration
  • getContentObjectRenderer() - Returns the current ContentObjectRenderer instance

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration:

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Frontend\ContentObject\Event\AfterStdWrapFunctionsExecutedEvent;
use TYPO3\CMS\Frontend\ContentObject\Event\AfterStdWrapFunctionsInitializedEvent;
use TYPO3\CMS\Frontend\ContentObject\Event\BeforeStdWrapFunctionsInitializedEvent;
use TYPO3\CMS\Frontend\ContentObject\Event\EnhanceStdWrapEvent;

final class EnhanceStdWrapEventListener
{
    #[AsEventListener]
    public function __invoke(EnhanceStdWrapEvent $event): void
    {
        // listen to all events
    }

    #[AsEventListener]
    public function individualListener(BeforeStdWrapFunctionsInitializedEvent $event): void
    {
        // listen on BeforeStdWrapFunctionsInitializedEvent only
    }

    #[AsEventListener]
    public function listenOnMultipleEvents(AfterStdWrapFunctionsInitializedEvent|AfterStdWrapFunctionsExecutedEvent $event): void
    {
        // Union type to listen to different events
    }
}
Copied!

Impact

Using the new PSR-14 events, it's now possible to fully influence the stdWrap functionality in TYPO3's Core API class \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer .

Using the new individual events, developers are now also able to simplify their code by just listening for the relevant parts in the stdWrap processing.

Feature: #102755 - PSR-14 event for modifying getImageResource result

See forge#102755

Description

A new PSR-14 event \TYPO3\CMS\Frontend\ContentObject\Event\AfterImageResourceResolvedEvent has been introduced which serves as a replacement for the now removed hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] .

The event is being dispatched just before ContentObjectRenderer->getImgResource() is about to return the resolved \TYPO3\CMS\Core\Imaging\ImageResource DTO. The event is therefore in comparison to the removed hook always dispatched, even if no ImageResource could be resolved. In this case, the corresponding return value is null.

To modify the getImgResource() result, the following methods are available:

  • setImageResource(): Allows to set the ImageResource to return
  • getImageResource(): Returns the resolved ImageResource or null
  • getFile(): Returns the $file, passed to the getImageResource function
  • getFileArray(): Returns the $fileArray, passed to the getImageResource function

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration:

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Frontend\ContentObject\Event\AfterImageResourceResolvedEvent;

final class AfterImageResourceResolvedEventListener
{
    #[AsEventListener]
    public function __invoke(AfterImageResourceResolvedEvent $event): void
    {
        $modifiedImageResource = $event
            ->getImageResource()
            ->withWidth(123);

        $event->setImageResource($modifiedImageResource);
    }
}
Copied!

Impact

Using the new PSR-14 Event, it's now possible to modify the resolved getImageResource() result.

Additionally, the ImageResource DTO allows an improved API as developers do no longer have to deal with unnamed array keys but benefit from the object-oriented approach, using corresponding getter and setter.

Feature: #102761 - Introduce class to generate/validate HMAC hashes

See forge#102761

Description

A new class \TYPO3\CMS\Core\Crypto\HashService has been introduced to enhance the security and flexibility in generating Hash-based Message Authentication Codes (HMACs). This class combines the functionality of GeneralUtility::hmac() and Extbase's HashService, while enforcing the use of an additional, mandatory secret for HMAC generation and HMAC string validation.

Impact

Using the new class \TYPO3\CMS\Core\Crypto\HashService , it is now possible to mitigate the risk of HMAC reuse in unauthorized scenarios for the same input.

Feature: #102793 - PSR-14 event for modifying default constraints in PageRepository

See forge#102793

Description

The API class \TYPO3\CMS\Core\Domain\Repository\PageRepository has a method getDefaultConstraints() which accumulates common restrictions for a database query to limit a query for TCA-based tables in order to filter out disabled, or scheduled records.

A new PSR-14 event \TYPO3\CMS\Core\Domain\Event\ModifyDefaultConstraintsForDatabaseQueryEvent has been introduced, which allows to remove, alter or add constraints compiled by TYPO3 for a specific table to further limit these constraints.

Impact

The new event contains a list of CompositeExpression objects, allowing to modify them via the getConstraints() and setConstraints(array $constraints) methods.

Additional information, such as the used ExpressionBuilder object or the table name and the current Context are also available within the event.

Feature: #102806 - BeforePageIsRetrievedEvent in PageRepository

See forge#102806

Description

A new PSR-14 event \TYPO3\CMS\Core\Domain\Event\BeforePageIsRetrievedEvent has been introduced, which serves as a more powerful replacement of the removed $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPage'] hook.

The new event therefore allows to modify the resolving of page records within \TYPO3\CMS\Core\Domain\PageRepository->getPage().

Impact

The event can be used to alter the incoming page ID or to even fetch a fully loaded page object before the default TYPO3 behaviour is executed, effectively bypassing the default page resolving.

To modify the incoming parameters, the following methods are available:

  • setPageId(): Allows to set the $uid of a page to resolve
  • setPage(): Allows to set a Page object which bypasses TYPO3 Core functionality

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration:

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Domain\Event\BeforePageIsRetrievedEvent;

final class CustomPageResolverEventListener
{
    #[AsEventListener]
    public function __invoke(BeforePageIsRetrievedEvent $event): void
    {
        if ($event->getPageId() === 13) {
            $event->setPageId(42);
        } elseif ($event->getContext()->getPropertyFromAspect('language', 'id') > 0) {
            $event->setPage(new \TYPO3\CMS\Core\Domain\Page(['uid' => 43]));
        }
    }
}
Copied!

Impact

Using the new PSR-14 event, it's now possible to fully customize the page resolving in TYPO3's Core API class \TYPO3\CMS\Core\Domain\PageRepository.

Feature: #102815 - Support ApplicationContext in TypoScript data

See forge#102815

Description

The new key applicationcontext is added for TypoScript's data function.

The application context is now available through:

if {
    value.data = applicationcontext
    equals = Production
}
Copied!

Impact

It is now possible to fetch the current application context as full blown string from TypoScript's data function.

Feature: #102834 - Auto-registration of New Content Element Wizard via TCA

See forge#102834

Description

Content element types defined in TCA field CType are now automatically registered for the New Content Element Wizard. This replaces the former extra step to define a wizard entry in page TSconfig mod.wizards.newContentElement.wizardItems.<group>.

The item entries value, label, description, group and icon are used to define the wizard entry.

The migration looks as follows:

Before:

EXT:my_extension/Configuration/page.tsconfig
# Add a new element (header) to the "common" group
mod.wizards.newContentElement.wizardItems.common.elements.header {
  iconIdentifier = content-header
  title = LLL:EXT:backend/Resources/Private/Language/locallang_db_new_content_el.xlf:common_headerOnly_title
  description = LLL:EXT:backend/Resources/Private/Language/locallang_db_new_content_el.xlf:common_headerOnly_description
  tt_content_defValues {
    CType = header
  }
}
mod.wizards.newContentElement.wizardItems.common.show := addToList(header)
Copied!

After:

EXT:my_extension/Configuration/TCA/Overrides/tt_content.php
// use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;

ExtensionManagementUtility::addPlugin(
    [
        'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_db_new_content_el.xlf:common_headerOnly_title',
        'description' => 'LLL:EXT:backend/Resources/Private/Language/locallang_db_new_content_el.xlf:common_headerOnly_description',
        'group' => 'default',
        'value' => 'header',
        'icon' => 'content-header',
    ],
    'CType',
    'my_extension',
);
Copied!

And for an Extbase plugin:

EXT:my_extension/Configuration/TCA/Overrides/tt_content.php
// use TYPO3\CMS\Extbase\Utility\ExtensionUtility;

ExtensionUtility::registerPlugin(
    'my_extension',         // extension name
    'my_plugin',            // plugin name
    'My plugin title',      // plugin title
    'my-icon',              // icon identifier
    'default',              // group
    'My plugin description' // plugin description
);
Copied!

The saveAndClose option is now defined through TCA as well:

Before:

EXT:my_extension/Configuration/page.tsconfig
mod.wizards.newContentElement.wizardItems {
  special.elements {
    div {
      saveAndClose = 1
    }
  }
}
Copied!

After:

EXT:my_extension/Configuration/TCA/Overrides/tt_content.php
<?php

$GLOBALS['TCA']['tt_content'] = array_merge_recursive(
    $GLOBALS['TCA']['tt_content'],
    [
        'types' => [
            'div' => [
                'creationOptions' => [
                    'saveAndClose' => true,
                ],
            ],
        ],
    ]
);
Copied!

The same applies to the default values. The option has been renamed from tt_content_defValues to defaultValues:

Before:

EXT:my_extension/Configuration/page.tsconfig
mod.wizards.newContentElement.wizardItems {
  special.elements {
    html {
      tt_content_defValues {
        bodytext = some text
      }
    }
  }
}
Copied!

After:

EXT:my_extension/Configuration/TCA/Overrides/tt_content.php
<?php

$GLOBALS['TCA']['tt_content'] = array_merge_recursive(
    $GLOBALS['TCA']['tt_content'],
    [
        'types' => [
            'html' => [
                'creationOptions' => [
                    'defaultValues' => [
                        'bodytext' => 'some text'
                    ],
                ],
            ],
        ],
    ]
);
Copied!

Removing items from the select box still works as before through page TSconfig TCEFORM. This will remove both the TCA items entry and the wizard entry.

EXT:my_extension/Configuration/page.tsconfig
TCEFORM.tt_content.CType {
  removeItems := addToList(header)
}
Copied!

To hide groups or elements in the wizard a new option removeItems is available.

EXT:my_extension/Configuration/page.tsconfig
# Before
mod.wizards.newContentElement.wizardItems.special.show := removeFromList(html)

# After
mod.wizards.newContentElement.wizardItems.special.removeItems := addToList(html)
Copied!

As mentioned, it's also possible to remove a whole group:

EXT:my_extension/Configuration/page.tsconfig
# This will remove the "menu" group
mod.wizards.newContentElement.wizardItems.removeItems := addToList(menu)
Copied!

Impact

The groups and elements of the new Content Element Wizard are now registered automatically from the TCA type field. This eases the creation of new content elements and plugins for integrators and developers, since the whole definition is done at a central place.

Feature: #102835 - Add PSR-14 events to manipulate TypoLinkCodecService

See forge#102835

Description

TYPO3's main API for encoding and decoding TypoLink's has been extended and now provides two new PSR-14 events \TYPO3\CMS\Core\LinkHandling\Event\BeforeTypoLinkEncodedEvent and \TYPO3\CMS\Core\LinkHandling\Event\AfterTypoLinkDecodedEvent , which allow developers to fully manipulate the encoding and decoding functionality.

A common use case for extensions is to extend the TypoLink parts to allow editors adding additional information, e.g. custom attributes to be added to the link markup. Previously, this required extensions to extended / cross class TypoLinkCodecService. This is no longer necessary when using the new events.

The BeforeTypoLinkEncodedEvent therefore allows to set $parameters, to be encoded while the AfterTypoLinkDecodedEvent allows to modify the decoded $typoLinkParts..

Both events provide the used $delimiter and the $emptyValueSymbol next to the corresponding input value, either the $typoLinkParts to be encoded or the $typoLink to be decoded.

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration:

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\LinkHandling\Event\AfterTypoLinkDecodedEvent;
use TYPO3\CMS\Core\LinkHandling\Event\BeforeTypoLinkEncodedEvent;

final class TypoLinkCodecServiceEventListener
{
    #[AsEventListener]
    public function encodeTypoLink(BeforeTypoLinkEncodedEvent $event): void
    {
        $typoLinkParameters = $event->getParameters();

        if (str_contains($typoLinkParameters['class'] ?? '', 'foo')) {
            $typoLinkParameters['class'] .= ' bar';
            $event->setParameters($typoLinkParameters);
        }
    }

    #[AsEventListener]
    public function decodeTypoLink(AfterTypoLinkDecodedEvent $event): void
    {
        $typoLink = $event->getTypoLink();
        $typoLinkParts = $event->getTypoLinkParts();

        if (str_contains($typoLink, 'foo')) {
            $typoLinkParts['foo'] = 'bar';
            $event->setTypoLinkParts($typoLinkParts);
        }
    }
}
Copied!

Impact

Using the new PSR-14 events, it's now possible to fully influence the encoding and decoding of any TypoLink.

Feature: #102849 - PSR-14 event for manipulating store cache functionality of stdWrap

See forge#102849

Description

A new PSR-14 event \TYPO3\CMS\Frontend\ContentObject\Event\BeforeStdWrapContentStoredInCacheEvent has been introduced which serves as a more powerful replacement for the now removed hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] .

The event is being dispatched just before the final stdWrap content is added to the cache and allows to fully manipulate the $content to be added, the cache $tags to be used as well as the corresponding cache $key and the cache $lifetime. Therefore, listeners can use the public getter and setter methods.

Additionally, the new event provides the full TypoScript $configuration and the current $contentObjectRenderer instance.

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration:

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Frontend\ContentObject\Event\BeforeStdWrapContentStoredInCacheEvent;

final class BeforeStdWrapContentStoredInCacheEventListener
{
    #[AsEventListener]
    public function __invoke(BeforeStdWrapContentStoredInCacheEvent $event): void
    {
        if (in_array('foo', $event->getTags(), true)) {
            $event->setContent('modified-content');
        }
    }
}
Copied!

Impact

Using the new PSR-14 event, it's now possible to fully manipulate the content, the cache tags as well as further relevant information, used by the caching functionality of stdWrap.

Feature: #102865 - PSR-14 event for modifying loaded form definition

See forge#102865

Description

A new PSR-14 event \TYPO3\CMS\Form\Mvc\Persistence\Event\AfterFormDefinitionLoadedEvent has been introduced which allows extensions to modify loaded form definitions.

The event is being dispatched after FormPersistenceManager has loaded the definition from either the cache or the filesystem. In latter case, the event is dispatched after FormPersistenceManager has stored the loaded definition in cache. This means, it's always possible to modify the cached version. However, the modified form definition is then overridden by TypoScript, in case a corresponding formDefinitionOverrides exists.

The event features the following methods:

  • getFormDefinition() - Returns the loaded form definition
  • setFormDefinition() - Allows to modify the loaded form definition
  • getPersistenceIdentifier() - Returns the persistence identifier, used to load the definition
  • getCacheKey() - Returns the calculated cache key

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration:

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Form\Mvc\Persistence\Event\AfterFormDefinitionLoadedEvent;

final class AfterFormDefinitionLoadedEventListener
{
    #[AsEventListener]
    public function __invoke(AfterFormDefinitionLoadedEvent $event): void
    {
        if ($event->getPersistenceIdentifier() === '1:/form_definitions/contact.form.yaml') {
            $formDefinition = $event->getFormDefinition();
            $formDefinition['label'] = 'some new label';
            $event->setFormDefinition($formDefinition);
        }
    }
}
Copied!

Impact

Using the new PSR-14 event, it's now possible to fully modify any loaded form definition, before being overridden by TypoScript.

Feature: #102935 - PSR-14 event for package initialization functionality

See forge#102935

Description

A new PSR-14 event \TYPO3\CMS\Core\Package\Event\PackageInitializationEvent has been introduced. It allows listeners to execute custom functionality after a package has been activated. The event is therefore being dispatched at several places, where packages get activated. Those are e.g. on extension installation by the extension manager, or on calling the typo3 extension:setup command. The main component, dispatching the event however is the new PackageActivationService. The new service is a drop-in replacement for the InstallUtility->install() method, which is from now on just a wrapper around PackageActivationService->activate(). The wrapper is used to still pass the current instance to listeners of the AfterPackageActivationEvent.

TYPO3 already registers a couple of listeners to this event:

  • \TYPO3\CMS\Core\Package\Initialization\ImportExtensionDataOnPackageInitialization
  • \TYPO3\CMS\Core\Package\Initialization\ImportStaticSqlDataOnPackageInitialization
  • \TYPO3\CMS\Core\Package\Initialization\CheckForImportRequirements
  • \TYPO3\CMS\Impexp\Initialization\ImportContentOnPackageInitialization
  • \TYPO3\CMS\Impexp\Initialization\ImportSiteConfigurationsOnPackageInitialization

Developers are able to listen to the new event before or after TYPO3 Core listeners have been executed, using before and after in the listener registration. All listeners are able to store arbitrary data in the Event using the addStorageEntry() method. This is also used by the core listeners to store their result, which was previously passed to the removed EXT:extensionmanager PSR-14 events.

Listeners can access that information using corresponding getStorageEntry() method. Those entries are a PackageInitializationResult object, which features the following methods:

  • getIdentifier() - Returns the entry identifier, which is the listener service name for the TYPO3 Core listeners
  • getResult() - Returns the result data, added by the corresponding listener

Using the new Event, listeners are equipped with following methods:

  • getExtensionKey() - Returns the extension key for the activated package
  • getPackage() - Returns the PackageInterface object of the activated package
  • getContainer() - Returns the ContainerInterface, used on activating the package
  • getEmitter() - Returns the emitter / the service, which has dispatched the event
  • hasStorageEntry() - Whether a storage entry for a given identifier exists
  • getStorageEntry() - Returns a storage entry for a given identifier
  • addStorageEntry() - Adds a storage entry ( PackageInitializationResult) to the event
  • removeStorageEntry() - Removes a storage entry by a given identifier

Example

The event listener class, using the PHP attribute #[AsEventListener] for registration, placing the listener after a specific core listener and adding a storage entry, using the listener class name as identifier (which is recommended and also done by TYPO3 Core):

use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Package\Event\PackageInitializationEvent;
use TYPO3\CMS\Core\Package\Initialization\ImportExtensionDataOnPackageInitialization;

final class PackageInitializationEventListener
{
    #[AsEventListener(after: ImportExtensionDataOnPackageInitialization::class)]
    public function __invoke(PackageInitializationEvent $event): void
    {
        if ($event->getExtensionKey() === 'my_ext') {
            $event->addStorageEntry(__CLASS__, 'my result');
        }
    }
}
Copied!

Impact

Using the new PSR-14 event, it's now possible to execute custom functionality when a package has been activated. Since TYPO3 Core also uses listeners to this event, custom extensions can easily place their functionality in between and fetch necessary information directly from the event's storage, instead of registering dedicated listeners.

Deprecation: #87889 - TYPO3 backend entry point script deprecated

See forge#87889

Description

The TYPO3 backend entry point script /typo3/index.php is no longer needed and deprecated in favor of handling all backend and frontend requests with /index.php. It is still in place in case webserver configuration has not been adapted yet.

Note that the maintenance tool is still available via /typo3/install.php.

Impact

The TYPO3 backend route path is made configurable in order to protect against application admin interface infrastructure enumeration (WSTG-CONF-05). Therefore, all requests are handled by the PHP script /index.php in order to allow for variable admin interface URLs. (via $GLOBALS['TYPO3_CONF_VARS']['BE']['entryPoint'] ).

Affected installations

All installations using the TYPO3 backend /typo3.

Migration

There is a silent update in place which automatically updates the webserver configuration file when accessing the install tool, at least for Apache and Microsoft IIS webservers.

Note: This does not work if you are not using the default configuration, which is shipped with Core and automatically applied during the TYPO3 installation process, as basis.

If you however use a custom web server configuration you may adapt as follows:

Apache configuration

It is most important to rewrite all typo3/* requests to /index.php, but also RewriteCond %{REQUEST_FILENAME} !-d should be removed in order for a request to /typo3/ to be directly served via /index.php instead of the deprecated entry point /typo3/index.php.

Apache configuration before:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^typo3/(.*)$ %{ENV:CWD}typo3/index.php [QSA,L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^.*$ %{ENV:CWD}index.php [QSA,L]
Copied!

Apache configuration after:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^typo3/(.*)$ %{ENV:CWD}index.php [QSA,L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^.*$ %{ENV:CWD}index.php [QSA,L]
Copied!

Nginx configuration

Nginx configuration before:

location /typo3/ {
    absolute_redirect off;
    try_files $uri /typo3/index.php$is_args$args;
}
Copied!

Nginx configuration after:

location /typo3/ {
    absolute_redirect off;
    try_files $uri /index.php$is_args$args;
}
Copied!

Deprecation: #101133 - IconState class

See forge#101133

Description

The class \TYPO3\CMS\Core\Type\Icon\IconState is marked as deprecated.

Impact

The class \TYPO3\CMS\Core\Type\Icon\IconState will be removed in TYPO3 v14.0. Passing an instance of this class to \TYPO3\CMS\Core\Imaging\IconFactory->getIcon() will lead to a deprecation level log entry.

Affected installations

All installations using the class \TYPO3\CMS\Core\Type\Icon\IconState.

Migration

// Before
$state = \TYPO3\CMS\Core\Type\Icon\IconState::cast(
    \TYPO3\CMS\Core\Type\Icon\IconState::STATE_DEFAULT
);

// After
$state = \TYPO3\CMS\Core\Imaging\IconState::STATE_DEFAULT;
Copied!

Deprecation: #101151 - DuplicationBehavior class

See forge#101151

Description

The class \TYPO3\CMS\Core\Resource\DuplicationBehavior is marked as deprecated.

Impact

The class \TYPO3\CMS\Core\Resource\DuplicationBehavior will be removed in TYPO3 v14.0.

Affected installations

All installations using the class \TYPO3\CMS\Core\Resource\DuplicationBehavior.

Migration

// Before
$behaviour = \TYPO3\CMS\Core\Resource\DuplicationBehavior::cast(
    \TYPO3\CMS\Core\Resource\DuplicationBehavior::RENAME
);

// After
$behaviour = \TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior::RENAME;
Copied!

Deprecation: #101163 - Abstract class Enumeration

See forge#101163

Description

The abstract class \TYPO3\CMS\Core\Type\Enumeration is deprecated in favor of PHP built-in backed enums.

Impact

All classes extending \TYPO3\CMS\Core\Type\Enumeration will trigger a deprecation level log entry.

Affected installations

Classes extending \TYPO3\CMS\Core\Type\Enumeration need to be converted into PHP built-in backed enums.

Migration

Class definition:

class State extends \TYPO3\CMS\Core\Type\Enumeration
{
    public const STATE_DEFAULT = 'somestate';
    public const STATE_DISABLED = 'disabled';
}
Copied!

should be converted into:

enum State: string
{
    case STATE_DEFAULT = 'somestate';
    case STATE_DISABLED = 'disabled';
}
Copied!

Existing method calls must be adapted.

See also Feature: #101396 - Let Extbase handle native enums.

Deprecation: #101174 - InformationStatus class

See forge#101174

Description

The class \TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus has been marked as deprecated in favour of the new nativ enum \TYPO3\CMS\Backend\Toolbar\InformationStatus .

Additionally, passing a string as $status to either addSystemInformation() or addSystemMessage() of class \TYPO3\CMS\Backend\Backend\ToolbarItems\SystemInformationToolbarItem has been deprecated as well. An instance of the new enum has to be provided.

Impact

Usage of the class \TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus, its constants or methods will trigger a PHP E_USER_DEPRECATED error. The class will be removed in TYPO3 v14.0.

Passing a string as $status to either addSystemInformation() or addSystemMessage() of class \TYPO3\CMS\Backend\Backend\ToolbarItems\SystemInformationToolbarItem will trigger a PHP E_USER_DEPRECATED error.

Affected installations

All installations using the class \TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus directly or passing a string as $status to either addSystemInformation() or addSystemMessage() of class \TYPO3\CMS\Backend\Backend\ToolbarItems\SystemInformationToolbarItem .

The extension scanner will report any usage of the deprecated class as strong match.

Migration

// Before
$status = \TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus::cast(
    \TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus::STATUS_INFO
);
$statusString = (string)$status;


// After
$status = \TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus::INFO;
$statusString = $status->value;
Copied!

Deprecation: #101175 - Methods in VersionState

See forge#101175

Description

The methods \TYPO3\CMS\Core\Versioning\VersionState::cast() and \TYPO3\CMS\Core\Versioning\VersionState->equals() have been marked as deprecated.

Impact

Calling the methods \TYPO3\CMS\Core\Versioning\VersionState::cast() and \TYPO3\CMS\Core\Versioning\VersionState->equals() will trigger a PHP deprecation warning.

Affected installations

TYPO3 installations calling \TYPO3\CMS\Core\Versioning\VersionState::cast() and \TYPO3\CMS\Core\Versioning\VersionState->equals().

Migration

Before:

$versionState = \TYPO3\CMS\Core\Versioning\VersionState::cast($value);
if ($versionState->equals(VersionState::MOVE_POINTER) {
    // ...
}
Copied!

After:

$versionState = \TYPO3\CMS\Core\Versioning\VersionState::tryFrom($value)
if ($versionState === VersionState::MOVE_POINTER) {
    // ...
}
Copied!

Deprecation: #101475 - Icon::SIZE_* string constants

See forge#101475

Description

The string constants representing icon sizes have been marked as deprecated:

  • \TYPO3\CMS\Core\Imaging\Icon::SIZE_DEFAULT
  • \TYPO3\CMS\Core\Imaging\Icon::SIZE_SMALL
  • \TYPO3\CMS\Core\Imaging\Icon::SIZE_MEDIUM
  • \TYPO3\CMS\Core\Imaging\Icon::SIZE_LARGE
  • \TYPO3\CMS\Core\Imaging\Icon::SIZE_MEGA

The following methods have been adapted to still accept the deprecated string constants and the new \TYPO3\CMS\Core\Imaging\IconSize enum:

  • \TYPO3\CMS\Core\Imaging\Icon->setSize()
  • \TYPO3\CMS\Core\Imaging\IconFactory->getIcon()
  • \TYPO3\CMS\Core\Imaging\IconFactory->getIconForFileExtension()
  • \TYPO3\CMS\Core\Imaging\IconFactory->getIconForRecord()
  • \TYPO3\CMS\Core\Imaging\IconFactory->getIconForResource()

The following method returns the string value of an IconSize enum, but will be removed in TYPO3 v14:

  • \TYPO3\CMS\Core\Imaging\Event\ModifyIconForResourcePropertiesEvent->getSize()

Impact

Passing the size as a string in the above mentioned methods will trigger a deprecation log entry.

Affected installations

All installations with third-party extensions using the Icon API are affected.

Migration

Migrate all usages of the aforementioned string constants to the IconSize as follows:

  • \TYPO3\CMS\Core\Imaging\Icon::SIZE_DEFAULT -> \TYPO3\CMS\Core\Imaging\IconSize::DEFAULT
  • \TYPO3\CMS\Core\Imaging\Icon::SIZE_SMALL -> \TYPO3\CMS\Core\Imaging\IconSize::SMALL
  • \TYPO3\CMS\Core\Imaging\Icon::SIZE_MEDIUM -> \TYPO3\CMS\Core\Imaging\IconSize::MEDIUM
  • \TYPO3\CMS\Core\Imaging\Icon::SIZE_LARGE -> \TYPO3\CMS\Core\Imaging\IconSize::LARGE
  • \TYPO3\CMS\Core\Imaging\Icon::SIZE_MEGA -> \TYPO3\CMS\Core\Imaging\IconSize::MEGA

Also migrate from \TYPO3\CMS\Core\Imaging\Event\ModifyIconForResourcePropertiesEvent->getSize() to \TYPO3\CMS\Core\Imaging\Event\ModifyIconForResourcePropertiesEvent->getIconSize().

Deprecation: #101554 - Obsolete TCA MM_hasUidField

See forge#101554

Description

When configuring MM relations in TCA, the field MM_hasUidField has been obsoleted: A uid column is only needed when multiple is set to true - when a record is allowed to be selected multiple times in a relation. In this case, the uid field is added automatically by the database analyzer.

Impact

The TCA configuration option MM_hasUidField is obsolete and can be removed.

The TCA migration, which is performed during TCA warmup, will automatically remove this option and creates according log entries, if needed.

Affected installations

Instances with extensions using MM relations may be affected.

Migration

Remove all occurrences of php:MM_hasUidField from TCA. The uid column is added as primary key automatically, if multiple = true is set, otherwise a combined primary key of the fields uid_local, uid_foreign plus eventually tablenames and fieldname is used.

Deprecation: #101793 - DataHandler checkStoredRecords properties

See forge#101793

Description

The backend DataHandler had a functionality to verify written records after they have been persisted in the database and log unexpected collisions.

This feature has been removed since it is rather useless with many databases in strict mode nowadays and since the default configuration was to not actually check single fields but to still create overhead by always querying records from the database without benefit.

Two TYPO3_CONF_VARS toggles have been obsoleted:

  • $GLOBALS['TYPO3_CONF_VARS']['BE']['checkStoredRecords']
  • $GLOBALS['TYPO3_CONF_VARS']['BE']['checkStoredRecordsLoose']

Two DataHandler properties have been marked as deprecated:

  • \TYPO3\CMS\Core\DataHandling\DataHandler->checkStoredRecords
  • \TYPO3\CMS\Core\DataHandling\DataHandler->checkStoredRecords_loose

Impact

There should be little to no impact for instances, except some less database queries when using the DataHandler. Extensions setting the DataHandler properties should stop using them, they will be removed with TYPO3 v14 and have no functionality with v13 anymore.

Affected installations

In rare cases, instances with extensions setting the DataHandler properties are affected. The extension scanner will find possible usages with a weak match.

Instances setting the TYPO3_CONF_VARS toggles in settings.php are updated silently by the install tool during the upgrade process to TYPO3 v13.

Migration

Extensions aiming for compatibility with TYPO3 v12 and v13 can continue to set the properties DataHandler->checkStoredRecords and DataHandler->checkStoredRecords_loose, they are kept in v13, but functionality bound to them is removed.

Extensions aiming for compatibility with TYPO3 v13 and above should remove usages of DataHandler->checkStoredRecords and DataHandler->checkStoredRecords_loose, they are without functionality in TYPO v13 and will be removed with TYPO3 v14.

Deprecation: #101799 - ExtensionManagementUtility::addPageTSConfig()

See forge#101799

Description

The method \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig() has been marked as deprecated in TYPO3 v13 and will be removed with TYPO3 v14.

The global configuration option $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] has been marked as deprecated in TYPO3 v13, will be ignored and removed from instance configuration files as silent upgrade with TYPO3 v14.

Impact

Setting default page TSconfig using ExtensionManagementUtility::addPageTSConfig() in ext_localconf.php files has been superseded by Automatic inclusion of page TSconfig of extensions with TYPO3 v12 already. The old way has been deprecated now, extensions should switch to the new functionality by placing default page TSconfig in Configuration/page.tsconfig files.

Affected installations

Instances with extensions using ExtensionManagementUtility::addPageTSConfig() or directly extending $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] are affected: Using ExtensionManagementUtility::addPageTSConfig() triggers a deprecation level log message. The extension scanner will find usages of ExtensionManagementUtility::addPageTSConfig() as strong match.

Migration

Add default page TSconfig to a Configuration/page.tsconfig file within an extension and remove calls to ExtensionManagementUtility::addPageTSConfig(). Placing default page TSconfig in Configuration/page.tsconfig files is available since TYPO3 v12, extensions aiming for v12 and v13 compatibility can simply switch over to the new way.

Deprecation: #101807 - ExtensionManagementUtility::addUserTSConfig()

See forge#101807

Description

The method \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig() has been marked as deprecated in TYPO3 v13 and will be removed with TYPO3 v14.

The global configuration option $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] has been marked as deprecated in TYPO3 v13, will be ignored and removed from instance configuration files as silent upgrade with TYPO3 v14.

Impact

Setting default user TSconfig using ExtensionManagementUtility::addUserTSConfig() in ext_localconf.php files has been superseded by Automatic inclusion of user TSconfig of extensions. The old way has been deprecated, extensions should switch to the new functionality by placing default user TSconfig in Configuration/user.tsconfig files.

Affected installations

Instances with extensions using ExtensionManagementUtility::addUserTSConfig() or directly extending $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] are affected: Using ExtensionManagementUtility::addUserTSConfig() triggers a deprecation level log message. The extension scanner will find usages of ExtensionManagementUtility::addUserTSConfig() as strong match.

Migration

Add default user TSconfig to a Configuration/user.tsconfig file within an extension and remove calls to ExtensionManagementUtility::addUserTSConfig().

Extensions with compatibility for both TYPO3 v12 and v13 should keep the old way and switch to the new way when v12 support is dropped.

Deprecation: #101912 - Passing jQuery objects to FormEngine validation

See forge#101912

Description

Both methods, validateField() and markFieldAsChanged() accept a form field as argument that is either of type HTMLInputElement, HTMLSelectElement, HTMLTextareaElement, or jQuery. Passing all of the aforementioned types is supported since TYPO3 v11, therefore, passing a jQuery object has been deprecated.

Impact

Calling any method, validateField() or markFieldAsChanged() with passing jQuery-based objects will render a warning in the browser console, along with a stacktrace to help identifying the caller code.

Affected installations

All third-party extensions using the deprecated methods of the @typo3/backend/form-engine-validation module are affected.

Migration

Do not pass jQuery-based objects into the deprecated methods. Consider migrating away from jQuery at all, or use $field.get(0) as interim solution.

Deprecation: #102032 - AbstractFile::FILETYPE_* constants

See forge#102032

Description

The int constants file types have been marked as deprecated:

  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_UNKNOWN
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_TEXT
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_IMAGE
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_AUDIO
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_VIDEO
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_APPLICATION

and will be removed in TYPO3 v14.0.

Impact

Using \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_* constants will be detected by the extension scanner.

Affected installations

All installations with third-party extensions using \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_* constants are affected.

Migration

Migrate all usages to use the new enum \TYPO3\CMS\Core\Resource\FileType as follows:

  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_UNKNOWN -> \TYPO3\CMS\Core\Resource\FileType::UNKNOWN->value
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_TEXT -> \TYPO3\CMS\Core\Resource\FileType::TEXT->value
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_IMAGE -> \TYPO3\CMS\Core\Resource\FileType::IMAGE->value
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_AUDIO -> \TYPO3\CMS\Core\Resource\FileType::AUDIO->value
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_VIDEO -> \TYPO3\CMS\Core\Resource\FileType::VIDEO->value
  • \TYPO3\CMS\Core\Resource\AbstractFile::FILETYPE_APPLICATION -> \TYPO3\CMS\Core\Resource\FileType::APPLICATION->value

Deprecation: #102440 - EXT:t3editor merged into EXT:backend

See forge#102440

Description

The JavaScript module specifier for modules shipped with the previous "t3editor" extension has changed from @typo3/t3editor/ to @typo3/backend/code-editor/. The old specifier @typo3/t3editor/ is still available, but deprecated.

The value of the existing TCA option renderType switched from t3editor to codeEditor.

Impact

The module specifier @typo3/t3editor/ automatically maps to @typo3/backend/code-editor/. The TCA render type t3editor is automatically migrated to codeEditor, triggering a deprecation log entry.

Affected installations

All extensions using t3editor are affected.

Migration

The JavaScript module namespace @typo3/t3editor/ maps to @typo3/backend/code-editor/.

Rewrite all TCA render types usages of t3editor to codeEditor.

Deprecation: #102581 - Unused Interface for ContentObjectRenderer hook

See forge#102581

Description

To use the $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['postInit'] hook, implementations had to implement \TYPO3\CMS\Frontend\ContentObject\ContentObjectPostInitHookInterface. Since the mentioned hook has been removed, the interface is not in use anymore and has been marked as deprecated.

Impact

Using the interface has no effect anymore and the extension scanner will report any usage.

Affected installations

TYPO3 installations using the PHP interface in custom extension code.

Migration

The PHP interface is still available for TYPO3 v13, so extensions can provide a version which is compatible with TYPO3 v12 (using the hook) and TYPO3 v13 (using the new PSR-14 event), at the same time. Remove any usage of the PHP interface and use the new PSR-14 event to avoid any further problems in TYPO3 v14+.

Deprecation: #102586 - Deprecate simple string connection driver middleware registration

See forge#102586

Description

Using the simple 'identifier' => MyClass::class,' configuration schema to register Doctrine DBAL middlewares for connection is now deprecated in favour of using a sortable registration configuration similar to the PSR-15 middleware registration.

Impact

Connection driver middleware registration using a simple string will emit a corresponding message to the deprecation log since TYPO3 v13, but converting it on-the-fly to a valid array configuration.

Affected installations

TYPO3 instances using third-party extension providing custom Doctrine DBAL driver middlewares and having them registered for one or more connections will emit a deprecation message since TYPO3 v13 and either an exception with TYPO3 v14 or an PHP type error.

Migration

Simple driver middleware registration, for example

use MyVendor\MyExt\Doctrine\Driver\MyDriverMiddlewareClass;

$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['SecondDatabase']['driverMiddlewares']['driver-middleware-identifier']
    = MyDriverMiddlewareClass::class;
Copied!

needs to be converted to

use MyVendor\MyExt\Doctrine\Driver\MyDriverMiddlewareClass;

$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['SecondDatabase']['driverMiddlewares']['driver-middleware-identifier'] = [
  'target' => MyDriverMiddlewareClass::class,
  'after' => [
    'typo3/core/custom-platform-driver-middleware',
  ],
];
Copied!

Registration for driver middlewares for TYPO3 v12 and v13

Extension authors providing dual Core support with one extension version can use the Typo3Version class to provide the configuration suitable for the Core version and avoiding the deprecation notice:

use TYPO3\CMS\Core\Information\Typo3Version;
use MyVendor\MyExt\Doctrine\Driver\MyDriverMiddlewareClass;

$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['SecondDatabase']['driverMiddlewares']['driver-middleware-identifier']
    = ((new Typo3Version)->getMajorVersion() < 13)
        ? MyDriverMiddlewareClass::class
        : [
          'target' => MyDriverMiddlewareClass::class,
          'after' => [
            'typo3/core/custom-platform-driver-middleware',
          ],
        ];
Copied!

Deprecation: #102614 - Unused Interface for GetData Hook

See forge#102614

Description

Using the $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getData'] hook, implementations had to implement \TYPO3\CMS\Frontend\ContentObject\ContentObjectGetDataHookInterface. Since the mentioned hook has been removed, the interface is not in use anymore and has been marked as deprecated.

Impact

Using the interface has no effect anymore and the extension scanner will report any usage.

Affected installations

TYPO3 installations using the PHP interface in custom extension code.

Migration

The PHP interface is still available for TYPO3 v13, so extensions can provide a version which is compatible with TYPO3 v12 (using the hook) and TYPO3 v13 (using the new PSR-14 event), at the same time. Remove any usage of the PHP interface and use the new PSR-14 event to avoid any further problems in TYPO3 v14+.

Deprecation: #102624 - Unused Interface for getImageSourceCollection Hook

See forge#102624

Description

Using the $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] hook, implementations had to implement \TYPO3\CMS\Frontend\ContentObject\ContentObjectOneSourceCollectionHookInterface. Since the mentioned hook has been removed, the interface is not in use anymore and has been marked as deprecated.

Impact

Using the interface has no effect anymore and the extension scanner will report any usage.

Affected installations

TYPO3 installations using the PHP interface in custom extension code.

Migration

The PHP interface is still available for TYPO3 v13, so extensions can provide a version which is compatible with TYPO3 v12 (using the hook) and TYPO3 v13 (using the new PSR-14 event), at the same time. Remove any usage of the PHP interface and use the new PSR-14 event to avoid any further problems in TYPO3 v14+.

Deprecation: #102631 - Deprecated Controller attribute for auto configuring backend controllers

See forge#102631

Description

In order to unify PHP attribute naming, the former introduced \TYPO3\CMS\Backend\Attribute\Controller attribute has been deprecated and is replaced by the new \TYPO3\CMS\Backend\Attribute\AsController attribute.

Impact

The attribute has changed from \TYPO3\CMS\Backend\Attribute\Controller to \TYPO3\CMS\Backend\Attribute\AsController and the old name has been deprecated.

Affected installations

All installations using the deprecated attribute \TYPO3\CMS\Backend\Attribute\Controller. The extension scanner will report usages.

Migration

Replace usages with the new attribute \TYPO3\CMS\Backend\Attribute\AsController in custom extension code.

Deprecation: #102745 - Unused interface for stdWrap hook

See forge#102745

Description

The ContentObject stdWrap hook required hook implementations to implement the \TYPO3\CMS\Frontend\ContentObject\ContentObjectStdWrapHookInterface. Since the mentioned hook has been removed, the interface is not in use anymore and has been marked as deprecated.

Impact

Using the interface has no effect anymore and the extension scanner will report any usage.

Affected installations

TYPO3 installations using the PHP interface in custom extension code.

Migration

The PHP interface is still available for TYPO3 v13, so extensions can provide a version which is compatible with TYPO3 v12 (using the hook) and TYPO3 v13 (using the new PSR-14 events), at the same time. Remove any usage of the PHP interface and use the new PSR-14 events to avoid any further problems in TYPO3 v14+.

Deprecation: #102755 - Unused interface for getImageResource hook

See forge#102755

Description

Using the $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImgResource'] hook, implementations had to implement \TYPO3\CMS\Frontend\ContentObject\ContentObjectGetImageResourceHookInterface. Since the mentioned hook has been removed, the interface is not in use anymore and has been marked as deprecated.

Impact

Using the interface has no effect anymore and the extension scanner will report any usage.

Affected installations

TYPO3 installations using the PHP interface in custom extension code.

Migration

The PHP interface is still available for TYPO3 v13, so extensions can provide a version which is compatible with TYPO3 v12 (using the hook) and TYPO3 v13 (using the new PSR-14 event), at the same time. Remove any usage of the PHP interface and use the new PSR-14 event to avoid any further problems in TYPO3 v14+.

Deprecation: #102763 - Extbase HashService

See forge#102763

Description

Internal class \TYPO3\CMS\Extbase\Security\Cryptography\HashService is deprecated in favor of \TYPO3\CMS\Core\Crypto\HashService , which requires an additional secret to prevent re-using generated hashes in different contexts.

Impact

Using class \TYPO3\CMS\Extbase\Security\Cryptography\HashService will trigger a PHP deprecation warning.

Affected installations

TYPO3 installations with custom extensions using \TYPO3\CMS\Extbase\Security\Cryptography\HashService.

Migration

Class \TYPO3\CMS\Core\Crypto\HashService must be used to migrate.

Before

$hashService = new \TYPO3\CMS\Extbase\Security\Cryptography\HashService();

$generatedHash = $hashService->generateHmac('123');
$isValidHash = $hashService->validateHmac('123', $generatedHash);

$stringWithAppendedHash = $hashService->appendHmac('123');
$validatedStringWithHashRemoved = $hashService->validateAndStripHmac($stringWithAppendedHash);
Copied!

After

$hashService = new \TYPO3\CMS\Core\Crypto\HashService();

$generatedHash = $hashService->hmac('123', 'myAdditionalSecret');
$isValidHash = $hashService->validateHmac('123', 'myAdditionalSecret', $generatedHash);

$stringWithAppendedHash = $hashService->appendHmac('123', 'myAdditionalSecret');
$validatedStringWithHashRemoved = $hashService->validateAndStripHmac($stringWithAppendedHash, 'myAdditionalSecret');
Copied!

Note, $additionalSecret string must be unique per context, so hashes for the same input are different depending on scope.

Deprecation: #102793 - PageRepository->enableFields

See forge#102793

Description

One of the common PHP APIs used in TYPO3 Core for fetching records is \TYPO3\CMS\Core\Domain\Repository\PageRepository . The method enableFields() is used to enhance a database query with additional restrictions such as filtering out versioned records from workspaces, hidden database entries or scheduled database entries.

This method has been marked as deprecated in favor of a new method getDefaultConstraints().

Impact

Calling the method will trigger a PHP deprecation warning.

Affected installations

TYPO3 installations with custom extensions using the method enableFields() of the PageRepository class.

Migration

A new method called getDefaultConstraints() has been introduced which supersedes the old method. The new method returns an array of CompositeExpression objects, which can be used instead.

Before (TYPO3 v12)

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable($tableName);

$constraints = GeneralUtility::makeInstance(PageRepository::class)
    ->enableFields($tableName);

$queryBuilder
    ->select('*')
    ->from($tableName);
    ->where(QueryHelper::stripLogicalOperatorPrefix($constraints);

$queryBuilder->execute();
Copied!

After (TYPO3 v13)

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
    ->getQueryBuilderForTable($tableName);

$constraints = GeneralUtility::makeInstance(PageRepository::class)
    ->getDefaultConstraints($tableName);

$queryBuilder
    ->select('*')
    ->from($tableName);

if ($constraints !== []) {
    $queryBuilder->where(...$constraints);
}

$queryBuilder->execute();
Copied!

Deprecation: #102806 - Interfaces for PageRepository hooks

See forge#102806

Description

Using the hooks $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\TYPO3\CMS\Core\Domain\PageRepository::class]['init'] and $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPage'] , implementations had to implement the \TYPO3\CMS\Core\Domain\Repository\PageRepositoryInitHookInterface respectively \TYPO3\CMS\Core\Domain\Repository\PageRepositoryGetPageHookInterface interface.

Since the mentioned hooks have been removed, the interfaces are not in use anymore and have been marked as deprecated.

Impact

The removed hooks are no longer evaluated, thus the interfaces are not in use anymore, but are kept for backwards-compatibility. As they are interfaces, they do not trigger a deprecation warning.

Affected installations

TYPO3 installations with third-party extensions utilizing the hooks and their interfaces.

The extension scanner in the install tool can find any usages to these interfaces and their interfaces.

Migration

The PHP interfaces are still available for TYPO3 v13, so extensions can provide a version which is compatible with TYPO3 v12 (using the hooks) and TYPO3 v13 (using the new PSR-14 event), at the same time. Remove any usage of the PHP interface and use the new PSR-14 event to avoid any further problems in TYPO3 v14+.

Deprecation: #102895 - ExtensionManagementUtility::getExtensionIcon

See forge#102895

Description

The PHP method \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getExtensionIcon has been deprecated in favor of \TYPO3\CMS\Core\Package\Package->getPackageIcon.

Impact

Calling the method \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getExtensionIcon will trigger a PHP deprecation warning.

Affected installations

TYPO3 installations with custom extensions calling the method.

Migration

Migrate towards the PackageManager implementation, which can be added via Dependency Injection or retrieved via GeneralUtility::makeInstance().

Before

$iconPathInPackage = ExtensionManagementUtility::getExtensionIcon($extensionKey);
$fullIconPath = ExtensionManagementUtility::getExtensionIcon($extensionKey, true);
Copied!

After

$packageManager = GeneralUtility::makeInstance(PackageManager::class);
$package = $packageManager->getPackage($extensionKey);
if ($package->getPackageIcon()) {
    $iconPathInPackage = $package->getPackageIcon();
    $fullIconPath = $package->getPackagePath() . $package->getPackageIcon();
}
Copied!

Deprecation: #102908 - Indexed Search content parsers returning arrays

See forge#102908

Description

Content parsers implemented in Indexed Search return an array solely defined for the internal indexer. It is now encouraged to return an instance of \TYPO3\CMS\IndexedSearch\Dto\IndexingDataAsString instead.

Impact

Returning an array is deprecated. The indexing process will catch such cases, convert the result to an IndexingDataAsString object and raise a deprecation warning.

Affected installations

All installations with custom content parsers are affected, which very unlikely do exist.

Migration

Let the custom content parser return an instance of IndexingDataAsString. Either use the constructor, or the static helper method fromArray().

In both cases, the following input is accepted:

  • title
  • body
  • keywords
  • description

Examples

// Using constructor
return new IndexingDataAsString('<title>', '<body>', '<keywords>', '<description>');
Copied!
// Using static helper
return IndexingDataAsString::fromArray([
    'title' => '<title>',
    'body' => '<body>',
    'keywords' => '<keywords>',
    'description' => '<description>',
]);
Copied!

Deprecation: #102943 - AbstractDownloadExtensionUpdate moved to ext:extensionmanager

See forge#102943

Description

The following upgrade wizard related classes have been moved from EXT:install to EXT:extensionmanager:

  • \TYPO3\CMS\Install\Updates\AbstractDownloadExtensionUpdate, new name \TYPO3\CMS\Extensionmanager\Updates\AbstractDownloadExtensionUpdate
  • \TYPO3\CMS\Install\Updates\ExtensionModel, new name \TYPO3\CMS\Extensionmanager\Updates\ExtensionModel

Class aliases have been established for TYPO3 v13, which will be removed with TYPO3 v14.

Impact

Extensions that extend AbstractDownloadExtensionUpdate and then most likely use ExtensionModel as well, should update the namespace.

Affected installations

Few instances should be affected: There are a couple of extensions that try to extend the upgrade range from two major Core versions, and ship older upgrade wizards. Apart from that, the abstract is most likely rarely used. Consuming extensions should adapt the namespace, the old class names will stop working with TYPO3 v14.

The extension scanner will find usages as strong match.

Migration

Adapt the namespaces in extension classes that extend AbstractDownloadExtensionUpdate from \TYPO3\CMS\Install to \TYPO3\CMS\Extensionmanager.

Important: #102130 - Optimizing T3D Import/Export module

See forge#102130

Description

The Import/Export backend module is based on code that is very hard to maintain, and is tightly coupled with both the DataHandler and RefIndex routines.

To improve maintainability and reliability in these areas, parts of the Import/Export module need to be refactored as an ongoing effort.

The documentation of the Import/Export module now addresses these challenges.

The ongoing refactoring may affect importing dumps from older versions of TYPO3. Importing these dumps may lead to missing data, also due to changes made longer ago (i.e. the pages_language_overlay table no longer exists, changes in workspaces, ...).

When performing an import of outdated TYPO3 versions, thoroughly check the generated warnings and errors, so that you can either recreate or upload missing data manually.

Important: #102402 - Extended Doctrine DBAL Platform classes

See forge#102402

Description

The Core needs to adapt some internal classes to prepare towards doctrine/dbal major version 4.x. The Doctrine team deprecated especially the Doctrine event manager, the Core used to populate custom adaptions.

The proposed way to mitigate the old events is to extend classes and integrate custom handling code directly. TYPO3 thus extends a couple of classes and replaces them using a factory.

Affected code is marked @internal. Extension author must not rely on the TYPO3 class names for instanceof checks and should check using the original Doctrine classes instead.

For example, doctrine/dbal has the following inheritance chain:

<?php

class MySQL80Platform extends MySQL57Platform {}
class MySQL57Platform extends MySQLPlatform {}
class MySQLPlatform extends AbstractMySQLPlatform {}
class AbstractMySQLPlatform extends AbstractPlatform {}
Copied!

TYPO3 now extends the concrete platform classes:

  • \TYPO3\CMS\Core\Database\Platform\MySQL80Platform extends \Doctrine\DBAL\Platforms\MySQL80Platform
  • \TYPO3\CMS\Core\Database\Platform\MySQL57Platform extends \Doctrine\DBAL\Platforms\MySQL57Platform
  • \TYPO3\CMS\Core\Database\Platform\MySQLPlatform extends \Doctrine\DBAL\Platforms\MySQLPlatform

The TYPO3 Core classes are only used as top layer, for example:

  1. \TYPO3\CMS\Core\Database\Platform\MySQL80Platform extends \Doctrine\DBAL\Platforms\MySQL80Platform
  2. \Doctrine\DBAL\Platforms\MySQL80Platform extends \Doctrine\DBAL\Platforms\MySQL57Platform
  3. \Doctrine\DBAL\Platforms\MySQL57Platform extends \Doctrine\DBAL\Platforms\MySQLPlatform
  4. \Doctrine\DBAL\Platforms\MySQLPlatform extends \Doctrine\DBAL\Platforms\AbstractMySQLPlatform
  5. \Doctrine\DBAL\Platforms\AbstractMySQLPlatform extends \Doctrine\DBAL\Platforms\AbstractPlatform

Custom extension code that needs to implement instanceof checks for specific platforms should use the Doctrine classes and not the TYPO3 Core classes, for example:

<?php

use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
use TYPO3\CMS\Core\Database\Platform\MySQL80Platform as Typo3MySQL80Platform;

// Usually incoming from elsewhere, eg. DI.
$platform = new Typo3MySQL80Platform();

$check = $platform instanceof DoctrineMySQLPlatform();
Copied!

Important: #102551 - FlexForm section _TOGGLE control removed

See forge#102551

Description

Historically, FlexForms store their section collapsing state within the flex structure in the database, having the impact that the state is reflected to every backend user. The control field _TOGGLE responsible for this behavior is now removed, the state is persisted in the backend user's local storage instead.

It is highly recommended to clean existing FlexForm records by invoking the following command:

bin/typo3 cleanup:flexforms
Copied!

If this is not possible, a scheduler task of type Execute console commands with the command cleanup:flexforms: Clean up database FlexForm fields that do not match the chosen data structure. may be set up and used.

Important: #102786 - Updated dependency: Symfony 7

See forge#102786

Description

TYPO3 v13 ships with Symfony components with at least version 7.0. Next to new features and bugfixes to be released in future Symfony 7 versions, the security support for Symfony 7 LTS reaches end-of-life in November 2028, allowing TYPO3 Core to run a stable and secure Symfony component library underneath.

Custom extensions relying on older versions of Symfony components need to adapt, see upgrade guides for Symfony on how to migrate.

Important: #102875 - Updated Dependency: Doctrine DBAL 4

See forge#102875

Description

TYPO3 v13 ships with Doctrine DBAL with at least version 4.0.

TYPO3 extends some Doctrine DBAL classes, enriching behaviour and provide these as public API surface, for example Connection, QueryBuilder and the ExpressionBuilder and are most likely used by extensions. Minor signature changes and removed methods are not mitigated and passed as breaking changes.

Custom extensions using low-level Doctrine DBAL API and functionality directly need to adapt, consult the upgrade guides for Doctrine DBAL on how to migrate.

See Doctrine DBAL 4.0 Upgrade Guide and Doctrine DBAL 3.8 Upgrade Guide for further information about Doctrine DBAL API changes and how to mitigate them.

Important: #102960 - TCA and system tables on one DB connection

See forge#102960

Description

TYPO3 v13 expects all database core system tables and especially all tables from extensions that have TCA attached to be configured for the main Default connection.

The TYPO3 core historically allowed configuration of any database table to point to additional configured database connections. This technically allows "ripping off" any table from the default connection table set, and have it on a different database.

TYPO3 now needs to restrict this a bit more to unblock further development and performance improvements: The core now declares that all "main" core tables (especially sys_*, pages, tt_content and in general all tables that have TCA) must not be declared for any connection other than the configured Default connection.

The reasons for this are actually pretty obvious: When looking at performance issues of bigger instances, the sheer amount of queries is usually the top-one bottleneck. The core aims to reduce this mid-term using more clever queries that join and prepare more data in fewer queries. Cross database joins are pretty much impossible.

This restriction has practically been the case with earlier core versions already: For instance when a TCA table configured "categories" and used them, the core already uses various joins to find categories attached to a record. Other places have been adapted with TYPO3 v13 already, for instance the ReferenceIndex. The core will try to additionally simplify the current API by avoiding getConnectionForTable() with further patches.

Apart from this, instances can still configure additional database connections. One target is directly querying data from some third party application in some custom extension. Another use case are database based caches: Those will of course never execute queries to join non-cache related data. A typical use is configuring a special database server for speed over integrity and persistence (for instance RAM driven) to power the "page" cache tables. This will continue to work, but might be turned into a dedicated feature of specific database backends, later.

13.x Changes by type

This lists all changes to the TYPO3 Core of minor versions grouped by their type.

Table of contents

Breaking Changes

Features

Deprecations

Important notes

ChangeLog v12

Every change to the TYPO3 Core which might affect your site is documented here.

Also available

12.4.x Changes

Table of contents

Breaking Changes

None since TYPO3 v12.4.0 LTS release.

Features

None since TYPO3 v12.4.0 LTS release.

Deprecation

Important

Deprecation: #102099 - Deprecate CKEditor5 bundle module

See forge#102099

Description

With the CKEditor5 integration in TYPO3 v12 a custom CKEditor5 build in form of a bundle has been introduced. Missing plugins had to be merged into that bundle again and again which lead to an increased bundle size. Also plugin authors had to reference the bundle module in order to fetch plugin exports from CKEditor.

With CKEditor5 suggestion to use named exports from the CKEditor5 package entry point modules, it became feasible to create smaller bundles. One bundle per scoped subpackage. For that reason @typo3/ckeditor5-bundle.js is now deprecated.

Impact

TYPO3 can ship all available CKEditor5 modules and only actually requested modules are loaded. Developers can write plugins as suggested by upstream documentation.

Affected Installations

Installations having custom extensions activated, that provide custom CKEditor5 plugins. Extensions that use @typo3/ckeditor5-bundle.js will still work as before (as the bundle module re-exports the exports of the split bundles) but will trigger a deprecation log message to the browser console.

Migration

Extension authors should import from scoped @ckeditor/ckeditor5-* packages directly.

// Before
import {Core, UI} from '@typo3/ckeditor5-bundle.js';

// After
import * as Core from '@ckeditor/ckeditor5-core';
import * as UI from '@ckeditor/ckeditor5-ui';
Copied!

Important: #96218 - Use proper surrounding "html" tags for Fluid SystemEmail

See forge#96218

Description

Due to usage of data-namespace-typo3-fluid="true" in the <html> declaration of the file EXT:core/Resources/Private/Layouts/SystemEmail.html, the whole <html>..</html> structure is removed from a sent HTML mail.

Validation and possibly utilities like SpamAssassin may fail or negatively score these mails due to these tags being missing.

Since the xmlns declaration of the ViewHelpers is semantically not wrong, it can actually be included in the email by removing the data-namespace-typo3-fluid attribute, instead of requiring the alternate more intrusive Fluid ViewHelper declaration.

Affected installations

All setups with customizations of the file EXT:core/Resources/Private/Layouts/SystemEmail.html for sending FluidEmails.

Migration

Adjust custom copies of the file EXT:core/Resources/Private/Layouts/SystemEmail.html like this:

Before (EXT:your_extension/Resources/Private/Layouts/SystemEmail.html)
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:v="urn:schemas-microsoft-com:vml"
      xmlns:o="urn:schemas-microsoft-com:office:office"
      xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
      data-namespace-typo3-fluid="true">
Copied!

into this:

After (EXT:your_extension/Resources/Private/Layouts/SystemEmail.html)
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:v="urn:schemas-microsoft-com:vml"
      xmlns:o="urn:schemas-microsoft-com:office:office"
      xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers">
Copied!

Important: #99781 - Exporting and downloading records in the list module

See forge#99781

Description

There are two different options for exporting records in the Web->List module.

One is using the export functionality, which is provided by EXT:impexp and is available via the "Export" docheader button in the single table view. It is possible to manage the display of the button using the Page TSconfig mod.web_list.noExportRecordsLinks option. However, the export functionality is by default disabled for non-admin users, making the button not showing up unless the functionality is explicitly enabled for the user with the user TSconfig options.impexp.enableExportForNonAdminUser option.

The "Download" functionality is available via the "Download" button in each tables header row. It is available in both, the list and also the single table view and can be managed using the Page TSconfig mod.web_list.displayRecordDownload option, which is enabled by default. Next to the general option is it also possible to set this option on a per-table basis using the mod.web_list.table.<tablename>.displayRecordDownload option. In case this option is set, it takes precedence over the general option.

# Page TSconfig
mod.web_list {
    # Disable "Export" button in docheader
    noExportRecordsLinks = 1

    # Generally disable "Download" button
    displayRecordDownload = 0

    # Enable "Download" button for table "tt_content"
    table.tt_content.displayRecordDownload = 1
}
Copied!

Important: #100847 - Added font plugin to CKEditor5

See forge#100847

Description

The font plugin has been added to the CKEditor5. In order to use the font plugin, the RTE configuration needs to be adapted:

editor:
  config:
    toolbar:
      items:
        # add button to select font family
        - fontFamily
        # add button to select font size
        - fontSize
        # add button to select font color
        - fontColor
        # add button to select font background color
        - fontBackgroundColor

    fontColor:
      colors:
        - { label: 'Orange', color: '#ff8700' }
        - { label: 'Blue', color: '#0080c9' }
        - { label: 'Green', color: '#209d44' }

    fontBackgroundColor:
      colors:
        - { label: 'Stage orange light', color: '#fab85c' }

    fontFamily:
      options:
        - 'default'
        - 'Arial, sans-serif'

    fontSize:
      options:
        - 'default'
        - 18
        - 21

    importModules:
      - { 'module': '@ckeditor/ckeditor5-font', 'exports': ['Font'] }
Copied!

More information can be found in the official documentation.

Important: #100889 - Allow insecure site resolution by query parameters

See forge#100889

Description

Resolving sites by the id and L HTTP query parameters is now denied by default. However, it is still allowed to resolve a particular page by, for example, "example.org" - as long as the page ID 123 is in the scope of the site configured for the base URL "example.org".

The new feature flag security.frontend.allowInsecureSiteResolutionByQueryParameters - which is disabled per default - can be used to reactivate the previous behavior:

$GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.frontend.allowInsecureSiteResolutionByQueryParameters'] = true;
Copied!

Impact

Resolving a page via query parameters is now restricted to the specific site where the page is located.

Affected installations

Installations which resolve pages from one domain via another domain.

Important: #100925 - Use dedicated cache for database schema information

See forge#100925

Description

To implement native JSON database field and TCA type=json support for TYPO3 v12 the need to cache the database schema information raised due to performance reason.

Using the core cache for schema information comes with various drawbacks:

  1. There is no way to flush single core cache entries, thus the complete core cache needs to be flushed when changing the database schema.
  2. The PHP Frontend provides no benefit, when the to be cached information has to be serialized anyway.

Therefore, a new cache is introduced that can be flushed individually after schema updates.

Additionally, some internal steps taken to mitigate some side effects are reverted. They are no longer needed with the dedicated cache.

Due to the nature of the chosen cache no database updates, configuration changes or other steps are needed.

Important: #101128 - CKEditor's highlight plugin introduces mark HTML tag

See forge#101128

Description

The introduction of the CKEditor plugin @ckeditor/ckeditor5-language allows an editor to use the mark tag, as well as the s tag.

It may become necessary to explicitly allow this tag in the lib.parseFunc_RTE TypoScript setup to allow the tag to be rendered properly in the frontend:

lib.parseFunc_RTE {
  allowTags := addToList(mark,s)
}
Copied!

Custom CSS styling for different markers classes needs to be implemented in a sitepackage for example, as no frontend CSS for this is emitted by default.

Important: #101567 - Use Symfony attribute to autoconfigure cli commands

See forge#101567

Description

The Symfony PHP attribute \Symfony\Component\Console\Attribute\AsCommand is now accepted to register console commands. This way CLI commands can be registered by setting the attribute on the command class. Only the parameters command, description, aliases and hidden are still viable. In order to overwrite the schedulable parameter use the old Services.yaml way to register console commands. By default schedulable is true.

Before:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\Command\MyCommand:
  tags:
    - name: 'console.command'
      command: 'myprefix:dofoo'
      description: 'My description'
      schedulable: true
    - name: 'console.command'
      command: 'myprefix:dofoo-alias'
      alias: true
Copied!

After:

The registration can be removed from the Services.yaml file and the attribute is assigned to the command class instead:

EXT:my_extension/Classes/Command/MyCommand.php
<?php

namespace MyVendor\MyExtension\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Attribute\AsCommand;

#[AsCommand(
    name: 'myprefix:dofoo',
    description: 'My description',
    aliases: ['myprefix:dofoo-alias']
)]
class MyCommand extends Command
{
}
Copied!

Impact

The registration of cli commands is simplified that way. When using this attribute there is no need to register the command in the Services.yaml file. Existing configurations work as before.

Important: #101580 - Introduce Content-Security-Policy-Report-Only handling

See forge#101580

Description

The feature flag security.frontend.reportContentSecurityPolicy ( $GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.frontend.reportContentSecurityPolicy'] ) can be used to apply the Content-Security-Policy-Report-Only HTTP header for frontend responses.

When both feature flags are activated, both headers are sent. You can deactivate one disposition in the site-specific configuration.

This allows to test and assess the potential impact on introducing Content-Security-Policy in the frontend - without actually blocking any functionality.

This behavior can be controlled on a site-specific scope as well, see Important: #104549 - Introduce site-specific Content-Security-Policy-Disposition.

Important: #101776 - Email validation in GeneralUtility::validEmail() now rejects spaces before "@"

See forge#101776

Description

The GeneralUtility::validEmail() method uses the package egulias/email-validator for validating emails. This library treats an email address like email @example.com with a space before the @ character as valid, but issues a warning, which has previously not been caught by TYPO3. Warnings like these are defined as "deviations from the RFC that in a broader interpretation are accepted."

In the context of TYPO3, such non-RFC email address shall be rejected. Thus, this specific warning ( CFWSNearAt) will now be caught, and the warning is turned into an invalidation of the given email address.

This will have the effect, that if integrators previously accepted email addresses formatted like these, validation will now fail (as the RFC implies).

Important: #102314 - Add title argument to IconViewhelper

See forge#102314

Description

The IconViewhelper in EXT:core has been extended with a new argument title. The new argument allows to set a corresponding title, which will be rendered as title attribute in the icon HTML markup. The title attribute will only be rendered, if explicitly passed. You can also pass an empty string.

This title attribute will improve accessibility, since screenreaders can choose not to ignore aria-hidden elements (e.g. the icons above the page tree), which is a mode people with low visibility might choose. If a title attribute is missing, a purely technical output will be given, which is very hard to make sense of.

Example

<core:icon title="Open actions menu" identifier="actions-menu" />
Copied!

This will be rendered as:

<span
    title="Open actions menu"
    class="t3js-icon icon icon-size-small icon-state-default icon-actions-menu"
    data-identifier="actions-menu" aria-hidden="true"
>
    <span class="icon-markup">
        <img
            src="/typo3/sysext/core/Resources/Public/Icons/T3Icons/actions/actions-menu.svg"
            width="16"
            height="16"
        >
    </span>
</span>
Copied!

Important: #102507 - Default CKEditor5 allowed classes and data attributes configurated reverted

See forge#102507

Description

With TYPO3 v12.4.7 (see forge#99738) an option to allow all classes in CKEditor5 has been enabled in the TYPO3 default configuration which implicitly caused all custom html elements to be allowed. This rule has now been dropped from the default configuration:

editor:
  config:
    htmlSupport:
      allow:
        - { classes: true, attributes: { pattern: 'data-.+' } }
Copied!

The configuration matched to any HTML element available in the CKEditor5 General HTML Support (GHS) schema definition. This became an issue, since CKEditor5 relies on the set of allowed elements and classes when processing content that is pasted from Microsoft Office.

Installations that relied on the fact that v12.4.7 allowed all CSS classes in CKEditor5 should encode the set of available style definitions via editor.config.style.definitions which will make them accessible to editors via the style dropdown toolbar element:

editor:
  config:
    style:
      definitions:
        - { name: "Descriptive Label", element: "p", classes: ['my-class'] }
Copied!

Custom data attributes can be allowed via General HTML Support:

editor:
  config:
    htmlSupport:
      allow:
        - { name: 'div', attributes: ['data-foobar'] }
Copied!

Important: #102904 - Use TCA group field as foreign selector

See forge#102904

Description

When using TCA type inline, developers have the possibility to use the "foreign selector" feature by defining the foreign_selector option, pointing to a field on the foreign (child) table. This way, editors can use the corresponding selector field to choose existing child records, to create a new inline relation. This can be further extended, using the useCombination appearance option, which allows to modify the child record via the parent record globally.

The field referenced in foreign_selector is usually a field with TCA type select, using the foreign_table option itself to provide the corresponding items to choose.

It's nevertheless also possible to use a TCA type group field as foreign_selector. In this case, the child records have to be selected from the table, defined via the allowed option. For this use case, only one table can be defined. This means, the first table name in allowed is taken, no matter if there are multiple table names defined.

Example using an intermediate table and the useCombination feature:

// Inline field in parent table "tx_extension_inline_usecombination"
'inline' => [
    'label' => 'inline',
    'config' => [
        'type' => 'inline',
        'foreign_table' => 'tx_extension_inline_usecombination_mm',  // Referencing the intermediate table
        'foreign_field' => 'group_parent',
        'foreign_selector' => 'group_child',
        'foreign_unique' => 'group_child',
        'appearance' => [
            'useCombination' => true,
        ],
    ],
],

// Reference fields in intermediate table "tx_extension_inline_usecombination_mm"
'group_parent' => [
    'label' => 'group parent',
    'config' => [
        'type' => 'select',
        'renderType' => 'selectSingle',
        'foreign_table' => 'tx_extension_inline_usecombination', // Referencing the parent table
    ],
],
'group_child' => [
    'label' => 'group child',
    'config' => [
        'type' => 'group',
        'allowed' => 'tx_extension_inline_usecombination_child', // Referencing the child table
        'foreign_table' => 'tx_extension_inline_usecombination_child', // ONLY USED FOR extbase!
    ],
],

// Child table "tx_extension_inline_usecombination_child" does not have any relation fields
Copied!

Important: #103392 - Form framework select markup changed

See forge#103392

Description

With forge#103117, the elementClassAttribute of the "SingleSelect", "CountrySelect" and "MultiSelect" fields got changed from form-control to form-select in EXT:form, as defined by Bootstrap, if the Bootstrap 5 markup ( templateVariant: version2) is used.

If needed, the old markup can be restored by overriding the configuration as follows:

prototypes:
  standard:
    formElementsDefinition:
      CountrySelect:
        variants:
          -
            identifier: template-variant
            properties:
              elementClassAttribute: form-control
      MultiSelect:
        variants:
          -
            identifier: template-variant
            properties:
              elementClassAttribute: form-control
      SingleSelect:
        variants:
          -
            identifier: template-variant
            properties:
              elementClassAttribute: form-control
Copied!

Important: #103496 - ISO format used for date rendering

See forge#103496

Description

The default format for date rendering configured in $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] has changed.

The former arbitrary 'd-m-y' format was replaced with the standard ISO 8601 'Y-m-d' format.

Examples of dates where the 'd-m-y' format led to unclear dates:

  • A 2-digit year could also be a day in a month: 21-04-23 could be understood as 2021-04-23 instead of 2023-04-21.
  • The century of years could not be distinguished: 21-04-71 could be 2071-04-21 or 1971-04-21

This affects date display in various locations so code relying on the previous format (e.g. acceptance tests) must be updated accordingly.

Important: #104549 - Introduce site-specific Content-Security-Policy-Disposition

See forge#104549

Description

The feature flags $GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.frontend.enforceContentSecurityPolicy'] and $GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.frontend.reportContentSecurityPolicy'] apply Content-Security-Policy headers to any frontend site. The dedicated sites/<my-site>/csp.yaml can now be used as alternative to declare the desired disposition of Content-Security-Policy and Content-Security-Policy-Report-Only individually.

It now is also possible, to apply both Content-Security-Policy and Content-Security-Policy-Report-Only HTTP headers at the same time with different directives for a particular site. Besides that it is possible to disable the disposition completely for a site.

The following new configuration schemes were introduced for sites/<my-site>/csp.yaml:

  • active (false) for disabling CSP for a particular site, which overrules any other setting for enforce or report
  • enforce (bool|disposition-array) for compiling the Content-Security-Policy HTTP header
  • report (bool|disposition-array) for compiling the Content-Security-Policy-Report-Only HTTP header

The disposition-array for enforce and report allows these properties:

  • inheritDefault (bool) inherits default site-unspecific frontend policy mutations (true per default)
  • includeResolutions (bool) includes dynamic resolutions, as persisted in the database via backend module (true per default)
  • mutations (mutation-item-array) defines additional directive mutations to be applied to the specific site
  • packages (package-item-array) defines packages/extensions whose static CSP mutations shall be dropped or included

Example: Disable Content-Security-Policy

The following example would completely disable CSP for a particular site.

config/sites/<my-site>/csp.yaml
# `active` is enabled per default if omitted
active: false
Copied!

Example: Use report disposition

The following example would dispose only Content-Security-Policy-Report-Only for a particular site (since the enforce property is not given).

config/sites/<my-site>/csp.yaml
report:
  # `inheritDefault` is enabled per default if omitted
  inheritDefault: true
  mutations:
   - mode: extend
     directive: img-src
     sources:
      - https://*.typo3.org
Copied!

The following example is equivalent to the previous, but shows that the legacy configuration (having inheritDefault and mutations on the top-level) is still supported.

The effective HTTP headers would then be resolved from the active feature flags security.frontend.enforceContentSecurityPolicy and security.frontend.reportContentSecurityPolicy - in case both flags are active, both HTTP headers Content-Security-Policy and Content-Security-Policy-Read-Only would be used.

config/sites/<my-site>/csp.yaml
# `inheritDefault` is enabled per default if omitted
inheritDefault: true
mutations:
 - mode: extend
   directive: img-src
   sources:
    - https://*.typo3.org
Copied!

Example: Use enforce and report dispositions at the same time

The following example would dispose Content-Security-Policy (enforce) and Content-Security-Policy-Report-Only (report) for a particular site.

This allows to test new CSP directives in the frontend - the example drops the static CSP directives of the package my-vendor/my-package in the enforced disposition and only applies it to the reporting disposition.

config/sites/<my-site>/csp.yaml
enforce:
  # `inheritDefault` is enabled per default if omitted
  inheritDefault: true
  # `includeResolutions` is enabled per default if omitted
  includeResolutions: true
  mutations:
    - mode: extend
      directive: img-src
      sources:
        - https://*.typo3.org
  packages:
    # all (`*`) packages shall be included (`true`)
    '*': true
    # the package `my-vendor/my-package` shall be dropped (`false`)
    my-vendor/my-package: false

report:
  # `inheritDefault` is enabled per default if omitted
  inheritDefault: true
  # `includeResolutions` is enabled per default if omitted
  includeResolutions: true
  mutations:
    - mode: extend
      directive: img-src
      sources:
        - https://*.my-vendor.example.org/
  # the `packages` section can be omitted in this case, since all packages
  # listed there shall be included - which is the default behavior in case
  # `packages` would not be configured
  packages:
    # all (`*`) packages shall be included (`true`)
    '*': true
    # the package `my-vendor/my-package` shall be included (`true`)
    my-vendor/my-package: true
Copied!

Important: #104693 - Setting allowLanguageSynchronization via columnsOverrides

See forge#104693

Description

Setting the TCA option allowLanguageSynchronization for a specific column in a record type via columnsOverrides is currently not supported by TYPO3 and therefore might lead to exceptions in the corresponding field wizard ( LocalizationStateSelector). To mitigate this, the option is now automatically removed from the TCA configuration via a TCA migration. A corresponding deprecation log entry is added to inform integrators about the necessary code adjustments.

Migration

Remove the allowLanguageSynchronization option from columnsOverrides for now.

// Before
'types' => [
    'text' => [
        'showitem' => 'header',
        'columnsOverrides' => [
            'header' => [
                'config' => [
                    'behaviour' => [
                        'allowLanguageSynchronization' => true
                    ],
                ],
            ],
        ],
    ],
],

// After
'types' => [
    'text' => [
        'showitem' => 'header',
    ],
],
Copied!

Important: #104827 - Allow to use Regular Expressions in CKEditor YAML

See forge#104827

Description

The CKEditor plugin can now be configured with YAML syntax utilizing Regular Expression objects for certain keys. By defining a Regular Expression, the CKEditor replacement/transformation functionality feature is now fully usable.

The CKEditor v5 configuration API allows to specify Regular Expression JavaScript objects, for example in editor.config.typing.transformations.extra.from or editor.config.htmlSupport.allow.name:

Example CKEditor JavaScript configuration excerpt
// part of `editor.config`
{
  typing: {
    transformations: {
      extra: {
        from: /(tsconf|t3ts)$/,
        to: 'TYPO3 TypoScript TSConfig'
      }
    }
  }
  htmlSupport: {
    allow: {
      name: /^(div|section|article)$/
    }
  }
}
Copied!

When TYPO3 passes YAML configuration of the CKEditor forward to JavaScript, it uses a html-entity encoded representation, which does not allow to utilize Regular Expression objects, and also the CKEditor API method buildQuotesRegExp() is not usable in this scenario.

This was remedied already for the configuration key htmlSupport with its sub-keys, so that when a YAML key named pattern was found, TYPO3 automatically converted that to a proper JavaScript Regular Expression:

Example YAML RTE configuration excerpt
editor:
  config:
    htmlSupport:
      allow:
        - { name: { pattern: '^(div|section|article)$', flags: '' } }
Copied!

This is now also possible for the editor.config.typing.transformations structure:

Example YAML RTE configuration excerpt
editor:
  config:
    typing:
      transformations:
        extra:
          - { from: { pattern: '(tsconf|t3ts)$', flags: '' }, to: 'TYPO3 TypoScript TSConfig' }
Copied!

This conversion of Regular Expressions must be explicitly applied to CKEditor configuration keys within the TYPO3 API, and cannot be used generally for every key.

Thus, using a pattern sub-key is currently applied only to the following configuration structures (and recursively their sub-structures):

  • editor.config.typing.transformations
  • editor.config.htmlSupport

Important: #104839 - RTE processing YAML configuration now respects removeTags

See forge#104839

Description

TYPO3 allows to configure which HTML tags are allowed to be persisted to the database in case of Richtext-elements. This can be configured either within the CKEditor YAML context, or via Page TSconfig:

EXT:rte_ckeditor/Configuration/RTE/Processing.yaml
processing:
  HTMLparser_db:
    # previous default: center, font, link, meta, o:p, sdfield,
    #                   style, title, strike, u
    removeTags: [link, meta, o:p, sdfield, style, title]
Copied!
EXT:my_extension/Configuration/TypoScript/page.tsconfig
RTE.default.proc {
  HTMLparser_db {
    removeTags: link, meta, o:p, sdfield, style, title
  }
}
Copied!

Due to a bug in interpreting the YAML configuration, the syntax using an array was actually never in effect.

This means, any implementation relying on such a YAML configuration (without providing Page TSconfig), would not have removed the listed tags.

Due to TYPO3's internal processing, from those tags listed above, the previous default tags center, font, strike and u were persisted to the database and also later evaluated in the frontend.

The other tags link, meta, o:p, sdfield, style and title were displayed as HTML encoded entities due to other sanitizing in the output (but still stored as HTML tags in the database).

These tags will no longer be stored by default now, and is considered a non-breaking bugfix, because these tags should not occur within an RTE.

This wrong parsing has now been fixed, so that now both an array syntax as well as string syntax is allowed in the YAML processing and will be applied. Adapting the removeTags setting allows to change the now applied defaults to any tag configuration needed.

Affected installations

TYPO3 setups with RTE YAML configurations utilizing either a custom removeTags processing directive or the default, defined via array notation instead of string.

Migration

Adjust the RTE YAML configuration processing directive removeTags to suit the expected tag removal, or accept the new defaults.

Important: #105856 - Allow site-specific Content-Security-Policy endpoints

See forge#105856

Description

The way Content-Security-Policy reporting endpoints are configured has been enhanced. Administrators can now disable the reporting endpoint globally or configure it per site as needed.

The global scope-specific setting contentSecurityPolicyReportingUrl can be set to zero ('0') to disable the CSP reporting endpoint:

  • [TYPO3_CONF_VARS][FE][contentSecurityPolicyReportingUrl] = '0'
  • [TYPO3_CONF_VARS][BE][contentSecurityPolicyReportingUrl] = '0'

Additionally, the behavior of the reporting endpoint can also be configured per site via sites/<my-site>/csp.yaml.

The new disposition-specific property reportingUrl can either be:

  • reportingUrl (true) to enable the reporting endpoint
  • reportingUrl (false) to disable the reporting endpoint
  • reportingUrl (string) to use the given value as external reporting endpoint

If defined, the site-specific configuration takes precedence over the global configuration.

In case the explicitly disabled endpoint still would be called, the server-side process responds with a 403 HTTP error message.

Example: Disabling the reporting endpoint

config/sites/<my-site>/csp.yaml
enforce:
  inheritDefault: true
  mutations: {}
  reportingUrl: false
Copied!

Example: Using custom external reporting endpoint

config/sites/<my-site>/csp.yaml
enforce:
  inheritDefault: true
  mutations: {}
  reportingUrl: https://example.org/csp-report
Copied!

12.4 Changes

Table of contents

Breaking Changes

None since TYPO3 v12.0 release.

Features

None since TYPO3 v12.3 release.

Deprecation

Important

Deprecation: #98093 - ext_icon.* as extension icon file location

See forge#98093

Description

Since forge#77349 it is possible to place the extension icon, which is displayed at various places in the backend, e.g. in the extension manager, in an extension's Resources/Public/Icons/ directory. The Resources/ directory is by convention the place to store such files. To simplify the extension registration and to fully follow the convention the following file locations have been deprecated:

  • ext_icon.png
  • ext_icon.svg
  • ext_icon.gif

Impact

Adding an extension icon using one of the mentioned file locations will raise a deprecation level log message and will stop working with TYPO3 v13.

Affected installations

TYPO3 installations with custom extensions using the deprecated file locations.

Migration

Place your extension icon as Extension.* into Resources/Public/Icons/, as described in Feature: #77349 - Additional locations for extension icons.

Deprecation: #99237 - MagicImageService

See forge#99237

Description

The class \TYPO3\CMS\Core\Resource\Service\MagicImageService, which was previously used for inline images by EXT:rtehtmlarea has been marked as deprecated, since its functionality is no longer needed for CKeditor.

Impact

Using \TYPO3\CMS\Core\Resource\Service\MagicImageService or one of its public methods will raise a deprecation level log message.

Affected installations

TYPO3 installations with custom extensions using the class or its public methods. The extension scanner will report usages as strong match.

Migration

There is no direct migration. In case you rely on any of the provided functionality, just copy the corresponding code into your custom extension.

Deprecation: #100173 - Various methods and properties in UserAuthentication classes now internal

See forge#100173

Description

Various methods and properties within the main classes regarding frontend user and backend user ( $GLOBALS[BE_USER]) authentication handling have been either marked as internal or have been deprecated for usage outside of the classes.

This is due to the further refactorings and decoupling work, as subclasses of AbstractUserAuthentication deal with many more functionality nowadays, and therefore have been moved to service classes. The tight coupling of these classes, for example, the database fields, or login form field names are now marked as internal, as these properties should not be modified from the outside scope.

Instead, functionality like PSR-14 events or Authentication Services should influence the authentication and authorization workflow.

The following properties and methods are now marked as internal in all user authentication related classes (extending \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication ):

  • lastLogin_column
  • formfield_uname
  • formfield_uident
  • formfield_status
  • loginSessionStarted
  • dontSetCookie
  • isSetSessionCookie()
  • isRefreshTimeBasedCookie()
  • removeCookie()
  • isCookieSet()
  • unpack_uc()
  • appendCookieToResponse()

Additionally, the following properties of the \TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication implementation are marked as internal:

  • formfield_permanent
  • is_permanent

Impact

The affected properties and methods have been marked as @internal and set to protected. With an additional trait, it is still possible to access them in TYPO3 v12. In case third-party extensions call them, a PHP deprecation warning is thrown.

Affected installations

TYPO3 installations with custom extensions accessing the properties or methods. The extension scanner reports corresponding places.

Migration

Depending on the specific requirements, it is recommended to use PSR-14 events or authentication services to modify behaviour of the authentication classes.

Deprecation: #100335 - TCA config MM_insert_fields

See forge#100335

Description

The TCA option MM_insert_fields has been marked as deprecated and should not be used anymore.

Impact

Using MM_insert_fields raises a deprecation level log message during TCA cache warmup. Its functionality is kept in TYPO3 v12 but will be removed in v13.

Affected installations

There may be extensions that use this option when configuring database MM relations. In most cases, the option can be removed. The migration section gives more details.

Migration

General scope: MM_insert_fields is used in combination with "true" database MM intermediate tables to allow many-to-many relations between two tables for group, select and sometimes even inline type fields.

A core example is the sys_category to tt_content relation, with sys_category_record_mm as intermediate table: The intermediate table has field uid_local (pointing to a uid of the "left" sys_category table), and uid_foreign (pointing to a uid of the "right" tt_content table). Note this specific relation also allows multiple different "right-side" table-field combinations, using the two additional fields tablenames and fieldname. All this is configured with TCA on the "left" and the "right" side table field, while table sys_category_record_mm has no TCA itself. Rows within the intermediate table are transparently handled by TYPO3 by the RelationHandler and extbase TCA-aware domain logic.

The MM_insert_fields now allows to configure a hard coded value for an additional column within the intermediate table. This is obsolete: There is no API to retrieve this value again, having a "stable" value in an additional column is useless. This config option should be removed from TCA definition.

Note on the related option MM_match_fields: This is important when an MM relation allows multiple "right" sides. In the example above, when a category is added to a tt_content record using the categories field, and when editing this relation from the "right" side (editing a tt_content record), then this option is used to select only relations for this tt_content.categories combination. The TCA column categories thus uses MM_match_fields to restrict the query. Note MM_match_fields is not set for the "left-side" sys_category items fields, this would indicate a TCA misconfiguration.

Various extensions in the wild did not get these details right, and often simply set both MM_insert_fields and MM_match_fields to the same values. Removing MM_insert_fields helps reducing confusion and simplifies this construct a bit. Affected extensions can simply remove the MM_insert_fields configuration and keep the MM_match_fields. Note the Core strives to further simplify these options and MM_match_fields may become fully obsolete in the future as well.

Deprecation: #100349 - TypoScript loginUser() and usergroup() conditions

See forge#100349

Description

The two TypoScript / TSconfig related condition functions [loginUser()] and [usergroup()] have been marked as deprecated with TYPO3 v12, should not be used anymore and will be removed in TYPO3 v13. They can be substituted using conditions based on the variables frontend.user and backend.user.

Impact

Using the old conditions in frontend TypoScript or TSconfig triggers a deprecation level log entry in TYPO3 v12 and will stop working with TYPO3 v13.

Affected installations

Instances with TypoScript or TSconfig using one of the above functions may be affected. This is a relatively common use case, but affected instances can be adapted quite easily.

Migration

There is a rather straightforward migration path. In general, switch to either frontend.user to test for frontend user state (available in frontend TypoScript), or to backend.user (available in frontend TypoScript and TSconfig).

Note the transition can be done in existing TYPO3 v11 projects already.

Some examples:

[loginUser('*')]
    page = PAGE
    page.20 = TEXT
    page.20.value = User is logged in<br />
[end]
[frontend.user.isLoggedIn]
    page = PAGE
    page.21 = TEXT
    page.21.value = User is logged in<br />
[end]

[loginUser('*') === false]
    page = PAGE
    page.30 = TEXT
    page.30.value = User is not logged in<br />
[end]
[!frontend.user.isLoggedIn]
    page = PAGE
    page.31 = TEXT
    page.31.value = User is not not logged in<br />
[end]

[loginUser(13)]
    page = PAGE
    page.40 = TEXT
    page.40.value = Frontend user has the uid 13<br />
[end]
[frontend.user.userId == 13]
    page = PAGE
    page.41 = TEXT
    page.41.value = Frontend user has the uid 13<br />
[end]

[loginUser('1,13')]
    page = PAGE
    page.50 = TEXT
    page.50.value = Frontend user uid is 1 or 13<br />
[end]
[frontend.user.userId in [1,13]]
    page = PAGE
    page.51 = TEXT
    page.51.value = Frontend user uid is 1 or 13<br />
[end]

[usergroup('*')]
    page = PAGE
    page.60 = TEXT
    page.60.value = A Frontend user is logged in and belongs to some usergroup.<br />
[end]
# Prefer [frontend.user.isLoggedIn] to not rely on magic array values.
[frontend.user.userGroupIds !== [0, -1]]
    page = PAGE
    page.61 = TEXT
    page.61.value = A Frontend user is logged in and belongs to some usergroup.<br />
[end]

[usergroup(11)]
    page = PAGE
    page.70 = TEXT
    page.70.value = Frontend user is member of group with uid 11<br />
[end]
[11 in frontend.user.userGroupIds]
    page = PAGE
    page.71 = TEXT
    page.71.value = Frontend user is member of group with uid 11<br />
[end]

[usergroup('1,11')]
    page = PAGE
    page.80 = TEXT
    page.80.value = Frontend user is member of group 1 or 11<br />
[end]
[1 in frontend.user.userGroupIds || 11 in frontend.user.userGroupIds]
    page = PAGE
    page.81 = TEXT
    page.81.value = Frontend user is member of group 1 or 11<br />
[end]
Copied!

Deprecation: #100355 - Deprecate methods in PasswordChangeEvent in ext:felogin

See forge#100355

Description

The following methods in the PSR-14 event PasswordChangeEvent of ext:felogin have been marked as deprecated and should not be used any more:

  • setAsInvalid()
  • getErrorMessage()
  • isPropagationStopped()
  • setHashedPassword()

Impact

Event listeners, who use one of the deprecated methods of the PasswordChangeEvent PSR-14 event, will raise a deprecation level log message. The functionality is kept in TYPO3 v12 but will be removed in v13.

Affected installations

Instances who use the PSR-14 event PasswordChangeEvent for password validation and who use one of the deprecated methods.

The extension scanner reports usages as a weak match.

Migration

Password validation for the password recovery functionality in ext:felogin must be implemented using a custom password policy validator.

See forge#97388 for details.

Deprecation: #100405 - Property TypoScriptFrontendController->type

See forge#100405

Description

The public property type of the main class in TYPO3 frontend \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController has been marked as internal, as it should not be used outside of this PHP class anymore in the future.

This is part of the overall part to reduce dependencies on this PHP class, as it is not always available in TYPO3 frontend.

Impact

Accessing this property will trigger a PHP deprecation notice. Accessing this property might also happen via TypoScript and TypoScript conditions.

Affected installations

TYPO3 installations using this property on checking various typeNum settings from TypoScript.

Migration

When using this property in PHP code via $GLOBALS['TSFE']->type , it is recommended to move to the PSR-7 request via $request->getAttribute('routing')->getPageType(), which is the property of the PageArguments object, as a result of the GET parameter type, or $GLOBALS['TSFE']->getPageArguments()->getPageType() if the request object is not available.

Within TypoScript, conditions and getData properties need to be adapted:

# Before
[getTSFE() && getTSFE().type == 13]

# After
[request.getPageArguments()?.getPageType() == 13]
Copied!

In TypoScript getData attributes:

# Before
page.10.data = TSFE:type

# After
page.10.data = request:routing|pageType
Copied!

Deprecation: #100454 - Legacy tree implementations

See forge#100454

Description

Due to many refactorings in TYPO3's tree implementations in the past versions, many implementations and functionality of the legacy rendering \TYPO3\CMS\Backend\Tree\AbstractTreeView is not needed anymore.

The following PHP classes are not in use anymore and have been marked as deprecated:

  • \TYPO3\CMS\Backend\Tree\View\BrowseTreeView
  • \TYPO3\CMS\Backend\Tree\View\ElementBrowserPageTreeView

The base class is still available, but discouraged to be used or extended, even though TYPO3 still uses this in a few places.

The following properties and methods within the base class AbstractTreeView have either been marked as deprecated or declared as internal:

  • AbstractTreeView->thisScript
  • AbstractTreeView->BE_USER
  • AbstractTreeView->clause
  • AbstractTreeView->title
  • AbstractTreeView->table
  • AbstractTreeView->parentField
  • AbstractTreeView->orderByFields
  • AbstractTreeView->fieldArray
  • AbstractTreeView->defaultList
  • AbstractTreeView->determineScriptUrl()
  • AbstractTreeView->getThisScript()
  • AbstractTreeView->PM_ATagWrap()
  • AbstractTreeView->addTagAttributes()
  • AbstractTreeView->getRootIcon()
  • AbstractTreeView->getIcon()
  • AbstractTreeView->getRootRecord()
  • AbstractTreeView->getTitleStr()
  • AbstractTreeView->getTitleAttrib()

Impact

Instantiating the deprecated classes or calling the deprecated methods will trigger a PHP deprecation warning, except for AbstractTreeView->getThisScript(), which is still used internally by deprecated code.

The Extension Scanner will find those usages and additionally also reports usages of the corresponding public properties of the AbstractTreeView class.

Affected installations

TYPO3 installations with custom extensions using this functionality. This is usually the case for old installations from TYPO3 v6 or TYPO3 v4 times.

Migration

It is recommended to avoid generating the markup directly in PHP. Instead use one of various other tree functionalities (for example, see PageTree implementations) in PHP and render trees via web components or Fluid.

Deprecation: #100459 - BackendUtility::getRecordToolTip()

See forge#100459

Description

The method \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordToolTip() has been marked as deprecated.

Impact

Calling this method will trigger a PHP deprecation warning.

Affected installations

TYPO3 installations with custom extensions using this method. This is usually the case for old installations where Fluid templates or Extbase backend modules were not common.

Migration

As this method is just a wrapper around BackendUtility::getRecordIconAltText() with a "title" attribute for the markup, the replacement is straightforward:

Before:

$link = '<a href="..." ' . BackendUtility::getRecordToolTip(...) . '>my link</a>';
Copied!

After:

$link = '<a href="..." title="' . BackendUtility::getRecordIconAltText(...) . '">my link</a>';
Copied!

Deprecation: #100461 - TypoScript option config.xhtmlDoctype

See forge#100461

Description

The TypoScript option config.xhtmlDoctype has been marked as deprecated. This is done in order to consolidate TypoScript options, as the option config.doctype is now the default.

Impact

Having config.xhtmlDoctype set, but not config.doctype will trigger a TypoScript deprecation warning.

Affected installations

TYPO3 installations having this TypoScript instruction set.

Migration

If the property config.xhtmlDoctype is set, replace it with config.doctype.

Deprecation: #100577 - FormEngine needs request object

See forge#100577

Description

The backend FormEngine construct (editing records in the backend) now expects the current ServerRequestInterface object to be hand over as initial data.

Impact

Backend modules that use the FormEngine data provider construct to render records should provide the current request object. Failing to do so will trigger a deprecation level log message and the system will fall back to $GLOBALS['TYPO3_REQUEST']. This will stop working with TYPO3 v13.

Affected installations

Instances with extensions that provide custom modules using the FormEngine construct are affected. This is a relatively seldom case.

Migration

Provide the request object as "initial data" when using the FormDataCompiler:

$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $myFormDataGroup);
$formDataCompilerInput = [
    'request' => $request,
    // further data, for example:
    'tableName' => $table,
    'vanillaUid' => $uid,
];
$formData = $formDataCompiler->compile($formDataCompilerInput);
Copied!

Deprecation: #100581 - Avoid constructor argument in FormDataCompiler

See forge#100581

Description

When instantiating the backend FormEngine related FormDataCompiler, the constructor argument FormDataGroupInterface should be omitted, the form data group should be provided as second argument to compile() instead.

Impact

Handing over the form data group as second argument to compile() allows injecting FormDataCompiler into controllers with TYPO3 v13 since the manual constructor argument will be removed.

Affected installations

Instances with own backend modules that use FormEngine to render records may be affected. Handing over the form data group as constructor argument to FormDataCompiler will trigger a deprecation level log warning with TYPO3 v12. With TYPO3 v13, the form data group must be provided as second argument to compile() and will not be optional anymore.

Migration

// before
$formDataCompiler = GeneralUtility::makeInstance(
    FormDataCompiler::class, GeneralUtility::makeInstance(MyDataGroup::class)
);
$formData = $formDataCompiler->compile($myFormDataCompilerInput);

// after
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class);
$formData = $formDataCompiler->compile(
    $myFormDataCompilerInput,
    GeneralUtility::makeInstance(MyDataGroup::class)
);
Copied!

Deprecation: #100584 - GeneralUtility::linkThisScript()

See forge#100584

Description

The method \TYPO3\CMS\Core\Utility\GeneralUtility::linkThisScript() has been marked as deprecated and should not be used any longer.

The method uses the super global $_GET which should be avoided. Instead, data should be retrieved via the PSR-7 ServerRequestInterface.

Controllers should typically create URLs using the \TYPO3\CMS\Backend\Routing\UriBuilder .

Impact

Using the method triggers a deprecation level log entry in TYPO3 v12, the method will be removed with TYPO3 v13.

Affected installations

The method was typically used in backend context: Extensions with own backend modules may be affected. The extension scanner finds usages with a strong match.

Migration

linkThisScript() was typically used when a link to some view is created that should return back to the current view later.

Controllers usually "know" the route a view should return to and the relevant GET parameters.

A transition could look like this:

$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$queryParams = $request->getQueryParams();
$url = $uriBuilder->buildUriFromRoute(
    'my_route',
    [
        'table' => $queryParams['table'] ?? '',
        'uid' => (int)($queryParams['uid'] ?? 0),
    ]
);
Copied!

Deprecation: #100587 - Deprecate form engine additionalJavaScriptPost and custom eval inline JavaScript

See forge#100587

Description

The result property additionalJavaScriptPost of the form engine result array is deprecated. It was used, for instance, in custom eval definitions, that provided inline JavaScript (configured via $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'] ).

Impact

Custom form engine components that assign the result property additionalJavaScriptPost, or custom eval class implementations for method returnFieldJS() that return a plain string (which is used as inline JavaScript), will raise a deprecation level log message.

Affected installations

Installations that use custom form engine components modifying the result array, or custom eval class implementations for method returnFieldJS() returning a plain string.

Migration

Instead of using inline JavaScript, functionality has to be bundled in a static JavaScript module. Custom eval class implementations for method returnFieldJS() have to return an instance of \TYPO3\CMS\Core\Page\JavaScriptModuleInstruction instead of a plain string.

Example

Deprecated custom eval implementation:

<?php
namespace TYPO3\CMS\Redirects\Evaluation;

class SourceHost
{
    public function returnFieldJS(): string
    {
        $jsCode = [];
        $jsCode[] = 'if (value === \'*\') {return value;}';
        $jsCode[] = 'var parser = document.createElement(\'a\');';
        $jsCode[] = 'parser.href = value.indexOf(\'://\') != -1 ? value : \'http://\' + value;';
        $jsCode[] = 'return parser.host;';
        return implode(' ', $jsCode);
    }
}
Copied!

Migrated custom eval implementation (JavaScript is now bundled in module @typo3/redirects/form-engine-evaluation.js):

<?php
namespace TYPO3\CMS\Redirects\Evaluation;

class SourceHost
{
    public function returnFieldJS(): JavaScriptModuleInstruction
    {
        return JavaScriptModuleInstruction::create(
            '@typo3/redirects/form-engine-evaluation.js',
            'FormEngineEvaluation'
        );
    }
}
Copied!

Deprecation: #100596 - GeneralUtility::_GET()

See forge#100596

Description

The method \TYPO3\CMS\Core\Utility\GeneralUtility::_GET() has been marked as deprecated and should not be used any longer.

Modern code should access GET and POST data from the PSR-7 ServerRequestInterface, and should avoid accessing superglobals $_GET directly. This also avoids future side-effects when using sub-requests. Some GeneralUtility related helper methods like _GET() violate this, using them is considered a technical debt. They are being phased out.

Impact

Calling the method from PHP code will log a PHP deprecation level entry, the method will be removed with TYPO3 v13.

Affected installations

TYPO3 installations with third-party extensions using GeneralUtility::_GET() are affected, typically in TYPO3 installations which have been migrated to the latest TYPO3 Core versions and haven't been adapted properly yet.

The extension scanner will find usages with a strong match.

Migration

GeneralUtility::_GET() is a helper method that retrieves incoming HTTP GET query arguments and returns the value.

The same result can be achieved by retrieving arguments from the request object. An instance of the PSR-7 ServerRequestInterface is handed over to controllers by TYPO3 Core's PSR-15 \TYPO3\CMS\Core\Http\RequestHandlerInterface and middleware implementations, and is available in various related scopes like the frontend \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer .

Typical code:

use TYPO3\CMS\Core\Utility\GeneralUtility;

// Before
$value = GeneralUtility::_GET('tx_scheduler');

// After
$value = $request->getQueryParams()['tx_scheduler'] ?? null;
Copied!

Deprecation: #100597 - BackendUtility methods getThumbnailUrl() and getLinkToDataHandlerAction()

See forge#100597

Description

The methods \TYPO3\CMS\Backend\Utility\BackendUtility::getThumbnailUrl() and \TYPO3\CMS\Backend\Utility\BackendUtility::getLinkToDataHandlerAction() have been marked as deprecated.

Impact

Calling those methods will trigger a PHP deprecation level log warning.

Affected installations

TYPO3 installations with custom extensions using those methods. The extension scanner will report usages as strong match.

Migration

Instead of calling BackendUtility::getThumbnailUrl(), inject and use the \TYPO3\CMS\Core\Resource\ResourceFactory directly:

// before
$url = BackendUtility::getThumbnailUrl(2004, [
    'width' => 20,
    'height' => 13,
    '_context' => ProcessedFile::CONTEXT_IMAGEPREVIEW
]);

// after
$url = $this->resourceFactory
    ->getFileObject(2004)
    ->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, ['width' => 20, 'height' => 13])
    ->getPublicUrl();
Copied!

Instead of calling BackendUtility::getLinkToDataHandlerAction(), inject and use the \TYPO3\CMS\Backend\Routing\UriBuilder directly:

// before
$url = BackendUtility::getLinkToDataHandlerAction(
    '&cmd[pages][123][localize]=10',
    (string)$uriBuilder->buildUriFromRoute('some_route')
);

// after
$url = (string)$this->uriBuilder->buildUriFromRoute(
    'tce_db',
    [
        'cmd' => [
            'pages' => [
                123 => [
                    'localize' => 10,
                ],
            ],
        ],
        'redirect' => (string)$uriBuilder->buildUriFromRoute('some_route'),
    ]
);
Copied!

In case the second paramter $redirectUrl was omitted, getLinkToDataHandlerAction automatically used the current request URI as the return URL. In case you relied on this, make sure the redirect parameter is set to $request->getAttribute('normalizedParams')->getRequestUri().

Deprecation: #100614 - Deprecate PageRenderer::$inlineJavascriptWrap and $inlineCssWrap

See forge#100614

Description

The protected properties $inlineJavascriptWrap and $inlineCssWrap of the class \TYPO3\CMS\Core\Page\PageRenderer have been deprecated and shall not be used any longer.

Impact

PageRenderer specifics concerning rendering XHTML or non-HTML5 content are not working any longer in affected installations having custom code extending \TYPO3\CMS\Core\Page\PageRenderer .

Affected installations

Installations with custom code extending \TYPO3\CMS\Core\Page\PageRenderer that are reading from or writing to the mentioned protected properties $inlineJavascriptWrap or $inlineCssWrap.

Migration

Avoid using the protected properties $inlineJavascriptWrap and $inlineCssWrap. In case any custom code needs to wrap with inline <script> or <style> tags, use the new protected methods wrapInlineScript($content) and wrapInlineStyle($content) within \TYPO3\CMS\Core\Page\PageRenderer .

Deprecation: #100622 - Extbase feature toggles

See forge#100622

Description

Extbase has an own system for feature toggles next to the Core feature toggle API. It has always been marked as internal, but is used for a couple of toggles within the Extbase framework.

All toggles and the internal PHP API have been marked as deprecated in TYPO3 v12 and should be avoided.

Impact

The PHP API for Extbase toggles has always been marked as internal. It will be removed with TYPO3 v13.

The single toggles can still be used in TYPO3 v12, but their triggered functionality will be removed with TYPO3 v13, if set to 1.

Affected installations

Extensions should not rely on \TYPO3\CMS\Extbase\ConfigurationConfigurationManagerInterface->isFeatureEnabled(). The method is marked as internal and should never have been used by extensions. The extension scanner still finds usages of this method in extensions as weak match.

All feature toggles have been marked as deprecated. Setting one of them to 1 in TypoScript will trigger a deprecation level log message, they will stop working with TYPO3 v13.

Migration

Extbase has three feature toggles in TYPO3 v12. All of them will be removed with TYPO3 v13. Instances with extensions setting those to 1 in TypoScript may need adaptions. Instances setting the toggles to 0 can simply remove them from TypoScript.

skipDefaultArguments = 1

This is an ancient toggle that was used before routing has been added with TYPO3 v9. It allowed to skip the controller and action argument in frontend plugin links, when linking to the default Extbase controller / action combination. This toggle has been documented as being broken in combination with Extbase plugin enhancer already. Consuming instances should switch to proper routing configuration instead.

ignoreAllEnableFieldsInBe = 1

This is another ancient toggle that triggers \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings->setIgnoreEnableFields(true) for Extbase repositories when used in backend scope. It allows ignoring default TCA flags like suppressing of deleted records in queries.

Extbase-based backend modules that rely on this toggle being set to 1 can easily migrate this: When the repository in question is only used in backend context, the code below should trigger the same behavior. Note as with other query settings, this toggle needs to be used with care, otherwise backend users may see records they are not supposed to see.

When the repository is used in both backend and frontend context, the code should be refactored a bit towards a public method that can be set by the Extbase backend controller only.

enableNamespacedArgumentsForBackend = 1

This toggle has been introduced in TYPO3 v12. See Feature: #97096 - Non-namespaced arguments in Extbase backend modules for more details. Extbase backend modules should no longer expect the namespace to be set. It may be necessary to adapt some Ajax calls and request-related argument checks in custom modules.

Deprecation: #100637 - Third argument ContentObjectRenderer->start()

See forge#100637

Description

When creating instances of the \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer , the third argument $request when calling start() should not be handed over anymore. Instead, setRequest() should be used after creating the object.

Impact

Handing over the third argument to start() has been marked as deprecated in TYPO3 v12, it will be ignored with TYPO3 v13.

Affected installations

Instances with casual extensions are probably not affected by this: Instances of ContentObjectRenderer are usually set-up framework internally.

Using the third argument on start() triggers a deprecation level log message. The extension scanner will not find usages, since the method name start() is used in different context as well and would lead to too many false positives.

Migration

Ensure the request is an instance of \Psr\Http\Message\ServerRequestInterface , and call setRequest() after instantiation instead of calling start() with three arguments.

Deprecation: #100639 - Deprecate AbstractPlugin

See forge#100639

Description

Abstract "pibase" class \TYPO3\CMS\Frontend\Plugin\AbstractPlugin has been marked @internal with changelog-Breaking-98281-MakeAbstractPluginInternal in TYPO3 v12.0 already and should not be used anymore.

It has now been fully deprecated with TYPO3 v12.4 and will be removed with TYPO3 v13.0.

Impact

Extending AbstractPlugin will trigger a deprecation level log warning since TYPO3 v12.4. The class will be removed with TYPO3 v13.0.

Affected installations

Instances with frontend plugin extensions that extend \TYPO3\CMS\Frontend\Plugin\AbstractPlugin are affected.

The extension scanner will find usages with a strong match.

Migration

Stop extending the class. A simple way to migrate is by copying needed methods over to an own controller class. See changelog-Breaking-98281-MakeAbstractPluginInternal for more details on this.

Deprecation: #100653 - Deprecated some methods in DebugUtility

See forge#100653

Description

The following methods in \TYPO3\CMS\Core\Utility\DebugUtility have been marked as deprecated:

  • debugInPopUpWindow()
  • debugRows()
  • printArray()

While debugRows() and printArray() duplicate already existing methods, debugInPopUpWindow() is discouraged to use as either external debuggers, e.g. Xdebug or \TYPO3\CMS\Extbase\Utility\DebuggerUtility may be used instead.

Impact

Calling any of the aforementioned methods will trigger deprecation log entries.

Affected installations

Instances using any of the aforementioned methods are affected.

The extension scanner will find and report usages.

Migration

In case of debugRows(), the identical method debug() can be used. The method printArray() can be replaced with viewArray(). However, the former method directly outputs the contents, which is not the case with viewArray().

The method debugInPopUpWindow() is deprecated without a direct replacement, consider using an external debugger or \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump() instead.

Deprecation: #100657 - TYPO3_CONF_VARS['BE']['languageDebug']

See forge#100657

Description

The configuration option $GLOBALS['TYPO3_CONF_VARS']['BE']['languageDebug'] has been marked as deprecated in TYPO3 v12, it will be removed with TYPO3 v13 along with the property \TYPO3\CMS\Core\Localization->debugKey.

Setting the configuration option languageDebug to true adds the label name including the path to the .xlf file to the output in the backend.

The intention was to allow translators to see where a specific localized string comes from in the backend to allow locating missing localization sources.

Judging from translators feedback, the option isn't used in practice, though: Setting the toggle to true leads to a massively convoluted backend experience that breaks tons of CSS and renders the backend so unusable that it's hardly a benefit at all.

TYPO3 v12 cleaned up lots of label usages and makes them more unique. Translators should find single label usages much more easily by searching the code base for label names and label files. Also, many Fluid templates are located more transparently and are easier to find, localizing labels within PHP classes is also improving a lot. Translators should in general have less headaches to see where labels are used, and this will improve further.

Impact

The option has been marked as deprecated in TYPO3 v12 and does not have any effect anymore with TYPO3 v13.

Affected installations

The target of this toggle were translators, production sites are not affected by this. Extensions using the property \TYPO3\CMS\Core\Localization->debugKey are found by the extension scanner as weak match.

Migration

Remove access to \TYPO3\CMS\Core\Localization->debugKey.

Deprecation: #100662 - ConfigurationManager->getContentObject()

See forge#100662

Description

The Extbase-related method \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface->getContentObject() has been marked as deprecated in TYPO3 v12 and should not be used anymore.

Impact

Calling ConfigurationManagerInterface->getContentObject() will trigger a deprecation level log message in TYPO3 v12, the method will be removed from the interface together with their implementations with TYPO3 v13.

Affected installations

Instances with Extbase extensions that use getContentObject() on injected ConfigurationManager instances are affected. The extension scanner has not been configured to find these calls, since the method name is used in different scope as well and would trigger too many false positives.

Migration

There may be instances with Extbase controllers that need to retrieve data from the current content object that initiated the frontend Extbase plugin call.

In this case, controllers can access the current content object from the Extbase request object using $request->getAttribute('currentContentObject') instead.

Deprecation: #100670 - DI-aware FormEngine nodes

See forge#100670

Description

When the FormEngine construct (used when editing records in the backend) has been rewritten back in TYPO3 v7, dependency injection for non-Extbase constructs has not been a thing, yet.

With dependency injection being part of the TYPO3 Core extension since TYPO3 v10, and the Extbase solution being out-phased, it is time to make FormEngine dependency injection aware as well.

This has some impact on classes implementing \TYPO3\CMS\Backend\Form\NodeInterface directly, or indirectly by extending \TYPO3\CMS\Backend\Form\AbstractNode and \TYPO3\CMS\Backend\Form\Element\AbstractFormElement . Custom implementations can use this already, but the full power will only be leveraged with TYPO3 v13.

Similar changes as described below can be done for classes implementing \TYPO3\CMS\Backend\Form\NodeResolverInterface as well, but the impact is much smaller since this construct is used less often in the wild.

Additionally, classes should either implement one of the interfaces directly, or extend an appropriate abstract. They must not extend any of the existing "leaf" classes the core provides, since those will be declared final with TYPO3 v13.

Impact

Using dependency injection within FormEngine related classes becomes possible in TYPO3 v12.

Affected installations

Instances with extensions that come with own FormEngine additions may be affected. The extensions scanner is not configured to find affected classes.

Migration

Compatibility with TYPO3 v11 and v12

Extensions that strive for both TYPO3 v11 and v12 compatibility should just keep their implementation as is.

Compatibility with TYPO3 v12 and v13

Extensions that strive for TYPO3 v12 compatibility, skipping v11, that want to support v13 as well, must adapt their implementations.

As main change, NodeInterface no longer declares __construct(), the class constructor is now "free" for injection. The NodeFactory uses the existence of method setData() as indicator if NodeFactory and $data array should be hand over as manual constructor argument (old way), or if setData() should be called after object instantiation. Note setData() will be activated as interface method with TYPO3 v13.

A class with both TYPO3 v12 and v13 compatibility should look like this:

public function __construct(
    // If the class creates sub elements
    NodeFactory $nodeFactory,
    // If the class needs IconFactory
    IconFactory $iconFactory,
    // Further dependencies
    private readonly MyService $myService,
) {
    $this->nodeFactory = $nodeFactory;
    $this->iconFactory = $iconFactory;
}

public function setData(array $data): void
{
    $this->data = $data;
}

public function render(): array
{
    // Implement render(), note the "array" return type hint,
    // which will be mandatory in TYPO3 v13.
}
Copied!

The class has to be registered for public DI in Services.yaml as well, since it is instantiated by NodeFactory using GeneralUtility::makeInstance():

MyVendor\MyExtension\Form\Element\MyElementClass:
  public: true
Copied!

Compatibility with v13

Extensions dropping TYPO3 v12 compatibility and going with v13 and up, can simplify the construct: In v13, setData() will be added to AbstractNode, extending classes don't need to implement it anymore. The class property $iconFactory ( AbstractFormElement only) will be removed from the abstracts, constructor property promotion can be used. NodeFactory will be injected in the abstracts, without polluting __construct(). Also, a dependency injection service provider pass will be added, to automatically set classes public that implement implement NodeInterface, so a public: true entry in Services.yaml can be skipped.

A typical class extending AbstractNode looks like this:

public function __construct(
    private readonly IconFactory $iconFactory,
    private readonly MyService $myService,
) {
}

// Implement render().
Copied!

Important: #94246 - Generic sudo mode configuration

See forge#94246

Description

Sudo mode has been integrated since TYPO3 v9.5.x to protect only Install Tool components. With TYPO3 v12 it has been changed to a generic configuration for backend routes (and implicitly modules).

Besides that, access to the Extension Manager now needs to pass the sudo mode verification as well.

Process in a nutshell

All simplified classnames below are located in the namespace \TYPO3\CMS\Backend\Security\SudoMode\Access). The low-level request orchestration happens in the middleware \TYPO3\CMS\Backend\Middleware\SudoModeInterceptor , markup rendering and payload processing in controller \TYPO3\CMS\Backend\Controller\Security\SudoModeController .

  1. A backend route is processed, that requires sudo mode for route URI /my/route in \TYPO3\CMS\Backend\Http\RouteDispatcher .
  2. Using AccessFactory and AccessStorage, the RouteDispatcher tries to find a valid and not expired AccessGrant item for the specific RouteAccessSubject('/my/route') aspect in the current backend user session data.
  3. In case no AccessGrant can be determined, a new AccessClaim is created for the specific RouteAccessSubject instance and temporarily persisted in the current user session data - the claim also contains the originally requested route as ServerRequestInstruction (a simplified representation of a ServerRequestInterface).
  4. Next, the user is redirected to the user interface for providing either their own password, or the global install tool password as alternative.
  5. Given, the password was correct, the AccessClaim is "converted" to an AccessGrant, which is only valid for the specific subject (URI /my/route) and for a limited lifetime.

Configuration

In general, the configuration for a particular route or module looks like this:

<?php
// ...
'sudoMode' => [
    'group' => 'individual-group-name',
    'lifetime' => AccessLifetime::veryShort,
],
Copied!
  • group (optional): if given, grants access to other objects of the same group without having to verify sudo mode again for a the given lifetime. Example: Admin Tool modules Maintainance and Settings are configured with the same systemMaintainer group - having access to one (after sudo mode verification) grants access to the other automatically.
  • lifetime: enum value of \TYPO3\CMS\Backend\Security\SudoMode\Access\AccessLifetime , defining the lifetime of a sudo mode verification, afterwards users have to go through the process again - cases are veryShort (5 minutes), short (10 minutes), medium (15 minutes), long (30 minutes), veryLong (60 minutes)

For backend routes declared via Configuration/Backend/Routes.php, the relevant configuration would look like this:

<?php
return [
    'my-route' => [
        'path' => '/my/route',
        'target' => MyHandler::class . '::process',
        'sudoMode' => [
            'group' => 'mySudoModeGroup',
            'lifetime' => AccessLifetime::short,
        ],
    ],
];
Copied!

For backend modules declared via Configuration/Backend/Modules.php, the relevant configuration would look like this:

<?php
return [
    'tools_ExtensionmanagerExtensionmanager' => [
        // ...
        'routeOptions' => [
            'sudoMode' => [
                'group' => 'systemMaintainer',
                'lifetime' => AccessLifetime::medium,
            ],
        ],
    ],
];
Copied!

Important: #100207 - Let DataMapper::createEmptyObject() use doctrine/instantiator

See forge#100207

Description

Introduction

This document explains the intended way in which the Extbase ORM thaws/hydrates objects.

Hydrating objects

Hydrating (the term originates from doctrine/orm), or in Extbase terms thawing, is the act of creating an object from a given database row. The responsible class involved is the DataMapper. During the process of hydrating, the DataMapper creates objects to map the raw database data onto.

Before diving into the framework internals, let's take a look at models from the user's perspective.

Creating objects with constructor arguments

Imagine you have a table tx_extension_domain_model_blog and a corresponding model or entity (entity is used as a synonym here) \Vendor\Extension\Domain\Model\Blog.

Now, also imagine there is a domain rule which states, that all blogs must have a title. This rule can easily be followed by letting the blog class have a constructor with a required argument string $title.

class Blog extends AbstractEntity
{
    protected ObjectStorage $posts;

    public function __construct(protected string $title)
    {
        $this->posts = new ObjectStorage();
    }
}
Copied!

This example also shows how the posts property is initialized. It is done in the constructor because PHP does not allow setting a default value that is of type object.

Hydrating objects with constructor arguments

Whenever the user creates new blog objects in extension code, the aforementioned domain rule is followed. It is also possible to work on the posts ObjectStorage without further initialization. new Blog('title') is all I need to create a blog object with a valid state.

What happens in the DataMapper however, is a totally different thing. When hydrating an object, the DataMapper cannot follow any domain rules. Its only job is to map the raw database values onto a Blog instance. The DataMapper could of course detect constructor arguments and try to guess which argument corresponds to what property but only if there is an easy mapping, i.e. if the constructor takes argument string $title and updates property title with it.

To avoid possible errors due to guessing, the DataMapper simply ignores the constructor at all. It does so with the help of the library doctrine/instantiator.

This pretty much explains the title of this document in detail. But there is more to all this.

Initializing objects

Have a look at the $posts property in the example above. If the DataMapper ignores the constructor, that property is in an invalid state, i.e. uninitialized.

To address this problem and possible others, the DataMapper will call the method initializeObject(): void on models, if it exists.

Here is an updated version of the model:

class Blog extends AbstractEntity
{
    protected ObjectStorage $posts;

    public function __construct(protected string $title)
    {
        $this->initializeObject();
    }

    public function initializeObject(): void
    {
        $this->posts = new ObjectStorage();
    }
}
Copied!

This example demonstrates how Extbase expects the user to set up their model(s). If method initializeObject() is used for initialization logic that needs to be triggered on initial creation AND on hydration. Please mind that __construct() SHOULD call initializeObject().

If there are no domain rules to follow, the recommended way to set up a model would then still be to define a __construct() and initializeObject() method like this:

class Blog extends AbstractEntity
{
    protected ObjectStorage $posts;

    public function __construct()
    {
        $this->initializeObject();
    }

    public function initializeObject(): void
    {
        $this->posts = new ObjectStorage();
    }
}
Copied!

Mutating objects

I'd like to add a few more words on mutators (setter, adder, etc.). One might think that DataMapper uses mutators during object hydration but it DOES NOT. mutators are the only way for the user (developer) to implement business rules besides using the constructor.

The DataMapper uses the @internal method AbstractDomainObject::_setProperty() to update object properties. This looks a bit dirty and is a way around all business rules but that's what the DataMapper needs in order to leave the mutators to the users.

Property visibility

One important thing to know is that Extbase needs entity properties to be protected or public. As written in the former paragraph, AbstractDomainObject::_setProperty() is used to bypass setters. AbstractDomainObject however, is not able to access private properties of child classes, hence the need to have protected or public properties.

Dependency injection

Without digging too deep into this topic the following statements have to be made. Extbase expects entities to be so called prototypes, i.e. classes that do have a different state per instance. DataMapper DOES NOT use dependency injection for the creation of entities, i.e. it does not query the object container. This also means, that dependency injection is not possible in entities.

If you think that your entities need to use/access services, you need to find other ways to implement it.

Important: #100525 - Dropped usage of .text(-)-right and .text(-)-left classes

See forge#100525

Description

The Core has dropped support for directional class names to better support RTL languages. We are now preferring the logical class names over the directional ones. This change also affects the default RTE configuration.

In summary, that means we are dropping the classes .text-right and .text-left and replacing them with their logical counterparts .text-end and .text-start.

We are still shipping the .text-right and .text-left classes with the default RTE content styling. Your content is persisted as is and we have no intention of changing this.

You will see the following:

  • Your content is still aligned as you set it once
  • The alignment button will not be active anymore for .text-left and .text-right
  • New alignments will now use .text-end and .text-start

While there is never a good time to introduce such a change, we still think this will benefit us all over time.

If you want to follow us on that route, we suggest that you add the following CSS to your frontend and or the custom CSS for your RTE.

.text-end {
    text-align: end;
}
.text-start {
    text-align: start;
}
Copied!

See caniuse for compatibility, which is 96.23% at the time of writing. For example: https://caniuse.com/?search=text-align%3A%20start

You need to adjust your RTE config, if you want to use the old classes.

EXT:my_extension/Configuration/RTE/MyPreset.yaml
editor:
  config:
    alignment:
      options:
        - { name: 'left', className: 'text-left' }
        - { name: 'center', className: 'text-center' }
        - { name: 'right', className: 'text-right' }
        - { name: 'justify', className: 'text-justify' }
Copied!

Important: #100634 - Rich Text Editor always enabled per user

See forge#100634

Description

Back in TYPO3 v3.x there was an RTE integrated into TYPO3 which only worked in Internet Explorer 4+, but not in Mozilla / Firefox browsers. This was a huge mess, as not every user / client was able to use an RTE and instead to had to write pure HTML in a <textarea> input field with special tags ("typolink" etc).

Since TYPO3 v4 a huge effort were made to integrate HTMLarea as Rich Text Editor, which was forked and developed by the TYPO3 community. It was then possible for most users working with a real RTE.

In v8, TYPO3 migrated towards CKEditor 4 as a dependency, and CKEditor 5 with TYPO3 v12, the Rich Text Editor is working very browser-native for modern browsers without an iframe around the RTE.

A lot of legacy code was moved and migrated, however, one option - the option to deactivate the Rich Text Editor on a per-user basis - which was necessary in TYPO3 v3, has now been removed, as it is not needed in 99.99% of TYPO3 installations and users anymore nowadays.

Impact

The previous user TSconfig setting setup.edit_RTE has no effect anymore.

Important: #100658 - Drop use TSconfig options createFoldersInEB and folderTree.hideCreateFolder

See forge#100658

Description

The user TSconfig options createFoldersInEB and folderTree.hideCreateFolder were used in the past to control the existence of the "Create folder" form in Element Browser instances. With the migration of the "Create folder" view into a separate modal used in EXT:filelist, which is based on Element Browser as well, those options became useless and are therefore dropped.

12.3 Changes

Table of contents

Breaking Changes

None since TYPO3 v12.0 release.

Features

Deprecation

Important

Feature: #45039 - Command to clean up local processed files

See forge#45039

Description

It is now possible to set up a recurring scheduler task or execute a CLI command to clean up locally processed files and their database records.

Impact

The command will delete sys_file_processedfile records with references to non-existing files. Also, files in the configured temporary directory (typically _processed_) will be deleted if there are no references to them.

Example

Delete files and records with confirmation:

./bin/typo3 cleanup:localprocessedfiles
Copied!

Delete files and records:

./bin/typo3 cleanup:localprocessedfiles -f
Copied!

Only show which files and records would be deleted:

./bin/typo3 cleanup:localprocessedfiles --dry-run -v
Copied!

Please note that the command currently only works for local drivers.

Feature: #65020 - Change button labels within TCA type=file

See forge#65020

Description

When working with file references ( sys_file_reference records) within FormEngine, there are up to three buttons available:

  • "Create new relation"
  • "Select & upload files"
  • "Add media by URL"

Whereas the first button text can be changed via TCA on a per-field basis via [config][appearance][createNewRelationLinkTitle] = 'LLL:my_extension/...'; the two other label fields are hard-coded. It is especially useful to override such a label when only a certain type of media is required (for example, just images) or online media of type YouTube.

It is now possible to do so by using two new TCA configuration settings for TCA type=file

  • [config][appearance][uploadFilesLinkTitle]
  • [config][appearance][addMediaLinkTitle]

Impact

An extension author can now completely modify the label texts of all buttons.

Feature: #83608 - Page TSconfig setting "options.defaultUploadFolder" added

See forge#83608

Description

A new page TSconfig option options.defaultUploadFolder is added.

Impact

Identical to the user TSconfig setting options.defaultUploadFolder, this allows default upload folder per page to be set.

If specified and the given folder exists, this setting will override the value defined in user TSconfig.

Example

# Set default upload folder to "fileadmin/page_upload" on PID 1
[page["uid"] == 1]
    options.defaultUploadFolder = 1:/page_upload/
[end]
Copied!

Feature: #83608 - PSR-14 event to modify resolved default upload folder

See forge#83608

Description

A new PSR-14 event \TYPO3\CMS\Core\Resource\Event\AfterDefaultUploadFolderWasResolvedEvent has been added, which allows the default upload folder to be modified after it has been resolved for the current page or user.

The new event can be used as a better alternative to the $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getDefaultUploadFolder'] hook, serving the same purpose.

The event features the following methods:

  • getUploadFolder() returns the currently resolved $uploadFolder
  • setUploadFolder() sets a new upload folder
  • getPid() returns the PID of the record we fetch the upload folder for
  • getTable() returns the table name of the record we fetch the upload folder for
  • getFieldName() returns the field name of the record we fetch the upload folder for

Registration of the event in your extension's Services.yaml:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\Resource\EventListener\MyEventListener:
  tags:
    - name: event.listener
      identifier: 'my-extension/after-default-upload-folder-was-resolved-event-listener'
Copied!

The corresponding event listener class:

EXT:my_extension/Classes/Resources/EventListener/MyEventListener.php
namespace MyVendor\MyExtension\Resource\EventListener;

use TYPO3\CMS\Core\Resource\Event\AfterDefaultUploadFolderWasResolvedEvent;

final class MyEventListener
{
    public function __invoke(AfterDefaultUploadFolderWasResolvedEvent $event): void
    {
        $event->setUploadFolder($event->getUploadFolder()->getStorage()->getFolder('/'));
    }
}
Copied!

Impact

As resolving the event was moved from BackendUserAuthentication to its own DefaultUploadFolderResolver class, this event is now the preferred way of modifying the default upload folder.

Feature: #86880 - Enable password view at backend login

See forge#86880

Description

On clicking, the TYPO3 backend login now displays an additional button to reveal the user's password, once something has been typed in the password field.

Impact

A user who is about to log in to the backend is now able to reveal the typed password. Once the password field is cleared, the visibility mode automatically switches back to its default to avoid revealing sensitive data by accident.

Feature: #94499 - Implement AddPageTypeZeroSource event listener

See forge#94499

Description

A new event listener for \TYPO3\CMS\Redirects\Event\SlugRedirectChangeItemCreatedEvent is introduced, which creates a \TYPO3\CMS\Redirects\RedirectUpdate\PageTypeSource for a page before the slug has been changed. The full URI is built to fill the source_host and source_path, which takes configured RouteEnhancers and RouteDecorators into account, for example, the PageType route decorator.

It is not possible to configure page types for which sources should be added. If you need to do so, read additional PageTypeSource auto-create redirect source type which provides an example of how to implement custom event listeners based on PageTypeSource.

If PageTypeSource for page type 0 results in a different source, the PlainSlugReplacementSource is not removed to keep the original behaviour, which some instances may rely on.

This behaviour can be modified by adding an event listener for SlugRedirectChangeItemCreatedEvent

Remove plain slug source if page type 0 differs:

Registration of the event in your extension's Services.yaml:

EXT:my_extension/Configuration/Services.yaml
MyExtension\MyPackage\Redirects\MyEventListener:
  tags:
    - name: event.listener
      identifier: 'my-extension/custom-page-type-redirect'
      # Registering after core listener is important, otherwise we would
      # not know if there is a PageType source for page type 0
      after: 'redirects-add-page-type-zero-source'
Copied!

The corresponding event listener class:

EXT:my_package/Classes/Redirects/MyEventListener.php
namespace MyVendor\MyExtension\Redirects;

use TYPO3\CMS\Redirects\Event\SlugRedirectChangeItemCreatedEvent;
use TYPO3\CMS\Redirects\RedirectUpdate\PageTypeSource;
use TYPO3\CMS\Redirects\RedirectUpdate\PlainSlugReplacementRedirectSource;
use TYPO3\CMS\Redirects\RedirectUpdate\RedirectSourceCollection;
use TYPO3\CMS\Redirects\RedirectUpdate\RedirectSourceInterface;

final class MyEventListener
{
    public function __invoke(
        SlugRedirectChangeItemCreatedEvent $event
    ): void {
        $changeItem = $event->getSlugRedirectChangeItem();
        $sources = $changeItem->getSourcesCollection()->all();
        $pageTypeZeroSource = $this->getPageTypeZeroSource(
            ...array_values($sources)
        );
        if ($pageTypeZeroSource === null) {
            // nothing we can do - no page type 0 source found
            return;
        }

        // Remove plain slug replacement redirect source from sources. We
        // already know, that if it is there it differs from the page type
        // 0 source, therefor it is safe to simply remove it by class check.
        $sources = array_filter(
            $sources,
            static fn ($source) => !($source instanceof PlainSlugReplacementRedirectSource)
        );

        // update sources
        $changeItem = $changeItem->withSourcesCollection(
            new RedirectSourceCollection(
                ...array_values($sources)
            )
        );

        // update change item with updated sources
        $event->setSlugRedirectChangeItem($changeItem);
    }

    private function getPageTypeZeroSource(
        RedirectSourceInterface ...$sources
    ): ?PageTypeSource {
        foreach ($sources as $source) {
            if ($source instanceof PageTypeSource
                && $source->getPageType() === 0
            ) {
               return $source;
            }
        }
        return null;
    }
}
Copied!

Impact

An additional redirect source is automatically added if a PageType suffix is configured in the SiteConfiguration for page type 0. In that case two redirects are created, one for the plain slug change and one with the suffix in the source_path. That way it does not break instances relying on the fact that plain slug based redirects are created.

Feature: #94499 - Provide additional PageTypeSource auto-create redirect source type

See forge#94499

Description

A new source type implementation based on \TYPO3\CMS\Redirects\RedirectUpdate\RedirectSourceInterface is added, providing the page type number as an additional value. The main use case for this source type is to provide additional source types where the source host and path are taken from a fully built URI before the page slug change occurred for a specific page type. That avoids the need for extension authors to implement a custom source type for the same task, and instead provides a custom event listener to build sources for non-zero page types. Sources can be added by implementing an event listener for \TYPO3\CMS\Redirects\Event\SlugRedirectChangeItemCreatedEvent.

This class features the following methods:

  • getHost(): Returns the source host for the redirect
  • getPath(): Returns the source path for the redirect
  • getPageType(): Returns the page type used to provide the host/path
  • getTargetLinkParameters(): Returns the link parameters which should be used to create the target based on t3:// syntax

Values can be set only by the constructor.

Example:

Registration of the event in your extension's Services.yaml:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\Redirects\MyEventListener:
  tags:
    - name: event.listener
      identifier: 'my-extension/custom-page-type-redirect'
      after: 'redirects-add-page-type-zero-source'
Copied!

The corresponding event listener class:

EXT:my_extension/Classes/Redirects/MyEventListener.php
namespace MyVendor\MyExtension\Redirects;

use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
use TYPO3\CMS\Core\Routing\RouterInterface;
use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Redirects\Event\SlugRedirectChangeItemCreatedEvent;
use TYPO3\CMS\Redirects\RedirectUpdate\PageTypeSource;
use TYPO3\CMS\Redirects\RedirectUpdate\RedirectSourceCollection;
use TYPO3\CMS\Redirects\RedirectUpdate\RedirectSourceInterface;

final class MyEventListener
{
    protected array $customPageTypes = [ 1234, 169999 ];

    public function __invoke(
        SlugRedirectChangeItemCreatedEvent $event
    ): void {
        $changeItem = $event->getSlugRedirectChangeItem();
        $sources = $changeItem->getSourcesCollection()->all();

        foreach ($this->customPageTypes as $pageType) {
            try {
                $pageTypeSource = $this->createPageTypeSource(
                    $changeItem->getPageId(),
                    $pageType,
                    $changeItem->getSite(),
                    $changeItem->getSiteLanguage(),
                );
                if ($pageTypeSource === null) {
                    continue;
                }
            } catch (UnableToLinkToPageException) {
                // Could not properly link to page. Continue to next page type
                continue;
            }

            if ($this->isDuplicate($pageTypeSource, ...$sources)) {
                // not adding duplicate,
                continue;
            }

            $sources[] = $pageTypeSource;
        }

        // update sources
        $changeItem = $changeItem->withSourcesCollection(
            new RedirectSourceCollection(
                ...array_values($sources)
            )
        );

        // update change item with updated sources
        $event->setSlugRedirectChangeItem($changeItem);
    }

    private function isDuplicate(
        PageTypeSource $pageTypeSource,
        RedirectSourceInterface ...$sources
    ): bool {
        foreach ($sources as $existingSource) {
            $existingHost = $existingSource->getHost();
            $pageTypeSourceHost = $pageTypeSource->getHost();
            $existingPath = rtrim($existingSource->getPath(), '/');
            $pageTypeSourcePath = rtrim($pageTypeSource->getPath(), '/');
            if ($existingSource instanceof PageTypeSource
                && $existingHost === $pageTypeSourceHost
                && $existingPath === $pageTypeSourcePath
            ) {
                // we do not check for the type, as that is irrelevant. Same
                // host+path tuple would lead to duplicated redirects if
                // type differs.
                return true;
            }
        }
        return false;
    }

    private function createPageTypeSource(
        int $pageUid,
        int $pageType,
        Site $site,
        SiteLanguage $siteLanguage
    ): ?PageTypeSource {
        if ($pageType === 0) {
            // pageType 0 is handled by \TYPO3\CMS\Redirects\EventListener\AddPageTypeZeroSource
            return null;
        }

        try {
            $context = $this->getAdjustedContext();
            $uri = $site->getRouter($context)->generateUri(
                $pageUid,
                [
                    '_language' => $siteLanguage,
                    'type' => $pageType,
                ],
                '',
                RouterInterface::ABSOLUTE_URL
            );
            return new PageTypeSource(
                $uri->getHost() ?: '*',
                $uri->getPath(),
                $pageType,
                [
                    'type' => $pageType,
                ],
            );
        } catch (\InvalidArgumentException | InvalidRouteArgumentsException $e) {
            throw new UnableToLinkToPageException(
                sprintf(
                    'The link to the page with ID "%d" and type "%d" could not be generated: %s',
                    $pageUid,
                    $pageType,
                    $e->getMessage()
                ),
                1675618235,
                $e
            );
        }
    }

    /**
     * Returns the adjusted current context with modified visibility settings
     * to build source url for hidden or scheduled pages.
     */
    private function getAdjustedContext(): Context
    {
        $adjustedVisibility = new VisibilityAspect(
            true,
            true,
            false,
            true,
        );
        $originalContext = GeneralUtility::makeInstance(Context::class);
        $context = clone $originalContext;
        $context->setAspect('visibility', $adjustedVisibility);
        return $context;
    }
}
Copied!

Impact

The new PageTypeSource can be used to provide additional sources, for example, based on custom page types using full URI building, which would take configured PageTypeSuffix decorators into account. For page type 0 (default), the Core implements an event listener which adds the source based on this source class for page type 0 with AddPageTypeZeroSource event listener.

Feature: #97389 - Add password policy validation for TCA type=password

See forge#97389

Description

It is now possible to assign a password policy to TCA fields of type password. For configured fields, the password policy validator will be used in DataHandler to ensure that the new password complies with the configured password policy.

Password policy requirements are shown below the password field when the focus is changed to the password field.

The TCA field password for tables be_users and fe_users uses now by default the password policy configured in $GLOBALS['TYPO3_CONF_VARS']['FE']['passwordPolicy'] (fe_users) or $GLOBALS['TYPO3_CONF_VARS']['BE']['passwordPolicy'] (be_users).

Example configuration

'password_field' => [
    'label' => 'Password',
    'config' => [
        'type' => 'password',
        'passwordPolicy' => 'default',
    ],
],
Copied!

This example will use the password policy default for the field.

Impact

For TYPO3 frontend and backend users, the global password policy is used. A new password is not saved if it does not comply with the password policy.

Feature: #97390 - Use password policy for password reset in ext:felogin

See forge#97390

Description

The password reset feature for TYPO3 frontend users now takes into account the configurable password policy introduced in #97388, if the feature toggle security.usePasswordPolicyForFrontendUsers is set to true (default for new TYPO3 websites).

Impact

Password validation configured through plugin.tx_felogin_login.settings.passwordValidators has been marked as deprecated, but will still be used for password validation, if the feature toggle security.usePasswordPolicyForFrontendUsers is set to false.

TYPO3 websites, which have the feature toggle security.usePasswordPolicyForFrontendUsers set to true, will use the globally configured password policy when a TYPO3 frontend user resets their password. The TYPO3 default password policy contains the following password requirements:

  • At least 8 chars
  • At least one number
  • At least one upper case char
  • At least one special char
  • Must be different than current password (if available)

Feature: #97667 - Add keyboard support for Multiselect

See forge#97667

Description

You are able to use the keyboard for selecting and deselecting options in Multiselect.

  • Enter adds options, either from right to left or left to right
  • Delete or Backspace removes an option for windows/mac users
  • Alt + ArrowUp moves the option one up
  • Alt + ArrowDown moves the option one down
  • Alt + Shift + ArrowUp moves it to the top
  • Alt + Shift + ArrowDown moves it to the bottom

More combinations are possible by default:

  • Shift + ArrowUp includes the upper option
  • Shift + ArrowDown includes the lower option
  • Home moves the cursor to the top
  • End move the cursor to the bottom

Impact

This currently affects the following TCA configurations:

  • 'type' => 'select', 'renderType' => 'selectMultipleSideBySide'
  • 'type' => 'group'
  • 'type' => 'folder'

Feature: #98132 - Extbase entity properties support union types

See forge#98132

Description

Extbase reflection now supports the detection of union types in entity properties.

Previously, whenever a union type was needed, union type declarations led to Extbase not detecting any type at all, resulting in the property not being mapped. Union types could be resolved via doc blocks however:

class Entity extends AbstractEntity
{
    /**
     * @var ChildEntity|LazyLoadingProxy
     */
    private $property;
}
Copied!

Now this is possible:

class Entity extends AbstractEntity
{
    private ChildEntity|LazyLoadingProxy $property;
}
Copied!

This is especially useful for lazy loaded relations where the property type is LazyLoadingProxy|ChildEntity.

There is something important to understand about how Extbase detects unions when it comes to property mapping, i.e. when a database row is mapped onto an object. In this case, Extbase needs to know the desired target type - no union, no intersection, just one type. In order to achieve this, Extbase uses the first declared type as a so-called primary type.

class Entity extends AbstractEntity
{
    private string|int $property;
}
Copied!

In this case, string is the primary type. int|string would result in int as primary type.

There is one important thing to note and one exception to this rule. First of all, null is not considered a type. null|string results in primary type string, which is nullable. null|string|int also results in primary type string. In fact, null means that all other types are nullable. null|string|int boils down to ?string or ?int.

Secondly, LazyLoadingProxy is never detected as primary type because it is just a proxy and not the actual target type, once loaded.

class Entity extends AbstractEntity
{
    private LazyLoadingProxy|ChildEntity $property;
}
Copied!

Extbase supports this and detects ChildEntity as primary type, although LazyLoadingProxy is the first item in the list. However, it is recommended to place the actual type first, for consistency reasons: ChildEntity|LazyLoadingProxy.

A final word on LazyObjectStorage: LazyObjectStorage is a subclass of ObjectStorage, therefore the following code works and has always worked:

class Entity extends AbstractEntity
{
    /**
     * @var ObjectStorage<ChildEntity>
     * @TYPO3\CMS\Extbase\Annotation\ORM\Lazy
     */
    private ObjectStorage $property;
}
Copied!

Impact

As described above, the main impact is Extbase being able to detect and support union type declarations for entity properties.

Feature: #98517 - Username in backend password reset mail

See forge#98517

Description

Many users forget their login username and try to login with their email address. The username of the backend user is now displayed in the password recovery email alongside the reset link.

Impact

The username of the backend user is displayed in the password recovery email alongside the reset link.

Feature: #99258 - Add minimum age option to EXT:lowlevel cleanup:deletedrecords command

See forge#99258

Description

Using the CLI command cleanup:deletedrecords to clean up the database periodically is not really possible with EXT:recycler, because all records marked for deletion are deleted immediately and thus the recycler seems less useful.

The new option --min-age added to the cleanup:deletedrecords CLI command allows a minimum age of the X days that a record needs to be marked as deleted before it really gets deleted to be defined.

Impact

Executing bin/typo3 cleanup:deletedrecords --min-age 30 will only delete records that have been marked for more than 30 days for deletion.

Feature: #99321 - Add presets for site languages

See forge#99321

Description

When adding a new language to a site, an integrator can now choose

a) to create a new language by defining all values themselves b) from a list of default language settings ("presets") c) to use an existing language if it is already used in a different site

Although c) is always recommended when working with multi-site setups, to keep language IDs between sites in sync, b) is now a quick start to setup a new site.

Impact

Integrators spend less time adding new site languages.

Feature: #99436 - List commands in scheduler module

See forge#99436

Description

Commands based on Symfony commands are the successor of regular tasks since TYPO3 v8.

The scheduler submodule Available scheduler commands & tasks has been extended to list not only available scheduler tasks, but CLI commands that can be added as scheduler tasks.

Impact

The submodule Available scheduler commands & tasks has been improved to list schedulable commands as well. This improves the overview and makes it easier to set up commands.

Feature: #99499 - Introduce Content-Security-Policy handling

See forge#99499

Description

A corresponding representation of the W3C standard of Content-Security-Policy (CSP) has been introduced to TYPO3. Content-Security-Policy declarations can either be provided by using the general builder pattern of \TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy , extension-specific mutations (changes to the general policy) via Configuration/ContentSecurityPolicies.php located in corresponding extension directories, or YAML path contentSecurityPolicies.mutations for site-specific declarations in the website frontend.

The PSR-15 middlewares ContentSecurityPolicyHeaders apply Content-Security-Policy HTTP headers to each response in the frontend and backend scope. In the case that other components have already added either the header Content-Security-Policy or Content-Security-Policy-Report-Only, those existing headers will be kept without any modification - these events will be logged with an info severity.

To delegate CSP handling to TYPO3, the scope-specific feature flags need to be enabled:

  • $GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.backend.enforceContentSecurityPolicy']
  • $GLOBALS['TYPO3_CONF_VARS']['SYS']['features']['security.frontend.enforceContentSecurityPolicy']

For new installations security.backend.enforceContentSecurityPolicy is enabled via factory default settings.

Potential CSP violations are reported back to the TYPO3 system and persisted internally in the database table sys_http_report. A corresponding Content-Security-Policy backend module supports users to keep track of recent violations and - if applicable - to select potential resolutions (stored in database table sys_csp_resolution) which extends the Content-Security-Policy for the given scope during runtime.

As an alternative, the reporting URL can be configured to use third-party services as well:

$GLOBALS['TYPO3_CONF_VARS']['BE']['contentSecurityPolicyReportingUrl']
    = 'https://csp-violation.example.org/';

$GLOBALS['TYPO3_CONF_VARS']['FE']['contentSecurityPolicyReportingUrl']
    = 'https://csp-violation.example.org/';
Copied!

Impact

Introducing CSP to TYPO3 aims to reduce the risk of being affected by Cross-Site-Scripting due to the lack of proper encoding of user-submitted content in corresponding outputs.

Configuration

Policy builder approach

<?php
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue;
use TYPO3\CMS\Core\Security\Nonce;

$nonce = Nonce::create();
$policy = (new Policy())
    // results in `default-src 'self'`
    ->default(SourceKeyword::self)
    // extends the ancestor directive ('default-src'), thus reuses 'self' and adds additional sources
    // results in `img-src 'self' data: https://*.typo3.org`
    ->extend(Directive::ImgSrc, SourceScheme::data, new UriValue('https://*.typo3.org'))
    // extends the ancestor directive ('default-src'), thus reuses 'self' and adds additional sources
    // results in `script-src 'self' 'nonce-[random]'` ('nonce-proxy' is substituted when compiling the policy)
    ->extend(Directive::ScriptSrc, SourceKeyword::nonceProxy)
    // sets (overrides) the directive, thus ignores 'self' of the 'default-src' directive
    // results in `worker-src blob:`
    ->set(Directive::WorkerSrc, SourceScheme::blob);
header('Content-Security-Policy: ' . $policy->compile($nonce));
Copied!

The result of the compiled and serialized result as HTTP header would look similar to this (the following sections are using the same example, but utilize different techniques for the declarations).

Content-Security-Policy: default-src 'self';
    img-src 'self' data: https://*.typo3.org; script-src 'self' 'nonce-[random]';
    worker-src blob:
Copied!

Extension-specific

A file Configuration/ContentSecurityPolicies.php in the base directory of any extension will automatically provide and apply corresponding settings.

EXT:my_extension/Configuration/ContentSecurityPolicies.php
<?php
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue;
use TYPO3\CMS\Core\Type\Map;

return Map::fromEntries([
    // provide declarations for the backend
    Scope::backend(),

    // NOTICE: When using `MutationMode::Set` existing declarations will be overridden
    new MutationCollection(
        // results in `default-src 'self'`
        new Mutation(MutationMode::Set, Directive::DefaultSrc, SourceKeyword::self),

        // extends the ancestor directive ('default-src'), thus reuses 'self' and adds additional sources
        // results in `img-src 'self' data: https://*.typo3.org`
        new Mutation(MutationMode::Extend, Directive::ImgSrc, SourceScheme::data, new UriValue('https://*.typo3.org')),
        // NOTICE: the following two instructions for `Directive::ImgSrc` are identical to the previous instruction,
        // `MutationMode::Extend` is a shortcut for `MutationMode::InheritOnce` and `MutationMode::Append`
        // new Mutation(MutationMode::InheritOnce, Directive::ImgSrc, SourceScheme::data),
        // new Mutation(MutationMode::Append, Directive::ImgSrc, SourceScheme::data, new UriValue('https://*.typo3.org')),

        // extends the ancestor directive ('default-src'), thus reuses 'self' and adds additional sources
        // results in `script-src 'self' 'nonce-[random]'` ('nonce-proxy' is substituted when compiling the policy)
        new Mutation(MutationMode::Extend, Directive::ScriptSrc, SourceKeyword::nonceProxy),

        // sets (overrides) the directive, thus ignores 'self' of the 'default-src' directive
        // results in `worker-src blob:`
        new Mutation(MutationMode::Set, Directive::WorkerSrc, SourceScheme::blob),
    ),
]);
Copied!

Site-specific (frontend)

In the frontend, the dedicated sites/<my-site>/csp.yaml can be used to declare CSP for a specific site as well.

config/sites/<my-site>/csp.yaml
# inherits default site-unspecific frontend policy mutations (enabled per default)
inheritDefault: true
mutations:
  # results in `default-src 'self'`
  - mode: set
    directive: 'default-src'
    sources:
      - "'self'"
  # extends the ancestor directive ('default-src'), thus reuses 'self' and adds additional sources
  # results in `img-src 'self' data: https://*.typo3.org`
  - mode: extend
    directive: 'img-src'
    sources:
      - 'data:'
      - 'https://*.typo3.org'
  # extends the ancestor directive ('default-src'), thus reuses 'self' and adds additional sources
  # results in `script-src 'self' 'nonce-[random]'` ('nonce-proxy' is substituted when compiling the policy)
  - mode: extend
    directive: 'script-src'
    sources:
      - "'nonce-proxy'"
  # results in `worker-src blob:`
  - mode: set
    directive: 'worker-src'
    sources:
      - 'blob:'
Copied!

PSR-14 events

PolicyMutatedEvent

The \TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\PolicyMutatedEvent will be dispatched once all mutations have been applied to the current policy object, just before the corresponding HTTP header is added to the HTTP response object. This allows individual changes for custom implementations. Next to the Scope, the Policy's and the MutationCollection's might the Event also provide the current PSR-7 ServerRequestInterface for additional context.

InvestigateMutationsEvent

The \TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\InvestigateMutationsEvent will be dispatched when the Content-Security-Policy backend module searches for potential resolutions to a specific CSP violation report. This way, third-party integrations that rely on external resources (for example, maps, file storage, content processing/translation, ...) can provide the necessary mutations.

Feature: #99608 - Add password policy action to exclude validators in SU mode

See forge#99608

Description

The new password policy action UPDATE_USER_PASSWORD_SWITCH_USER_MODE has been added in order to allow administrators to exclude a password policy validator, if the current user is in switch user mode.

The new password policy action is used in the global default password policy for the NotCurrentPasswordValidator.

Impact

When the current backend user is in switch user mode, it is not validated, if the new password equals the current user password in ext:setup.

Feature: #99629 - Webhooks - Outgoing webhooks for TYPO3

See forge#99629

Description

A webhook is an automated message sent from one application to another via HTTP.

This feature adds the possibility to configure webhooks in TYPO3.

A new backend module System > Webhooks provides the possibility to configure webhooks. The module is available in the TYPO3 backend for users with administrative rights.

A webhook is defined as an authorized POST or GET request to a defined URL. For example, a webhook can be used to send a notification to a Slack channel when a new page is created in TYPO3.

Any webhook record is defined by a universally unique identifier (UUID), a speaking name, an optional description, a trigger, the target URL and a signing-secret. Both the unique identifier and the signing-secret are generated in the backend when a new webhook is created.

Triggers provided by the TYPO3 Core

The TYPO3 Core currently provides the following triggers for webhooks:

  • Page Modification: Triggers when a page is created, updated or deleted
  • File Added: Triggers when a file is added
  • File Updated: Triggers when a file is updated
  • File Removed: Triggers when a file is removed
  • Login Error Occurred: Triggers when a login error occurred
  • Redirect Was Hit: Triggers when a redirect has been hit

These triggers are meant as a first set of triggers that can be used to send webhooks, further triggers will be added in the future. In most projects however, it is likely that custom triggers are required.

Custom triggers

Trigger by PSR-14 events

Custom triggers can be added by creating a Message for an specific PSR-14 event and tagging that message as a webhook message.

The following example shows how to create a simple webhook message for the \TYPO3\CMS\Core\Resource\Event\AfterFolderAddedEvent :

namespace TYPO3\CMS\Webhooks\Message;

use TYPO3\CMS\Core\Attribute\WebhookMessage;
use TYPO3\CMS\Core\Messaging\WebhookMessageInterface;
use TYPO3\CMS\Core\Resource\Event\AfterFolderAddedEvent;

#[WebhookMessage(
    identifier: 'typo3/folder-added',
    description: 'LLL:EXT:webhooks/Resources/Private/Language/locallang_db.xlf:sys_webhook.webhook_type.typo3-folder-added'
)]
final class FolderAddedMessage implements WebhookMessageInterface
{
    public function __construct(
        private readonly int $storageUid,
        private readonly string $identifier,
        private readonly string $publicUrl
    ) {
    }

    public static function createFromEvent(AfterFolderAddedEvent $event): self
    {
        $file = $event->getFile();
        return new self($file->getStorage()->getUid(), $file->getIdentifier(), $file->getPublicUrl());
    }

    public function jsonSerialize(): array
    {
        return [
            'storage' => $this->storageUid,
            'identifier' => $this->identifier,
            'url' => $this->publicUrl,
        ];
    }
}
Copied!
  1. Create a final class implementing \TYPO3\CMS\Core\Messaging\WebhookMessageInterface.
  2. Add the \TYPO3\CMS\Core\Attribute\WebhookMessage attribute to the class. The attribute requires the following information:

    • identifier: The identifier of the webhook message.
    • description: The description of the webhook message. This description is used to describe the trigger in the TYPO3 backend.
  3. Add a static method createFromEvent() that creates a new instance of the message from the event you want to use as a trigger.
  4. Add a method jsonSerialize() that returns an array with the data that should be sent with the webhook.

Trigger by hooks or custom code

In case a trigger is not provided by the TYPO3 Core or a PSR-14 event is not available, it is possible to create a custom trigger - for example by using a TYPO3 hook.

The message itself should look similar to the example above, but does not need the createFromEvent() method.

Instead, the custom code (hook implementation) will create the message and dispatch it.

Example hook implementation for a DataHandler hook (see \TYPO3\CMS\Webhooks\Listener\PageModificationListener ):

public function __construct(
    protected readonly \Symfony\Component\Messenger\MessageBusInterface $bus
) {
}

public function processDatamap_afterDatabaseOperations($status, $table, $id, $fieldArray, DataHandler $dataHandler)
{
    if ($table !== 'pages') {
        return;
    }
    // ...
    $message = new PageModificationMessage(
            'new',
            $id,
            $fieldArray,
            $site->getIdentifier(),
            (string)$site->getRouter()->generateUri($id),
            $dataHandler->BE_USER,
    );
    // ...
    $this->bus->dispatch($message);
}
Copied!

Use Services.yaml instead of the PHP attribute

Instead of the PHP attribute the Services.yaml can be used to define the webhook message. The following example shows how to define the webhook message from the example above in the Services.yaml:

EXT:my_extension/Configuration/Services.yaml
TYPO3\CMS\Webhooks\Message\FolderAddedMessage:
    tags:
      - name: 'core.webhook_message'
        identifier: 'typo3/folder-added'
        description: 'LLL:EXT:webhooks/Resources/Private/Language/locallang_db.xlf:sys_webhook.webhook_type.typo3-folder-added'
Copied!

HTTP headers of every webhook

With every webhook request, the following HTTP headers are sent:

  • Content-Type: application/json
  • Webhook-Signature-Algo: sha256
  • Webhook-Signature: <hash>

The hash is calculated with the secret of the webhook and the JSON encoded data of the request. The hash is created with the PHP function hash_hmac. See the following section about the hash calculation.

Hash calculation

The hash is calculated with the following PHP code:

$hash = hash_hmac('sha256', sprintf(
    '%s:%s',
    $identifier, // The identifier of the webhook (uuid)
    $body // The JSON encoded body of the request
), $secret); // The secret of the webhook
Copied!

The hash is sent as HTTP header Webhook-Signature and should be used to validate that the request was sent from the TYPO3 instance and has not been manipulated. To verify this on the receiving end, build the hash with the same algorithm and secret and compare it with the hash that was sent with the request.

The hash is not meant to be used as a security mechanism, but as a way to verify that the request was sent from the TYPO3 instance.

Technical background and advanced usage

The webhook system is based on the Symfony Messenger component. The messages are simple PHP objects that implement an interface that denotes them as webhook messages.

That message is then dispatched to the Symfony Messenger bus. The TYPO3 Core provides a \TYPO3\CMS\Webhooks\MessageHandler\WebhookMessageHandler that is responsible for sending the webhook requests to the third-party system, if configured to do so. The handler looks up the webhook configuration and sends the request to the configured URL.

Messages are sent to the bus in any case. The handler is then responsible for checking whether or not an external request (webhook) should be sent.

If advanced request handling is necessary or a custom implementation should be used, a custom handler can be created that handles WebhookMessageInterface messages.

Impact

The TYPO3 Core now provides a convenient GUI to create and send webhooks to third-party systems. In combination with the system extension reactions TYPO3 can now be used as a low-code/no-code integration platform between multiple systems.

Feature: #99735 - New Country Select form element

See forge#99735

Description

Since Feature: #99618 - List of countries in the world and their localized names, TYPO3 provides a list of countries, together with an API and a Fluid form ViewHelper. A new "Country select" form element has now been added to the TYPO3 Form Framework for creating a country select in a form easily. The new form element features a couple of configuration options, which can either be configured via the Forms module or directly in the corresponding YAML file.

Available options

  • First option ( prependOptionLabel): Define the "empty option", i.e. the first element of the select. You can use this to provide additional guidance for the user.
  • Prioritized countries ( prioritizedCountries): Define a list of countries which should be listed as first options in the form element.
  • Only countries ( onlyCountries): Restrict the countries to be rendered in the list.
  • Exclude countries ( excludeCountries): Define which countries should not be shown in the list.

The new element will be rendered as single select ( <select>) HTML element in the frontend.

Impact

The new "Country select" form element is now available in the Form Framework with a couple of specific configuration options.

Feature: #99739 - Associative array keys for TCA items

See forge#99739

Description

It is now possible to define associative array keys for the items configuration of TCA types select, radio and check. The new keys are called: label, value, icon, group and description.

Examples:

'columns' => [
    'select' => [
        'label' => 'My select field',
        'config' => [
            'type' => 'select',
            'renderType' => 'selectSingle',
            'items' => [
                [
                    'label' => 'Selection 1',
                    'value' => '1',
                    'icon' => 'my-icon-identifier',
                    'group' => 'default',
                ],
                [
                    'label' => 'Selection 2',
                    'value' => '2',
                ],
            ],
        ],
    ],
    'select_checkbox' => [
        'label' => 'My select checkbox field',
        'config' => [
            'type' => 'select',
            'renderType' => 'selectCheckBox',
            'items' => [
                [
                    'label' => 'My select checkbox field',
                    'value' => '1',
                    'icon' => 'my-icon-identifier',
                    'group' => 'default',
                    'description' => 'My custom description',
                ],
                [
                    'label' => 'My select checkbox field',
                    'value' => '2',
                ],
            ],
        ],
    ],
    'radio' => [
        'label' => 'My radio field',
        'config' => [
            'type' => 'radio',
            'items' => [
                [
                    'label' => 'Radio 1',
                    'value' => '1',
                ],
                [
                    'label' => 'Radio 2',
                    'value' => '2',
                ],
            ],
        ],
    ],
    'check' => [
        'config' => [
            'type' => 'check',
            'items' => [
                [
                    'invertStateDisplay' => true,
                    'label' => 'Click on me',
                ],
            ],
        ],
    ],
],
Copied!

Impact

It is now much easier and clearer to define the TCA items configuration with associative array keys. The struggle to remember which option is first, label or value, is now over. In addition, optional keys like icon and group can be omitted, for example, when one desires to set the description option.

Feature: #99802 - New PSR-14 ModifyRedirectManagementControllerViewDataEvent

See forge#99802

Description

A new PSR-14 event \TYPO3\CMS\Redirects\Event\ModifyRedirectManagementControllerViewDataEvent is introduced, allowing extension authors to modify or enrich view data for the \TYPO3\CMS\Redirects\Controller\ManagementController . This allows to display more or other information along the way.

This event features the following methods:

  • getDemand(): Return the demand object used to retrieve the redirects
  • getRedirects(): Return the retrieved redirects
  • setRedirects(): Can be used to set the redirects, for example, after enriching redirect fields
  • getRequest(): Return the current request
  • getHosts(): Returns the hosts to be used for the host filter select-box
  • setHosts(): Can be used to update which hosts are available in the filter select-box
  • getStatusCodes(): Returns the status codes for the filter select box
  • setStatusCodes(): Can be used to update which status codes are available in the filter select-box
  • getCreationTypes(): Returns creation types for the filter select box
  • setCreationTypes(): Can be used to update which creation types are available in the filter select-box
  • getShowHitCounter(): Returns if hit counter should be displayed
  • setShowHitCounter(): Can be used to manage if the hit counter should be displayed
  • getView(): Returns the current view object, without controller data assigned yet
  • setView(): Can be used to assign additional data to the view

For example, this event can be used to add additional information to current page records.

Therefore, it can be used to generate custom data, directly assigning to the view. With overriding the backend view template via page TSconfig this custom data can be displayed where it is needed, and rendered the way it is wanted.

Example:

Registration of the event listener:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\Redirects\MyEventListener:
  tags:
    - name: event.listener
      identifier: 'my-extension/modify-redirect-management-controller-view-data'
Copied!

The corresponding event listener class:

EXT:my_extension/Classes/Redirects/MyEventListener.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Redirects;

use TYPO3\CMS\Redirects\Event\ModifyRedirectManagementControllerViewDataEvent;

final class MyEventListener {

    public function __invoke(
        ModifyRedirectManagementControllerViewDataEvent $event
    ): void {
        $hosts = $event->getHosts();

        // remove wildcard host from list
        $hosts = array_filter($hosts, static fn ($host) => $host['name'] !== '*');

        // update changed hosts list
        $event->setHosts($hosts);
    }
}
Copied!

Impact

With the new ModifyRedirectManagementControllerViewDataEvent, it is now possible to modify view data or inject further data to the view for the management view of redirects.

Feature: #99803 - New PSR-14 BeforeRedirectMatchDomainEvent

See forge#99803

Description

A new PSR-14 event \TYPO3\CMS\Redirects\Event\BeforeRedirectMatchDomainEvent is introduced to the \TYPO3\CMS\Redirects\Service\RedirectService , allowing extension authors to implement a custom redirect matching upon the loaded redirects or return matched redirect record from other sources.

This event features following methods:

  • getDomain(): Returns the domain for which redirects should be checked for, "*" for all domains.
  • getPath(): Returns the path which should be checked.
  • getQuery(): Returns the query part which should be checked.
  • getMatchDomainName(): Returns current check domain name.
  • getMatchedRedirect(): Returns the matched sys_redirect record, set by another event listener or null.
  • setMatchedRedirect(): Can be used to clear prior matched redirect by setting it to null or set a matched sys_redirect record.

Example:

Registration of the event listener:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\Redirects\MyEventListener:
  tags:
    - name: event.listener
      identifier: 'my-extension/before-redirect-match-domain'
Copied!

The corresponding event listener class:

EXT:my_extension/Classes/Redirects/MyEventListener.php
namespace MyVendor\MyExtension\Redirects;

use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Redirects\Event\BeforeRedirectMatchDomainEvent;

final class MyEventListener
{
    public function __invoke(BeforeRedirectMatchDomainEvent $event): void
    {
        $matchedRedirectRecord = $this->customRedirectMatching($event);
        if ($matchedRedirectRecord !== null) {
            $event->setMatchedRedirect($matchedRedirectRecord);
        }
    }

    private function customRedirectMatching(
        BeforeRedirectMatchDomainEvent $event
    ): ?array {

        // @todo Implement custom redirect record loading and matching. If
        //       a redirect based on custom logic is determined, return the
        //       :sql:`sys_redirect` tables conform redirect record.

        // Note: Below is simplified example code with no real value.
        $record = BackendUtility::getRecord('sys_redirect', 123);

        // Do custom matching logic against the record and return matched
        // record - if there is one.
        if ($record
            && /* custom condition against the record */
        ) {
            return $record;
        }

        // return null to indicate that no matched redirect could be found
        return null;
    }
}
Copied!

Impact

With the new BeforeRedirectMatchDomainEvent it is now possible to implement custom redirect matching methods before core matching is processed.

Feature: #99834 - New PSR-14 AfterAutoCreateRedirectHasBeenPersistedEvent

See forge#99834

Description

A new PSR-14 event \TYPO3\CMS\Redirects\Event\AfterAutoCreateRedirectHasBeenPersistedEvent is introduced, allowing extension authors to react on persisted auto-created redirects. This can be used to call external API or do other tasks based on the real persisted redirects.

Example:

Registration of the event listener:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\Redirects\MyEventListener:
  tags:
    - name: event.listener
      identifier: 'my-extension/after-auto-create-redirect-has-been-persisted'
Copied!

The corresponding event listener class:

EXT:my_extension/Classes/Redirects/MyEventListener.php
namespace MyVendor\MyExtension\Redirects;

use TYPO3\CMS\Redirects\Event\AfterAutoCreateRedirectHasBeenPersistedEvent;
use TYPO3\CMS\Redirects\RedirectUpdate\PlainSlugReplacementRedirectSource;

class MyEventListener {

    public function __invoke(
        AfterAutoCreateRedirectHasBeenPersistedEvent $event
    ): void {
        $redirectUid = $event->getRedirectRecord()['uid'] ?? null;
        if ($redirectUid === null
            && !($event->getSource() instanceof PlainSlugReplacementRedirectSource)
        ) {
            return;
        }

        // Implement code what should be done with this information. E.g.
        // write to another table, call a rest api or similar. Find your
        // use-case.
    }
}
Copied!

Impact

With the new AfterAutoCreateRedirectHasBeenPersistedEvent, it is now possible to react on persisted auto-created redirects. Manually created redirects can be handled by using one of the available \TYPO3\CMS\Core\DataHandling\DataHandler hooks, not suitable for auto-created redirects.

Feature: #99834 - New PSR-14 ModifyAutoCreateRedirectRecordBeforePersistingEvent

See forge#99834

Description

A new PSR-14 \TYPO3\CMS\Redirects\Event\ModifyAutoCreateRedirectRecordBeforePersistingEvent is introduced, allowing extension authors to modify the redirect record before it is persisted to the database. This can be used to change values based on circumstances, for example, like different sub tree settings, not covered by the Core site configuration. Another use-case could be to write data to additional sys_redirect columns added by a custom extension for later use.

Example:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\Redirects\MyEventListener:
  tags:
    - name: event.listener
      identifier: 'my-extension/modify-auto-create-redirect-record-before-persisting'
Copied!

The corresponding event listener class:

EXT:my_extension/Classes/Redirects/MyEventListener.php
namespace MyVendor\MyExtension\Redirects;

use TYPO3\CMS\Redirects\Event\ModifyAutoCreateRedirectRecordBeforePersistingEvent;
use TYPO3\CMS\Redirects\RedirectUpdate\PlainSlugReplacementRedirectSource;

final class MyEventListener {

    public function __invoke(
        ModifyAutoCreateRedirectRecordBeforePersistingEvent $event
    ): void {

        // only work on plain slug replacement redirect sources.
        if (!($event->getSource() instanceof PlainSlugReplacementRedirectSource)) {
            return;
        }

        // Get prepared redirect record and change some values
        $record = $event->getRedirectRecord();

        // override the status code, eventually to another value than
        // configured in the site configuration
        $record['status_code'] = 307;

        // Set value to a field extended by a custom extension, to persist
        // additional data to the redirect record.
        $record['custom_field_added_by_a_extension']
            = 'page_' . $event->getSlugRedirectChangeItem()->getPageId();

        // Update changed record in event to ensure changed values are saved.
        $event->setRedirectRecord($record);
    }
}
Copied!

Impact

With the new ModifyAutoCreateRedirectRecordBeforePersistingEvent, it is now possible to modify the auto-create redirect record before it is persisted to the database. Manually created redirects or updated redirects can be handled by using the well-known \TYPO3\CMS\Core\DataHandling\DataHandler and the available hooks.

Feature: #99861 - Add tile view to element browser

See forge#99861

Description

The file list is the default implementation for TYPO3 to navigate and manage assets. This patch extends the usage of the file list to the element browser, the build-in component to select the assets for file fields and folder fields in the backend.

Impact

The rendering of files and folder now deliver a unified experience and allow the user to use the tile view to select assets.

The search within the file browser now respects the selected folder and searches all subfolders for the provided search term.

To have an even more reliable experience, the user will now always start the selection process in the root folder of the default storage.

Resource tiles are now adapting to the surrounding container instead of the viewport, to make better use of the available space.

The file list now holds all related code to the file and folder browser.

Feature: #99874 - Edit task groups within the Scheduler module

See forge#99874

Description

Task groups can be managed in the backend module itself. Users can create, update and delete task groups within the Scheduler module. Sorting is done via drag&drop (drag the panel header) and inline-style editing is used to change the title name. Only empty groups may be deleted.

Impact

Users may edit groups in the Scheduler module.

Feature: #99976 - Introduce ignoreFlexFormSettingsIfEmpty Extbase configuration

See forge#99976

Description

It is now possible to exclude empty FlexForm settings from being merged into Extbase extension settings. Extension authors and integrators can use the new Extbase TypoScript configuration ignoreFlexFormSettingsIfEmpty to define FlexForm settings, which will be ignored in the merge process of the extension settings, if their value is considered empty (either an empty string or a string containing 0).

In the following example, settings.showForgotPassword and settings.showPermaLogin from FlexForm will not be merged into extension settings, if the individual value is empty:

plugin.tx_felogin_login.ignoreFlexFormSettingsIfEmpty = showForgotPassword,showPermaLogin
Copied!

If an extension already defined ignoreFlexFormSettingsIfEmpty, integrators are advised to use addToList or removeFromList to modify existing settings as shown in the following example:

plugin.tx_felogin_login.ignoreFlexFormSettingsIfEmpty := removeFromList(showForgotPassword)
plugin.tx_felogin_login.ignoreFlexFormSettingsIfEmpty := addToList(domains)
Copied!

It is possible to define the ignoreFlexFormSettingsIfEmpty configuration globally for an extension using the plugin.tx_extension TypoScript configuration or for an individual plugin using the plugin.tx_extension_plugin TypoScript configuration.

Extension authors can use the new PSR-14 event \TYPO3\CMS\Extbase\Event\Configuration\BeforeFlexFormConfigurationOverrideEvent to implement a FlexForm override process in a custom extension based on the original FlexForm configuration and the framework configuration.

Additionally, the new Extbase TypoScript configuration is used in EXT:felogin to ensure that empty FlexForm settings are not merged into extension settings.

Event example

Register an event listener in your Services.yaml file:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\FlexForm\EventListener\MyEventListener:
  tags:
    - name: event.listener
      identifier: 'my-extension/custom-absolute-path'
Copied!

Implement the event listener:

EXT:my_extension/Classes/FlexForm/EventListener/MyEventListener.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\FlexForm\EventListener;

use TYPO3\CMS\Extbase\Event\Configuration\BeforeFlexFormConfigurationOverrideEvent;

final class MyEventListener
{
    public function __invoke(BeforeFlexFormConfigurationOverrideEvent $event): void
    {
        // Configuration from TypoScript
        $frameworkConfiguration = $event->getFrameworkConfiguration();

        // Configuration from FlexForm
        $originalFlexFormConfiguration = $event->getOriginalFlexFormConfiguration();

        // Currently merged configuration
        $flexFormConfiguration = $event->getFlexFormConfiguration();

        // Implement custom logic
        $flexFormConfiguration['settings']['foo'] = 'set from event listener';
        $event->setFlexFormConfiguration($flexFormConfiguration);
    }
}
Copied!

Impact

Empty FlexForm extension settings can now conditionally be excluded from the FlexForm configuration merge process.

Also, it is now possible again to use global TypoScript extension settings in EXT:felogin, which previously might have been overridden by empty FlexForm settings.

In addition, with the new BeforeFlexFormConfigurationOverrideEvent it is now possible to further manipulate the merged configuration after standard override logic is applied.

Feature: #100027 - Copy files and folders within the File > List module

See forge#100027

Description

With TYPO3 v12.2, the feature to drag+drop files and folders between the tree structure was added. Now it is also possible to copy or move resources within the actual file listing (tile view or list view), for example, into a different subfolder by selecting them, and using the mouse to drop them on to a target folder.

Impact

The File > List module is now fully usable with drag+drop between the tree and within the listing itself.

All features make it easier for editors to manage and organize the digital assets used within TYPO3.

Feature: #100071 - Introduce non-magic repository find methods

See forge#100071

Description

Extbase repositories come with a magic __call() method to allow calling the following methods without implementing:

  • findBy[PropertyName]($propertyValue)
  • findOneBy[PropertyName]($propertyValue)
  • countBy[PropertyName]($propertyValue)

Magic methods are quite handy but they have a huge disadvantage. There is no proper IDE support i.e. most IDEs show an error or at least a warning, saying method findByAuthor() does not exist. Also, type declarations are impossible to use because with __call() everything is mixed. And last but not least, static code analysis - like PHPStan - cannot properly analyze those and give meaningful errors.

Therefore, there is a new set of methods without all those downsides:

  • findBy(array $criteria, ...): QueryResultInterface
  • findOneBy(array $criteria, ...): object|null
  • count(array $criteria, ...): int

The naming of those methods follows those of doctrine/orm and only count() differs from the formerly countBy(). While all magic methods only allow for a single comparison (propertyName = propertyValue), those methods allow for multiple comparisons, called constraints.

Example:

$this->blogRepository->findBy(['author' => 1, 'published' => true]);
Copied!

Impact

The new methods support a broader feature set, support IDEs, static code analyzers and type declarations.

Feature: #100088 - New TCA type "json"

See forge#100088

Description

In our effort of introducing dedicated TCA types for special use cases, a new TCA field type called json has been added to TYPO3 Core. Its main purpose is to simplify the TCA configuration when working with fields, containing JSON data. It therefore replaces the previously introduced dbtype=json of TCA type user.

Using the new type, TYPO3 automatically takes care of adding the corresponding database column.

The TCA type json features the following column configuration:

  • behaviour: allowLanguageSynchronization
  • cols
  • default
  • enableCodeEditor
  • fieldControl
  • fieldInformation
  • fieldWizard
  • placeholder
  • readOnly
  • required
  • rows

The following column configuration can be overwritten by page TSconfig:

  • cols
  • rows
  • readOnly

Impact

It is now possible to use a dedicated TCA type for rendering of JSON fields. Using the new TCA type, corresponding database columns are added automatically.

Feature: #100089 - Introduce Doctrine DBAL v3 driver middlewares

See forge#100089

Description

Since v3, Doctrine DBAL supports adding custom driver middlewares. These middlewares act as a decorator around the actual Driver component. Subsequently, the Connection, Statement and Result components can be decorated as well. These middlewares must implement the \Doctrine\DBAL\Driver\Middleware interface. A common use case would be a middleware for implementing SQL logging capabilities.

For more information on driver middlewares, see https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/architecture.html. Furthermore, you can look up the implementation of the \TYPO3\CMS\Adminpanel\Log\DoctrineSqlLoggingMiddleware in ext:adminpanel as an example.

Registering a new driver middleware

$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driverMiddlewares']['adminpanel_loggingmiddleware']
    = \TYPO3\CMS\Adminpanel\Log\DoctrineSqlLoggingMiddleware::class;
Copied!

Impact

Using custom middlewares allows to enhance the functionality of Doctrine components.

Feature: #100093 - Show path to record location in group elements

See forge#100093

Description

To ease the usage of group fields in the FormEngine, for example, like in the "Insert records" content element, the record overview now shows the path to the location where each assigned record is stored, respectively.

Impact

Elements of type group now show the path to the page where any assigned record is stored in.

Feature: #100116 - Make PSR-7 request accessible for authentication services

See forge#100116

Description

Authentication services can now access the PSR-7 request object via the $authInfo array. Previously, custom TYPO3 authentication services did not have direct access to the object and therefore had to either use PHP super globals or TYPO3's GeneralUtility::getIndpEnv() method.

The following example shows how to retrieve the PSR-7 request in the initAuth() method of a custom authentication service:

public function initAuth($mode, $loginData, $authInfo, $pObj)
{
    /** @var ServerRequestInterface $request */
    $request = $authInfo['request'];

    /** @var NormalizedParams $normalizedParams */
    $normalizedParams = $request->getAttribute('normalizedParams');
    $isHttps = $normalizedParams->isHttps();
}
Copied!

Impact

Custom TYPO3 authentication services can now directly access the PSR-7 request object from the authentication process. It is available via the request key of the $authInfo array, which is handed over to the initAuth() method.

Feature: #100143 - Add scheduler command to execute and list tasks

See forge#100143

Description

The CLI command scheduler:run of EXT:scheduler offers a way to run a task using a cronjob. It also allows to run tasks if the UID of the task is known.

To make it more convenient to use the command, scheduler:list and scheduler:execute were introduced.

The scheduler:list command shows an overview of all available tasks or a given group with an option to watch and reload the list every X seconds (default every 1 second).

Example:

# List all tasks in group 1 and group 2 and watch for changes every second.
vendor/bin/typo3 scheduler:list --group 1 --group 2 --watch

# List all tasks without a group and watch for changes every 2 seconds.
vendor/bin/typo3 scheduler:list --group 0 --watch 2

# Same as above with shortcut parameter
vendor/bin/typo3 scheduler:list -g 0 -w 2
Copied!

The scheduler:execute command displays a list of groups and available tasks for the selection. If a group is selected all tasks within this group are executed.

Example:

# Run alls tasks without a group and task 8
vendor/bin/typo3 scheduler:execute --task g:0 --task 8

# Same as above with shortcut parameter
vendor/bin/typo3 scheduler:execute -t g:0 -t 8
Copied!

Impact

The new commands scheduler:list and scheduler:execute enable the user to manage and run tasks without leaving the terminal.

Feature: #100167 - AdminPanel: Add SQL and memory metrics to toolbar

See forge#100167

Description

This extends the AdminPanel toolbar with more metrics:

  • Peak memory usage
  • Amount of SQL queries
  • Time spent processing SQL queries

Impact

The AdminPanel toolbar now shows more information.

Feature: #100171 - Introduce TCA type uuid

See forge#100171

Description

In our effort of introducing dedicated TCA types for special use cases, a new TCA field type called uuid has been added to TYPO3 Core. Its main purpose is to simplify the TCA configuration when working with fields, containing a UUID.

The TCA type uuid features the following column configuration:

  • enableCopyToClipboard
  • fieldInformation
  • required: Defaults to true
  • size
  • version

The following column configuration can be overwritten by page TSconfig:

  • size
  • enableCopyToClipboard

An example configuration looks like the following:

'identifier' => [
    'label' => 'My record identifier',
    'config' => [
        'type' => 'uuid',
        'version' => 6,
    ],
],
Copied!

Impact

It is now possible to use a dedicated TCA type for rendering of a UUID field. Using the new TCA type, corresponding database columns are added automatically.

Feature: #100187 - ICU-based date and time formatting

See forge#100187

Description

TYPO3 now supports rendering date and time based on formats/patterns defined by the International Components for Unicode standard (ICU).

TYPO3 previously only supported rendering of dates based on the PHP-native functions date() and strftime().

However, date() can only format dates with English texts, such as "December" as non-localized values, the C-based strftime() function works only with the locale defined in PHP and availability in the underlying operating system.

In addition, ICU-based date and time formatting is much more flexible in rendering, as it ships with default patterns for date and time (namely FULL, LONG, MEDIUM and SHORT) which are based on the given locale.

This means, that when the locale en-US is given, the short date is rendered as mm/dd/yyyy whereas de-AT uses the dd.mm.yyyy syntax automatically, without having to define a custom pattern just by using the SHORT default pattern.

In addition, the patterns can be adjusted more fine-grained, and can easily deal with time zones for output when DateTime objects are handed in.

TYPO3 also adds prepared custom patterns:

  • FULLDATE (like FULL, but only the date information)
  • FULLTIME (like FULL, but only the time information)
  • LONGDATE (like LONG, but only the date information)
  • LONGTIME (like LONG, but only the time information)
  • MEDIUMDATE (like MEDIUM, but only the date information)
  • MEDIUMTIME (like MEDIUM, but only the time information)
  • SHORTDATE (like SHORT, but only the date information)
  • SHORTTIME (like SHORT, but only the time information)

See https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax for more information on the patterns.

Impact

A new stdWrap feature called formattedDate is added, and the new formatting can also be used in Fluid's <f:format.date> ViewHelper.

The locale is typically fetched from the locale of the site language (stdWrap or ViewHelper), or the backend user's language (in backend context) for the ViewHelper usages.

Examples for stdWrap:

page.10 = TEXT
page.10.value = 1998-02-20 3:00:00
# see all available options https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
page.10.formattedDate = FULL
# optional, if a different locale is wanted other than the Site Language's locale
page.10.formattedDate.locale = de-DE
Copied!

will result in "Freitag, 20. Februar 1998 um 03:00:00 Koordinierte Weltzeit".

page.10 = TEXT
page.10.value = -5 days
page.10.formattedDate = FULL
page.10.formattedDate.locale = fr-FR
Copied!

will result in "jeudi 9 mars 2023 à 21:40:49 temps universel coordonné".

Examples for Fluid <f:format.date> ViewHelper:

<f:format.date pattern="dd. MMMM yyyy" locale="de-DE">{date}</f:format.date>
Copied!

will result in "20. Februar 1998".

As soon as the pattern attribute is used, the format attribute is disregarded.

Both new ViewHelper arguments are optional.

Feature: #100218 - Improved TypoScript and page TSconfig modules

See forge#100218

Description

TYPO3 v12 comes with a rewritten TypoScript syntax parser. See Breaking: #97816 - TypoScript syntax changes and Feature: #97816 - TypoScript syntax improvements for more details on this.

The new parser allowed us to refactor the related backend modules along the way: While many of these have been done with earlier v12 releases already, v12.3 now finishes the basic feature set of these new and refactored modules.

This is a summary of these UI changes:

Frontend TypoScript

  • The well-known main module Web > Template has been renamed and moved, and can be found as Site Management > TypoScript.
  • "TypoScript records overview": This submodule was more hidden in previous versions. It gives an overview which page records have TypoScript template records.
  • "Constant Editor": This submodule is mainly kept as-is from previous versions.
  • "Edit TypoScript Record": This submodule was known as "Info / Modify" from previous versions. Its main functionality is kept.
  • "Active TypoScript": This submodule was known as "TypoScript Object Browser" in previous versions. The UI of this module received a major streamlining and gives a better overview of the compiled TypoScript on a page: The module now shows both "constants" and "setup" at the same time, gives more detail information, and the tree is quicker to navigate.
  • "Included TypoScript": This submodule was known as "Template Analyzer" in previous versions. Similar to "Active TypoScript", it shows "constants" and "setup" at the same time. It allows to simulate the effect of conditions to the include tree, and shows sub-includes from @import and similar as nodes within the tree. A basic syntax scanner finds broken TypoScript syntax snippets.

Page TSconfig

  • The previous submodule Web > Info > Page TSconfig has been heavily refactored and can be found as new main module Site Management > Page TSconfig.
  • The new page TSconfig module is similar in its look and feel to the TypoScript module.
  • "Page TSconfig Records": This submodule did not exist as such in previous versions and gives an overview which page records in the system contain page TSconfig settings.
  • "Active Page TSconfig": This is similar to "Active TypoScript" from the "TypoScript" module. It allows browsing current page TSconfig and allows simulating the effect of conditions.
  • "Included page TSconfig": This is similar to the "Included TypoScript" from the "TypoScript" module. It shows all source files and records that create the final page TSconfig of a page. A basic syntax scanner finds broken syntax snippets.

Impact

The refactored modules allow more fine grained analysis of page TSconfig and TypoScript.

Feature: #100232 - Load additional stylesheets in TYPO3 backend

See forge#100232

Description

It is now possible to load additional CSS files for the TYPO3 backend interface via regular $TYPO3_CONF_VARS settings in a settings.php file of a project (previously known as LocalConfiguration.php) file or in an extension's ext_localconf.php.

Previously this was done via the outdated $TBE_STYLES global array which has been deprecated.

Impact

By defining a specific stylesheet, a single CSS file or all CSS files of a folder, extension authors can now modify the styling via:

EXT:my_extension/ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['BE']['stylesheets'][my_extension]
    = 'EXT:myextension/Resources/Public/Css/myfile.css';

$GLOBALS['TYPO3_CONF_VARS']['BE']['stylesheets'][my_extension]
    = 'EXT:myextension/Resources/Public/Css/';
Copied!

in their extension's ext_localconf.php file.

Site administrators can handle this in their settings.php or additional.php file.

Feature: #100278 - PSR-14 Event after failed logins in backend or frontend users

See forge#100278

Description

A new PSR-14 event \TYPO3\CMS\Core\Authentication\Event\LoginAttemptFailedEvent has been introduced. The event allows to notify remote systems about failed logins.

The event features the following methods:

  • isFrontendAttempt(): Whether this was a login attempt from a frontend login form
  • isBackendAttempt(): Whether this was a login attempt in the backend
  • getUser(): Returns the \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication derivative in question
  • getRequest(): Returns the current PSR-7 request object
  • getLoginData(): The attempted login data without sensitive information

Registration of the event in your extension's Services.yaml:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\Authentication\EventListener\MyEventListener:
    tags:
      - name: event.listener
        identifier: 'my-extension/login-attempt-failed'
Copied!

The corresponding event listener class:

EXT:my_extension/Classes/Authentication/EventListener/MyEventListener.php
namespace MyVendor\MyExtension\Authentication\EventListener;

use TYPO3\CMS\Core\Authentication\Event\LoginAttemptFailedEvent;

final class MyEventListener
{
    public function __invoke(LoginAttemptFailedEvent $event): void
    {
        if ($event->getRequest()->getAttribute('normalizedParams')->getRemoteAddress() !== '198.51.100.42') {
            // send an email because an external user login attempt failed
        }
    }
}
Copied!

Impact

It is now possible to notify external loggers about failed login attempts while having the full request.

Feature: #100284 - Add CKEditor Inspector for backend RTE forms

See forge#100284

Description

This feature introduces the ability to show the CKEditor Inspector for backend RTE forms.

With CKEditor 5 and the introduction of the intermediate CKEditor model, knowing the internals is a requirement to build plugins. The best way to debug during the plugin development is the CKEditor Inspector.

For regular pages, there is a simple bookmarklet that can be included to show the Inspector, but in the TYPO3 backend the usage of frames does not allow this option. Giving developers a config option in the RTE simplifies this process.

The Inspector can be activated in two different ways:

  • By enabling $GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] and being in the Development context
  • By setting the option editor.config.debug to true in your CKEditor configuration

Example for setting the CKEditor configuration:

editor:
  config:
    debug: true
Copied!

Impact

Being in the right context or enabling the given option, it is now possible to debug CKEditor instances for plugin development in an easier way.

Feature: #100293 - New ContentObject EXTBASEPLUGIN in TypoScript

See forge#100293

Description

In order to lower the barrier for newcomers in the TYPO3 world, TYPO3 now has a custom ContentObject in TypoScript called EXTBASEPLUGIN.

Previously, TypoScript code for Extbase plugins looked like this:

page.10 = USER
page.10 {
    userFunc = TYPO3\\CMS\\Extbase\\Core\\Bootstrap->run
    extensionName = shop
    pluginName = cart
}
Copied!

The new way, which Extbase plugin registration uses under the hood now, looks like this:

page.10 = EXTBASEPLUGIN
page.10.extensionName = shop
page.10.pluginName = cart
Copied!

The old way still works, but it is recommended to use the EXTBASEPLUGIN ContentObject, as the direct reference to a PHP class (Bootstrap) might be optimized in future versions.

Impact

This change is an effort to distinguish between plugins and regular other more static content.

Extbase is the de-facto standard for plugins, which serve dynamic content by custom PHP code divided in controllers and actions by extension developers.

Regular other content can be written in pure TypoScript, such as ContentObjects like FLUIDTEMPLATE, HMENU, COA or TEXT is used for other kind of renderings in the frontend.

Feature: #100294 - Add PSR-14 event to enrich password validation ContextData

See forge#100294

Description

A new PSR-14 event \TYPO3\CMS\Core\PasswordPolicy\Event\EnrichPasswordValidationContextDataEvent has been added, which allows extension authors to enrich the \TYPO3\CMS\Core\PasswordPolicy\Validator\Dto\ContextData DTO used in password policy validation.

The PSR-14 event is dispatched in all classes, where a user password is validated against the globally configured password policy.

The event features the following methods:

  • getContextData() returns the current ContextData DTO
  • getUserData() returns an array with user data available from the initiating class
  • getInitiatingClass() returns the class name, where the ContextData DTO is created

The event can be used to enrich the ContextData DTO with additional data used in custom password policy validators.

Registration of the event in your extension's Services.yaml:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\PasswordPolicy\EventListener\MyEventListener:
  tags:
    - name: event.listener
      identifier: 'my-extension/enrich-context-data'
Copied!

The corresponding event listener class:

EXT:my_extension/Classes/PasswordPolicy/EventListener/MyEventListener.php
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\PasswordPolicy\Event\EnrichPasswordValidationContextDataEvent;

final class MyEventListener
{
    public function __invoke(EnrichPasswordValidationContextDataEvent $event): void
    {
        if ($event->getInitiatingClass() === DataHandler::class) {
            $event->getContextData()->setData('currentMiddleName', $event->getUserData()['middle_name'] ?? '');
            $event->getContextData()->setData('currentEmail', $event->getUserData()['email'] ?? '');
        }
    }
}
Copied!

Impact

With the new EnrichPasswordValidationContextDataEvent, it is now possible to enrich the ContextData DTO used in password policy validation with additional data.

Feature: #100307 - PSR-14 events for user login & logout

See forge#100307

Description

Three new PSR-14 events have been added:

  • \TYPO3\CMS\Core\Authentication\Event\BeforeUserLogoutEvent
  • \TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedOutEvent
  • \TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent

The purpose of these events is to trigger any kind of action when a user has been successfully logged in or logged out.

TYPO3 Core itself uses AfterUserLoggedInEvent in the TYPO3 backend to send an email to a user, if the login was successful.

The event features the following methods:

  • getUser(): Returns the \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication derivative in question

The PSR-14 event BeforeUserLogoutEvent on top has the possibility to bypass the regular logout process by TYPO3 (removing the cookie and the user session) by calling $event->disableRegularLogoutProcess() in an event listener.

The PSR-14 event AfterUserLoggedInEvent contains the method getRequest() to return PSR-7 request object of the current request.

Registration of the event in your extension's Services.yaml:

EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\Authentication\EventListener\MyEventListener:
    tags:
      - name: event.listener
        identifier: 'my-extension/after-user-logged-in'
Copied!

The corresponding event listener class for AfterUserLoggedInEvent:

EXT:my_extension/Classes/Authentication/EventListener/MyEventListener.php
namespace MyVendor\MyExtension\Authentication\EventListener;

use TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent;

final class MyEventListener
{
    public function __invoke(AfterUserLoggedInEvent $event): void
    {
        if (
            $event->getUser() instanceof BackendUserAuthentication
            && $event->getUser()->isAdmin()
        )
        {
            // Do something like: Clear all caches after login
        }
    }
}
Copied!

Impact

It is now possible to modify and adapt user functionality based on successful login or active logout.

Deprecation: #83608 - Backend user's getDefaultUploadFolder hook

See forge#83608

Description

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getDefaultUploadFolder'] has been marked as deprecated in favor of a new PSR-14 event AfterDefaultUploadFolderWasResolvedEvent.

Along with the hook, the two methods:

  • BackendUserAuthentication->getDefaultUploadFolder()
  • BackendUserAuthentication->getDefaultUploadTemporaryFolder()

have been marked as internal, as they are not considered part of the public TYPO3 API anymore.

Impact

Using this hook will trigger a PHP deprecation notice every time the method BackendUserAuthentication->getDefaultUploadFolder() is called,

Affected installations

TYPO3 installations with special functionality in extensions using these methods or the hook.

Migration

Migrate to the PSR-14 event AfterDefaultUploadFolderWasResolvedEvent in your custom extensions.

It is fired after various page TSconfig settings have been applied and allows for more fine-grained control.

Deprecation: #97390 - TypoScript validators for password reset in ext:felogin

See forge#97390

Description

The TypoScript password validation configured through plugin.tx_felogin_login.settings.passwordValidators has been marked as deprecated.

The TypoScript validators are used when the feature toggle security.usePasswordPolicyForFrontendUsers is set to false (default for existing TYPO3 installations).

An upgrade wizard will ask the user during the TYPO3 upgrade if security.usePasswordPolicyForFrontendUsers should be activated or, if deprecated, TypoScript validators should be used.

Impact

Validators configured in plugin.tx_felogin_login.settings.passwordValidators will trigger a deprecation log entry when a password reset is performed.

Affected installations

TYPO3 installations using validators configured in plugin.tx_felogin_login.settings.passwordValidators.

Migration

Special password requirements configured using custom validators in TypoScript must be migrated to a custom password policy validator as described in #97388.

Before creating a custom password policy validator, it is recommended to check if the CorePasswordValidator used in the default password policy suits current password requirements.

Deprecation: #99739 - Indexed array keys for TCA items

See forge#99739

Description

Using indexed array keys for the items configuration of TCA types select, radio and check is now deprecated.

Impact

Using indexed array keys for the items configuration array items of TCA types select, radio and check will trigger a deprecation level log entry. A TCA migration is in place.

Affected installations

All installations having custom extensions that make use of TCA types select, radio or check and define at least one entry in the items array.

itemsProcFunc

The items array handed over to custom itemsProcFunc functions contains the new object type \TYPO3\CMS\Core\Schema\Struct\SelectionItem which acts as a compatibility layer for old style indexed keys. Accessing, writing and reading items still work in the old way. Added items will be automatically converted. For third-party extensions supporting both TYPO3 v11 (or lower) and v12 it is recommended to keep using indexed keys.

Migration

To migrate your TCA, change all indexed keys according to the following mapping table:

Before After
0 label
1 value
2 icon
3 group
4 description

Examples:

// Before
'select' => [
    'label' => 'My select field',
    'config' => [
        'type' => 'select',
        'renderType' => 'selectSingle',
        'items' => [
            [
                'Selection 1',
                '1',
                'my-icon-identifier',
                'default',
            ],
            [
                0 => 'Selection 2',
                1 => '2',
            ],
        ],
    ],
],

// After
'select' => [
    'label' => 'My select field',
    'config' => [
        'type' => 'select',
        'renderType' => 'selectSingle',
        'items' => [
            [
                'label' => 'Selection 1',
                'value' => '1',
                'icon' => 'my-icon-identifier',
                'group' => 'default',
            ],
            [
                'label' => 'Selection 2',
                'value' => '2',
            ],
        ],
    ],
],

// Before
'select_checkbox' => [
    'label' => 'My select checkbox field',
    'config' => [
        'type' => 'select',
        'renderType' => 'selectCheckBox',
        'items' => [
            [
                'My select checkbox field',
                '1',
                'my-icon-identifier',
                'default',
                'My custom description',
            ],
            [
                0 => 'My select checkbox field',
                1 => 'value' => '2',
            ],
        ],
    ],
],

// After
'select_checkbox' => [
    'label' => 'My select checkbox field',
    'config' => [
        'type' => 'select',
        'renderType' => 'selectCheckBox',
        'items' => [
            [
                'label' => 'My select checkbox field',
                'value' => '1',
                'icon' => 'my-icon-identifier',
                'group' => 'default',
                'description' => 'My custom description',
            ],
            [
                'label' => 'My select checkbox field',
                'value' => '2',
            ],
        ],
    ],
],

// Before
'radio' => [
    'label => 'My radio field',
    'config' => [
        'type' => 'radio',
        'items' => [
            [
                'Radio 1',
                '1',
            ],
            [
                0 => 'Radio 2',
                1 => '2',
            ],
        ],
    ],
],

// After
'radio' => [
    'label => 'My radio field',
    'config' => [
        'type' => 'radio',
        'items' => [
            [
                'label' => 'Radio 1',
                'value' => '1',
            ],
            [
                'label' => 'Radio 2',
                'value' => '2',
            ],
        ],
    ],
],

// Before
'check' => [
    'config' => [
        'type' => 'check',
        'items' => [
            ['Click on me'],
        ],
    ],
],

// After
'check' => [
    'config' => [
        'type' => 'check',
        'items' => [
            ['label' => 'Click on me'],
        ],
    ],
],

// Before
'check' => [
    'config' => [
        'type' => 'check',
        'items' => [
            [
                'invertStateDisplay' => true,
                0 => 'Click on me',
            ],
        ],
    ],
],

// After
'check' => [
    'config' => [
        'type' => 'check',
        'items' => [
            [
                'invertStateDisplay' => true,
                'label' => 'Click on me',
            ],
        ],
    ],
],
Copied!

Before:

<select_single_1>
    <label>select_single_1 description</label>
    <description>field description</description>
    <config>
        <type>select</type>
        <renderType>selectSingle</renderType>
        <items>
            <numIndex index="0">
                <numIndex index="0">foo1</numIndex>
                <numIndex index="1">foo1</numIndex>
            </numIndex>
            <numIndex index="1">
                <numIndex index="0">foo2</numIndex>
                <numIndex index="1">foo2</numIndex>
            </numIndex>
        </items>
    </config>
</select_single_1>
Copied!

After:

<select_single_1>
    <label>select_single_1 description</label>
    <description>field description</description>
    <config>
        <type>select</type>
        <renderType>selectSingle</renderType>
        <items>
            <numIndex index="0">
                <label>foo1</label>
                <value>foo1</value>
            </numIndex>
            <numIndex index="1">
                <label>foo2</label>
                <value>foo2</value>
            </numIndex>
        </items>
    </config>
</select_single_1>
Copied!

Deprecation: #99810 - "versionNumberInFilename" option now boolean

See forge#99810

Description

The system-wide setting $TYPO3_CONF_VARS['FE']['versionNumberInFilename'] was previously evaluated as a "string" value, having three possible options:

  • ""
  • "querystring"
  • "embed"

Depending on the option, resources used in TYPO3's frontend templates, such as JavaScript or CSS assets, had their "modification time" in either the querystring (myfile.js?1675703622), or in the file name itself myfile.1675703622.js - the "embed" option). The latter option required a .htaccess rule.

This existing feature ("cachebusting") is especially important for proxy / CDN setups.

For the sake of simplicity, the option is now a boolean option - and behaves similarly to the backend variant $TYPO3_CONF_VARS['BE']['versionNumberInFilename'].

Impact

If the option is now set to "false", it behaves as "querystring" did before, setting it to "true", the feature behaves exactly as "embed". The original empty option is removed, so all assets within the TYPO3 frontend rendering always include cachebusting, by default a querystring, which is fully backwards-compatible.

Affected installations

TYPO3 installations that have actively set this option in LocalConfiguration.php, AdditionalConfiguration.php or in an extension ext_localconf.php.

Migration

When updating TYPO3 and accessing the maintenance area, an explicitly set option is automatically migrated. If this is not possible - for example, configuration in AdditionalConfiguration.php is set - the value is always migrated on-the-fly when the setting is evaluated.

Deprecation: #99882 - Site language "typo3Language" setting

See forge#99882

Description

A language configuration defined for a site has had various settings, one of them being typo3Language. The setting is used to define the language key which should be used for fetching the proper XLF file (such as de_AT.locallang.xlf).

Since TYPO3 v12 it is unnecessary to set this property in the site configuration and it is removed from the backend UI. The information is now automatically derived from the locale setting of the site configuration.

The previous value "default", which matched "en" as language key is now unnecessary as "default" is now a synonym for "en".

As a result, the amount of options in the user interface for integrators is reduced.

Impact

An administrator cannot select a value for the typo3Language setting anymore via the TYPO3 backend. If a custom value is required, the site configuration needs to be manually edited and the typo3Language setting needs to be added.

If this is the case, please file a bug report in order to give the TYPO3 development team feedback on what use case is required.

However, saving a site configuration via the TYPO3 backend will still keep the typo3Language setting so no values will be lost.

Affected installations

TYPO3 installations created before TYPO3 v12.3.

Migration

No migration is needed as the explicit option is still evaluated. It is however recommended to check if the setting is really necessary.

Examples:

  1. If typo3Language: "default" and locale: "en_US.UTF-8", the setting can be removed.
  2. If typo3Language: "pt_BR" and locale: "pt_BR.UTF-8", the setting can be removed.
  3. If typo3Language: "de" and locale: "de_AT.UTF-8" , the setting can be removed, plus the label files check for de_AT.locallang.xlf and de.locallang.xlf as fallback when accessing a translated label.
  4. If typo3Language: "pt_BR" and locale: "de_DE.UTF-8" it is likely a misconfiguration in the setup, and should be analyzed if the custom value is really needed.

Deprecation: #99900 - $limit parameter of GeneralUtility::intExplode()

See forge#99900

Description

The static method GeneralUtility::intExplode() has a lesser known fourth parameter $limit. The reason it was added to the intExplode() method is purely historical, when it used to extend the trimExplode() method. The dependency was resolved, but the parameter stayed. As this method is supposed to only return int values in an array, the $limit parameter is now deprecated.

Impact

Calling GeneralUtility::intExplode() with the fourth parameter $limit will trigger a deprecation warning and will add an entry to the deprecation log.

Affected installations

TYPO3 installations that call GeneralUtility::intExplode() with the fourth parameter $limit.

Migration

In the rare case that you are using the $limit parameter you will need to switch to PHP's native explode() function, and then use array_map() to convert the resulting array to integers. If that's impractical, you can simply copy the old intExplode method to your own code.

Deprecation: #99905 - Site language "iso-639-1" setting

See forge#99905

Description

A language configuration defined for a site has had various settings, one of them being iso-639-1 (also known as "twoLetterIsoCode").

This setting was previously introduced to define the current ISO 639-1 code, which was different from the locale or the typo3Language setting. However, this information is now properly retrieved with the method: SiteLanguage->getLocale()->getLanguageCode().

Since TYPO3 v12 it is not necessary to set this property in the site configuration anymore, and it has been removed from the backend UI. The information is now automatically derived from the locale setting of the site configuration.

This property originally came from an option in TypoScript called config.sys_language_isocode which in turn was created in favor of the previous sys_language database table. The TYPO3 Core never evaluated this setting properly before TYPO3 v9.

As a result, the amount of options in the user interface for integrators is reduced.

The PHP method SiteLanguage->getTwoLetterIsoCode() serves no purpose anymore and is deprecated.

This also affects the TypoScript getData property siteLanguage:twoLetterIsoCode, and the TypoScript condition [siteLanguage("twoLetterIsoCode")].

Impact

Using the TypoScript settings or the PHP method will trigger a PHP deprecation notice.

An administrator cannot select a value for the iso-639-1 setting anymore via the TYPO3 backend. However, saving a site configuration via the TYPO3 backend will still keep the iso-639-1 setting so no information is lost.

Affected installations

TYPO3 installations actively accessing this property via PHP or TypoScript.

Migration

No migration is needed as the explicit option is still evaluated. It is however recommended to check if the setting is really necessary, and if the first part of the locale setting matches the iso-639-1 setting. If so, the line with iso-639-1 can be removed.

As for TypoScript, it is recommended to use siteLanguage:locale:languageCode instead of siteLanguage:twoLetterIsoCode.

Deprecation: #99908 - Site language "hreflang" setting

See forge#99908

Description

A language configuration defined for a site has had various settings, one of them being hreflang. The setting is used to generate hreflang meta tags to link to alternative language versions of a translated page, and to add the lang attribute to the <html> tag of a frontend page in HTML format.

Since TYPO3 v12 it is not necessary to set this property in the site configuration anymore. The information is now automatically derived from the locale setting of the site configuration if not set in the site configuration.

This also affects the TypoScript getData property siteLanguage:hrefLang, and the TypoScript condition [siteLanguage("hrefLang")].

Impact

Using the TypoScript settings or the PHP method will trigger a PHP deprecation notice.

An administrator cannot select a value for the hreflang setting anymore via the TYPO3 backend. However, when saving a site configuration via the TYPO3 backend it will still keep the hreflang setting so no information is lost.

Affected installations

TYPO3 installations actively accessing this property via PHP or TypoScript.

Migration

No migration is needed as the explicit option is still evaluated. It is however recommended to check if the setting is really necessary, and if the locale of the site language in the config.yaml matches the same value - even in a different format ( locale: "de_AT.UTF-8", hreflang: "de-AT") - the setting hreflang can be removed.

Any calls to SiteLanguage->getHrefLang() can be replaced by SiteLanguage->getLocale()->getName().

As for TypoScript, it is recommended to use siteLanguage:locale:full instead of siteLanguage:hrefLang.

Deprecation: #99916 - Site language "direction" setting

See forge#99916

Description

A language configuration defined for a site has had various settings, one of them being direction. The setting is used to add the dir attribute to the <html> tag of a frontend page in HTML format, defining the direction of the language.

However, according to https://meta.wikimedia.org/wiki/Template:List_of_language_names_ordered_by_code the list of languages that have a directionality of "right-to-left" is fixed and does not need to be configured anymore.

Since TYPO3 v12 it is not necessary to set this property in the site configuration anymore, and has been removed from the backend UI. The information is now automatically derived from the locale setting of the site configuration.

As a result, the amount of options in the user interface for integrators is reduced.

The PHP method SiteLanguage->getDirection() serves no purpose anymore and is deprecated.

Impact

Using the PHP method will trigger a PHP deprecation notice.

An administrator can not select a value for the direction setting anymore via the TYPO3 backend. However, when saving a site configuration via the TYPO3 backend it will still keep the direction setting so no information is lost.

Affected installations

TYPO3 installations actively accessing this property via PHP or TypoScript, and mainly related to TYPO3 installations with languages that have a "right-to-left" reading direction.

Migration

No migration is needed as the explicit option is still evaluated. It is however not necessary in 99.99% of the use cases. If the locale of the site language in the site's config.yaml matches the natural direction of the language (Arabic and direction = rtl), the setting direction can be removed.

Any calls to SiteLanguage->getDirection() can be replaced by SiteLanguage->getLocale()->isRightToLeftLanguageDirection() ? 'rtl' : 'ltr'.

The frontend output does not set ltr in the <html> tag anymore, as this is the default for HTML documents (see https://www.w3.org/International/questions/qa-html-dir).

Deprecation: #99932 - PageRenderer::removeLineBreaksFromTemplate

See forge#99932

Description

The following method has been marked as deprecated and will be removed in TYPO3 v13:

  • \TYPO3\CMS\Core\Page\PageRenderer::enableDebugMode()

The method acts as as shortcut to quickly disable some functions in the backend context to ease output inspection. However, the properties set by the method are ignored in the backend context anyway, the method is obsolete.

Impact

Using the method will raise a deprecation level log entry and will stop working in TYPO3 v13.

Affected installations

Instances with extensions that call the method are affected.

The extension scanner reports usages as a weak match.

Migration

All calls to the deprecated messages should be removed from the codebase.

Deprecation: #100014 - Function getParameterFromUrl() of @typo3/backend/utility module

See forge#100014

Description

The function getParameterFromUrl() of the @typo3/backend/utility module was used to obtain a query string argument from an arbitrary URL. Meanwhile, browsers received the URLSearchParams API that can be used instead.

Therefore, getParameterFromUrl() has been marked as deprecated.

Impact

Calling getParameterFromUrl() will trigger a deprecation warning.

Affected installations

All installations using third-party extensions relying on the deprecated code are affected.

Migration

Migrate to the following snippet to get the same result:

const paramValue = new URL(url, window.location.origin).searchParams.get(parameter);
Copied!

Deprecation: #100033 - TBE_STYLES stylesheet and stylesheet2

See forge#100033

Description

The usage of $GLOBALS['TBE_STYLES']['stylesheet'] and $GLOBALS['TBE_STYLES']['stylesheet2'] to add custom CSS files to the TYPO3 backend has been marked as deprecated in TYPO3 v12 and will be removed in TYPO3 v13.

Impact

Using any of the following configuration declarations

  • $GLOBALS['TBE_STYLES']['stylesheet']
  • $GLOBALS['TBE_STYLES']['stylesheet2']

will trigger a PHP deprecation notice and will throw a fatal PHP error in TYPO3 v13.

Affected installations

The extension scanner will find extensions using

  • $GLOBALS['TBE_STYLES']['stylesheet']
  • $GLOBALS['TBE_STYLES']['stylesheet2']

as "weak" matches.

Migration

Extensions should use $GLOBALS['TYPO3_CONF_VARS']['BE']['stylesheets']['my_extension'] where 'my_extension' is the extension key.

Example

EXT:my_extension/ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['BE']['stylesheets']['my_extension'] = 'EXT:my_extension/Resources/Public/Css';
Copied!

In the example above, all CSS files in the configured directory will be loaded in TYPO3 backend.

Deprecation: #100047 - Deprecated ConditionMatcher classes

See forge#100047

Description

The following classes have been marked as deprecated in TYPO3 v12 and will be removed with v13:

  • \TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\ConditionMatcherInterface
  • \TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractConditionMatcher
  • \TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher
  • \TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher

Impact

The TYPO3 Core only uses these classes within the old TypoScript parser classes, which have been deprecated as well. Using the classes will trigger a deprecation level log entry.

Affected installations

There was probably little need to implement new variants of the above classes as the underlying ExpressionLanguage construct has its own API to add new variables and functions for this TypoScript condition related to Symfony expression language usage.

Migration

No direct migration possible. These classes have been merged into the new TypoScript parser approach, specifically for class \TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeConditionMatcherVisitor .

Adding TypoScript related expression language variables and functions should be done using \TYPO3\CMS\Core\ExpressionLanguage\ProviderInterface .

Deprecation: #100047 - Page TSconfig and user TSconfig must not rely on request

See forge#100047

Description

Using request and function ip() in page TSconfig or user TSconfig conditions has been marked as deprecated in TYPO3 v12. Such conditions will stop working in TYPO3 v13 and will always evaluate to false.

Page TSconfig and user TSconfig should not rely on request related data: They should not check for given arguments or similar: the main reason is that the Backend DataHandler makes heavy use of page TSconfig, but the DataHandler itself is not request-aware. The DataHandler (the code logic that updates data in the database in the backend) can be used and must work in a CLI context, so any page TSconfig that depends on a given request is flawed by design since it will never act as expected in a CLI context.

To avoid further issues with the DataHandler in web and CLI contexts, TSconfig-related conditions must no longer be request-aware.

Impact

Using request-related conditions in page TSconfig or user TSconfig will raise a deprecation level warning in TYPO3 v12 and will always evaluate to false in TYPO3 v13.

Affected installations

There may be instances of page TSconfig using conditions using request-related conditions. These need to look for different solutions that achieve a similar goal.

Migration

Try to get rid of ip() or request related information in page TSconfig conditions.

A typical example is highlighting something when a developer is using the live domain:

[request.getRequestHost() == 'development.my.site']
    mod.foo = bar
[end]
Copied!

Switch to the application context in such cases:

[applicationContext == "Development"]
    mod.foo = bar
[end]
Copied!

There are similar alternatives for other use cases: You can not rely on given GET / POST arguments anymore, but it should be possible to switch to backend.user.isAdmin or similar conditions in most cases, or to handle related switches within controller classes in PHP.

Relying on request arguments for page TSconfig conditions is fiddly, especially when using this for core related controllers: those are not considered API and may change at anytime. Instead, needs should be dealt with explicitly using toggles within controllers.

Deprecation: #100053 - GeneralUtility::_GP()

See forge#100053

Description

The method \TYPO3\CMS\Core\Utility\GeneralUtility::_GP() has been marked as deprecated and should not be used any longer.

Modern code should access GET and POST data from the PSR-7 ServerRequestInterface, and should avoid accessing superglobals $_GET and $_POST directly. This also avoids future side-effects when using sub-requests. Some GeneralUtility related helper methods like _GP() violate this, using them is considered a technical debt. They are being phased out.

Impact

Calling the method from PHP code will log a PHP deprecation level entry, the method will be removed with TYPO3 v13.

Affected installations

TYPO3 installations with third-party extensions using GeneralUtility::_GP() are affected, typically in TYPO3 installations which have been migrated to the latest TYPO3 Core versions and haven't been adapted properly yet.

The extension scanner will find usages with a strong match.

Migration

GeneralUtility::_GP() is a helper method that retrieves incoming HTTP GET query arguments and POST body parameters and returns the value.

The same result can be achieved by retrieving arguments from the request object. An instance of the PSR-7 ServerRequestInterface is handed over to controllers by TYPO3 Core's PSR-15 \TYPO3\CMS\Core\Http\RequestHandlerInterface and middleware implementations, and is available in various related scopes like the frontend \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer .

Typical code:

use TYPO3\CMS\Core\Utility\GeneralUtility;

// Before
$value = GeneralUtility::_GP('tx_scheduler');

// After
$value = $request->getParsedBody()['tx_scheduler'] ?? $request->getQueryParams()['tx_scheduler'] ?? null;
Copied!

Deprecation: #100071 - Magic repository findBy() methods

See forge#100071

Description

Extbase repositories come with a magic __call() method to allow calling the following methods without implementing them:

  • findBy[PropertyName]($propertyValue)
  • findOneBy[PropertyName]($propertyValue)
  • countBy[PropertyName]($propertyValue)

These have now been marked as deprecated, as they are "magic", meaning that proper IDE support is not possible, and other PHP-related tool functionality such as PhpStorm.

In addition, it is not possible for Extbase repositories to build their own magic method functionality as the logic is already in use.

Impact

As these methods are widely used in almost all Extbase-based extensions, they are marked as deprecated in TYPO3 v12, but will only trigger a deprecation notice in TYPO3 v13, as they will be removed in TYPO3 v14.

This way, migration towards the new API methods can be made without pressure.

Affected installations

All installations with third-party extensions that use those magic methods.

Migration

A new set of methods without all the downsides have been added:

  • findBy(array $criteria, ...): QueryResultInterface
  • findOneBy(array $criteria, ...):object|null
  • count(array $criteria, ...): int

The naming of the methods follows those of doctrine/orm and only count() differs from the formerly countBy(). While all magic methods only allow for a single comparison (propertyName = propertyValue), those methods allow for multiple comparisons, called constraints.

findBy[PropertyName]($propertyValue) can be replaced with a call to findBy:

$this->blogRepository->findBy(['propertyName' => $propertyValue]);
Copied!

findOneBy[PropertyName]($propertyValue) can be replaced with a call to findOneBy:

$this->blogRepository->findOneBy(['propertyName' => $propertyValue]);
Copied!

countBy[PropertyName]($propertyValue) can be replaced with a call to count:

$this->blogRepository->count(['propertyName' => $propertyValue]);
Copied!

Deprecation: #100232 - $TBE_STYLES skinning functionality

See forge#100232

Description

The global configuration array $TBE_STYLES has been deprecated in favor of a new setting $TYPO3_CONF_VARS['BE']['stylesheets']. Previously, before TYPO3 v6.0, $TBE_STYLES allowed for defining more styles within PHP instead of using CSS. However, now that CSS has become been much more powerful than 10 years ago, it is time to change the logic and also consolidate TYPO3's internal configuration settings.

This deprecation is in order to be more flexible for styling purposes, as the registration of custom stylesheets can now be handled on a per-project basis.

Extensions can use almost the same syntax, however registration is now done in an extension's ext_localconf.php to reduce loading times for ext_tables.php files.

Impact

Registration of backend styles via $GLOBALS['TBE_STYLES']['skins'] in an extension's ext_tables.php file will trigger a PHP deprecation notice.

Setting $GLOBALS['TBE_STYLES']['stylesheets']['admPanel'] will also trigger a deprecation notice every time the Admin Panel is loaded in the TYPO3 frontend.

Affected installations

TYPO3 installations with custom styling in the TYPO3 backend or the Admin Panel via $GLOBALS['TBE_STYLES'].

Migration

Migrate to the new configuration setting $GLOBALS['TYPO3_CONF_VARS']['BE']['stylesheets'] which can be set per site or within an extension's ext_localconf.php.

For a custom stylesheet in the TYPO3 Admin Panel, it is recommended to use the new AdminPanel Module API (available since TYPO3 v9 LTS) where custom CSS and JavaScript files can be registered dynamically.

Deprecation: #100247 - Various interconnected methods in EXT:scheduler

See forge#100247

Description

The scheduler system extension, responsible for executing long-running, timed or recurring tasks, has been included since TYPO3 v4.3, but never received an overhaul of its code base.

Back then, the main \TYPO3\CMS\Scheduler\Scheduler class and the \TYPO3\CMS\Scheduler\Task\AbstractTask class were the main API classes, all logic being included, whereas AbstractTask is the main class that all custom tasks within extensions derive from.

However, in the past 15 years TYPO3's code base has undergone a lot of API design changes related to separation of concerns. In order to achieve this in the scheduler extension, almost all access to the actual database access around task retrieving and scheduling has been moved into its own \TYPO3\CMS\Scheduler\Domain\Repository\SchedulerTaskRepository class.

For this reason, the following methods within the original API classes are now either marked as deprecated or internal - not part of TYPO3's public API anymore - as they have now been moved into the new repository class.

  • Scheduler->addTask()
  • Scheduler->log() - marked as internal
  • Scheduler->removeTask()
  • Scheduler->saveTask()
  • Scheduler->fetchTask()
  • Scheduler->fetchTaskRecord()
  • Scheduler->fetchTaskWithCondition()
  • Scheduler->isValidTaskObject()
  • Scheduler->log() - marked as internal
  • AbstractTask->isExecutionRunning()
  • AbstractTask->markExecution()
  • AbstractTask->unmarkExecution()
  • AbstractTask->unmarkAllExecutions()
  • AbstractTask->save() - marked as internal
  • AbstractTask->remove()
  • AbstractTask->setScheduler() - marked as internal
  • AbstractTask->unsetScheduler() - marked as internal
  • AbstractTask->registerSingleExecution() - marked as internal
  • AbstractTask->getExecution() - marked as internal
  • AbstractTask->setExecution() - marked as internal
  • AbstractTask->getNextDueExecution() - marked as internal
  • AbstractTask->areMultipleExecutionsAllowed() - marked as internal
  • AbstractTask->stop() - marked as internal

Impact

Calling any of the deprecated methods will trigger a PHP warning. Using the internal methods should be avoided and is not covered by the TYPO3 backwards compatibility promise.

Affected installations

TYPO3 installations with extensions that include custom scheduler tasks accessing these methods. The Extension Scanner might be helpful to detect these usages.

Migration

Use the SchedulerTaskRepository methods instead.

Deprecation: #100278 - PostLoginFailureProcessing hook

See forge#100278

Description

The hook $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing'] which can be used to handle custom notifications that a login in a frontend or backend context failed, has been marked as deprecated.

Impact

If the hook is registered in a TYPO3 installation, a PHP E_USER_DEPRECATED error is triggered.

The extension scanner also detects any usage of the deprecated interface as a strong match, and the definition of the hook as a weak match.

Affected installations

TYPO3 installations with custom extensions using this hook.

Migration

Migrate to the newly introduced PSR-14 event \TYPO3\CMS\Core\Authentication\Event\LoginAttemptFailedEvent.

Important: #100032 - Add HTTP security headers for backend by default

See forge#100032

Description

The following HTTP security headers are now added by default for the TYPO3 backend:

  • Strict-Transport-Security: max-age=31536000 (only if $GLOBALS[TYPO3_CONF_VARS][BE][lockSSL] is active)
  • X-Content-Type-Options: nosniff
  • Referrer-Policy: strict-origin-when-cross-origin

The default HTTP security headers are configured globally in $GLOBALS['TYPO3_CONF_VARS']['BE']['HTTP']['Response']['Headers'] and include a unique array key, so it is possible to individually unset/remove unwanted headers.

Important: #100088 - Remove dbType json for TCA type user

See forge#100088

Description

With forge#99226 the dbType=json option has been added for TCA type user. After some reconsideration, it has been decided to drop this option again in favor of the dedicated TCA type json. Have a look to the according changelog for further information.

Since the dbType option has not been released in any LTS version yet, the option is dropped without further deprecation. Also no TCA migration is applied.

In case you make already use of this dbType in your custom extension, you need to migrate to the new TCA type.

Example:

// Before
'myField' => [
    'config' => [
        'type' => 'user',
        'renderType' => 'myRenderType',
        'dbType' => 'json',
    ],
],

// After
'myField' => [
    'config' => [
        'type' => 'json',
        'renderType' => 'myRenderType',
    ],
],
Copied!

12.2 Changes

Table of contents

Breaking Changes

None since TYPO3 v12.0 release.

Features

Deprecation

Important

Feature: #97390 - Use password policy for backend user password in ext:install

See forge#77072

Description

The password used to create the backend user during install (GUI and setup command) now considers the configurable password policy introduced in #97388.

Impact

The globally configured password policy is now taken into account when the backend user is created during the install process.

For each violation of the password policy a message will be displayed to the user (GUI and setup command).

Feature: #86913 - Automatic support for language files of languages with region suffix

See forge#86913

Description

TYPO3's native support for label files - that is: translatable text for system labels such as from plugins, and for texts within TYPO3 backend - supports over 50 languages. Languages are identified by their "language key" of the ISO 639-1 standard, which also allows the use of a region-specific language. This happens mostly in countries/regions that have a variation of the language, such as "en-US" for American English, or "de-CH" for the German language in Switzerland.

To support these region-specific language keys, which are composed of ISO 639-1 and ISO 3166-1 and separated with -, TYPO3 integrators had to configure the additional language manually to translate region-specific terms.

Common examples are "Behavior" (American English) vs. "Behaviour" (British English), or "Offerte" (Swiss German) vs. "Angebot" (German), where all labels except a few terms should stay the same.

Impact

TYPO3 now allows integrators to use a custom label file with the locale prefix de_CH.locallang.xlf in an extension next to de.locallang.xlf and locallang.xlf (default language English).

When integrators then use de-CH within their site configuration, TYPO3 first checks if a term is available in the translation file de_CH.locallang.xlf, and then automatically falls back to the non-region-specific de translation file de.locallang.xlf without any further configuration to TYPO3.

Previously, such region-specific locales had to be configured via:

$GLOBALS['TYPO3_CONF_VARS']['SYS']['localization']['locales']['user'] = [
    'de-CH' => 'German (Switzerland)',
];
Copied!

The same fallback functionality also works when overriding labels via TypoScript:

plugin.tx_myextension._LOCAL_LANG.de = Angebot
plugin.tx_myextension._LOCAL_LANG.de-CH = Offerte
Copied!

Feature: #88137 - Multi-level fallback for content in frontend rendering

See forge#88137

Description

TYPO3's site handling was introduced in TYPO3 v9 and allows to define a "fallback type".

A fallback type allows to define the behavior of how pages and the content should be fetched from the database when rendering a page in the frontend.

The option strict only renders content which was explicitly translated or created in the defined language, and keeps the sorting behavior of the default language.

The option free does not consider the default language or its sorting, and only fetches directly content of the given language ID.

The option fallback allows to define a fallback chain of languages. If a certain page is not available in the given language, TYPO3 first checks the fallback chain if a page is available in one of the languages in the fallback chain.

A common scenario is this:

  • German (Austria) - Language = 2
  • German (Germany) - Language = 1
  • English (Default) - Language = 0

TYPO3 now can deal with the language chain in fallback mode not only for pages, but also for any kind of content.

Impact

When working in a scenario with fallback and multiple languages in the fallback chain, TYPO3 now checks for each content if the target language is available, and then checks for the same content if it is translated in the language of the fallback chain (example above in "German (Germany)"), before falling back to the default language - which was the behavior until now.

The language chain processing works with fallback mode (a.k.a. "overlays in mixed mode"), both in TypoScript and Extbase code. Under the hood, the method PageRepository->getLanguageOverlay() is responsible for the chaining.

Current limitations:

  • Content fallback only works in fallbackType=fallback
  • Content fallback always stops at the default language (as this was the previous behavior)

Feature: #92517 - Custom namespace for Extbase plugin enhancer

See forge#92517

Description

The Extbase plugin enhancer for frontend routing allows to either set extension and plugin OR to set namespace. If extension and plugin were given, those were used.

However, the namespace option is automatically constituted by the extension and plugin options if it was not set intentionally. It is mainly used when overriding the custom extension and plugin options with a custom (usually shortened) namespace, so that the namespace is now always respected and preferred if all three options are set.

Impact

If all of namespace and extension and plugin options are configured, the namespace option is now preferred within the Extbase plugin enhancer.

Feature: #97392 - Use password policy for new admin users created in ext:install

See forge#97392

Description

The password for a new administrative backend user created using EXT:install now considers the configurable password policy introduced by #97388.

Impact

The global password policy is now taken into account when a new administrative backend user is created using EXT:install. Password policy requirements are shown below the password field and a message is shown, if the new password does not meet the password policy requirements.

Feature: #97700 - Adopt Symfony Messenger as a message bus and queue

See forge#97700

Description

This feature provides a basic implementation of a message bus based on the Symfony Messenger component. For backwards compatibility, the default implementation uses the synchronous transport. This means that the message bus will behave exactly as before, but it will be possible to switch to a different (async) transport on a per-project base. To offer asynchronicity, the feature also provides a transport implementation based on the Doctrine DBAL messenger transport from Symfony and a basic implementation of a consumer command.

As an example, the workspace StageChangeNotification has been rebuilt as a message and corresponding handler.

"Everyday" usage - as a developer

Dispatch a message

  • Add a PHP class for your message object (arbitrary PHP class) ( DemoMessage)

    <?php
    
    namespace TYPO3\CMS\Queue\Message;
    
    final class DemoMessage
    {
        public function __construct(public readonly string $content)
        {
        }
    }
    Copied!
  • Inject \Symfony\Component\Messenger\MessageBusInterface into your class
  • Call dispatch() method with a message as argument

    public function __construct(private readonly MessageBusInterface $bus)
    {
    }
    
    public function yourMethod(): void
    {
        // ...
        $this->bus->dispatch(new DemoMessage('test'));
        // ...
    }
    Copied!

Register a handler

Use a tag to register a handler. Use before/after to define order. Define handled message by argument type reflection or by key message.

namespace TYPO3\CMS\Queue\Handler;

use TYPO3\CMS\Queue\Message\DemoMessage;

class DemoHandler
{
    public function __invoke(DemoMessage $message): void
    {
        // do something with $message
    }
}
Copied!
TYPO3\CMS\Queue\Handler\DemoHandler:
  tags:
    - name: 'messenger.message_handler'

TYPO3\CMS\Queue\Handler\DemoHandler2:
  tags:
    - name: 'messenger.message_handler'
      before: 'TYPO3\CMS\Queue\Handler\DemoHandler'
Copied!

Everyday Usage - as a sysadmin/integrator

By default, the system behaves as before. This means that the message bus uses the synchronous transport and all messages are handled immediately. To benefit from the message bus, it is recommended to switch to an asynchronous transport. Using asynchronous transports increases the resilience of the system by decoupling external dependencies even further.

The TYPO3 Core currently provides an asynchronous transport based on the Doctrine DBAL messenger transport. This transport is configured to use the default TYPO3 database connection. It is pre-configured and can be used by changing the settings in config/settings.php:

$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing']['*'] = 'doctrine';
Copied!

This will route all messages to the asynchronous transport.

If you are using the Doctrine transport, make sure to take care of running the consume command (see below).

Async message handling - The consume command

Run the command ./bin/typo3 messenger:consume <receiver-name> to consume messages. By default, you should run ./bin/typo3 messenger:consume doctrine. The command is a slimmed-down wrapper for the Symfony command messenger:consume, it only provides the basic consumption functionality. As this command is running as a worker, it is stopped after 1 hour to avoid memory leaks. The command should therefore be run from a service manager like systemd to automatically restart it after the command exits due to the time limit.

Create a service via /etc/systemd/system/typo3-message-consumer.service:

[Unit]
Description=Run the TYPO3 message consumer
Requires=mariadb.service
After=mariadb.service

[Service]
Type=simple
User=www-data
Group=www-data
ExecStart=/usr/bin/php8.1 /var/www/myproject/vendor/bin/typo3 messenger:consume doctrine --exit-code-on-limit 133
# Generally restart on error
Restart=on-failure
# Restart on exit code 133 (which is returned by the command when limits are reached)
RestartForceExitStatus=133
# ..but do not interpret exit code 133 as an error (as it's just a restart request)
SuccessExitStatus=133

[Install]
WantedBy=multi-user.target
Copied!

The message worker can than be enabled and started via systemctl enable --now typo3-message-consumer

Advanced Usage

Configure a custom transport (senders/receivers)

Set up transports in services configuration. To configure one transport per message, the TYPO3 configuration (config/settings.php, config/additional.php on system level or ext_localconf.php) is used. The transport/sender name used in the settings is resolved to a service that has been tagged with message.sender and the respective identifier.

$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger'] = [
    'routing' => [
        // use "messenger.transport.demo" as transport for DemoMessage
        \TYPO3\CMS\Queue\Message\DemoMessage::class => 'demo',
        // use "messenger.transport.default" as transport for all other messages
        '*' => 'default',
    ]
];
Copied!
messenger.transport.demo:
  factory: [ '@TYPO3\CMS\Core\Messenger\DoctrineTransportFactory', 'createTransport' ]
  class: 'Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport'
  arguments:
    $options:
      queue_name: 'demo'
  tags:
    - name: 'messenger.sender'
      identifier: 'demo'
    - name: 'messenger.receiver'
      identifier: 'demo'

messenger.transport.default:
  factory: [ '@Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory', 'createTransport' ]
  class: 'Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport'
  arguments:
    $dsn: 'in-memory://default'
    $options: [ ]
  tags:
    - name: 'messenger.sender'
      identifier: 'default'
    - name: 'messenger.receiver'
      identifier: 'default'
Copied!

The TYPO3 Core has been tested with three transports:

  • \Symfony\Component\Messenger\Transport\Sync\SyncTransport (default)
  • \Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport (using the Doctrine DBAL messenger transport)
  • \Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport (for testing)

InMemoryTransport for testing

\Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport is a transport that should only be used while testing. See the SymfonyCasts tutorial for more details.

messenger.transport.default:
  factory: [ '@Symfony\Component\Messenger\Transport\InMemory\InMemoryTransportFactory', 'createTransport' ]
  class: 'Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport'
  public: true
  arguments:
    $dsn: 'in-memory://default'
    $options: [ ]
  tags:
    - name: 'messenger.sender'
      identifier: 'default'
    - name: 'messenger.receiver'
      identifier: 'default'
Copied!

Configure a custom middleware

Set up a middleware in the services configuration. By default, \Symfony\Component\Messenger\Middleware\SendMessageMiddleware and \Symfony\Component\Messenger\Middleware\HandleMessageMiddleware are registered - see also Symfony's documentation. To add your own message middleware, tag it as messenger.middleware and set the order using TYPO3's before and after ordering mechanism.

Symfony\Component\Messenger\Middleware\SendMessageMiddleware:
  arguments:
    $sendersLocator: '@Symfony\Component\Messenger\Transport\Sender\SendersLocatorInterface'
    $eventDispatcher: '@Psr\EventDispatcher\EventDispatcherInterface'
  tags:
    - { name: 'messenger.middleware' }

Symfony\Component\Messenger\Middleware\HandleMessageMiddleware:
  arguments:
    $handlersLocator: '@Symfony\Component\Messenger\Handler\HandlersLocatorInterface'
  tags:
    - name: 'messenger.middleware'
      after: 'Symfony\Component\Messenger\Middleware\SendMessageMiddleware'
Copied!

Feature: #97923 - Improve performance and usability while editing sys_file_collection

See forge#97923

Description

The two fields storage and folder of the sys_file_collection table are now combined into the new field folder_identifier. The field contains the so-called combined identifier in the format storage:folder, where storage is the uid of the corresponding sys_file_storage record and folder the absolute path to the folder, e.g. 1:/user_upload.

An upgrade wizard is in place to migrate the two fields of the existing records to the new field.

The TCA type folder is now used in the backend editing form to improve the usability on selecting the corresponding folder via the folder selector, when using the file collections with type folder.

Impact

Editing sys_file_collection records for the record type folder in the backend is improved. Instead of selecting the storage first, reloading the form and selecting the folder in a possibly large list afterwards, are users now able to select the folder using the folder selector in a single step.

This additionally improves the performance of the backend form, especially for storages with a huge amount of folders.

Also working with such records is improved, since only one field has to be taken into account.

Feature: #98394 - Introduce event to prevent downloading of language packs

See forge#98394

Description

EXT:my_extension/Configuration/Services.yaml
services:
  MyVendor\MyExtension\EventListener\ModifyLanguagePacks:
    tags:
      - name: event.listener
        identifier: 'modifyLanguagePacks'
        event: TYPO3\CMS\Install\Service\Event\ModifyLanguagePacksEvent
        method: 'modifyLanguagePacks'
Copied!
EXT:my_extension/Classes/EventListener/ModifyLanguagePacks.php
<?php
namespace MyVendor\MyExtension\EventListener;

use TYPO3\CMS\Install\Service\Event\ModifyLanguagePacksEvent;

final class ModifyLanguagePacks
{
    public function modifyLanguagePacks(ModifyLanguagePacksEvent $event): void
    {
        $extensions = $event->getExtensions();
        foreach ($extensions as $key => $extension){
            if($extension['type'] === 'typo3-cms-framework'){
                $event->removeExtension($key);
            }
        }
        $event->removeIsoFromExtension('de', 'styleguide');
    }
}
Copied!

Impact

With the newly introduced event, it is possible to ignore extensions or individual language packs for extensions when downloading the language packs. However, only language packs for extensions and languages available in the system can be downloaded. The options of the language:update command can be used to further restrict the download (ignore additional extensions or download only specific languages), but not to ignore decisions made by the event.

Feature: #98528 - New file location for ENABLE_INSTALL_TOOL

See forge#98528

Description

To access the standalone Install Tool, the file typo3conf/ENABLE_INSTALL_TOOL needed to be created. With TYPO3 v12, the location of this file has been changed.

For Composer-based installations the following file paths are checked:

  • var/transient/ENABLE_INSTALL_TOOL
  • config/ENABLE_INSTALL_TOOL

For legacy installations the following file paths are checked:

  • typo3temp/var/transient/ENABLE_INSTALL_TOOL
  • typo3conf/ENABLE_INSTALL_TOOL

Using the previous known path typo3conf/ENABLE_INSTALL_TOOL is still possible.

Impact

Especially for Composer-based installation this change allows to completely drop the usage of the typo3conf/ directory.

Add the new paths to your .gitignore file to avoid deploying this file to production environments.

Feature: #99191 - Create folders via modals

See forge#99191

Description

The creation of new folders in the File > Filelist module has been improved. Instead of a new window, the Create Folder button now opens a modal window to create a folder. Both the button in the docheader and the corresponding option in the context menu are affected.

The modal window also contains the folder tree to select the parent folder. To allow editors creating folders sequentially, the modal is not automatically closed.

Impact

With the new modal window, backend users are able to create folders in an improved way: They do not lose focus of the current view anymore. Additionally, the parent folder can easily be changed inside the modal window, which allows to create folders for different levels without leaving the form. After closing the modal window, the File > Filelist module automatically reloads to instantly display the latest changes.

Feature: #99220 - Add event to modify search results

See forge#99220

Description

A new PSR-14 event \TYPO3\CMS\Backend\Search\Event\ModifyResultItemInLiveSearchEvent is added to allow extension developers to take control over search result items rendered in the backend search.

The event has a public method called getResultItem(), returning the \TYPO3\CMS\Backend\Search\LiveSearch\ResultItem instance of the search result item.

Impact

Search result items may be modified within a custom event listener, e.g. to add custom actions.

Example

EXT:my_extension/Classes/Search/EventListener/AddLiveSearchResultActionsListener.php
<?php

namespace MyVendor\MyExtension\Search\EventListener;

use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Search\Event\ModifyResultItemInLiveSearchEvent;
use TYPO3\CMS\Backend\Search\LiveSearch\DatabaseRecordProvider;
use TYPO3\CMS\Backend\Search\LiveSearch\ResultItemAction;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;

final class AddLiveSearchResultActionsListener
{
    protected LanguageService $languageService;

    public function __construct(
        protected readonly IconFactory $iconFactory,
        protected readonly LanguageServiceFactory $languageServiceFactory,
        protected readonly UriBuilder $uriBuilder
    ) {
        $this->languageService = $this->languageServiceFactory->createFromUserPreferences($GLOBALS['BE_USER']);
    }

    public function __invoke(ModifyResultItemInLiveSearchEvent $event): void
    {
        $resultItem = $event->getResultItem();
        if ($resultItem->getProviderClassName() !== DatabaseRecordProvider::class) {
            return;
        }

        if (($resultItem->getExtraData()['table'] ?? null) === 'tt_content') {
            /**
             * WARNING: THIS EXAMPLE OMITS ANY ACCESS CHECK FOR SIMPLICITY REASONS.
             *          DO NOT USE AS-IS!
             */
            $showHistoryAction = (new ResultItemAction('view_history'))
                ->setLabel($this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:history'))
                ->setIcon($this->iconFactory->getIcon('actions-document-history-open', Icon::SIZE_SMALL))
                ->setUrl((string)$this->uriBuilder->buildUriFromRoute('record_history', [
                    'element' => $resultItem->getExtraData()['table'] . ':' . $resultItem->getExtraData()['uid']
                ]));
            $resultItem->addAction($showHistoryAction);
        }
    }
}
Copied!
EXT:my_extension/Configuration/Services.yaml
MyVendor\MyExtension\Search\EventListener\AddLiveSearchResultActionsListener:
  tags:
    - name: event.listener
      identifier: 'site/add-live-search-result-actions-listener'
Copied!

Feature: #99285 - Add Fluid TrimViewHelper

See forge#99285

Description

A trim ViewHelper to trim strings is now available.

Possible sides are:

  • both Strip whitespace (or other characters) from the beginning and end of a string
  • left Strip whitespace (or other characters) from the beginning of a string
  • right Strip whitespace (or other characters) from the end of a string

Examples

Trim from both sides

#<f:format.trim>   String to be trimmed.   </f:format.trim>#
Copied!

Results in the output:

#String to be trimmed.#
Copied!

Trim only one side

#<f:format.trim side="right">   String to be trimmed.   </f:format.trim>#
Copied!

Results in the output:

#   String to be trimmed.#
Copied!

Trim special characters

#<f:format.trim characters=" St.">   String to be trimmed.   </f:format.trim>#