This chapter contains a description of the formal requirements or standards
regarding coding that you should adhere to when you develop TYPO3
extensions or Core parts.
This chapter goes into details about automatic testing: Writing,
maintaining and running them in various scopes.
Introduction
TYPO3 is a content management system based on PHP.
The TYPO3 - Getting Started Tutorial gives you an
introduction to the core concepts of TYPO3 and will help you to kickstart your
first project.
A basic TYPO3 installation
Installation with Composer is covered in the Getting Started Guide
(see Installation chapter).
If you think you have found a bug in the TYPO3 Core code, have a look at our
issue tracker on forge
where you can check if the bug has already been reported or not.
API A-Z
The TYPO3 APIs are first and foremost documented inside of the source
scripts. It would be impossible to maintain documentation at more than
one location given the fact that things change and sometimes fast.
This chapter describes the most important elements of the API.
The TYPO3 component responsible for rendering the HTML and adding assets to a
TYPO3 frontend or backend page is called
\TYPO3\CMS\Core\Page\PageRenderer .
The
PageRenderer collects all assets to be rendered, takes care of
options such as concatenation or compression and finally generates the necessary
tags.
There are multiple ways to add assets to the
PageRenderer in TYPO3.
For configuration options via TypoScript (usually used for the main theme files),
see the TypoScript Reference. In
extensions, both directly using the
PageRenderer as well as using the
more convenient
AssetCollector is possible.
Asset collector
With the
\TYPO3\CMS\Core\Page\AssetCollector class, CSS and JavaScript
code (inline or external) can be added multiple times, but rendered only once in
the output. The class may be used directly in PHP code or the assets can be
added via the
<f:asset.css> and
<f:asset.script> ViewHelpers.
The
priority flag (default:
false) controls where the asset is
inserted:
JavaScript will be output inside
<head> if
$priority == true,
or at the bottom of the
<body> tag if
$priority == false.
CSS will always be output inside
<head>, yet grouped by
$priority.
The asset collector helps to work with content elements as components,
effectively reducing the CSS to be loaded. It takes advantage of HTTP/2, which
removes the necessity to concatenate all files in one file.
The asset collector class is implemented as a singleton
(
\TYPO3\CMS\Core\SingletonInterface ). It replaces various other existing
options in TypoScript and methods in PHP for
inserting JavaScript and CSS code.
The asset collector also collects information about images on a page,
which can be used in cached and non-cached components.
The API
classAssetCollector
Fully qualified name
\TYPO3\CMS\Core\Page\AssetCollector
The Asset Collector is responsible for keeping track of
- everything within <script> tags: javascript files and inline javascript code
- inline CSS and CSS files
The goal of the asset collector is to:
- utilize a single "runtime-based" store for adding assets of certain kinds that are added to the output
- allow to deal with assets from non-cacheable plugins and cacheable content in the Frontend
- reduce the "power" and flexibility (I'd say it's a burden) of the "god class" PageRenderer.
- reduce the burden of storing everything in PageRenderer
As a side-effect this allows to:
- Add a single CSS snippet or CSS file per content block, but assure that the CSS is only added once to the output.
Note on the implementation:
- We use a Singleton to make use of the AssetCollector throughout Frontend process (similar to PageRenderer).
- Although this is not optimal, I don't see any other way to do so in the current code.
One dimensional hash map (array with non numerical keys) with scalar values
Returns
self
removeJavaScript(string $identifier)
param $identifier
the identifier
Returns
self
removeInlineJavaScript(string $identifier)
param $identifier
the identifier
Returns
self
removeStyleSheet(string $identifier)
param $identifier
the identifier
Returns
self
removeInlineStyleSheet(string $identifier)
param $identifier
the identifier
Returns
self
removeMedia(string $identifier)
param $identifier
the identifier
Returns
self
getMedia()
Returns
array
getJavaScripts(?bool $priority = NULL)
param $priority
the priority, default: NULL
Returns
array
getInlineJavaScripts(?bool $priority = NULL)
param $priority
the priority, default: NULL
Returns
array
getStyleSheets(?bool $priority = NULL)
param $priority
the priority, default: NULL
Returns
array
getInlineStyleSheets(?bool $priority = NULL)
param $priority
the priority, default: NULL
Returns
array
hasJavaScript(string $identifier)
param $identifier
the identifier
Returns
bool
hasInlineJavaScript(string $identifier)
param $identifier
the identifier
Returns
bool
hasStyleSheet(string $identifier)
param $identifier
the identifier
Returns
bool
hasInlineStyleSheet(string $identifier)
param $identifier
the identifier
Returns
bool
hasMedia(string $fileName)
param $fileName
the fileName
Returns
bool
Note
If the same asset is registered multiple times using different attributes or
options, both sets are merged. If the same attributes or options are given
with different values, the most recently registered ones overwrite the
existing ones. The
has methods can be used to check if an asset
exists before generating it again, hence avoiding redundancy.
ViewHelper
There are also two ViewHelpers, the
f:asset.css and the
f:asset.script ViewHelper which
use the
AssetCollector API.
Rendering order
Currently, CSS and JavaScript registered with the asset collector will be
rendered after their page renderer counterparts. The order is:
<head>
page.includeJSLibs.forceOnTop
page.includeJSLibs
page.includeJS.forceOnTop
page.includeJS
AssetCollector::addJavaScript() with 'priority'
page.jsInline
AssetCollector::addInlineJavaScript() with 'priority'
The TYPO3 Core uses Services for the authentication process.
This family of services (of type "auth") are the only Core usage that consumes the
Services API.
The aim of this chapter is to describe the authentication
services so that developers feel confident about writing
their own.
Services provide the flexibility needed for such a complex
process of authentication, where many methods may be desirable
(single sign-on, IP-based authentication, third-party servers
such as LDAP, etc.) depending on the context.
The ease with which such services can be developed is a strong
point in favor of TYPO3, especially in corporate environments.
Being able to toy with priority and quality allows for
precise fine-tuning of the authentication chain.
Alternative services are available in the TYPO3 Extension Repository.
It is thus possible to find solutions for using LDAP as an
authentication server, for example.
You can check which authentication services are installed
using the System > Reports > Installed Services
view:
All installed authentication services and their priority
Note
For the Reports module to be visible, the system extension
reports has to be installed. You can install it via Composer:
composer require typo3/cms-reports
Copied!
The Authentication Process
The authentication process is not managed entirely by services.
It is handled essentially by class
\TYPO3\CMS\Core\Authentication\BackendUserAuthentication
for the backend (BE) and by class
\TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication
for the frontend (FE), which both inherit from class
\TYPO3\CMS\Core\Authentication\AbstractUserAuthentication .
The objects for these classes are available via
$GLOBALS['BE_USER'] for
BackendUserAuthentication and
"frontend.user" request attribute for
FrontendUserAuthentication
These classes are called by the
bootstrapping process.
They manage the workflow of the authentication process.
Services are used strictly to identify and validate
users based on whatever form of credentials a given service
relies on (by default, a username and a password).
The authentication process kicks in on every page request,
be it in the FE or the BE. However if a valid session already exists,
that session is kept. Strictly speaking, no authentication
is performed in such a case.
Note
When no session exists, the authentication process is triggered
by a login request. In the frontend, this happens when a form field
called logintype is submitted with value login. The same
happens for the backend, but with a form field called login_status.
JSON Web Tokens (JWT) are used to transport user
session identifiers in be_typo_user and fe_typo_user cookies.
Using JWT's HS256 (HMAC signed based on SHA256) allows to determine whether a
session cookie is valid before comparing with server-side stored session data.
This enhances the overall performance a bit, since sessions cookies would be
checked for every request to TYPO3's backend and frontend.
The session cookies can be pre-validated without querying the database, which
can filter invalid requests and might improve overall performance a bit.
As a consequence session tokens are not sent "as is", but are wrapped in a
corresponding JWT message, which contains the following payload:
identifier reflects the actual session identifier
time reflects the time of creating the cookie (RFC 3339 format)
The login data
There is a typical set of data that is transmitted to authentication
service in order to enable them to do their work:
uname
This is the user name. This can be whatever makes sense for the
available authentication services. For the default service, this
will match data from the "username" column of the "be_users" or
"fe_users" table for BE or FE authentication respectively.
uident
This is the password, possibly encrypted.
uident_text
This is the clear text value of the password. If the password is
originally submitted in clear text, both "uident" and "uident_text"
contain the same value.
Inside an authentication service, this data is available in
$this->login.
The "auth" services API
The services of type "auth" are further divided into subtypes,
which correspond to various steps in the authentication process.
Most subtypes exist for both FE and BE and are differentiated
accordingly.
To each subtype corresponds a part of the "auth" services public
API. They are listed below in the order in which they are called
during the authentication process.
processLoginDataBE, processLoginDataFE
This subtype performs preprocessing on the submitted
login data.
The method to implement is
processLoginData().
It receives as argument the login data and the password
transmission strategy (which corresponds to the login security
level, where only 'normal' can be used.
It returns the boolean value
true,
when the login data has been successfully processed .
It may also return a numerical value equal to 200 or greater,
which indicates that no further login data processing should
take place (see The service chain).
In particular, this subtype is implemented by the TYPO3 Core
AuthenticationService, which trims the given login data.
getUserFE, getUserBE
This subtype corresponds to the operation of searching in the
database if the credentials that were given correspond to an
existing user. The method to implement is
getUser().
It is expected to return an array containing the user information
or
false if no user was found.
authUserFE, authUserBE
This subtype performs the actual authentication based on the
provided credentials. The method to implement is
authUser().
It receives the user information (as returned by
getUser())
as an input and is expected to return a numerical value,
which is described later.
Note
Before any of the above-mentioned methods are called, the authentication
process will call the
initAuth() method of each service. This
sets up a lot of data for the service. It also makes it possible to
override part of the default settings with
service-specific options.
This represents very advanced tuning and is not described here.
Please refer to
\TYPO3\CMS\Core\Authentication\AbstractAuthenticationService::initAuth()
to learn more about the possibilities offered during authentication services
initialization.
The service chain
No matter what subtype, authentication services are always called
in a chain. This means that
all registered "auth" services will be called, in order of
decreasing priority and quality.
However, for some subtypes, there are ways to stop the chain.
For "processLoginDataBE" and "processLoginDataFE" subtypes, the
processLoginData()
method may return a numerical value of 200 or more. In such a case
no further services are called and login data is not further
processed. This makes it possible for a service to perform
a form of final transformation on the login data.
For "authUserFE" and "authUserBE" subtypes, the
authUser() method may
return different values:
Warning
Previously, there was an error in the documentation. It did not match
the actual behaviour. This has now been fixed. For details, see
forge#91993.
a negative value or 0 (<=0) indicates that the authentication has
definitely failed and that no other "auth" service should be
called up.
a value larger than 0 and smaller than 100 indicates that the authentication
was successful, but that further services should also perform their
own authentication.
a value of 100 or more (>= 100) indicates that the user was not authenticated,
this service is not responsible for the authentication and that further
services should authenticate.
a value of 200 or more (>=200) indicates that the authentication was successful
and that no further tries should be made by other services down
the chain.
auth failed
auth success
no auth
continue
1..99
100..199
stop
<= 0
>= 200
For "getUserFE" and "getUserBE" subtypes, the logic is reversed.
The service chain will stop as soon as one user is found.
Developing an authentication service
Use the Service API to implement your service class.
When developing your own "auth" services, the chances are high
that you will want to implement only the "getUser*" and "authUser*"
subtypes.
There are several public extensions providing such services, so you should
be able to find examples to inspire and guide you. Anyway authentication
services can be very different from one another, so it wouldn't make much
sense to try and provide an example in this manual.
One important thing to know is that the TYPO3 authentication
process needs to have users inside database records ("fe_users" or
"be_users"). This means that if you interface with a third-party
server, you will need to create records on the TYPO3 side. It is
up to you to choose whether this process happens on the fly (during
authentication) or if you want to create an import process (as a
Scheduler task, for example) that will synchronize users between
TYPO3 and the remote system.
Note
You probably do not want to store the actual password of imported
users in the TYPO3 database. It is recommended to store
an arbitrary string in such case, making sure that such string
is random enough for security reasons. TYPO3 provides method
\TYPO3\CMS\Core\Crypto\Random::generateRandomHexString()
which can be used for such a purpose.
For the
authUser() method, you will want to take care
about the return values. If your service should be the final
authority for authentication, it should not only have a high priority,
but also return values which stop the service chain (i.e.
a negative value for failed authentication, 200 or more for a
successful one). On the other hand, if your service is an alternative
authentication, but should fall back on TYPO3 if unavailable,
you will want to return 100 on failure, so that the default service
can take over.
Things can get a bit hairy if you have a scenario with mixed sources,
for example some users come from a third-party server but others
exist only in TYPO3. In such a case, you want to make sure that
your service returns definite authentication failures only for those
users which depend on the remote system and let the default
authentication proceed for "local" TYPO3 users.
Advanced Options
There are some special configuration options which can be used
to modify the behaviour of the authentication process. Some
impact the inner working of the services themselves, others
influence when services are called.
It is possible to force TYPO3 to go through the
authentication process for every request no matter any
existing session. By setting the following local configuration
either for the FE or the BE:
This could be used in a scenario where users go through a login portal
and then choose to access the TYPO3 backend, for example. In such a case
we would want the users to be automatically authenticated, but would not
need to repeat the process upon each request.
The authentication process can also be forced to go through
all services for the "getUser*" subtype by setting:
for BE or FE respectively. This will collect all possible users rather than
stopping at the first one available.
CSRF-like request token handling
New in version 12.0
A CSRF-like request token handling is available to mitigate potential cross-site
requests on actions with side effects. This approach does not require an
existing server-side user session, but uses a nonce as a "pre-session". The
main scope is to ensure a user actually has visited a page, before submitting
data to the webserver.
This token can only be used for HTTP methods POST, PUT or PATCH, but
for instance not for GET request.
The
\TYPO3\CMS\Core\Middleware\RequestTokenMiddleware resolves
request tokens and nonce values from a request and enhances responses with
a nonce value in case the underlying application issues one. Both items are
serialized as a JSON Web Token (JWT) hash signed with HS256. Request tokens
use the provided nonce value during signing.
Session cookie names involved for providing the nonce value:
typo3nonce_[hash] in case request served with plain HTTP
__Secure-typo3nonce_[hash] in case request served with secured HTTPS
Submitting request token value to application:
HTTP body, for example in <form> via parameter __RequestToken
HTTP header, for example in XHR via header X-TYPO3-Request-Token
Attention
When working with multiple browser tabs, an existing nonce value (stored as
session cookie in the browser of the user) might be overridden.
Note
The current concept uses the
\TYPO3\CMS\Core\Security\NoncePool which
supports five different nonces in the same request. The pool purges nonces
15 minutes (900 seconds) after they have been issued.
This happens on the previous legitimate visit on a page that offers
a corresponding form that shall be protected. The RequestToken and Nonce
objects (later created implicitly in this example) are organized in the
\TYPO3\CMS\Core\Context\SecurityAspect .
<?phpuseTYPO3\CMS\Core\Security\RequestToken;
useTYPO3\CMS\Fluid\View\StandaloneView;
finalclassMyController{
private StandaloneView $view;
publicfunctionshowFormAction(){
// creating new request token with scope 'my/process' and hand over to view
$requestToken = RequestToken::create('my/process');
$this->view->assign('requestToken', $requestToken);
// ...
}
publicfunctionprocessAction(){
// for the implementation, see below
}
}
Invoke action request and provide nonce and request token values
When submitting the form and invoking the corresponding action, same-site
cookies typo3nonce_[hash] and request-token value __RequestToken are
sent back to the server. Without using a separate nonce in a scope that is
protected by the client, the corresponding request token could be easily
extracted from markup and used without having the possibility to verify the
procedural integrity.
The middleware
\TYPO3\CMS\Core\Middleware\RequestTokenMiddleware
takes care of providing the received nonce and received request token values
in
\TYPO3\CMS\Core\Context\SecurityAspect . The handling controller
action needs to verify that the request token has the expected
'my/process' scope.
<?phpuseTYPO3\CMS\Core\Context\Context;
useTYPO3\CMS\Core\Context\SecurityAspect;
useTYPO3\CMS\Core\Utility\GeneralUtility;
finalclassMyController{
publicfunctionshowFormAction(){
// for the implementation, see above
}
publicfunctionprocessAction(){
$context = GeneralUtility::makeInstance(Context::class);
$securityAspect = SecurityAspect::provideIn($context);
$requestToken = $securityAspect->getReceivedRequestToken();
if ($requestToken === null) {
// No request token was provided in the request// for example, (overridden) templates need to be adjusted
} elseif ($requestToken === false) {
// There was a request token, which could not be verified with the nonce// for example, when nonce cookie has been overridden by another HTTP request
} elseif ($requestToken->scope !== 'my/process') {
// There was a request token, but for a different scope// for example, when a form with different scope was submitted
} else {
// The request token was valid and for the expected scope$this->doTheMagic();
// The middleware takes care to remove the cookie in case no other// nonce value shall be emitted during the current HTTP requestif ($requestToken->getSigningSecretIdentifier() !== null) {
$securityAspect->getSigningSecretResolver()->revokeIdentifier(
$requestToken->getSigningSecretIdentifier(),
);
}
}
}
}
TYPO3 is capable of authentication via multiple factors, in short
"multi-factor authentication" or "MFA". This is sometimes also referred to
"2FA" as a 2-factor authentication process, where - in order to log in - the
user needs
"something to know" (= the password) and
"something to own" (= an authenticator device, or an authenticator app
on mobile phones or desktop devices).
TYPO3 ships with some built-in MFA providers by default. But more importantly,
TYPO3 provides an API to allow extension authors to integrate their own
MFA providers.
The API is designed in a way to allow providers to be used for TYPO3 backend
authentication or frontend authentication with a multi-factor step in-between.
Note
Currently, TYPO3 provides the integration for the TYPO3 backend. It is
planned to support multi-factor authentication for the frontend in the
future.
Managing MFA providers is currently possible via the User Settings
module in the tab called Account security.
Manage your MFA providers in the User Settings module
The Account security tab displays the current state:
whether MFA can be configured
whether MFA is activated or
whether some MFA providers are locked
Included MFA providers
TYPO3 Core includes two MFA providers:
Time-based one-time password (TOTP)
TOTP is the most common MFA implementation. A QR code is scanned (or alternatively,
a shared secret can be entered) to connect an authenticator app such as Google
Authenticator, Microsoft Authenticator, 1Password, Authly, or others to the
system and then synchronize a token, which changes every 30 seconds.
On each log-in, after successfully entering the password, the six-digit code
shown by the authenticator app must be entered.
Recovery codes
This is a special provider which can only be activated, if at least one other
provider is active. It is only meant as a fallback provider, in case the
authentication credentials for the "main" provider(s) are lost. It is encouraged
to activate this provider, and keep the codes at a safe place.
Each provider is displayed with its icon, the name and a short description in
the MFA configuration module. In case a provider is active, this is indicated by
a corresponding label, next to the provider's title. The same goes for a locked
provider - an active provider, which can currently not be used since the
provider-specific implementation detected some unusual behaviour, for example,
too many false authentication attempts. Additionally, the configured default
provider indicates this state with a "star" icon, next to the provider's title.
Each inactive provider contains a Setup button which opens the
corresponding configuration view. This view can be different depending on the
MFA provider.
MFA TOTP provider configuration screen
Each provider contains an Edit/Change button, which allows to adjust
the provider's settings. This view allows, for example, to set a provider as the
default (primary) provider, to be used on authentication.
Note
The default provider setting will be automatically applied on activation of
the first provider, or in case it is the recommended provider for this user.
In case the provider is locked, the Edit/Change button changes its button
title to Unlock. This button can be used to unlock the provider.
This, depending on the provider to unlock, may require further actions by the
user.
The Deactivate button can be used to deactivate the provider.
Depending on the provider, this will usually completely remove all
provider-specific settings.
The "Authentication view" is displayed as soon as a user
with at least one active provider has successfully passed the username and
password mask.
As for the other views, it is up to the specific provider, used for the current
multi-factor authentication attempt, what content is displayed in which view.
If the user has further active providers, the view displays them
as "Alternative providers" in the footer to allow the user to switch between all
activated providers on every authentication attempt.
All providers need to define a locking functionality. In case of the TOTP and
recovery code providers, this, for example, includes an attempts count. These
providers are locked in case a wrong OTP was entered three times in a
row. The count is automatically reset as soon as a correct OTP is
entered or the user unlocks the provider in the backend.
All TYPO3 Core providers also feature the "Last used" and "Last updated"
information which can be retrieved in the "Edit/Change" view.
By default, the field in the User Settings module is displayed for
every backend user. It is possible to
disable it for specific
users via user TSconfig:
setup.fields.mfaProviders.disabled = 1
Copied!
Administration of user's MFA providers
If a user is not able to access the backend anymore, for example, because all of
their active providers are locked, MFA needs to be disabled by an administrator
for this specific user.
Administrators are able to manage the user's MFA providers in the corresponding
user record. The new Multi-factor authentication field displays a
list of active providers and a button to deactivate MFA for the user, or
only a specific MFA provider.
Note
All of these deactivate buttons are executed immediately, after
confirming the dialog, and cannot be undone.
The listing of backend users in the System > Backend Users module
also displays for each user, whether MFA is enabled or currently locked. This
allows an administrator to analyze the MFA usage of their users at a glance.
The System > Configuration admininistration module shows an overview
of all currently registered providers in the installation. This is especially
helpful to find out the exact provider identifier, needed for some
user TSconfig options.
Do not require multi-factor authentication (default)
1
Require multi-factor authentication for all users
2
Require multi-factor authentication only for non-admin users
3
Require multi-factor authentication only for admin users
To set this requirement only for a specific user or user group, a user TSconfig
option auth.mfa.required <t3tsref:user-auth-mfa-required> is available.
The user TSconfig option overrules the global configuration.
auth.mfa.required = 1
Copied!
Allowed provider
It is possible to only allow a subset of the available providers for some users
or user groups.
A configuration option "Allowed multi-factor authentication providers" is
available in the user groups record in the "Access List" tab.
There may be use cases in which a single provider should be
disallowed for a specific user, which is configured to be allowed in
one of the assigned user groups. Therefore, the user TSconfig option
auth.mfa.disableProviders can
be used. It overrules the configuration from the "Access List": if a provider is
allowed in "Access List" but disallowed via user TSconfig, it will be disallowed
for the user or user group the TSconfig applies to.
This does not affect the remaining allowed providers from the "Access List".
To set a recommended provider on a per user or user group basis, the user
TSconfig option auth.mfa.recommendedProvider
can be used, which overrules the global configuration.
auth.mfa.recommendedProvider = totp
Copied!
TYPO3 integration and API
To register a custom MFA provider, the provider class has to implement the
EXT:core/Classes/Authentication/Mfa/MfaProviderInterface.php (GitHub), shipped via a
third-party extension. The provider then has to be configured in the extension's
Services.yaml or Services.php file with the
mfa.provider
tag.
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Authentication\Mfa\MyProvider:tags:-name:mfa.provideridentifier:'my-provider'title:'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myProvider.title'description:'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myProvider.description'setupInstructions:'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myProvider.setupInstructions'icon:'tx-myextension-provider-icon'
This will register the provider
MyProvider with the
my-provider
identifier. To change the position of your provider the
before and
after arguments can be useful. This can be needed, for example, if you
like your provider to show up prior to any other provider in the MFA
configuration module. The ordering is also taken into account in the
authentication step while logging in. Note that the user-defined default
provider will always take precedence.
If you do not want your provider to be selectable as a default provider, set the
defaultProviderAllowed argument to false.
You can also completely deactivate existing providers with:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationTYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvider:~
Copied!
You can also register multiple providers:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Authentication\Mfa\MyFirstProvider:tags:-name:mfa.provideridentifier:'my-provider-1'title:'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myProvider1.title'description:'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myProvider1.description'setupInstructions:'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myProvider1.setupInstructions'icon:'tx-myextension-provider1-icon'MyVendor\MyExtension\Authentication\Mfa\MySecondProvider:class:TYPO3\CMS\Core\Authentication\Mfa\Provider\TotpProvidertags:-name:mfa.provideridentifier:'my-provider-2'title:'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myProvider2.title'description:'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myProvider2.description'setupInstructions:'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myProvider2.setupInstructions'icon:'tx-myextension-provider2-icon'# Important so that this provider acts as a fallbackdefaultProviderAllowed:truebefore:'recovery-codes'# Execute after the primary totpafter:'totp'
Copied!
The
MfaProviderInterface contains a lot of methods to be implemented by
the providers. This can be split up into state-providing ones, for example,
isActive() or
isLocked(), and functional ones, for example,
activate() or
update().
Their exact task is explained in the corresponding PHPDoc of the interface files
and the Core MFA provider implementations.
All of these methods are receiving either the current
PSR-7 request object, the
\TYPO3\CMS\Core\Authentication\Mfa\MfaProviderPropertyManager or both.
The
MfaProviderPropertyManager can be used to retrieve and update the
provider-specific properties and also contains the
getUser() method,
providing the current user object.
To store provider-specific data, the MFA API uses a new database field
mfa,
which can be freely used by the providers. The field contains a JSON-encoded
array with the identifier of each provider as array key. Common properties of
such provider array could be active or lastUsed. Since the information is
stored in either the
be_users or the
fe_users table, the context
is implicit. Same goes for the user the providers deal with. It is important to
have such a generic field so providers are able to store arbitrary data, TYPO3
does not need to know about.
To retrieve and update the providers data, the already mentioned
MfaProviderPropertyManager, which is automatically passed to all
necessary provider methods, should be used. It is highly discouraged
to directly access the
mfa database field.
Autoloading
The class autoloader takes care of finding classes in TYPO3.
About
makeInstance()
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance() is a generic way
throughout Core and extensions to create objects. It takes care of singleton
and XCLASS handling.
Note
When dependent services are injected via Dependency Injection, there is no need for
makeInstance():
Injecting a different object is done by configuration - that's what
dependency injection is for.
A developer can instantiate classes using
makeInstance() - if dependency
injection cannot be used. There are some situations where
new is used
over
makeInstance(), effectively dropping especially the direct ability
to XCLASS:
Data transfer objects are often created with
new. A good example are
PSR-14 events: The calling class creates a data
transfer object that is hand over to the consumer. These DTOs must never be
changed by an extension, since they are a contract both caller and consumer
must stick to. They are thus created using
new to prevent XCLASSing.
Structures with a dedicated API that allows own implementations on a
configuration level sometimes do not use
makeInstance: Many Core
constructs come with an API to allow custom classes by dedicated
configuration. Those implement a factory pattern to deal with this. An
example is the PSR-15 middleware stack.
Autoloading classes
There is one autoloader used, the one of Composer. No matter if you run TYPO3 in
Composer mode or not (Classic mode), TYPO3 uses the Composer autoloader to
resolve all class file locations.
Loading classes with Composer mode
In Composer mode, the autoloader checks for (classmap and PSR-4) autoloading
information inside your extension's composer.json. If you do not provide
any information, the autoloader falls back to the classmap autoloading like in
non-Composer mode.
Troubleshooting
Dump the class loading information manually via composer dumpautoload and
check that the autoload information is updated. Typically you would check
vendor/composer/ to hold files like autoload_classmap.php and
autoload_psr4.php, etc.
This means, you did not install TYPO3 via a require statement inside your
composer.json. It's a regular old-school install where the TYPO3 source
and the symlinks (typo3/index.php) are setup manually.
In this case, every time you install an extension, the autoloader scans the
whole extension directory for classes. No matter if they follow any convention
at all. There is just one rule: put each class into its own file. This also
means that there can only be a single class per file.
You can also explicitly configure autoloading in the
ext_emconf.php.
The generated typo3conf/autoload_classmap.php is a large array with a
mapping of classnames to their location on the disk:
This method is failsafe unless the autoload information cannot be written. In
this case, check the Install Tool for warnings and make sure thattypo3temp/is writable.
Troubleshooting:
If your classes cannot be found, try the following approaches.
Dump the class loading information manually with the following command:
php typo3/sysext/core/bin/typo3 dumpautoload
Copied!
If that command itself fails, please (manually) uninstall the extension and
try reinstalling it (via the Extension Manager).
If you are still not lucky, the issue is definitely on your side and you
should double check the write permissions on typo3temp.
Best practices
If you didn't do so before, have a look at the PSR-4 standard. It defines
very good rules for naming classes and the files they reside in. Really,
read the specs and start using PSR-4 in your projects. It's unlikely that
there will be any other more advanced standard in the near future in the
PHP world. PSR-4 is the way to go and you should embrace it.
Even if you do not use Composer mode and the class mapping of the autoloader
allows you to use whatever you want, stick to PSR-4. It's not only a very
good standard to find classes, but it will also help organizing your code.
PSR-4 is all about namespaces. No matter if you like namespaces or not, use
them. Namespaces exist since PHP 5.3, so you will be able to use them in any
modern TYPO3 project due to the minimum PHP requirements of TYPO3 itself.
Tip
PSR-4 is a standard that has been developed by the PHP Framework Interop
Group (FIG). PSR-4 is an advanced standard for autoloading PHP classes and
replaces PSR-0. If you want to know more about the PHP FIG in general and
PSR-4 in specific, please visit https://www.php-fig.org/psr/psr-4/.
In our efforts to make TYPO3 faster and closer oriented
to common PHP standard systems, we looked into the integration of the
class loader that is used by all Composer-based projects. We consider
this functionality a crucial feature for the future of TYPO3 on the
base level, but also as a dramatic increase of the overall performance
of every request inside TYPO3.
Understanding the TYPO3 class loader
The TYPO3 class loader is instantiated within the TYPO3 Bootstrap at
a very early point. It does a lot of logic (checking ext_autoload.php,
ClassAliasMap.php), and caches this logic away on a per-class basis by
default in typo3temp/Cache/ to store all information for a class. This
information contains: The full path to the class, the namespaced class
name itself and possible class aliases.
The latter part looks into all extensions and checks the
Migrations/ClassAliasMap.php file for any possible “legacy class” that
could be used (e.g. t3lib_extmgm). This way, all extensions still using
non-namespaced class that are shipped with the TYPO3 core are still made
available.
The information is stored in a SimpleFileBackend via the built-in
Caching Framework by default. At the early stage of the bootstrap
process some classes need to be included manually as the whole TYPO3
core engine has not been loaded yet. This is done for all PHP classes in
use, which may result in 500+ files inside typo3temp/Cache which are
created one by one on an initial request with no caches set up. This is
done by intention on a per-file-basis during runtime as a cache file is
only created if a PHP class is requested to be instantiated. On a second
hit, the caching framework does not create the cache files, but fetches
one by one for each class used in the request via a separate
file_get_contents() call.
When debugging TYPO3 on an initial request, there are a
lot of file_get_contents() and file_put_contents() calls to
store and fetch this information. This is quite a lot of overhead for loading
PHP classes. Even without a lot of class aliases (e.g. in CMS7) this
overhead of writing / storing the file caches still exists. Some
overhead however is already taken care, especially if a class is loaded
which is shipped with the core and no class alias is used.
This is all built in a way so a lot of backwards-compatibility can be
ensured.
Understanding the Composer class loader
Compared to the TYPO3 class loader, the Composer class loader
concept differs in the following major points:
Caching on build stage
When setting up a project, like a TYPO3 project, Composer checks the
main composer.json of a project and builds a static file with all PSR-4
prefixes defined in that json file. Unless in a development environment
or when updating the source, this file does not need to be rebuilt as
the PHP classes of the loaded packages won’t change in a regular
instance. This way all classes available inside TYPO3 are always
available to the class loader.
Using PSR-4 compatible prefix-based resolving
Instead of looking up every single class and caching the information
away, Composer works on a “prefix”-based resolution. As an example, the
Composer class loader only needs to know that all PHP classes starting
with
\TYPO3\CMS\Core are located within EXT:core/Classes. The
rest is done by a simple resolution to include the necessary PHP class
files. This means that the information to be cached away is only the
list of available namespace prefixes.
The definition of these prefixes is set inside the composer.json file of each
package or distribution / project.
Autoloading developer-specific data differently
The Composer class loader checks the composer.json for a development
installation differently, including for example unit and functional tests
separately to the rest of the installation. The static map with all
namespaces are thus different when using Composer with composer
install or composer install --no-dev.
Integration Approach
The Composer class loader is injected inside the Bootstrap process of
TYPO3 and registered before the TYPO3 class loader. This means that
a lookup on a class name is first checked via the Composer logic, and if
none found, the regular TYPO3 class loader takes over.
The support for class aliases is quite important for TYPO3, but is
not supported by Composer by default. There is a separate Composer
package created by Helmut Hummel (available on GitHub) which serves as a facade
to the Composer class loader and creates not just the information for
the prefixes but also the available class aliases for a class and loads
them as well.
The necessary information about the “which namespaced classes can be
found at which place” is created before every release and shipped inside
the typo3_src directory. The generated class information is available
under typo3/contrib/vendor/composer/. For TYPO3 installations that are
set up with composer, the TYPO3 bootstrap checks
Packages/Libraries/autoload.php first which can be shipped with any
Composer-based project and include many more PHP Composer packages than
just TYPO3 extensions. To ensure maximum backwards-compatibility, the
option to load from Packages/Library/autoload.php instead of the shipped
"required-core-packages-only" needs to be activated via an environment
variable called
TYPO3_COMPOSER_AUTOLOAD which needs to be set on
server-side level.
If the Composer-based logic is not used in some legacy cases (for
extensions etc), the usual TYPO3 class loader comes into play and does
the same logic as before.
Project setup and extension considerations
If you already use Composer to set up your project, and the
composer.json and their extensions ship a valid composer.json, the
Composer class loader generates the valid PSR-4 cache file with all
prefixes on installation and update. Running "composer update" will
automatically re-generate the PSR-4 cache file.
The Composer class loader also supports PSR-0 and static inclusion of
files, which can be used as well.
As a base line: Any regular installation will see a proper speed
improvement after the update to the Composer class loader.
Backend APIs
The following APIs are of interest if you want to configure or extend the
functionalities of the backend.
TYPO3 features an access control system based on users and groups.
Users
Each user of the backend must be represented with a single record in
the table "be_users". This record contains the username and password,
other meta data and some permissions settings.
Part of the editing form for user "simple_editor" of the Introduction Package
The above screenshot shows a part of the editing form for the backend
user "simple_editor" from the Introduction Package. If you have an Introduction
Package available, you can check further properties of that user. It is
part of the "Simple editors" group, has a name, an email address and
its default language for the backend is English.
It is possible to assign rights directly to a user, but it is much better
done using groups. Furthermore groups offer far more options.
Groups
Each user can also be a member of one or more groups (from the
"be_groups" table) and each group can include sub-groups. Groups
contain the main permission settings you can set for a user. Many
users can be a member of the same group and thus share permissions.
When a user is a member of many groups (including sub-groups) then the
permission settings are added together so that the more groups a user
is a member of, the more access is granted to him.
Part of the editing form for group "Simple editors" of the Introduction Package
This screenshot shows just an extract of the group editing form.
It contains many more fields!
There is a special kind of backend users called "Admin".
When creating a backend user, just check the "Admin!" box in the
"General" tab and that user will become an administrator.
There's no need to set further access options for such a user:
an admin user can access every single feature of the TYPO3
backend, like the "root" user on a UNIX system.
All systems must have at least one "admin" user and most systems
should have only "admin" users for the developers - not for any
editor. Make sure to not share TYPO3 accounts with multiple users
but create dedicated accounts for everyone.
Not even "super users" should be allowed "admin" access since that
will most likely grant them access to more than they need.
Admin users are differentiated with an orange icon.
In Web > List view, the different icon for admin users
Note
There's no other level between admin and ordinary users.
This seems to be a strong limitation, especially
when you consider that ordinary users may not access TypoScript
templates.
However, there is a security reason for this. From a TypoScript template,
you can call a PHP script. So - in effect - a user with access to
TypoScript can run arbitrary PHP code on the server, for example
in order to create an admin account for himself. This type of escalation
cannot be allowed.
Location of users and groups
Since both backend users and backend groups are represented by records
in the database, they are edited just as any other record in the
system. However backend users and groups are configured to exist
only in the root of the page tree where only admin users have
access:
Users and groups reside on the root page
Records located in the page tree root are identified by having their
"pid" fields set to zero. The "pid" field normally contains the
relation to the page where a record belongs. Since no pages can have
the id of zero, this is the id of the root. Notice that only "admin"
users can edit records in the page root!
If you need non-admin users to create new backend users, have a look
at the TYPO3 system extension sys_action for a possible solution.
Password reset functionality
TYPO3 backend users can reset their password if they use the default TYPO3 login
mechanism.
To display the reset link on the backend login page, the following criteria must
be met:
The user has a password entered previously (indicating that no third-party
login has been used).
The user has a valid email address added to their user record.
The user is neither deleted nor disabled.
The email address is used only once for all backend users of the instance.
Once the user has entered their email address, an email is sent with a link that
allows to set a new password, which must consist of at least eight characters.
The link is valid for 2 hours and a token is added to the link. If the password
is entered correctly, it will be updated for the user and they can log in.
New in version 12.1
The new password that the user specifies must comply with the configured
password policy for the backend.
New in version 12.3
The username of the backend user is displayed in the password recovery
email alongside the reset link.
Notes on security
When having multiple users with the same email address, no reset
functionality is provided.
No information disclosure is built-in, so if the email address is not in the
system, it is not disclosed to the outside.
Rate limiting is enabled so that three emails can be sent per email address
within 30 minutes.
Tokens are stored in the database but hashed again just like the password.
When a user has logged in successfully (for example, because they remembered
the password), the token is removed from the database, effectively
invalidating all existing email links.
Implications of displaying the username in the email
A third-party gaining access to the email account has all information needed
to log in into the TYPO3 backend and potentially cause damage to the
website.
Without the username a third-party could only reset the password of the
TYPO3 backend user, but not log in, if the username is different from the
email address.
It is also possible to override the ResetRequested email templates
to remove the username and customize the result.
Both options can be configured in the Admin Tools > Settings module
or in the Install Tool, but can also be set manually via
config/system/settings.php or config/system/additional.php.
Reset password for user
Administrators can reset a user's password. This is useful primarily for
security reasons, so that an administrator does not have to send a password over
to a user in plain text (for example, by email).
The backend URL is necessary to generate the correct links to the TYPO3
instance from CLI context.
Roles
Another popular approach to setting up users is roles. This
concept is basically about identifying certain roles that users can
take and then allow for a very easy application of these roles to
users.
TYPO3 access control is far more flexible and allows for such detailed
configuration that it lies very far from the simple and straight
forward concept of roles. This is necessary as the foundation of a
system like TYPO3 should fit many possible usages.
However it is perfectly possible to create groups that act like "roles".
This is what you should do:
Identify the roles you need; Developer, Administrator, Editor, Super
User, User, ... etc.
Configure a group for each role: attribute permissions needed to
fulfill each role.
Consider having a general group which all other groups include - this
would basically configure a shared set of permissions for all users.
Access Control Options
The permissions of fully initialized backend users are the result of the
rights granted in their own user records and all the user groups they
belong to.
The permissions are divided into the following conceptual categories:
Access lists
These grant access to backend modules, database tables and fields.
Mounts
Parts of the page tree and server file system.
Page permissions
Access to work on individual pages based on the user id and group ids.
User TSconfig
A flexible and hierarchical configuration structure
defined by TypoScript syntax. This typically describes "soft"
permission settings and options for the user or group which can be used to
customize the backend and individual modules.
Access lists are defined at group-level. Usage of access lists for defining
user rights is described in chapter Setting up User Permissions.
The various access lists are described here for reference, with
additional technical details, where necessary.
Note
Access list don't apply to admin users. As mentioned before, admin
users have access to every single feature of the TYPO3 CMS backend.
Modules
This is a list of submodules a user may be given access to. Access to a main
module is implicit, as soon as a user has access to at least one of its
submodules.
Not all submodules appear in this list. It is possible to restrict a
submodule to admin users only. This is the case, in particular, for all
Admin Tools and System modules, as well as the
Web > Template module.
Note
This is the only access list that is also available for definition
at user-level.
This section is only available with activated
dashboard system extension.
Tables for listing
A list of all tables a user may be allowed to read in the backend.
Again this in not a list of all tables in the database. Some tables
are low level and never appear in the backend at all, even for admin
users. Other tables are restricted to admin users and thus do not show
up in the access list.
All tables that are allowed for modification (see below) are
also allowed for read access, so no need to select them in this
list as well.
Tables for editing
This is exactly the same list of tables as before, but for granting
modification rights.
Page types
TYPO3 CMS defines a number of page types. A user can be restricted
to access only some of them.
For a full discussion on page types, please refer to the
page types chapter.
Excludefields
When defining column tables in TCA, it is possible to set the
"exclude" property to "1".
This ensures that the field is hidden to users by default.
Access to it must be explicitly granted in this access list.
Explicitly allow/deny field values
When a field offers a list of options to select from, it is possible
to tell TYPO3 CMS that access to these options is restricted and should
be granted explicitly. Such fields and their values appear here.
By default users can edit records regardless of what language they are assigned to.
Using this list it is possible to restrict users to working only in selected
languages.
When a user is a member of more than one group, the access lists for
the groups are "added" together.
Mounts
TYPO3 CMS natively supports two kinds of hierarchical tree structures:
the page tree (typically visible in the Web module) and the folder
tree (typically visible in the File module). Each tree is
generated based on the mount points configured for the current user. So a
page tree is drawn from the DB Mounts which are one or more page ids
telling the Core from which "start page" to draw the tree(s). Likewise
is the folder tree drawn based on filemounts configured for the user.
DB mounts (page mounts) are set by pointing out the
page that should be mounted for the user (at user or group-level):
The DB mounts for group "Editors"
This is what the user will see:
Warning
A DB mount will appear only if the page permissions
allow the user at least read access to the mounted page (and subpages).
Otherwise nothing will appear at all!
File Mounts are a little more difficult to set up, as they
involve several steps. First of all, you need to have at least
one File Storage. By
default, you will always have one, pointing
to the fileadmin directory. It is created by TYPO3 CMS
upon installation.
Note
The fileadmin directory is the default place where
TYPO3 CMS expects media resources to be located. It can be
changed using the global configuration option
$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'].
A File Storage is essentially defined by a File Driver
and the path to which it points.
Next we can create a File Mount record (on the root page),
which refers to a File Storage:
A file mount pointing to the "user_upload" directory
When defining a File Mount, you can point to a specific folder
within the chosen File Storage. Finally the mount is assigned
to a user or group:
The file mount is assigned to the "Editors" group
After a successful configuration, the file mount will appear to
the user:
DB and File Mounts can be set for both the user and group records.
Having more than one DB or File Mount will just result in more than
one mount point appearing in the trees. However the backend users
records have two flags which determine whether the DB/File Mounts of
the groups the user belongs to will be mounted as well! This is
the default behaviour. So make sure to unset these flags if users
should see only their "private" mount points and not those from their
groups:
By default DB and File Mounts from groups are set for member users
"Admin" users do not need mount points. As always, they have access
to every part of the installation.
Page Permissions
Page permissions are designed to work like file permissions on UNIX
systems. Each page record has an owner user and group and
permission settings for the owner, the group and "everybody". This is
summarized here:
Every page has an owner, group and everybody-permission
The owner and group of a page can be empty. Nothing matches with an
empty user/group (except "admin" users).
Every page has permissions for owner, group and everybody in these
five categories (next to the label is the corresponding value):
Show (1)
See/Copy page and the page content.
Edit page content (16)
Change/Add/Delete/Move page content.
Edit page (2)
Change/Move the page, eg. change title, startdate, hidden flag.
Delete page (4)
Delete the page and page content.
New pages (8)
Create new pages under the page.
Note
Here "Page content" means all records related to that page,
except other pages.
Page permissions are set and viewed with the module System > Access
module:
The Access module and its overview of page rights and owners
Editing permissions is described in details in chapter
Page Permissions.
A user must be "admin" or the owner of a page in order to edit its
permissions.
When a user creates new pages in TYPO3 CMS they will by default get the
creating user as owner. The owner group will be set to the first
listed user group configured for the users record (if any). These defaults
can be changed through page TSconfig.
User TSconfig
User TSconfig is a hierarchical configuration structure entered in
plain text TypoScript. It can be used by all kinds of applications
inside of TYPO3 CMS to retrieve customized settings for users which
relates to a certain module or part. The options available are
described in the document TSconfig .
Other Options
This chapter presents a few more, miscellaneous options for
backend users and groups.
Backend users
Default language
This is the language in which the backend will be localized for the
user. The users can change the language themselves in the
User Settings
module.
Note
Language packs must be downloaded using the Admin Tools > Maintenance > Manage Language Packs
module. As long as the language packs are not available, the backend
will still display in English.
Fileoperation permissions
This is a complement to the File Mounts and defines exactly which operations
the user is allowed to perform on both files and folders.
Access options
A backend user can be disabled (first flag in the "General" tab). A disabled
user cannot log into the backend anymore. Furthermore, in the "Access" tab
a start and end time can be given, defining a time interval during which the
user will be allowed to log into the backend. Authentication before the start
time and after the end time will automatically fail.
Lock to domain
This setting constrains the user to use a specific domain for logging
into the TYPO3 backend. This is very useful in setups with multiple
sites.
Backend Groups
Disable
Setting this flag will immediately disable the group for
all members
Lock to domain
This restricts a group to a given domain. If a user logs in from another
domain, that group membership will be ignored.
Hide in lists
This flag will prevent the group from appearing in various
listings in TYPO3. This includes modules like System > Access.
Inherit settings from groups (Sub Groups)
Assigns sub-groups to this group. Sub-groups are
evaluated before the group including them. If a user is a member of
a group which includes one or more sub-groups, the user will also be
a member of the sub-groups.
More about file mounts
File mounts require a little more description of the concepts provided
by TYPO3. All files are handled by an application layer called
the "File Abstraction Layer" (FAL). You can find more information
about the basic concepts of FAL.
The FAL is comprised of the following components:
Drivers
Drivers are what makes it possible to access a given type of media
storage. The Core provides a driver to access the local file system.
Extensions exist that provide a driver for remote systems, like SFTP
or platforms like Amazon S3.
Storages
A file storage uses a driver to connect to a given storage system.
It is defined by a path pointing into that storage system. There can
be several storages using the same driver and pointing to different
"directories". The storage configuration depends on the driver it uses.
Thanks to the storage and its driver, the user is able to browse
files from within the TYPO3 backend as if they were stored locally.
File mounts
As discussed before, a file mount is the element which is used to
actually give access to users to some directories. A file mount is always
related to a storage.
Create a new filemount
New in version 11.3
Starting with TYPO3 v11.3 it is possible to create a new filemount via
the context menu of the folder.
To create a new filemount go to the module File > Filelist and create the
folder for the mount if it didn't exist yet. Then open the context menu on that
folder and choose New Filemount, then give the new filemount a name.
The entry point is already set.
It is also possible to create a filemount manually in the List
module by creating a record of type
Filemount. In this case you have
to choose the storage and folder manually.
Paths for local driver storage
The file storages based on the "local file system" driver have an
option for relative or absolute paths.
The paths options for a storage based on the local file system driver
"Relative" means that the given path is relative to the fileadmin/
folder (or whatever other folder was configured using
$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'].
Absolute paths are full paths starting at the root of the file system
(i.e. / on Unix systems).
Attention
If the fileadmin/ location is not changed, be aware
that all files beneath it will be accessible via a browser,
since this directory is located below the web root. This is
perfectly fine in most cases, and indeed generally a desired
behaviour. Just be careful what kind of files you store in that
place, as they will be publicly accessible.
Absolute paths outside of the web root
must be explicitly declared in the global configuration option
$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']. Any absolute
path that you want to declare in a file storage needs to have its
first part match the value of $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']
(or of the web root, which can be retrieved with \TYPO3\CMS\Core\Core\Environment::getPublicPath()).
As an example, let's say you want to define two storages, one
pointing to /home/foo/bar and one pointing to
/home/foo/baz. You could declare
$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] to be
equal to /home/foo/.
Home directories
TYPO3 CMS also features the concept of "home directories". These are paths
that are automatically mounted if they are present at a path
configured in the global configuration. Thus they don't need to have a file
mount record representing them - they just need a properly named
directory to be present.
Note
That last sentence is important. The directories need to exist.
They are not created automatically.
The parent directory of user/group home directories is defined by
$GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath'] and
$GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath']
respectively. Let's say we define the following:
The first part of the definition (before the colon :) is
the id of a file storage. The second part is a path relative to
that file storage. Assuming file storage with a uid of "1" is the
default one pointing to fileadmin/, the following path
needs to exist on the server: /path/to/web/root/fileadmin/user_homes/.
Then a directory needs to exist for each user. Again let's assume that
we have a user with a uid of "3" and a username of "editor", either
of those paths would have to exist:
/path/to/web/root/fileadmin/user_homes/3/
/path/to/web/root/fileadmin/user_homes/3_editor/
The second possibility is more explicit, but will break if the
username is changed.
The same goes for groups, but only using the uid.
Assuming a group called "editors" with a uid of "1",
and:
we have to create a directory /path/to/web/root/fileadmin/groups/1/.
Having set up all these properties and folders, the user
should see the following when moving to the FILE > Filelist
module:
where only the first mount was explicitly assigned to that user.
A different icon visually distinguishes automatic file mounts.
Note
If the $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir'] option is
also used, it is appended to the user home directory name. Thus a value
of _uploads would mean that our home directories become
/path/to/web/root/fileadmin/user_homes/3_uploads/
or /path/to/web/root/fileadmin/user_homes/3_editor_uploads/.
This does not apply to group home directories.
The concept of home directories can be efficiently combined with
the TSconfig defaultUploadFolder option,
which automatically directs all files uploaded by the user to the
given directory.
Backend users module
The System > Backend users module offers a convenient way
of working with backend users and groups. It provides a list of both
users and groups. The users list can be searched and filtered.
Comparing Users or Groups
The Backend users module offers the possibility to compare users.
Just add users using the
"+ Compare" button and then hit the "Compare user list" button.
For example, this is the comparison of the three different editors
provided by the Introduction Package:
Comparing users in the Backers users module
The same functionality is available for user groups, including a comparison
of their inherited permissions.
Impersonating Users ("Switch to")
We can impersonate (switch) to a user by clicking the Switch to user
action icon:
The button to simulate another user
You will then be logged in as that user (note how the user name
is prefixed with "SU" for "Simulated User"). To "switch back",
use the "Exit" button (which replaces the usual "Logout"
button).
This chapter describes the API that can be used to create custom backend modules
in extensions. See the following chapter for a tutorial on how to create
custom backend modules.
The PSR-14 BeforeModuleCreationEvent allows extension authors
to manipulate the module configuration before it is used to create and
register the module.
Registration of backend modules was changed with version 12. If you are
using an older version of TYPO3 please use the version switcher on the top
left of this document to go to the respective version.
The Configuration/Backend/Modules.php configuration files are
read and processed when building the container. This
means the state is fixed and cannot be changed at runtime.
If the module should be a submodule, the parent identifier, for example web
has to be set here. Have a look into the
list of available toplevel modules.
Extensions can add additional parent modules, see
Toplevel modules.
path
path
Type
string
Default
/module/<mainModule>/<subModule>
Define the path to the default endpoint. The path can be anything, but
will fallback to the known /module/<mainModule>/<subModule> pattern,
if not set.
access
access
Type
string
Can be user (editor permissions), admin, or systemMaintainer.
workspaces
workspaces
Type
string
Can be * (= always), live or offline. If not set, the value of the
parent module - if any - is used.
position
position
Type
array
The module position. Allowed values are top and bottom as
well as the key value pairs before => <identifier> and
after => <identifier>.
Whether the module should use the parents navigation component.
This option defaults to
true and can therefore be used to
stop the inheritance for submodules.
moduleData
moduleData
Type
array
All properties of the module data object
that may be overridden by
GET /
POST parameters of the request
get their default value defined here.
Example
Excerpt of EXT:my_extension/Configuration/Backend/Modules.php
List of identifiers that are aliases to this module. Those are added as route
aliases, which allows to use them for building links, for example with the
\TYPO3\CMS\Backend\Routing\UriBuilder . Additionally, the aliases can
also be used for references in other modules, for example to specify a
module's parent.
Examples
Example for a new module identifier:
Excerpt of EXT:my_extension/Configuration/Backend/Modules.php
<?phpdeclare(strict_types=1);
return [
'workspaces_admin' => [
'parent' => 'web',
// ...// choose the previous name or an alternative name'aliases' => ['web_WorkspacesWorkspaces'],
],
];
Copied!
Example for a route alias identifier:
Excerpt of EXT:my_extension/Configuration/Backend/Modules.php
Generic side information that will be merged with each generated
\TYPO3\CMS\Backend\Routing\Route::$options array. This can be
used for information, that is not relevant for a module aspect,
but more relevant for the routing aspect, for example
sudo mode.
Define the routes to this module. Each route requires at least the target.
The _default route is mandatory, except for modules which can fall back
to a submodule. The path of the _default route is taken from the
top-level configuration. For all other routes, the route identifier is taken
as path, if not explicitly defined. Each route can define any
controller/action pair and can restrict the allowed HTTP methods:
Excerpt of EXT:my_extension/Configuration/Backend/Modules.php
All subroutes are automatically registered in a
\TYPO3\CMS\Core\Routing\RouteCollection . The full syntax for route
identifiers is <module_identifier>.<sub_route>, for example,
my_module.edit. Therefore, using the
\TYPO3\CMS\Backend\Routing\UriBuilder to create a link to such a
sub-route might look like this:
Using these Extbase configurations tells the Core to bootstrap Extbase and expecting
controllers based on
\TYPO3\CMS\Extbase\Mvc\Controller\ActionController .
Do not use it for non-Extbase controller. Use routes
instead.
The extension name in UpperCamelCase for which the module is registered. If
the extension key is my_example_extension the extension name would be
MyExampleExtension.
controllerActions
controllerActions
Type
array
Define the controller action pair. The array keys are the
controller class names and the values are the actions, which
can either be defined as array or comma-separated list:
Excerpt of EXT:my_extension/Configuration/Backend/Modules.php
The modules define explicit routes for each controller/action combination,
as long as the
enableNamespacedArgumentsForBackend feature
toggle is turned off (which is the default). This effectively means
human-readable URLs, since the controller/action combinations are no longer
defined via query parameters, but are now part of the path.
The route identifier of corresponding routes is registered with similar
syntax as standard backend modules:
<module_identifier>.<controller>_<action>. Above configuration will
therefore register the following routes:
web_ExtkeyExample
web_ExtkeyExample.MyModule_list
web_ExtkeyExample.MyModule_detail
Debug the module configuration
All registered modules are stored as objects in a registry. They can be viewed
in the backend in the System > Configuration > Backend Modules
module.
Exploring registered Backend Modules in the Configuration module
The ModuleProvider API allows extension
authors to work with the registered modules.
Backend modules with sudo mode
You can configure the sudo mode in your backend module like this:
This allows administrators to define access permissions via the module
access logic for those modules individually. It also allows to influence the
position of the third-level module.
Example
Registration of an additional third-level module for the
Web > Template module in the Configuration/Backend/Modules.php
file of an extension:
The following toplevel modules are provided by the Core:
web: Web
All modules requiring a page tree by default. These modules are mostly used
to manage content that should be displayed in the frontend.
site: Site Management
Settings for the complete site such as redirects and site settings.
file: File
All modules requiring a file system tree such as modules dealing with file
metadata, uploading etc.
tools: Admin Tools
By convention modules in this toplevel section should only be
available for admins with system maintainer rights. Therefore the
configuration array of a module displayed here should always have the
following key-value pair:
'access' => 'systemMaintainer'.
In this toplevel section modules that deal with installing and updating the Core
and extensions are available. System-wide settings are also found here.
system: System
By convention, modules in this toplevel section should only be
accessible by admins. Therefore the configuration array of a module
displayed here should always have the following key-value pair:
'access' => 'admin'.
In this toplevel section modules are situated that deal with backend user
rights or might reveal security relevant data.
The backend user interface is essentially driven by the "backend"
system extension and extended by many other system extensions.
It is divided into the following main areas:
An overview of the visual structure of the backend
Top bar
The top bar is always present. It is itself divided into two
areas: the logo and top bar tools.
The logo can be changed using the
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['backend']['backendLogo'] option.
Additional top bar tools can be registered using
$GLOBALS['TYPO3_CONF_VARS']['BE']['toolbarItems'] .
Module menu
This is the main navigation. All modules are structured in main
modules (which can be collapsed) and submodules which is where
the action really happens.
The module menu can be opened or closed by using the icon on the top left.
In the TYPO3 world, "module" is typically used for
the backend. Extension components which add features in the frontend
are referred to as "plugins".
Navigation frame
Any backend module may have a navigation frame or not. This frame
will typically display the page tree or the folder tree, but
custom navigation frames are possible.
The current location (i.e. page or frame) is carried over between
navigation frames when changing modules. This means, for example, that
when you move from the Web > Page module to the
Web > List module, the same page stays selected in the page tree.
DocHeader
This part is always located above the Content area. It will generally
contain a drop-down menu called the "Function menu", which allows to
navigate into the various functions offered by the module. When editing
it will also contain all the buttons necessary for saving, closing or
reverting. It may contain additional buttons for shortcuts
or any specific feature needed by the module.
Content area
This is the actual work area. Any information to show or content
to edit will be displayed here.
Contextual menus
(Right) clicking on record icons will often reveal a contextual menu.
New functions can be added to the contextual menus, but the
mechanisms vary: the page tree behaves differently than the
rest of the backend.
A typical contextual menu appears when clicking on a record icon
DocHeaderComponent
The
\TYPO3\CMS\Backend\Template\Components\DocHeaderComponent can be
used to display a standardized header section in a backend module with buttons,
menus etc. It can also be used to hide the header section in case it is
not desired to display it.
The module header displayed by the DocHeaderComponent
The
\TYPO3\CMS\Backend\Module\ModuleData object contains the user
specific module settings, for example whether the clipboard is shown,
for the requested module. Those settings
are fetched from the user's session. A PSR-15 middleware automatically
creates the object from the stored user data and attaches it to the PSR-7 Request.
The
\TYPO3\CMS\Backend\Module\ModuleData object is available as
attribute of the PSR-7 Request - in case a TYPO3 backend module is requested -
and contains the stored module data, which might have been overwritten
through the current request (with
GET /
POST).
Through the module registration one can define, which properties can
be overwritten via
GET /
POST and their default value.
The whole determination is done before the requested route target - usually
a backend controller - is called. This means, the route target can
read the final module data.
The allowed properties are defined with their default value in the
module registration:
The
ModuleData object provides the following methods:
Method
Parameters
Description
createFromModule()
$module
$data
Create a new object for the given module, while
overwriting the default values with
$data.
getModuleIdentifier()
Returns the related module identifier
get()
$propertyName
$default
Returns the value for
$propertyName, or the
$default, if not set.
set()
$propertyName
$value
Updates
$propertyName with the given
$value.
has()
$propertyName
Whether
$propertyName exists.
clean()
$propertyName
$allowedValues
Cleans a single property by the given allowed
list and falls back to either the default value
or the first allowed value.
cleanUp()
$allowedData
$useKeys
Cleans up all module data, which are defined in
the given allowed data list. Usually called with
$MOD_MENU in a controller with module menu.
toArray()
Returns the module data as
array.
In case a controller needs to store changed module data, this can still be done
using
$backendUser->pushModuleData('my_module', $this->moduleData->toArray());.
Note
It is possible to store and retrieve arbitrary module data. The
definition of
moduleData in the module registration only defines,
which properties can be overwritten in a request (with
GET /
POST).
To restrict the values of module data properties, the given
ModuleData
object can be cleaned, for example, in a controller:
If
ModuleData contains
property, the value is checked
against the
$allowedValues list. If the current value is valid,
nothing happens. Otherwise the value is either changed to the default
or if this value is also not allowed, to the first allowed value.
ModuleInterface
The registered backend modules are stored as objects in a registry and can be
fetched using the \TYPO3\CMS\Backend\Module\ModuleProvider.
All module objects implement the
\TYPO3\CMS\Backend\Module\ModuleInterface .
The
ModuleInterface basically provides getters for the options
defined in the module registration and additionally provides methods for
relation handling (main modules and sub modules).
The internal name of the module, used for referencing in permissions etc
Returns
string
getPath()
Return the main route path
Returns
string
getIconIdentifier()
The icon identifier for the module
Returns
string
getTitle()
The title of the module, used in the menu
Returns
string
getDescription()
A longer description, common for the "About" section with a long explanation
Returns
string
getShortDescription()
A shorter description, used when hovering over a module in the menu as title attribute
Returns
string
isStandalone()
Useful for main modules that are also "clickable" such as the dashboard module
Returns
bool
getComponent()
Returns the view component responsible for rendering the module (iFrame or name of the web component)
Returns
string
getNavigationComponent()
The web component to be rendering the navigation area
Returns
string
getPosition()
The position of the module, such as [top] or [bottom] or [after => anotherModule] or [before => anotherModule]
Returns
array
getAppearance()
Returns a modules appearance options, e.g. used for module menu
Returns
array
getAccess()
Can be user (editor permissions), admin, or systemMaintainer
Returns
string
getWorkspaceAccess()
Can be "*" (= empty) or "live" or "offline"
Returns
string
getParentIdentifier()
The identifier of the parent module during registration
Returns
string
getParentModule()
Get the reference to the next upper menu item
Returns
?\TYPO3\CMS\Backend\Module\ModuleInterface
hasParentModule()
Can be checked if the module is a "main module"
Returns
bool
hasSubModule(string $identifier)
Checks whether this module has a submodule with the given identifier
param $identifier
the identifier
Returns
bool
hasSubModules()
Checks if this module has further submodules
Returns
bool
getSubModule(string $identifier)
Return a submodule given by its full identifier
param $identifier
the identifier
Returns
?\TYPO3\CMS\Backend\Module\ModuleInterface
getSubModules()
Return all direct descendants of this module
Returns
\ModuleInterface[]
getDefaultRouteOptions()
Returns module related route options - used for the router
Returns
array
getDefaultModuleData()
Get allowed and available module data properties and their default values.
Returns
array
getAliases()
Return a list of identifiers that are aliases to this module
Returns
array
ModuleProvider
The
ModuleProvider API allows extension authors to work with the
registered modules.
This API is the central point to retrieve modules, since it
automatically performs necessary access checks and prepares specific structures,
for example for the use in menus.
ModuleProvider API
classModuleProvider
Fully qualified name
\TYPO3\CMS\Backend\Module\ModuleProvider
This is the central point to retrieve modules from the ModuleRegistry, while
performing the necessary access checks, which ModuleRegistry does not deal with.
isModuleRegistered(string $identifier)
Simple wrapper for the registry, which just checks if a
module is registered. Does NOT perform any access checks.
Return the requested (main) module if exist and allowed, prepared
for menu generation or similar structured output (nested). Takes
TSConfig into account. Does not respect "appearance[renderInModuleMenu]".
Backend controllers should use ModuleTemplateFactory::create()
to create instances of a
\TYPO3\CMS\Backend\Template\ModuleTemplate .
API functions of the
ModuleTemplate can be used to add buttons to
the button bar. It also implements the
\TYPO3\CMS\Core\View\ViewInterface
so values can be assigned to it in the actions.
classModuleTemplate
Fully qualified name
\TYPO3\CMS\Backend\Template\ModuleTemplate
A class taking care of the "outer" HTML of a module, especially
the doc header and other related parts.
assign(string $key, ?mixed $value)
Add a variable to the view data collection.
param $key
the key
param $value
the value
Returns
self
assignMultiple(array $values)
Add multiple variables to the view data collection.
param $values
the values
Returns
self
render(string $templateFileName = '')
Render the module.
param $templateFileName
the templateFileName, default: ''
Returns
string
renderResponse(string $templateFileName = '')
Render the module and create an HTML 200 response from it. This is a
lazy shortcut so controllers don't need to take care of this in the backend.
param $templateFileName
the templateFileName, default: ''
Returns
\Psr\Http\Message\ResponseInterface
setBodyTag(string $bodyTag)
Set to something like '<body id="foo">' when a special body tag is needed.
param $bodyTag
the bodyTag
Returns
self
setTitle(string $title, string $context = '')
Title string of the module: "My module · Edit view"
param $title
the title
param $context
the context, default: ''
Returns
self
getDocHeaderComponent()
Get the DocHeader. Can be used in controllers to add custom
buttons / menus / ... to the doc header.
ModuleTemplate by default uses queue 'core.template.flashMessages'. Modules
may want to maintain an own queue. Use this method to render flash messages
of a non-default queue at the default position in module HTML output. Call
this method before adding single messages with addFlashMessage().
param $flashMessageQueue
the flashMessageQueue
Returns
self
setUiBlock(bool $uiBlock)
UI block is a spinner shown during browser rendering phase of the module,
automatically removed when rendering finished. This is done by default,
but the UI block can be turned off when needed for whatever reason.
param $uiBlock
the uiBlock
Returns
self
getView()
Deprecated: since v12, will be removed in v13.
Returns
\TYPO3\CMS\Fluid\View\StandaloneView
setContent(string $content)
Deprecated: since v12, will be removed in v13.
param $content
the content
Returns
self
renderContent()
Deprecated: since v12, will be removed in v13. Remove together with $legacyView property and Templates/Module.html.
Creates a tab menu where the tabs or collapsible are rendered with bootstrap markup
param $menuItems
Tab elements, each element is an array with "label" and "content"
param $domId
DOM id attribute, will be appended with an iteration number per tab.
param $defaultTabIndex
Default tab to open (for toggle <=0). Value corresponds to integer-array index + 1(index zero is "1", index "1" is 2 etc.). A value of zero (or something non-existingwill result in no default tab open., default: 1
param $collapsible
If set, the tabs are rendered as headers instead over each sheet. Effectively this meansthere is no tab menu, but rather a foldout/fold-in menu., default: false
param $wrapContent
If set, the content is wrapped in div structure which provides a padding and borderstyle. Set this FALSE to get unstyled content pane with fullsize content area., default: true
param $storeLastActiveTab
If set, the last open tab is stored in local storage and will be re-open again.If you don't need this feature, e.g. for wizards like import/export you candisable this behaviour., default: true
Returns
string
header(string $text, bool $inlineEdit = true)
Deprecated: since v12, will be removed in v13.
Returns the header-bar in the top of most backend modules
Closes section if open.
param $text
The text string for the header
param $inlineEdit
Whether the header should be editable (e.g. page title), default: true
Return description
HTML content
Returns
string
isUiBlock()
Deprecated: since v12, will be removed in v13.
Returns
bool
Example: Create and use a ModuleTemplate in an Extbase Controller
In many backend modules all actions should have the same module header.
So it is useful to initialize the backend module template in a function commonly
used by all actions:
The backend module of an extension can be configured via TypoScript.
The configuration is done
in
module.tx_<lowercaseextensionname>_<lowercasepluginname> or
in
module.tx_<lowercaseextensionname>.
If the part
_<lowercasepluginname> is omitted, then the setting is used
for all backend modules of that extension.
Even in the backend the TypoScript setup is used. The
settings should be done globally and not changed on a per-page basis.
Therefore they are usually set in the file
EXT:my_extension/ext_typoscript_setup.typoscript.
When accessing modules in the Admin Tools via backend user interface,
currently logged in backend users have to confirm their user password again
in order to get access to the modules in this section.
As an alternative, it is also possible to use the install
tool password. This is done
in order to mitigate unintended modifications that might occur as result
of for example possible cross-site scripting vulnerabilities in the system.
Authentication in for sudo mode in extensions using the auth service
Albeit default local authentication mechanisms are working well, there are
side effects for 3rd party extensions that make use of these auth service
chains as well - such as multi-factor authentication or single sign-on handling.
As an alternative, it is possible to confirm actions using the Install Tool
password, instead of confirming with user's password (which might be handled
with separate remote services).
Services that extend authentication with custom additional factors (2FA/MFA)
are advised to intercept only valid login requests instead of all authUser
invocations.
<?phpnamespaceMyVendor\MyExtension\Authentication;
useTYPO3\CMS\Core\Authentication\AbstractAuthenticationService;
classMyAuthenticationServiceextendsAbstractAuthenticationService{
publicfunctionauthUser(array $user){
// only handle actual login requestsif (($this->login['status'] ?? '') !== 'login') {
// skip this service, hand over to next in chainreturn100;
}
// ...// usual processing for valid login requests// ...return0;
}
}
Copied!
Custom backend modules requiring the sudo mode
New in version 12.4
With TYPO3 v12.4 sudo mode has been changed to a generic configuration for
backend routes (and implicitly modules).
In general, the configuration for a particular route or module looks like this:
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)
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 .
A backend route is processed, that requires sudo mode for route URI /my/route
in
\TYPO3\CMS\Backend\Http\RouteDispatcher .
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.
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).
Next, the user is redirected to the user interface for providing either
their own password, or the global install tool password as alternative.
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.
JavaScript in TYPO3 backend
Some third-party JavaScript libraries are packaged with the TYPO3 source code.
The TYPO3 backend itself relies on quite a lot of JavaScript to do its job.
The topic of this chapter is to present how to use JavaScript properly
with TYPO3, in particular in the backend. It presents the most important
APIs in that regard.
Starting with TYPO3 v12.0 JavaScript ES6 modules may be used instead of
AMD modules, both in backend and frontend context.
JavaScript node-js style path resolutions are managed by
import maps, which allow web
pages to control the behavior of JavaScript imports.
In November 2022 import maps are supported natively by Google Chrome,
a polyfill is available for Firefox and Safari and included by TYPO3 Core
and applied whenever an import map is emitted.
For security reasons, import map configuration is only emitted when the modules
are actually used, that means when a module has been added to the current
page response via
PageRenderer->loadJavaScriptModule() or
JavaScriptRenderer->addJavaScriptModuleInstruction().
Exposing all module configurations is possible via
JavaScriptRenderer->includeAllImports(), but that should only be
done in backend context for logged-in users to avoid disclosing installed
extensions to anonymous visitors.
Configuration
A simple configuration example for an extension that maps
the Public/JavaScript folder to an import prefix @vendor/my-extensions:
<?phpreturn [
// required import configurations of other extensions,// in case a module imports from another package'dependencies' => ['backend'],
'imports' => [
// recursive definiton, all *.js files in this folder are import-mapped// trailing slash is required per importmap-specification'@vendor/my-extension/' => 'EXT:my_extension/Resources/Public/JavaScript/',
],
];
Copied!
Complex configuration example containing recursive-lookup exclusions,
third-party library definitions and overwrites:
<?phpreturn [
'dependencies' => ['core', 'backend'],
'imports' => [
'@vendor/my-extension/' => [
'path' => 'EXT:my_extension/Resources/Public/JavaScript/',
// Exclude files of the following folders from being import-mapped'exclude' => [
'EXT:my_extension/Resources/Public/JavaScript/Contrib/',
'EXT:my_extension/Resources/Public/JavaScript/Overrides/',
],
],
// Adding a third party package'thirdpartypkg' => 'EXT:my_extension/Resources/Public/JavaScript/Contrib/thidpartypkg/index.js',
'thidpartypkg/' => 'EXT:my_extension/Resources/Public/JavaScript/Contrib/thirdpartypkg/',
// Overriding a file from another package'@typo3/backend/modal.js' => 'EXT:my_extension/Resources/Public/JavaScript/Overrides/BackendModal.js',
],
];
Copied!
Loading ES6
A module can be added to the current page response either via
PageRenderer or as
JavaScriptModuleInstruction via
JavaScriptRenderer:
No ES6 JavaScript files are created directly in the TYPO3 Core. JavaScript
is created as TypeScript module which is then converted to ES6 JavaScript
during the build process. However, TypeScript and ES6 are quite similar, you
can therefore look into those files for reference. The TypeScript files can be
found on GitHub at
Build/Sources/TypeScript.
For examples of an ES6 JavaScript file have a look at the JavaScript example in
the LinkHandler Tutorial or
the example in the Notification API.
In the TYPO3 Core usage of jQuery is eliminated step-by-step as the necessary
functionality is provided by native JavaScript nowadays.
If you still have to use jQuery in your third-party extension, include it
with the following statement:
import $ from'jquery';
Copied!
Add JavaScript modules to import map in backend form
The JavaScript module import map is static and only generated and
loaded in the first request to a document. All possible future
modules requested in later Ajax calls need to be registered already
in the first initial request.
The tag
backend.form is used to identify
JavaScript modules that can be used within backend forms. This
ensures that the import maps are available for these modules
even if the element is not displayed directly.
A typical use case for this is an InlineRelationRecord where the
CKEditor is not part of the main record but needs to be loaded for
the child record.
RequireJS is shimmed to prefer ES6 modules if available, allowing any extension
to ship ES6 modules by providing an import map configuration in
Configuration/JavaScriptModules.php while providing full backward
compatibility support for extensions that load modules via RequireJS.
Existing RequireJS modules can load new ES6 modules via a bridge that
prefers ES6 modules over traditional RequireJS AMD modules. This allows
extensions authors to migrate to ES6 without breaking dependencies that
previously loaded a module of that extension via RequireJS.
Registering modules via
$pageRenderer->requireJsModules will still
work in TYPO3 v12. These modules will be loaded after modules registered via
$pageRenderer->javaScriptModules. Extensions that use
$pageRenderer->requireJsModules will work as before but trigger a
PHP
E_USER_DEPRECATED error.
If your extension wants to support both TYPO3 v11 and v12 you can keep the
RequireJS version and remove it upon switching to TYPO3 v13.
If you want to prevent deprecation warnings you can also implement both
RequireJS (with a version switch) and native ECMAScript v6/v11 (ES6) modules.
This approach is recommended if you are working with TypeScript and the
JavaScript will be generated anyway.
Migrate your JavaScript from the AMD module format to native ES6 modules
and register your configuration in
EXT:my_extension/Configuration/JavaScriptModules.php, also see
Loading ES6 for more information:
Then use
TYPO3\CMS\Core\Page\PageRenderer::loadJavaScriptModules()
instead of
TYPO3\CMS\Core\Page\PageRenderer::loadRequireJsModule() to
load the ES6 module:
EXT:my_extension/Classes/SomeClass.php
<?phpuseTYPO3\CMS\Core\Information\Typo3Version;
// ...
$typo3Version = new Typo3Version();
if ($typo3Version->getMajorVersion() > 11) {
$this->pageRenderer->loadJavaScriptModule(
'@vendor/my-extension/my-example.js',
);
} else {
// keep RequireJs for TYPO3 below v12.0$this->pageRenderer->loadRequireJsModule(
'TYPO3/CMS/MyExtension/MyExample',
);
}
Copied!
In Fluid templates includeJavaScriptModules is to be used instead of
includeRequireJsModules.
In Fluid template the includeJavaScriptModules attribute of the
<f:be.pageRenderer> ViewHelper may be used:
The RequireJS project has been discontinued and was therefore
replaced by native ECMAScript v6/v11 modules in TYPO3 v12.0. The
infrastructure for configuration and loading of RequireJS
modules is deprecated with v12.0 and will be removed in TYPO3 v13. See
RequireJS to ES6 migration.
The RequireJS project has been discontinued and was therefore
replaced by native ECMAScript v6/v11 modules in TYPO3 v12.0. The
infrastructure for configuration and loading of RequireJS
modules is deprecated with v12.0 and will be removed in TYPO3 v13. See
RequireJS to ES6 migration.
To be able to use RequireJS at all, some prerequisites must be fulfilled:
Your extension must have a Resources/Public/JavaScript directory. That directory is used for autoloading the modules stored in your extension.
Each module has a namespace and a module name. The namespace is TYPO3/CMS/<EXTKEY>, <EXTKEY> is your extension key in UpperCamelCase, e.g. foo_bar = FooBar
The namespace maps automatic to your Resources/Public/JavaScript directory
The filename is the modulename + .js
Think about what's the purpose of the module. You can only write one module per file (anything else is bad practice anyway)
A complete example: TYPO3/CMS/FooBar/MyMagicModule is resided in EXT:foo_bar/Resources/Public/JavaScript/MyMagicModule.js
Every AMD (Asynchronous Module Definition) is wrapped in the same construct:
define([], function() {
// your module logic here
});
Copied!
This is the "container" of the module. It holds the module logic and takes care of dependencies.
TYPO3 defines in its own modules an object to hold the module logic in properties and methods.
The object has the same name as the module. In our case "MyMagicModule":
define([], function() {
var MyMagicModule = {
foo: 'bar'
};
MyMagicModule.init = function() {
// do init stuff
};
// To let the module be a dependency of another module, we return our objectreturn MyMagicModule;
});
Copied!
Dependency handling
Attention
Deprecated since version 12.0
The RequireJS project has been discontinued and was therefore
replaced by native ECMAScript v6/v11 modules in TYPO3 v12.0. The
infrastructure for configuration and loading of RequireJS
modules is deprecated with v12.0 and will be removed in TYPO3 v13. See
RequireJS to ES6 migration.
Let us try to explain the dependency handling with the most used JS lib: jQuery
To prevent the "$ is undefined" error, you should use the dependency handling of RequireJS.
To get jQuery working in your code, use the following line:
define(['jquery'], function($) {
// in this callback $ can be used
});
Copied!
The code above is very easy to understand:
every dependency in the array of the first argument
will be injected in the callback function at the same position
Let us combine jQuery with our own module from the Extension example
define(['jquery', 'TYPO3/CMS/FooBar/MyMagicModule'], function($, MyMagicModule) {
// $ is our jQuery object// MyMagicModule is the object, which is returned from our own moduleif(MyMagicModule.foo == 'bar'){
MyMagicModule.init();
}
});
Copied!
Loading your own or other RequireJS modules
Attention
Deprecated since version 12.0
The RequireJS project has been discontinued and was therefore
replaced by native ECMAScript v6/v11 modules in TYPO3 v12.0. The
infrastructure for configuration and loading of RequireJS
modules is deprecated with v12.0 and will be removed in TYPO3 v13. See
RequireJS to ES6 migration.
In case you use the ready event, you may wonder how to use the module.
Answer: it depends! If you use Fluid's
f:be.pageRenderer view helper
add the argument
includeRequireJsModules:
Bonus:
loadRequireJsModule takes a second argument
$callBackFunction which is executed right after the module
was loaded. The callback function must be wrapped within
function() {}:
$pageRenderer->loadRequireJsModule(
'TYPO3/CMS/FooBar/MyMagicModule',
'function() { console.log("Loaded own module."); }'
);
Copied!
Shim Library to Use it as Own RequireJS Modules
Attention
Deprecated since version 12.0
The RequireJS project has been discontinued and was therefore
replaced by native ECMAScript v6/v11 modules in TYPO3 v12.0. The
infrastructure for configuration and loading of RequireJS
modules is deprecated with v12.0 and will be removed in TYPO3 v13. See
RequireJS to ES6 migration.
Not all javascript libraries are compatible with RequireJS. In the rarest cases, you can
adjust the library code to be AMD or UMD compatible. So you need to configure RequireJS to
accept the library.
In RequireJS you can use
requirejs.config({}) to shim a library. In
TYPO3 the RequireJS config will be defined in the
PageRenderer:
The
${...} literal used in template tags can basically contain any
JavaScript instruction - as long as their result can be casted to string
again or is of type lit-html.TemplateResult. This allows to
make use of custom conditions as well as iterations:
The result won't look much different than the first example - however the
custom attribute
@click will be transformed into an according event
listener bound to the element where it has been declared.
Custom HTML elements
A web component based on the W3C custom elements ("web-components_") specification
can be implemented using lit-element.
import {LitElement, html, customElement, property} from'lit-element';
@customElement('my-element')
class MyElement extends LitElement {
// Declare observed properties@property()
value: string = 'awesome';
// Avoid Shadow DOM so global styles apply to the element contents
createRenderRoot(): Element|ShadowRoot {
returnthis;
}
// Define the element's template
render() {
return html`<p>Hello ${this.value}!</p>`;
}
}
The modal API provided by the module
@typo3/backend/modal.js has been
adapted to be backed by a custom web component and therefore gained an updated,
stateless interface. See also section Migration.
Actions that require a user's attention must be visualized by modal windows.
TYPO3 provides an API as basis to create modal windows with severity
representation. For better UX, if actions (buttons) are attached to the modal,
one button must be a positive action. This button should get a btnClass
to highlight it.
Modals should be used rarely and only for confirmations. For information that
does not require a confirmation
the Notification API (flash message) should be used.
For complex content, like forms or a lot of information, use normal pages.
API
Changed in version 12.0
The return type of all
Modal.* factory methods has been changed from
JQuery to
ModalElement.
Callback that is triggered on button click - either a simple function or
DeferredAction /
ImmediateAction
active
active
Type
bool
Marks the button as active. If true, the button gets the focus.
btnClass
btnClass
Type
string
The CSS class for the button.
Changed in version 12.0
The
Button property dataAttributes has been removed without
replacement, as the functionality can be expressed via
Button.name
or
Button.trigger and is therefore redundant.
Data Attributes
It is also possible to use
data attributes to trigger a modal,
for example on an anchor element, which prevents the default behavior.
data-title
The title text for the modal.
data-bs-content
The content text for the modal.
data-severity
The severity for the modal, default is info (see
TYPO3.Severity.*).
data-href
The target URL, default is the
href attribute of the element.
data-button-close-text
Button text for the close/cancel button.
data-button-ok-text
Button text for the ok button.
class="t3js-modal-trigger"
Marks the element as modal trigger.
data-static-backdrop
Render a static backdrop to avoid closing the modal when clicking it.
A basic modal (without anything special) can be created this way:
TYPO3.Modal.confirm('The title of the modal', 'This the the body of the modal');
Copied!
A modal as warning with button:
TYPO3.Modal.confirm('Warning', 'You may break the internet!', TYPO3.Severity.warning, [
{
text: 'Break it',
active: true,
trigger: function() {
// break the net
}
}, {
text: 'Abort!',
trigger: function() {
TYPO3.Modal.dismiss();
}
}
]);
Copied!
A modal as warning:
TYPO3.Modal.confirm('Warning', 'You may break the internet!', TYPO3.Severity.warning);
Copied!
Action buttons in modals created by the
TYPO3/CMS/Backend/Modal module may
make use of
TYPO3/CMS/Backend/ActionButton/ImmediateAction and
TYPO3/CMS/Backend/ActionButton/DeferredAction.
As an alternative to the existing
trigger option, the option
action may be used with an instance of the previously mentioned modules.
Modal.confirm('Header', 'Some content', Severity.error, [
{
text: 'Based on trigger()',
trigger: function () {
console.log('Vintage!');
}
},
{
text: 'Based on action',
action: new DeferredAction(() => {
returnnew AjaxRequest('/any/endpoint').post({});
})
}
]);
Copied!
Activating any action disables all buttons in the modal. Once the action is
done, the modal disappears automatically.
Buttons of the type
DeferredAction render a spinner on activation
into the button.
A modal with static backdrop:
import Modal from'@typo3/backend/modal.js';
Modal.advanced({
title: 'Hello',
content: 'This modal is not closable via clicking the backdrop.',
size: Modal.sizes.small,
staticBackdrop: true
});
Copied!
Templates, using the HTML class
.t3js-modal-trigger to initialize
a modal dialog are also able to use the new option by adding the
data-static-backdrop attribute to the corresponding element.
<buttonclass="btn btn-default t3js-modal-trigger"data-title="Hello"data-bs-content="This modal is not closable via clicking the backdrop."data-static-backdrop>
Open modal
</button>
Copied!
Migration
Given the following fully-fledged example of a modal that uses custom buttons,
with custom attributes, triggers and events, they should be migrated away
from
JQuery to
ModalElement usage.
The JavaScript module
MultiStepWizard can be used to show a modal multi-step
wizard with the following features:
Navigation to previous / next steps
Steps may have descriptive labels like "Start" or "Finish!"
Steps may require actions before becoming available.
Code examples:
// Show/ hide the wizard
MultiStepWizard.show();
MultiStepWizard.dismiss();
// Add a slide to the wizard
MultiStepWizard.addSlide(
identifier,
stepTitle,
content,
severity,
progressBarTitle,
function() {
...
}
);
// Lock/ unlock navigation buttons
MultiStepWizard.lockNextStep();
MultiStepWizard.unlockNextStep();
MultiStepWizard.lockPrevStep();
MultiStepWizard.unlockPrevStep();
Copied!
DocumentService (jQuery.ready substitute)
The module
TYPO3/CMS/Core/DocumentService provides native JavaScript
functions to detect DOM ready-state returning a
Promise<Document>.
Internally the Promise is resolved when native
DOMContentLoaded event has
been emitted or when
document.readyState is defined already. It means
the initial HTML document has been completely loaded and parsed, without
waiting for stylesheets, images, and sub-frames to finish loading.
TYPO3 ships a module acting as a wrapper for
sessionStorage. It
behaves similar to the
localStorage, except that the stored data is dropped
after the browser session has ended.
The module
TYPO3/CMS/Core/Storage/BrowserSession allows
to store data in the
sessionStorage.
The module is called
@typo3/backend/storage/abstract-client-storage,
implemented by:
@typo3/backend/storage/browser-session
@typo3/backend/storage/client
get(key)
Fetches the data behind the key.
getByPrefix('common-prefix-')
Obtains multiple items prefixed by a given key.
set(key, value)
Sets/overrides a key with any arbitrary content.
isset(key) (bool)
Checks if the key is in use.
unset(key)
Removes a key from the storage.
clear()
Empties all data inside the storage.
unsetByPrefix(prefix)
Empties all data inside the storage with their keys
starting with a prefix.
Ajax request
TYPO3 Core ships an API to send Ajax requests to the server. This API is based
on the fetch API, which is implemented in every modern browser (for
example, Chrome, Edge, Firefox, Safari).
Prepare a request
To be able to send a request, the module
@typo3/core/ajax/ajax-request.js
must be imported. To prepare a request, create a new instance of
AjaxRequest per request and pass the URL as the constructor argument:
import AjaxRequest from"@typo3/core/ajax/ajax-request.js";
let request = new AjaxRequest('https://example.org/my-endpoint');
Copied!
The API offers a method
withQueryArguments() which allows to attach a query
string to the URL. This comes in handy, if the query string is programmatically
generated. The method returns a clone of the
AjaxRequest object. It is
possible to pass either strings, arrays or objects as an argument.
The API presets the request configuration with
{credentials: 'same-origin', signal: AbortController.signal}.
The body of the request is automatically converted to a FormData object, if
the submitted payload is an object. To send a JSON-encoded object instead, set
the Content-Type header to application/json. If the payload is a string, no
conversion will happen, but it is still recommended to set proper headers.
response is an object of type
AjaxResponse shipped by TYPO3
(
@typo3/core/ajax/ajax-response.js). The object is a simple wrapper for
the original Response object.
AjaxResponse exposes the following
methods which eases the handling of responses:
resolve()
Returns the correct response based on the received Content-Type header,
either plaintext or a JSON object.
Of course, a request may fail for various reasons. In such case, a second
function may be passed to
then(), which handles the exceptional case. The
function may receive a
AjaxResponse object which contains the original
response object.
import AjaxRequest from"@typo3/core/ajax/ajax-request.js";
let request = new AjaxRequest('https://example.org/my-endpoint');
const json = {foo: 'bar'};
let promise = request.post(json, {
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
});
promise.then(asyncfunction (response) {
}, function (error) {
console.error(`The request failed with ${error.response.status}: ${error.response.statusText}`);
});
Copied!
Hint
The fetch API handles responses with faulty statuses like 404 or 500 still
as "successful", but sets the response's
ok field to
false. The
Ajax API converts such responses into errors for convenience reasons.
Abort a request
In some cases it might be necessary to abort a running request. The Ajax API has
you covered them, an instance of AbortController is attached to each request.
To abort the request, just call the
abort() method:
request.abort();
Copied!
Event API
The TYPO3 JavaScript Event API enables JavaScript developers to have a stable event listening
interface. The API takes care of common pitfalls like event delegation and clean
event unbinding.
Warning
When using
import statements, it is vital that you use the suffix
.js to any import statements, when you are in the scope of JavaScript/ES6.
Only when you create your code in TypeScript (using .ts suffix), you
need to omit the .js extension in
import statements.
See https://github.com/microsoft/TypeScript/issues/16577 for the reasoning of this.
Event Binding
Each event strategy (see below) has two ways to bind a listener to an event:
Direct Binding
The event listener is bound to the element that triggers the event. This is done
by using the method
bindTo(), which accepts any element,
document and
window.
import RegularEvent from'@typo3/core/event/regular-event.js';
new RegularEvent('click', function (e) {
// Do something
}).bindTo(document.querySelector('#my-element'));
Copied!
Event Delegation
The event listener is called if the event was triggered to any matching element
inside its bound element.
import RegularEvent from'@typo3/core/event/regular-event.js';
new RegularEvent('click', function (e) {
// Do something
}).delegateTo(document, 'a[data-action="toggle"]');
Copied!
The event listener is now called every time the element matching the selector
a[data-action="toggle"] within
document is clicked.
Release an event
Since each event is an object instance, it is sufficient to call
release() to
detach the event listener.
import RegularEvent from'@typo3/core/event/regular-event.js';
const clickEvent = new RegularEvent('click', function (e) {
// Do something
}).delegateTo(document, 'a[data-action="toggle"]');
// Do more stuff
clickEvent.release();
Copied!
Event strategies
The Event API brings several strategies to handle event listeners:
RegularEvent
The
RegularEvent attaches a simple event listener to an event and element
and has no further tweaks. This is the common use case for event handling.
import DebounceEvent from'@typo3/core/event/debounce-event.js';
new DebounceEvent('mousewheel', function (e) {
console.log('Triggered once after 250ms!');
}, 250).bindTo(document);
Copied!
ThrottleEvent
Arguments:
eventName (string) - the event to listen on
callback (function) - the event listener
limit (number) - the amount of milliseconds to wait before the event listener is called
The
ThrottleEvent is similar to the
DebounceEvent. The important
difference is that the event listener is called after the configured wait time
during the overall event time.
If an event time is about 2000ms and the wait time is configured to be 100ms,
the event listener gets called up to 20 times in total (2000 / 100).
import ThrottleEvent from'@typo3/core/event/throttle-event.js';
new ThrottleEvent('mousewheel', function (e) {
console.log('Triggered every 100ms!');
}, 100).bindTo(document);
Copied!
RequestAnimationFrameEvent
The
RequestAnimationFrameEvent binds its execution to the browser's
RequestAnimationFrame API. It is suitable for event listeners that
manipulate the DOM.
import RequestAnimationFrameEvent from'@typo3/core/event/request-animation-frame-event.js';
new RequestAnimationFrameEvent('mousewheel', function (e) {
console.log('Triggered every 16ms (= 60 FPS)!');
});
Copied!
Navigation via JavaScript
Navigate to URL
Navigate to a URL once selected drop-down is changed:
$data refers to value of
data-navigate-value, $value to selected value,
$data=~s/$value/ replaces the literal ${value} with the selected value in
data-navigate-value.
Show info popup
Invoke the
TYPO3.InfoWindow.showItem module function to display details
for a given record:
<aclass="btn btn-default"href="#"data-dispatch-action="TYPO3.InfoWindow.showItem"data-dispatch-args-list="be_users,123"
>
Some text
</a>
Copied!
or (using JSON arguments)
<aclass="btn btn-default"href="#"data-dispatch-action="TYPO3.InfoWindow.showItem"data-dispatch-args="["tt_content",123]"
>
Some text
</a>
Copied!
Shows the info popup of database table
tt_content, having uid=123 in
the example above.
Checkboxes used to send a particular value when unchecked can be achieved by using
data-empty-value="0". If this attribute is omitted, an empty string '' is sent.
Submitting a form on change
<inputtype="checkbox"data-global-event="change"data-action-submit="$form"><!-- ... or (using CSS selector) ... --><inputtype="checkbox"data-global-event="change"data-action-submit="#formIdentifier">
Copied!
Submits a form once a value has been changed.
($form refers to the parent form element, using CSS selectors like #formIdentifier
is possible as well)
Ajax in the backend
An Ajax endpoint in the TYPO3 backend is usually implemented as a method in a
regular controller. The method receives a request object implementing the
\Psr\Http\Message\ServerRequestInterface , which allows to access all
aspects of the requests and returns an appropriate response in a normalized way.
This approach is standardized as PSR-7.
Create a controller
By convention, a controller is placed within the extension's Controller/
directory, optionally in a subdirectory. To have such controller, create a new
ExampleController in Classes/Controller/ExampleController.php
inside your extension.
The controller needs not that much logic right now. We create a method called
doSomethingAction() which will be our Ajax endpoint.
In its current state, the method does nothing yet. We can add a very generic
handling that exponentiates an incoming number by 2. The incoming value will be
passed as a query string argument named input.
The endpoint must be registered as route. Create a
file called Configuration/Backend/AjaxRoutes.php in your extension. The
file basically just returns an array of route definitions. Every route in this
file will be exposed to JavaScript automatically. Let us register our endpoint
now:
The naming of the key myextension_example_dosomething and path
/my-extension/example/do-something are up to you, but should contain the
extension name, controller name and action name to avoid potential conflicts
with other existing routes.
Attention
Flushing caches is mandatory after modifying any route definition.
Use in Ajax
Since the route is registered in AjaxRoutes.php it is exposed to
JavaScript now and stored in the global
TYPO3.settings.ajaxUrls object
identified by the used key in the registration. In this example it is
TYPO3.settings.ajaxUrls.myextension_example_dosomething.
Now you are free to use the endpoint in any of your Ajax calls. To complete this
example, we will ask the server to compute our input and write the result into
the console.
Backend layouts can be defined as database records or via page TSconfig.
Page TSconfig should be preferred as it can be stored in the file system and
be kept under version control.
Backend layout video
Benjamin Kott: How to implement frontend layouts in TYPO3 using backend layouts
Backend layout configuration
The backend layout to be used can be configurated for each page and/or a pages'
subpages in the Page properties > Appearance. Multiple backend
layouts are available if an
extension providing backend layouts is installed or
backend layouts have been
defined as records or page TSconfig.
Choose the backend layout in the page properties
The Info module gives an overview of the backend layouts configured or
inherited from a parent page at
Web > Info > Pagetree overview > Type: Layouts:
Overview of the backend layouts used
Backend layout definition
Backend layouts can be configured either as "backend layout" record in a sysfolder or as page TSconfig entry in
mod.web_layout.BackendLayouts. Each layout will be saved with a key. The "backend layout" records are
using their uid as a key, therefore layouts defined via page TSconfig should use a non-numeric string key. It is a good
practice to use a descriptive name as key.
The entries title and icon are being used to display the backend layout options in the page properties.
The overall grid size will be defined by
config.backend_layout.colCount and
rowCount.
Additional rows in the
rows array and additional columns in the each rows
columns section
will be ignored when they are greater than
rowCount or
colCount respectively.
Each column position can span several columns and or several rows. Each column position must have a distinct number
between 0 and n. It is best practice to always assign "0" to the main column if there is such a thing as a
main column. Multiple backend layouts that contain similar parts, i.e. header, footer, aside, ... should each have
assigned the same number within one project. This leads to a uniform position of the content, which makes it more clear
for further use.
Backend layout simple example
The following page TSconfig example creates a simple backend layout consisting of two rows and just one column.
The following page TSconfig example creates a 3x3 backend layout with 5 column position sections in total. The topmost
row (here called "header") spans all 3 columns. There is an "aside" spanning two rows on the right.
mod.web_layout.BackendLayouts {
exampleKey {
title = Example
icon = EXT:example_extension/Resources/Public/Images/BackendLayouts/default.gif
config {
backend_layout {
colCount = 3
rowCount = 3
rows {
1 {
columns {
1 {
name = Header
colspan = 3
colPos = 1
}
}
}
2 {
columns {
1 {
name = Main
colspan = 2
colPos = 0
}
2 {
name = Aside
rowspan = 2
colPos = 2
}
}
}
3 {
columns {
1 {
name = Main Left
colPos = 5
}
2 {
name = Main Right
colPos = 6
}
}
}
}
}
}
}
}
Copied!
Output of a backend layout in the frontend
The backend layout to be used on a certain page gets determined either by the backend layout being chosen directly and
stored in the pages field "backend_layout" or by the field "backend_layout_next_level" of a parent page up the rootline.
To avoid complex TypoScript for integrators, the handling of backend layouts has
been simplified for the frontend.
To get the correct backend layout, the following TypoScript code can be used:
In many cases besides defining fixed backend layouts a more modular approach with the possibility of combining different
backend layouts and frontend layouts may be feasible. The extension
EXT:container
integrates the grid layout concept also to regular content elements.
The extension EXT:content_defender offers advanced options to
the column positions i.e. allowed or disallowed content elements, a maximal number of content elements.
Backend routing
Each request to the backend is eventually executed by a controller.
A list of routes is defined which maps a given request to a controller
and an action.
Routes are defined inside extensions, in the files
<?phpuseTYPO3\CMS\Backend\Controller;
return [
// Login screen of the TYPO3 Backend'login' => [
'path' => '/login',
'access' => 'public',
'target' => Controller\LoginController::class . '::formAction',
],
// Main backend rendering setup (previously called backend.php) for the TYPO3 Backend'main' => [
'path' => '/main',
'referrer' => 'required,refresh-always',
'target' => Controller\BackendController::class . '::mainAction',
],
// ...
];
Copied!
So, a route file essentially returns an array containing route mappings. A route
is defined by a key, a path, a referrer and a target. The "public"
access
property indicates that no authentication is required for that action.
Note
The current route object is available as a route attribute in the PSR-7 request object of every
backend request. It is added through the PSR-15 middleware stack and can be
retrieved using
$request->getAttribute('route').
Backend routing and cross-site scripting
Public backend routes (those having option
'access' => 'public') do not
require any session token, but can be used to redirect to a route that requires
a session token internally. For this context, the backend user logged in must
have a valid session.
This scenario can lead to situations where an existing cross-site scripting
vulnerability (XSS) bypasses the mentioned session token, which can be
considered cross-site request forgery (CSRF). The difference in terminology is
that this scenario occurs on same-site requests and not cross-site - however,
potential security implications are still the same.
Backend routes can enforce the existence of an HTTP referrer header by adding a
referrer to routes to mitigate the described scenario.
Values for
referrer are declared as a comma-separated list:
required enforces existence of HTTP Referer header that has to match
the currently used backend URL (for example, https://example.org/typo3/),
the request will be denied otherwise.
refresh-empty triggers an HTML-based refresh in case HTTP Referer
header is not given or empty - this attempt uses an HTML refresh, since
regular HTTP Location redirect still would not set a referrer. It implies
this technique should only be used on plain HTML responses and will not have
any impact, for example, on JSON or XML response types.
This technique should be used on all public routes (without session token) that
internally redirect to a restricted route (having a session token). The goal is
to protect and keep information about the current session token internal.
The request sequence in the TYPO3 Core looks like this:
HTTP request to https://example.org/typo3/ having a valid user
session
Internally public backend route /login is processed
Internally redirects to restricted backend route /main since an
existing and valid backend user session was found
+ HTTP redirect to https://example.org/typo3/main?token=...
+ exposing the token is mitigated with referrer route option mentioned
above
Attention
Please keep in mind these steps are part of a mitigation strategy, which
requires to be aware of mentioned implications when implementing custom web
applications.
Dynamic URL parts in backend URLs
New in version 12.1
Backend routes can be registered with path segments that contain dynamic parts,
which are then resolved into a PSR-7 request attribute called
routing.
These routes are defined within the route path as named placeholders:
Using the UriBuilder API, you can generate any kind of URL for the backend, may
it be a module, a typical route or an Ajax call. Therefore use either
buildUriFromRoute() or
buildUriFromRoutePath(). The
UriBuilder then returns a PSR-7 conform
Uri object that can be
cast to a string when needed. Furthermore, the
UriBuilder automatically
generates and applies the mentioned session token.
To generate a backend URL via the
UriBuilder you'd usually use the route
identifier and optional
parameters.
In case of Extbase controllers you can append the controller action to the route
identifier to directly target those actions. See also module configuration: controllerActions.
Via Fluid ViewHelper
To generate a backend URL in Fluid you can simply use html:<f:be.link> (which
is using
UriBuilder internally).
<f:be.linkroute="web_layout"parameters="{id:42}">go to page 42</f:be.link><f:be.linkroute="web_ExtkeyExample">go to custom BE module</f:be.link><f:be.linkroute="web_ExtkeyExample.MyModuleController_list">
go to custom BE module but specific controller action
</f:be.link>
Copied!
Via PHP
Example within a controller (we use here a non-Extbase controller):
The backend user of a session is always available in extensions
as the global variable
$GLOBALS['BE_USER']. The object is created in
\TYPO3\CMS\Backend\Middleware\BackendUserAuthenticator middleware for a standard web request
and is an instance of the class
\TYPO3\CMS\Core\Authentication\BackendUserAuthentication
(which extends
\TYPO3\CMS\Core\Authentication\AbstractUserAuthentication ).
When working with CLI and commands you might initialize the backend user object with
\TYPO3\CMS\Core\Core\Bootstrap::initializeBackendUser().
Checking user access
The
$GLOBALS['BE_USER'] object is mostly used to check user access right,
but contains other helpful information. This is presented here by a few examples:
Checking access to current backend module
Deprecated since version 12.0
The method
->modAccess() of the backend user object is deprecated.
Use
ModuleProvider->accessGranted() from the
ModuleProvider API instead.
$MCONF is module configuration and the key
$MCONF['access'] determines
the access scope for the module. This function call will check if the
$GLOBALS['BE_USER'] is allowed to access the module and if not, the function
will exit with an error message.
Changing the "1" for other values will check other permissions:
use "2" for checking if the user may edit the page
use "4" for checking if the user may delete the page.
Is a page inside a DB mount?
Access to a page should not be checked only based on page permissions
but also if a page is found within a DB mount for ther user. This can
be checked by this function call (
$id is the page uid):
If you wish to make a SQL statement which selects pages from the
database and you want it to be only pages that the user has read
access to, you can have a proper WHERE clause returned by this
function call:
Again the number "1" represents the "read" permission; "2" is "edit"
and "4" is "delete" permission. The result from the above query could be this string:
Result of the above query
((pages.perms_everybody & 1 = 1)OR(pages.perms_userid = 2 AND pages.perms_user & 1 = 1)OR(pages.perms_groupid in (1) AND pages.perms_group & 1 = 1))
Copied!
Saving module data
This stores the input variable
$compareFlags (an array!, retrieved from
the request object) with the key
"tools_beuser/index.php/compare":
The internal
->uc array contains options which are managed by the
User Tools > User Settings module (extension "setup"). These values are accessible in
the
$GLOBALS['BE_USER']->uc array. This will return the current state of
"Notify me by email, when somebody logs in from my account" for the user:
It is possible to send broadcast messages from anywhere in TYPO3 that are
listened to via JavaScript.
Warning
This API is considered internal and may change anytime until declared being stable.
Send a message
Changed in version 12.0
The RequireJS module
TYPO3/CMS/Backend/BroadcastService has been migrated
to the ES6 module
@typo3/backend/broadcast-service.js.
See also ES6 in the TYPO3 Backend.
Any backend module may send a message using the
@typo3/backend/broadcast-service.jsES6 module.
The payload of such a message is an object that consists at least of the
following properties:
componentName - the name of the component that sends the message (e.g. extension name)
eventName - the event name used to identify the message
A message may contain any other property as necessary. The final event
name to listen is a composition of "typo3", the component name and the
event name, e.g. typo3:my_extension:my_event.
To send a message, the
post() method must be used.
To receive and thus react on a message, an event handler needs to be registered that listens to the composed event
name (e.g. typo3:my_component:my_event) sent to
document.
The event itself contains a property called detailexcluding the component name and event name.
classMyEventHandler{
constructor() {
document.addEventListener('typo3:my_component:my_event', (e) => eventHandler(e.detail));
}
functioneventHandler(detail) {
console.log(detail); // contains 'hello' and 'foo' as sent in the payload
}
}
exportdefaultnew MyEventHandler();
Hook into
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']['constructPostProcess']
to load a custom
\TYPO3\CMS\Backend\Controller\BackendController hook
that loads the event handler's JavaScript.
The backend module menu button bar can display
dropdowns. This enables interface interactions, such as switching the current
view from list to tiles, or group actions like clipboard and thumbnail
visibility. This helps unclutter the views and allow the user to see more
information at a glance.
Each dropdown consists of various elements ranging from headings to item links
that can display the current status. The button automatically changes the
icon representation to the icon of the the first active radio icon in the
dropdown list.
DropDown button component in the File > Filelist module
DropDownButton
This button type is a container for dropdown items. It will render a dropdown
containing all items attached to it. There are different kinds available, each
item needs to implement the
\TYPO3\CMS\Backend\Template\Components\Buttons\DropDown\DropDownItemInterface .
When this type contains elements of type DropDownRadio
it will use the icon of the first active item of this type.
This dropdown item type renders an element with an active state. Use this
element to display a radio-like selection of a state. When set to active, it
will show a dot in front of the icon and text to indicate that this is the
current selection.
At least two of these items need to exist within a dropdown button, so a user
has a choice of a state to select.
This dropdown item type renders an element with an active state. When set to
active, it will show a checkmark in front of the icon and text to indicate the
current state.
The class
\TYPO3\CMS\Backend\Clipboard\Clipboard is marked
@internal. It is a specific Backend implementation and is not
considered part of the Public TYPO3 API. It might change without notice.
You can easily access the internal clipboard in TYPO3 from your
backend modules:
useTYPO3\CMS\Backend\Clipboard\Clipboard;
useTYPO3\CMS\Core\Utility\DebugUtility;
useTYPO3\CMS\Core\Utility\GeneralUtility;
classModuleControllerextendsActionControllerimplementsLoggerAwareInterface{
protectedfunctiondebugClipboard(){
/** @var $clipboard Clipboard */
$clipboard = GeneralUtility::makeInstance(Clipboard::class);
// Read the clipboard content from the user session
$clipboard->initializeClipboard();
DebugUtility::debug($clipboard->clipData);
}
}
Copied!
In this simple piece of code we instantiate a clipboard object and make it
load its content. We then dump this content into the BE module's debug
window, with the following result:
A dump of the clipboard in the debug window
This tells us what objects are registered on the default tab ("normal")
(a content element with id 216 in "copy" mode) and the numeric tabs (which can
each contain more than one element). It also tells us that the current
tab is number 2. We can compare with the BE view of the clipboard:
The clipboard as seen in the backend
which indeed contains two files.
Clipboard content should not be accessed directly, but using the
elFromTable() method of the clipboard object:
useTYPO3\CMS\Backend\Clipboard\Clipboard;
useTYPO3\CMS\Core\Utility\GeneralUtility;
classModuleControllerextendsActionControllerimplementsLoggerAwareInterface{
protectedfunctiongetCurrentClipboard():array{
/** @var $clipboard Clipboard */
$clipboard = GeneralUtility::makeInstance(Clipboard::class);
// Read the clipboard content from the user session
$clipboard->initializeClipboard();
// Access files and pages content of current pad
$clipboardContent = [
'files' => $clipboard->elFromTable('_FILE'),
'pages' => $clipboard->elFromTable('pages'),
];
return $clipboardContent;
}
}
Copied!
Here we first try to get all files and then all page records on the
current pad (which is pad 2). Then we change to the "Normal" pad, call
the elFromTable() method again.
In the "examples" extension, this data is passed to a BE module view
for display, which is really just information:
Clipboard items
Context-sensitive menus
Contextual menus exist in many places in the TYPO3 backend. Just try your
luck clicking on any icon that you see. Chances are good that a contextual
menu will appear, offering useful functions to execute.
The context menu shown after clicking on the Content Element icon
Context menu rendering flow
Markup
Deprecated since version 12.1
The configuration of the context menu was streamlined. Replace
class="t3js-contextmenutrigger" with
data-contextmenu-trigger="click"
data-table="pages" with
data-contextmenu-table="pages"
data-uid="10" with :html:data-contextmenu-uid="10"`
data-context="tree" with :html:data-contextmenu-context="tree"`
to be compatible with TYPO3 v12+. To be compatible with TYPO3 v11 and v12
keep the previous attributes.
Using the deprecated JavaScript API will trigger a warning in the console
and will stop working with TYPO3 v13.
New in version 12.1
The context menu JavaScript API was adapted to also support opening
the menu through the "contextmenu" event type (right click) only.
The context menu is shown after clicking on the HTML element which has the
data-contextmenu-trigger attribute set together with
data-contextmenu-table,
data-contextmenu-uid and optional
data-contextmenu-context attributes.
The HTML attribute
data-contextmenu-trigger has the following options:
click: Opens the context menu on the "click" and "contextmenu"
events
contextmenu: Opens the context menu only on the "contextmenu" event
The JavaScript click event handler is implemented in the
ES6 module
@typo3/backend/context-menu.js. It takes the
data attributes mentioned above and executes an Ajax call to the
\TYPO3\CMS\Backend\Controller\ContextMenuController->getContextMenuAction().
Changed in version 12.0
The RequireJS module
TYPO3/CMS/Backend/ContextMenu has been migrated
to the ES6 module
@typo3/backend/context-menu.js.
See also ES6 in the TYPO3 Backend.
ContextMenuController
ContextMenuController asks
\TYPO3\CMS\Backend\ContextMenu\ContextMenu
to generate an array of items.
ContextMenu builds a list of available
item providers by asking each whether it can provide items
(
$provider->canHandle()), and what priority it has
(
$provider->getPriority()).
Item providers registration
Changed in version 12.0
ContextMenu item providers, implementing
\TYPO3\CMS\Backend\ContextMenu\ItemProviders\ProviderInterface
are now automatically registered. The registration via
$GLOBALS['TYPO3_CONF_VARS']['BE']['ContextMenu']['ItemProviders']
is not evaluated anymore.
Custom item providers must implement
\TYPO3\CMS\Backend\ContextMenu\ItemProviders\ProviderInterface and can
extend
\TYPO3\CMS\Backend\ContextMenu\ItemProviders\AbstractProvider .
They are then automatically registered by adding the
backend.contextmenu.itemprovider
tag, if
autoconfigure is enabled in Services.yaml. The class
\TYPO3\CMS\Backend\ContextMenu\ItemProviders\ItemProvidersRegistry then
automatically receives those services and registers them.
If
autoconfigure is
not enabled in your Configuration/Services.(yaml|php) file,
manually configure your item providers with the
backend.contextmenu.itemprovider tag.
There are two item providers which are always available:
A list of providers is sorted by priority, and then each provider is asked to
add items. The generated array of items is passed from an item provider with
higher priority to a provider with lower priority.
After that, a compiled list of items is returned to the
ContextMenuController which passes it back to the
ContextMenu.js as JSON.
Menu rendering in JavaScript
Changed in version 12.0
The RequireJS module
TYPO3/CMS/Backend/ContextMenuActions has been migrated
to the ES6 module
@typo3/backend/context-menu-actions.js.
See also ES6 in the TYPO3 Backend.
Based on the JSON data context-menu.js is rendering a context menu. If
one of the items is clicked, the according JavaScript
callbackAction is
executed on the ES6 module
@typo3/backend/context-menu-actions.js
or other modules defined in the JSON as
additionalAttributes['data-callback-module'].
Several TYPO3 Core modules are already using this API for adding or modifying
items. See following places for a reference:
EXT:impexp module adds import and export options for pages, content elements
and other records. See item provider
\TYPO3\CMS\Impexp\ContextMenu\ItemProvider and ES6 module
@typo3/impexp/context-menu-actions.js.
EXT:filelist module provides several item providers for files, folders,
filemounts, filestorage, and drag-drop context menu for the folder tree.
See following item providers:
\TYPO3\CMS\Filelist\ContextMenu\ItemProviders\FileDragProvider,
\TYPO3\CMS\Filelist\ContextMenu\ItemProviders\FileProvider ,
\TYPO3\CMS\Filelist\ContextMenu\ItemProviders\FileStorageProvider,
\TYPO3\CMS\Filelist\ContextMenu\ItemProviders\FilemountsProvider
and the ES6 module
@typo3/filelist/context-menu-actions.js
Adding context menu to elements in your backend module
Enabling context menu in your own backend modules is quite straightforward.
The examples below are taken from the "beuser" system extension and
assume that the module is Extbase-based.
The first step is to include the needed JavaScript using the
includeJavaScriptModules property
of the standard backend container Fluid view helper (or backend page
renderer view helper).
Doing so in your layout is sufficient (see
typo3/sysext/beuser/Resources/Private/Layouts/Default.html).
<!-- TYPO3 v12 and above --><f:be.pageRendererincludeJavaScriptModules="{0: '@typo3/backend/context-menu.js'}">
// ...
</f:be.pageRenderer><!-- TYPO3 v11 and v12 --><f:be.pageRendererincludeRequireJsModules="{0:'TYPO3/CMS/Backend/ContextMenu'}">
// ...
</f:be.pageRenderer>
Copied!
The second step is to activate the context menu on the icons. This kind of markup
is required (taken from
typo3/sysext/beuser/Resources/Private/Templates/BackendUser/Index.html):
the relevant line being highlighted. The attribute
data-contextmenu-trigger
triggers a context menu functionality for the current element. The
data-contextmenu-table attribute contains a table name of the record and
data-contextmenu-uid the
uid of the record.
The attribute
data-contextmenu-trigger has the following options:
click: Opens the context menu on the "click" and "contextmenu"
events
contextmenu: Opens the context menu only on the "contextmenu" event
One additional data attribute can be used
data-contextmenu-context with
values being, for example,
tree for context menu triggered from
the page tree. Context is used to hide menu items independently for page tree
independently from other places (disabled items can be configured in TSconfig).
Note
In most cases the
data-contextmenu-uid attribute contains an integer value.
However, in case of files and folders this attribute takes file/folder path
as a value like
data-contextmenu-uid="1:/some-folder/some-file.pdf"
Disabling Context Menu Items from TSConfig
Context menu items can be disabled in TSConfig by adding item name to the
options.contextMenu option corresponding to the table and context
you want to cover.
For example, disabling
edit and
new items for
table
pages use:
Follow these steps to add a custom menu item for pages records. You will add a
"Hello world" item which will show an info after clicking.
Context menu with custom item
Step 1: Implementation of the item provider class
Implement your own item provider class. Provider must implement
\TYPO3\CMS\Backend\ContextMenu\ItemProviders\ProviderInterface and
can extend
\TYPO3\CMS\Backend\ContextMenu\ItemProviders\AbstractProvider
or any other provider from EXT:backend.
See comments in the following code snippet clarifying implementation details.
<?phpnamespaceT3docs\Examples\ContextMenu;
/**
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/useTYPO3\CMS\Backend\ContextMenu\ItemProviders\AbstractProvider;
/**
* Item provider adding Hello World item
*/classHelloWorldItemProviderextendsAbstractProvider{
/**
* This array contains configuration for items you want to add
* @var array
*/protected $itemsConfiguration = [
'hello' => [
'type' => 'item',
'label' => 'Hello World', // you can use "LLL:" syntax here'iconIdentifier' => 'actions-lightbulb-on',
'callbackAction' => 'helloWorld', //name of the function in the JS file
],
];
/**
* Checks if this provider may be called to provide the list of context menu items for given table.
*
* @return bool
*/publicfunctioncanHandle(): bool{
// Current table is: $this->table// Current UID is: $this->identifier// return $this->table === 'pages';returntrue;
}
/**
* Returns the provider priority which is used for determining the order in which providers are processing items
* to the result array. Highest priority means provider is evaluated first.
*
* This item provider should be called after PageProvider which has priority 100.
*
* BEWARE: Returned priority should logically not clash with another provider.
* Please check @see \TYPO3\CMS\Backend\ContextMenu\ContextMenu::getAvailableProviders() if needed.
*
* @return int
*/publicfunctiongetPriority(): int{
return55;
}
/**
* Registers the additional JavaScript RequireJS callback-module which will allow to display a notification
* whenever the user tries to click on the "Hello World" item.
* The method is called from AbstractProvider::prepareItems() for each context menu item.
*
* @param string $itemName
* @return array
*/protectedfunctiongetAdditionalAttributes(string $itemName): array{
return [
'data-callback-module' => '@t3docs/examples/context-menu-actions',
// Here you can also add any other useful "data-" attribute you'd like to use in your JavaScript (e.g. localized messages)
];
}
/**
* This method adds custom item to list of items generated by item providers with higher priority value (PageProvider)
* You could also modify existing items here.
* The new item is added after the 'info' item.
*
* @param array $items
* @return array
*/publicfunctionaddItems(array $items): array{
$this->initDisabledItems();
// renders an item based on the configuration from $this->itemsConfiguration
$localItems = $this->prepareItems($this->itemsConfiguration);
if (isset($items['info'])) {
//finds a position of the item after which 'hello' item should be added
$position = array_search('info', array_keys($items), true);
//slices array into two parts
$beginning = array_slice($items, 0, $position+1, true);
$end = array_slice($items, $position, null, true);
// adds custom item in the correct position
$items = $beginning + $localItems + $end;
} else {
$items = $items + $localItems;
}
//passes array of items to the next item providerreturn $items;
}
/**
* This method is called for each item this provider adds and checks if given item can be added
*
* @param string $itemName
* @param string $type
* @return bool
*/protectedfunctioncanRender(string $itemName, string $type): bool{
// checking if item is disabled through TSConfigif (in_array($itemName, $this->disabledItems, true)) {
returnfalse;
}
$canRender = false;
switch ($itemName) {
case'hello':
$canRender = $this->canSayHello();
break;
}
return $canRender;
}
/**
* Helper method implementing e.g. access check for certain item
*
* @return bool
*/protectedfunctioncanSayHello(): bool{
//usually here you can find more sophisticated condition. See e.g. PageProvider::canBeEdited()returntrue;
}
}
Copied!
Step 2: JavaScript actions
Provide a JavaScript file (ES6 module) which will be
called after clicking on the context menu item.
/**
* Module: @t3docs/examples/context-menu-actions
*
* JavaScript to handle the click action of the "Hello World" context menu item
*/classContextMenuActions{
helloWorld(table, uid) {
if (table === 'pages') {
//If needed, you can access other 'data' attributes here from $(this).data('someKey')//see item provider getAdditionalAttributes method to see how to pass custom data attributes
top.TYPO3.Notification.error('Hello World', 'Hi there!', 5);
}
};
}
exportdefaultnew ContextMenuActions();
Copied!
Register the JavaScript ES6 modules of your extension if not done yet:
If you have
autoconfigure: true set in your extension's Services.yaml file all
classes implementing
\TYPO3\CMS\Backend\ContextMenu\ItemProviders\ProviderInterface
get registered as context menu items automatically:
Due to historical reasons, a context menu item was bound to
this in its callback action which was used to access the
context menu item's
dataset. With TYPO3 v12.0 the invocation of the
assigned callback actions is adapted to pass the
dataset as the
third argument.
Binding the context menu item to
this in the callback is marked as
deprecated.
To access data attributes, use the
dataset argument passed as the third
argument in the context menu callback action.
TYPO3 allows extension developers to register their own
permission options, managed automatically by the built-in user group
access lists. The options can be grouped in categories. A custom
permission option is always a checkbox (on/off).
The scope of such options is the backend only.
Registration
Options are configured in the global variable
$GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'] in
EXT:my_extension/ext_tables.php. The syntax is demonstrated in
the following example, which registers two custom permission options:
EXT:my_extension/ext_tables.php
<?phpdeclare(strict_types=1);
defined('TYPO3') ordie();
// Register some custom permission options shown in BE group access lists
$GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions']['tx_styleguide_custom'] = [
'header' => 'Custom styleguide permissions',
'items' => [
'key1' => [
'Option 1',
// Icon has been registered in Icons.php'tcarecords-tx_styleguide_forms-default',
'Description 1',
],
'key2' => [
'Option 2',
],
],
];
Copied!
The result is that these options appear in the group access lists like
this:
The custom permissions appear in the Access List tab of backend user groups
As you can see it is possible to add both an icon and a description text.
If icons not provided by the Core are used, they need to be registered
with the Icon API.
Evaluation
To check if a custom permission option is set call the following API
function from the user object:
$catKey is the category in which the option resides. From the example
above this would be tx_examples_cat1.
$itemKey is the key of the item in the category you are evaluating.
From the example above this could be key1, key2 or key3
depending on which one of them you want to evaluate.
The function returns true if the option is set, otherwise false.
Keys for Options
It is good practice to use the extension keys prefixed with tx_ on
the first level of the array to avoid potential conflicts with other
custom options.
Attention
Never pick a key containing any of the characters
",:\|". They are reserved delimiter characters.
Backend login form API
Registering a login provider
The concept of the backend login is based on "login providers".
A login provider can be registered within your config/system/settings.php
or config/system/additional.php like this:
The login provider class name, which must implement
\TYPO3\CMS\Backend\LoginProvider\LoginProviderInterface .
sorting
The sorting is important for the ordering of the links to the possible
login providers on the login screen.
iconIdentifier
Accepts any icon identifier that is available in the Icon Registry.
label
The label for the login provider link on the login screen.
For a new login provider you have to register a new key - by best practice
the current unix timestamp - in
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['backend']['loginProviders'] .
If your login provider extends another one, you may only overwrite necessary
settings. An example would be to extend an existing provider and
replace its registered
provider class with your custom class.
Implement this method and set the template for your form.
This is also the right place to assign data to the view
and add necessary JavaScript resources to the page renderer.
As mentioned above, the render method gets the Fluid StandaloneView as first parameter.
You have to set the template path and filename using the methods of this object.
The template file must only contain the form fields, not the form-tag.
Later on, the view renders the complete login screen.
View requirements:
The template must use the Login-layout provided by the
Core <f:layout name="Login">.
Form fields must be provided within the section
<f:section name="loginFormFields">.
Use the backend UriBuilder to link to "Edit Records"
It is often needed to create links to edit records in the TYPO3 backend.
The same syntax is also used for creating new records.
TYPO3 provides an API for creating such links, namely
\TYPO3\CMS\Backend\Routing\UriBuilder.
Hint
Make sure to use \TYPO3\CMS\Backend\Routing\UriBuilder to create
backend links and not \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder.
The variable available as $this->uriBuilder in a controller is the
web routing UriBuilder and can only be used for frontend links.
When using Fluid templates the URI
either has to be created via PHP in the controller or a ViewHelper to be used.
The examples above leads to the normal edit form for a page:
Page 1 ready for editing with the standard form
Additional options for editing records
When creating the link via PHP it is possible to add more options.
You can specify as many tables and UIDs as needed and you will get them all in
one single form!
(short way of editing more records from the same table at once).
Also the fields to be displayed can be restricted.
The fields to be included can be listed in the columnsOnly parameter, as a comma-separated list.
The order of the fields doesn't matter, they get displayed in the order they appear in the TCA.
If a field is missing or access restricted in one of the tables it just doesn't appear.
However if one record to be edited is missing none of the records gets displayed.
The example above results in the following:
Editing of fields of two pages and one haiku at once
The link triggers the creation a new record for the table tx_examples_haiku
on page 1. It also sets a default value for the title field ("New haiku") and
selects the season "Spring". It only displays the fields defined by columnsOnly.
Note the following things:
the first parameter is called "edit" even if this is about creating a new record.
The creation of a record is indicated by the value "new".
the key of the entry with value "new" indicates the pid on which the record is to be created.
the values get automatically url-encoded so you can use any special char in the defaults
This results in the following new record form with a pre-filled
title and season field.
Form for creating a new haiku with pre-filled title and season and restricted columns
Bitsets & Enumerations
Use an enumeration, if you have a fixed list of values.
Use a bitset, if you have a list of boolean flags.
Do not use PHP constants directly, if your code is meant to be extendable,
as constants cannot be deprecated, but the values of an enumeration or
methods of a bitset can.
Before version 8.1, PHP had no enumeration concept as part of the language.
Therefore the TYPO3 Core includes a custom enumeration implementation.
In TYPO3, enumerations are implemented by extending the abstract class
\TYPO3\CMS\Core\Type\Enumeration . It was originally implemented similar
to
\SplEnum which is unfortunately part of the unmaintained package
PECL spl_types.
With PHP version 8.1, an enumeration concept was implemented (see the
Enumeration documentation for more details). This makes it possible to drop
the custom enumeration concept from the Core in a future TYPO3 version.
How to use enumerations
Create an enumeration
To create a new enumeration you have to extend the class
\TYPO3\CMS\Core\Type\Enumeration . Make sure your enumeration is marked as
final, this ensures your code only receives a known set of values.
Otherwise adding more values by extension will lead to undefined behavior in
your code.
Values are defined as constants in your implementation. The names of the
constants must be given in uppercase.
A special, optional constant
__default represents the default value of
your enumeration, if it is present. In that case the enumeration can be
instantiated without a value and will be set to the default.
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Enumerations;
useTYPO3\CMS\Core\Type\Enumeration;
finalclassLikeWildcardextendsEnumeration{
publicconst __default = self::BOTH;
/**
* @var int Do not use any wildcard
*/publicconst NONE = 0;
/**
* @var int Use wildcard on left side
*/publicconst LEFT = 1;
/**
* @var int Use wildcard on right side
*/publicconst RIGHT = 2;
/**
* @var int Use wildcard on both sides
*/publicconst BOTH = 3;
}
Copied!
Use an enumeration
You can create an instance of the
Enumeration class like you would
usually do, or you can use the
Enumeration::cast() method for
instantiation. The
Enumeration::cast() method can handle:
Enumeration instances (where it will simply return the value) and
simple types with a valid
Enumeration value,
whereas the "normal"
__construct() will always try to create a new
instance.
That allows to deprecate enumeration values or do special value
casts before finding a suitable value in the enumeration.
Example:
EXT:my_extension/Classes/SomeClass.php
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension;
useMyVendor\MyExtension\Enumerations\LikeWildcard;
finalclassSomeClass{
publicfunctiondoSomething(){
// ...
$likeWildcardLeft = LikeWildcard::cast(LikeWildcard::LEFT);
$valueFromDatabase = 1;
// will cast the value automatically to an enumeration.// Result is true.
$likeWildcardLeft->equals($valueFromDatabase);
$enumerationWithValueFromDb = LikeWildcard::cast($valueFromDatabase);
// Remember to always use ::cast and never use the constant directly
$enumerationWithValueFromDb->equals(LikeWildcard::cast(LikeWildcard::RIGHT));
// ...
}
// ...
}
Copied!
Exceptions
If the enumeration is instantiated with an invalid value, a
\TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException is thrown.
This exception must be caught, and you have to decide what the appropriate
behavior should be.
Attention
Always be prepared to handle exceptions when instantiating
enumerations from user defined values!
Sometimes it makes sense to not only validate a value, but also to
have custom logic as well.
For example, the
\TYPO3\CMS\Core\Versioning\VersionState enumeration
contains values of version states. Some of the values indicate that the state is
a "placeholder". This logic can be implemented by a custom method:
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension;
finalclassSomeClass{
publicfunctiondoSomething(){
// ...
$myVersionState = VersionState::cast($versionStateValue);
if ($myVersionState->indicatesPlaceholder()) {
echo'The state indicates that this is a placeholder';
}
// ...
}
// ...
}
Copied!
How to use bitsets
Bitsets are used to handle boolean flags efficiently.
The class
\TYPO3\CMS\Core\Type\BitSet provides a TYPO3 implementation of
a bitset. It can be used standalone and accessed from the outside, but we
recommend creating specific bitset classes that extend the TYPO3
BitSet class.
The functionality is best described by an example:
TYPO3 uses multiple caching strategies to ensure fast content delivery.
Depending on the content a page contains, TYPO3 chooses the best caching
strategy for that use case.
For example, you might have a fully-cacheable page, a page that is at least
partially cacheable or a page that is completely dynamic. Dynamic elements in
TYPO3 are also known as USER_INT or
COA_INT objects - as these are the matching
Typoscript objects used to render non-cacheable content.
When visiting a TYPO3 web site, TYPO3 knows the following states:
first time hit, page has never been rendered ("cache miss")
consecutive hit, page has been rendered before ("cache hit")
In the first case, TYPO3 renders the complete page and writes cache entries as
configured. In the second case, TYPO3 fetches those entries from the cache and
delivers them to the user without re-triggering the rendering.
In that second case, either the page is fully cached and directly delivered from
the cache, or the page has non-cacheable elements on it. If a page has
non-cacheable elements, TYPO3 first fetches the cached part of the page and then
renders all dynamic parts.
Note
The cache contains placeholders for dynamic elements to tell TYPO3 where to
render dynamic parts.
Hint
For developers: If you are developing a plugin,
think about your plugin's cache lifetime. Ideally, it can be fully cached,
but if not, read the section about the
caching framework to learn how to leverage
TYPO3's caching mechanism to cache your plugin for however long you can -
even 30 seconds might improve performance in some scenarios.
Caching variants - or: What is a "cache hash"?
TYPO3 ideally delivers fully cached pages for maximum performance. However, in
scenarios where the same page will deliver different content depending on URL
parameters, TYPO3 needs a possibility to identify these "variants" and cache
each of them differently. For example, if you have a news plugin and a detail
page, the detail page is different for every news entry.
To identify the variant, TYPO3 combines a set of parameters and generates a hash
value as identifier. These parameters include by default:
id: The current page ID
type: The current page type (typeNum)
groupIds: The user groups of the logged-in user (if no user is logged in:
0, -1 as default values)
staticRouteArguments: Any route argument configured in the
routing configuration and resolved in the current
request
dynamicArguments: Any "GET" parameters influencing the rendered page
Imagine the following URL https://example.org/news/?tx_example_news[id]=123
displaying the news with ID 123. If TYPO3 would cache that page with that
parameter without any security mechanisms, it would open a potential denial of
service attack as an unlimited amount of cache entries could be generated by
adding arbitrary parameters. To avoid that, TYPO3 generates a so-called "cHash"
parameter, which is a hash that basically signs the valid parameters for that
request. So any parameter that validly influences the rendered page needs to be
part of that cHash.
With routing you can configure TYPO3 not to display the cHash
in your URLs in most cases. Routing adds an explicit mapping of incoming
readable URL slugs to internal parameter values. This both adds an additional
layer for validating slugs as well as reduces the parameters to a limited (and
predictable) set of values.
Various configuration options exist to configure the cHash behavior via
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']
in the file config/system/settings.php or config/system/additional.php:
cachedParametersWhiteList
cachedParametersWhiteList
Only the given parameters will be evaluated in the cHash calculation.
Example: tx_news_pi1[uid]
Attention
This option will lead to cache calculation being skipped for all
parameters except the ones listed here. Caching of pages will not be
influenced by other parameters beyond the initial caching anymore.
requireCacheHashPresenceParameters
requireCacheHashPresenceParameters
Configure parameters that require a cHash. If no cHash is given, but one of
the parameters are set, then TYPO3 triggers the configured cHash error
behavior
excludedParameters
excludedParameters
The given parameters will be ignored in the cHash calculation.
Example: L,tx_search_pi1[query]
excludedParametersIfEmpty
excludedParametersIfEmpty
Configure parameters only being relevant for the cHash if there is an
associated value available. Set excludeAllEmptyParameters to true to skip
all empty parameters.
excludeAllEmptyParameters
excludeAllEmptyParameters
If true, all parameters relevant to cHash are only considered when they are
not empty.
enforceValidation
enforceValidation
If this option is enabled, the same validation is used to calculate a cHash
value as when a valid or invalid "cHash" parameter is given to a request,
even when no cHash is given.
All properties can be configured with an array of values. Besides exact matches
(equals) it is possible to apply partial matches at the beginning of a
parameter (startsWith) or inline occurrences (contains).
URL parameter names are prefixed with the following indicators:
= (equals): exact match, default behavior if not given
^ (startsWith): matching the beginning of a parameter name
~ (contains): matching any inline occurrence in a parameter name
These indicators can be used for all previously existing sub-properties
cachedParametersWhiteList,
excludedParameters,
excludedParametersIfEmpty
and
requireCacheHashPresenceParameters.
Clearing the cache for a single page is done by using the "clear cache button"
on that page in the backend (usually visualized with a "bolt" icon). Depending
on user rights this option is available for editors.
Clearing the frontend caches and all caches can be done via the system toolbar
in the TYPO3 backend. This option should only be available to administrators.
Clearing all caches can have a significant impact on the performance of the web
site and should be used sparingly on production systems.
The "fullest" option to clear all caches can be reached via the
System Tools > Maintenance section. In the development context when
new classes have been added to the system or in case of problems with the system
using this cache clearing option will clear all caches including compiled code
like the dependency injection container.
Clear cache command
In addition to the GUI options explained above, caches can also be cleared
via a CLI command:
vendor/bin/typo3 cache:flush
Copied!
typo3/sysext/core/bin/typo3 cache:flush
Copied!
Specific cache groups can be defined via the group option.
The usage is described as this:
cache:flush [--group <all|system|di|pages|...>]
Copied!
All available cache groups can be supplied as
option. The command defaults to flush all available cache groups as the
System Tools > Maintenance area does.
Extensions that register custom caches may listen to the CacheFlushEvent,
but usually the cache flush via cache manager groups will suffice to clear those
caches, too.
Cache warmup
It is possible to warmup TYPO3 caches using the command line.
The administrator can use the following
CLI command:
vendor/bin/typo3 cache:warmup
Copied!
typo3/sysext/core/bin/typo3 cache:warmup
Copied!
Specific cache groups can be defined via the group option.
The usage is described as this:
cache:warmup [--group <all|system|di|pages|...>]
Copied!
All available cache groups can be supplied as
option. The command defaults to warm all available cache groups.
Extensions that register custom caches are encouraged to implement cache warmers
via CacheWarmupEvent.
Note
TYPO3 frontend caches will not be warmed by TYPO3 Core, such functionality
could be added by third-party extensions with the help of
CacheWarmupEvent.
Use case: deployment
It is often required to clear caches during deployment of TYPO3 instance.
The integrator may decide to flush all caches or may alternatively flush
selected groups (for example, "pages"). It is common practice to clear all
caches during deployment of a TYPO3 instance update. This means that the first
request after a deployment usually takes a major amount of time and blocks other
requests due to cache-locks.
TYPO3 caches can be warmed during deployment in release preparatory steps in
symlink-based deployment/release procedures. This enables fast first requests
with all (or at least system) caches being prepared and warmed.
Caches are often filesystem relevant (file paths are calculated into cache
hashes), therefore cache warmup should only be performed on the the live system,
in the final folder of a new release, and ideally before switching
to that new release (via symlink switch).
Warning
Caches that have been pre-created on the continuous integration server
will likely be useless as cache hashes will not match (as the final file
system path is relevant to the hash generation).
To summarize: Cache warmup is to be used during deployment, on the live system
server, inside the new release folder and before switching to the new release.
An example deployment could consist of:
Before the release:
git-checkout/rsync your codebase to the continuous integration / build
server
composer install on the continuous integration / build server
vendor/bin/typo3 cache:warmup --group system (only on the live
system)
Change release symlink to the new release folder
After the release:
vendor/bin/typo3 cache:flush --group pages
The conceptional idea is to warmup all file-related caches before (symlink)
switching to a new release and to only flush database and frontend (shared)
caches after the symlink switch. Database warmup could be implemented with
the help of the CacheWarmupEvent as an additionally functionality by
third-party extensions.
Note that file-related caches (summarized into the group "system") can safely be
cleared before doing a release switch, as it is recommended to keep file caches
per release. In other words, share var/session/, var/log/,
var/lock/ and var/charset/ between releases, but keep
var/cache/ be associated only with one release.
Caching framework
TYPO3 contains a data caching framework which supports a wide variety of storage
solutions and options for different caching needs. Each cache can be configured
individually and can implement its own specific storage strategy.
The caching framework exists to help speeding up TYPO3 sites, especially
heavily loaded ones. It is possible to move all caches to a dedicated cache
server with specialized cache systems like the Redis key-value store (a so
called NoSQL database).
Major parts of the original caching framework were originally backported from
TYPO3 Flow.
If specific settings should be applied to the configuration, they should be
added to config/system/settings.php. All settings in
config/system/settings.php will be merged with
DefaultConfiguration.php. The easiest way to see the final cache
configuration is to use the TYPO3 backend module
System > Configuration > $GLOBALS['TYPO3_CONF_VARS']
(with installed lowlevel system extension).
Example for a configuration of a
Redis cache backend on Redis database number 42
instead of the default database backend with compression for the pages cache:
Most cache backends do not have an internal system to remove old cache entries
that exceeded their lifetime. A cleanup must be triggered externally to find and
remove those entries, otherwise caches could grow to arbitrary size. This could
lead to a slow website performance, might sum up to significant hard disk or
memory usage and could render the server system unusable.
It is advised to always enable the scheduler and
run the "Caching framework garbage collection" task to retain clean and small
caches. This housekeeping could be done once a day when the system is otherwise
mostly idle.
Registry of all configured caches. Each cache is identified by its array
key. Each cache can have the sub-keys
frontend,
backend and
options to configure the used frontend, backend and possible backend
options.
Cache configurations
Unfortunately in TYPO3, all ext_localconf.php files of the extensions
are loaded after the instance-specific configuration from
config/system/settings.php and config/system/additional.php.
This enables extensions to overwrite cache configurations already done for the
instance. All extensions should avoid this situation and should define the very
bare minimum of cache configurations. This boils down to define the array key to
populate a new cache to the system. Without further configuration, the cache
system falls back to the default backend and default frontend settings:
Extensions, like Extbase, define default caches this way,
giving administrators full freedom for specific and possibly quicker setups
(for example, a memory-driven cache for the Extbase reflection cache).
Administrators can overwrite specific settings of the cache configuration in
config/system/settings.php or config/system/additional.php. Here
is an example configuration to switch pages to the Redis backend using
database 3:
Some backends have mandatory as well as optional parameters (which are
documented in the Cache backends section). If not all
mandatory options are defined, the specific backend will throw an exception, if
accessed.
How to disable specific caches
During development it can be convenient to disable certain caches. This is
especially helpful for central caches like the language or autoloader cache.
This can be achieved by using the null backend as
storage backend.
Warning
Do not use this in production, it will slow the system down considerably!
Example configuration to switch the extbase_reflection cache to use the
null backend:
The caching framework can handle multiple caches with different configurations.
A single cache consists of any number of cache entries.
A single cache entry is defined by these fields:
identifier
A string as unique identifier within this cache. It is used to store and
retrieve entries.
data
The data to be cached.
lifetime
A lifetime in seconds of this cache entry. An entry can not be retrieved
from cache, if the lifetime is expired.
tags
Additional tags (an array of strings) assigned to the entry. It is used to
remove specific cache entries.
Tip
The difference between identifier and tags is quite simple: an identifier
uniquely identifies a cache entry, and a tag is additional data applied to
an entry (used for cache eviction). Thus, an identifier refers to a single
cache entry to store and retrieve an entry, and a tag can refer to multiple
cache entries.
About the identifier
The identifier is used to store ("set") and retrieve ("get") entries from the
cache and holds all information to differentiate entries from each other.
For performance reasons, it should be quick to calculate.
Suppose a resource-intensive extension is added as a plugin on two different
pages. The calculated content depends on the page on which it is inserted and
if a user is logged in or not. So, the plugin creates at maximum four different
content outputs, which can be cached in four different cache entries:
page 1, no user logged in
page 1, a user is logged in
page 2, no user logged in
page 2, a user is logged in
To differentiate all entries from each other, the identifier is built from the
page ID where the plugin is located, combined with the information whether a
user is logged in. These are concatenated and hashed. In PHP this could look
like this:
sha1 is a good hash algorithm in this case, as collisions are extremely
unlikely. It scales O(n) with the input length.
When the plugin is accessed, the identifier is calculated early in the program
flow. Next, the plugin looks up for a cache entry with this identifier.
If such an entry exists, the plugin can return the cached content,
else it calculates the content and stores a new cache entry with this identifier.
In general, the identifier is constructed from all dependencies
which specify a unique set of data. The identifier should be based on
information which already exist in the system at the point of its calculation.
In the above scenario the page ID and whether or not a user is logged in
are already determined during the frontend bootstrap and
can be retrieved from the system quickly.
About tags
Tags are used to drop specific cache entries when some information they are
based on is changed.
Suppose the above plugin displays content based on different news entries.
If one news entry is changed in the backend, all cache entries
which are compiled from this news row must be dropped to ensure that
the frontend renders the plugin content again and does not deliver old content
on the next frontend call.
For example, if the plugin uses news number one and two on one page, and news
one on another page, the related cache entries should be tagged with these tags:
page 1, tags news_1, news_2
page 2, tag news_1
If entry 2 is changed, a simple backend logic (probably a hook in
DataHandler) could be created, which drops all cache
entries tagged with news_2. In this case the first entry would be
invalidated while the second entry still exists in the cache after the
operation.
While there is always exactly one identifier for each cache entry,
an arbitrary number of tags can be assigned to an entry and one specific tag
can be assigned to multiple cache entries. All tags a cache entry has are given
to the cache when the entry is stored ("set").
Caches in the TYPO3 Core
The TYPO3 Core defines and uses several caching framework caches by default.
This section gives an overview of default caches, its usage and behaviour. If
not stated otherwise, the default database backend
with variable frontend is used.
The various caches are organized in groups. Currently, the following groups
exist:
pages
Frontend-related caches.
system
System caches. Flushing system caches should be avoided as much
as possible, as rebuilding them requires significant resources.
lowlevel
Low-level caches. Flushing low-level caches manually should be avoided
completely.
all
All other caches.
Cache clearing commands can be issued to target a particular group. If a cache
does not belong to a group, it will be flushed when the "all" group is flushed,
but such caches should normally be transient
anyway.
This cache is instantiated very early during
bootstrap and can not be re-configured
by instance-specific config/system/settings.php or similar.
Cache entries are located in directory var/cache/code/core/
(for Composer-based installations) and
typo3temp/var/cache/code/core/ (for Classic mode installations). The
full directory and any file in this directory can be safely removed and
will be re-created upon next request. This is especially useful during
development
hash
groups: all, pages
Stores several key-value based cache entries, mostly used during
frontend rendering.
pages
groups: all, pages
The frontend page cache. It stores full frontend pages.
The content is compressed by default to reduce database memory and
storage overhead.
runtime
Runtime cache to store data specific for current request.
Used by several Core parts during rendering to re-use already calculated
data.
Valid for one request only.
Can be re-used by extensions that have similar caching needs.
rootline
groups: all, pages
Cache for rootline calculations.
A quick and simple cache dedicated for Core usage, it should not be
re-used by extensions.
imagesizes
groups: lowlevel
Cache for imagesizes.
It should only be cleared manually, if you know what you are doing.
assets
groups: system
Cache for assets.
Examples: backend icons, RTE or JavaScript configuration.
Contains the contents of RSS feeds retrieved by RSS widgets on the
dashboard.
This cache can be used by extension authors for their own RSS widgets.
Tip
In rare cases, for example, when classes that are required during the
bootstrap process are introduced (usually when working on the TYPO3 Core),
cache clearings requests themselves might throw fatal errors.
The solution here is to manually remove the cache files from
var/cache/code/ (for Composer-based installations) or
typo3temp/var/cache/code/ (for Classic mode installations).
Garbage collection task
The Core system provides a scheduler task to
collect the garbage of all cache backends. This is
important for backends like the database backend
that do not remove old cache entries and tags internally. It is highly
recommended to add this scheduler task and run it once in a while (maybe once a
day at night) for all used backends that do not delete entries which exceeded
their lifetime on their own to free up memory or hard disk space.
Cache API
The caching framework architecture is based on the following classes:
\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Main interface to handle cache entries of a specific cache. Different
frontends and further interfaces exist to handle different data types.
\TYPO3\CMS\Core\Cache\Backend\BackendInterface
Main interface that every valid storage backend must implement. Several
backends and further interfaces exist to specify specific backend
capabilities. Some frontends require backends to implement additional
interfaces.
Note
The
\TYPO3\CMS\Core\Cache\CacheManager was used before TYPO3 v10.1 to
retrieve an object implementing
FrontendInterface. It is now
recommended to use dependency injection
to retrieve this object and no longer use the
CacheManager directly.
Warning
Do not use the
CacheManager in ext_localconf.php - instead
load caches on demand at the place where they are needed.
Cache frontends
Frontend API
All frontends must implement the API defined in interface TYPO3\CMS\Core\Cache\Frontend\FrontendInterface.
All operations on a specific cache must be done with these methods. The frontend object of a cache is the main object
any cache manipulation is done with, usually the assigned backend object should not be used directly.
Method
Description
getIdentifier
Returns the cache identifier.
getBackend
Returns the backend instance of this cache. It is seldom needed in usual code.
set
Sets/overwrites an entry in the cache.
get
Returns the cache entry for the given identifier.
has
Checks for existence of a cache entry.
Do no use this prior to get() since get()
returns NULL if an entry does not exist.
remove
Removes the entry for the given identifier from the cache.
flushByTag
Flushes all cache entries which are tagged with the given tag.
collectGarbage
Calls the garbage collection method of the backend.
This is important for backends which are unable to do this internally
(like the DB backend).
isValidEntryIdentifier
Checks if a given identifier is valid.
isValidTag
Checks if a given tag is valid.
requireOnce
PhpFrontend only Requires a cached PHP file directly.
Available Frontends
Currently two different frontends are implemented. The main difference are
the data types which can be stored using a specific frontend.
Variable Frontend
Strings, arrays and objects are accepted by this frontend.
Data is serialized before it is passed to the backend.
Tip
The variable frontend is the most frequently used frontend and handles
the widest range of data types.
PHP Frontend
This is a special frontend to cache PHP files. It extends the string frontend
with the method requireOnce() which allows PHP files to be require()'d
if a cache entry exists. This can be used by extensions to cache and speed up loading
of calculated PHP code and becomes handy if a lot of reflection and
dynamic PHP class construction is done.
A backend to be used in combination with the PHP frontend must implement the interface
TYPO3\CMS\Core\Cache\Backend\PhpCapableBackendInterface. Currently the file backend and
the simple file backend fulfill this requirement.
Note
The PHP frontend can only be used to cache PHP files.
It does not work with strings, arrays or objects.
It is not intended as a page content cache.
Cache backends
A variety of storage backends exists. They have different characteristics
and can be used for different caching needs. The best backend depends on
a given server setup and hardware, as well as cache type and usage.
A backend should be chosen wisely, as a wrong decision could end up actually
slowing down a TYPO3 installation.
Backend API
All backends must implement at least interface TYPO3\CMS\Core\Cache\Backend\BackendInterface.
All operations on a specific cache must be done with these methods. There are several further interfaces that can be
implemented by backends to declare additional capabilities. Usually, extension code should not handle cache backend operations
directly, but should use the frontend object instead.
Method
Description
setCache
Reference to the frontend which uses the backend. This method is mostly used internally.
set
Save data in the cache.
get
Load data from the cache.
has
Checks if a cache entry with the specified identifier exists.
remove
Remove a cache entry with the specified identifier.
flush
Remove all cache entries.
collectGarbage
Does garbage collection.
flushByTag
TaggableBackendInterface only Removes all cache entries which are tagged by the specified tag.
findIdentifiersByTag
TaggableBackendInterface only Finds and returns all cache entry identifiers which are tagged by the specified tag.
requireOnce
PhpCapableBackendInterface only Loads PHP code from the cache and require_onces it right away.
freeze
FreezableBackendInterface only Freezes this cache backend.
isFrozen
FreezableBackendInterface only Tells if this backend is frozen.
Common Options
Option
Description
Mandatory
Type
Default
defaultLifetime
Default lifetime in seconds of a cache entry if it is not specified for a specific entry on set()
No
integer
3600
Database Backend
This is the main backend suitable for most storage needs.
It does not require additional server daemons nor server configuration.
The database backend does not automatically perform garbage collection.
Instead the Scheduler garbage collection task should be used.
It stores data in the configured database (usually MySQL)
and can handle large amounts of data with reasonable performance.
Data and tags are stored in two different tables, every cache needs its own set of tables.
In terms of performance the database backend is already pretty well optimized
and should be used as default backend if in doubt. This backend is the default backend if no backend
is specifically set in the configuration.
The Core takes care of creating and updating required database tables "on the fly".
Note
However, caching framework tables which are not needed anymore are not deleted automatically. That is why the database analyzer in the Install Tool will propose you to rename/delete caching framework tables after you changed the caching backend to a non-database one.
For caches with a lot of read and write operations, it is important to tune the MySQL setup.
The most important setting is innodb_buffer_pool_size. A generic goal is to give MySQL
as much RAM as needed to have the main table space loaded completely in memory.
The database backend tends to slow down if there are many write operations
and big caches which do not fit into memory because of slow harddrive seek and write performance.
If the data table grows too big to fit into memory, it is possible to compress given data transparently
with this backend, which often shrinks the amount of needed space to 1/4 or less.
The overhead of the compress/uncompress operation is usually not high.
A good candidate for a cache with enabled compression is the Core pages cache:
it is only read or written once per request and the data size is pretty large.
The compression should not be enabled for caches which are read or written
multiple times during one request.
InnoDB Issues
The database backend for MySQL uses InnoDB tables. Due to the nature of InnoDB, deleting records
does not reclaim the actual disk space. E.g. if the cache uses 10GB,
cleaning it will still keep 10GB allocated on the disk even though phpMyAdmin will show 0 as the cache table size.
To reclaim the space, turn on the MySQL option file_per_table, drop the cache tables and re-create
them using the Install Tool.
This does not by any mean that you should skip the scheduler task. Deleting records still improves performance.
Options
Option
Description
Mandatory
Type
Default
compression
Whether or not data should be compressed with gzip.
This can reduce size of the cache data table, but incurs CPU overhead
for compression and decompression.
No
boolean
false
compressionLevel
Gzip compression level (if the compression option is set to true).
The default compression level is usually sufficient.
-1: Default gzip compression (recommended)
0: No compression
9: Maximum compression (costs a lot of CPU)
No
integer from -1 to 9
-1
Memcached Backend
Memcached is a simple, distributed key/value RAM database.
To use this backend, at least one memcached daemon must be reachable,
and the PECL module "memcache" must be loaded.
There are two PHP memcached implementations: "memcache" and "memcached".
Currently, only memcache is supported by this backend.
Warning and Design Constraints
Memcached is a simple key-value store by design . Since the caching framework
needs to structure it to store the identifier-data-tags relations, for each
cache entry it stores an identifier->data, identifier->tags and a
tag->identifiers entry.
This leads to structural problems:
If memcache runs out of memory but must store new entries,
it will toss some other entry out of the cache
(this is called an eviction in memcached speak).
If data is shared over multiple memcache servers and some server fails,
key/value pairs on this system will just vanish from cache.
Both cases lead to corrupted caches. If, for example, a tags->identifier entry is lost,
dropByTag() will not be able to find the corresponding identifier->data entries
which should be removed and they will not be deleted. This results in old data delivered by the cache.
Additionally, there is currently no implementation of the garbage collection that could rebuild cache integrity.
It is important to monitor a memcached system for evictions and server outages
and to clear caches if that happens.
Furthermore memcache has no sort of namespacing.
To distinguish entries of multiple caches from each other,
every entry is prefixed with the cache name.
This can lead to very long runtimes if a big cache needs to be flushed,
because every entry has to be handled separately and it is not possible
to just truncate the whole cache with one call as this would clear
the whole memcached data which might even hold non TYPO3 related entries.
Because of the mentioned drawbacks, the memcached backend should be used with care
or in situations where cache integrity is not important or if a cache has no need to use tags at all.
Currently, the memcache backend implements the TaggableBackendInterface, so the implementation does allow tagging,
even if it is not advised to used this backend together with heavy tagging.
Warning
Since memcached has no sort of namespacing and access control,
this backend should not be used if other third party systems have access
to the same memcached daemon for security reasons.
This is a typical problem in cloud deployments where access to memcache is cheap
(but could be read by third parties) and access to databases is expensive.
Options
Option
Description
Mandatory
Type
Default
servers
Array of used memcached servers. At least one server must be defined.
Each server definition is a string, allowed syntaxes:
hostname or IP: TCP connect to host on memcached default port
(usually 11211, defined by PHP ini variable memcache.default_port)
hostname:port: TCP connect to host on port
tcp://hostname:port: Same as above
unix:///path/to/memcached.sock: Connect to memcached server using unix sockets
Yes
array
compression
Enable memcached internal data compression.
Can be used to reduce memcached memory consumption,
but adds additional compression / decompression CPU overhead
on the related memcached servers.
No
boolean
false
Redis Backend
Redis is a key-value storage/database.
In contrast to memcached, it allows structured values.
Data is stored in RAM but it allows persistence to disk
and doesn't suffer from the design problems of the memcached backend implementation.
The redis backend can be used as an alternative to the database backend
for big cache tables and helps to reduce load on database servers this way.
The implementation can handle millions of cache entries each with hundreds of tags
if the underlying server has enough memory.
Redis is known to be extremely fast but very memory hungry.
The implementation is an option for big caches with lots of data
because most important operations perform O(1) in proportion to the number of (redis) keys.
This basically means that the access to an entry in a cache with a million entries
is not slower than to a cache with only 10 entries,
at least if there is enough memory available to hold the complete set in memory.
At the moment only one redis server can be used at a time per cache,
but one redis instance can handle multiple caches without performance loss when flushing a single cache.
Attention
The scheduler garbage collection task should be run regularly to
find and delete old cache tags entries. These do not expire on their own and
would remain in memory indefinitely - unless cache is flushed.
The implementation is based on the PHP phpredis module,
which must be available on the system.
Warning
Please check the section on
configuration and monitor
memory usage (and eviction, if enabled). Otherwise, you may run into
problems, if not enough memory for the cache entries is reserved in the Redis
server (maxmemory).
Note
It is important to monitor the redis server and tune its settings
to the specific caching needs and hardware capabilities.
There are several articles on the net and the redis configuration file
contains some important hints on how to speed up the system if it reaches bounds.
A full documentation of available options is far beyond this documentation.
Redis example
The Redis caching backend configuration is very similar to that of other
backends, but there is one caveat.
TYPO3 caches should be separated in case the same keys are used.
This applies to the pages and pagesection caches.
Both use "tagIdents:pageId_21566" for a page with an id of 21566.
How you separate them is more of a system administrator decision. We provide
examples with several databases but this may not be the best option
in production where you might want to use multiple cores (which do not
support databases). The separation has the additional advantage that
caches can be flushed individually.
If you have several of your own caches which each use unique keys (for example
by using a different prefix for the cache identifier for each cache), you can
store them in the same database, but it is good practice to separate the core
caches.
In practical terms, Redis databases should be used to separate different keys
belonging to the same application (if needed), and not to use a single Redis
instance for multiple unrelated applications.
Activate a persistent connection to redis server. This could be a benefit under high load cloud setups.
No
boolean
false
database
Number of the database to store entries. Each cache should use its own database,
otherwise all caches sharing a database are flushed if the flush operation
is issued to one of them. Database numbers 0 and 1 are used and flushed by the Core unit tests
and should not be used if possible.
No
integer
password
Password used to connect to the redis instance if the redis server needs authentication.
Warning
The password is sent to the redis server in plain text.
No
string
compression
Whether or not data compression with gzip should be enabled.
This can reduce cache size, but adds some CPU overhead for the compression
and decompression operations in PHP.
No
boolean
false
compressionLevel
Set gzip compression level to a specific value. The default compression level is usually sufficient.
-1: Default gzip compression (recommended)
0: No compression
9: Maximum compression (but more CPU overhead)
No
integer from -1 to 9
-1
Redis server configuration
This section is about the configuration on the Redis server, not the client.
For the flushing by cache tags to work, it is important that the integrity of
the cache entries and cache tags is maintained. This may not be the case,
depending on which eviction policy (maxmemory-policy) is used. For example,
for a page id=81712, the following entries may exist in the Redis page cache:
If entries are evicted (due to memory shortage), there is no mechanism in
place which ensures that all entries which are related, will be evicted. If
maxmemory-policy allkeys-lru is used, for example, this may
result in the situation that the cache entry (identData) still exists, but the
tag entry (tagIdents) does not. The tag entry reflects the relation
"cache tag => cache identifier" and is used for
RedisBackend::flushByTag()). If this entry is gone, the cache
can no longer be flushed if content is changed on the page or an explicit
flushing of the page cache for this page is requested. Once this is the case,
cache flushing (for this page) is only possible via other means (such as full
cache flush).
Because of this, the following recommendations apply:
Allocate enough memory (maxmemory) for the cache.
Use the maxmemory-policyvolatile-ttl. This will ensure
that no tagIdents entries are removed. (These have no expiration date).
Regularly run the TYPO3 scheduler garbage collection task for the Redis cache
backend.
Monitor evicted_keys in case an eviction policy is used.
Monitor used_memory if eviction policy noeviction is used. The
used_memory should always be less then maxmemory.
Tip
The information about evicted_keys etc. can be obtained via redis-cli and
the info command or via php-redis. Further information of the results of
info is in the documentation.
(recommended) Will flush only entries with an expiration date. Should be ok
with TYPO3.
noeviction
(Not recommended) Once memory is full, no new entries will be saved to cache.
Only use if you can ensure that there is always enough memory.
allkeys-lru, allkeys-lfu, allkeys-random
(Not recommended) This may result in tagIdents being removed, but not the
related identData entry, which makes it impossible to flush the cache
entries by tag (which is necessary for TYPO3 cache flushing on changes to
work and the flush page cache to work for specific pages).
The file backend stores every cache entry as a single file to the file system.
The lifetime and tags are added after the data part in the same file.
This backend is the big brother of the Simple file backend and implements additional interfaces. Like the simple file
backend it also implements the PhpCapableInterface, so it can be used with PhpFrontend. In contrast to
the simple file backend it furthermore implements TaggableInterface and FreezableInterface.
A frozen cache does no lifetime check and has a list of all existing cache entries that is reconstituted during initialization.
As a result, a frozen cache needs less file system look ups and calculation time if accessing cache entries. On the other
hand, a frozen cache can not manipulate (remove, set) cache entries anymore. A frozen cache must flush the complete cache
again to make cache entries writable again. Freezing caches is currently not used in the TYPO3 Core. It can be an option
for code logic that is able to calculate and set all possible cache entries during some initialization phase, to then freeze
the cache and use those entries until the whole thing is flushed again. This can be useful especially if caching PHP code.
In general, the backend was specifically optimized to cache PHP code, the get and set operations have low
overhead. The file backend is not very good with tagging and does not scale well with the number of tags. Do not use this
backend if cached data has many tags.
Warning
The performance of flushByTag() is bad and scales just O(n).
On the contrary performance of get() and set() operations.
is good and scales well. Of course if many entries have to be handled, this might
still slow down after a while and a different storage strategy should be used
(e.g. RAM disks, battery backed up RAID systems or SSD hard disks).
Options
Option
Description
Mandatory
Type
Default
cacheDirectory
The directory where the cache files are stored. By default it is assumed that the directory is below
TYPO3_DOCUMENT_ROOT. However, an absolute path can be selected, too. Every cache should be assigned
its own directory, otherwise flushing of one cache would flush all other caches within the same directory as well.
No
string
typo3temp/cache/
Simple File Backend
The simple file backend is the small brother of the file backend. In contrast to most
other backends, it does not implement the TaggableInterface, so cache entries can not be tagged and flushed
by tag. This improves the performance if cache entries do not need such tagging. The TYPO3 Core uses this backend
for its central Core cache (that hold autoloader cache entries and other important cache entries). The Core cache is
usually flushed completely and does not need specific cache entry eviction.
PDO Backend
The PDO backend can be used as a native PDO interface to databases which are connected to PHP via PDO.
It is an alternative to the database backend if a cache should be stored in a database which is otherwise
only supported by TYPO3 dbal to reduce the parser overhead.
The garbage collection is implemented for this backend and should be called to clean up hard disk space or memory.
Note
There is currently very little production experience with this backend, especially not with a capable database like Oracle.
Any feedback for real life use cases of this cache is appreciated.
Options
Option
Description
Mandatory
Type
Default
dataSourceName
Data source name for connecting to the database. Examples:
mysql:host=localhost;dbname=test
sqlite:/path/to/sqlite.db
sqlite::memory
Yes
string
username
Username for the database connection.
No
string
password
Password to use for the database connection.
No
string
Transient Memory Backend
The transient memory backend stores data in a PHP array. It is only valid for one request. This becomes handy if code
logic needs to do expensive calculations or must look up identical information from a database over and over again
during its execution. In this case it is useful to store the data in an array once and lookup the entry from the
cache for consecutive calls to get rid of the otherwise additional overhead. Since caches are available system wide and
shared between Core and extensions they can profit from each other if they need the same information.
Since the data is stored directly in memory, this backend is the quickest backend available. The stored data adds to
the memory consumed by the PHP process and can hit the memory_limit PHP setting.
Null Backend
The null backend is a dummy backend which doesn't store any data and always returns false
on get(). This backend becomes handy in development context to practically "switch off" a cache.
Developer information
This chapter is aimed at extension authors who want to use the caching
framework for their needs. It is about how to use the framework properly. For
details about its inner working, please refer to the
section about architecture.
Example usages can be found throughout the TYPO3 Core, in particular in the
system extensions core and extbase.
Cache registration
Registration of a new cache should be done in an extension's
ext_localconf.php. The example below defines an empty sub-array in
cacheConfigurations. Neither frontend nor backend are defined: The cache
manager will choose the default
variable frontend and the
database backend by default.
The null coalescing assignment operator (
??=) check is used to
enable administrators to overwrite configuration of caches in
config/system/settings.php. During
bootstrap, any ext_localconf.php is loaded
afterconfig/system/settings.php and
config/system/additional.php are loaded, so it is important to make
sure that the administrator did not already set any configuration of the
extension's cache.
If special settings are needed, for example, a specific backend (like the
transient memory backend), it can be defined
with an additional line below the cache array declaration. The extension
documentation should hint an integrator about specific caching needs or setups
in this case.
Tip
Extensions should not force specific settings, therefore the null coalescing
assignment operator (
??=) is used to allow administrators to
overwrite those settings. It is recommended to set up a cache configuration
with sane defaults, but administrators should always be able to overwrite
them for whatever reason.
services:# Place here the default dependency injection configurationcache.myext_mycache:class:TYPO3\CMS\Core\Cache\Frontend\FrontendInterfacefactory:['@TYPO3\CMS\Core\Cache\CacheManager','getCache']arguments:['myext_mycache']
The name of the service for the injection configuration is
cache.myext_mycache, the name of the cache is myext_mycache (as
defined in
ext_localconf.php). Both can be anything you like, just make
sure they are unique and clearly hint at the purpose of your cache.
Here is some example code which retrieves the cache via dependency injection:
EXT:my_extension/Classes/MyClass.php
<?phpnamespaceMyVendor\MyExtension;
useTYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
finalclassMyClass{
publicfunction__construct(
private readonly FrontendInterface $cache,
){}
//...privatefunctiongetCachedValue(string $cacheIdentifier, array $tags, int|null $lifetime): array{
// If value is false, it has not been cached
$value = $this->cache->get($cacheIdentifier);
if ($value === false) {
// Store the data in cache
$value = $this->calculateData();
$this->cache->set($cacheIdentifier, $value, $tags, $lifetime);
}
return $value;
}
privatefunctioncalculateData(): array{
$data = [];
// todo: implementreturn $data;
}
}
Copied!
Tip
It is not needed to call
$this->cache->has() before accessing cache
entries with
$this->cache->get() as the latter returns
false
if no entry exists.
Since the auto-wiring feature of the
dependency injection container cannot detect which cache configuration should be
used for the
$cache argument of
MyClass, the container
service configuration needs to
be extended:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configuration# and the configuration of the cache from aboveMyVendor\MyExtension\MyClass:arguments:$cache:'@cache.myext_mycache'
TYPO3 provides a generic categorization system.
Categories can be created in the backend like any other type of
record.
A TCA field of the column type category is
available.
Pages, content elements and files contain category fields by default.
Changed in version 11.4
Starting with v11.4 the formerly used PHP function
ExtensionManagementUtility::makeCategorizable() is deprecated and
removed with v12.0. Use a TCA field of the type
category instead.
Using Categories
Managing Categories
System categories are defined like any other record. Each category
can have a parent, making a tree-like structure.
A category with a parent defined
The Items tab shows all related records, for example all records
that have been marked as belonging to this category.
Adding categories to a table
Categories can be added to a table by defining a TCA field of the TCA column
type category. While using this type, TYPO3
takes care of generating the necessary TCA configuration and also adds
the database column automatically. Developers only have to configure the
TCA column and add it to the desired record types:
The newly added field to define relations to categories
Using categories in FlexForms
It is possible to create relations to categories also in
FlexForms.
Due to some limitations in FlexForm, the property
relationshipmanyToMany is not supported. Therefore, the default value for this property
is oneToMany.
The
\TYPO3\CMS\Core\Category\Collection\CategoryCollection
class provides the API for retrieving records related
to a given category. It is extended by class
\TYPO3\CMS\Frontend\Category\Collection\CategoryCollection
which does the same job but in the frontend, i.e.
respecting all enable fields and performing version
and language overlays.
The main method is
load() which will return a
traversable list of items related to the given category.
Here is an example usage, taken from the RECORDS content object:
$collection = \TYPO3\CMS\Frontend\Category\Collection\CategoryCollection::load(
$aCategory,
true,
$table,
$relationField
);
if ($collection->count() > 0) {
// Add items to the collection of records for the current tableforeach ($collection as $item) {
$tableRecords[$item['uid']] = $item;
// Keep track of all categories a given item belongs toif (!isset($categoriesPerRecord[$item['uid']])) {
$categoriesPerRecord[$item['uid']] = [];
}
$categoriesPerRecord[$item['uid']][] = $aCategory;
}
}
Copied!
As all collection classes in the TYPO3 Core implement the
Iterator interface, it is also possible to use expected methods like
next(),
rewind(), etc. Note that methods such as
add() will only add items to the collection temporarily.
The relations are not persisted in the database.
Usage with TypoScript
In the frontend, it is possible to get collections of
categorized records loaded into a RECORDS content object
for rendering. Check out the
categories property.
In most aspects, system categories are treated like any other record. They can
be viewed or edited by editors if they are stored in a folder where the editor
has access to and if the table
sys_category is allowed in the field
Tables (listing) and Tables (modify) in the tab
Access Lists of the user group.
Additionally it is possible to set Mounts and Workspaces >
Category Mounts in the user group. If at
least one category is set in the category mounts only the chosen categories are
allowed to be attached to records.
Console commands (CLI)
It is possible to run TYPO3 scripts from the command line.
This functionality can be used to set up cron jobs, for example.
TYPO3 uses Symfony commands API for writing CLI (command line interface) commands.
These commands can also be run from the TYPO3
scheduler if this option is not
disabled in the Configuration/Services.yaml.
The starting point for the commands differs depending on the type of your
TYPO3 installation.
For installations with Composer, the starting point is the project
folder, which also contains the composer.json file of the project.
The CLI commands usually start with vendor/bin/typo3.
For Classic mode installations (without Composer), the starting point is usually
the web root, so CLI commands start with typo3/sysext/core/bin/typo3.
By default, it is possible to run a command from the TYPO3 scheduler as well. To do this, select the task Execute console commands
followed by your command in the Schedulable Command field.
Note
You need to save and reopen the task to define command arguments.
In order to prevent commands from being set up as scheduler tasks,
see 1. Register the command.
The extension EXT:typo3_console ships many commands to execute TYPO3
actions, which otherwise would only be accessible via the TYPO3 backend.
This page assumes that the code is run on a Composer based installation with
default binaries location. Here you can read how to run them in general and
on Classic mode installations:
Run a command from the command line.
The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given
Options
--debug
--debug
Tail the completion debug log
Value
None allowed
Default value
false
Help
The completion command dumps the shell completion script required
to use shell autocompletion (currently, bash, fish, zsh completion are supported).
Static installation
Dump the script to a global completion file and restart your shell:
completion | sudo tee /etc/bash_completion.d/typo3
Copied!
Or dump the script to a local file and source it:
completion > completion.sh
# source the file whenever you use the project
source completion.sh
# or add this line at the end of your "~/.bashrc" file:
source /path/to/completion.sh
Copied!
Dynamic installation
Add this to the end of your shell configuration file (e.g. "~/.bashrc"):
Variable TYPO3_DB_PASSWORD (option --password) can be used to provide a
password for the database and TYPO3_SETUP_ADMIN_PASSWORD
(option --admin-user-password) for the admin user password.
Using this can be a security risk since the password may end up in shell
history files. Prefer the interactive mode. Additionally, writing a command
to shell history can be suppressed by prefixing the command with a space
when using bash or zsh.
Variable "TYPO3_BE_USER_PASSWORD" and options "-p" or "--password" can be
used to provide a password. Using this can be a security risk since the password
may end up in shell history files. Prefer the interactive mode. Additionally,
writing a command to shell history can be suppressed by prefixing the command
with a space when using bash or zsh.
Setting start page in page tree. Default is the page tree root, 0 (zero)
Value
Required
--depth
--depth / -d
Setting traversal depth. 0 (zero) will only analyze start page (see --pid), 1 will traverse one level of subpages etc.
Value
Required
--dry-run
--dry-run
If this option is set, the records will not be updated, but only show the output which records would have been updated.
Value
None allowed
Default value
false
Help
Traverse page tree and find and clean/update records with dirty FlexForm values. If you want to get more detailed information, use the --verbose option.
If this option is set, the references will not be removed, but just the output which references would be deleted are shown
Value
None allowed
Default value
false
--update-refindex
--update-refindex
Setting this option automatically updates the reference index and does not ask on command line. Alternatively, use -n to avoid the interactive mode
Value
None allowed
Default value
false
Help
Assumptions:
- a perfect integrity of the reference index table (always update the reference index table before using this tool!)
- all database references to check are integers greater than zero
- does not check if a referenced record is inside an offline branch, another workspace etc. which could make the reference useless in reality or otherwise question integrity
Records may be missing for these reasons (except software bugs):
- someone deleted the record which is technically not an error although it might be a mistake that someone did so.
- after flushing published versions and/or deleted-flagged records a number of new missing references might appear; those were pointing to records just flushed.
An automatic repair is only possible for managed references are (not for soft references), for
offline versions records and non-existing records. If you just want to list them, use the --dry-run option.
The references in this case are removed.
If the option "--dry-run" is not set, all managed files (TCA/FlexForm attachments) will silently remove the references
to non-existing and offline version records.
All soft references with relations to non-existing records, offline versions and deleted records
require manual fix if you consider it an error.
Manual repair suggestions:
- For soft references you should investigate each case and edit the content accordingly.
- References to deleted records can theoretically be removed since a deleted record cannot be selected and hence
your website should not be affected by removal of the reference. On the other hand it does not hurt to ignore it
for now. To have this automatically fixed you must first flush the deleted records after which remaining
references will appear as pointing to Non Existing Records and can now be removed with the automatic fix.
If you want to get more detailed information, use the --verbose option.
Find and delete records that have lost their connection with the page tree.
Usage
cleanup:orphanrecords [--dry-run]
Copied!
Options
--dry-run
--dry-run
If this option is set, the records will not actually be deleted, but just the output which records would be deleted are shown
Value
None allowed
Default value
false
Help
Assumption: All actively used records on the website from TCA configured tables are located in the page tree exclusively.
All records managed by TYPO3 via the TCA array configuration has to belong to a page in the page tree, either directly or indirectly as a version of another record.
VERY TIME, CPU and MEMORY intensive operation since the full page tree is looked up!
Automatic Repair of Errors:
- Silently deleting the orphaned records. In theory they should not be used anywhere in the system, but there could be references. See below for more details on this matter.
Manual repair suggestions:
- Possibly re-connect orphaned records to page tree by setting their "pid" field to a valid page id. A lookup in the sys_refindex table can reveal if there are references to an orphaned record. If there are such references (from records that are not themselves orphans) you might consider to re-connect the record to the page tree, otherwise it should be safe to delete it.
If you want to get more detailed information, use the --verbose option.
Clean up expired preview links from shared workspace previews.
Usage
cleanup:previewlinks
Copied!
Help
Look for preview links within the database table "sys_preview" that have been expired and and remove them. This command should be called regularly when working with workspaces.
Setting start page in page tree. Default is the page tree root, 0 (zero)
Value
Required
--depth
--depth / -d
Setting traversal depth. 0 (zero) will only analyze start page (see --pid), 1 will traverse one level of subpages etc.
Value
Required
--dry-run
--dry-run
If this option is set, the records will not actually be deleted/modified, but just the output which records would be touched are shown
Value
None allowed
Default value
false
--action
--action
Specify which action should be taken. Set it to "versions_in_live", "published_versions" or "invalid_workspace"
Value
Optional
Help
Traverse page tree and find versioned records. Also list all versioned records, additionally with some inconsistencies in the database, which can cleaned up with the "action" option. If you want to get more detailed information, use the --verbose option.
If set, existing records with the same UID will be updated instead of inserted.
Value
None allowed
Default value
false
--ignore-pid
--ignore-pid
If set, page IDs of updated records are not corrected (only works in conjunction with --update-records).
Value
None allowed
Default value
false
--force-uid
--force-uid
If set, UIDs from file will be forced.
Value
None allowed
Default value
false
--import-mode
--import-mode
Set the import mode of this specific record. Pattern is "{table}:{record}={mode}". Available modes for new records are "force_uid" and "exclude" and for existing records "as_new", "ignore_pid", "respect_pid" and "exclude". Examples are "pages:987=force_uid", "tt_content:1=as_new", etc.
Value
Optional (multiple)
Default value
[]
--enable-log
--enable-log
If set, all database actions are logged.
Value
None allowed
Default value
false
Help
Imports a T3D / XML file with content into a page tree
Run upgrade wizard. Without arguments all available wizards will be run.
Usage
upgrade:run [<wizardName>]
Copied!
Arguments
wizardName
wizardName
Help
This command allows running upgrade wizards on CLI. To run a single wizard add the identifier of the wizard as argument. The identifier of the wizard is the name it is registered with in ext_localconf.
Some workspaces can have an auto-publish publication date to put all "ready to publish" content online on a certain date.
Tutorial
Create a console command from scratch
A console command is always situated in an extension. If you want to create
one, kickstart a custom extension or use your
sitepackage extension.
Creating a basic command
The extension kickstarter "Make" offers a convenient
console command that creates a new command in an extension of your choice:
Create a new console command with "Make".
You can use "Make" to create a console command even if your extension was
created by different means.
The Symfony PHP attribute
\Symfony\Component\Console\Attribute\AsCommand
is now accepted to register console commands.
See the section Use the PHP attribute to register commands
for more details.
Register the command in Configuration/Services.yaml by adding the service
definition for your class as tag
console.command:
EXT:examples/Configuration/Services.yaml
services:_defaults:autowire:trueautoconfigure:truepublic:falseT3docs\Examples\:resource:'../Classes/*'exclude:'../Classes/Domain/Model/*'T3docs\Examples\Command\DoSomethingCommand:tags:-name:console.commandcommand:'examples:dosomething'description:'A command that does nothing and always succeeds.'# Also an alias for the command can be configured-name:console.commandcommand:'examples:dosomethingalias'alias:true
Copied!
The following attributes are available:
command
The name under which the command is available.
description
Give a short description. It will be displayed in the list of commands and
the help information of the command.
schedulable
By default, a command can be used in the scheduler, too. This can be disabled by setting
schedulable to
false.
hidden
A command can be hidden from the command list by setting
hidden to
true.
alias
A command can be made available under a different name. Set to
true,
if your command name is an alias.
Note
Despite using autoconfigure: true the commands
have to be explicitly defined in Configuration/Services.yaml. It
is recommended to always supply a description, otherwise there is
an empty space in the list of commands.
2. Create the command class
Create a class called
DoSomethingCommand extending
\Symfony\Component\Console\Command\Command.
<?phpdeclare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project. [...]
*/namespaceT3docs\Examples\Command;
useSymfony\Component\Console\Command\Command;
useSymfony\Component\Console\Input\InputInterface;
useSymfony\Component\Console\Output\OutputInterface;
classDoSomethingCommandextendsCommand{
protectedfunctionconfigure(): void{
$this->setHelp('This command does nothing. It always succeeds.');
}
protectedfunctionexecute(InputInterface $input, OutputInterface $output): int{
// Do awesome stuffreturn Command::SUCCESS;
}
}
Copied!
The following two methods should be overridden by your class:
configure()
As the name would suggest, allows to configure the command.
The method allows to add a help text and/or define arguments and options.
execute()
Contains the logic when executing the command. Must
return an integer. It is considered best practice to
return the constants
Command::SUCCESS or
Command::FAILURE.
The command will return without a message as it does nothing but stating it
succeeded.
Note
If a newly created or changed command is not found, clear the cache:
vendor/bin/typo3 cache:flush
Copied!
Use the PHP attribute to register commands
New in version 12.4.8
CLI commands can be registered by setting the attribute
\Symfony\Component\Console\Attribute\AsCommand on the command class.
When using this attribute there is no need to register the command in the
Services.yaml file.
Note
Only the parameters command, description, aliases and hidden are
available. In order to overwrite the parameter schedulable use the
registration via
Services.yaml.
By default, schedulable is true.
<?phpdeclare(strict_types=1);
namespaceT3docs\Examples\Command;
useSymfony\Component\Console\Attribute\AsCommand;
useSymfony\Component\Console\Command\Command;
useSymfony\Component\Console\Input\InputInterface;
useSymfony\Component\Console\Output\OutputInterface;
#[AsCommand(
name: 'examples:dosomething',
description: 'A command that does nothing and always succeeds.',
aliases: ['examples:dosomethingalias'],
)]
classDoSomethingCommandextendsCommand{
protectedfunctionconfigure(): void{
$this->setHelp('This command does nothing. It always succeeds.');
}
protectedfunctionexecute(InputInterface $input, OutputInterface $output): int{
// Do awesome stuffreturn Command::SUCCESS;
}
}
Copied!
Create a command with arguments and interaction
Passing arguments
Since a command extends
\Symfony\Component\Console\Command\Command,
it is possible to define arguments (ordered) and options (unordered) using the Symfony
command API. This is explained in depth on the following Symfony Documentation page:
Add an optional argument and an optional option to your command:
Class T3docs\Examples\Command\CreateWizardCommand
useSymfony\Component\Console\Input\InputArgument;
useSymfony\Component\Console\Input\InputOption;
finalclassCreateWizardCommandextendsCommand{
protectedfunctionconfigure(): void{
$this
->setHelp('This command accepts arguments')
->addArgument(
'wizardName',
InputArgument::OPTIONAL,
'The wizard\'s name'
)
->addOption(
'brute-force',
'b',
InputOption::VALUE_NONE,
'Allow the "Wizard of Oz". You can use --brute-force or -b when running command'
);
}
}
Copied!
This command takes one optional argument
wizardName and one optional option,
which can be passed on the command line:
useTYPO3\CMS\Core\Core\Bootstrap;
useSymfony\Component\Console\Input\InputInterface;
useSymfony\Component\Console\Output\OutputInterface;
finalclassDoBackendRelatedThingsCommandextendsCommand{
protectedfunctionexecute(InputInterface $input, OutputInterface $output): int{
Bootstrap::initializeBackendAuthentication();// Do backend related stuffreturn Command::SUCCESS;
}
}
Copied!
This is necessary when using DataHandler
or other backend permission handling related tasks.
Simulating a Frontend Request in TYPO3 Commands
When executing TYPO3 commands in the CLI, there is no actual frontend (web)
request. This means that several request attributes required for link generation
via Fluid or TypoScript are missing by default. While setting the site
attribute in the request is a first step, it does not fully replicate the
frontend behavior.
The Challenge
In a web request, TYPO3 automatically provides various objects that influence
link generation:
ContentObjectRenderer (cObj): Processes TypoScript-based rendering,
including link generation.
page Attribute: Holds the current page context.
PageInformation Object: Provides additional metadata about the current
page.
Router: Ensures proper URL resolution.
FrontendTypoScriptFactory (was part of TSFE at the time): Collects
TypoScript and provides settings like linkAccessRestrictedPages and
typolinkLinkAccessRestrictedPages.
One critical limitation is that the ContentObjectRenderer (cObj) is only
available when a TypoScript-based content element, such as FLUIDTEMPLATE, is
rendered. Even if cObj is manually instantiated in a CLI command, its data
array remains empty, meaning it lacks the context of a real tt_content record.
As a result, TypoScript properties like field = my_field or data = my_data
will not work as expected.
Similarly, the FrontendTypoScriptFactory is not automatically
available in CLI. If CLI-generated links should respect settings like
linkAccessRestrictedPages, it would have to be manually instantiated and
configured.
A Minimal Request Example
In some cases, a minimal request configuration may be sufficient, such as when
generating simple links or using FluidEmail. The following example demonstrates
how to set up a basic CLI request with applicationType and site attributes:
<?phpdeclare(strict_types=1);
namespaceT3docs\Examples\Command;
useSymfony\Component\Console\Attribute\AsCommand;
useSymfony\Component\Console\Command\Command;
useSymfony\Component\Console\Input\InputInterface;
useSymfony\Component\Console\Output\OutputInterface;
useTYPO3\CMS\Core\Core\Bootstrap;
useTYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
useTYPO3\CMS\Core\Http\ServerRequest;
useTYPO3\CMS\Core\Mail\FluidEmail;
useTYPO3\CMS\Core\Mail\MailerInterface;
useTYPO3\CMS\Core\Site\SiteFinder;
#[AsCommand(
name: 'examples:dosomething',
description: 'A command that does nothing and always succeeds.',
aliases: ['examples:dosomethingalias'],
)]
classDoSomethingCommandextendsCommand{
publicfunction__construct(
private readonly SiteFinder $siteFinder,
private readonly MailerInterface $mailer,
){
parent::__construct();
}
protectedfunctionexecute(InputInterface $input, OutputInterface $output): int{
Bootstrap::initializeBackendAuthentication();
// The site has to have a fully qualified domain name
$site = $this->siteFinder->getSiteByPageId(1);
$request = (new ServerRequest())
->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE)
->withAttribute('site', $site);
$GLOBALS['TYPO3_REQUEST'] = $request;
// Send some mails with FluidEmail
$email = new FluidEmail();
$email->setRequest($request);
// Set receiver etc$this->mailer->send($email);
return Command::SUCCESS;
}
}
Copied!
Note
It is important to understand that there is no simple way to fully simulate
a frontend request in CLI. Some aspects, like basic link generation, can
work by manually setting request attributes. However, complex
TypoScript-based link modifications, access restrictions, and context-aware
rendering will not behave identically to a real web request. Developers
need to be aware of these limitations when working with link generation in
CLI commands.
More information
See implementation of existing command controllers in the Core:
typo3/sysext/*/Classes/Command
This chapter handles content elements & plugins: What they are, how they can be
created, how existing content elements or plugins can be customized etc.
In TYPO3, Content elements and plugins are both stored as Database records
in table
tt_content. They are usually edited in the backend in module
Web > Page.
Content elements and plugins are both used to present and manage
content on a website, but they serve different purposes and have distinct
characteristics:
A content element is a standard unit for managing and displaying content,
such as text, images, videos, tables, and more.
TYPO3 provides a variety of built-in content elements. It is possible
to define custom content elements.
A plugin in TYPO3 is more complex, typically providing dynamic
or interactive functionality. Plugins are usually provided by extensions
that introduce new features to the website.
The data to be displayed is usually supplied by a special PHP class
called a "controller". Depending on the technology used in the controller
the plugin can be an Extbase plugin or a plain plugin.
Content elements in TYPO3
A content element is a standard unit for managing and displaying content,
such as text, images, videos, tables, and more.
In the TYPO3 backend, content elements are commonly managed in module
Web > Page.
From a technical point of view content elements are records stored in the
database table tt_content. Each content
element has a specific content element type, specified by the database field
tt_content.CType. This type influences both the backend form and the frontend
output.
The appearance of a content element in the backend form is defined via the
TYPO3 Configuration Array (TCA) of table tt_content.
Each content element type is configured by one entry in the section
$TCA['types'].
The output of the content element in the frontend is configured by an entry in
the TypoScript top-level object tt_content using the
same key as in TCA. In most cases a FLUIDTEMPLATE
is used delegating the actual output to the templating engine
Fluid.
A content element can be of a type
supplied by TYPO3, such as textmedia (text with or without images or videos).
Or it can have a custom type supplied by an extension such as carousel
provided by the
bk2k/bootstrap-package
extension.
Adding custom content elements is
possible without writing PHP code and can therefore also be done by
TYPO3 integrators.
Plugins in TYPO3
A plugin in TYPO3 is a more complex implementation, typically providing dynamic
or interactive functionality. Plugins are usually provided by extensions
that introduce new features to the website.
The data to be displayed is usually supplied by a special PHP class
called a "controller". Depending on the technology used in the controller
the plugin can be an Extbase plugin or a plain plugin.
Extbase plugins
For usage in the TYPO3 backend Extbase plugins are registered with utility
functions of class
\TYPO3\CMS\Extbase\Utility\ExtensionUtility (not to
be confused with
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility ).
By using ExtensionUtility::PLUGIN_TYPE_PLUGIN as fifth parameter is is also
possible to add the plugin as a list type. See CType vs list_type plugins.
Method
ExtensionUtility::configurePlugin() also takes care of registering
the plugin for frontend output in TypoScript using an object of type
EXTBASEPLUGIN.
If it is desired that editors can insert the Extbase plugin like a content
element into the page it also needs to be registered with
ExtensionUtility::registerPlugin() in the TCA Overrides, for example file
EXT:my_extension/Configuration/TCA/Overrides/tt_content.php:
It is possible to create a plugin without using Extbase by creating a plain PHP
class as a controller.
In this case you have to define the TypoScript configuration yourself. A
USER or USER_INT TypoScript object can be used
to delegate the rendering to your controller:
By using 'list_type' as second parameter is is also possible to add the plugin
as a list type. See CType vs list_type plugins.
Plugins are a specific type of content elements. Plugins use the CType='list'.
Each plugin has its own plugin type, which is used in the database field
tt_content.list_type. The list_type could be understood as subtype of CType.
Typical characteristics of plugins
Plugins often use additional database tables which contain records which are
dynamically displayed via the plugin - often in a list view, a single view,
optionally with pagination and search functionality. An extension may provide
several plugins, each with a dedicated function, such as the list view.
Plugins are often used if more complex functionality is required (than in non-
plugin content elements)
Plugins can be created using the Extbase framework or by Core functionality.
A typical extension with plugins is the
georgringer/news
extension
which comes with plugins to display news records in lists or as a single view
with only one news record.
The news records are stored in a custom database table (tx_news_domain_model_news)
and can be edited in the backend.
There are also system extensions that have plugins.
typo3/cms-felogin
has a plugin that allow frontend users, stored in table fe_users to log into
the website.
typo3/cms-indexed-search
has a plugin that can be
used to search in the index and display search results.
CType vs list_type plugins
Historically it was common to add plugins as a list type to the content element
types. In this case the column CType is set to 'list' for all plugins while
the field list_type contains the key of the actual plugin.
As different plugins need different fields in the backend form this let to
the creation of all type of complicated TCA constructs to influence the
behaviour of backend forms for plugins.
The existence of the list_type also made a separate layer of content element
definitions in the TypoScript necessary.
Therefore the list_type complicates registration and configuration of plugins
while it poses no advantages. Therefore it is recommended to always use the
CType for new plugin types while the list_type is retained for now for
backward compatibility.
If you are refactoring the plugins of your extension, for example while getting
rid of switchable controller actions it is recommended to migrate your plugins
to use the CType. You should then supply a
upgrade wizard
for easy migration for your users.
Backend Layouts can be configured to define how content elements
are arranged in the TYPO3 backend (in rows, columns, grids). This can be used in
the frontend to determine how the content elements are to be arranged (e.g. in
the footer of the page, left column etc.).
Often content elements and plugins contain a number of fields. Not all of these may
be relevant for your site. It is good practice to configure which fields will be
displayed in the backend. There are a number of ways to do this:
This page explains how to create your own custom content element types. These
are comparable to the predefined content element types supplied by TYPO3. The latter
can be found in the system extension fluid_styled_content.
A content element can be based on fields already available in the tt_content
table.
It is also possible to add extra fields to the tt_content
table, see Extending tt_content.
The data of the content element is then passed to a TypoScript object, in most
cases to a FLUIDTEMPLATE.
<?phpdeclare(strict_types=1);
defined('TYPO3') ordie();
// Adds the content element to the "Type" dropdown
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem(
'tt_content',
'CType',
[
// title'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myextension_newcontentelement_title',
// plugin signature: extkey_identifier'value' => 'myextension_newcontentelement',
// icon identifier'icon' => 'content-text',
// group'group' => 'default',
// description'description' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myextension_newcontentelement_description',
],
'textmedia',
'after',
);
// Adds the content element icon to TCA typeicon_classes
$GLOBALS['TCA']['tt_content']['ctrl']['typeicon_classes']['myextension_newcontentelement'] = 'content-text';
// ...
Copied!
Now the new content element is available in the backend form. However it
currently contains no fields but the CType field.
CType dropdown in tt_content
About the icon
You can either use an existing icon from the TYPO3 core or register your
own icon using the Icon API. In this example we use the icon
content-text, the same icon as the Regular Text Element uses.
Add it to the new content element wizard
Content elements in the New Content Element Wizard are easier
to find for editors. It is therefore advised to add the new content element
to this wizard (via page TSconfig).
EXT:my_extension/Configuration/page.tsconfig
mod.wizards.newContentElement.wizardItems {
// add the content element to the tab "common"
common {
elements {
myextension_newcontentelement {
iconIdentifier = content-text
title = LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myextension_newcontentelement_title
description = LLL:EXT:my_extension/Resources/Private/Language/locallang.xlf:myextension_newcontentelement_description
tt_content_defValues {
CType = myextension_newcontentelement
}
}
}
show := addToList(myextension_newcontentelement)
}
}
The
lib.contentElement path is defined in file
EXT:fluid_styled_content/Configuration/TypoScript/Helper/ContentElement.typoscript.
and uses a FLUIDTEMPLATE.
We reference fluid_styled_content
lib.contentElement from our new content element and only change
the Fluid template to be used.
The Fluid template is configured by the
Properties property as
NewContentElement.
This will load a NewContentElement.html template file from the path
defined at the
templateRootPaths.
In the example extension you can find the file at
EXT:examples/Resources/Private/Templates/NewContentElement.html
tt_content fields can now be used in the Fluid template by accessing them via the data variable.
The following example shows the text entered in the richtext enabled field bodytext, using the html
saved by the richtext editor:
<htmldata-namespace-typo3-fluid="true"xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"><h2>Data available to the content element: </h2><f:debuginline="true">{_all}</f:debug><h2>Output</h2><div><f:format.html>{data.bodytext}</f:format.html></div></html>
Copied!
All fields of the table
tt_content are now available in the variable
data. Since we saved the content of bodytext in the richt text editor we
have to run it through f:format.html to resolve all links and other
formatting. Read more about Fluid.
Tip
During development you can output all available variables in a Fluid
template by adding
<f:debug>{_all}</f:debug>.
Even more convenient:
<f:if condition="{condition}"><f:debug>{_all}</f:debug></f:if>
lets you easily turn debugging on or off, depending on whether you
fill in "1" or "0" for condition.
Below you can see the example output of the new content element and a
dump of all available data:
Extended example: Extend tt_content and use data processing
You can find the complete example in the TYPO3 Documentation Team extension
EXT:examples. The steps for
creating a simple new content element as above need to be repeated. We use the
key examples_newcontentcsv in this example.
We want to output comma separated values (CSV) stored in the field bodytext.
As different programs use different separators to store CSV we want to make
the separator configurable.
Extending tt_content
If the available fields in the table tt_content are not sufficient you can add
your own fields. In this case we need a field
tx_examples_separator from
which to choose the desired separator.
Extending the database schema
First we extend the database schema by adding the following to the file
ext_tables.sql:
Do a database compare in the Admin Tools > Maintenance module
after changing the database schema (system maintainers only). Or call the
console command:
You can read more about defining fields via TCA in the TCA Reference.
Now the new field can be used in your Fluid template just like any other
tt_content field.
Another example shows the connection to a foreign table. This allows you to be
more flexible with the possible values in the select box. The new field
myextension_reference is a reference to another table of the extension
called
tx_myextension_mytable:
An individual modification of the newly added field
myextension_reference
to the TCA definition of the table
tt_content can be done in the
TYPO3 Core Engine (TCE) page TSconfig. In most cases it is necessary to set the page
id of the general storage folder. Then the examples extension will only use
the content records from the given page id.
If more than one page id is allowed, this configuration must be used instead
(and the above TCA must be modified to use the marker ###PAGE_TSCONFIG_IDLIST###
instead of ###PAGE_TSCONFIG_ID###):
<htmldata-namespace-typo3-fluid="true"xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"><h2>Data in variable "myTable"</h2><f:debuginline="true">{myTable}</f:debug><h2>Output, {data.imagecols} columns separated by char {data.tx_examples_separator}</h2><tableclass="table table-hover"><f:foreach="{myTable}"as="columns"iteration="i"><tr><thscope="row">{i.cycle}</th><f:foras="column"each="{columns}"><td>{column}</td></f:for><tr></f:for></table></html>
Copied!
The output would look like this (we added a debug of the variable myTable):
Custom data processors
When there is no suitable data processor that
prepares the variables needed for your content element or template, you can
define a custom data processor by implementing
\TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface .
You can find the example below in the TYPO3 Documentation Team extension
EXT:examples.
Using a custom data processor in TypoScript
The data processor can be configured through a TypoScript setup configuration. A
custom data processor can be used in the definition of a "new custom content
element" as follows:
tt_content {
examples_dataproccustom =< lib.contentElement
examples_dataproccustom {
templateName = DataProcCustom
# Before TYPO3 v12.1 you have to give the fully-qualified class name of the processor# dataProcessing.10 = T3docs\Examples\DataProcessing\CustomCategoryProcessor# Since TYPO3 v12.1 one can also use a (in Services.yaml) configured alias
dataProcessing.10 = custom-category
dataProcessing.10 {
as = categories
categoryList.field = categories
}
}
}
Copied!
In the extension examples you can find the code in
EXT:examples/Configuration/TypoScript/DataProcessors/Processors/CustomCategoryProcessor.typoscript.
In the field
categories the comma-separated categories are stored.
Note
The custom data processor described here should serve as a simple example.
It can therefore only work with comma-separated values, not with an m:n
relationship as used in the field
categories of tables like
tt_content. For that, further logic would need to be implemented.
Register an alias for the data processor (optional)
New in version 12.1
Instead of using the fully-qualified class name as data processor identifier
(in the example above
\T3docs\Examples\DataProcessing\CustomCategoryProcessor)
you can also define a short alias in Configuration/Services.yaml:
The alias
custom-category can now be used as data processor identifier
like in the TypoScript example above.
Note
When registering a data processor alias please be sure you don't override
an existing alias (from TYPO3 Core or a third-party extension) as this may
cause errors.
Tip
It is recommended to tag custom data processors as this will
automatically add them to the internal
DataProcessorRegistry,
enabling dependency injection by default.
Otherwise, the service would need to be set public.
Note
If your data processor should not be shared then
you need to set the
shared: false tag attribute for the service.
Implementing the custom data processor
The custom data processor must implement
\TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface .
The main method
process() gets called with the following parameters:
ContentObjectRenderer $cObj
Receives the data of the current TypoScript context, in this case the
data of the calling content element.
array $contentObjectConfiguration
Contains the configuration of the calling content element. In this example it is
the configuration
tt_content.examples_dataproccustom
array $processorConfiguration
Contains the configuration of the currently called data processor. In this
example it is the value of
as and the
stdWrap
configuration of the
categoryList
array $processedData
On calling, contains the processed data of all previously called data
processors on this same content element. Your custom data processor also stores
the variables to be sent to the Fluid template here.
This is an example implementation of a custom data processor:
<?phpdeclare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project. [...]
*/namespaceT3docs\Examples\DataProcessing;
useT3docs\Examples\Domain\Repository\CategoryRepository;
useTYPO3\CMS\Core\Utility\GeneralUtility;
useTYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
useTYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/**
* Class for data processing comma separated categories
*/classCustomCategoryProcessorimplementsDataProcessorInterface{
/**
* Process data for the content element "My new content element"
*
* @param ContentObjectRenderer $cObj The data of the content element or page
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
* @return array the processed data as key/value store
*/publicfunctionprocess(
ContentObjectRenderer $cObj,
array $contentObjectConfiguration,
array $processorConfiguration,
array $processedData
){
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
return $processedData;
}
// categories by comma separated list
$categoryIdList = $cObj->stdWrapValue('categoryList', $processorConfiguration ?? []);
$categories = [];
if ($categoryIdList) {
$categoryIdList = GeneralUtility::intExplode(',', (string)$categoryIdList, true);
/** @var CategoryRepository $categoryRepository */
$categoryRepository = GeneralUtility::makeInstance(CategoryRepository::class);
foreach ($categoryIdList as $categoryId) {
$categories[] = $categoryRepository->findByUid($categoryId);
}
// set the categories into a variable, default "categories"
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'categories');
$processedData[$targetVariableName] = $categories;
}
return $processedData;
}
}
Copied!
In the extension examples you can find the code in
EXT:/examples/Classes/DataProcessing/CustomCategoryProcessor.php.
On being called, the
CustomCategoryProcessor runs
stdWrap
on the calling ContentObjectRenderer, which has the data of the table
tt_content in the calling content element.
The field
categoryList gets configured in TypoScript as follows:
categoryList.field = categories
Copied!
stdWrap fetches the value of
categoryList from
tt_content.tx_examples_main_category of the currently calling content
element.
Now the custom data processor processes the comma-separated values into an array
of integers that represent uids of the table
sys_category. It then
fetches the category data from the
CategoryRepository by calling
findByUid.
The data of the category records then get stored in the desired key in the
$processedData array.
To make the data processor more configurable, we test for a TypoScript
if condition at the beginning, and name the key
we use to store the data configurable by the configuration
as.
Create plugins
How to create plugins with the Extbase framework and Fluid templating engine
is handled in depth in the chapter
Registration of frontend plugins.
There are basically two ways to create frontend plugins in TYPO3:
Create a plugin using Core functionality (without Extbase) and a custom controller
Generally speaking, if you already use Extbase, it is good practice to
create your plugins using the Extbase framework. This also involves:
creating controller actions
create a domain model and repository (if your plugin requires records
that are persisted in the database)
create a view using Fluid templates
Configure custom backend preview for content element
To allow editors a smoother experience, all custom content elements and plugins
should be configured with a corresponding backend preview that shows an
approximation of the element's appearance in the TYPO3 page module. The
following sections describe how to achieve that.
A preview renderer is used to facilitate (record) previews in TYPO3. This
class is responsible for generating the preview and the wrapping.
The default preview renderer is
\TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer
and handles the Core's built-in content types (field
CType in table
tt_content).
Extend the default preview renderer
There are two ways to provide previews for your custom content types:
via page TSconfig or event listener.
Page TSconfig
This is the "integrator" way, no PHP coding is required. Just some page TSconfig
and a Fluid template.
This requires at least some PHP coding, but allows more flexibility in
accessing and processing the content elements properties.
New in version 12.0
Since version 12.0 this technique replaces the former hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem']
The event
PageContentPreviewRenderingEvent is being dispatched by the
StandardContentPreviewRenderer. You can listen to it with your own
event listener.
Contract for classes capable of rendering previews of a given record
from a table. Responsible for rendering preview header, preview content
and wrapping of those two values.
Responsibilities are segmented into three methods, one for each responsibility,
which is done in order to allow overriding classes to change those parts
individually without having to replace other parts. Rather than relying on
implementations to be friendly and divide code into smaller pieces and
give them (at least) protected visibility, the key methods are instead required
on the interface directly.
Callers are then responsible for calling each method and combining/wrapping
the output appropriately.
Dedicated method for rendering preview header HTML for
the page module only. Receives the the GridColumnItem
that contains the record for which a preview header
should be rendered and returned.
Dedicated method for rendering preview body HTML for
the page module only. Receives the the GridColumnItem
that contains the record for which a preview should be
rendered and returned.
Dedicated method for wrapping a preview header and body
HTML. Receives $item, an instance of GridColumnItem holding
among other things the record, which can be used to determine
appropriate wrapping.
param $previewHeader
the previewHeader
param $previewContent
the previewContent
param $item
the item
Returns
string
Implementing these methods allows you to control the exact composition of the
preview.
This means assuming your preview renderer returns
<h4>Header</h4>
from the header render method and
<p>Body</p> from the preview content
rendering method and your wrapping method does
return '<div>' . $previewHeader . $previewContent . '</div>'; then the
entire output becomes
<div><h4>Header</h4><p>Body</p></div> when
combined.
Should you wish to reuse parts of the default preview rendering and only change,
for example, the method that renders the preview body content, you can extend
\TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer in your custom
preview renderer class - and selectively override the methods from the API
displayed above.
Configuring the implementation
Individual preview renderers can be defined by using one of the following
approaches:
This specifies the preview renderer only for records of type
$type as
determined by the type field of your table.
Table and field have a
subtype_value_field TCA setting
If your table and field have a
subtype_value_field TCA
setting (like
tt_content.list_type for example) and you want to
register a preview renderer that applies only when that value is selected
(assume, when a certain plugin type is selected and you can't match it with
the "type" of the record alone):
Where
$type is, for example,
list (indicating a plugin) and
$subType is the value of the
list_type field when the type of
plugin you want to target is selected as plugin type.
The content elements
text,
textpic,
textmedia and
image have their own
PreviewRenderer. Therefore it's not
sufficient to overwrite the
StandardContentPreviewRenderer but
you need to use the second approach from above for every single of
these content elements.
Add content elements to the Content Element Wizard
The content element wizard opens when a new content element is
created. It can be fully configured using ref:Page TSconfig <t3tsref:pagetsconfig>.
Our extension key is example and the name of the content element or
plugin is registration.
<?phpreturn [
// use same identifier as used in TSconfig for icon'example-registration' => [
'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
'source' => 'EXT:example/Resources/Public/Icons/example-registration.svg',
],
];
Copied!
After clearing cache, create a new content element
After clearing the cache via Admin Tools > Maintenance or the
command
vendor/bin/typo3 cache:flush you should now see the icon,
title and description you just added!
Content element wizard with the new content element
Add your plugin or content element to a different tab
The above example adds your plugin to the tab "Plugin" in the content element wizard.
You can add it to one of the other existing tabs or create a new one.
Tip
Look into the module Info > Page TSconfig for existing
configurations of
mod.wizards.newContentElement.wizardItems.
If you add it to any of the other tabs (other than plugins), you must add
the name to
show as well:
mod.wizards.newContentElement.wizardItems.common {
elements {
example_registration {
iconIdentifier = example-registration
title = Example title
description = Example description
tt_content_defValues {
CType = list
list_type = example_registration
}
}
}
show := addToList(example_registration)
}
Copied!
When you look at existing page TSconfig in the Info module, you may
notice that
show has been set to include all for the
Plugins tab:
show = *
Copied!
Create a new tab
See the bootstrap_package
for an example of creating a new tab Interactive and adding
elements to it:
mod.wizards.newContentElement.wizardItems.interactive {
elements {
accordion {
iconIdentifier = content-bootstrappackage-accordion
title = LLL:EXT:bootstrap_package/Resources/Private/Language/Backend.xlf:content_element.accordion
description = LLL:EXT:bootstrap_package/Resources/Private/Language/Backend.xlf:content_element.accordion.description
tt_content_defValues {
CType = accordion
}
}
}
show := addToList(accordion)
}
Copied!
Best practices
Following are some good practices for creating custom content element types and
plugins and for customizing content elements for usage in the backend.
Coding / structure
Use a sitepackage extension to maintain your site customization (such as
backend layouts, custom content elements etc.)
How you structure your extensions depends a little on the use case and if
they will be reused in several projects and / or made public. If you create one
extension for every custom content element, you may want to think about
whether they might be merged into one extension.
Do not use deprecated functionality. Read the Core Changelog
to check for deprecations and breaking changes between TYPO3 versions.
Some naming conventions are described in the chapter Naming conventions.
Content Security Policy (CSP) is a security standard introduced to prevent
cross-site scripting (XSS), clickjacking and other code
injection attacks resulting of malicious content being executed in the trusted
web page context.
Think of CSP in terms of an "allow/deny" list for remote contents.
See also
If you are not familiar with Content Security Policy, please read the
following resources:
CSP rules are used to describe, which external assets or functionality are allowed for
certain HTML tags (like
<script>,
<img>,
<iframe>). This allows
to restrict external resources or JavaScript execution with security in mind. When
accessing a page, these rules are sent as part of the HTTP request from the server to
the browser, and the browser will enforce these rules (and reject non-allowed content).
These rejection are always logged in the browser console. Additionally, external tools
can be configured to receive and track violations of the policy.
Content Security Policy declarations can be applied to a TYPO3 website in
frontend and backend scope with a dedicated API. This API allows for site-specific or
extension-specific configuration instead of manually setting the CSP rules with
server-side configuration through httpd.conf/nginx.conf or .htaccess files.
To delegate Content Security Policy handling to the TYPO3 frontend, at least one of
the feature flags:
enforce:inheritDefault:true# site-specific mutations could also be listed, like this:# mutations:# - mode: "append"# directive: "img-src"# sources:# - "cdn.example.com"# - "assets.example.com"# alternatively (or additionally!), reporting can be set too,# for example when testing stricter rules than above# (note the missing 'assets.example.com')# reporting:# inheritDefault: true# mutations:# - mode: "append"# directive: "img-src"# sources:# - "cdn.example.com"#
Copied!
For new installations
security.backend.enforceContentSecurityPolicy is
enabled by default.
Terminology
This document will use very specific wording that is part of the CSP W3C specification,
these terms are not "invented" by TYPO3. Since reading the W3C RFC can be very
intimidating, here are a few key concepts.
Note
Skip to the section Example scenario to see a "real-life" usage
scenario, if you can better understand from actual code examples.
Directives
CSP consists of multiple rules (or "directives"), that are part of a "policy". This
policy says, what functionality the site's output is allowed to use.
With that, several HTML tags can be controlled, like from which URLs images can be
requested from, if and from where iframes are allowed, if and from where JavaScripts
are allowed and so on. There is a long list of applicable directives, see
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives
with specific identifiers like default-src, img-src and so on.
Each directive may have several "attributes" (like the allowed URLs).
Directives may build upon each other, a bit like CSS definitions (Cascading Style Sheet)
do. However, these are more meant to modify a basic rule on an earlier level,
and called "mutations". The relation of a "child rule" to its "parent" is
also called "ancestor chain". An example:
If frame-src is not defined, it falls back to child-src, and finally falls
back to default-src. But if frame-src is defined, it is used, and
the sources from default-src are not used. In such a case, default-src listed
sources have to be repeated (when wanted) explicitly in frame-src.
Applying the policy
A final policy is compiled of all these directives, and then sent as a HTTP
response header Content-Security-Policy: ... (respectively
Content-Security-Policy-Reporty-Only).
In TYPO3, directives can be specified via PHP syntax (within Extensions) and
YAML syntax (within site configuration). Additionally, rules can be
set via the PSR-14 event PolicyMutatedEvent.
Mutations
These rules can influence each other, this is where the concept of "mutations" come in.
The "policy builder" of TYPO3 applies each configured mutation, no matter where it was
defined.
Because of this, each mutation (directive definition) needs a specific "mode" that can
instruct, how this mutation is applied: Should an existing directive be
set, inherited, appended, remove or extended to the final policy (see
Content Security Police modes).
Each directive is then applied in regard to its defined mode and can list one or more
"sources" with the values of additional parameters of a directive. Sources are
web site addresses / URLs (or just protocols), and also include some special keywords like
self/none/data:.
Nonces
There are possible exemptions to directives for specific content created on specific
pages created by TYPO3 in your frontend (or backend modules). To verify, that these
exemptions are valid in a policy, a so-called "Nonce" (a unique "number used once")
is created (details on https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce).
TYPO3 can manage these Nonces and apply them were configured.
Nonces are retrieved from
\TYPO3\CMS\Core\Security\ContentSecurityPolicy\ConsumableNonce
and will be used for any directive within the scope of a single HTTP request.
When a webpage with activated policies is shown in a client's browser, each HTML tag
violating the policy will not be interpreted by the browser.
Depending on a configuration of a possible "Report", such violations can be submitted
back to a server and be evaluated there. TYPO3 provides such an endpoint to receive
and display reports in a backend module, but also third-party servers are usable.
Policies can be declared with "dispositions", to indicate how they are handled.
"Enforce" means that a policy is in effect, and "Report" allows to only pretend
a policy is in effect, to gather knowledge about possible improvements of a
webpage's output. Both dispositions can be set independently in TYPO3.
You have one TYPO3 installation with two sites (frontend), example.com and example.org.
You have created custom backend modules for some distinct functionality.
example.com is a site where your editors fully control all frontend content, and they
want to have full flexibility of what and how to embed. You use a CDN network to deliver
your own large assets, like videos.
example.org is a community-driven site, where users can manage profiles and post chats,
and where you want to prevent exploits on your site. Some embedding to a set of allowed
web services (YouTube, Google Analytics) must be possible.
So you need to take care of security measures, and find a pragmatic way how to allow foreign
content (like YouTube, widgets, tracking codes, assets)
Specifically you want to to set these following rules, as an example.
Note
The domains listed are for demonstration only, and will not match real requirements;
for example, YouTube is already allowed by the default TYPO3 frontend CSP configuration,
which can be inherited.
Rules for example.com (editorial)
<iframe> to many services should be allowed
<img> sources to anywhere should be allowed
<script> sources to cdn.example.com and *.youtube.com and *.google.com
should be allowed
Rules for example.org (community)
<iframe> to cdn.example.com, *.youtube.com should be allowed
<img> sources to cdn.example.com and *.instagram.com should be allowed
<script> sources to cdn.example.com and *.youtube.com and *.google.com
should be allowed
Rules for the TYPO3 backend
Normal TYPO3 backend rules need to be applied, so we only want to add some
rules for custom backend modules:
<iframe> to cdn.example.com should be allowed
<img> sources to cdn.example.com should be allowed
<script> sources to cdn.example.com should be allowed
Resulting configuration example:
And this is how you would do that with a CSP YAML configuration file, one per site:
# Inherits default frontend policy mutations provided by Core and 3rd-party extensions (enabled per default)inheritDefault:truemutations:# Allow frames/iframes to TRUSTED specific locations# Avoid "protocol only" white-list like "https:" here,# because it could inject javascript easily, the most important reason# why CSP was invented was to block security issues like this.# (Note: it's "frame-src" not "iframe-src")-mode:"extend"directive:"frame-src"sources:-"https://*.example.org"-"https://*.example.com"-"https://*.instagram.com"-"https://*.vimeo.com"-"https://*.youtube.com"# Allow img src to anyhwere (HTTPS only, not HTTP)-mode:"extend"directive:"img-src"sources:-"https:"# Allow script src to the specified domains (HTTPS only)-mode:"extend"directive:"script-src"sources:-"https://cdn.example.com"-"https://*.youtube.com"-"https://*.google.com"
# Inherits default frontend policy mutations provided by Core and 3rd-party extensions (enabled per default)inheritDefault:truemutations:# Allow frame/iframe src to the specified domains (HTTPS only)-mode:"extend"# (Note: it's "frame-src" not "iframe-src")directive:"frame-src"sources:-"https://cdn.example.com"-"https://*.youtube.com"# Allow img src to the specified domains (HTTPS only)-mode:"extend"directive:"img-src"sources:-"https://cdn.example.com"-"https://*.instagram.com"# Allow script src to the specified domains (HTTPS only)-mode:"extend"directive:"script-src"sources:-"https://cdn.example.com"-"https://*.youtube.com"-"https://*.google.com"
This is really just a simple demo, that has room for improvements. For example,
the allowed list of *-src values to any directive could actually be set through their common
parent, the default-src attribute. There is a very deep and nested possibility
to address the attributes of many HTML5 tags, which is covered in depth on
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives.
You can take a look into the PHP enum
\TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive ,
which gives you an overview of all supported directives.
Read on to understand more of the underlying API builder concepts below.
Configuration
Policy builder approach
The following approach illustrates how a policy is build:
<?phpuseTYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue;
useTYPO3\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):
The policy builder is the low-level representation and interaction in PHP,
any other configuration is using the same verbs to describe the CSP
instructions. The
\TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy
object is used for compiling the CSP in a middleware.
Thus, custom controllers or middlewares could use this approach; the last
line
<?phpdeclare(strict_types=1);
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue;
useTYPO3\CMS\Core\Type\Map;
return Map::fromEntries(
[
// Provide declarations for the backend
Scope::backend(),
// NOTICE: When using `MutationMode::Set` existing declarations will be overriddennew 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,
),
),
],
[
// You can also additionally provide frontend declarations
Scope::frontend(),
new MutationCollection(
// Sets (overrides) the directive,// thus ignores 'self' of the 'default-src' directive// Results in `worker-src https://*.workers.example.com:`new Mutation(
MutationMode::Set,
Directive::WorkerSrc,
new UriValue('https://*.workers.example.com'),
),
),
],
);
Copied!
The API here is much like the YAML syntax. The PHP code needs to return
a mapped array of an
MutationCollection instance with all rules
put into a sub-array, containing instances of a single
Mutation.
Each
Mutation instance is like a Data Object (DO) where its constructor
allows you to specifiy a mode (type
MutationMode), a directive
(type
Directive) and one ore more actual values ("sources", type
UriValue
or SourceKeyword).
Additionally, a
Scope instance object is included, which can either
be
Scope::backend() or
Scope::frontend().
A good PHP IDE will allow for good autocompletion and hinting, and using
a boilerplate configuration like the example above helps you to get started.
Backend-specific
The YAML configuration only applies to the frontend part of TYPO3.
Backend policies need to be set using the PHP API, within an extension
as described in the section above.
You need to ensure that Scope::backend() is set in the mapped return array
for the rules you want to setup.
Site-specific (frontend)
In frontend, a dedicated sites/<my_site>/csp.yaml can be
used to declare policies for a specific site, for example:
# Inherits default site-unspecific frontend policy mutations (enabled per default)inheritDefault:truemutations:# 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!
Disable CSP for a site
New in version 12.4.20
The Content Security Policy for a particular site can be disabled with the
active key set to
false:
# "active" is enabled by default if omittedactive:false
Copied!
Site-specific Content-Security-Policy endpoints
The reporting endpoint is used to receive browser reports about violations to
the security policy, for example if a YouTube URL was requested, but could
not be displayed in an iframe due to a directive not allowing this.
Reports like this can help to gain insight, what URLs are used by editors
and might need inclusion into the policy.
Since reports can be sent by any browser, they can possibly easily flood
a site with requests and take up storage space. Reports are stored in the
sys_http_report database table when using the endpoint provided by TYPO3.
To influence whether this endpoint accepts reports,
the disposition-specific property reportingUrl can be configured and
set to either:
true
to enable the reporting endpoint
false
to disable the reporting endpoint
(string)
to use the given value as external reporting endpoint
The nonce attribute is useful to allowlist specific elements, such as a
particular inline script or style elements. It can help you to avoid using
the CSP unsafe-inline directive, which would allowlist all inline scripts or
styles.
<linkrel="stylesheet"href="/_assets/af46f1853e4e259cbb8ebcb816eb0403/Css/styles.css?1687696548"media="all"nonce="sqK8LkqFp-aWHc7jkHQ4aT-RlUp5cde9ZW0F0-BlrQbExX-PRMoTkw"
><stylenonce="sqK8LkqFp-aWHc7jkHQ4aT-RlUp5cde9ZW0F0-BlrQbExX-PRMoTkw">/* some inline styles */</style><scriptsrc="/_assets/27334a649e36d0032b969fa8830590c2/JavaScript/scripts.js?1684880443"nonce="sqK8LkqFp-aWHc7jkHQ4aT-RlUp5cde9ZW0F0-BlrQbExX-PRMoTkw"
></script><scriptnonce="sqK8LkqFp-aWHc7jkHQ4aT-RlUp5cde9ZW0F0-BlrQbExX-PRMoTkw">/* some inline JavaScript */</script>
Copied!
The nonce changes with each request so that (possibly malicious) inline scripts
or styles are blocked by the browser.
The nonce is applied automatically, when scripts or styles are defined with the
TYPO3 API, like TypoScript (
page.includeJS, etc.) or the
asset collector. This only refers to referenced files
(via
src and
href attributes) and not inline scripts
or inline styles. For those, you should either use the PHP/Fluid approach
as listed below, or use TypoScript only for passing DOM attributes
and using external scripts to actually evaluate these attributes to control
functionality.
TYPO3 provides APIs to get the nonce for the current request:
Nonces are implemented via a PSR middleware and thus applied dynamically. This
also means, they are somewhat "bad" for caching (especially for reverse proxies),
since they create unique output for a specific visitor.
Since the goal of nonces are to allow "exemptions" for otherwise forbidden content,
this closely relates to validity or integrity of this forbidden content. Instead
of emitting unique nonces, another possibility is to utilize hashing functionality
to content regarded as "safe".
This can be done with sha256/sha384/sha512 hashing of referenced script, and including
them as a valid directive, like this:
The "sha256-..." block would be the SHA256 hash created from a file like 'script.js'.
For example, a file like this:
script.js (some javascript file that is included in your website)
console.log('Hello.');
Copied!
would correspond to a SHA256 hash of 6c7d3c1bf856597a2c8ae2ca7498cb4454a32286670b20cf36202fa578b491a9.
Note
These hashes can be created by shell scripts like sha256 and several libraries,
also in nodeJS bundling tools.
The browser would evaluate a reference JavaScript file and calculate it's SHA256
hash and compare it to the list of allowed hashes.
The downside of this is: Everytime an embedded file changes (like via build processes),
the CSP SHA hash would need to be adopted. This could be automated by a PHP definition
of CSP rules and hashing files automatically, which would be a performance-intense
process and call for its own caching.
There is no automatism for this kind of hashing in TYPO3 (yet, see
https://forge.typo3.org/issues/100887), so it has to be done manually
as outlined above.
Reporting of violations, CSP Backend module
Potential CSP violations are reported back to the TYPO3 system and persisted
internally in the database table
sys_http_report. A corresponding
Admin Tools > Content Security Policy backend module supports users
to keep track of recent violations and - if applicable - to select potential
resolutions (stored in the database table
sys_csp_resolution) which
extends the Content Security Policy for the given scope during runtime:
Backend module "Content Security Policy" which displays the violations
Clicking on a row displays the details of this violation on the right side
including suggestions on how to resolve this violation. You have the choice to
apply this suggestion, or to mute or delete the specific violation.
Note
If you apply the suggestion, it is stored in the database table
sys_csp_resolution. To have all policies in one place, you
should consider adding the suggestion to your
extension-specific or
site-specific CSP definitions
manually.
Warning
Resolutions, once applied, can not be removed again via the GUI. You would
need to manually remove entries in the
sys_csp_resolution database
table.
Using a third-party service
As an alternative to the built-in reporting module, an external reporting URL
can be configured to use a third-party service as well:
config/system/additional.php
// For backend
$GLOBALS['TYPO3_CONF_VARS']['BE']['contentSecurityPolicyReportingUrl']
= 'https://csp-violation.example.org/';
// For frontend
$GLOBALS['TYPO3_CONF_VARS']['FE']['contentSecurityPolicyReportingUrl']
= 'https://csp-violation.example.org/';
Copied!
Violations are then sent to the third-party service instead of the TYPO3
endpoint. Resolutions would then not be applied dynamically.
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.
The global scope-specific setting contentSecurityPolicyReportingUrl can
be set to zero ('0') to disable the CSP reporting endpoint:
config/system/additional.php
// For backend
$GLOBALS['TYPO3_CONF_VARS']['BE']['contentSecurityPolicyReportingUrl'] = '0';
// For frontend
$GLOBALS['TYPO3_CONF_VARS']['FE']['contentSecurityPolicyReportingUrl'] = '0';
Copied!
Active content security policy rules
The backend module System > Configuration > Content Security Policy Mutations
uses a simple tree display of all configured directives, grouped by
frontend or backend. Each rule shows where it is defined, and what
its final policy is set to:
Backend module "Configuration > Content Security Policy Mutations" which displays
a tree of all policy directives.
The Context API encapsulates various information for data retrieval (for
example, inside the database) and analysis of current permissions and caching
information.
The context is set up at the very beginning of each TYPO3 entry point, keeping
track of, for example, the current time, if a user is logged in and which
workspace is currently accessed.
The
\TYPO3\CMS\Core\Context\Context object can be retrieved via
dependency injection:
This property is kept for compatibility reasons. Do not use, if not really
necessary, the option will be removed rather sooner than later.
Overlay types
LanguageAspect::OVERLAYS_OFF
Just fetch records from the selected language as given by
$GLOBALS['TSFE']->sys_language_content . No overlay will happen, no
fetching of the records from the default language. This boils down to
"free mode" language handling. Records without a default language parent are
included.
LanguageAspect::OVERLAYS_MIXED
Fetch records from the default language and overlay them with translations.
If a record is not translated, the default language will be used.
LanguageAspect::OVERLAYS_ON
Fetch records from the default language and overlay them with translations.
If a record is not translated, it will not be displayed.
LanguageAspect::OVERLAYS_ON_WITH_FLOATING
Fetch records from the default language and overlay them with translations.
If a record is not translated, it will not be shown. Records without a
default language parent are included.
The preview aspect may be used to indicate that the frontend is in preview mode
(for example, in case a workspace is previewed or hidden pages or records should
be shown).
The preview aspect,
\TYPO3\CMS\Frontend\Aspect\PreviewAspect , contains
the following property:
TYPO3 ships a list of countries of the world. The list is based on the
ISO 3166-1 standard, with the alphanumeric short name ("FR" or "FRA" in its
three-letter short name), the English name ("France"), the official name
("Republic of France"), also the numerical code, and the country's flag as
emoji (UTF-8 representation).
Note
The country list is based on Debian's ISO code list and shipped
statically as PHP content in the Country API.
The method returns an array of
\TYPO3\CMS\Core\Country\Country objects.
Get a country
EXT:my_extension/Classes/MyClass.php
// Get the country by Alpha-2 code
$france = $this->countryProvider->getByIsoCode('FR');
// Get the country by name
$france = $this->countryProvider->getByEnglishName('France');
// Get the country by Alpha-3 code
$france = $this->countryProvider->getByAlpha3IsoCode('FRA');
Copied!
The methods return a
\TYPO3\CMS\Core\Country\Country object.
Filter countries
One can use filters to get the desired countries:
EXT:my_extension/Classes/MyClass.php
useTYPO3\CMS\Core\Country\CountryFilter;
$filter = new CountryFilter();
// Alpha-2 and Alpha-3 ISO codes can be used
$filter
->setOnlyCountries(['AT', 'DE', 'FR', 'DK'])
->setExcludeCountries(['AUT', 'DK']);
// Will be an array with "Germany" and "France"
$filteredCountries = $this->countryProvider->getFiltered($filter);
Copied!
The method
getFiltered() return an array of
\TYPO3\CMS\Core\Country\Country objects.
The
Country object
A country object can be used to fetch all information about it, also with
translatable labels:
The following examples are meant to add one single
cropping configuration to sys_file_reference, which will then apply to every
record referencing images.
In this example we configure two crop variants, one with the id "mobile",
one with the id "desktop". The array key defines the crop variant id, which will be used
when rendering an image with the image view helper.
For each crop variant there's at least one ratio configuration defined as allowedAspectRatios:
its key must not contain the dot character (.):
good examples: NaN, 4:3 or other-format
bad example: 1:1.441
its value is an array consisting of two keys:
title: should be a string (or even better: a LLL reference)
It is also possible to define an initial crop area. If no initial crop area is defined, the default selected crop area will cover the complete image.
Crop areas are defined relatively with floating point numbers. The x and y coordinates and width and height must be specified for that.
The below example has an initial crop area in the size the previous image cropper provided by default.
Users can also select a focus area, when configured. The focus area is always inside
the crop area and marks the area of the image which must be visible for the image to transport
its meaning. The selected area is persisted to the database but will have no effect on image processing.
The data points are however made available as data attribute when using the <f:image /> view helper and
can be used by Javascript libraries.
The below example adds a focus area, which is initially one third of the size of the image
and centered.
Images are often used in a context where they are overlaid with other DOM elements
like a headline. To give editors a hint which area of the image is affected, when selecting a crop area,
it is possible to define multiple so-called cover areas. These areas are shown inside
the crop area. The focus area cannot intersect with any of the cover areas.
It is possible to provide a configuration per content element. If you want a different
cropping configuration for tt_content images, then you can add the following to
your image field configuration of tt_content records:
Please note, you need to specify the target column name as array key. Most of the time this will be crop
as this is the default field name for image manipulation in sys_file_reference
It is also possible to set the cropping configuration only for a specific tt_content element type by using the
columnsOverrides feature:
Please note, as the array for overrideChildTca is merged with the child TCA, so are the crop variants that are defined
in the child TCA (most likely sys_file_reference). Because you cannot remove crop variants easily, it is possible to disable them
for certain field types by setting the array key for a crop variant disabled to the value true as you can see in the
example above for the default variant.
Database (Doctrine DBAL)
This chapter describes accessing the database on the level of the Doctrine
Database Abstraction Layer (DBAL).
The Doctrine Database Abstraction Layer (DBAL) in TYPO3 provides developers
with a powerful and flexible way to interact with databases, allowing them to
perform database operations through an object-oriented API while ensuring
compatibility across different database systems.
In Extbase based extensions tables are abstracted as
Extbase models. Operations such as creating, updating and
deleting database records are usually performed from within a
Extbase repository with methods provided by Extbase
classes. However, Doctrine DBAL can also be used by extensions that use, for
example an Extbase controller.
TYPO3 relies on storing its data in a relational database management
system (RDBMS). The Doctrine DBAL component is used to enable connecting to
different database management systems. Most used is still MySQL / MariaDB, but
thanks to Doctrine others like PostgreSQL and SQLite are also an option.
The corresponding DBMS can be selected during installation.
This chapter gives an overview of the basic TYPO3 database table structure,
followed by some information on upgrading and maintaining table and field
consistency, and then deep dives into the programming API.
Doctrine DBAL
Database queries in TYPO3 are done with an API based on Doctrine DBAL.
The API is provided by the system extension core, which is always loaded and
thus always available.
Extension authors can use this low-level API to manage query operations
directly on the configured DBMS.
Doctrine DBAL is rich in features. Drivers for various target systems enable
TYPO3 to run on a long list of ANSI SQL-compatible DBMSes. If used properly,
queries created with this API are translated to the specific database engine by
Doctrine without an extension developer taking care of that specifically.
The API provided by the Core is basically a pretty small and lightweight facade
in front of Doctrine DBAL that adds some convenient methods as well as some
TYPO3-specific sugar. The facade additionally provides methods to retrieve
specific connection objects per configured database connection based on the
table that is queried. This enables instance administrators to configure
different database engines for different tables, while being transparent to
extension developers.
This document does not outline every single method that the API provides. It
sticks to those that are commonly used in extensions, and some parts like the
rewritten schema migrator are omitted as they are usually of little to no
interest to extensions.
Understanding Doctrine DBAL and Doctrine ORM
Doctrine is a two-part project, with Doctrine DBAL being the low-level
database abstraction and the interface for building queries to specific database
engines, while Doctrine ORM is a high-level object relational mapping on top
of Doctrine DBAL.
The TYPO3 Core implements - only - the DBAL part. Doctrine ORM is neither
required nor implemented nor used.
Low-level and high-level database calls
This documentation focuses on low-level database calls. In many cases, it is
better to use higher level APIs such as the DataHandler or Extbase repositories and
to let the framework handle persistence details internally.
Tip
Always remember the high-level database calls and use them when
appropriate!
Configuration
The configuration of Doctrine DBAL for TYPO3 is about specifying the single
database endpoints and passing the connection credentials. The framework
supports the parallel usage of multiple database connections, a specific
connection is mapped depending on its table name. The table space can be seen as
a transparent layer that determines which specific connection is chosen for a
query to a single or a group of tables: It allows "swapping out" single tables
from the Default connection to point them to a different database endpoint.
As with other central configuration options, the database endpoint and mapping
configuration is done in config/system/settings.php and ends up in
$GLOBALS['TYPO3_CONF_VARS'] after the Core bootstrap. The specific sub-array is
$GLOBALS['TYPO3_CONF_VARS']['DB'] .
Example: one connection
A typical basic example using only the Default connection with a single
database endpoint:
The Default connection must be configured, this can not be left out or
renamed.
For the
mysqli driver: If the
host is set to
localhost
and if the default PHP options in this area are not changed, the connection
will be socket-based. This saves a little overhead. To force a TCP/IP-based
connection even for
localhost, the IPv4 address 127.0.0.1 or IPv6
address ::1/128 respectively must be used as
host value.
The connection options are passed to Doctrine DBAL without much
manipulation from TYPO3 side. Please refer to the
doctrine connection docs for a full overview of the settings.
If the
charset option is not specified, it defaults to
utf8.
The option
wrapperClass is used by TYPO3 to insert the extended
Connection class
\TYPO3\CMS\Core\Database\Connection as main facade around Doctrine DBAL.
Example: two connections
Another example with two connections, where the
be_sessions table is
mapped to a different endpoint:
The array key
Sessions is just a name. It can be different, but it is
good practice to give it a useful, descriptive name.
It is possible to map multiple tables to a different endpoint by adding
further table name / connection name pairs to
TableMapping.
However, this "connection per table" approach is limited: In the above
example, if a join query is executed that spans different connections, an
exception will be thrown. It is up to the administrator to group the
affected tables to the same connection in those cases, or a developer should
implement fallback logic to suppress the
join().
Attention
The TYPO3 installer supports only a single MariaDB or MySQL connection at
the moment.
The database tables used by TYPO3 can be roughly divided into two categories:
Internal tables
Tables that are used internally by the system and are invisible to backend users
(for example,
be_sessions,
sys_registry, cache-related tables). In
the Core extension, there are often dedicated PHP APIs for managing entries in
these tables, for instance, the caching framework API.
Managed tables
Tables that can be managed via the TYPO3 backend are shown in the
Web > List module and can be edited using the FormEngine.
Requirements
There are certain requirements for such managed tables:
The table must be configured in the global TCA array,
for example:
table name
features that are required
fields of the table and how they should be rendered in the backend
relations to other tables
and so on.
The table must contain at least these fields:
uid - an auto-incremented integer and primary key for the table,
containing the unique ID of the record in the table.
pid - an integer pointing to the
uid of the page (record
from
pages table) to which the record belongs.
The fields are created automatically when the table is associated
with a TCA configuration.
Typical fields
A
title field holding the title of the record as seen in the backend.
A
description field holding a description displayed in the
Web > List view.
A
crdate field holding the creation time of the record.
A
tstamp field holding the last modification time of the record.
A
sorting field holding an order when records are sorted manually.
A
deleted field which tells TYPO3 that the record is deleted
(actually implementing a "soft delete" feature; records with a
deleted field are not truly deleted from the database).
A
hidden or
disabled field for records which exist but should
not be used (for example, disabled backend users, content not visible in the
frontend).
Note
With the exception of the
uid and
pid fields, all other fields
do not automatically fill a role as soon as they exist. Their existence must
be declared in the TCA configuration. This means that
such fields can also be named freely, the above are the default names TYPO3
uses - for consistency reasons it is recommended to name them that way.
The "pages" table
The
pages table has a special status: It is the backbone of TYPO3, as it
provides the hierarchical page structure into which all other records managed by
TYPO3 are placed. All other managed tables in TYPO3 have a
pid
field that points to a
uid record in this table. Thus, each managed table
record in TYPO3 is always placed on exactly one page in the page tree. This
makes the
pages table the mother of all other managed tables. It can be
seen as a directory tree with all other table records as files.
Standard pages are literally website pages in the frontend. But they can also be
storage spaces in the backend, similar to folders on a hard disk. For each
record, the
pid field contains a reference to the page where that
record is stored. For pages, the
pid fields behaves as a reference to
their parent pages.
The special "root" page has some unique properties: its
pid is 0 (zero),
it does not exist as a row in the
pages table, only users with
administrative rights can access records on it, and these records must be
explicitly configured to reside in the root page - usually, table records can only be created
on a real page.
MM relations
When tables are connected via a many-to-many relationship, another table must
store these relations. Examples are the table storing relations between
categories and categorized records
(
sys_category_record_mm) or the table storing relations between files
and their various usages in pages, content elements, etc.
(
sys_file_reference). The latter is an interesting example, because it
actually appears in the backend, although only as part of
inline records.
Other tables
The internal tables which are not managed through the TYPO3 backend serve
various purposes. Some of the most common are:
Cache: If a cache is defined to use the database as a
cache backend, TYPO3 automatically creates and
manages the relevant cache tables.
System information: There are tables that store information about
sessions, both frontend and backend (
fe_sessions
and be_sessions respectively), a table for a central registry
(
sys_registry) and some others.
All these tables are not subject to the
uid/
pid constraint
mentioned above, but they may have such fields if it is convenient for some
reason.
There is no way to manage such tables through the TYPO3 backend, unless a
specific module provides some form of access to them. For example, the
System > Log module provides an interface for browsing records
from the
sys_log table.
Upgrade table and field definitions
Each extension in TYPO3 can provide the ext_tables.sql file that defines
which tables and fields the extension needs. Gathering all
ext_tables.sql files thus defines the complete set of tables, fields and
indexes of a TYPO3 instance to unfold its full functionality. The
Analyze Database Structure section in the
Admin Tools > Maintenance backend module can compare the defined set
with the current active database schema and shows options to align these two by
adding fields, removing fields and so on.
When you upgrade to newer versions of TYPO3 or upgrade an extension, the data
definition of tables and fields may have changed. The database structure
analyzer detects such changes.
When you install a new extension, any change to the database is performed
automatically. When upgrading to a new major version of TYPO3, you should
normally go through the upgrade wizard, whose first step is to perform all
necessary database changes:
The Upgrade Wizard indicating that the database needs updates
If you want to perform minor updates, update extensions or generally check the
functionality of your system, you can go to
Admin Tools > Maintenance > Analyze Database Structure:
The Database analyzer is part of the maintenance area
This tool collects the information from all ext_tables.sql files of all
active extensions and compares them with the current database structure. Then it
proposes to perform the necessary changes, grouped by type:
creating new tables
adding new fields to existing tables
altering existing fields
dropping unused tables and fields
You can choose which updates you want to perform. You can even decide not to
create new fields and tables, although that will very likely break your
installation.
See also
For more information about the process of upgrading TYPO3, see chapter
Upgrades.
The ext_tables.sql files
As mentioned above, all data definition statements are stored in files named
ext_tables.sql, which may exist in any extension.
The peculiarity is that these files may not always contain a complete and valid
SQL data definition. For example, the "dashboard" system extension defines a new
table for storing dashboards:
EXT:dashboard/ext_tables.sql
CREATETABLE be_dashboards (
identifier varchar(120) DEFAULT''NOTNULL,
title varchar(120) DEFAULT''NOTNULL,
widgets text
);
Copied!
This is a complete and valid SQL data definition. However, the community
extension "news" extends the
tt_content table with additional fields. It
also provides these changes in the form of a SQL
CREATE TABLE statement:
The classes which take care of assembling the complete SQL data definition will
compile all the
CREATE TABLE statements for a given table and turn them
into a single
CREATE TABLE statement. If the table already exists,
missing fields are isolated and
ALTER TABLE statements are proposed
instead.
This means that as an extension developer you should always only have
CREATE TABLE statements in your ext_tables.sql files, the system
will handle them as needed.
Basic create, read, update, and delete operations (CRUD)
This section provides a list of basic usage examples of the query API. This is
just a starting point. Details about the single methods can be found in the
following chapters, especially QueryBuilder and
Connection.
The engine encloses field names in quotes, adds default TCA restrictions such as
deleted=0, and prepares a query to be executed with this final statement:
SELECT`uid`, `pid`, `bodytext`FROM`tt_content`WHERE (`uid` = '4')
AND ((`tt_content`.`deleted` = 0)
AND (`tt_content`.`hidden` = 0)
AND (`tt_content`.`starttime` <= 1669838885)
AND ((`tt_content`.`endtime` = 0) OR (`tt_content`.`endtime` > 1669838885)))
Copied!
Note
The default restrictions
deleted,
hidden,
startime and
endtime based on the TCA setting of a table are
only applied to
select() calls, they are not added for
delete() or other query types.
Select multiple rows with some "where" magic
Advanced query using the
QueryBuilder and manipulating the default
restrictions:
Doctrine DBAL provides a set of PHP objects to represent, create and handle SQL
queries and their results. The basic class structure has been slightly enriched
by TYPO3 to add CMS-specific features. Extension authors will usually interact
with these classes and objects:
\TYPO3\CMS\Core\Database\Connection
This object represents a specific connection to
one connected database. It provides "shortcut" methods for simple standard
queries like
SELECT or
UPDATE. To create more complex queries,
an instance of the QueryBuilder can be
retrieved.
\TYPO3\CMS\Core\Database\ConnectionPool
The ConnectionPool is the main entry point
for extensions to retrieve a specific connection over which to execute a
query. Usually it is used to return a Connection or a QueryBuilder
object.
The ExpressionBuilder object is used to
model complex expressions. It is mainly used for
WHERE and
JOIN conditions.
\TYPO3\CMS\Core\Database\Query\QueryBuilder
With the help of the QueryBuilder one can
create all sort of complex queries executed on a specific connection. It
provides the main CRUD methods for
select(),
delete() and friends.
TYPO3\CMS\Core\Database\Query\Restriction\...
Restrictions are a set of classes that
add expressions like
deleted=0 to a query, based on the
TCA settings of a table. They automatically adds
TYPO3-specific restrictions like start time and end time, as well as deleted
and hidden flags. Further restrictions for language overlays and workspaces
are available. In this documentation, these classes are referred as
RestrictionBuilder.
\Doctrine\DBAL\Driver\Statement
This result object is returned when a
SELECT or
COUNT query was executed. Single rows are returned
as an array by calling
->fetchAssociative() until the method
returns
false.
ConnectionPool
TYPO3's interface for executing queries via Doctrine DBAL starts with
a request to the
ConnectionPool for a
QueryBuilder or a
Connection object and passing the table name to be queried:
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Domain\Repository;
useTYPO3\CMS\Core\Database\ConnectionPool;
finalclassMyTableRepository{
privateconst TABLE_NAME = 'tx_myextension_domain_model_mytable';
publicfunction__construct(
private readonly ConnectionPool $connectionPool,
){}
publicfunctionfindSomething(){
// Get a query builder for a table
$queryBuilder = $this->connectionPool
->getQueryBuilderForTable(self::TABLE_NAME);
// Or get a connection for a table
$connection = $this->connectionPool
->getConnectionForTable(self::TABLE_NAME);
}
}
Copied!
The
QueryBuilder is the default object used by extension
authors to express complex queries, while a
Connection instance can be
used as a shortcut to handle some simple query cases.
Pooling: multiple connections to different database endpoints
TYPO3 can handle multiple connections to different database endpoints at the
same time. This can be configured for each individual table in
$GLOBALS['TYPO3_CONF_VARS'] (see database configuration for details). This makes it possible to run tables on
different databases without an extension developer having to worry about it.
The
ConnectionPool
implements this feature: It looks for configured
table-to-database mapping and can return a
Connection or a
QueryBuilder instance
for that specific connection. These objects know
internally which target connection they are dealing with and will quote field
names accordingly, for instance.
Beware
However, the transparency of tables for different database endpoints is limited.
Executing a table
JOIN between two tables that reference different
connections will result in an exception. This restriction may in practice lead
to implicit "groups" of tables that must to point to a single connection when an
extension or the TYPO3 Core joins these tables.
This can be problematic when several different extensions use, for instance, the
Core category or collection API with their mm table joins between Core internal
tables and their extension counterparts.
That situation is not easy to deal with. At the time of writing the Core
development will implement eventually some non-join fallbacks for typical cases
that would be good to decouple, though.
Tip
In the case joins cannot be decoupled, but still need to run affected tables
on different databases, and when the code can not be easily adapted, some
DBMS like PostgreSQL allow these queries to be executed by having their own
connection handlers to various other endpoints.
The query builder provides a set of methods to create queries
programmatically.
This chapter provides examples of the most common queries.
Warning
From a security point of view, the documentation of
->createNamedParameter()
and ->quoteIdentifier() are
an absolute must read and follow section. Make very sure you understand
this and use it for each and every query to prevent SQL
injections!
The query builder comes with a happy little list of small methods:
Set type of query:
->select(),
->count(),
->update(),
->insert() and
->delete()
Prepare
WHERE conditions
Manipulate default
WHERE restrictions added by TYPO3 for
->select()
Add
LIMIT,
GROUP BY and other SQL functions
executeQuery() executes a
SELECT query and returns a result,
a
\Doctrine\DBAL\Result object
executeStatement() executes an
INSERT,
UPDATE or
DELETE statement and returns the number of affected rows.
Most of the query builder methods provide a fluent interface, return
an instance of the current query builder itself, and can be chained:
$queryBuilder
->select('uid')
->from('pages');
Copied!
Instantiation
To create an instance of the query builder, call
ConnectionPool::getQueryBuilderForTable() and pass the table as an
argument. The ConnectionPool object can be
injected via dependency injection.
Never instantiate and initialize the query builder manually using
dependency injection or
GeneralUtility::makeInstance(), otherwise you
will miss essential dependencies and runtime setup.
Warning
The QueryBuilder holds internal state and must not be reused for
different queries. In addition, a reuse comes with a
significant performance penalty and memory consumption.
Use one query builder per query. Get a fresh one by calling
$connection->createQueryBuilder() if the same table is
involved, or use
$connectionPool->getQueryBuilderForTable() for a
query to a different table. Do not worry, creating those object instances
is quite fast.
->select() and a number of other methods of the query builder
are variadic
and can handle any number of arguments. In
->select() each argument
is interpreted as a single field name to be selected:
// SELECT `tt_content`.`bodytext` AS `t1`.`text`
$queryBuilder->select('tt_content.bodytext AS t1.text')
Copied!
With
->select() the list of fields to be selected is specified, and with
->addSelect() further elements can be added to an existing list.
Mind that
->select()replaces any formerly registered list instead of
appending it. Thus, it is not very useful to call
select() twice in a
code flow or after an
->addSelect(). The methods
->where() and
->andWhere() share the same behavior:
->where() replaces all
formerly registered constraints,
->andWhere() appends additional
constraints.
A useful combination of
->select() and
->addSelect() can be:
$queryBuilder->select(...$defaultList);
if ($needAdditionalFields) {
$queryBuilder->addSelect(...$additionalFields);
}
Copied!
Calling the
executeQuery() function on a
->select() query returns
a result object of type
\Doctrine\DBAL\Result. To receive single rows, a
->fetchAssociative() loop is used on that object, or
->fetchAllAssociative() to return a single array with all rows. A typical
code flow of a
SELECT query looks like this:
->select() and
->count() queries trigger TYPO3 magic that adds
further default where clauses if the queried table is also registered via
$GLOBALS['TCA'] . See the RestrictionBuilder section for details on that topic.
Similar to the
->select() query type,
->count() automatically
triggers the magic of the RestrictionBuilder that adds default restrictions such as
deleted,
hidden,
starttime and
endtime when
defined in TCA.
Similar to
->select() query types,
->executeQuery() with
->count() returns a result object of type
\Doctrine\DBAL\Result.
To fetch the number of rows directly, use
->fetchOne().
The first argument to
->count() is required, typically
->count(*) or
->count('uid') is used, the field name is
automatically quoted.
There is no support for
DISTINCT, instead a
->groupBy() has to
be used, for example:
If
->count() is combined with
->groupBy(), the result may
return multiple rows. The order of those rows depends on the used
DBMS. Therefore, to ensure the same
order of result rows on multiple different databases, a
->groupBy()
should always be combined with an
->orderBy().
delete()
Create a
DELETE FROM query. The method requires the table name from which
data is to be deleted. Classic usage:
For simple cases it is often easier to write and read using the
->delete() method of the Connection
object.
In contrast to
->select(),
->delete() does not
automatically add
WHERE restrictions like AND `deleted` = 0.
->delete() does not magically transform a
DELETE FROM `tt_content` WHERE `uid` = 4711 into something like
UPDATE `tt_content` SET `deleted` = 1 WHERE `uid` = 4711 internally.
A soft-delete must be handled at application level with a dedicated
lookup in
$GLOBALS['TCA']['theTable']['ctrl']['delete'] to check if
a specific table can handle the soft-delete, together with an
->update() instead.
Deleting from multiple tables at once is not supported:
DELETE FROM `table1`, `table2` can not be created.
->delete() ignores
->join()
->delete() ignores
setMaxResults():
DELETE with
LIMIT does not work.
->update() requires the table to update as the first argument and a table
alias (for example,
t) as optional second argument. The table alias can
then be used in
->set() and
->where() expressions:
->set() requires a field name as the first argument and automatically
quotes it internally. The second mandatory argument is the value to set a field
to. The value is automatically transformed to a named parameter of a prepared
statement. This way,
->set() key/value pairs are automatically SQL
protected from injection by default.
If a field should be set to the value of another field from the row, quoting
must be turned off and
->quoteIdentifier() and
false have to
be used:
->values() expects an array of key/value pairs. Both keys (field
names / identifiers) and values are automatically quoted. In rare cases,
quoting of values can be turned off by setting the second argument to
false. Then quoting must be done manually, typically by using
->createNamedParameter() on the values.
->executeStatement() after
->insert() returns the number of
inserted rows, which is typically 1.
An alternative to using the query builder for inserting data is using the
Connection object with its
->insert()
method.
The query builder does not provide a method for inserting multiple rows
at once, use
->bulkInsert() of the Connection
object instead to achieve that.
from()
->from() is essential for
->select() and
->count() query
types.
->from() requires a table name and an optional alias name. The
method is usually called once per query creation and the table name is usually
the same as the one passed to
->getQueryBuilderForTable(). If the query
joins multiple tables, the argument should be the name of the first table within
the
->join() chain:
// FROM `myTable`
$queryBuilder->from('myTable');
// FROM `myTable` AS `anAlias`
$queryBuilder->from('myTable', 'anAlias');
Copied!
->from() can be called multiple times and will create the Cartesian
product of tables if not constrained by a respective
->where() or
->andWhere() expression. In general, it is a good idea to use
->from() only once per query and instead model the selection of multiple
tables with an explicit
->join().
where(), andWhere() and orWhere()
The three methods are used to create
WHERE restrictions for
SELECT,
COUNT,
UPDATE and
DELETE query types. Each argument is
usually an ExpressionBuilder object that
is converted to a string on
->executeQuery() or
->executeStatement():
The three methods are variadic.
They can handle any number of arguments. For instance, if
->where()
receives four arguments, they are handled as single expressions, all
combined with
AND.
createNamedParameter
is used to create a placeholder for a field value of a prepared statement.
Always use this when dealing with user input in expressions to protect
the statement from SQL injections.
->where() should be called only once per query and resets all
previously set
->where(),
->andWhere() and
->orWhere()
expressions. A
->where() call after a previous
->where(),
->andWhere() or
->orWhere() usually indicates a bug or a
rather weird code flow. Doing so is discouraged.
When creating complex
WHERE restrictions,
->getSQL() and
->getParameters() are helpful debugging tools to verify parenthesis
and single query parts.
If only
->eq() expressions are used, it is often easier to switch to
the according method of the Connection object
to simplify quoting and improve readability.
It is possible to feed the methods directly with strings, but this is
discouraged and usually used only in rare cases where expression strings
are created in a different place that can not be easily resolved.
join(), innerJoin(), rightJoin() and leftJoin()
Joining multiple tables in a
->select() or
->count() query is done
with one of these methods. Multiple joins are supported by calling the methods
more than once. All methods require four arguments: The name of the table on the
left (or its alias), the name of the table on the right, an alias for the name
of the table on the right, and the join restriction as fourth argument:
The query operates with the
sys_language table as the main table,
this table name is given to
getQueryBuilderForTable().
The query joins the
pages table as
INNER JOIN and gives it the
alias
p.
The join condition is `p`.`sys_language_uid` = `sys_language`.`uid`. It
would have been identical to swap the expression arguments of the fourth
->join() argument
->eq('sys_language.uid', $queryBuilder->quoteIdentifier('p.sys_language_uid')).
The second argument of the join expression instructs the
ExpressionBuilder to quote the value as
a field identifier (a field name, here a combination of table and field
name). Using createNamedParameter
would lead in quoting as value (' instead of ` in MySQL) and the query
would fail.
The alias
p - the third argument of the
->join() call - does
not necessarily have to be set to a different name than the table name
itself here. It is sufficient to use
pages as third argument and not
to specify any other name. Aliases are mostly useful when a join to the same
table is needed:
SELECT `something` FROM `tt_content` JOIN `tt_content` `content2` ON ....
Aliases are also useful to increase the readability of ->where()
expressions.
The RestrictionBuilder has added
additional
WHERE conditions for both tables involved! The
sys_language table obviously only specifies a
'disabled' => 'hidden' as
enableColumns in its
TCA ctrl section, while the
pages table
specifies the fields
deleted,
hidden,
starttime and
stoptime.
A more complex example with two joins. The first join points to the first table,
again using an alias to resolve a language overlay scenario. The second join
uses the alias of the first join target as left side:
->join() and innerJoin are identical. They create an
INNER JOIN query, this is identical to a
JOIN query.
->leftJoin() creates a
LEFT JOIN query, this is identical to
a
LEFT OUTER JOIN query.
->rightJoin() creates a
RIGHT JOIN query, this is identical to
a
RIGHT OUTER JOIN query.
Calls to
join() methods are only considered for
->select() and
->count() type queries.
->delete(),
->insert()
and
update() do not support joins, these query parts are ignored and
do not end up in the final statement.
The argument of
->getQueryBuilderForTable() should be the leftmost
main table.
Joining two tables that are configured to different connections will throw
an exception. This restricts the tables that can be configured for different
database endpoints. It is possible to test the connection objects of the
involved tables for equality and implement a fallback logic in PHP if they
are different.
Doctrine DBAL does not support the use of join methods in combination with
->update(),
->insert() and
->delete() methods, because
such a statement is not cross-platform compatible.
Multiple join condition expressions can be resolved as strings like:
->orderBy() resets all previously specified orders. It makes no sense
to call this function again after a previous
->orderBy() or
->addOrderBy().
Both methods need a field name or a
table.fieldName or a
tableAlias.fieldName as first argument. In the example above the call
to
->orderBy('sys_language.sorting') would have been identical. All
identifiers are quoted automatically.
The second, optional argument of both methods specifies the sort order. The
two allowed values are
'ASC' and
'DESC', where
'ASC'
is default and can be omitted.
To create a chain of orders, use
->orderBy() and then multiple
->addOrderBy() calls. The call to
->orderBy('header')->addOrderBy('bodytext')->addOrderBy('uid', 'DESC')
creates ORDER BY `header` ASC, `bodytext` ASC, `uid` DESC
To add more complex sorting you can use
->add('orderBy', 'FIELD(eventtype, 0, 4, 1, 2, 3)', true),
remember to quote properly!
groupBy() and addGroupBy()
Add
GROUP BY to a
->select() statement. Each argument of the
methods is a single identifier:
// GROUP BY `pages`.`sys_language_uid`, `sys_language`.`uid`
->groupBy('pages.sys_language_uid', 'sys_language.uid');
Copied!
Remarks:
Similar to
->select() and
->where(), both methods are variadic
and take any number of arguments, argument unpacking is supported:
->groupBy(...$myGroupArray)
Each argument is either a direct field name GROUP BY `bodytext`,
a
table.fieldName or a
tableAlias.fieldName and is properly
quoted.
->groupBy() resets all previously defined group specification and
should only be called once per statement.
For more complex statements you can use
->add('groupBy', $sql, $append), remember to quote properly!
setMaxResults() and setFirstResult()
Add
LIMIT to restrict the number of records and
OFFSET for
pagination of query parts. Both methods should be called only once per
statement:
It is allowed to call
->setMaxResults() without calling
->setFirstResult().
It is possible to call
->setFirstResult() without calling
setMaxResults(): This is equivalent to "Fetch everything, but leave
out the first n records". Internally,
LIMIT will be added by
Doctrine DBAL and set to a very high value.
->setMaxResults(0) can be used to retrieve all results.
If an unlimited result set is needed, and no
reset of previous instructions is required, this method call should best
be omitted for best compatibility (Starting with TYPO3 v13.0,
->setMaxResults(null) will have to be used).
add()
Tip
This method will be removed with TYPO3 v13. You can prepare yourself by
using the dedicated methods instead (with at least TYPO3 v12.4.11 or
Doctrine DBAL v3.8):
The
->add() method appends or replaces a single, generic query part. It
can be used as a low level call when more specific calls do not provide enough
freedom to express parts of statements:
The first argument is the SQL part. One of:
select,
from,
set,
where,
groupBy,
having or
orderBy.
The second argument is the (properly quoted!) SQL segment of this part.
The optional third boolean argument specifies whether the SQL fragment
should be appended (
true) or replace a possibly existing SQL part of
this name (
false, default).
getSQL()
The
->getSQL() method returns the created query statement as string. It
is incredibly useful during development to verify that the final statement is
executed exactly as a developer expects:
This is debugging code. Take proper actions to ensure that these calls do
not end up in production!
The method is usually called directly before
->executeQuery() or
->executeStatement() to output the final statement.
Casting a query builder object to
(string) has the same effect as
calling
->getSQL(), but the explicit call using the method should be
preferred to simplify a search operation for this kind of debugging
statements.
The method is a simple way to see what restrictions the
RestrictionBuilder has added.
Doctrine DBAL always creates prepared statements: Each value added via
createNamedParameter
creates a placeholder that is later replaced when the real query is
triggered via
->executeQuery() or
->executeStatement().
->getSQL() does not show these values, instead it displays the
placeholder names, usually with a string like :dcValue1. There is no
simple solution to show the fully replaced query within the framework, but
you can use getParameters to
see the array of parameters used to replace these placeholders within the
query. On the frontend, the queries and parameters are available in the
admin panel.
getParameters()
The
->getParameters() method returns the values for the placeholders of
the prepared statement in an array. This is incredibly useful during development
to verify that the final statement is executed as a developer expects:
This is debugging code. Take proper actions to ensure that these calls do
not end up in production!
The method is usually called directly before
->executeQuery() or
->executeStatement() to output the final statement.
Doctrine DBAL always creates prepared statements: Each value added via
createNamedParameter
creates a placeholder that is later replaced when the real query is
triggered via
->executeQuery() or
->executeStatement().
->getParameters() does not show the statement or the placeholders,
instead the values are displayed, usually an array using keys like
:dcValue1. There is no simple solution to show the fully replaced query
within the framework, but you can use getSql to see the string with placeholders, which
is used as a prepared statement.
execute(), executeQuery() and executeStatement()
Changed in version 11.5
The widely used
->execute() method has been split into:
->executeQuery() returning a
\Doctrine\DBAL\Result instead of
a
\Doctrine\DBAL\Statement and
->executeStatement() returning the number of affected rows.
Although
->execute() still works for backwards compatibility, you
should prefer to use
->executeQuery() for
SELECT and
COUNT statements and
->executeStatement() for
INSERT,
UPDATE and
DELETE queries.
executeQuery()
This method compiles and fires the final query statement. This is usually the
last call on a query builder object. It can be called for
SELECT and
COUNT queries.
On success, it returns a result object of type
\Doctrine\DBAL\Result
representing the result set. The
Result object can then be used by
fetchAssociative(),
fetchAllAssociative() and
fetchOne().
executeQuery() returns a
\Doctrine\DBAL\Result and not a
\Doctrine\DBAL\Statement anymore.
Note
It is not possible to rebind placeholder values on the result and execute
another query, as was sometimes done with the
Statement returned by
execute().
If the query fails for some reason (for instance, if the database connection
was lost or if the query contains a syntax error), an
\Doctrine\DBAL\Exception is thrown. It is usually bad habit to catch and
suppress this exception, as it indicates a runtime error a program error. Both
should bubble up. For more information on proper exception handling, see the
coding guidelines.
executeStatement()
The
executeStatement() method can be used for
INSERT,
UPDATE and
DELETE statements. It returns the number of affected
rows as an integer.
expr()
This method returns an instance of the ExpressionBuilder. It is used to create complex
WHERE query
parts and
JOIN expressions:
This object is stateless and can be called and worked on as often as needed.
However, it is bound to the specific connection for which a statement is
created and therefore only available through the query builder, which is
specific to a connection.
Never reuse the ExpressionBuilder,
especially not between multiple query builder objects, but always get an
instance of the expression builder by calling
->expr().
createNamedParameter()
Note
Doctrine DBAL v4 (used with TYPO3 v13) dropped the support for using the
\PDO::PARAM_*
constants in favor of the enum types. Be aware of this and use
\TYPO3\CMS\Core\Database\Connection::PARAM_*, which can already be
used in TYPO3 v12 and v11 (like in the examples below).
This method creates a placeholder for a field value of a prepared statement.
Always use this when dealing with user input in expressions to protect the
statement from SQL injections:
The above example shows the importance of using
->createNamedParameter():
The search word kl'aus is "tainted" and would break the query if not
channeled through
->createNamedParameter(), which quotes the backtick and
makes the value SQL injection-safe.
Not convinced? Suppose the code would look like this:
// NEVER EVER DO THIS!
$_POST['searchword'] = "'foo' UNION SELECT username FROM be_users";
$searchWord = $request->getParsedBody()['searchword']);
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
$queryBuilder->getRestrictions()->removeAll();
this fails with syntax error to prevent copy and paste
$queryBuilder
->select('uid')
->from('tt_content')
->where(
// MASSIVE SECURITY ISSUE DEMONSTRATED HERE// USE ->createNamedParameter() ON $searchWord!
$queryBuilder->expr()->eq('bodytext', $searchWord)
);
Mind the missing
->createNamedParameter() method call in the
->eq() expression for a given value! This code would happily execute
the statement
SELECT uid FROM `tt_content` WHERE `bodytext` = 'foo' UNION SELECT username FROM be_users;
returning a list of backend user names!
Note
->set() automatically converts the second mandatory parameter into
a named parameter of a prepared statement. If the second parameter is
wrapped in a
->createNamedParameter() call, this will result in an
error during execution. This behaviour can be disabled by passing
false as third parameter to
->set().
Always use
->createNamedParameter() for any input, no matter
where it comes from.
The second argument of
->expr() is always either a call to
->createNamedParameter() or
->quoteIdentifier().
The second argument of
->createNamedParameter() specifies the type of
input. For string, this can be omitted, but it is good practice to add
\TYPO3\CMS\Core\Database\Connection::PARAM_INT for integers or similar for
other field types. This is not strict rule currently, but if you follow it
you will have fewer headaches in the future, especially with DBMSes that are not as relaxed as MySQL when it
comes to field types. The
Connection constants can be used for simple
types like bool, string, null, lob and integer. Additionally, the
two constants Connection::PARAM_INT_ARRAY and Connection::PARAM_STR_ARRAY
can be used when handling an array of strings or integers, for instance in
an IN() expression.
Keep the
->createNamedParameter() method as close to the expression
as possible. Do not structure your code in a way that it quotes something
first and only later stuffs the already prepared names into the expression.
Having
->createNamedParameter() directly within the created
expression, is much less error-prone and easier to review. This is a general
rule: Sanitizing input must be done as close as possible to the "sink" where
a value is passed to a lower part of the framework. This paradigm should
also be followed for other quote operations like
htmlspecialchars()
or
GeneralUtility::quoteJSvalue(). Sanitization should be obvious
directly at the very place where it is important:
->quoteIdentifier() must be used when not a value but a field name is
handled. The quoting is different in those cases and typically ends up with
backticks ` instead of ticks ':
// SELECT `uid` FROM `tt_content` WHERE (`header` = `bodytext`)// Return list of rows where header and bodytext values are identical
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
$queryBuilder
->select('uid')
->from('tt_content')
->where(
$queryBuilder->expr()->eq(
'header',
$queryBuilder->quoteIdentifier('bodytext')
)
);
The method quotes single field names or combinations of table names or table
aliases with field names:
Some quote examples
// Single field name: `bodytext`
->quoteIdentifier('bodytext');
// Table name and field name: `tt_content`.`bodytext`
->quoteIdentifier('tt_content.bodytext')
// Table alias and field name: `foo`.`bodytext`
->from('tt_content', 'foo')->quoteIdentifier('foo.bodytext')
Copied!
Remarks:
Similar to ->createNamedParameter() this method is crucial to
prevent SQL injections. The same rules apply here.
The ->set() method for
UPDATE statements expects its second argument to be a field value by
default, and quotes it internally using
->createNamedParameter(). If
a field should be set to the value of another field, this quoting can be
turned off and an explicit call to
->quoteIdentifier() must be added.
Internally,
->quoteIdentifier() is automatically called on all method
arguments that must be a field name. For instance,
->quoteIdentifier()
is called for all arguments of ->select().
->quoteIdentifiers() (mind the plural) can be used to quote multiple
field names at once. While that method is "public" and thus exposed as an
API method, this is mostly useful internally only.
escapeLikeWildcards()
Helper method to quote % characters within a search string. This is helpful in
->like() and
->notLike() expressions:
Even when using
->escapeLikeWildcards() the value must be
encapsulated again in a
->createNamedParameter() call. Only calling
->escapeLikeWildcards() does not make the value SQL injection
safe!
The
\TYPO3\CMS\Core\Database\Connection class extends the basic Doctrine
DBAL
\Doctrine\DBAL\Connection class and is mainly used internally in
TYPO3 to establish, maintain and terminate connections to single database
endpoints. These internal methods are not the scope of this documentation, since
an extension developer usually does not have to deal with them.
However, for an extension developer, the class provides a list of short-hand
methods that allow you to deal with query cases without the complexity
of the query builder. Using these methods
usually ends up in rather short and easy-to-read code. The methods have in common
that they only support "equal" comparisons in
WHERE conditions, that all
fields and values are automatically fully quoted, and that the created queries
are executed right away.
Note
The
Connection object is designed to work on a single table only. If
queries are performed on multiple tables, the object must not be reused.
Instead, a single
Connection instance per target table should be
retrieved via ConnectionPool. However, it
is allowed to use one
Connection object for multiple queries on the
same table.
Instantiation
Using the connection pool
An instance of the
\TYPO3\CMS\Core\Database\Connection class is retrieved
from the ConnectionPool by calling
->getConnectionForTable() and passing the table name for which a query
should be executed. The
ConnectionPool can be injected via constructor:
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Domain\Repository;
useTYPO3\CMS\Core\Database\Connection;
finalclassMyTableRepository{
publicfunction__construct(
private readonly Connection $connection,
){}
publicfunctionfindSomething(){
// Here you can use $this->connection directly
}
}
Copied!
Parameter types
The parameter types are used in various places to bind values to types, for
example, when using named parameters in the
query builder:
// use TYPO3\CMS\Core\Database\Connection;
$queryBuilder->createNamedParameter(42, Connection::PARAM_INT);
The name of the table the row should be inserted. Required.
An associative array containing field/value pairs. The key is a field name,
the value is the value to be inserted. All keys are quoted to field names
and all values are quoted to string values. Required.
Specify how single values are quoted. This is useful if a date, number or
similar should be inserted. Optional.
The example below quotes the first value to an integer and the second one to
a string:
The name of the table the row should be inserted. Required.
An array of the values to be inserted. Required.
An array containing the column names of the data which should be inserted.
Optional.
Specify how single values are quoted. Similar to insert(); if omitted, everything will be quoted
to strings. Optional.
The number of inserted rows are returned. If something goes wrong, a
\Doctrine\DBAL\Exception is thrown.
Note
MySQL is quite forgiving when it comes to insufficient field quoting:
Inserting a string into an
int field does not cause an error and
MySQL adjusts internally. However, other DBMSes are not that relaxed and may raise errors. It is good practice to
specify field types for each field, especially if they are not strings.
Doing this immediately will reduce the number of bugs that occur when people
run your extension an anything else than MySQL.
update()
Create an
UPDATE statement and execute it. The example from FAL's
ResourceStorage sets a storage to offline:
An associative array containing field/value pairs to be updated. The key is
a field name, the value is the value. In SQL they are mapped to the
SET keyword. Required.
The update criteria as an array of key/value pairs. The key is the field
name, the value is the value. In SQL they are mapped in a
WHERE
keyword combined with
AND. Required.
Specify how single values are quoted. Similar to insert(); if omitted, everything will be quoted
to strings. Optional.
The method returns the number of updated rows. If something goes wrong, a
\Doctrine\DBAL\Exception is thrown.
Note
The third argument WHERE `foo` = 'bar' supports only equal =. For more
complex stuff the query builder must be used.
delete()
Execute a
DELETE query using equal conditions in
WHERE, example
from
BackendUtility, to mark rows as no longer locked by a user:
The delete criteria as an array of key/value pairs. The key is the field
name, the value is the value. In SQL they are mapped in a
WHERE
keyword combined with
AND. Required.
Specify how single values are quoted. Similar to insert(); if omitted, everything will be quoted
to strings. Optional.
The method returns the number of deleted rows. If something goes wrong, a
\Doctrine\DBAL\Exception is thrown.
Note
TYPO3 uses a "soft delete" approach for
many tables. Instead of deleting a row directly in the database, a field -
often called
deleted - is set from 0 to 1. Executing a
DELETE
query circumvents this and really removes rows from a table. For most
tables, it is better to use the DataHandler API
to handle deletions instead of executing such low-level queries directly.
truncate()
This method empties a table, removing all rows. It is usually much faster than
a delete() of all rows. This typically
resets "auto increment primary keys" to zero. Use with care:
The argument is the name of the table to be truncated. If something goes wrong,
a
\Doctrine\DBAL\Exception is thrown.
count()
This method executes a
COUNT query. Again, this becomes useful when very
simple
COUNT statements are to be executed. The example below returns the
number of active rows (not hidden or deleted or disabled by time) from the
table
tx_myextension_mytable whose
field
some_value field set to
$something:
The select criteria as an array of key/value pairs. The key is the field
name, the value is the value. In SQL they are mapped in a
WHERE
keyword combined with
AND. Required.
The method returns the counted rows.
Remarks:
Connection::count() returns the number directly as an integer, unlike
the method of the query builder it is not
necessary to call
->fetchColumns(0) or similar.
The third argument expects all
WHERE values to be strings, each
single expression is combined with
AND.
The restriction builder kicks in and
adds additional
WHERE conditions based on TCA settings.
Field names and values are quoted automatically.
If anything more complex than a simple equal condition on
WHERE is
needed, the query builder methods are the
better choice: next to select(),
the
->count() query is often the least useful method of the
Connection object.
select()
This method creates and executes a simple
SELECT query based on equal
conditions. Its usage is limited, the restriction builder kicks in and key/value pairs are automatically
quoted:
The columns of the table which to select as an array. Required.
The name of the table. Required.
The select criteria as an array of key/value pairs. The key is the field
name, the value is the value. In SQL they are mapped in a
WHERE
keyword combined with
AND. Optional.
The columns to group the results by as an array. In SQL they are mapped
in a
GROUP BY keyword. Optional.
An associative array of column name/sort directions pairs. In SQL they are
mapped in an
ORDER BY keyword. Optional.
The maximum number of rows to return. In SQL it is mapped in a
LIMIT
keyword. Optional.
The first result row to select (when used the maximum number of rows). In
SQL it is mapped in an
OFFSET keyword. Optional.
In contrast to the other short-hand methods,
->select() returns a
Result object ready for
->fetchAssociative() to
get single rows or for
->fetchAllAssociative() to get all rows at once.
Remarks:
For non-trivial
SELECT queries it is often better to switch to the
according method of the query builder
object.
The restriction builder adds default
WHERE restrictions. If these restrictions do not match the query
requirements, it is necessary to switch to the
QueryBuilder->select()
method for fine-grained
WHERE manipulation.
lastInsertId()
This method returns the
uid of the last insert() statement. This is useful if the id is to be used
directly afterwards:
->lastInsertId($tableName) takes the table name as first argument.
Although it is optional, you should always specify the table name for
Doctrine DBAL compatibility with engines like PostgreSQL.
If the name of the auto increment field is not
uid, the second
argument must be specified with the name of that field. For simple TYPO3
tables,
uid is fine and the argument can be omitted.
createQueryBuilder()
The query builder should not be reused for
multiple different queries. However, sometimes it is convenient to first fetch a
connection object for a specific table and execute a simple query, and later
create a query builder for a more complex query from that connection object. The
usefulness of this method is limited, however, and at the time of writing no
good example could be found in the Core.
The method can also be useful in loops to save some precious code characters:
TYPO3 Core's Database API based on Doctrine DBAL supports the native
database field type json, which is already available for all supported DBMS
of TYPO3 v12.
JSON-like objects or arrays are automatically serialized during writing a
dataset to the database, when the native JSON type was used in the database
schema definition.
By using the native database field declaration json in ext_tables.sql
file within an extension, TYPO3 converts arrays or objects of type
\JsonSerializable into a serialized JSON value in the database when
persisting such values via
Connection->insert() or
Connection->update() if no explicit DB types are handed in as additional
method argument.
TYPO3 now utilizes the native type mapping of Doctrine to convert special types
such as JSON database field types automatically for writing.
When reading a record from the database via QueryBuilder, it is still necessary
though to transfer the serialized value to an array or object doing custom
serialization.
ExpressionBuilder class is responsible to dynamically create SQL query parts.
It takes care building query conditions while ensuring table and column names
are quoted within the created expressions / SQL fragments. It is a facade to
the actual Doctrine ExpressionBuilder.
The ExpressionBuilder is used within the context of the QueryBuilder to ensure
queries are being build based on the requirements of the database platform in
use.
Basic usage
An instance of the ExpressionBuilder is retrieved from the
QueryBuilder object:
It is good practice not to assign an instance of the ExpressionBuilder to
a variable, but to use it directly within the code flow of the query builder
context:
It is of crucial importance to quote values correctly to not introduce SQL
injection attack vectors into your application. See the according
section of the query builder
for details.
Junctions
Changed in version 11.5.10
The
andX() and
orX() methods are deprecated and replaced by
and() and
or() to match with Doctrine DBAL, which deprecated
these methods.
->and() conjunction
->or() disjunction
Combine multiple single expressions with
AND or
OR. Nesting is
possible, both methods are variadic and accept any number of arguments, which
are all combined. However, it usually makes little sense to pass zero or only
one argument.
Creates an equality comparison expression with the given arguments.
param $fieldName
The fieldname. Will be quoted according to database platform automatically.
param $value
The value. No automatic quoting/escaping is done.
Returns
string
neq(string $fieldName, ?mixed $value)
Creates a non equality comparison expression with the given arguments.
First argument is considered the left expression and the second is the right expression.
When converted to string, it will generated a <left expr> <> <right expr>. Example:
->like() and
->notLike() values must be additionally
quoted with a call to $queryBuilder->escapeLikeWildcards($value) to suppress the special
meaning of % characters from $value.
Aggregate functions used in
SELECT parts, often combined with
GROUP BY. The first argument is the field name (or table name / alias
with field name), the second argument is an optional alias.
trim(string $fieldName, int $position, ?string $char = NULL)
Creates a TRIM expression for the given field.
param $fieldName
Field name to build expression for
param $position
Either constant out of LEADING, TRAILING, BOTH
param $char
Character to be trimmed (defaults to space), default: NULL
Returns
string
Using the
->trim() expression ensures that the fields are trimmed at the
database level. The following examples give a better idea of what is possible:
Database tables in TYPO3 that can be managed in the backend have TCA definitions that specify how single fields and rows of the table
should be handled and displayed by the framework.
The ctrl section of a table's TCA array specifies optional framework-internal
handling of soft deletes and language overlays: For instance, when a row is
deleted in the backend using the page or list module, many tables are configured
to not drop that row entirely from the table, but to set a field (often
deleted) for that row from 0 to 1. Similar mechanisms apply for start and
end times, and to language and workspace overlays as well. See the
Table properties (ctrl) chapter in the TCA reference for details on this topic.
However, these mechanisms come at a price: developers of extensions dealing with
low-level queries must take care that overlaid or deleted rows are not included
in the result set of a simple query.
This is where this "automatic restriction" enters the picture: The construct is
created on top of native Doctrine DBAL as a
TYPO3-specific extension. It automatically adds WHERE expressions that
suppress rows which are marked as deleted or have exceeded their "active"
lifecycle. All this is based on the TCA configuration of the affected table.
Rationale
A developer might ask why they need to do all this to themselves, and why this
extra material is added on top of a low-level query layer when "just a simple
query" should be fired. The construct implements some important design goals:
Simple:
Query creation should be easy to handle without a developer having to deal too
much with the tedious TCA details..
Cope with developer laziness:
If the framework would force a developer to always add casual restrictions
for every single query, this is easy to forget. We are all lazy, aren't we?
Security:
When in doubt, it is better to show a little too little than too much. It is
much better to deal with a customer complaining that some records are not
displayed than to show too many records. The former is "just a bug", while
the latter can easily lead to a serious privilege escalation security issue.
Automatic query upgrades:
If a table was originally designed without a soft delete and a delete flag
is later added and registered in TCA, queries executed on that table will
automatically upgrade and the according
deleted = 0 restriction will
be added.
Handing over restriction details to the framework:
Having the restriction expressions handled by the framework gives it the
ability to change details without breaking the extension code. This may well
happen in the future, and a happy little upgrade path for such cases may
prove very useful later.
Flexibility:
The class construct is designed in a way so that developers can extend or
or substitute it with their own restrictions if that makes sense for
modeling the domain in question.
Main construct
The restriction builder is called whenever a
SELECT or
COUNT
query is executed using either
\TYPO3\CMS\Core\Database\Query\QueryBuilder
or
\TYPO3\CMS\Core\Database\Connection . The QueryBuilder allows manipulation of those restrictions, while the
simplified Connection class does not. When a query
deals with multiple tables in a join, restrictions are added for all affected
tables.
Each single restriction such as a
\TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction or a
\TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction is modeled
as a single class that implements the
\TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionInterface . Each
restriction looks up in TCA whether it should be applied. If so, the
according expressions are added to the
WHERE clause when compiling the
final statement.
Multiple restrictions can be grouped into containers which implement the
\TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface .
The
\TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer
is always added by default: It adds the
Note that this applies to all contexts in which a query is executed: It does not
matter whether a query is created from a frontend, a backend, or a
CLI call, they all add the
DefaultRestrictionContainer unless explicitly stated otherwise by an
extension developer.
Note
Having this
DefaultRestrictionContainer used everywhere is the second
iteration of this code construct:
The first variant automatically added contextual restrictions. For instance,
a query triggered by a call in the backend did not add the hidden flag,
while a query triggered in the frontend did. We quickly figured out that
this leads to a huge mess: The distinction between frontend, backend and CLI
is not that sharp in TYPO3, so for example the frontend behaves much more
like a backend call when using the admin panel.
The currently active variant is much easier: It always adds sane defaults
everywhere, a developer only has to deal with details if they do not fit.
The Core Team hopes that this approach is a good balance between hidden
magic, security, transparency, and convenience.
Use
\TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction instead.
Determines the current workspace a backend user is working in and adds
a couple of restrictions to select only records of that workspace if the
table supports workspace-enabled records.
WorkspaceRestriction has been added to overcome downsides of
FrontendWorkspaceRestriction and
BackendWorkspaceRestriction.
The workspace restriction limits an SQL query to only select records which
are "online" and in live or current workspace.
Note
As an important note and limitation of any workspace-related restrictions,
fetching the exact records need to be handled after the SQL results are
fetched, by overlaying the records with
\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordWSOL(),
\TYPO3\CMS\Core\Domain\Repository\PageRepository->versionOL() or
\TYPO3\CMS\Core\DataHandling\PlainDataResolver .
When a restriction needs to be enforced, a restriction could implement the
interface \TYPO3\CMS\Core\Database\Query\Restriction\EnforceableQueryRestrictionInterface.
If a restriction implements
EnforceableQueryRestrictionInterface, the
following applies:
->removeAll() will remove all restrictions except the ones that
implement the interface
EnforceableQueryRestrictionInterface.
->removeByType() will remove a restriction completely, also
restrictions that implement the interface
EnforceableQueryRestrictionInterface.
This container should be added by a developer to a query when creating
query statements in frontend context or when handling frontend stuff from
within CLI calls.
This restriction container applies added restrictions only to the given
table aliases. See Limit restrictions to tables for more
information. Enforced restrictions are treated equally to all other
restrictions.
Limit restrictions to tables
With
\TYPO3\CMS\Core\Database\Query\Restriction\LimitToTablesRestrictionContainer
it is possible to apply restrictions to a query only for a given set of tables,
or - to be precise - table aliases. Since it is a restriction container, it can
be added to the restrictions of the query builder and can hold restrictions
itself.
Examples
If you want to apply one or more restrictions to only one table, that is
possible as follows. Let us say you have content in the
tt_content table
with a relation to categories. Now you want to get all records with their
categories except those that are hidden. In this case, the hidden restriction
should apply only to the
tt_content table, not to the
sys_category
or
sys_category_*_mm table.
SELECT"c1".*
FROM"tt_content""c1"LEFTJOIN"tt_content""c2"ON c1.parent_field = c2.uid
WHERE (("c2"."uid"ISNULL) OR ("c2"."pid" = 1))
AND ("c2"."hidden" = 0))
Copied!
Custom restrictions
It is possible to add additional query restrictions by adding class names as key
to
$GLOBALS['TYPO3_CONF_VARS']['DB']['additionalQueryRestrictions'] .
These restriction objects will be added to any select query executed using the
QueryBuilder.
If these added restriction objects additionally implement
\TYPO3\CMS\Core\Database\Query\Restriction\EnforceableQueryRestrictionInterface
and return
true in the to be implemented method
isEnforced(),
calling
$queryBuilder->getRestrictions()->removeAll() such restrictions
will still be applied to the query.
If an enforced restriction must be removed, it can still be removed with
$queryBuilder->getRestrictions()->removeByType(SomeClass::class).
Implementers of custom restrictions can therefore have their restrictions always
enforced, or even not applied at all, by returning an empty expression in certain cases.
To add a custom restriction class, use the following snippet:
The class name must be the array key and the value must always be an array,
which is reserved for options given to the restriction objects.
Attention
Restrictions added by third-party extensions will impact the whole system.
Therefore this API does not allow removing restrictions added by the system
and adding restrictions should be handled with care.
Removing third party restrictions is possible, by setting the option
disabled for a restriction to
true in global TYPO3 configuration
or ext_localconf.php of an extension:
Often the default restrictions are sufficient. Nothing needs to be done in those
cases.
However, many backend modules still want to show disabled records and remove the
start time and end time restrictions to allow administration of those records
for an editor. A typical setup from within a backend module:
EXT:some_extension/Classes/SomeClass.php
// use TYPO3\CMS\Core\Utility\GeneralUtility;// use TYPO3\CMS\Core\Database\Connection;// use TYPO3\CMS\Core\Database\ConnectionPool;// use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction// SELECT `uid`, `bodytext` FROM `tt_content` WHERE (`pid` = 42) AND (`tt_content`.`deleted` = 0)
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
// Remove all restrictions but add DeletedRestriction again
$queryBuilder
->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$result = $queryBuilder
->select('uid', 'bodytext')
->from('tt_content')
->where($queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)
))
->executeQuery()
->fetchAllAssociative(();
The
DeletedRestriction should be kept in almost all cases. Usually, the
only extension that dismisses that flag is the recycler module to list and
resurrect deleted records. Any object implementing the
QueryRestrictionInterface can be given to
->add(). This allows
extensions to deliver own restrictions.
An alternative to the recommended way of first removing all restrictions and
then adding needed ones again (using
->removeAll(), then
->add())
is to kick specific restrictions with a call to
->removeByType():
EXT:some_extension/Classes/SomeClass.php
// use TYPO3\CMS\Core\Utility\GeneralUtility;// use TYPO3\CMS\Core\Database\ConnectionPool;// use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction// use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction// Remove starttime and endtime, but keep hidden and deleted
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
$queryBuilder
->getRestrictions()
->removeByType(StartTimeRestriction::class)
->removeByType(EndTimeRestriction::class);
In the frontend it is often needed to swap the
DefaultRestrictionContainer
with the
FrontendRestrictionContainer:
EXT:some_extension/Classes/SomeClass.php
// use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer// Remove default restrictions and add list of default frontend restrictions
$queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
Copied!
Note that
->setRestrictions() resets any previously specified
restrictions. Any class instance implementing
QueryRestrictionContainerInterface can be given to
->setRestrictions(). This allows extensions to deliver and use an custom
set of restrictions for own query statements if needed.
Tip
It can be very helpful to debug the final statements created by the
RestrictionBuilder using
debug($queryBuilder->getSQL()) right
before the final call to
$queryBuilder->executeQuery(). Just take
care these calls do notend up in production code.
Unlike
\Doctrine\DBAL\Statement returned formerly by ->execute(), a single prepared statement with different
values cannot be executed multiple times.
Warning
The return type of single field values is not type safe! If you select a
value from a field that is defined as
INT, the
Result result
may very well return that value as a PHP
string. This is also true
for other database column types like
FLOAT,
DOUBLE and others.
This is an issue with the database drivers used underneath. It may happen
that MySQL returns an integer value for an
INT field, while others
may return a string. In general, the application itself must take care of an
according type cast to achieve maximum DBMS compatibility.
fetchAssociative()
This method fetched the next row from the result. It is usually used in
while() loops. This is the recommended way of accessing the result in
most use cases.
// use TYPO3\CMS\Core\Database\Connection// Fetch all records from tt_content on page 42
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
$result = $queryBuilder
->select('uid', 'bodytext')
->from('tt_content')
->where(
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter(42, Connection::PARAM_INT)
)
)
->executeQuery();
while ($row = $result->fetchAssociative()) {
// Do something useful with that single $row
}
->fetchAssociative() returns an array reflecting one result row with
field/value pairs in one call and retrieves the next row with the next call.
It returns
false when no more rows can be found.
fetchAllAssociative()
This method returns an array containing all rows of the result set by internally
implementing the same while loop as above. Using that method saves some precious
code characters, but is more memory intensive if the result set is large and
contains many rows and data, since large arrays are carried around in PHP:
The method returns a single column from the next row of a result set, other
columns from this result row are discarded. It is especially handy for
QueryBuilder->count() queries:
// use TYPO3\CMS\Core\Database\Connection;// Get the number of tt_content records on pid 42 into variable $numberOfRecords
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
$numberOfRecords = $queryBuilder
->count('uid')
->from('tt_content')
->where(
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter(42, Connection::PARAM_INT)
)
)
->executeQuery()
->fetchOne();
The Connection->count() implementation
does exactly that to return the number of rows directly.
rowCount()
This method returns the number of rows affected by the last execution of this
statement. Use this method instead of counting the number of records in a
->fetchAssociative() loop manually.
Warning
->rowCount() works well with
DELETE,
UPDATE and
INSERT queries. However, it does not return a valid number for
SELECT queries on some DBMSes.
Never use
->rowCount() on
SELECT queries. This may work with
MySQL, but will fail with other databases like SQLite.
Reuse prepared statement
Doctrine DBAL usually prepares a statement first and then executes it with the
given parameters. The implementation of prepared statements depends on the
particular database driver. A driver that does not implement prepared
statements properly falls back to a direct execution of a given query.
There is an API that allows to make real use of prepared statements. This is
handy when the same query is executed over and over again with different
arguments. The example below prepares a statement for the
pages table
and executes it twice with different arguments.
// use TYPO3\CMS\Core\Database\Connection;
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
$statement = $queryBuilder
->select('uid')
->from('pages')
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createPositionalParameter(0, Connection::PARAM_INT)
)
)
->prepare();
$pages = [];
foreach ([24, 25] as $pageId) {
// Bind $pageId value to the first (and in this case only) positional parameter
$statement->bindValue(1, $pageId, Connection::PARAM_INT);
$result = $statement->executeQuery();
$pages[] = $result->fetchAssociative();
$result->free(); // free the resources for this result
}
Prepare SELECT `uid` FROM `pages` WHERE `uid` = ?
Execute SELECT `uid` FROM `pages` WHERE `uid` = '24'
Execute SELECT `uid` FROM `pages` WHERE `uid` = '25'
Copied!
The log shows one statement preparation with two executions.
Doctrine DBAL driver middlewares
New in version 12.3
Doctrine DBAL supports custom driver middlewares since version 3. 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 to implement SQL logging capabilities.
Use Find usages of PhpStorm for examples! The source code of the
Core is a great way to learn how specific methods of the API are used. In
PhpStorm it is extremely helpful to right-click on a single method and list
all method usages with Find usages. This is especially handy
to quickly see usage examples for complex methods like
join()
from the query builder.
INSERT,
UPDATE and
DELETE statements are often better
to read and write using the Connection object
instead of the query builder.
SELECT DISTINCT aField is not supported but can be substituted with a
->groupBy('aField').
Doctrine DBAL throws exceptions if something goes wrong when calling API methods.
The exception type is
\Doctrine\DBAL\Exception. Typical extensions should
usually not catch such exceptions but let it bubble up to be handled by the
global TYPO3 core error and exception handling: They most often indicate a
broken connection, database schema or programming error and extensions should
usually not try to hide away or escalate them on their own.
count() <database-query-builder-count> query types using the
query builder normally call
->fetchOne() to receive the count value.
The count() method of the
Connection object does this automatically and
returns the result of the count value directly.
Database records
In TYPO3, a record refers to an individual piece of content or data that
is stored in the database. Each record is part of a table and represents a
specific entity, such as a page, a content element, a backend user, or an
extension configuration.
TYPO3 uses a modular structure where different types of data are managed as
records, making it easy to organize and manipulate content.
Understanding records in TYPO3 is fundamental, as they are the building blocks
for managing content and data within the system.
These represent pages in the page tree, which structure the website. They
are stored in table
pages.
Content records
These include text, images, videos, or any other element placed within a
page. They are stored in table
tt_content.
Backend user records
Information about users who have access to the TYPO3 backend. They
are stored in table
be_users. The backend user groups they are
organized in are stored in table
be_groups.
System records
System records control the configuration and management of the TYPO3 system.
Examples include file references, file mounts, or categories.
Extension-specific records
Extensions often define custom records to store specific data, such as
products for a shop system or events for a calendar.
Technical structure of a record:
Each record is stored in a database table. Each row represents one record. Each
column represents a field of the record or some kind of metadata.
A record typically includes a unique identifier in column uid, the id of the
page record on which it is located in column pid, columns for various
attributes (for example, title, content), and metadata like creation and
modification timestamps, visibility, information on translation
and workspace handling. A record can have relations to other records.
TCA (Table Configuration Array)
TYPO3 uses the TCA to define how records of a specific table are
structured, how they are displayed in the backend, and how they interact
with other parts of the system. See the TCA Reference
for details.
Types and subtypes in records
In TYPO3, different types and subtypes of records are often stored in the same
database table, even though not all types share the same columns. This approach
allows for flexibility and efficiency in handling diverse content and data
structures within a unified system.
TYPO3 uses a single-table inheritance strategy, where records of various
types are distinguished by a specific field, often named
type.
For historical reasons the field is named
CType for content elements
and
doktype for pages. The field that is used for the type is defined in
TCA in ctrl > type. The types itself are stored in
types.
This allows TYPO3 to store related records, such as different content types,
in a shared table like
tt_content while supporting custom
fields for each record type.
For content elements in table
tt_content there is a second level of
subtypes in use where the field CType contains the value "list" and the field
list-type contains the actual type. This second level of types exists for
historic reasons. Read more about it in chapter Content Elements & Plugins.
Extbase domain models
TYPO3 extensions based on Extbase typically introduce a class inheriting from
\TYPO3\CMS\Extbase\DomainObject\AbstractEntity to represent a record
fully or partially within the domain of the extension. Multiple Extbase models
can store their data in the same database table. Additionally, the same record
can be represented in various ways by different Extbase models, depending on
the specific requirements of each model.
The DataHandler is the class that handles alldata
writing to database tables configured in TCA. In addition the class handles
commands such as copy, move, delete. It will handle
undo/history and versioning of records and everything will be logged
to the
sys_log table. It will make sure that write permissions are
evaluated correctly for the user trying to write to the database. Generally,
any processing specific option in the
$GLOBALS['TCA'] array is handled
by DataHandler.
Using DataHandler for manipulation of the database content in the TCA-configured
tables guarantees that the data integrity of TYPO3 is
respected. This cannot be safely guaranteed, if you write to TCA-configured
database tables directly. It will also manage the relations
to files and other records.
DataHandler requires a backend login to work. This is due to the fact that
permissions are observed (of course) and thus DataHandler needs a backend user
to evaluate against. This means you cannot use DataHandler from the
frontend scope. Thus writing to tables (such as a guestbook) will have
to be done from the frontend without DataHandler.
See also
The features of TCA are described in the TCA Reference.
Files
DataHandler can also handle files. The file operations are normally
performed in the File > Filelist module where you can manage a
directory on the server by copying, moving, deleting and editing files
and directories. The file operations are managed by two Core classes,
\TYPO3\CMS\Core\Utility\File\BasicFileUtility and
\TYPO3\CMS\Core\Utility\File\ExtendedFileUtility .
When you are using DataHandler from your backend applications you need to
prepare two arrays of information which contain the instructions to
DataHandler (
\TYPO3\CMS\Core\DataHandling\DataHandler )
of what actions to perform. They fall into two categories:
data and commands.
"Data" is when you want to write information to a database table or
create a new record.
"Commands" is when you want to move, copy or delete a record in the
system.
The data and commands are created as multidimensional arrays, and to
understand the API of DataHandler you need to understand the
hierarchy of these two arrays.
Caution
The DataHandler needs a properly configured TCA. If
your field is not configured in the TCA the DataHandler will not be able to
interact with it. This also is the case if you configured
"type"="none" (which is in fact a valid type) or if an invalid
type is specified. In that case, the DataHandler is not
able to determine the correct value of the field.
Basic usage
EXT:my_extension/Classes/DataHandling/MyClass.php
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\DataHandling;
useTYPO3\CMS\Core\DataHandling\DataHandler;
useTYPO3\CMS\Core\Utility\GeneralUtility;
finalclassMyClass{
publicfunctionbasicUsage(): void{
/** @var DataHandler $dataHandler */// Do not inject or reuse the DataHander as it holds state!// Do not use `new` as GeneralUtility::makeInstance handles dependencies
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$cmd = [];
$data = [];
$dataHandler->start($data, $cmd);
// ... do something more ...
}
}
Copied!
After this initialization you usually want to perform the actual operations by
calling one (or both) of these two methods:
Any error that might have occurred during your DataHandler operations can be
accessed via its public property
$this->dataHandler->errorLog.
See Error handling.
Commands array
Syntax:
$cmd[ tablename ][ uid ][ command ] = value
Copied!
Description of keywords in syntax:
tablename
tablename
Data type
string
Name of the database table. It must be configured in the
$GLOBALS['TCA'] array, otherwise it cannot be processed.
uid
uid
Data type
integer
The UID of the record that is manipulated. This is always an integer.
command
command
Data type
string (command keyword)
The command type you want to execute.
Note
Only one command can be executed at a time for each
record! The first command in the array will be taken.
The significance of the value depends on whether it is positive or
negative:
Positive value
The value points to a page UID. A copy of the record
(and possibly child elements/tree below) will be inserted inside that
page as the first element.
Negative value
The (absolute) value points to another record from the
same table as the record being copied. The new record will be inserted
on the same page as that record and if
$GLOBALS['TCA'][$table]['ctrl']['sortby']
is set, then it will be positioned after.
Zero value
Record is inserted on tree root level.
array
The array has to contain the integer value as in examples above and
may contain field => value pairs for updates. The array is structured
like:
[
'action' => 'paste', // 'paste' is used for both move and copy commands'target' => $pUid, // Defines the page to insert the record, or record uid to copy after'update' => $update, // Array with field => value to be updated.
]
Copied!
move
move
DataType
integer
Works like copy but moves the record instead of
making a copy.
This action will set the "deleted" flag back to 0.
localize
localize
Data type
integer
The value is the
languageId (defined in the
site configuration) to localize the
record into. Basically a localization of a record is making a copy of the
record (possibly excluding certain fields defined with
l10n_mode) but
changing relevant fields to point to the right language ID.
Requirements for a successful localization is this:
A
languageId must be configured in the site configuration.
The record to be localized by currently be set to default language
and not have any value set for the TCA
transOrigPointerField either.
There cannot exist another localization to the given language for the
record (looking in the original record PID).
Apart from this, ordinary permissions apply as if the user wants to
make a copy of the record on the same page.
The
localize DataHandler command should be used when translating
records in "connected mode"
(strict translation of records from the default language). This command is
used when selecting the "Translate" strategy in the content elements
translation wizard.
copyToLanguage
copyToLanguage
Data type
integer
It behaves like localize command (both record and
child records are copied to given language), but does not set
transOrigPointerField
fields (for example,
l10n_parent).
The
copyToLanguage command should be used when localizing records in
the "free mode". This
command is used when localizing content elements using translation wizard's
"Copy" strategy.
inlineLocalizeSynchronize
inlineLocalizeSynchronize
Data type
array
Performs localization or synchronization of child records.
The command structure is like:
$cmd['tt_content'][13]['inlineLocalizeSynchronize'] = [ // 13 is a parent record uid'field' => 'tx_myfieldname', // field we want to synchronize'language' => 2, // uid of the target language// either the key 'action' or 'ids' must be set'action' => 'localize'// or 'synchronize''ids' => [1, 2, 3] // array of child IDs to be localized
]
Copied!
version
version
Data type
array
Versioning action.
Note
This section is currently outdated.
Keys:
[action]
Keyword determining the versioning action. Options are:
"new"
Indicates that a new version of the record should be
created. Additional keys, specific for "new" action:
[treeLevels]
(Only pages) Integer, -1 to 4, indicating the number of levels
of the page tree to version together with a page. This is also
referred to as the versioning type:
-1 ("element") means only the page record gets versioned
(default)
0 ("page") means the page + content tables (defined by ctrl
flag versioning_followPages )
>0 ("branch") means the the whole branch is versioned
(full copy of all tables), down to the level indicated by
the value (1 = 1 level down, 2 = 2 levels down, etc.). The
treeLevel is recorded in the field t3ver_swapmode
and will be observed when the record is swapped during
publishing.
[label]
Indicates the version label to apply. If not given, a standard
label including version number and date is added.
"swap"
Indicates that the current online version should be swapped
with another. Additional keys, specific for "swap" action:
[swapWith]
Indicates the uid of the record to swap current version with!
[swapIntoWS]
Boolean, indicates that when a version is published it should be
swapped into the workspace of the offline record.
"clearWSID"
Indicates that the workspace of the record should be set to zero
(0). This removes versions out of workspaces without publishing
them.
"flush"
Completely deletes a version without publishing it.
"setStage"
Sets the stage of an element. Special feature: The id key in the
array can be a comma-separated list of ids in order to perform the
stageChange over a number of records. Also, the internal variable
->generalComment (also available through `/record/commit` route as
`&generalComment`) can be used to set a default comment for all
stage changes of an instance of the data handler. Additional keys
for this action are:
[stageId]
Values are:
-1 (rejected)
0 (editing, default)
1 (review),
10 (publish)
[comment]
Comment string that goes into the log.
Examples of commands
EXT:my_extension/Classes/DataHandling/MyClass.php
$cmd['tt_content'][54]['delete'] = 1; // Deletes tt_content record with uid=54
$cmd['tt_content'][1203]['copy'] = -303; // Copies tt_content uid=1203 to the position after tt_content uid=303 (new record will have the same pid as tt_content uid=1203)
$cmd['tt_content'][1203]['copy'] = 400; // Copies tt_content uid=1203 to first position in page uid=400
$cmd['tt_content'][1203]['move'] = 400; // Moves tt_content uid=1203 to the first position in page uid=400
Copied!
Accessing the uid of copied records
The
DataHandler keeps track of records created by copy
operations in its
$copyMappingArray_merged property. This
property is public but marked as
@internal. So it is subject to change
in future TYPO3 versions without notice.
The
$copyMappingArray_merged property can be used to determine the UID
of a record copy based on the UID of the copied record.
Caution
The
$copyMappingArray_merged property should not be mixed up with
the
$copyMappingArray property which contains only information
about the last copy operation and is cleared between each operation.
The structure of the
$copyMappingArray_merged property looks like this:
The property contains the names of the manipulated tables as keys and a map
of original record UIDs and UIDs of record copies as values.
EXT:my_extension/Classes/DataHandling/MyClass.php
$cmd['tt_content'][1203]['copy'] = 400; // Copies tt_content uid=1203 to first position in page uid=400$this->dataHandler->start([], $cmd);
$this->dataHandler->process_cmdmap();
$uid = $this->dataHandler->copyMappingArray_merged['tt_content'][1203];
Name of the database table. There must be a configuration for the table in
$GLOBALS['TCA'] array, otherwise it cannot be processed.
uid
uid
Data type
string|int
The UID of the record that is modified. If the record already exists,
this is an integer.
If you are creating new records, use a random string prefixed with NEW,
for example, NEW7342abc5e6d. You can use static strings (NEW1, NEW2,
...) or generate them using
\TYPO3\CMS\Core\Utility\StringUtility::getUniqueId('NEW').
Caution
If you supply your own string NEW must not be followed by an underscore.
The occurance of an underscore implies a reference to a record in a table.
fieldname
fieldname
Data type
string
Name of the database field you want to set a value for. The columns of the
table must be configured in
$GLOBALS['TCA'][$table]['columns'].
value
value
Data type
string
Value for "fieldname".
For fields of type inline this is a
comma-separated list of UIDs of referenced records.
Note
For FlexForms the data array of the FlexForm field is
deeper than three levels. The number of possible levels for FlexForms
is infinite and defined by the data structure of the FlexForm. But
FlexForm fields always end with a "regular value" of course.
Caution
Changed in version 12.4.11/11.5.35
Modifying the
sys_file table using DataHandler is blocked since TYPO3
version 11.5.35, 12.4.11, and 13.0.1. The table
should not be extended and additional fields should be added to
sys_file_metadata. See security advisory TYPO3-CORE-SA-2024-006
for more information.
Examples of data submission
This creates a new page titled "The page title" as the first page
inside page id 45:
EXT:my_extension/Classes/DataHandling/MyClass.php
$data['pages']['NEW9823be87'] = [
'title' => 'The page title',
'subtitle' => 'Other title stuff',
'pid' => '45'
];
Copied!
This creates a new page titled "The page title" right after page id 45
in the tree:
EXT:my_extension/Classes/DataHandling/MyClass.php
$data['pages']['NEW9823be87'] = [
'title' => 'The page title',
'subtitle' => 'Other title stuff',
'pid' => '-45'
];
Copied!
This creates two new pages right after each other, located right after
the page id 45:
Notice how the second "pid" value points to the "NEW..." id
placeholder of the first record. This works because the new id of the
first record can be accessed by the second record. However it works
only when the order in the array is as above since the processing
happens in that order!
This creates a new content record with references to existing and
one new system category:
EXT:my_extension/Classes/DataHandling/MyClass.php
$data['sys_category']['NEW9823be87'] = [
'title' => 'New category',
'pid' => 1,
];
$data['tt_content']['NEWbe68s587'] = [
'header' => 'Look ma, categories!',
'pid' => 45,
'categories' => [
1,
2,
'NEW9823be87', // You can also use placeholders here
],
];
Copied!
Note
To get real uid of the record you have just created use DataHandler's
substNEWwithIDs property like:
$uid = $this->dataHandler->substNEWwithIDs['NEW9823be87'];
This updates the page with uid=9834 to a new title, "New title for
this page", and no_cache checked:
EXT:my_extension/Classes/DataHandling/MyClass.php
$data['pages'][9834] = [
'title' => 'New title for this page',
'no_cache' => '1'
];
Copied!
Clear cache
DataHandler also has an API for clearing the cache tables of TYPO3:
EXT:my_extension/Classes/DataHandling/MyClass.php
$this->dataHandler->clear_cacheCmd($cacheCmd);
Copied!
Values for the
$cacheCmd argument:
[integer]
[integer]
Clear the cache for the page ID given.
"all"
"all"
Clears all cache tables (cache_pages, cache_pagesection,
cache_hash).
Only available for admin-users unless explicitly allowed by User
TSconfig "options.clearCache.all".
"pages"
"pages"
Clears all pages from cache_pages.
Only available for admin-users unless explicitly allowed by User
TSconfig "options.clearCache.pages".
Clear cache using cache tags
Every processing of data or commands is finalized with flushing a few caches in
the
pages group. Cache tags are used to specifically flush the
relevant cache entries instead of the cache as whole.
By default the following cache tags are flushed:
The table name of the updated record, for example,
pages when
updating a page or
tx_myextension_mytable when updating a record of
this table.
A combination of table name and record UID, for example,
pages_10
when updating the page with UID 10 or
tx_myextension_mytable_20 when
updating the record with UID 20 of this table.
A page UID prefixed with
pageID_ (
pageId_<page-uid>), for
example,
pageId_10 when updating a page with UID 10 (additionally all
related pages, see
clearcache-pagegrandparent
and
clearcache-pagesiblingchildren)
and
pageId_10 when updating a record if a record of any table placed
on the page with UID 10 (
<table>.pid = 10) is updated.
Notice that you can also use the
TypoScriptFrontendController->addCacheTags()
method to register additional tags for the cache entry of the current page while
it is rendered. This way you can implement an elaborate caching behavior which
ensures that every record update in the TYPO3 backend (which is processed by the
DataHandler) automatically flushes the cache of all pages where that
record is displayed.
Following the rules mentioned above you could register cache tags
from within your Extbase plugin (for example, controller or a
custom ViewHelper):
You can configure cache post-processing with a user defined PHP
function. Configuration of the hook can be done from
ext_localconf.php. An example might look like:
There are a few internal variables you can set prior to executing
commands or data submission. These are the most significant:
->copyTree
->copyTree
Data type
integer
Default
0
Sets the number of branches on a page tree to copy.
0
Branch is not copied
1
Pages on the first level are copied.
2
Pages on the second level are copied.
And so on.
->reverseOrder
->reverseOrder
Data type
boolean
Default
false
If set, the data array is reversed in the order, which is a nice thing
if you are creating a whole bunch of new records.
->copyWhichTables
->copyWhichTables
Data type
list of strings (tables)
Default
"*"
This list of tables decides which tables will be copied. If empty then
none will. If "*" then all will (that the user has permission to, of
course).
Using the DataHandler in scripts
You can use the class
\TYPO3\CMS\Core\DataHandling\DataHandler in your
own scripts: Inject the
DataHandler class, build a
$data/
$cmd array you want to pass to the class, and call a few
methods.
Attention
Mind that these scripts have to be run in the
backend scope! There must be a global
$GLOBALS['BE_USER'] object.
It is possible to use the DataHandler for scripts started from the command line
or by the scheduler as well. You can do this by
creating a Symfony Command.
These scripts use the _cli_ backend user. Before using the DataHandler in your
execute() method, you should make sure that this user is initialized like
this:
If you forget to add the backend user authentication, an error similar to this
will occur:
[1.2.1]: Attempt to modify table "pages" without permission
Copied!
DataHandler examples
What follows are a few code listings with comments which will provide you with
enough knowledge to get started. It is assumed that you have populated
the
$data and
$cmd arrays correctly prior to these chunks of code.
The syntax for these two arrays is explained in the
DataHandler basics chapter.
Warning
A new
DataHandler object should be created using
GeneralUtility::makeInstance(DataHandler::class) before each use.
It is a stateful service and has to be considered polluted after use. Do not
call
DataHandler::start() or
DataHandler::process_datamap()
multiple time on the same instance.
The
DataHandler class must not be injected into the constructor via
dependency injection. This can cause unexpected
side effects.
Submitting data
This is the most basic example of how to submit data into the database.
EXT:my_extension/Classes/DataHandling/MyClass.php
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\DataHandling;
useTYPO3\CMS\Core\DataHandling\DataHandler;
useTYPO3\CMS\Core\Utility\GeneralUtility;
finalclassMyClass{
publicfunctionsubmitData(): void{
// Prepare the data array
$data = [
// ... the data ...
];
/** @var DataHandler $dataHandler */// Do not inject or reuse the DataHander as it holds state!// Do not use `new` as GeneralUtility::makeInstance handles dependencies
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
// Register the $data array inside DataHandler and initialize the// class internally.
$dataHandler->start($data, []);
// Submit data and have all records created/updated.
$dataHandler->process_datamap();
}
}
Copied!
Executing commands
The most basic way of executing commands:
EXT:my_extension/Classes/DataHandling/MyClass.php
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\DataHandling;
useTYPO3\CMS\Core\DataHandling\DataHandler;
useTYPO3\CMS\Core\Utility\GeneralUtility;
finalclassMyClass{
publicfunctionexecuteCommands(): void{
/** @var DataHandler $dataHandler */// Do not inject or reuse the DataHander as it holds state!// Do not use `new` as GeneralUtility::makeInstance handles dependencies
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
// Prepare the cmd array
$cmd = [
// ... the cmd structure ...
];
// Registers the $cmd array inside the class and initialize the// class internally.
$dataHandler->start([], $cmd);
// Execute the commands.
$dataHandler->process_cmdmap();
}
}
Copied!
Clearing cache
In this example the cache clearing API is used. No data is submitted, no
commands are executed. Still you will have to initialize the class by
calling the
start() method (which will initialize internal state).
Note
Clearing a given cache is possible only for users that are
"admin" or have specific permissions to do so.
EXT:my_extension/Classes/MyClass.php
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\DataHandling;
useTYPO3\CMS\Core\DataHandling\DataHandler;
useTYPO3\CMS\Core\Utility\GeneralUtility;
finalclassMyClass{
publicfunctionclearCache(): void{
/** @var DataHandler $dataHandler */// Do not inject or reuse the DataHander as it holds state!// Do not use `new` as GeneralUtility::makeInstance handles dependencies
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start([], []);
$dataHandler->clear_cacheCmd('all');
}
}
Copied!
Caches are organized in groups. Clearing "all" caches will actually clear caches
from the "all" group and not really all caches. Check the
caching framework architecture section
for more details about available caches and groups.
Complex data submission
Imagine the
$data array contains something like this:
This aims to create two new pages in the page with uid "456". In the
following code this is submitted to the database. Notice the reversing of
the order of the array: This is done because otherwise "page 1" is created
first, then "page 2" in the same PID meaning that "page 2" will end up
above "page 1" in the order. Reversing the array will create "page 2" first and
then "page 1" so the "expected order" is preserved.
To insert a record after a given record, set the other record's negative
uid as pid in the new record you're setting as data.
Apart from this a "signal" will be send that the page tree should
be updated at the earliest occasion possible. Finally, the cache for
all pages is cleared.
EXT:my_extension/Classes/DataHandling/MyClass.php
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\DataHandling;
useTYPO3\CMS\Backend\Utility\BackendUtility;
useTYPO3\CMS\Core\DataHandling\DataHandler;
useTYPO3\CMS\Core\Utility\GeneralUtility;
finalclassMyClass{
publicfunctionsubmitComplexData(): void{
$data = [
'pages' => [
'NEW_1' => [
'pid' => 456,
'title' => 'Title for page 1',
],
'NEW_2' => [
'pid' => 456,
'title' => 'Title for page 2',
],
],
];
/** @var DataHandler $dataHandler */// Do not inject or reuse the DataHander as it holds state!// Do not use `new` as GeneralUtility::makeInstance handles dependencies
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->reverseOrder = true;
$dataHandler->start($data, []);
$dataHandler->process_datamap();
BackendUtility::setUpdateSignal('updatePageTree');
$dataHandler->clear_cacheCmd('pages');
}
}
Copied!
Both data and commands executed with alternative user object
In this case it is shown how you can use the same object instance to
submit both data and execute commands if you like. The order will
depend on the order in the code.
First the
start() method is called, but this time with the third
possible argument which is an alternative
$GLOBALS['BE_USER'] object.
This allows you to force another backend user account to create stuff in the
database. This may be useful in certain special cases. Normally you
should not set this argument since you want DataHandler to use the global
$GLOBALS['BE_USER'].
EXT:my_extension/Classes/DataHandling/MyClass.php
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\DataHandling;
useTYPO3\CMS\Core\Authentication\BackendUserAuthentication;
useTYPO3\CMS\Core\DataHandling\DataHandler;
useTYPO3\CMS\Core\Utility\GeneralUtility;
finalclassMyClass{
publicfunctionuseAlternativeUser(BackendUserAuthentication $alternativeBackendUser): void{
// Prepare the data array
$data = [
// ... the data ...
];
// Prepare the cmd array
$cmd = [
// ... the cmd structure ...
];
/** @var DataHandler $dataHandler */// Do not inject or reuse the DataHander as it holds state!// Do not use `new` as GeneralUtility::makeInstance handles dependencies
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start($data, $cmd, $alternativeBackendUser);
$dataHandler->process_datamap();
$dataHandler->process_cmdmap();
}
}
Copied!
Error handling
The data handler has a property errorLog as an array.
In this property, the data handler collects all errors.
You can use these, for example, for logging or other error handling.
EXT:my_extension/Classes/DataHandling/MyClass.php
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\DataHandling;
usePsr\Log\LoggerInterface;
useTYPO3\CMS\Core\DataHandling\DataHandler;
useTYPO3\CMS\Core\Utility\GeneralUtility;
finalclassMyClass{
publicfunction__construct(
private readonly LoggerInterface $logger,
){}
publicfunctionhandleError(): void{
/** @var DataHandler $dataHandler */// Do not inject or reuse the DataHander as it holds state!// Do not use `new` as GeneralUtility::makeInstance handles dependencies
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
// ... previous call of DataHandler's process_datamap() or process_cmdmap()if ($dataHandler->errorLog !== []) {
$this->logger->error('Error(s) while creating content element');
foreach ($dataHandler->errorLog as $log) {
// handle error, for example, in a log$this->logger->error($log);
}
}
}
}
Copied!
The "/record/commit" route
This route is a gateway for posting form data to the
\TYPO3\CMS\Backend\Controller\SimpleDataHandlerController .
You can send data to this file either as GET or POST vars where POST
takes precedence. The variable names you can use are:
data
data
Data type
array
Data array on the form [tablename][uid][fieldname] = value.
Typically it comes from a POST form which submits a form field like
<input name="data[tt_content][123][header]" value="This is the
headline">.
cmd
cmd
Data type
array
Command array on the form [tablename][uid][command] = value. This
array may get additional data set internally based on clipboard
commands send in CB var!
Typically this comes from GET vars passed to the script like
&cmd[tt_content][123][delete]=1 which will delete the content
element with UID 123.
cacheCmd
cacheCmd
Data type
string
Cache command sent to
DataHandler->clear_cacheCmd().
redirect
redirect
Data type
string
Redirect URL. The script will redirect to this location after performing
operations (unless errors has occurred).
flags
flags
Data type
array
Accepts options to be set in DataHandler object. Currently, it supports
"reverseOrder" (boolean).
mirror
mirror
Data type
array
Example: [mirror][table][11] = '22,33' will look for content in
[data][table][11] and copy it to [data][table][22] and
[data][table][33].
CB
CB
Data type
array
Clipboard command array. May trigger changes in "cmd".
To display additional debug information in the backend, set
$GLOBALS['TYPO3_CONF_VARS']['BE']['debug']
in config/system/settings.php and log in with an administrator
account.
It shows for example the names of fields and in case of select, radio and
checkbox fields the values in addition, which are generated by the
FormEngine. These can be used to set access permissions or
configuration using Page TSconfig.
If EXT:lowlevel is installed, the name of the
database table or field is appended to the select options in the
System > DB Check > Full Search module.
Additionally, in debug mode, the page renderer does not compress or concatenate
JavaScript or CSS resources.
DebugUtility::debug()
The TYPO3 Core provides a simple
debug() (defined in
EXT:core/Classes/Core/GlobalDebugFunctions.php). It wraps around
\TYPO3\CMS\Core\Utility\DebugUtility::debug() and will output debug
information only if it matches a set of IP addresses (defined in
$GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']).
In general, look at class
\TYPO3\CMS\Core\Utility\DebugUtility for useful
debugging tools.
Extbase DebuggerUtility
Extbase's DebuggerUtility::var_dump() is a debugging function in TYPO3 that outputs detailed, human-readable information about variables, including their type and structure. It offers features like depth control and optional backtrace information to assist developers in effectively debugging complex data structures.
You can also use the Extbase DebuggerUtility to debug SQL Querys for example. To do so, put the following code snippet before the execute function of your SQL query:
The Fluid Debug ViewHelper is a part of the Fluid template engine and generates a HTML dump of the tagged variable. The ViewHelper can be used in any Fluid template to output the value of variables or objects in a human-readable format.
Example:
<f:debug>{myVariable}</f:debug>
To display all available variables in your Fluid template, you can use the _all placeholder:
<f:debug>{_all}</f:debug>
Note
If you are debugging in a Fluid partial or a Fluid section, make sure that all variables you want to analyse are passed (defined in the arguments attribute of the render tag).
This chapter explains "Dependency Injection (DI)" as used in TYPO3. Readers
interested in the general concepts and principles may want to look at, for
example, Dependency Injection in "PHP The Right Way" or
What is dependency injection? by Fabien
Potencier. Whenever a class has a service dependency to another class the
technique of dependency injection should be used to satisfy that need. TYPO3
uses a Symfony component for dependency injection. The component is PSR-11 compliant, and it is used throughout
Core and extensions to standardize object initialization. By default all API
services shipped with the TYPO3 Core system extensions offer dependency
injection. The recommended usage is constructor injection. Available as well are method injection and interface injection.
To activate the Symfony component for dependency injection a few lines of
configuration are necessary.
Introduction
The title of this chapter is "dependency injection" (DI), but the scope is a bit
broader: In general, this chapter is about TYPO3 object lifecycle management and
how to obtain objects, with one sub-part of it being dependency injection.
This chapter not only talks about Symfony DI and its configuration via Services.yaml,
but also a bit about services in general, about
GeneralUtility::makeInstance() and the
SingletonInterface. And since the TYPO3 core already had an object lifecycle management
solution with the extbase
ObjectManager before Symfony services were implemented, we'll
also talk about how to transition away from it towards the core-wide Symfony solution.
Background and history
Obtaining object instances in TYPO3 has always been straightforward: Call
GeneralUtility::makeInstance(\MyVendor\MyExtension\Some\Class::class) and hand over
mandatory and optional
__construct() arguments as additional arguments.
There are two quirks to that:
First, a class instantiated through makeInstance() can implement
SingletonInterface.
This empty interface definition tells makeInstance() to instantiate the object exactly once
for this request, and if another makeInstance() call asks for it, the same object instance
is returned - otherwise makeInstance() always creates a new object instance and returns it.
Second,
makeInstance() allows "XCLASSing". This is a - rather dirty -
way to substitute a class with a custom implementation. XCLASS'ing in general is
brittle and seen as a last resort hack if no better solution is available. In connection
with Symfony containers, XCLASSing services should be avoided in general and service
overrides should be used instead. Replacing the XCLASS functionality in TYPO3 is still work in progress.
In contrast, XCLASSing is still useful for data objects, and there is no good other solution yet.
Using
makeInstance() worked very well for a long time. It however lacked a feature
that has been added to the PHP world after
makeInstance() had been invented: Dependency injection.
There are lots of articles about dependency injection on the net, so we won't go too deep
here but rather explain the main idea: The general issue appears when classes follow the
separation of concerns
principle.
One of the standard examples is logging. Let's say a class's responsibility is the
creation of users - it checks everything and finally writes a row to database. Now, since
this is an important operation, the class wants to log an information like "I just created user 'foo'".
And this is where dependency injection enters the game: Logging is a huge topic, there are
various levels of error, information can be written to various destinations and so on. The
little class does not want to deal with all those details, but just wants to tell the framework: "Please
give me some logger I can use and that takes care of all details, I don't want to know about details". This
separation is the heart of single responsibility and separation of concerns.
Dependency injection does two things for us here: First, it allows separating concerns, and second,
it hands the task of finding an appropriate implementation of a dependency over to the framework,
so the framework decides - based on configuration - which specific instance is given to the
consumer. Note in our example, the logging instance itself may have dependencies again - the process
of object creation and preparation may be further nested.
In more abstract software engineering terms: Dependency injection
is a pattern used to delegate the task of resolving class dependencies away from a consumer
towards the underlying framework.
Back to history: After
makeInstance() has been around for quite a while and lacked an
implementation of dependency injection, Extbase appeared in 2009. Extbase brought a first container
and dependency injection solution, it's main interface being the Extbase
ObjectManager.
The Extbase object manager has been widely used for a long time, but suffered from some
issues younger approaches don't face. One of the main drawbacks of Extbase object manager
is the fact that it's based on runtime reflection: Whenever an object is to be instantiated,
the object manager scans the class for needed injections and prepares dependencies to be
injected. This process is quite slow though mitigated by various caches. And these also
come with costs. In the end, these issues have been the main reason the object manager
was never established as a main core concept but only lived in Extbase scope.
The object lifecycle and dependency injection solution based on Symfony DI has been
added in TYPO3v10 and is a general core concept: Next to the native
dependency injection, it is also wired into
makeInstance() as a long living
backwards compatibility solution, and it fully substitutes the Extbase object manager. In
contrast to the Extbase solution, Symfony based object management does not have the
overhead of
expensive runtime calculations. Instead it is an instance wide build-time solution: When
TYPO3 bootstraps, all object creation details of all classes are read from
a single cache file just once, and afterwards no expensive calculation is required
for actual creation.
Symfony based DI was implemented in TYPO3 v10 and usage of the Extbase
ObjectManager was discouraged. With TYPO3 v11 the core doesn't use the
ObjectManager any more. It is actively deprecated in v11 and thus leads to
'deprecation' level log entries.
With TYPO3 v12 the Extbase ObjectManager is actually gone. Making use of
Symfony DI integration still continues. There
are still various places in the core to be improved. Further streamlining will
be done over time. For instance, the final fate of
makeInstance()
and the
SingletonInterface has not fully been decided on yet. Various
tasks remain to be solved in younger TYPO3 developments to further improve the
object lifecycle management provided by the core.
Build-time caches
To get a basic understanding of the core's lifecycle management it is helpful to
get a rough insight on the main construct. As already mentioned, object lifecycle
management is conceptualized as steps to take place at build-time.
It is done very early and only once during the TYPO3
bootstrap process. All calculated
information is written to a special cache that can not be reconfigured and is available
early. On subsequent requests the cache file is loaded.
Extensions can not mess with the construct if they adhere to the core API.
Besides being created early, the state of the container is independent and
exactly the same in frontend, backend and CLI scope. The same container instance may
even be used for multiple requests. This is becoming more and more important nowadays with the
core being able to execute sub requests. The only exception to this is the
Install Tool: It uses a more basic container that "cannot fail". This difference
is not important for extension developers however since they can't hook into the Install Tool
at those places anyways.
The Symfony container implementation is usually configured to actively scan the
extension classes for needed injections. All it takes are just a couple
of lines within the Services.yaml file. This should be done within all extensions that
contain PHP classes and it is the fundamental setup we will outline in the following sections.
For developers, it is important to understand that dealing with Symfony DI is
an early core bootstrap thing. The system will fail upon misconfiguration, so
frontend and backend may be unreachable.
Attention
Errors in the DI cache may block frontend and backend!
The DI cache does not heal by itself but needs to be cleared manually!
The container cache entry (at the time of this writing) is not deleted when a
backend admin user clicks "Clear all cache" in the backend top toolbar. The only
way to force a DI recalculation is using the "Admin tools" -> "Maintenance" -> "Flush Caches"
button of the backend embedded Install Tool or the standalone Install Tool (/typo3/install.php) itself. This
means: Whenever core or an extension fiddles with DI (or more general "Container") configuration,
this cache has to be manually emptied for a running instance by clicking this button.
The backend Extension Manager however does empty the cache automatically when loading or unloading extensions.
Another way to quickly drop this cache during development is to remove all
var/cache/code/di/* files, which reside in typo3temp/ in Classic mode
instances or elsewhere in Composer Mode instances (see Environment). TYPO3 will
then recalculate the cache upon the next access, no matter if it's a frontend, a backend
or a CLI request.
The main takeaway is: When a developer fiddles with container configuration,
the cache needs to be manually cleared. And if some configuration issue slipped in,
which made the container or DI calculation fail, the system does not heal itself and
needs both a fix of the configuration plus probably a cache removal. The standalone Install
Tool however should always work, even if the backend breaks down, so the "Flush caches"
button is always reachable. Note that if the container calculation fails, the
var/log/typo3_* files contain the exception with backtrace!
Important terms
We will continue to use a couple of technical terms in this chapter, so let's quickly
define them to align. Some of them are not precisely used in our world, for
instance some Java devs may stumble upon "our" understanding of a prototype.
Prototype
The broad understanding of a prototype within the TYPO3 community is
that it's simply an object that is created anew every time. Basically the direct
opposite of a singleton. In fact, the prototype pattern describes a base object that
is created once, so
__construct() is called to set it up, after that it is
cloned each time one wants to have a new instance of it. The community isn't well aware
of that, and the core provides no "correct" prototype API, so the word prototype is
often misused for an object that is always created anew when the framework is
asked to create one.
Singleton
A singleton is an object that is instantiated
exactly once within one request. If an instance is requested and the object has been
created once already, the same instance is returned. Codewise, this is sometimes done by
implementing a static
getInstance() method that parks the instance in a property.
In TYPO3, this can also be achieved by implementing the
SingletonInterface,
where
makeInstance() then stores the object internally. Within containers, this can be done
by declaring the object as shared (
shared: true), which is the default. We'll come back to
details later. Singletons must not have state - they must act the same way each time
they're used, no matter where, how often or when they've been used before. Otherwise the behavior of a singleton
object is undefined and will lead to obscure errors.
Service
This is another "not by the book" definition. We use the understanding
"What is a service?" from Symfony: In Symfony, everything that is instantiated through
the service container (both directly via
$container->get() and indirectly via DI)
is a service. These are many things - for instance controllers are services, as well as
- non static - utilities, repositories and obvious classes like mailers and similar. To emphasize:
Not only classes named with a
*Service suffix are services but basically anything. It
does not matter much if those services are stateless or not. Controllers, for instance,
are usually not stateless. (This is just a configuration detail from this point of view.)
Note: The TYPO3 Core does not strictly follow this behavior in all cases yet, but it strives
to get this more clean over time.
Data object
Data objects are the opposite of services. They are not available
through service containers. Here calling
$container->has() returns
false and they can not be
injected. They are instantiated either with
new() or
GeneralUtility::makeInstance().
Domain models or DTO are a typical example of data objects.
Note:Data objects are not "service container aware"
and do not support DI. Although the TYPO3 core does not strictly follow this rule in all cases
until now the ambition is to get this done over time.
Using DI
Now that we have a general understanding of what a service and what a data
object is, let's turn to usages of services. We will mostly use examples for
this.
The general rule is: Whenever your class has a service dependency to another class,
one of the following solutions should be used.
When to use Dependency Injection in TYPO3
Class dependencies to services should be injected via constructor injection or
setter methods. Where possible, Symfony dependency injection should be used for
all cases where DI is required.
Non-service "data objects" like Extbase domain model instances or DTOs should
be instantiated via
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance()
if they are non-final and support XCLASSing. For final classes without
dependencies plain instantiation via the new keyword must be used.
In some APIs dependency injection cannot be used yet. This applies to classes that need
specific data in their constructors or classes that are serialized and deserialized as, for
example, scheduler tasks.
When dependency injection cannot be used directly yet, create a service class and make it public
in the Configuration/Services.yaml. Create an instance of the service class via
GeneralUtility::makeInstance(...) you can then use dependency injection in the service class.
Constructor injection
Assume we're writing a controller that renders a list of users. Those users are
found using a custom
UserRepository, so the repository service is a direct dependency of the
controller service. A typical constructor dependency injection to resolve the
dependency by the framework looks like this:
Here the Symfony container sees a dependency to
UserRepository when scanning
__construct()
of the
UserController. Since autowiring is enabled by default (more on that below), an instance of the
UserRepository is created and provided when the controller is created. The example uses constructor
property promotion
and sets the property
readonly, so it can not be written a second time.
Method injection
A second way to get services injected is by using
inject*() methods:
This ends up with the basically the same result as above: The controller instance has
an object of type
UserRepository in class property
$userRepository.
The injection via methods was introduced by Extbase and TYPO3 core implemented it in addition to
the default Symfony constructor injection. Why did we do that, you may ask? Both
strategies have subtle differences: First, when using
inject*() methods, the type
hinted class property needs to be nullable, otherwise PHP >= 7.4 throws a warning
since the instance is not set during
__construct(). But that's just an
implementation detail. More important is an abstraction scenario. Consider this case:
We have an abstract controller service with a dependency plus a controller service that extends
the abstract and has further dependencies.
Now assume the abstract class is provided by TYPO3 core and the consuming class
is provided by an extension. If the abstract class would use constructor injection,
the consuming class would need to know the dependencies of the abstract, add its
own dependency to the constructor, and then call
parent::__construct($logger) to
satisfy the dependency of the abstract. This would hardcode all dependencies
of the abstract into extending classes. If later the abstract is changed and
another dependency is added to the constructor, this would break consuming
classes since they did not know that.
Differently put: When core classes "pollute"
__construct() with dependencies,
the core can not add dependencies without being breaking. This is the reason why
for example the extbase
AbstractController uses
inject*() methods for its
dependencies: Extending classes can then use constructor injection, do not need
to call
parent::__construct(), and the core is free to change dependencies of
the abstract.
In general, when the core provides abstract classes that are expected to be
extended by extensions, the abstract class should use
inject*() methods instead of
constructor injection. Extensions of course can follow this idea in similar
scenarios.
This construct has some further implications: Abstract classes should
think about making their dependency properties
private, so extending classes
can not rely on them. Furthermore, classes that should not be extended by extensions
are free to use constructor injection and should be marked
final, making
sure they can't be extended to allow internal changes.
As a last note on method injection, there is another way to do it: It is possible
to use a
setFooDependency() method if it has the annotation
@required.
This second way of method injection however is not used within the TYPO3
framework, should be avoided in general, and is just mentioned here for completeness.
Interface injection
Apart from constructor injection and
inject*() method injection, there is another
useful dependency injection scenario. Look at this example:
See the difference? We're requesting the injection of an interface and not a class!
It works for both constructor and method injection. It forces the service container
to look up which specific class is configured as implementation of the interface and
inject an instance of it. This is the true heart of dependency injection: A consuming
class no longer codes on a specific implementation, but on the signature of the interface.
The framework makes sure something is injected that satisfies the interface,
the consuming class does not care, it just knows about the interface methods. An
instance administrator can decide to configure the framework to inject some
different implementation than the default, and that's fully transparent
for consuming classes.
Here's an example scenario that demonstrates how you can define the specific
implementations that shall be used for an interface type hint:
services:_defaults:autowire:trueautoconfigure:truepublic:false# Define the default implementation of an interfaceMyVendor\MyExtension\Service\MyServiceInterface:'@MyVendor\MyExtension\Service\MyDefaultService'# Within MySecond- and MyThirdController different implementations for said# interface shall be used instead.# Version 1: when working with constructor injectionMyVendor\MyExtension\Controller\MySecondController:arguments:$service:'@MyVendor\MyExtension\Service\MySecondService'# Version 2: when working with method injectionMyVendor\MyExtension\Controller\MyThirdController:calls:-method:'injectMyService'arguments:$service:'@MyVendor\MyExtension\Service\MyThirdService'
Copied!
Using container->get()
[WIP] Service containers provide two methods to obtain objects, first via
$container->get(),
and via DI. This is only available for services itself: Classes that are registered
as a service via configuration can use injection or
$container->get(). DI is
supported in two ways: As constructor injection, and as
inject*() method injection.
They lead to the same result, but have subtle differences. More on that later.
In general, services should use DI (constructor or method injection) to obtain dependencies.
This is what you'll most often find when looking at core implementations. However, it
is also possible to get the container injected and then use
$container->get()
to instantiate services. This is useful for factory-like services where the exact name of classes is determined at runtime.
Configuration
Configure dependency injection in extensions
Extensions have to configure their classes to make use of the
dependency injection. This can be done in Configuration/Services.yaml.
Alternatively, Configuration/Services.php can also be used.
A basic Services.yaml file of an extension looks like the following.
Note
Whenever the service configuration or class dependencies change, the Core
cache must be flushed in the Admin Tools > Maintenance or via the CLI
command cache:flush to rebuild the compiled Symfony container. Flushing
all caches from the Clear cache menu does not flush the compiled Symfony
container.
autowire: true instructs the dependency injection component to
calculate the required dependencies from type declarations. This works for
constructor injection and
inject*() methods. The calculation
generates a service initialization code which is cached in the TYPO3 Core
cache.
Attention
An extension does not have to use autowiring, but can wire
dependencies manually in the service configuration file.
autoconfigure
It is suggested to enable
autoconfigure: true as this
automatically adds Symfony service tags based on implemented interfaces or
base classes. For example, autoconfiguration ensures that classes
implementing
\TYPO3\CMS\Core\SingletonInterface are publicly
available from the Symfony container and marked as shared
(
shared: true).
Model exclusion
The path exclusion
exclude: '../Classes/Domain/Model/*' excludes
your models from the dependency injection container, which means you cannot inject them
nor inject dependencies into them. Models are not services and therefore
should not require dependency injection. Also, these objects are created by
the Extbase persistence layer, which does not support the DI container.
Arguments
In case you turned off
autowire or need special arguments, you can
configure those as well. This means that you can set
autowire: false for
an extension, but provide the required arguments via config specifically for
the desired classes. This can be done in chronological order or by naming.
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configuration from aboveMyVendor\MyExtension\UserFunction\ClassA:arguments:$argA:'@TYPO3\CMS\Core\Database\ConnectionPool'MyVendor\MyExtension\UserFunction\ClassB:arguments:-'@TYPO3\CMS\Core\Database\ConnectionPool'
Copied!
This allows you to inject concrete objects like the Connection:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configuration from aboveconnection.pages:class:'TYPO3\CMS\Core\Database\Connection'factory:-'@TYPO3\CMS\Core\Database\ConnectionPool'-'getConnectionForTable'arguments:-'pages'MyVendor\MyExtension\UserFunction\ClassA:public:truearguments:-'@connection.pages'
Copied!
Now you can access the
Connection instance within
ClassA. This
allows you to execute your queries without further instantiation. For example,
this method of injecting objects also works with extension configurations and
with TypoScript settings.
Public
public: false is a performance optimization and should therefore be set
in extensions. This settings controls which services are available through the
dependency injection container used internally by
GeneralUtility::makeInstance(). However, some classes that need to be
public are automatically marked as public due to
autoconfigure: true
being set. These classes include singletons, as they must be shared with code
that uses
GeneralUtility::makeInstance() and Extbase controllers.
What to make public
Every class that is instantiated using
GeneralUtility::makeInstance()and requires dependency injection must be marked as public. The same goes
for instantiation via
GeneralUtility::makeInstance() using constructor
arguments.
Any other class which requires dependency injection and is retrieved by
dependency injection itself can be private.
Instances of
\TYPO3\CMS\Core\SingletonInterface and Extbase controllers
are automatically marked as public. This allows them to be retrieved using
GeneralUtility::makeInstance() as done by TYPO3 internally.
More examples of classes that must be marked as public:
With this configuration, you can use dependency injection in
\MyVendor\MyExtension\UserFunction\ClassA when it is created, for example
in the context of a
USER TypoScript object, which would not be
possible if this class were private.
If objects that use dependency injection are not configured properly, one or
more of the following issues may result. In such a case, check whether the
class has to be configured as
public: true.
ArgumentCountError is raised on missing dependency injection for
Constructor injection:
(1/1) ArgumentCountError
Too few arguments to function MyVendor\MyExtension\Namespace\Class::__construct(),
0 passed in typo3/sysext/core/Classes/Utility/GeneralUtility.php on line 3461 and exactly 1 expected
Copied!
An
Error is thrown on missing dependency injection for
Method injection, once the dependency is used within the code:
(1/1) Error
Call to a member function methodName() on null
Copied!
Installation-wide configuration
New in version 12.1
One can set up a global service configuration for a project that can be used in
multiple project-specific extensions. For example, this way you can alias an
interface with a concrete implementation that can be used in several extensions.
It is also possible to register project-specific CLI commands without requiring a project-specific extension.
However, this only works - due to security restrictions - if TYPO3 is configured
in a way that the project root is outside the document root, which is usually
the case in Composer-based installations.
How to override service arguments?
Some services in the TYPO3 core use service arguments, which can be overridden by third-party extensions.
For example, the
$rateLimiterFactory argument in the
\ControllerPasswordRecoveryController of the
typo3/cms-felogin
extension uses a service with the ID
feloginPasswordRecovery.rateLimiterFactory.
This service is defined in the
Services.yaml file of
ext:felogin and includes a service
argument named
$config, which specifies the configuration for the Symfony Rate Limiter used in
the class.
A third-party extension can override the
feloginPasswordRecovery.rateLimiterFactory service in
its own Services.yaml file, as shown in the example below:
It is important that the 3rd party extension is loaded after the extension whose service it overrides.
This can be achieved by requiring the original extension as a dependency in the packages/my-extension/composer.json file
of the 3rd party extension.
The global service configuration files services.yaml and
services.php are now read within the config/system/ path
of a TYPO3 project in Composer-based installations.
Example:
You want to use the PSR-20 clock interface as a type hint for dependency
injection in the service classes of your project's various extensions. Then the
concrete implementation may change without touching your code. In this example,
we use lcobucci/clock for the concrete implementation.
It is possible to use dependency injection when calling custom user functions,
for example .userFunc within TypoScript or
in (legacy) hooks, usually via
\TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction().
This method
callUserFunction() internally uses the dependency-injection-aware
helper
GeneralUtility::makeInstance(), which can recognize and inject
classes/services that are marked public.
However, this backend module is executed in a special low-level context
that disables some functionality for failsafe-reasons. Specifically,
this prevents dependency injection from being used in this scenario.
If you need to utilize services and other classes inside user functions
that are called there, you need to perform custom
GeneralUtility::makeInstance()
calls inside your own user function method to initialize those needed classes/services.
Dependency injection in a XCLASSed class
When extending an existing class (for example, an Extbase controller) using
XCLASS and injecting additional dependencies using constructor
injection, ensure that a reference to the extended class is added in the
Configuration/Services.yaml file of the extending extension, as shown in
the example below:
Deprecation handling: logging, marking and finding deprecations
TYPO3 logs calls to deprecated functions to help developers identify and update
outdated code. Deprecated methods will be removed in future TYPO3 versions, so
they should be avoided.
Deprecations are triggered by
trigger_error() and pass through TYPO3’s
logging and exception system. In development, they are shown as exceptions by
default; in production, they are typically ignored.
TYPO3 ships with a default configuration where deprecation logging is
disabled. If you upgrade to the latest TYPO3 version, you need to change
your development configuration to enable deprecation logging if you need
it.
Enabling the deprecation log can be done in the
Admin Tools > Settings backend module. Click on
Choose Preset in the Configuration Presets pane, open
Debug settings, activate the Debug option and submit
with Activate preset. Disabling the deprecation log can be done by
selecting the Live preset instead.
Enabling the debug preset
The debug preset also enables some other debug settings.
Note
These steps only enable/disable the FileWriter,
which comes with the TYPO3 default configuration. If you manually configured
additional writers for the TYPO3.CMS.deprecations logger, you need to
manually remove them to completely disable deprecation logging.
Via configuration file directly
Instead of using the GUI you can also enable
or disable the deprecation log with the
disabled option:
Excerpt of config/system/settings.php | typo3conf/system/settings.php
For more information on how to configure the writing of deprecation logs see
Writer configuration.
Find calls to deprecated functions
The extension scanner provides an interactive
interface to scan extension code for usage of removed or deprecated TYPO3 Core
API.
It is also possible to do a file search for
@deprecated and
E_USER_DEPRECATED . Using an IDE you can find all calls to the affected
methods.
The deprecations are also listed in the changelog of the
corresponding TYPO3 version.
Deprecate functions in extensions
Methods that will be removed in future versions of your extension should be
marked as deprecated by both the doc comment and a call to the PHP error method:
Excerpt of EXT:my_extension/Classes/MyClass.php
/**
* @deprecated since version 3.0.4, will be removed in version 4.0.0
*/publicfunctiondecreaseColPosCountByRecord(array $record, int $dec = 1): int{
trigger_error(
'Method "decreaseColPosCountByRecord" is deprecated since version 3.0.4, will be removed in version 4.0.0',
E_USER_DEPRECATED
);
// ... more logic
}
The TYPO3 Core includes an environment class that contains all
environment-specific information, mostly paths within the
filesystem. This implementation replaces previously used global variables and
constants like
PATH_site that have been removed with TYPO3 v10.
The fully qualified class name is
\TYPO3\CMS\Core\Core\Environment . The
class provides static methods to access the necessary information.
To simulate environments in testing scenarios, the
initialize()-method can
be called to adjust the information.
Environment PHP API
Tip
A comprehensive list of methods can be found in the
Class Reference.
getProjectPath()
The environment provides the path to the folder containing the composer.json.
For projects without Composer setup, this is equal to getPublicPath().
getPublicPath()
The environment provides the path to the public web folder with
index.php for the TYPO3 frontend. This was previously
PATH_site.
For projects without Composer setup, this is equal to getProjectPath().
getVarPath()
The environment provides the path to the var/ folder. This folder contains
data like logs, sessions, locks, and cache files.
For Composer-based installations, it returns var/, in Classic mode
installations typo3temp/var/.
In Composer-based installation this method provides the path
config/, in Classic mode installations
typo3conf/.
The directory returned by this method contains the folders system/
containing the configuration filessystem/settings.php and system/additional.php and the folder
sites/ containing the site configurations.
The environment provides the path to var/labels/ in
Composer-based installations, respective typo3conf/l10n/
folder in Classic mode installations. This folder contains downloaded translation files.
Returns the path and filename to the current PHP script.
getContext()
Returns the current Application context, usually defined via the TYPO3_CONTEXT environment variable.
May be one of Production, Testing, or Development with optional sub-contexts like Production/Staging.
useTYPO3\CMS\Core\Core\Environment;
$applicationContext = Environment::getContext();
if ($applicationContext->isProduction()) {
// do something only when in production context
}
Copied!
Error and exception handling
TYPO3 has a built-in error and exception handling system. Administrators can
configure how errors and exceptions are displayed in both the backend and the
frontend.
It is also possible to write changes manually into the configuration file
config/system/settings.php or
config/system/additional.php.
Most configuration options related to error and exception handling are
part of
$GLOBALS['TYPO3_CONF_VARS']['SYS'] .
The following configuration values are of interest:
Defines a list of IP addresses which will allow development output to
display. Setting to "*" will allow all. Setting it to an empty string
allows none.
The default debug exception handler displays
the complete stack trace of any encountered
exception. The error message and the stack trace is logged to the
configured logs.
Exception handler for rendering TypoScript content objects
Exceptions which occur during rendering of content objects (typically plugins)
will be caught by default in production context and an error message is shown
along with the rendered output. For more information and examples have a look
into the TypoScript reference for
config.contentObjectExceptionHandler.
Error Handler
Class
\TYPO3\CMS\Core\Error\ErrorHandler is the default error handler in
TYPO3.
Functions:
Can be registered for all PHP errors or for only a subset of the PHP errors
which will then be handled by an error handler.
Displays error messages as flash messages in the Backend (if
exceptionHandler is set to
\TYPO3\CMS\Core\Error\DebugExceptionHandler ).
Since flash messages are integrated in the Backend template, PHP messages
will not destroy the Backend layout.
Displays errors as TsLog messages in the adminpanel.
Logs error messages to the sys_log table. Logged errors are displayed
in the belog extension (Admin Tools > Log). This will work only with an
existing DB connection.
Production exception handler
Functionality of the
\TYPO3\CMS\Core\Error\ProductionExceptionHandler :
Shows brief exception message ("Oops, an error occurred!") using
\TYPO3\CMS\Core\Controller\ErrorPageController and its attendant template.
Logs exception messages to the sys_log table. Logged errors are displayed
in the belog extension (Admin Tools > Log). This will only work with an
existing DB connection.
Depending on the Logging writer configuration
the exception output can be found for example in the following locations:
\TYPO3\CMS\Core\Log\Writer\FileWriter
In Composer-based installations the information can be found in directory
var/logs/. In Classic mode installations in typo3temp/var/logs/.
\TYPO3\CMS\Core\Log\Writer\SyslogWriter
Logs exception messages to the
sys_log table. Logged errors are displayed
in the backend module Admin Tools > Log.
The generic error message "Oops, an error occurred!" is displayed when an
exception or error happens within a TypoScript content object like
FLUIDTEMPLATE or a plugin. When the exception
affects only one content element or plugin it is displayed in place of that
elements. However, if it affects the content element representing the whole page
like FLUIDTEMPLATE only a plain page with this text on
it is displayed.
This message is displayed in production context
instead of a more detailed exception message. The detailed message can then be
found in the log.
Show detailed exception output
When the frontend debugging is activated, a detailed exception message is output
instead of the generic "Oops, an error occurred!" message.
Logs exception messages to the sys_log table. Logged errors are displayed
in the belog extension (Admin Tools > Log). This will work only if there is
an existing DB connection.
Examples
Debugging and development setup
Attention
Do not use debug / development setup in production. This setup generates error
messages in the Frontend and a number of log messages for low severity errors.
The messages in the Frontend will be visible to the user, give a potential attacker
more information about your system and the logging will fill your filesystem / DB,
which degrades performance and can potentially be used to bring down your system
by filling storage with log messages. See Use staging servers for developments and tests for more
information.
Very verbose configuration which logs and displays all errors and
exceptions.
In config/system/settings.php or config/system/additional.php:
Use this setting, to get more context and a stacktrace in the Frontend in case of an exception.
Attention
Do not set config.contentObjectExceptionHandler to 0 in production. It will
display a complete stack dump in the Frontend, when an exception occurs. Use
config.contentObjectExceptionHandler = 1, which is the default, in production.
Example for a production configuration which displays only errors and
exceptions, if the devIPmask setting
matches. Errors and exceptions are only logged, if their
log level is at least
\Psr\Log\LogLevel::WARNING.
In config/system/settings.php or config/system/additional.php:
Since the error and exception handling and also the logging need some
performance, here's an example how to disable error and exception
handling completely.
In config/system/settings.php or config/system/additional.php:
PHP warnings, among other things, are added to the TYPO3 log via
the Writer configuration of the Logging API.
In production or from a performance perspective, you may not want this.
The default value is
\Psr\Log\LogLevel::WARNING.
Depending on project requirements, the loglevel can be increased to
\Psr\Log\LogLevel::ERROR
(or higher).
In .htaccess:
.htaccess
php_flag display_errors offphp_flag log_errors off
Copied!
How to extend the error and exception handling
If you want to register your own error or exception handler:
Create a corresponding class in your extension
Override the Core defaults for productionExceptionHandler, debugExceptionHandler
or errorHandler in config/system/additional.php:
We use config/system/additional.php and notext_localconf.php
in the extension (as previously documented) because that will be executed
after the error / exception handlers are initialized in the bootstrap process.
An error or exception handler class must register an error (exception)
handler in its constructor. Have a look at the files in EXT:core/Classes/Error/
to see how this should be done.
If you want to use the built-in error and exception handling but
extend it with your own functionality, derive your class from the
error and exception handling classes shipped with TYPO3.
Example Debug Exception Handler
This uses the default Core exception handler DebugExceptionHandler and overrides some
of the functionality:
Events and hooks provide an easy way to extend the functionality of the TYPO3
Core and its extensions without blocking others to do the same.
Events are being emitted by the TYPO3 Core or an
extension via the event dispatcher. The event will be
received by all implemented event listeners for the event in question. Events
are strongly typed. Events only allow changes to variables that are intended to
be changed by the event.
Hooks are basically places in the source code where a
user function will be called for processing, if such has been configured.
Changed in version 12.0
Signals and slots and all related classes have been removed from the
Core. Use PSR-14 events instead.
TYPO3 extending mechanisms video
Lina Wolf: Extending Extensions @ TYPO3 Developer Days 2019
Events and hooks vs. XCLASS extensions
Events and hooks are the recommended way of extending TYPO3 compared to
extending PHP classes with a child class (see
XCLASS extensions). Using the XCLASS functionality only one
extension of a PHP class can exist at a time while hooks and events
may allow many different user-designed processor functions to be executed.
With TYPO3 v10 the event dispatcher was introduced. It is a strongly typed
way of extending TYPO3 and therefore recommended to use wherever available.
However, events have to be emitted and hooks have to be implemented in the TYPO3
Core or an extension before they can be used, while extending a PHP class via
the XCLASS method allows you to extend any class you like.
Proposing events
If you need to extend the functionality of a class which has no event or hook
yet, then you should suggest emitting an event. Normally that is rather easily
done by the author of the source you want to extend:
For a third-party extension create an issue in the according issue tracker
of that extension.
Event dispatcher (PSR-14 events)
The event dispatcher system was added to extend TYPO3's Core behaviour in
TYPO3 v10.0. In the past, this was done via Extbase's signal/slot and TYPO3's
custom hook system. The event dispatcher system is a fully-capable replacement
for new code in TYPO3, as well as a possibility to migrate away from previous
TYPO3 solutions.
Don't get hooked, listen to events! PSR-14 within TYPO3 v10.
-- Benni Mack @ TYPO3 Developer Days 2019
For a basic example on listening to an event, see the chapter
Listen to an event in the
extension development how-to section.
This quick start section shows how to create your own event class and dispatch it.
If you just want to listen on an existing event, see section
Implementing an event listener in your extension.
Create an event class.
An event class is basically a plain PHP object with getters for immutable
properties and setters for mutable properties. It contains a constructor
for all properties:
PSR-14 is a lean solution that builds upon wide-spread solutions for hooking
into existing PHP code (Frameworks, CMS, and the like).
PSR-14 consists of the following four components:
The event dispatcher object
The
EventDispatcher object is used to trigger an event. TYPO3 has a
custom event dispatcher implementation. In PSR-14 all event dispatchers of all
frameworks are implementing
\Psr\EventDispatcher\EventDispatcherInterface , thus it is possible to
replace the event dispatcher with another. The
EventDispatcher's main
method
dispatch() is called in TYPO3 Core or extensions. It receives a
PHP object which will then be handed to all available listeners.
The listener provider
A
ListenerProvider object that contains all listeners which have been
registered for all events. TYPO3 has a custom listener provider that collects
all listeners during compile time. This component is only used internally inside of
TYPO3's Core Framework.
The events
An
Event object can be any PHP object and is called from TYPO3 Core or
an extension ("emitter") containing all information to be transported to the
listeners. By default, all registered listeners get triggered by an event,
however, if an event has the interface
\Psr\EventDispatcher\StoppableEventInterface implemented, a listener can
stop further execution of other event listeners. This is especially useful, if
the listeners are candidates to provide information to the emitter. This allows
to finish event dispatching, once this information has been acquired.
If an event allows modifications, appropriate methods should be available, although
due to PHP's nature of handling objects and the PSR-14 listener signature, it
cannot be guaranteed to be immutable.
Extensions and PHP packages can add listeners that are registered via YAML. They
are usually associated to
Event objects by the fully-qualified class name
of the event to be listened on. It is the task of the listener provider to
provide configuration mechanisms to represent this relationship.
If multiple event listeners for a specific event are registered, their order can
be configured or an existing event listener can also be overridden with a different one.
The System > Configuration > Event Listeners (PSR-14) backend module (requires
the system extension lowlevel) reveals an overview of all registered
event listeners, see Debugging event handling.
Advantages of the event dispatcher over hooks
The main benefits of the event dispatcher approach over hooks is an
implementation which helps extension authors to better understand the
possibilities by having a strongly typed system based on PHP. In addition, it
serves as a bridge to also incorporate other events provided by frameworks that
support PSR-14.
Impact on TYPO3 Core development in the future
TYPO3's event dispatcher serves as the basis to replace all
hooks in the future. However, for the time being, hooks
work the same way as before, unless migrated to an event dispatcher-like code,
whereas a PHP
E_USER_DEPRECATED error can be triggered.
Some hooks might not be replaced 1:1 to event dispatcher, but rather superseded
with a more robust or future-proof API.
Implementing an event listener in your extension
Hint
For a basic example on listening to an event, see the chapter
Listen to an event in the
extension development how-to section.
Registering the event listener
If an extension author wants to provide a custom event listener, an according
entry with the tag
event.listener can be added to the
Configuration/Services.yaml file of that extension.
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\EventListener\NullMailer:tags:-name:event.listenermethod:handleEventidentifier:'myListener'before:'redirects, anotherIdentifier'
The tag name
event.listener identifies that a listener should be registered.
The custom PHP class
\MyVendor\MyExtension\EventListener\NullMailer
serves as the listener whose
handleEvent() method is called, once the
event is dispatched. The name of the listened event is specified as
a typed argument to that dispatch method.
handleEvent(\TYPO3\CMS\Core\Mail\Event\AfterMailerInitializationEvent $event) will
for example listen on the event AfterMailerInitializationEvent.
The
identifier is a common name, so
orderings can be built upon the identifier, the optional
before and
after attributes allow for custom sorting against the
identifier
of other listeners. If no
identifier is specified, the service name (usually
the fully-qualified class name of the listener) is automatically used.
If no attribute
method is given, the class is treated as invokable, thus
its
__invoke() method will be called:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\EventListener\NullMailer:tags:-name:event.listeneridentifier:'myListener'before:'redirects, anotherIdentifier'
Once the emitter is triggering an event, this listener is called automatically.
Be sure to inspect the event's PHP class to fully understand the capabilities
provided by an event.
If you want to replace this event listener with your custom implementation, your extension can
achieve this by specifying:
EXT:my_extension/Configuration/Services.yaml
# Provide your custom event class:MyVendor\MyExtension\EventListener\Seo\HrefLangEventListener:tags:-name:event.listener# Use the same identifier of the extension that you override!identifier:'ext-some-extension/modify-hreflang'
Copied!
Note
When overriding an event listener, be sure to check whatever the
listener provided as changes to an event. Your own event listener
implementation is now responsible for any functionality, because
the original listener will no longer be executed.
Make sure that you set the identifier property to exactly
the string which the original implementation uses. If the identifier is not mentioned specifically
in the original implementation, the service name (when unspecified, the fully-qualified name of the event
listener class) is used. You can inspect that identifier in the
System > Configuration > Event Listeners (PSR-14) backend module (requires the system extension
lowlevel), see Debugging event handling. In this example,
if
identifier: 'ext-some-extension/modify-hreflang' is not defined, the identifier
will be set to
identifier: 'SomeVendor\SomeExtension\Seo\HrefLangEventListener' and you could
use that identifier in your implementation.
Note
Overriding listeners requires your extension to declare a dependency on the
EXT:some_extension extension (through composer.json, or for non-Composer
mode ext_emconf.php).
This ensures a proper loading order, so your extension is processed after the extension you want
to override.
Best practices
When configuring listeners, it is recommended to add one listener class per
event type, and have it called via
__invoke().
When creating a new event PHP class, it is recommended to add an
Event suffix to the PHP class, and to move it into an appropriate
folder like Classes/Event/ to easily discover events provided by a
package. Be careful about the context that should be exposed.
The same applies to creating a new event listener PHP class: Add
an
Listener suffix to the PHP class, and move it to a folder
Classes/EventListener/.
Emitters (TYPO3 Core or extension authors) should always use
Dependency Injection to receive the event
dispatcher object as a constructor argument, where possible, by adding a
type declaration for
\Psr\EventDispatcher\EventDispatcherInterface .
A unique and descriptive
identifier should be used for event listeners.
Any kind of event provided by TYPO3 Core falls under TYPO3's Core API
deprecation policy, except for its constructor arguments, which may vary. Events
that should only be used within TYPO3 Core, are marked as
@internal, just
like other non-API parts of TYPO3. Events marked as
@internal should be
avoided whenever technically possible.
Debugging event handling
A complete list of all registered event listeners can be viewed in the the
module System > Configuration > Event Listeners (PSR-14). The
lowlevel system extension has to be installed for
this module to be available.
List of event listeners in the Configuration module
To debug all events that are actually dispatched during a frontend request you
can use the admin panel:
Go to Admin Panel > Debug > Events and see all dispatched events.
The adminpanel system extension has to be
installed for this module to be available.
List of dispatched events in the Admin Panel
Event list
The following list contains PSR-14 events
in the TYPO3 Core .
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\AfterBackendPageRenderEvent
gets triggered after the page in the backend is rendered and includes the
rendered page body. Listeners may overwrite the page string if desired.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/after-backend-page-render'
This event triggers after a page has been rendered.
Listeners may update the page content string with a modified
version if appropriate.
getContent()
Returns
string
setContent(string $content)
param $content
the content
getView()
Returns
\TYPO3\CMS\Core\View\ViewInterface
AfterFormEnginePageInitializedEvent
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\AfterFormEnginePageInitializedEvent
is available to listen for after the form engine has been
initialized (all data has been persisted).
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\AfterPageColumnsSelectedForLocalizationEvent
is available to listen for after the form engine has been
initialized (and all data has been persisted). It will be dispatched after
records and columns are collected in the
\TYPO3\CMS\Backend\Controller\Page\LocalizationController .
The event receives:
The default columns and columns list built by
LocalizationController
The list of records that were analyzed to create the columns manifest
The parameters received by the
LocalizationController
The event allows changes to:
the columns
the columns list
This allows third-party code to read or manipulate the "columns manifest" that
gets displayed in the translation modal when a user has clicked the
Translate button in the page module, by implementing a listener for
the event.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event triggers after the LocalizationController (AJAX) has
selected page columns to be translated. Allows third parties to
add to or change the columns and content elements withing those
columns which will be available for localization through the
"translate" modal in the page module.
getColumns()
Returns list of columns, indexed by column position number, value is label (either LLL: or hardcoded).
Returns
array
setColumns(array $columns)
param $columns
the columns
getColumnList()
Returns a list of integer column position numbers used in the BackendLayout.
Returns an array of records which were used when building the original column
manifest and column position numbers list.
Returns
array
getParameters()
Returns request parameters passed to LocalizationController.
Returns
array
AfterPagePreviewUriGeneratedEvent
New in version 12.0
This PSR-14 event replaces the
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass']
postProcess hook.
The
\TYPO3\CMS\Backend\Routing\Event\AfterPagePreviewUriGeneratedEvent
is executed in
\TYPO3\CMS\Backend\Routing->buildUri(), after the preview
URI has been built - or set by an event listener to
BeforePagePreviewUriGeneratedEvent. It allows to overwrite the built
preview URI. However, this event does not feature the possibility to modify the
parameters, since this will not have any effect, as the preview URI is directly
returned after event dispatching and no further action is done by the
\TYPO3\CMS\Backend\Routing\PreviewUriBuilder .
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-preview-uri'
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\AfterPageTreeItemsPreparedEvent
allows prepared page tree items to be modified.
It is dispatched in the
\TYPO3\CMS\Backend\Controller\Page\TreeController
class after the page tree items have been resolved and prepared. The event
provides the current PSR-7 request object as well as the page tree items. All
items contain the corresponding page record in the special
_page key.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-page-tree-items'
Listeners to this event will be able to modify the prepared page tree items for the page tree
getRequest()
Returns
\Psr\Http\Message\ServerRequestInterface
getItems()
Returns
array
setItems(array $items)
param $items
the items
AfterRecordSummaryForLocalizationEvent
New in version 12.0
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\AfterRecordSummaryForLocalizationEvent
is fired in the
\TYPO3\CMS\Backend\Controller\Page\RecordSummaryForLocalization class
and allows extensions to modify the payload of the
JsonResponse.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/after-record-summary-for-localization'
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Backend\EventListener;
useTYPO3\CMS\Backend\Controller\Event\AfterRecordSummaryForLocalizationEvent;
finalclassMyEventListener{
publicfunction__invoke(AfterRecordSummaryForLocalizationEvent $event): void{
// Get current records
$records = $event->getRecords();
// ... do something with $records// Set new records
$event->setRecords($records);
// Get current columns
$columns = $event->getColumns();
// ... do something with $columns// Set new columns
$event->setColumns($columns);
}
}
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\BeforeFormEnginePageInitializedEvent
allows to listen for before the form engine has been
initialized (before all data will be persisted).
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Backend\Module\BeforeModuleCreationEvent
allows extension authors to manipulate the module configuration, before it is used to create and register the
module.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-module-icon'
This PSR-14 event replaces the
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass']
preProcess hook.
The
\TYPO3\CMS\Backend\Routing\Event\BeforePagePreviewUriGeneratedEvent
is executed in
\TYPO3\CMS\Backend\Routing->buildUri(), before the preview
URI is actually built. It allows to either adjust the parameters, such as the
page id or the language id, or to set a custom preview URI, which will then stop
the event propagation and also prevents
\TYPO3\CMS\Backend\Routing\PreviewUriBuilder from building the URI based
on the parameters.
Note
The overwritten parameters are used for building the URI and are also passed
to the AfterPagePreviewUriGeneratedEvent. They however do not
overwrite the related class properties in
PreviewUriBuilder.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-parameters'
Listeners to this event will be able to modify the corresponding parameters, before
the page preview URI is being generated, when linking to a page in the frontend.
The TYPO3 backend search (also known as "Live Search") uses the
\TYPO3\CMS\Backend\Search\LiveSearch\DatabaseRecordProvider to search
for records in database tables, having
searchFields configured in TCA.
In some individual cases it may not be desirable to search in a specific table.
Therefore, the PSR-14 event
\TYPO3\CMS\Backend\Search\Event\BeforeSearchInDatabaseRecordProviderEvent
is available, which allows to exclude / ignore such tables by adding them
to a deny list. Additionally, the PSR-14 event can be used to restrict the
search result on certain page IDs or to modify the search query altogether.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/before-search-in-database-record-provider-event-listener'
PSR-14 event to modify the incoming input about which tables should be searched for within
the live search results. This allows adding additional DB tables to be excluded / ignored, to
further limit the search result on certain page IDs or to modify the search query altogether.
This event replaces the
customControls
hook option, which is only available for TCA type
inline.
Listeners to the PSR-14 event
\TYPO3\CMS\Backend\Form\Event\CustomFileControlsEvent
are able to add custom controls to a TCA type
file field in form engine.
Custom controls are always displayed below the file references. In contrast
to the selectors, e.g. Select & upload files are custom controls
independent of the
readonly and
showFileSelectors options.
This means, you have full control in which scenario your custom controls
are being displayed.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event
\TYPO3\CMS\Backend\View\Event\IsContentUsedOnPageLayoutEvent
serves as a drop-in replacement for the removed
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['record_is_used']
hook.
Use the PSR-14 event
\TYPO3\CMS\Backend\View\Event\IsContentUsedOnPageLayoutEvent
to identify if content has been used in a column that is not in a backend layout.
Setting
$event->setUsed(true) prevent the following message for the affected content element,
setting it to false displays it:
Warning
Unused elements detected on this page
These elements don't belong to any of the available columns of this page. You should either delete
them or move them to existing columns. We highlighted the problematic records for you.
Example: Display "Unused elements detected on this page" for elements with missing parent
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Listener;
useTYPO3\CMS\Backend\View\Event\IsContentUsedOnPageLayoutEvent;
finalclassContentUsedOnPage{
publicfunction__invoke(IsContentUsedOnPageLayoutEvent $event): void{
// Get the current record from the event.
$record = $event->getRecord();
// This code will be your domain logic to indicate if content// should be hidden in the page module.if ((int)($record['colPos'] ?? 0) === 999
&& !empty($record['tx_myext_content_parent'])
) {
// Flag the current element as not used. Set it to true, if you// want to flag it as used and hide it from the page module.
$event->setUsed(false);
}
}
}
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Listener;
useTYPO3\CMS\Backend\View\Event\IsContentUsedOnPageLayoutEvent;
finalclassContentUsedOnPage{
publicfunction__invoke(IsContentUsedOnPageLayoutEvent $event): void{
// Get the current record from the event.
$record = $event->getRecord();
// This code will be your domain logic to indicate if content// should be hidden in the page module.if ((int)($record['colPos'] ?? 0) === 999
&& !empty($record['tx_myext_content_parent'])
) {
// Flag the current element as not used. Set it to true, if you// want to flag it as used and hide it from the page module.
$event->setUsed(false);
}
}
}
Use this Event to identify whether a content element is used.
getRecord()
Returns
array
isRecordUsed()
Returns
bool
setUsed(bool $isUsed)
param $isUsed
the isUsed
getKnownColumnPositionNumbers()
Returns
array
getPageLayoutContext()
Returns
\TYPO3\CMS\Backend\View\PageLayoutContext
IsFileSelectableEvent
New in version 12.1
The PSR-14 event
\TYPO3\CMS\Backend\ElementBrowser\Event\IsFileSelectableEvent
allows to decide whether a file can be selected in the file browser.
To get the image dimensions (width and height) of a file, you can retrieve the
file and use the
getProperty() method.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-file-is-selectable'
Listeners to this event are able to define whether a file can be selected in the file browser
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
isFileSelectable()
Returns
bool
allowFileSelection()
denyFileSelection()
ModifyAllowedItemsEvent
New in version 12.0
This event has been introduced together with
ModifyLinkHandlersEvent to
serve as a direct replacement for the following removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['LinkBrowser']['hooks'] .
It replaces the method
modifyAllowedItems() in this hook.
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\ModifyAllowedItemsEvent
allows extension authors to add or remove from the list of allowed link
types.
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/allowed-items'
This event allows extensions to add or remove from the list of allowed link types.
getAllowedItems()
Returns
string[]
addAllowedItem(string $item)
param $item
the item
Returns
self
removeAllowedItem(string $new)
param $new
the new
Returns
self
getCurrentLinkParts()
Returns
array<string,mixed>
ModifyButtonBarEvent
New in version 12.0
This event serves as a direct replacement for the removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['Backend\Template\Components\ButtonBar']['getButtonsHook'] .
The PSR-14 event
\TYPO3\CMS\Backend\Template\Components\ModifyButtonBarEvent
can be used to modify the button bar in the TYPO3 backend module
docheader.
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-button-bar'
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Backend\EventListener;
useTYPO3\CMS\Backend\Template\Components\ModifyButtonBarEvent;
finalclassMyEventListener{
publicfunction__invoke(ModifyButtonBarEvent $event): void{
// Do your magic here
}
}
Listeners can modify the buttons of the button bar in the backend module docheader
getButtons()
Returns
array
setButtons(array $buttons)
param $buttons
the buttons
getButtonBar()
Returns
\TYPO3\CMS\Backend\Template\Components\ButtonBar
ModifyClearCacheActionsEvent
New in version 11.4
The PSR-14 event
\TYPO3\CMS\Backend\Backend\Event\ModifyClearCacheActionsEvent
is fired in the
\TYPO3\CMS\Backend\Backend\ToolbarItems\ClearCacheToolbarItem
class and allows extension authors to modify the clear cache actions, shown
in the TYPO3 backend top toolbar.
The event can be used to change or remove existing clear cache
actions, as well as to add new actions. Therefore, the event also
contains, next to the usual "getter" and "setter" methods, the convenience
method
add() for the
cacheActions and
cacheActionIdentifiers arrays.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/toolbar/my-event-listener'
This event has been introduced which serves as a drop-in replacement for the removed
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][PageLayoutView::class]['modifyQuery']
hook.
Use the PSR-14 event
\TYPO3\CMS\Backend\View\Event\ModifyDatabaseQueryForContentEvent
to filter out certain content elements from being shown in the
Page module.
Example
Registration of the event in your extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-database-query-for-content'
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Backend\EventListener;
useTYPO3\CMS\Backend\View\Event\ModifyDatabaseQueryForContentEvent;
useTYPO3\CMS\Core\Database\Connection;
finalclassMyEventListener{
publicfunction__invoke(ModifyDatabaseQueryForContentEvent $event): void{
// Early return if we do not need to reactif ($event->getTable() !== 'tt_content') {
return;
}
// Retrieve QueryBuilder instance from event
$queryBuilder = $event->getQueryBuilder();
// Add an additional condition to the QueryBuilder for the table// Note: This is only a example, modify the QueryBuilder instance// here to your needs.
$queryBuilder = $queryBuilder->andWhere(
$queryBuilder->expr()->neq(
'some_field',
$queryBuilder->createNamedParameter(1, Connection::PARAM_INT),
),
);
// Set updated QueryBuilder to event
$event->setQueryBuilder($queryBuilder);
}
}
The PSR-14 event
\TYPO3\CMS\Backend\View\Event\ModifyDatabaseQueryForRecordListingEvent
allows to alter the query builder SQL
statement before a list of records is rendered in record lists, such as
the List module or an element browser.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-database-query-for-record-list'
This event serves as a more powerful and flexible alternative for the removed
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck']
hook.
The PSR-14 event
\TYPO3\CMS\Backend\Form\Event\ModifyEditFormUserAccessEvent\ModifyEditFormUserAccessEvent
provides the full database row of the record in question next to the
exception, which might have been set by the Core. Additionally, the event allows
to modify the user access decision in an object-oriented way, using
convenience methods.
In case any listener to the new event denies user access, while it was initially
allowed by Core, the
\TYPO3\CMS\Backend\Form\Exception\AccessDeniedListenerException
will be thrown.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-edit-form-user-access'
Listeners to the PSR-14 event
\TYPO3\CMS\Backend\Form\Event\ModifyFileReferenceControlsEvent
are able to modify the controls of a single file reference of a TCA type
file field. This event is similar to the
ModifyInlineElementControlsEvent, which is only available for TCA
type inline.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Listeners to this Event will be able to modify the controls
of a single file reference of a TCA type=file field.
getControls()
Returns all controls with their markup
Returns
array
setControls(array $controls)
Overwrite the controls
param $controls
the controls
getControl(string $identifier)
Returns the markup for the requested control
param $identifier
the identifier
Returns
string
setControl(string $identifier, string $markup)
Set a control with the given identifier and markup
IMPORTANT: Overwrites an existing control with the same identifier
param $identifier
the identifier
param $markup
the markup
hasControl(string $identifier)
Returns whether a control exists for the given identifier
param $identifier
the identifier
Returns
bool
removeControl(string $identifier)
Removes a control from the file reference, if it exists
param $identifier
the identifier
Return description
Whether the control could be removed
Returns
bool
getElementData()
Returns the whole element data
Returns
array
getRecord()
Returns the current record, the controls are created for
Returns
array
getParentUid()
Returns the uid of the parent (embedding) record (uid or NEW...)
Returns
string
getForeignTable()
Returns the table (foreign_table) the controls are created for
Returns
string
getFieldConfiguration()
Returns the TCA configuration of the TCA type=file field
Returns
array
isVirtual()
Returns whether the current records is only virtually shown and not physically part of the parent record
Returns
bool
ModifyFileReferenceEnabledControlsEvent
New in version 12.0
Listeners to the PSR-14 event
\TYPO3\CMS\Backend\Form\Event\ModifyFileReferenceEnabledControlsEvent
are able to modify the state (enabled or disabled) for the controls of a single
file reference of a TCA type file field. This
event is similar to the ModifyInlineElementEnabledControlsEvent, which
is only available for TCA type inline.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Listeners to this Event will be able to modify the state (enabled or disabled) for controls of a file reference
enableControl(string $identifier)
Enable a control, if it exists
param $identifier
the identifier
Return description
Whether the control could be enabled
Returns
bool
disableControl(string $identifier)
Disable a control, if it exists
param $identifier
the identifier
Return description
Whether the control could be disabled
Returns
bool
hasControl(string $identifier)
Returns whether a control exists for the given identifier
param $identifier
the identifier
Returns
bool
isControlEnabled(string $identifier)
Returns whether the control is enabled.
Note: Will also return FALSE in case no control exists for the requested identifier
param $identifier
the identifier
Returns
bool
getControlsState()
Returns all controls with their state (enabled or disabled)
Returns
array
getEnabledControls()
Returns all enabled controls
Returns
array
getElementData()
Returns the whole element data
Returns
array
getRecord()
Returns the current record of the controls are created for
Returns
array
getParentUid()
Returns the uid of the parent (embedding) record (uid or NEW...)
Returns
string
getForeignTable()
Returns the table (foreign_table) the controls are created for
Returns
string
getFieldConfiguration()
Returns the TCA configuration of the TCA type=file field
Returns
array
isVirtual()
Returns whether the current records is only virtually shown and not physically part of the parent record
Returns
bool
ModifyGenericBackendMessagesEvent
New in version 12.0
This event serves as direct replacement for the now removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['displayWarningMessages'] .
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\ModifyGenericBackendMessagesEvent
allows to add or alter messages that are displayed in the About
module (default start module of the TYPO3 backend).
Extensions such as the EXT:reports system extension
use this event to display custom messages based on the system status:
A generic backend message in the about module
Example
Registration of an event listener in your extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/add-message'
This event serves as a direct replacement for the now removed
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['Backend/Form/Element/ImageManipulationElement']['previewUrl']
hook.
The PSR-14 event
\TYPO3\CMS\Backend\Form\Event\ModifyImageManipulationPreviewUrlEvent
can be used to modify the preview URL within the
image manipulation element, used
for example for the
crop field of the
sys_file_reference table.
As soon as a preview URL is set, the image manipulation element will display
a corresponding button in the footer of the modal window, next to the
Cancel and Accept buttons. On click, the preview
URL will be opened in a new window.
Note
The element's crop variants will always be appended to the preview URL
as JSON-encoded string, using the cropVariants parameter.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-imagemanipulation-previewurl'
Listeners to this Event will be able to modify the preview url, used in the ImageManipulation element
getDatabaseRow()
Returns
array
getFieldConfiguration()
Returns
array
getFile()
Returns
\TYPO3\CMS\Core\Resource\File
getPreviewUrl()
Returns
string
setPreviewUrl(string $previewUrl)
param $previewUrl
the previewUrl
ModifyInlineElementControlsEvent
New in version 12.0
This event, together with ModifyInlineElementEnabledControlsEvent,
serves as a more powerful and flexible replacement
for the removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms_inline.php']['tceformsInlineHook']
The PSR-14 event
\TYPO3\CMS\Backend\Form\Event\ModifyInlineElementControlsEvent
is called after the markup for all enabled controls has been generated. It
can be used to either change the markup of a control, to add a new control
or to completely remove a control.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-enabled-controls'method:'modifyEnabledControls'-name:event.listeneridentifier:'my-extension/backend/modify-controls'method:'modifyControls'
Listeners to this Event will be able to modify the controls of an inline element
getControls()
Returns all controls with their markup
Returns
array
setControls(array $controls)
Overwrite the controls
param $controls
the controls
getControl(string $identifier)
Returns the markup for the requested control
param $identifier
the identifier
Returns
string
setControl(string $identifier, string $markup)
Set a control with the given identifier and markup
IMPORTANT: Overwrites an existing control with the same identifier
param $identifier
the identifier
param $markup
the markup
hasControl(string $identifier)
Returns whether a control exists for the given identifier
param $identifier
the identifier
Returns
bool
removeControl(string $identifier)
Removes a control from the inline element, if it exists
param $identifier
the identifier
Return description
Whether the control could be removed
Returns
bool
getElementData()
Returns the whole element data
Returns
array
getRecord()
Returns the current record of the controls are created for
Returns
array
getParentUid()
Returns the uid of the parent (embedding) record (uid or NEW...)
Returns
string
getForeignTable()
Returns the table (foreign_table) the controls are created for
Returns
string
getFieldConfiguration()
Returns the TCA configuration of the inline record field
Returns
array
isVirtual()
Returns whether the current records is only virtually shown and not physically part of the parent record
Returns
bool
ModifyInlineElementEnabledControlsEvent
New in version 12.0
This event, together with ModifyInlineElementControlsEvent,
serves as a more powerful and flexible replacement
for the removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms_inline.php']['tceformsInlineHook']
The PSR-14 event
\TYPO3\CMS\Backend\Form\Event\ModifyInlineElementEnabledControlsEvent
is called before any control markup is generated. It can be used to
enable or disable each control. With this event it is therefore possible
to enable a control, which is disabled in TCA, only for some use case.
Listeners to this Event will be able to modify the state (enabled or disabled) for controls of an inline element
enableControl(string $identifier)
Enable a control, if it exists
param $identifier
the identifier
Return description
Whether the control could be enabled
Returns
bool
disableControl(string $identifier)
Disable a control, if it exists
param $identifier
the identifier
Return description
Whether the control could be disabled
Returns
bool
hasControl(string $identifier)
Returns whether a control exists for the given identifier
param $identifier
the identifier
Returns
bool
isControlEnabled(string $identifier)
Returns whether the control is enabled.
Note: Will also return FALSE in case no control exists for the requested identifier
param $identifier
the identifier
Returns
bool
getControlsState()
Returns all controls with their state (enabled or disabled)
Returns
array
getEnabledControls()
Returns all enabled controls
Returns
array
getElementData()
Returns the whole element data
Returns
array
getRecord()
Returns the current record of the controls are created for
Returns
array
getParentUid()
Returns the uid of the parent (embedding) record (uid or NEW...)
Returns
string
getForeignTable()
Returns the table (foreign_table) the controls are created for
Returns
string
getFieldConfiguration()
Returns the TCA configuration of the inline record field
Returns
array
isVirtual()
Returns whether the current records is only virtually shown and not physically part of the parent record
Returns
bool
ModifyLinkExplanationEvent
New in version 12.0
This event serves as a more powerful and flexible alternative
for the removed
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['linkHandler']
hook.
While the removed hook effectively only allowed to modify the link explanation
of TCA link fields in case the resolved link type did not already match
one of those, implemented by TYPO3 itself, the new event allows to
always modify the link explanation of any type. Additionally, this also allows
to modify the additionalAttributes, displayed below the actual link
explanation field. This is especially useful for extended link handler setups.
To modify the link explanation, the following methods are available:
getLinkExplanation(): Returns the current link explanation data
setLinkExplanation(): Set the link explanation data
getLinkExplanationValue(): Returns a specific link explanation value
setLinkExplanationValue(): Sets a specific link explanation value
The link explanation array usually contains the following values:
text : The text to show in the link explanation field
icon: The markup for the icon, displayed in front of the link explanation field
additionalAttributes: The markup for additional attributes, displayed below the link explanation field
The current context can be evaluated using the following methods:
getLinkData(): Returns the resolved link data, such as the page uid
getLinkParts(): Returns the resolved link parts, such as url, target and additionalParams
getElementData(): Returns the full FormEngine $data array for the current element
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-link-explanation'
This event has been introduced together with
ModifyAllowedItemsEvent to
serve as a direct replacement for the following removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['LinkBrowser']['hooks'] .
It replaces the method
modifyLinkHandlers() in this hook.
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\ModifyLinkHandlersEvent
is triggered before link handlers are executed, allowing listeners
to modify the set of handlers that will be used.
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/link-handlers'
This event allows extensions to modify the list of link handlers and their configuration before they are invoked.
getLinkHandlers()
Returns
array<string,array>
getLinkHandler(string $name)
Gets an individual handler by name.
param $name
The handler name, including trailing period.
Return description
The handler definition, or null if not defined.
Returns
array<string,mixed>|null
setLinkHandler(string $name, array $handler)
Sets a handler by name, overwriting it if it already exists.
param $name
The handler name, including trailing period.
param $handler
the handler
Returns
$this
getCurrentLinkParts()
Returns
array<string,mixed>
ModifyNewContentElementWizardItemsEvent
New in version 12.0
This event serves as a more powerful and flexible alternative
for the removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms']['db_new_content_el']['wizardItemsHook'] .
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\ModifyNewContentElementWizardItemsEvent
is called after TYPO3 has already prepared the wizard items,
defined in page TSconfig (mod.wizards.newContentElement.wizardItems).
The event allows listeners to modify any available wizard item as well
as adding new ones. It is therefore possible for the listeners to, for example,
change the configuration, the position or to remove existing items altogether.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-wizard-items'
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\ModifyPageLayoutContentEvent
allows to modify page module content.
It is possible to add additional content, overwrite existing
content or reorder the content.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/backend/modify-page-module-content'
Listeners to this Event will be able to modify the header and footer content of the page module
getRequest()
Returns
\Psr\Http\Message\ServerRequestInterface
getModuleTemplate()
Returns
\TYPO3\CMS\Backend\Template\ModuleTemplate
setHeaderContent(string $content)
Set content for the header. Can also be used to e.g. reorder existing content.
IMPORTANT: This overwrites existing content from previous listeners!
param $content
the content
addHeaderContent(string $content)
Add additional content to the header
param $content
the content
getHeaderContent()
Returns
string
setFooterContent(string $content)
Set content for the footer. Can also be used to e.g. reorder existing content.
IMPORTANT: This overwrites existing content from previous listeners!
param $content
the content
addFooterContent(string $content)
Add additional content to the footer
param $content
the content
getFooterContent()
Returns
string
ModifyPageLayoutOnLoginProviderSelectionEvent
The PSR-14 event
\TYPO3\CMS\Backend\LoginProvider\Event\ModifyPageLayoutOnLoginProviderSelectionEvent
allows to modify variables for the view depending on a special login provider
set in the controller.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Allows to modify variables for the view depending on a special login provider set in the controller.
getController()
Returns
\TYPO3\CMS\Backend\Controller\LoginController
getView()
Returns
\TYPO3\CMS\Fluid\View\StandaloneView
getPageRenderer()
Returns
\TYPO3\CMS\Core\Page\PageRenderer
ModifyQueryForLiveSearchEvent
New in version 12.0
The PSR-14 event
\TYPO3\CMS\Backend\Search\Event\ModifyQueryForLiveSearchEvent
can be used to modify the live search queries in the backend.
This can be used to adjust the limit for a specific table or to change the
result order.
This event is fired in the
\TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch class
and allows extensions to modify the query builder
instance before execution.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/modify-query-for-live-search-event-listener'
PSR-14 event to modify the query builder instance for the live search
getQueryBuilder()
Returns
\TYPO3\CMS\Core\Database\Query\QueryBuilder
getTableName()
Returns
string
ModifyRecordListHeaderColumnsEvent
New in version 11.4
Changed in version 12.0
Due to the integration of EXT:recordlist into EXT:backend the namespace of
the event changed from
\TYPO3\CMS\Recordlist\Event\ModifyRecordListHeaderColumnsEvent
to
\TYPO3\CMS\Backend\RecordList\Event\ModifyRecordListHeaderColumnsEvent .
For TYPO3 v12 the moved class is available as an alias under the old
namespace to allow extensions to be compatible with TYPO3 v11 and v12.
The PSR-14 event
\TYPO3\CMS\Backend\RecordList\Event\ModifyRecordListHeaderColumnsEvent
allows to modify the header columns for a table in the record list.
Add a new column or override an existing one. Latter is only possible,
in case $columnName is given. Otherwise, the column will be added with
a numeric index, which is generally not recommended.
Note: Due to the behaviour of DatabaseRecordList, just adding a column
does not mean that it is also displayed. The internal $fieldArray needs
to be adjusted as well. This method only adds the column to the data array.
Therefore, this method should mainly be used to edit existing columns, e.g.
change their label.
param $column
the column
param $columnName
the columnName, default: ''
hasColumn(string $columnName)
Whether the column exists
param $columnName
the columnName
Returns
bool
getColumn(string $columnName)
Get column by its name
param $columnName
the columnName
Return description
The column or NULL if the column does not exist
Returns
string|null
removeColumn(string $columnName)
Remove column by its name
param $columnName
the columnName
Return description
Whether the column could be removed - Will thereforereturn FALSE if the column to remove does not exist.
Returns
bool
setColumns(array $columns)
param $columns
the columns
getColumns()
Returns
array
setHeaderAttributes(array $headerAttributes)
param $headerAttributes
the headerAttributes
getHeaderAttributes()
Returns
array
getTable()
Returns
string
getRecordIds()
Returns
array
getRecordList()
Returns the current DatabaseRecordList instance.
Returns
\TYPO3\CMS\Backend\RecordList\DatabaseRecordList
ModifyRecordListRecordActionsEvent
New in version 11.4
Changed in version 12.0
Due to the integration of EXT:recordlist into EXT:backend the namespace of
the event changed from
\TYPO3\CMS\Recordlist\Event\ModifyRecordListRecordActionsEvent
to
\TYPO3\CMS\Backend\RecordList\Event\ModifyRecordListRecordActionsEvent .
For TYPO3 v12 the moved class is available as an alias under the old
namespace to allow extensions to be compatible with TYPO3 v11 and v12.
The PSR-14 event
\TYPO3\CMS\Backend\RecordList\Event\ModifyRecordListRecordActionsEvent
allows to modify the displayed record actions (for example
edit, copy, delete) for a table in
the record list.
Add a new action or override an existing one. Latter is only possible,
in case $columnName is given. Otherwise, the column will be added with
a numeric index, which is generally not recommended. It's also possible
to define the position of an action with either the "before" or "after"
argument, while their value must be an existing action.
Note: In case non or an invalid $group is provided, the new action will
be added to the secondary group.
param $action
the action
param $actionName
the actionName, default: ''
param $group
the group, default: ''
param $before
the before, default: ''
param $after
the after, default: ''
hasAction(string $actionName, string $group = '')
Whether the action exists in the given group. In case non or
an invalid $group is provided, both groups will be checked.
param $actionName
the actionName
param $group
the group, default: ''
Returns
bool
getAction(string $actionName, string $group = '')
Get action by its name. In case the action exists in both groups
and non or an invalid $group is provided, the action from the
"primary" group will be returned.
Remove action by its name. In case the action exists in both groups
and non or an invalid $group is provided, the action will be removed
from both groups.
param $actionName
the actionName
param $group
the group, default: ''
Return description
Whether the action could be removed - Will thereforereturn FALSE if the action to remove does not exist.
Returns
bool
getActionGroup(string $group)
Get the actions of a specific group
param $group
the group
Returns
?array
setActions(array $actions)
param $actions
the actions
getActions()
Returns
array
getTable()
Returns
string
getRecord()
Returns
array
getRecordList()
Returns the current DatabaseRecordList instance.
Returns
\TYPO3\CMS\Backend\RecordList\DatabaseRecordList
ModifyRecordListTableActionsEvent
New in version 11.4
Changed in version 12.0
Due to the integration of EXT:recordlist into EXT:backend the namespace of
the event changed from
\TYPO3\CMS\Recordlist\Event\ModifyRecordListTableActionsEvent
to
\TYPO3\CMS\Backend\RecordList\Event\ModifyRecordListTableActionsEvent .
For TYPO3 v12 the moved class is available as an alias under the old
namespace to allow extensions to be compatible with TYPO3 v11 and v12.
The PSR-14 event
\TYPO3\CMS\Backend\RecordList\Event\ModifyRecordListTableActionsEvent
allows to modify the multi record selection actions (for example
edit, copy to clipboard) for a table in the record list.
Usage
An example registration of the events in your extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/recordlist/my-event-listener'method:'modifyRecordActions'-name:event.listeneridentifier:'my-extension/recordlist/my-event-listener'method:'modifyHeaderColumns'-name:event.listeneridentifier:'my-extension/recordlist/my-event-listener'method:'modifyTableActions'
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Backend\EventListener;
usePsr\Log\LoggerInterface;
useTYPO3\CMS\Backend\RecordList\Event\ModifyRecordListHeaderColumnsEvent;
useTYPO3\CMS\Backend\RecordList\Event\ModifyRecordListRecordActionsEvent;
useTYPO3\CMS\Backend\RecordList\Event\ModifyRecordListTableActionsEvent;
finalclassMyEventListener{
private LoggerInterface $logger;
publicfunction__construct(LoggerInterface $logger){
$this->logger = $logger;
}
publicfunctionmodifyRecordActions(ModifyRecordListRecordActionsEvent $event): void{
$currentTable = $event->getTable();
// Add a custom action for a custom table in the secondary action bar, before the "move" actionif ($currentTable === 'my_custom_table' && !$event->hasAction('myAction')) {
$event->setAction(
'<button>My Action</button>',
'myAction',
'secondary',
'move',
);
}
// Remove the "viewBig" action in case more than 4 actions exist in the groupif (count($event->getActionGroup('secondary')) > 4 && $event->hasAction('viewBig')) {
$event->removeAction('viewBig');
}
// Move the "delete" action after the "edit" action
$event->setAction('', 'delete', 'primary', '', 'edit');
}
publicfunctionmodifyHeaderColumns(ModifyRecordListHeaderColumnsEvent $event): void{
// Change label of "control" column
$event->setColumn('Custom Controls', '_CONTROL_');
// Add a custom class for the table header row
$event->setHeaderAttributes(['class' => 'my-custom-class']);
}
publicfunctionmodifyTableActions(ModifyRecordListTableActionsEvent $event): void{
// Remove "edit" action and log, if this failed
$actionRemoved = $event->removeAction('unknown');
if (!$actionRemoved) {
$this->logger->warning('Action "unknown" could not be removed');
}
// Add a custom clipboard action after "copyMarked"
$event->setAction('<button>My action</button>', 'myAction', '', 'copyMarked');
// Set a custom label for the case, no actions are available for the user
$event->setNoActionLabel('No actions available due to missing permissions.');
}
}
Add a new action or override an existing one. Latter is only possible,
in case $actionName is given. Otherwise, the action will be added with
a numeric index, which is generally not recommended. It's also possible
to define the position of an action with either the "before" or "after"
argument, while their value must be an existing action.
param $action
the action
param $actionName
the actionName, default: ''
param $before
the before, default: ''
param $after
the after, default: ''
hasAction(string $actionName)
Whether the action exists
param $actionName
the actionName
Returns
bool
getAction(string $actionName)
Get action by its name
param $actionName
the actionName
Return description
The action or NULL if the action does not exist
Returns
string|null
removeAction(string $actionName)
Remove action by its name
param $actionName
the actionName
Return description
Whether the action could be removed - Will thereforereturn FALSE if the action to remove does not exist.
Returns
bool
setActions(array $actions)
param $actions
the actions
getActions()
Returns
array
setNoActionLabel(string $noActionLabel)
param $noActionLabel
the noActionLabel
getNoActionLabel()
Get the label, which will be displayed, in case no
action is available for the current user. Note: If
this returns an empty string, this only means that
no other listener set a label before. TYPO3 will
always fall back to a default if this remains empty.
Returns
string
getTable()
Returns
string
getRecordIds()
Returns
array
getRecordList()
Returns the current DatabaseRecordList instance.
Returns
\TYPO3\CMS\Backend\RecordList\DatabaseRecordList
ModifyResultItemInLiveSearchEvent
New in version 12.2
The PSR-14 event
\TYPO3\CMS\Backend\Search\Event\ModifyResultItemInLiveSearchEvent
allows extension developers to take control over search result
items rendered in the backend search.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/add-live-search-result-actions-listener'
PSR-14 event to modify as result item created by the live search
getResultItem()
Returns
\TYPO3\CMS\Backend\Search\LiveSearch\ResultItem
PageContentPreviewRenderingEvent
New in version 12.0
This event has been introduced which serves as a drop-in replacement for the removed
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem']
hook.
Use the PSR-14 event
\TYPO3\CMS\Backend\View\Event\PageContentPreviewRenderingEvent
to ship an alternative rendering for a specific content type or
to manipulate the record data of a content element.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Backend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/preview-rendering-example-ctype'
Due to the integration of EXT:recordlist into EXT:backend the namespace of
the event changed from
\TYPO3\CMS\Recordlist\Event\RenderAdditionalContentToRecordListEvent
to
\TYPO3\CMS\Backend\Controller\Event\RenderAdditionalContentToRecordListEvent .
For TYPO3 v12 the moved class is available as an alias under the old
namespace to allow extensions to be compatible with TYPO3 v11 and v12.
The PSR-14 event
\TYPO3\CMS\Backend\Controller\Event\RenderAdditionalContentToRecordListEvent
allows to add content before or after the main content of the List
module.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is triggered when a "SU" (switch user) action has been triggered
getSessionId()
Returns
string
getTargetUser()
Returns
array
getCurrentUser()
Returns
array
SystemInformationToolbarCollectorEvent
The PSR-14 event
\TYPO3\CMS\Backend\Backend\Event\SystemInformationToolbarCollectorEvent
allows to enrich the system information toolbar in the TYPO3 backend top toolbar
with various information.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
When user groups are loaded, for example when a backend editor's groups and permissions
are calculated, a new PSR-14 event AfterGroupsResolvedEvent is fired.
This event contains a list of retrieved groups from the database which can
be modified via event listeners. For example, more groups might be added when a
particular user logs in or is seated at a special location.
Hint
This event acts as a substitution for the removed TYPO3 hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['fetchGroups_postProcessing'] .
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Event fired after user groups have been resolved for a specific user
getSourceDatabaseTable()
Return description
'be_groups' or 'fe_groups' depending on context.
Returns
string
getGroups()
List of group records including sub groups as resolved by core.
Note order is important: A user with main groups "1,2", where 1 has sub group 3,
results in "3,1,2" as record list array - sub groups are listed before the group
that includes the sub group.
Returns
array
setGroups(array $groups)
List of group records as manipulated by the event.
param $groups
the groups
getOriginalGroupIds()
List of group uids directly attached to the user
Returns
array
getUserData()
Full user record with all fields
Returns
array
AfterUserLoggedInEvent
New in version 12.3
The event replaces the deprecated hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['backendUserLogin'] .
The purpose of the PSR-14 event
\TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent
is to trigger any kind of action when a backend user has been successfully
logged in.
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Authentication\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/after-user-logged-in'
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Authentication\EventListener;
useTYPO3\CMS\Core\Authentication\BackendUserAuthentication;
useTYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent;
finalclassMyEventListener{
publicfunction__invoke(AfterUserLoggedInEvent $event): void{
if (
$event->getUser() instanceof BackendUserAuthentication
&& $event->getUser()->isAdmin()
) {
// Do something like: Clear all caches after login
}
}
}
Copied!
Note
With TYPO3 v13 the event is also dispatched when a successful frontend user
login is performed. Prepare your code and check, if the user is an instance
of
\TYPO3\CMS\Core\Authentication\BackendUserAuthentication (where
necessary) like in the example above.
The event replaces the deprecated hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_post_processing'] .
The purpose of the PSR-14 event
\TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedOutEvent
is to trigger any kind of action when a user has been successfully logged out.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The event
\TYPO3\CMS\Core\Authentication\Event\BeforeRequestTokenProcessedEvent
allows to intercept or adjust a request token
during active user authentication process.
Example
The event can be used to generate the request token individually. This can be
the case when you are not using a login callback and have not the possibility
to submit a request token:
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Authentication\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/process-request-token-listener'
The event replaces the deprecated hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'] .
The purpose of the PSR-14 event
\TYPO3\CMS\Core\Authentication\Event\BeforeUserLogoutEvent is to trigger
any kind of action before a user will be logged out.
The event 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.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The event replaces the deprecated hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postLoginFailureProcessing']
.
The purpose of the PSR-14 event
\TYPO3\CMS\Core\Authentication\Event\LoginAttemptFailedEvent
is to allow to notify remote systems about failed logins.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Authentication\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/login-attempt-failed'
This event was introduced to replace and improve the method
parseDataStructureByIdentifierPostProcess() of the hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] .
The PSR-14 event
\TYPO3\CMS\Core\Configuration\Event\AfterFlexFormDataStructureIdentifierInitializedEvent
can be used to control the FlexForm parsing in an
object-oriented approach.
Registration of the events in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Configuration\EventListener\FlexFormParsingModifyEventListener:tags:-name:event.listeneridentifier:'my-extension/set-data-structure'method:'setDataStructure'-name:event.listeneridentifier:'my-extension/modify-data-structure'method:'modifyDataStructure'-name:event.listeneridentifier:'my-extension/set-data-structure-identifier'method:'setDataStructureIdentifier'-name:event.listeneridentifier:'my-extension/modify-data-structure-identifier'method:'modifyDataStructureIdentifier'
Listeners to this event are able to modify or enhance the data structure identifier,
which is used for a given TCA flex field.
This event can be used to add additional data to an identifier. Be careful here, especially if
stuff from the source record like uid or pid is added! This may easily lead to issues with
data handler details like copy or move records, localization and version overlays.
Test this very well! Multiple listeners may add information to the same identifier here -
take care to namespace array keys. Information added here can be later used in the
data structure related PSR-14 Events (BeforeFlexFormDataStructureParsedEvent and
AfterFlexFormDataStructureParsedEvent) again.
See the note on FlexFormTools regarding the schema of $dataStructure.
getFieldTca()
Returns the full TCA of the currently handled field, having
type=flex set.
Returns
array
getTableName()
Returns
string
getFieldName()
Returns
string
getRow()
Returns the whole database row of the current record.
Returns
array
setIdentifier(array $identifier)
Allows to modify or completely replace the initialized data
structure identifier.
param $identifier
the identifier
getIdentifier()
Returns the initialized data structure identifier, which has
either been defined by an event listener or set to the default
by the FlexFormTools component.
Returns
array
AfterFlexFormDataStructureParsedEvent
New in version 12.0
This event was introduced to replace and improve the method
getDataStructureIdentifierPostProcess() of the hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] .
The PSR-14 event
\TYPO3\CMS\Core\Configuration\Event\AfterFlexFormDataStructureParsedEvent
can be used to control the FlexForm parsing in an
object-oriented approach.
Listeners to this event are able to modify or enhance a flex form data
structure that corresponds to a given identifier, after it was parsed and
before it is used by further components.
Note: Since this event is not stoppable, all registered listeners are
called. Therefore, you might want to namespace your identifiers in a way,
that there is little chance they overlap (e.g. prefix with extension name).
See the note on FlexFormTools regarding the schema of $dataStructure.
getIdentifier()
Returns
array
getDataStructure()
Returns the current data structure, which has been processed and
parsed by the FlexFormTools component. Might contain additional
data from previously called listeners.
Returns
array
setDataStructure(array $dataStructure)
Allows to modify or completely replace the parsed data
structure identifier.
param $dataStructure
the dataStructure
AfterTcaCompilationEvent
The PSR-14 event
\TYPO3\CMS\Core\Configuration\Event\AfterTcaCompilationEvent is
dispatched after
$GLOBALS['TCA'] is built to allow to further manipulate
the TCA.
Note
It is possible to check against the original TCA as this is stored within
$GLOBALS['TCA'] before this event is fired.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event was introduced to replace and improve the method
getDataStructureIdentifierPreProcess() of the hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] .
The PSR-14 event
\TYPO3\CMS\Core\Configuration\Event\BeforeFlexFormDataStructureIdentifierInitializedEvent
can be used to control the FlexForm parsing in an
object-oriented approach.
Listeners to this event are able to specify the data structure identifier,
used for a given TCA flex field.
Listeners should call ->setIdentifier() to set the identifier or ignore the
event to allow other listeners to set it. Do not set an empty string as this
will immediately stop event propagation!
The identifier SHOULD include the keys specified in the Identifier definition
on FlexFormTools, and nothing else. Adding other keys may or may not work,
depending on other code that is enabled, and they are not guaranteed nor
covered by BC guarantees.
Warning: If adding source record details like the uid or pid here, this may turn out to be fragile.
Be sure to test scenarios like workspaces and data handler copy/move well, additionally, this may
break in between different core versions.
It is probably a good idea to return at least something like [ 'type' => 'myExtension', ... ], see
the core internal 'tca' and 'record' return values below
See the note on FlexFormTools regarding the schema of $dataStructure.
getFieldTca()
Returns the full TCA of the currently handled field, having
type=flex set.
Returns
array
getTableName()
Returns
string
getFieldName()
Returns
string
getRow()
Returns the whole database row of the current record.
Returns
array
setIdentifier(array $identifier)
Allows to define the data structure identifier for the TCA field.
Setting an identifier will immediately stop propagation. Avoid
setting this parameter to an empty array as this will also stop
propagation.
param $identifier
the identifier
getIdentifier()
Returns the current data structure identifier, which will always be
null for listeners, since the event propagation is
stopped as soon as a listener defines an identifier.
Returns
?array
isPropagationStopped()
Returns
bool
Migration
Using the removed hook method
getDataStructureIdentifierPreProcess() of
the hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['modifyDifferenceArray']
previously required implementations to always return an
array.
This means, implementations returned an empty
array in case they did
not want to set an identifier, allowing further implementations to be
called.
This behaviour has now changed. As soon as a listener sets the identifier
using the
setIdentifier() method, the event propagation is stopped
immediately and no further listeners are being called. Therefore, listeners
should avoid setting an empty
array but should just "return" without
any change to the
$event object in such a case.
BeforeFlexFormDataStructureParsedEvent
New in version 12.0
This event was introduced to replace and improve the method
parseDataStructureByIdentifierPreProcess() of the hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] .
The PSR-14 event
\TYPO3\CMS\Core\Configuration\Event\BeforeFlexFormDataStructureParsedEvent
can be used to control the FlexForm parsing in an
object-oriented approach.
Listeners to this event are able to specify a flex form data structure that
corresponds to a given identifier.
Listeners should call ->setDataStructure() to set the data structure (this
can either be a resolved data structure string, a "FILE:" reference or a
fully parsed data structure as array) or ignore the event to allow other
listeners to set it. Do not set an empty array or string as this will
immediately stop event propagation!
See the note on FlexFormTools regarding the schema of $dataStructure.
getDataStructure()
Returns the current data structure, which will always be null
for listeners, since the event propagation is stopped as soon as
a listener sets a data structure.
Returns
array|string|?null
setDataStructure(array|string $dataStructure)
Allows to either set an already parsed data structure as array,
a file reference or the XML structure as string. Setting a data
structure will immediately stop propagation. Avoid setting this parameter
to an empty array or string as this will also stop propagation.
param $dataStructure
the dataStructure
getIdentifier()
Returns
array
isPropagationStopped()
Returns
bool
Migration
Using the removed hook method
parseDataStructureByIdentifierPreProcess()
of hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['modifyDifferenceArray']
previously required implementations to always return an
array or
string. Implementations returned an empty
array or empty
string in case they did not want to set a data structure.
This behaviour has now changed. As soon as a listener sets a data structure
using the
setDataStructure() method, the event propagation is stopped
immediately and no further listeners are called.
Therefore, listeners should avoid setting an empty
array or an empty
string but should just "return" without any change to the
$event object in such a case.
ModifyLoadedPageTsConfigEvent
Extensions can modify page TSconfig
entries that can be overridden or added, based on the root line.
Changed in version 12.2
The event has moved its namespace from
\TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent to
\TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent .
Apart from that no changes were made. TYPO3 v12 triggers both the old
and the new event, and TYPO3 v13 will stop calling the old event.
See also Compatibility with TYPO3 v11 and v12.
Extensions can modify page TSconfig entries that can be overridden or added, based on the root line
getTsConfig()
Returns
array
addTsConfig(string $tsConfig)
param $tsConfig
the tsConfig
setTsConfig(array $tsConfig)
param $tsConfig
the tsConfig
getRootLine()
Returns
array
Compatibility with TYPO3 v11 and v12
Extensions that want to stay compatible with both TYPO3 v11 and v12 and prepare v13
compatibility as much as possible should start listening for the new event as well,
and suppress handling of the old event in TYPO3 v12 to not handle things twice.
Example from b13/bolt extension:
Registration of both events in the file Services.yaml:
EXT:bolt/Configuration/Services.yaml
services:# Place here the default dependency injection configurationB13\Bolt\TsConfig\Loader:public:truetags:# Remove when TYPO3 v11 compat is dropped-name:event.listeneridentifier:'add-site-configuration-v11'event:TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEventmethod:'addSiteConfigurationCore11'# TYPO3 v12 and above-name:event.listeneridentifier:'add-site-configuration'event:TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEventmethod:'addSiteConfiguration'
Handle the old event in TYPO3 v11 only, but skip old event with TYPO3 v12:
EXT:bolt/Classes/TsConfig/Loader.php
<?phpdeclare(strict_types=1);
namespaceB13\Bolt\TsConfig;
useTYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEventasLegacyModifyLoadedPageTsConfigEvent;
useTYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent;
classLoader{
publicfunctionaddSiteConfigurationCore11(LegacyModifyLoadedPageTsConfigEvent $event): void{
if (class_exists(ModifyLoadedPageTsConfigEvent::class)) {
// TYPO3 v12 calls both old and new event. Check for class existence of new event to// skip handling of old event in v12, but continue to work with < v12.// Simplify this construct when v11 compat is dropped, clean up Services.yaml.return;
}
$this->findAndAddConfiguration($event);
}
publicfunctionaddSiteConfiguration(ModifyLoadedPageTsConfigEvent $event): void{
$this->findAndAddConfiguration($event);
}
protectedfunctionfindAndAddConfiguration($event): void{
// Business code
}
}
The PSR-14 event
\TYPO3\CMS\Core\Configuration\Event\SiteConfigurationBeforeWriteEvent
allows the modification of the site configuration array
before writing the configuration to disk.
Event fired before a site configuration is written to a yaml file
allows dynamic modification of the site's configuration before writing.
getSiteIdentifier()
Returns
string
getConfiguration()
Returns
array
setConfiguration(array $configuration)
param $configuration
overwrite the configuration array of the site
SiteConfigurationLoadedEvent
New in version 12.0
The PSR-14 event
\TYPO3\CMS\Core\Configuration\Event\SiteConfigurationLoadedEvent
allows the modification of the site configuration array
before loading the configuration.
The PSR-14 event
\TYPO3\CMS\Core\Core\Event\BootCompletedEvent is fired
on every request when TYPO3 has been fully booted, right after all configuration
files have been added.
This event complements the AfterTcaCompilationEvent which is executed
after TCA configuration has been assembled.
Use cases for this event include running extension's code which needs to be
executed at any time and needs TYPO3's full configuration including all loaded
extensions.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Bootstrap\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/boot-completed'
The PSR-14 event
\TYPO3\CMS\Core\Database\Event\AlterTableDefinitionStatementsEvent
allows to intercept the
CREATE TABLE statement from all loaded
extensions.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Core\DataHandling\Event\AppendLinkHandlerElementsEvent
is fired so listeners can intercept and add elements when checking
links within the soft reference parser.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Event fired so listeners can intercept add elements when checking links within the SoftRef parser
getLinkParts()
Returns
array
getContent()
Returns
string
getElements()
Returns
array
getIdx()
Returns
int
getTokenId()
Returns
string
setLinkParts(array $linkParts)
param $linkParts
the linkParts
setContent(string $content)
param $content
the content
setElements(array $elements)
param $elements
the elements
addElements(array $elements)
param $elements
the elements
isResolved()
Returns
bool
IsTableExcludedFromReferenceIndexEvent
The PSR-14 event
\TYPO3\CMS\Core\DataHandling\Event\IsTableExcludedFromReferenceIndexEvent
allows to intercept, if a certain table should be excluded from the
reference index.
There is no need to add tables without a definition in
$GLOBALS['TCA']
since the reference index only handles those.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event serves as a replacement for the removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'] .
The PSR-14 event
\TYPO3\CMS\Core\Domain\Event\AfterRecordLanguageOverlayEvent
can be used to modify the actual translated record (if found) to add additional
information or perform custom processing of the record.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Event which is fired after a record was translated (or tried to be localized).
getTable()
Returns
string
getRecord()
Returns
array
getLanguageAspect()
Returns
\TYPO3\CMS\Core\Context\LanguageAspect
setLocalizedRecord(?array $localizedRecord)
param $localizedRecord
the localizedRecord
getLocalizedRecord()
Returns
?array
overlayingWasAttempted()
Determines if the overlay functionality happened, thus, returning the lo
Returns
bool
BeforePageLanguageOverlayEvent
New in version 12.0
This event serves as a replacement for the removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPageOverlay'] .
The PSR-14 event
\TYPO3\CMS\Core\Domain\Event\BeforePageLanguageOverlayEvent
is a special event which is fired when TYPO3 is about to do the language overlay
of one or multiple pages, which could be one full record or multiple page IDs.
This event is fired only for pages and in-between the events
BeforeRecordLanguageOverlayEvent and AfterRecordLanguageOverlayEvent.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event serves as replacement for the removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'] .
The PSR-14 event
\TYPO3\CMS\Core\Domain\Event\BeforeRecordLanguageOverlayEvent
can be used to modify information (such as the
LanguageAspect or the actual incoming
record from the database) before the database is queried.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event serves as replacement for the removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_checkEnableFields'] .
The PSR-14 event
\TYPO3\CMS\Core\Domain\Access\RecordAccessGrantedEvent
can be used to either define whether a record access is granted
for a user, or to modify the record in question. In case the
$accessGranted
property is set (either
true or
false), the defined settings are
directly used, skipping any further event listener as well as any further
evaluation.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Domain\Access\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/set-access-granted'
The PSR-14 event
\TYPO3\CMS\Core\Html\Event\BrokenLinkAnalysisEvent
can be used to get information about broken links set in the
rich text editor (RTE).
The procedure for marking the broken links in the RTE is as follow:
The RTE content is fetched from the database. Before it is displayed in
the edit form, RTE transformations are performed.
The transformation function parses the text and detects links.
For each link, a new PSR-14 event is dispatched.
If a listener is attached, it may set the link as broken and will set
the link as "checked".
If a link is detected as broken, RTE will mark it as broken.
This functionality is implemented in the system extension
linkvalidator. Other extensions can use the
event to override the default behaviour.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Core\Mail\Event\AfterMailerInitializationEvent
is fired once a new mailer is instantiated with specific transport
settings. So it is possible to add custom mailing settings.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Mail\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/after-mailer-initialization'
This event is fired once a new Mailer is instantiated with specific transport settings.
So it is possible to add custom mailing settings.
getMailer()
Returns
\Symfony\Component\Mailer\MailerInterface
AfterMailerSentMessageEvent
New in version 12.0
The PSR-14 event
\TYPO3\CMS\Core\Mail\Event\AfterMailerSentMessageEvent
is dispatched as soon as the message has been sent via the corresponding
\Symfony\Component\Mailer\Transport\TransportInterface.
It receives the current mailer instance, which depends on the implementation -
usually
\TYPO3\CMS\Core\Mail\Mailer . It contains the
\Symfony\Component\Mailer\SentMessage object, which can be retrieved
using the
getSentMessage() method.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Mail\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/process-sent-message'
This event is fired once a Mailer has sent a message and allows listeners to execute
further code afterwards, depending on the result, e.g. the SentMessage.
Note: Usually TYPO3CMSCoreMailMailer is given to the event. This implementation
allows to retrieve the SentMessage using the getSentMessage() method. Depending
on the Transport, used to send the message, this might also be NULL.
getMailer()
Returns
\Symfony\Component\Mailer\MailerInterface
BeforeMailerSentMessageEvent
New in version 12.0
The PSR-14 event
\TYPO3\CMS\Core\Mail\Event\BeforeMailerSentMessageEvent
is dispatched before the message is sent by the mailer and can be
used to manipulate the
\Symfony\Component\Mime\RawMessage and the
\Symfony\Component\Mailer\Envelope. Usually a
\Symfony\Component\Mime\Email or
\TYPO3\CMS\Core\Mail\FluidEmail
instance is given as
RawMessage. Additionally the mailer instance is
given, which depends on the implementation - usually
\TYPO3\CMS\Core\Mail\Mailer . It contains the
\Symfony\Component\Mailer\Transport object, which can be retrieved using
the
getTransport() method.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Mail\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/modify-message'
This event is fired before the Mailer has sent a message and
allows listeners to manipulate the RawMessage and the Envelope.
Note: Usually TYPO3CMSCoreMailMailer is given to the event. This implementation
allows to retrieve the TransportInterface using the getTransport() method.
The event was introduced to replace the Signal/Slot
\TYPO3\CMS\Extensionmanager\Utility\InstallUtility::afterExtensionInstall.
The PSR-14 event
\TYPO3\CMS\Core\Package\Event\AfterPackageActivationEvent
is triggered after a package has been activated.
Attention
This event is dispatched when an extension is activated in the
Extension Manager, therefore starting with TYPO3 v11 this
event is only dispatched in Classic mode installations, not in Composer-based
installations. Use
installer events by Composer
for Composer-based installations.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Package\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/extension-activated'
Event that is triggered after a package has been activated
getPackageKey()
Returns
string
getType()
Returns
string
getEmitter()
Returns
?object
AfterPackageDeactivationEvent
New in version 10.3
The event was introduced to replace the Signal/Slot
\TYPO3\CMS\Extensionmanager\Utility\InstallUtility::afterExtensionUninstall.
The PSR-14 event
\TYPO3\CMS\Core\Package\Event\AfterPackageDeactivationEvent
is triggered after a package has been deactivated.
Attention
This event is dispatched when an extension is deactivated in the
Extension Manager, therefore starting with TYPO3 v11 this
event is only dispatched in Classic mode installations, not in Composer-based
installations. Use
installer events by Composer
for Composer-based installations.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Event that is triggered after a package has been de-activated
getPackageKey()
Returns
string
getType()
Returns
string
getEmitter()
Returns
?object
BeforePackageActivationEvent
The PSR-14 event
\TYPO3\CMS\Core\Package\Event\BeforePackageActivationEvent
is triggered before a number of packages should become active.
Attention
This event is dispatched before an extension is activated in the
Extension Manager, therefore starting with TYPO3 v11 this
event is only dispatched in Classic mode installations, not in Composer-based
installations. Use
installer events by Composer
for Composer-based installations.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Event that is triggered before a number of packages should become active
getPackageKeys()
Returns
array
PackagesMayHaveChangedEvent
New in version 10.3
The event was introduced to replace the Signal/Slot
\TYPO3\CMS\Core\Package\PackageManager::packagesMayHaveChanged.
The PSR-14 event
\TYPO3\CMS\Core\Package\Event\PackagesMayHaveChangedEvent
is a marker event to ensure that Core is re-triggering the package ordering and
package listings.
Attention
This event is dispatched when an extension is changed in the
Extension Manager, therefore starting with TYPO3 v11 this
event is only dispatched in Classic mode installations, not in Composer-based
installations. Use
installer events by Composer
for Composer-based installations.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Core\Page\Event\BeforeJavaScriptsRenderingEvent
is fired once before
\TYPO3\CMS\Core\Page\AssetRenderer::render[Inline]JavaScript
renders the output.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired once before TYPO3CMSCorePageAssetRenderer::render[Inline]JavaScript renders the output.
getAssetCollector()
Returns
\TYPO3\CMS\Core\Page\AssetCollector
isInline()
Returns
bool
isPriority()
Returns
bool
BeforeStylesheetsRenderingEvent
The PSR-14 event
\TYPO3\CMS\Core\Page\Event\BeforeStylesheetsRenderingEvent
is fired once before
\TYPO3\CMS\Core\Page\AssetRenderer::render[Inline]Stylesheets
renders the output.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event is dispatched in all classes where a user password is
validated against the globally configured password policy.
Note
The user data returned by the method
getUserData() will include user
data available from the initiating class only. Therefore, event listeners
should always consider the initiating class name when accessing data from
getUserData(). If specific user data is not available via
getUserData(), it can possibly be retrieved by a custom database
query (for example, data from the user table in the password reset process
by fetching the user with the
uid given in
getUserData()
array).
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\PasswordPolicy\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/enrich-context-data-event-listener'
Event is dispatched before the ContextData DTO is passed to the password policy validator.
Note, that the $userData array will include user data available from the initiating class only.
Event listeners should therefore always consider the initiating class name when accessing data
from getUserData().
The event can be used as an improved alternative for the deprecated
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getDefaultUploadFolder']
hook, serving the same purpose.
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterDefaultUploadFolderWasResolvedEvent
allows to modify the default upload folder after it has been resolved for the
current page or user.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Resource\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/after-default-upload-folder-was-resolved'
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileAddedEvent
is fired after a file was added to the resource
storage /
driver.
Example: Using listeners for this event allows, for example, to post-check
permissions or perform specific analysis of files like additional metadata
analysis after adding them to TYPO3.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired after a file was added to the Resource Storage / Driver.
Use case: Using listeners for this event allows to e.g. post-check permissions or
specific analysis of files like additional metadata analysis after adding them to TYPO3.
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
AfterFileAddedToIndexEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileAddedToIndexEvent
is fired once an index was just added to the database (= indexed).
Example: Using listeners for this event allows to additionally populate custom
fields of the
sys_file /
sys_file_metadata database records.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired once an index was just added to the database (= indexed).
Examples: Allows to additionally populate custom fields of the sys_file/sys_file_metadata database records.
getFileUid()
Returns
int
getRecord()
Returns
array
AfterFileCommandProcessedEvent
New in version 11.4
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileCommandProcessedEvent can be used
to perform additional tasks for specific file commands. For example, trigger a
custom indexer after a file has been uploaded.
This event is fired in the
\TYPO3\CMS\Core\Utility\File\ExtendedFileUtility class.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Resource\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/after-file-command-processed'
Event that is triggered after a file command has been processed. Can be used
to perform additional tasks for specific commands. For example, trigger a
custom indexer after a file has been uploaded.
This event is fired after the contents of a file got set / replaced.
Examples: Listeners can analyze content for AI purposes within Extensions.
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
getContent()
Returns
string
AfterFileCopiedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileCopiedEvent
is fired after a file was copied within a resource
storage /
driver.
The folder represents the "target folder".
Example: Listeners can sign up for listing duplicates using this event.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired after a file was copied within a Resource Storage / Driver.
The folder represents the "target folder".
Example: Listeners can sign up for listing duplicates using this event.
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getNewFileIdentifier()
Returns
string
getNewFile()
Returns
?\TYPO3\CMS\Core\Resource\FileInterface
AfterFileCreatedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileCreatedEvent
is fired after a file was created within a resource
storage /
driver.
The folder represents the "target folder".
Example: This allows to modify a file or check for an appropriate signature
after a file was created in TYPO3.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before a file was created within a Resource Storage / Driver.
The folder represents the "target folder".
Example: This allows to modify a file or check for an appropriate signature after a file was created in TYPO3.
getFileName()
Returns
string
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
AfterFileDeletedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileDeletedEvent
is fired after a file was deleted.
Example: If an extension provides additional functionality (for example
variants), this event allows listeners to also clean up their custom handling.
This can also be used for versioning of files.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Example: If an extension provides additional functionality (e.g. variants), this event allows listener to also clean
up their custom handling. This can also be used for versioning of files.
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
AfterFileMarkedAsMissingEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileMarkedAsMissingEvent
is fired once a file was just marked as missing in the database
(table
sys_file).
Example: If a file is marked as missing, listeners can try to recover a file.
This can happen on specific setups where editors also work via FTP.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired once a file was just marked as missing in the database (sys_file).
Example: If a file is marked as missing, listeners can try to recover a file. This can happen on specific setups
where editors also work via FTP.
getFileUid()
Returns
int
AfterFileMetaDataCreatedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileMetaDataCreatedEvent
is fired once metadata of a file was added to the database,
so it can be enriched with more information.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired once metadata of a file was added to the database, so it can be
enriched with more information.
getFileUid()
Returns
int
getMetaDataUid()
Returns
int
getRecord()
Returns
array
setRecord(array $record)
param $record
the record
AfterFileMetaDataDeletedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileMetaDataDeletedEvent
is fired once all metadata of a file was removed, in order to manage custom
metadata that was added previously.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired once all metadata of a file was removed, in order to manage custom metadata that was
added previously
getFileUid()
Returns
int
AfterFileMetaDataUpdatedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileMetaDataUpdatedEvent
is fired once metadata of a file was updated, in order to update custom metadata
fields accordingly.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired once metadata of a file was updated, in order to update custom metadata fields accordingly
getFileUid()
Returns
int
getMetaDataUid()
Returns
int
getRecord()
Returns
array
AfterFileMovedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileMovedEvent
is fired after a file was moved within a resource
storage /
driver.
The folder represents the "target folder".
Example: Use this to update custom third-party handlers that rely on specific
paths.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired after a file was moved within a Resource Storage / Driver.
The folder represents the "target folder".
Examples: Use this to update custom third party handlers that rely on specific paths.
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getOriginalFolder()
Returns
\TYPO3\CMS\Core\Resource\FolderInterface
AfterFileProcessingEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileProcessingEvent
is fired after a file object has been processed.
This allows to further customize a file object's processed file.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired once a file was just removed in the database (sys_file).
Example can be to further handle files and manage them separately outside of TYPO3's index.
getFileUid()
Returns
int
AfterFileRenamedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileRenamedEvent
is fired after a file was renamed in order to further process a file or filename
or update custom references to a file.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Example: Further process a file or create variants, or index the contents of a file for AI analysis etc.
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
getLocalFilePath()
Returns
string
AfterFileUpdatedInIndexEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFileUpdatedInIndexEvent
is fired once an index was just updated inside the database (= indexed).
Custom listeners can update further index values when a file was updated.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired after a folder was copied to the Resource Storage / Driver.
Example: Custom listeners can analyze contents of a file or add custom permissions to a folder automatically.
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getTargetParentFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getTargetFolder()
Returns
?\TYPO3\CMS\Core\Resource\FolderInterface
AfterFolderDeletedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFolderDeletedEvent
is fired after a folder was deleted. Custom listeners can then further clean up
permissions or third-party processed files with this event.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired after a folder was deleted. Custom listeners can then further clean up permissions or
third-party processed files with this event.
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
isDeleted()
Returns
bool
AfterFolderMovedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFolderMovedEvent
is fired after a folder was moved within the resource
storage /
driver.
Custom references can be updated via listeners of this event.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired after a folder was moved within the Resource Storage / Driver.
Custom references can be updated via listeners of this event.
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getTargetParentFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getTargetFolder()
Returns
?\TYPO3\CMS\Core\Resource\FolderInterface
AfterFolderRenamedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterFolderRenamedEvent
is fired after a folder was renamed.
Example: Add custom processing of folders or adjust permissions.
This event is also used by TYPO3 itself to synchronize folder relations in
records (for example in the table
sys_filemounts) after renaming of
folders.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Examples: Add custom processing of folders or adjust permissions.
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getSourceFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
AfterResourceStorageInitializationEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\AfterResourceStorageInitializationEvent
is fired after a resource object was built/created. Custom handlers can be
initialized at this moment for any kind of resource as well.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The purpose of the PSR-14 event
\TYPO3\CMS\Core\Resource\OnlineMedia\Event\AfterVideoPreviewFetchedEvent
is to modify the preview file of online media previews (like YouTube and Vimeo).
If, for example, a processed file is bad (blank or outdated), this event can be
used to modify and/or update the preview file.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Resource\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/after-video-preview-fetched'
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFileAddedEvent
is fired before a file is about to be added to the resource
storage /
driver.
This allows to perform custom checks to a file or restrict access to a file
before the file is added.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before a file is about to be added to the Resource Storage / Driver.
This allows to do custom checks to a file or restrict access to a file before the file is added.
getFileName()
Returns
string
setFileName(string $fileName)
param $fileName
the fileName
getSourceFilePath()
Returns
string
getTargetFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getStorage()
Returns
\TYPO3\CMS\Core\Resource\ResourceStorage
getDriver()
Returns
\TYPO3\CMS\Core\Resource\Driver\DriverInterface
BeforeFileContentsSetEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFileContentsSetEvent
is fired before the contents of a file gets set / replaced.
This allows to further analyze or modify the content of a file before it is
written by the driver.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before the contents of a file gets set / replaced.
This allows to further analyze or modify the content of a file before it is written by the driver.
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
getContent()
Returns
string
setContent(string $content)
param $content
the content
BeforeFileCopiedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFileCopiedEvent
is fired before a file is about to be copied within a resource
storage /
driver.
The folder represents the "target folder".
This allows to further analyze or modify the file or metadata before it is
written by the driver.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before a file is about to be copied within a Resource Storage / Driver.
The folder represents the "target folder".
This allows to further analyze or modify the file or metadata before it is written by the driver.
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
BeforeFileCreatedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFileCreatedEvent
is fired before a file is about to be created within a resource
storage /
driver.
The folder represents the "target folder".
This allows to further analyze or modify the file or filename before it is
written by the driver.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before a file is about to be deleted.
Event listeners can clean up third-party references with this event.
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
BeforeFileMovedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFileMovedEvent
is fired before a file is about to be moved within a resource
storage /
driver.
The folder represents the "target folder".
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before a file is about to be moved within a Resource Storage / Driver.
The folder represents the "target folder".
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getTargetFileName()
Returns
string
BeforeFileProcessingEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFileProcessingEvent
is fired before a file object is processed. This allows to add further
information or enrich the file before the processing is kicking in.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFileRenamedEvent
is fired before a file is about to be renamed. Custom listeners can further
rename the file according to specific guidelines based on the project.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before a file is about to be renamed. Custom listeners can further rename the file
according to specific guidelines based on the project.
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
getTargetFileName()
Returns
?string
BeforeFileReplacedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFileReplacedEvent
is fired before a file is about to be replaced. Custom listeners can check for
file integrity or analyze the content of the file before it gets added.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before a file is about to be replaced.
Custom listeners can check for file integrity or analyze the content of the file before it gets added.
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
getLocalFilePath()
Returns
string
BeforeFolderAddedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFolderAddedEvent is
fired before a folder is about to be added to the resource
storage /
driver.
This allows to further specify folder names according to regulations for a
specific project.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before a folder is about to be added to the Resource Storage / Driver.
This allows to further specify folder names according to regulations for a specific project.
getParentFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getFolderName()
Returns
string
BeforeFolderCopiedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFolderCopiedEvent
is fired before a folder is about to be copied to the resource
storage /
driver.
Listeners could add deferred processing / queuing of large folders.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before a folder is about to be deleted.
Listeners can use this event to clean up further external references to a folder / files in this folder.
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
BeforeFolderMovedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFolderMovedEvent is
fired before a folder is about to be moved to the resource
storage /
driver.
Listeners can be used to modify a folder name before it is actually moved or to
ensure consistency or specific rules when moving folders.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before a folder is about to be moved to the Resource Storage / Driver.
Listeners can be used to modify a folder name before it is actually moved or to ensure consistency
or specific rules when moving folders.
getFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getTargetParentFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getTargetFolderName()
Returns
string
BeforeFolderRenamedEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\BeforeFolderRenamedEvent
is fired before a folder is about to be renamed. Listeners can be used to modify
a folder name before it is actually moved or to ensure consistency or specific
rules when renaming folders.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before a resource object is actually built/created.
Example: A database record can be enriched to add dynamic values to each resource (file/folder) before
creation of a storage
getStorageUid()
Returns
int
setStorageUid(int $storageUid)
param $storageUid
the storageUid
getRecord()
Returns
array
setRecord(array $record)
param $record
the record
getFileIdentifier()
Returns
?string
setFileIdentifier(?string $fileIdentifier)
param $fileIdentifier
the fileIdentifier
EnrichFileMetaDataEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\EnrichFileMetaDataEvent
is called after a record has been loaded from database. It allows other places
to perform the extension of metadata at runtime or, for example, translation
and workspace overlay.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Event that is called after a record has been loaded from database
Allows other places to do extension of metadata at runtime or
for example translation and workspace overlay.
getFileUid()
Returns
int
getMetaDataUid()
Returns
int
getRecord()
Returns
array
setRecord(array $record)
param $record
the record
GeneratePublicUrlForResourceEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\GeneratePublicUrlForResourceEvent
is fired before TYPO3 FAL's native URL generation for a resource is instantiated.
This allows listeners to create custom links to certain files (for example
restrictions) for creating authorized deep links.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired before TYPO3 FAL's native URL generation for a Resource is instantiated.
This allows for listeners to create custom links to certain files (e.g. restrictions) for creating
authorized deeplinks.
getResource()
Returns
\TYPO3\CMS\Core\Resource\ResourceInterface
getStorage()
Returns
\TYPO3\CMS\Core\Resource\ResourceStorage
getDriver()
Returns
\TYPO3\CMS\Core\Resource\Driver\DriverInterface
getPublicUrl()
Returns
?string
setPublicUrl(?string $publicUrl)
param $publicUrl
the publicUrl
ModifyFileDumpEvent
New in version 11.4
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\ModifyFileDumpEvent is
fired in the
\TYPO3\CMS\Core\Controller\FileDumpController and allows
extensions to perform additional access / security checks before dumping a file.
The event does not only contain the file to dump but also the PSR-7 Request.
In case the file dump should be rejected, the event has to set a PSR-7
response, usually with a 403 status code. This will then immediately
stop the propagation.
With the event, it is not only possible to reject the file dump request,
but also to replace the file, which should be dumped.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Resource\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/modify-file-dump'
Event that is triggered when a file should be dumped to the browser, allowing to perform custom
security/access checks when accessing a file through a direct link, and returning an alternative
Response.
It is also possible to replace the file during this event, but not setting a response.
As soon as a custom Response is added, the propagation is stopped.
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\ModifyIconForResourcePropertiesEvent
is dispatched when an icon for a resource (file or folder) is fetched, allowing
to modify the icon or overlay in an event listener.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This is an Event every time an icon for a resource (file or folder) is fetched, allowing
to modify the icon or overlay in an event listener.
getResource()
Returns
\TYPO3\CMS\Core\Resource\ResourceInterface
getSize()
Returns
string
getOptions()
Returns
array
getIconIdentifier()
Returns
?string
setIconIdentifier(?string $iconIdentifier)
param $iconIdentifier
the iconIdentifier
getOverlayIdentifier()
Returns
?string
setOverlayIdentifier(?string $overlayIdentifier)
param $overlayIdentifier
the overlayIdentifier
SanitizeFileNameEvent
The PSR-14 event
\TYPO3\CMS\Core\Resource\Event\SanitizeFileNameEvent is
fired once an index was just added to the database (= indexed), so it is possible
to modify the file name, and name the files according to naming conventions of a
specific project.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event is fired once an index was just added to the database (= indexed), so it is possible
to modify the file name, and name the files according to naming conventions of a specific project.
getFileName()
Returns
string
setFileName(string $fileName)
param $fileName
the fileName
getTargetFolder()
Returns
\TYPO3\CMS\Core\Resource\Folder
getStorage()
Returns
\TYPO3\CMS\Core\Resource\ResourceStorage
getDriver()
Returns
\TYPO3\CMS\Core\Resource\Driver\DriverInterface
Security
The following list contains PSR-14 events
in EXT:core, namespace Security.
The PSR-14 event
\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.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\PolicyMutatedEvent
will be dispatched once all mutations have been applied to the current
Content Security Policy object, just before the
corresponding HTTP header is added to the HTTP response object. This allows
individual adjustments for custom implementations.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\ContentSecurityPolicy\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/mutate-policy'
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\ContentSecurityPolicy\EventListener;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\Event\PolicyMutatedEvent;
useTYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue;
finalclassMyEventListener{
publicfunction__invoke(PolicyMutatedEvent $event): void{
if ($event->scope->type->isFrontend()) {
// In our example, only the backend policy should be adjustedreturn;
}
// Allow images from example.org
$event->getCurrentPolicy()->extend(
Directive::ImgSrc,
new UriValue('https://example.org/'),
);
}
}
This event is a substitution for the
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['Core/TypoScript/TemplateService']['runThroughTemplatesPostProcessing']
hook.
The PSR-14 event
\TYPO3\CMS\Core\TypoScript\IncludeTree\Event\AfterTemplatesHaveBeenDeterminedEvent
can be used to manipulate
sys_template rows. The event receives the list
of resolved
sys_template rows and the
\Psr\Http\Message\ServerRequestInterface and allows manipulating the
sys_template rows array.
The event is called in the code of the Site Management > TypoScript
backend module, for example in the submodule Included TypoScript,
and in the frontend.
Extensions using the old hook that want to stay compatible with TYPO3 v11
and v12 can implement both the hook and the event.
Example
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\TypoScript\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/post-process-sys-templates'
To implement
myModifierFunction, an extension needs to register
an event listener in an extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\TypoScript\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/evaluate-modifier-function'
Listeners should take care function names can not overlap with function names
from other extensions and should thus namespace, example naming: "extNewsSortFunction()"
getFunctionName()
The function name, for example "extNewsSortFunction" when using "foo := extNewsSortFunction()"
Returns
string
getFunctionArgument()
Optional function argument, for example "myArgument" when using "foo := extNewsSortFunction(myArgument)"
Returns
?string
getOriginalValue()
Original / current value, for example "fooValue" when using:
foo = fooValue
foo := extNewsSortFunction(myArgument)
Returns
?string
setValue(string $value)
Set the updated value calculated by a listener.
Note you can not set to null to "unset", since getValue() falls back to
originalValue in this case. Set to empty string instead for this edge case.
param $value
the value
getValue()
Used by AstBuilder to fetch the updated value, falls back to given original value.
Can be used by Listeners to see if a previous listener changed the value already
by comparing with getOriginalValue().
Returns
?string
Extbase
The following list contains PSR-14 events
in EXT:extbase.
The PSR-14 event
\TYPO3\CMS\Extbase\Event\Configuration\BeforeFlexFormConfigurationOverrideEvent
can be used to implement a custom FlexForm override process
based on the original FlexForm configuration and the framework configuration.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Extbase\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/before-flexform-configuration-override'
Event which is dispatched before flexForm configuration overrides framework configuration. Possible core flexForm
overrides have already been processed in $flexFormConfiguration.
Listeners can implement a custom flexForm override process by using the original flexForm configuration available
in $originalFlexFormConfiguration.
The PSR-14 event
\TYPO3\CMS\Extbase\Event\Mvc\AfterRequestDispatchedEvent
is fired after the dispatcher has successfully dispatched a request to a
controller action.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Event which is fired after the dispatcher has successfully dispatched a request to a controller/action.
getRequest()
Returns
\TYPO3\CMS\Extbase\Mvc\RequestInterface
getResponse()
Returns
\Psr\Http\Message\ResponseInterface
BeforeActionCallEvent
The PSR-14 event
\TYPO3\CMS\Extbase\Event\Mvc\BeforeActionCallEvent is
triggered before any Extbase action is called within the
ActionController
or one of its subclasses.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Extbase\Event\Persistence\EntityAddedToPersistenceEvent
is dispatched after persisting the object, but before updating the reference
index and adding the object to the persistence session.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Extbase\Event\Persistence\EntityRemovedFromPersistenceEvent
is fired after an object/entity was sent to the persistence layer to be removed.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Extbase\Event\Persistence\ModifyQueryBeforeFetchingObjectDataEvent
is fired before the storage backend is asked for results from a given query.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Extbase\Event\Persistence\ModifyResultAfterFetchingObjectDataEvent
is fired after the storage backend has pulled results from a given query.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The event was introduced to replace the Signal/Slot
\TYPO3\CMS\Extensionmanager\Utility\InstallUtility::afterExtensionStaticSqlImport.
The PSR-14 event
\TYPO3\CMS\Extensionmanager\Event\AfterExtensionDatabaseContentHasBeenImportedEvent
is triggered after a package has imported the database file shipped within a
t3d/xml import file.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The event was introduced to replace the Signal/Slot
\TYPO3\CMS\Extensionmanager\Utility\InstallUtility::afterExtensionFileImport.
The PSR-14 event
\TYPO3\CMS\Extensionmanager\Event\AfterExtensionFilesHaveBeenImportedEvent
is triggered after a package has imported all extension files
(from Initialisation/Files/).
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The event was introduced to replace the Signal/Slot
\TYPO3\CMS\Extensionmanager\Utility\InstallUtility::afterExtensionStaticSqlImport.
The PSR-14 event
\TYPO3\CMS\Extensionmanager\Event\AfterExtensionStaticDatabaseContentHasBeenImportedEvent
is triggered after a package has imported the database file shipped within
ext_tables_static+adt.sql.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The event was introduced to replace the Signal/Slot
\TYPO3\CMS\Extensionmanager\ViewHelper\ProcessAvailableActionsViewHelper::processActions.
The PSR-14 event
\TYPO3\CMS\Extensionmanager\Event\AvailableActionsForExtensionEvent
is triggered when rendering an additional action (currently within
a Fluid ViewHelper) in the extension manager.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event can be used as an improved alternative for the removed
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/file_edit.php']['preOutputProcessingHook']
hook.
The PSR-14 event
\TYPO3\CMS\Filelist\Event\ModifyEditFileFormDataEvent
allows to modify the form data, used to render the file edit form in the
File > Filelist module using
FormEngine data compiling.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\FileList\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/modify-edit-file-form-data'
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\FileList\EventListener;
useTYPO3\CMS\Filelist\Event\ModifyEditFileFormDataEvent;
finalclassMyEventListener{
publicfunction__invoke(ModifyEditFileFormDataEvent $event): void{
// Get current form data
$formData = $event->getFormData();
// Change TCA "renderType" based on the file extension
$fileExtension = $event->getFile()->getExtension();
if ($fileExtension === 'ts') {
$formData['processedTca']['columns']['data']['config']['renderType'] = 'tsRenderer';
}
// Set updated form data
$event->setFormData($formData);
}
}
Listeners to this event are be able to modify the form data,
used to render the edit file form in the filelist module.
getFormData()
Returns
array
setFormData(array $formData)
param $formData
the formData
getFile()
Returns
\TYPO3\CMS\Core\Resource\FileInterface
getRequest()
Returns
\Psr\Http\Message\ServerRequestInterface
ProcessFileListActionsEvent
New in version 11.4
The PSR-14 event
\TYPO3\CMS\Core\Configuration\Event\ProcessFileListActionsEvent
is fired after generating the actions for the files and folders listing in the
File > Filelist module.
This event can be used to manipulate the icons/actions, used for the edit control
section in the files and folders listing within the File > Filelist
module.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\FileList\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/process-file-list'
The PSR-14 event
\TYPO3\CMS\Frontend\Event\AfterCacheableContentIsGeneratedEvent can be
used to decide if a page should be stored in cache.
It is executed right after all cacheable content is generated.
It can also be used to manipulate the content before it is stored in
TYPO3's page cache. In the Core, the event is used in
EXT:indexed_search to index cacheable content.
The
AfterCacheableContentIsGeneratedEvent contains the
information if a generated page is able to store in cache via the
$event->isCachingEnabled() method. This can be used to
differentiate between the previous hooks contentPostProc-cached and
contentPostProc-all. The later hook was called regardless of whether the
cache was enabled or not.
Example
Registration of the AfterCacheableContentIsGeneratedEvent in your
extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Frontend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/content-modifier'
Event that allows to enhance or change content (also depending if caching is enabled).
Think of $this->isCachingEnabled() as the same as $TSFE->no_cache.
Depending on disable or enabling caching, the cache is then not stored in the pageCache.
The PSR-14 event
\TYPO3\CMS\Frontend\Event\AfterCachedPageIsPersistedEvent
is commonly used to generate a static file cache. This event is only called if
the page was actually stored in TYPO3's page cache.
Example
Registration of the AfterCachedPageIsPersistedEvent in your
extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Frontend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/content-modifier'
Event that is used directly after all cached content is stored in
the page cache.
If a page is called from the cache, this event is NOT fired.
This event is also NOT FIRED when $TSFE->no_cache (or manipulated via AfterCacheableContentIsGeneratedEvent)
is set.
The amount of seconds until the cache entry is invalid.
Returns
int
AfterLinkIsGeneratedEvent
New in version 12.0
This PSR-14 event supersedes the
UrlProcessorInterface logic
which allowed to modify mail URNs or external URLs, but not the
full anchor tag.
In addition, this PSR-14 event also replaces the
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['typoLink_PostProc']
hook which was not executed at all times, and had a cumbersome API
to modify values.
It is also recommended to use the PSR-14 event instead of the global
getATagParams hook (
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] )
to add additional attributes (see example below) to links.
The PSR-14 event
\TYPO3\CMS\Frontend\Event\AfterLinkIsGeneratedEvent
allows PHP developers to modify any kind of link generated by TYPO3's mighty
typolink() functionality.
By using this event, it is possible to add attributes to links to
internal pages, or links to files, as the event contains the actual information
of the link type with it.
As this event works with the
\TYPO3\CMS\Frontend\Typolink\LinkResultInterface
object it is possible to modify or replace the LinkResult information instead of
working with string replacement functionality for adding, changing or removing
attributes.
If a link could not be generated, a
\TYPO3\CMS\Frontend\Typolink\UnableToLinkException might be thrown.
Example
Registration of the AfterLinkIsGeneratedEvent in your extension's
Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Frontend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/link-modifier'
The PSR-14 event
\TYPO3\CMS\Frontend\Event\AfterPageAndLanguageIsResolvedEvent
is fired in the frontend process after a given page has been resolved
including its language.
This event modifies TYPO3's language resolution logic through custom additions.
It also allows sending a custom response via event listeners (for example,
a custom 403 response).
Note
There are three events in the process when the main
TypoScriptFrontendController class resolves a page and its root line
based on the incoming request. They are triggered in the following order:
A PSR-14 event fired in the frontend process after a given page has been resolved including
its language.
This event is intended to e.g. modify TYPO3's language resolving logic by custom additions.
This event also allows to send a custom Response via Event Listeners (e.g. a custom 403 response)
The PSR-14 event
\TYPO3\CMS\Frontend\Event\AfterPageWithRootLineIsResolvedEvent
fires in the frontend process after a given page has been resolved with
permissions, root line, etc.
This is useful for modifying the page and root (but before resolving the
language), to direct or load content from another page, or for modifying the
page response if additional permissions should be checked.
Note
There are three events in the process when the main
TypoScriptFrontendController class resolves a page and its root line
based on the incoming request. They are triggered in the following order:
A PSR-14 event fired in the frontend process after a given page has been resolved with permissions, rootline etc.
This is useful to modify the page + rootline (but before the language is resolved)
to direct or load content from a different page, or modify the page response if additional
permissions should be checked.
This PSR-14 event replaces the
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PreProcessing']
hook.
The PSR-14 event
\TYPO3\CMS\Frontend\Event\BeforePageIsResolvedEvent is
processed when the main class
TypoScriptFrontendController is resolving a
page and its root line, based on the incoming request.
The event fires before the frontend process attempts to fully resolve a given
page based on its page ID and request. Event listeners can modify incoming
parameters (such as
$controller->id) or the context for resolving a page.
Note
There are three events in the process when the main
TypoScriptFrontendController class resolves a page and its root line
based on the incoming request. They are triggered in the following order:
This event has been introduced to serve as a more powerful and flexible
alternative for the removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/tslib/class.tslib_menu.php']['filterMenuPages'] .
This event is fired after TYPO3 has filtered all menu items. The menu can then
be adjusted by adding, removing or modifying the menu items. Also changing the
order is possible.
Additionally, more information about the currently rendered menu, such as
the menu items which were filtered out, is available.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
API
classFilterMenuItemsEvent
Fully qualified name
\TYPO3\CMS\Frontend\Event\FilterMenuItemsEvent
Listeners to this Event will be able to modify items for a menu generated with HMENU
getAllMenuItems()
Returns
array
getFilteredMenuItems()
Returns
array
setFilteredMenuItems(array $filteredMenuItems)
param $filteredMenuItems
the filteredMenuItems
getMenuConfiguration()
Returns
array
getItemConfiguration()
Returns
array
getBannedMenuItems()
Returns
array
getExcludedDoktypes()
Returns
array
getSite()
Returns
\TYPO3\CMS\Core\Site\Entity\Site
getContext()
Returns
\TYPO3\CMS\Core\Context\Context
getCurrentPage()
Returns
array
ModifyCacheLifetimeForPageEvent
New in version 12.0
This event serves as a successor for the
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['get_cache_timeout']
hook.
This event allows to modify the lifetime of how long a rendered page of a
frontend call should be stored in the "pages" cache.
Example
Register the listener:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Frontend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/cache-timeout'
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Frontend\EventListener;
useTYPO3\CMS\Core\Core\Environment;
useTYPO3\CMS\Frontend\Event\ModifyCacheLifetimeForPageEvent;
finalclassMyEventListener{
publicfunction__invoke(ModifyCacheLifetimeForPageEvent $event): void{
// Only cache all pages for 30 seconds when in development contextif (Environment::getContext()->isDevelopment()) {
$event->setCacheLifetime(30);
}
}
}
Event to allow listeners to modify the amount of seconds that a generated frontend page
should be cached in the "pages" cache when initially generated.
setCacheLifetime(int $cacheLifetime)
param $cacheLifetime
the cacheLifetime
getCacheLifetime()
Returns
int
getPageId()
Returns
int
getPageRecord()
Returns
array
getRenderingInstructions()
Returns
array
getContext()
Returns
\TYPO3\CMS\Core\Context\Context
ModifyHrefLangTagsEvent
The PSR-14 event
\TYPO3\CMS\Frontend\Event\ModifyHrefLangTagsEvent is available to alter
the
hreflang tags just before they get rendered.
The class
\TYPO3\CMS\Seo\HrefLang\HrefLangGenerator (identifier
typo3-seo/hreflangGenerator) is also available as an event. Its purpose
is to provide the default
hreflang tags. This way it is possible to
register a custom event listener after or instead of this implementation.
Example
An example implementation could look like this:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Frontend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/cache-timeout'after:'typo3-seo/hreflangGenerator'
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Frontend\EventListener;
useTYPO3\CMS\Frontend\Event\ModifyHrefLangTagsEvent;
finalclassMyEventListener{
publicfunction__invoke(ModifyHrefLangTagsEvent $event): void{
$hrefLangs = $event->getHrefLangs();
$request = $event->getRequest();
// Do anything you want with $hrefLangs
$hrefLangs = [
'en-US' => 'https://example.org',
'nl-NL' => 'https://example.org/nl',
];
// Override all hrefLang tags
$event->setHrefLangs($hrefLangs);
// Or add a single hrefLang tag
$event->addHrefLang('de-DE', 'https://example.org/de');
}
}
Copied!
API
classModifyHrefLangTagsEvent
Fully qualified name
\TYPO3\CMS\Frontend\Event\ModifyHrefLangTagsEvent
Listeners to this event will be able to modify the hreflang tags that will be generated. You can use this when you
have an edge case language scenario and need to alter the default hreflang tags.
getHrefLangs()
Returns
array
getRequest()
Returns
\Psr\Http\Message\ServerRequestInterface
setHrefLangs(array $hrefLangs)
Set the hreflangs. This should be an array in format:
This event has been introduced to serve as a more powerful and flexible
alternative for the removed hook
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typolinkProcessing']['typolinkModifyParameterForPageLinks'] .
The PSR-14 event
\TYPO3\CMS\Frontend\Event\ModifyPageLinkConfigurationEvent
is called after a page has been resolved, and includes arguments such as the
generated fragment and the to-be-used query parameters.
The page to be linked to can also be modified to link to a different page.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Frontend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/modify-page-link-configuration'
<?phpdeclare(strict_types=1);
namespaceMyVendor\MyExtension\Frontend\EventListener;
useTYPO3\CMS\Frontend\Event\ModifyPageLinkConfigurationEvent;
finalclassMyEventListener{
publicfunction__invoke(ModifyPageLinkConfigurationEvent $event): void{
// Do your magic here
}
}
A generic PSR 14 Event to allow modifying the incoming (and resolved) page when building a "page link".
This event allows Event Listener to change the page to be linked to, or add/remove possible query
parameters / fragments to be generated.
getConfiguration()
Returns
array
setConfiguration(array $configuration)
param $configuration
the configuration
getLinkDetails()
Returns
array
getPage()
Returns
array
setPage(array $page)
param $page
the page
getQueryParameters()
Returns
array
setQueryParameters(array $queryParameters)
param $queryParameters
the queryParameters
getFragment()
Returns
string
setFragment(string $fragment)
param $fragment
the fragment
pageWasModified()
Returns
bool
ModifyResolvedFrontendGroupsEvent
New in version 11.5
This event is intended to restore the functionality found in the
getGroupsFE authentication service that was removed in TYPO3 v11.
The PSR-14 event
\TYPO3\CMS\Frontend\Authentication\ModifyResolvedFrontendGroupsEvent
event allows frontend groups to be added to a (frontend) request, regardless of
whether a user is logged in or not.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
The PSR-14 event
\TYPO3\CMS\Frontend\Event\ShouldUseCachedPageDataIfAvailableEvent
allows TYPO3 extensions to register event listeners to modify if a
page should be read from cache (if it has been created in store already), or
if it should be re-built completely ignoring the cache entry for the request.
This event can be used to avoid loading from the cache when indexing via
CLI happens from an external source, or if the cache should be ignored when
logged in from a certain IP address.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Frontend\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/avoid-cache-loading'
Notification before a redirect is made, which also allows to modify
the actual redirect URL. Setting the redirect to an empty string
will avoid triggering a redirect.
getLoginType()
Returns
string
getRedirectUrl()
Returns
string
setRedirectUrl(string $redirectUrl)
param $redirectUrl
the redirectUrl
getRequest()
Returns
\Psr\Http\Message\ServerRequestInterface
LoginConfirmedEvent
The PSR-14 event
\TYPO3\CMS\FrontendLogin\Event\LoginConfirmedEvent is
triggered when a login was successful.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
A notification when a log in has successfully arrived at the plugin, via the view and the controller, multiple
information can be overridden in Event Listeners.
A notification if something went wrong while trying to log in a user.
LogoutConfirmedEvent
The PSR-14 event
\TYPO3\CMS\FrontendLogin\Event\LogoutConfirmedEvent is
triggered when a logout was successful.
Example: Delete stored private key from disk on logout
Upon logout a private key the user uploaded for decryption of private
information should be deleted at once. There is only a logout event if the user
actively clicks the logout button, so if the user would just close the browser
window there would be no
LogoutConfirmedEvent. For this case we need
a second line of defense like a scheduler task (out of scope of this example).
The currently logged-in user derived from the
\TYPO3\CMS\Core\Context\Context
is now an anonymous user that is not logged in. The information on which user
just logged out cannot be determined from the context or the methods from the
event. We therefore need different logic to determine the user who just logged
out. This logic is not part of the example below.
Register the event listener in the Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\EventListener\DeletePrivateKeyOnLogout:tags:-name:event.listeneridentifier:'my-extension/delete-private-key-on-logout'
A notification when a log out has successfully arrived at the plugin, via the view and the controller, multiple
information can be overridden in Event Listeners.
The PSR-14 event
\TYPO3\CMS\FrontendLogin\Event\ModifyLoginFormViewEvent
allows to inject custom variables into the login form.
Changed in version 12.0
The interface
\TYPO3\CMS\Extbase\Mvc\View\ViewInterface has been
removed with v12. The
getView() method signature has been changed
to
\TYPO3Fluid\Fluid\View\ViewInterface with the v12 release.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
Allows to inject custom variables into the login form.
getView()
Returns
\TYPO3Fluid\Fluid\View\ViewInterface
PasswordChangeEvent
The PSR-14 event
\TYPO3\CMS\FrontendLogin\Event\PasswordChangeEvent
contains information about the password that has been set and will be
stored in the database shortly.
Note
You can find a basic example implementation of a listener to this event
in the chapter Listen to an event.
Deprecated since version 12.4
The
PasswordChangeEvent should not be used to validate user passwords,
since it is not possible to visualize to the user why a password has been
rejected. Therefore the following methods of the event are deprecated:
Informal event that contains information about the password which was set, and is about to be stored in the database.
getUser()
Returns
array
getHashedPassword()
Returns
string
setHashedPassword(string $passwordHash)
Deprecated: will be removed in TYPO3 13
param $passwordHash
the passwordHash
getRawPassword()
Returns
string
setAsInvalid(string $message)
Deprecated: will be removed in TYPO3 13
param $message
the message
getErrorMessage()
Deprecated: will be removed in TYPO3 13
Returns
?string
isPropagationStopped()
Deprecated: will be removed in TYPO3 13
Returns
bool
SendRecoveryEmailEvent
The PSR-14 event
\TYPO3\CMS\FrontendLogin\Event\SendRecoveryEmailEvent
contains the email to be sent and additional information about the user who
requested a new password.
Example
Note
Currently, we do not have an example for this event. If you can provide a
useful one, please open an issue with your code snippets or
a pull request.
This event has been introduced as a more powerful and flexible alternative
to
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/web_info/class.tx_cms_webinfo.php']['drawFooterHook']
which has now been removed.
The PSR-14 event
\TYPO3\CMS\Info\Controller\Event\ModifyInfoModuleContentEvent
allows the content above and below the info module to be modified. The content
added in the event is displayed in each submodule of Web > Info.
The event also provides the
getCurrentModule() method, which
returns the current requested submodule. It is therefore possible to
limit the added content to a subset of the available submodules.
Next to
getRequest() and the
getModuleTemplate()
methods this event also features getters and setters for the header
and footer content.
Access control
The added content is by default always displayed. This event
provides the
hasAccess() method, returning whether the access checks
in the module were passed by the user.
This way, event listeners can decide on their own, whether their content
should always be shown, or only if a user also has access to the main module
content.
Example
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Info\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/content-to-info-module'
Listeners to this Event will be able to modify the header and footer content of the info module
hasAccess()
Whether the current user has access to the main content of the info module.
IMPORTANT: This is only for informational purposes. Listeners can therefore
decide on their own if their content should be added to the module even if
the user does not have access to the main module content.
Returns
bool
getRequest()
Returns
\Psr\Http\Message\ServerRequestInterface
getCurrentModule()
Returns
\TYPO3\CMS\Backend\Module\ModuleInterface
getModuleTemplate()
Returns
\TYPO3\CMS\Backend\Template\ModuleTemplate
setHeaderContent(string $content)
Set content for the header. Can also be used to e.g. reorder existing content.
IMPORTANT: This overwrites existing content from previous listeners!
param $content
the content
addHeaderContent(string $content)
Add additional content to the header
param $content
the content
getHeaderContent()
Returns
string
setFooterContent(string $content)
Set content for the footer. Can also be used to e.g. reorder existing content.
IMPORTANT: This overwrites existing content from previous listeners!
param $content
the content
addFooterContent(string $content)
Add additional content to the footer
param $content
the content
getFooterContent()
Returns
string
Install
The following list contains PSR-14 events
in the TYPO3 Core .
Registration of the event listener in the extension's Services.yaml:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\EventListener\CustomMirror:tags:-name:event.listeneridentifier:'my-extension/custom-mirror'
The PSR-14 event
\TYPO3\CMS\Install\Service\Event\ModifyLanguagePacksEvent
allows to ignore extensions or individual language packs for extensions when
downloading language packs.
The options of the
language:update command can be used to further
restrict the download (ignore additional extensions or download only certain
languages), but not to ignore decisions made by the event.
Example
Registration of the event:
EXT:my_extension/Configuration/Services.yaml
services:# Place here the default dependency injection configurationMyVendor\MyExtension\Install\EventListener\MyEventListener:tags:-name:event.listeneridentifier:'my-extension/modify-language-packs'
The PSR-14 event
\TYPO3\CMS\Linkvalidator\Event\BeforeRecordIsAnalyzedEvent
allows to modify results (= add results) or modify the record
before LinkValidator analyzes the record.
Example
In this example we are checking if there are external links containing the URL
of the project itself, as editors tend to set external links on internal pages
at times.
Create a class that works as event listener. This class does not implement or
extend any class. It has to provide a method that accepts an event of type
\TYPO3\CMS\Linkvalidator\Event\BeforeRecordIsAnalyzedEvent . By default,
the method is called
__invoke:
Class T3docs\Examples\EventListener\LinkValidator\CheckExternalLinksToLocalPagesEventListener
The listener must then be registered in the extensions
Services.yaml:
EXT:examples/Configuration/Services.yaml
services:# Place here the default dependency injection configurationT3docs\Examples\EventListener\LinkValidator\CheckExternalLinksToLocalPagesEventListener:tags:-name:event.listeneridentifier:'txExampleCheckExternalLinksToLocalPages'
For the implementation we need the
\TYPO3\CMS\Linkvalidator\Repository\BrokenLinkRepository to register
additional link errors and the
\TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory so
we can automatically parse for links. These two classes have to be injected via
dependency injection:
Class T3docs\Examples\EventListener\LinkValidator\CheckExternalLinksToLocalPagesEventListener
Now we use the
SoftReferenceParserFactory to find all registered link
parsers for a soft reference. Then we apply each of these parsers in turn to
the configured field in the current record. For each link found we can now
match, if it is an external link to an internal page.
Class T3docs\Examples\EventListener\LinkValidator\CheckExternalLinksToLocalPagesEventListener