TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,522 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _feature-86003:
|
||||
|
||||
==========================================================
|
||||
Feature: #86003 - Composition based API for the Adminpanel
|
||||
==========================================================
|
||||
|
||||
See :issue:`86003`
|
||||
|
||||
Description
|
||||
===========
|
||||
|
||||
A new API to extend the adminpanel for extension authors has been introduced.
|
||||
Enabling future enhancements for the adminpanel without having to make breaking changes for existing module providers
|
||||
is a key ingredient for providing a future proof extensible solution. Using single big interfaces that need to change on
|
||||
updates break backwards compatibility and do not provide sufficient feature encapsulation.
|
||||
|
||||
The adminpanel APIs have been refactored to use a composition pattern to allow modules more flexibility.
|
||||
Modules can now only implement the interfaces they need instead of implementing all functionality.
|
||||
For example an adminpanel module that only provides page related settings does no longer have to implement
|
||||
the :php:`getContent()` method.
|
||||
|
||||
Small interfaces have been provided as building blocks for modules with rich functionality.
|
||||
Easy addition of new interfaces that _can_ (not must) be implemented allow future improvements.
|
||||
|
||||
Additionally the API has been modified to allow a more object-oriented approach using simple DTOs instead
|
||||
of associative arrays for better type-hinting and a better developer experience. Storing and rendering data
|
||||
have been separated in two steps allowing to completely disconnect the rendered adminpanel from the current page.
|
||||
This can be used as a base for building a complete standalone adminpanel without API changes.
|
||||
|
||||
General Request Flow and the Adminpanel
|
||||
=======================================
|
||||
|
||||
To better understand how the adminpanel stores and renders data, let's take a short look at how the adminpanel
|
||||
is initialized and rendered.
|
||||
|
||||
Since TYPO3 v9 TYPO3 uses PSR-15 middlewares. The adminpanel brings three that are relevant to its rendering process:
|
||||
|
||||
* :php:`AdminPanelInitiator` - Called early in the request stack to allow initialisation of modules to catch most of the request data (for example log entries)
|
||||
* :php:`AdminPanelDataPersister` - Called at nearly the end of a frontend request to store the collected data (this is where module data gets saved)
|
||||
* :php:`AdminPanelRenderer` - Called as one of the last steps in the rendering process, currently replacing the closing body tag with its own code (this is where module content gets rendered)
|
||||
|
||||
When building own modules keep in mind at which step your modules` methods get called.
|
||||
In the last step for example (the rendering), you should not depend on any data outside of
|
||||
that provided to the module directly (for example do not rely on :php:`$GLOBALS` to be filled).
|
||||
|
||||
Current Design Considerations
|
||||
------------------------------
|
||||
|
||||
While the API of the adminpanel is very flexible in combining interfaces, the UI has a fixed structure
|
||||
and therefor a few things to consider when implementing own modules.
|
||||
|
||||
* The bottom bar of the adminpanel will only be rendered for modules that have submodules and implement the :php:`SubmoduleProviderInterface`
|
||||
* ShortInfo (see below) is only displayed for "TopLevel" modules
|
||||
* Content is only rendered for submodules
|
||||
|
||||
|
||||
How-To add own modules
|
||||
======================
|
||||
|
||||
Adding custom adminpanel modules always follows these steps:
|
||||
|
||||
#. Create a class implementing the basic :php:`ModuleInterface`
|
||||
#. Register the class in :file:`ext_localconf.php`
|
||||
#. Implement further interfaces for additional capabilities
|
||||
|
||||
|
||||
1. Create module class
|
||||
----------------------
|
||||
|
||||
To create your own admin panel module, create a
|
||||
new PHP class implementing :php:`\TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface`.
|
||||
The interface denotes your class as an adminpanel module and requires the
|
||||
implementation of :php:`getIdentifier()` and :php:`getLabel()` as a minimum of methods for a module.
|
||||
|
||||
2. Register your module
|
||||
------------------------
|
||||
|
||||
Displayed as a top level module:
|
||||
++++++++++++++++++++++++++++++++
|
||||
|
||||
Register your module by adding the following in your :file:`ext_localconf.php`::
|
||||
|
||||
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']['mynamespace_modulename'] => [
|
||||
'module' => \Your\Namespace\Adminpanel\Modules\YourModule::class,
|
||||
'before' => ['cache'],
|
||||
];
|
||||
|
||||
via :php:`before` or :php:`after` you can influence where your module will be displayed in the module menu
|
||||
by referencing the identifier / array key of other modules.
|
||||
|
||||
Displayed as a sub module:
|
||||
++++++++++++++++++++++++++
|
||||
|
||||
Register your module by adding the following in your :file:`ext_localconf.php`::
|
||||
|
||||
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']['info']['submodules']['mynamespace_modulename'] => [
|
||||
'module' => \Your\Namespace\Adminpanel\Modules\YourModule::class
|
||||
];
|
||||
|
||||
Note the :php:`submodules` key in the array allowing you to introduce hierarchical configuration.
|
||||
|
||||
|
||||
3. Add additional interfaces
|
||||
----------------------------
|
||||
|
||||
Your module is currently registered but is not doing anything yet, as it has no additional capabilities.
|
||||
The adminpanel provides additional separate interfaces (see list below).
|
||||
By implementing multiple interfaces you have fine-grained control over how your module behaves, which
|
||||
data it stores and how it gets rendered.
|
||||
|
||||
Adminpanel Interfaces
|
||||
=====================
|
||||
|
||||
ModuleInterface
|
||||
---------------
|
||||
|
||||
Purpose
|
||||
+++++++
|
||||
Base interface all adminpanel modules share, defines common methods.
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
- :php:`getIdentifier()` - Returns :php:`string` identifier of a module (for example `mynamespace_modulename`)
|
||||
- :php:`getLabel()` - Returns speaking label of a module (for example `My Module`)
|
||||
|
||||
|
||||
ConfigurableInterface
|
||||
---------------------
|
||||
|
||||
Purpose
|
||||
+++++++
|
||||
Used to indicate that an adminpanel module can be enabled or disabled via configuration
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
- :php:`isEnabled` - Returns :php:`bool` depending on whether the module is enabled.
|
||||
|
||||
Example implementation
|
||||
++++++++++++++++++++++
|
||||
|
||||
:php:`\TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule::isEnabled()`::
|
||||
|
||||
/**
|
||||
* Returns true if the module is
|
||||
* -> either enabled via TSConfig admPanel.enable
|
||||
* -> or any setting is overridden
|
||||
* override is a way to use functionality of the admin panel without displaying the panel to users
|
||||
* for example: hidden records or pages can be displayed by default
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
$identifier = $this->getIdentifier();
|
||||
$result = $this->isEnabledViaTsConfig();
|
||||
if ($this->mainConfiguration['override.'][$identifier] ?? false) {
|
||||
$result = (bool)$this->mainConfiguration['override.'][$identifier];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ContentProviderInterface
|
||||
------------------------
|
||||
|
||||
Purpose
|
||||
+++++++
|
||||
Adminpanel interface to denote that a module has content to be rendered
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
* :php:`getContent(ModuleData $data)` - Return content as HTML. For modules implementing the :php:`DataProviderInterface`
|
||||
the "ModuleData" object is automatically filled with the stored data - if no data is given a "fresh" ModuleData object is injected.
|
||||
|
||||
Example implementation
|
||||
++++++++++++++++++++++
|
||||
|
||||
:php:`\TYPO3\CMS\Adminpanel\Modules\Debug\QueryInformation::getContent`::
|
||||
|
||||
public function getContent(ModuleData $data): string
|
||||
{
|
||||
$view = new StandaloneView();
|
||||
$view->setTemplatePathAndFilename(
|
||||
'typo3/sysext/adminpanel/Resources/Private/Templates/Modules/Debug/QueryInformation.html'
|
||||
);
|
||||
$this->getLanguageService()->includeLLFile('EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf');
|
||||
$view->assignMultiple($data->getArrayCopy());
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
|
||||
DataProviderInterface
|
||||
------------------------
|
||||
|
||||
Purpose
|
||||
+++++++
|
||||
Adminpanel interface to denote that a module provides data to be stored for the current request.
|
||||
|
||||
Adminpanel modules can save data to the adminpanel request cache and access this data in the rendering process.
|
||||
Data necessary for rendering the module content has to be returned via this interface implementation, as this allows
|
||||
for separate data collection and rendering and is a pre-requisite for a standalone debug tool.
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
- :php:`getDataToStore(ServerRequestInterface $request): ModuleData` - Return a `ModuleData` instance with the data to store
|
||||
|
||||
Example implementation
|
||||
++++++++++++++++++++++
|
||||
|
||||
:php:`\TYPO3\CMS\Adminpanel\Modules\Info\RequestInformation::getDataToStore`::
|
||||
|
||||
public function getDataToStore(ServerRequestInterface $request): ModuleData
|
||||
{
|
||||
return new ModuleData(
|
||||
[
|
||||
'post' => $_POST,
|
||||
'get' => $_GET,
|
||||
'cookie' => $_COOKIE,
|
||||
'session' => $_SESSION,
|
||||
'server' => $_SERVER,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
InitializableInterface
|
||||
------------------------
|
||||
|
||||
Purpose
|
||||
+++++++
|
||||
|
||||
Adminpanel interface to denote that a module has tasks to perform on initialization of the request.
|
||||
|
||||
Modules that need to set data / options early in the rendering process to be able to collect data, should implement
|
||||
this interface - for example the log module uses the initialization to register the adminpanel log collection early
|
||||
in the rendering process.
|
||||
|
||||
Initialize is called in the PSR-15 middleware stack through adminpanel initialisation via the AdminPanel MainController.
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
* :php:`initializeModule(ServerRequestInterface $request)` - Called on adminpanel initialization
|
||||
|
||||
Example implementation
|
||||
++++++++++++++++++++++
|
||||
|
||||
:php:`\TYPO3\CMS\Adminpanel\Modules\CacheModule::initializeModule`::
|
||||
|
||||
public function initializeModule(ServerRequestInterface $request): void
|
||||
{
|
||||
if ($this->configurationService->getConfigurationOption('cache', 'noCache')) {
|
||||
$this->getTypoScriptFrontendController()->set_no_cache('Admin Panel: No Caching', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ModuleSettingsProviderInterface
|
||||
-------------------------------
|
||||
|
||||
Purpose
|
||||
+++++++
|
||||
|
||||
Adminpanel module settings interface denotes that a module has own settings.
|
||||
|
||||
The adminpanel knows two types of settings:
|
||||
|
||||
* ModuleSettings are relevant for the module itself and its representation (for example the log module provides settings
|
||||
where displayed log level and grouping of the module can be configured)
|
||||
|
||||
* PageSettings are relevant for rendering the page (for example the preview module provides settings showing or hiding
|
||||
hidden content elements or simulating a specific rendering time)
|
||||
|
||||
If a module provides settings relevant to its own content, use this interface.
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
* :php:`getSettings(): string` - Return settings as rendered HTML form elements
|
||||
|
||||
Example implementation
|
||||
++++++++++++++++++++++
|
||||
|
||||
:php:`\TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall::getSettings`::
|
||||
|
||||
public function getSettings(): string
|
||||
{
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/TsDebug/TypoScriptSettings.html';
|
||||
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
|
||||
$view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
|
||||
|
||||
$view->assignMultiple(
|
||||
[
|
||||
'tree' => (int)$this->getConfigurationOption('tree'),
|
||||
...
|
||||
]
|
||||
);
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
|
||||
OnSubmitActorInterface
|
||||
-----------------------
|
||||
|
||||
Purpose
|
||||
+++++++
|
||||
|
||||
Adminpanel interface for modules that need to react on changed configuration
|
||||
(for example if fluid debug settings change, the frontend cache should be cleared).
|
||||
|
||||
OnSubmitActors are currently called upon persisting new configuration _before_ the page is reloaded.
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
* :php:`onSubmit(array $configurationToSave, ServerRequestInterface $request)` - Can act when configuration gets saved.
|
||||
Configuration form vars are provided in :php:`$configurationToSave` as an array.
|
||||
|
||||
Example implementation
|
||||
++++++++++++++++++++++
|
||||
|
||||
:php:`\TYPO3\CMS\Adminpanel\Modules\PreviewModule::onSubmit`::
|
||||
|
||||
/**
|
||||
* Clear page cache if fluid debug output setting is changed
|
||||
*
|
||||
* @param array $input
|
||||
* @param ServerRequestInterface $request
|
||||
* @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
|
||||
*/
|
||||
public function onSubmit(array $input, ServerRequestInterface $request): void
|
||||
{
|
||||
$activeConfiguration = (int)$this->getConfigOptionForModule('showFluidDebug');
|
||||
if (isset($input['preview_showFluidDebug']) && (int)$input['preview_showFluidDebug'] !== $activeConfiguration) {
|
||||
$pageId = (int)$request->getParsedBody()['TSFE_ADMIN_PANEL']['preview_clearCacheId'];
|
||||
$cacheManager = GeneralUtility::makeInstance(CacheManager::class);
|
||||
$cacheManager->getCache('cache_pages')->flushByTag('pageId_' . $pageId);
|
||||
$cacheManager->getCache('fluid_template')->flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
PageSettingsProviderInterface
|
||||
-----------------------------
|
||||
|
||||
Purpose
|
||||
+++++++
|
||||
|
||||
Adminpanel page settings interface denotes that a module has settings regarding the page rendering.
|
||||
|
||||
The adminpanel knows two types of settings:
|
||||
|
||||
* ModuleSettings are relevant for the module itself and its representation (for example the log module provides settings
|
||||
where displayed log level and grouping of the module can be configured)
|
||||
|
||||
* PageSettings are relevant for rendering the page (for example the preview module provides settings showing or hiding
|
||||
hidden content elements or simulating a specific rendering time)
|
||||
|
||||
If a module provides settings changing the rendering of the main page request, use this interface.
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
* :php:`getSettings(): string` - Return HTML form elements for settings
|
||||
|
||||
Example implementation
|
||||
++++++++++++++++++++++
|
||||
|
||||
:php:`\TYPO3\CMS\Adminpanel\Modules\EditModule::getPageSettings`::
|
||||
|
||||
public function getPageSettings(): string
|
||||
{
|
||||
$editToolbarService = GeneralUtility::makeInstance(EditToolbarService::class);
|
||||
$toolbar = $editToolbarService->createToolbar();
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/Settings/Edit.html';
|
||||
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
|
||||
$view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
|
||||
$view->assignMultiple(
|
||||
[
|
||||
'feEdit' => ExtensionManagementUtility::isLoaded('feedit'),
|
||||
...
|
||||
]
|
||||
);
|
||||
return $view->render();
|
||||
|
||||
|
||||
PageSettingsProviderInterface
|
||||
-----------------------------
|
||||
|
||||
Purpose
|
||||
+++++++
|
||||
|
||||
Adminpanel interface to denote that a module has own resource files.
|
||||
|
||||
An adminpanel module implementing this interface may deliver custom JavaScript and Css files to provide additional
|
||||
styling and JavaScript functionality
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
* :php:`getJavaScriptFiles(): array` - Returns a string array with javascript files that will be rendered after the module
|
||||
* :php:`getCssFiles(): array` - Returns a string array with CSS files that will be rendered after the module
|
||||
|
||||
Example implementation
|
||||
++++++++++++++++++++++
|
||||
|
||||
:php:`\TYPO3\CMS\Adminpanel\Modules\TsDebugModule`::
|
||||
|
||||
public function getJavaScriptFiles(): array
|
||||
{
|
||||
return ['EXT:adminpanel/Resources/Public/JavaScript/Modules/TsDebug.js'];
|
||||
}
|
||||
|
||||
public function getCssFiles(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
ShortInfoProviderInterface
|
||||
-----------------------------
|
||||
|
||||
Purpose
|
||||
+++++++
|
||||
|
||||
Adminpanel shortinfo provider interface can be used to add the module to the short info bar of the adminpanel.
|
||||
|
||||
Modules providing shortinfo will be displayed in the bottom bar of the adminpanel and may provide "at a glance" info
|
||||
about the current state (for example the log module provides the number of warnings and errors directly).
|
||||
|
||||
Be aware that modules with submodules at the moment can only render one short info (the one of the "parent" module).
|
||||
This will likely change in v10.
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
* :php:`getShortInfo(): string` - Info string (no HTML) that should be rendered
|
||||
* :php:`getIconIdentifier(): string` - An icon for this info line, needs to be registered in :php:`IconRegistry`
|
||||
|
||||
Example implementation
|
||||
++++++++++++++++++++++
|
||||
|
||||
:php:`\TYPO3\CMS\Adminpanel\Modules\InfoModule`::
|
||||
|
||||
public function getShortInfo(): string
|
||||
{
|
||||
$parseTime = $this->getTimeTracker()->getParseTime();
|
||||
return sprintf($this->getLanguageService()->sL(
|
||||
'LLL:EXT:adminpanel/Resources/Private/Language/locallang_info.xlf:module.shortinfo'
|
||||
), $parseTime);
|
||||
}
|
||||
|
||||
public function getIconIdentifier(): string
|
||||
{
|
||||
return 'actions-document-info';
|
||||
}
|
||||
|
||||
|
||||
|
||||
SubmoduleProviderInterface
|
||||
-----------------------------
|
||||
|
||||
Purpose
|
||||
+++++++
|
||||
|
||||
Adminpanel interface providing hierarchical functionality for modules.
|
||||
|
||||
A module implementing this interface may have submodules. Be aware that the current implementation of the adminpanel
|
||||
renders a maximum level of 2 for modules. If you need to render more levels, write your own module and implement
|
||||
multi-hierarchical rendering in the getContent method.
|
||||
|
||||
Methods
|
||||
+++++++
|
||||
|
||||
* :php:`setSubModules(array $subModules)` - Sets array of module instances (instances of :php:`ModuleInterface`) as submodules
|
||||
* :php:`getSubModules(): array` - Returns an array of module instances
|
||||
* :php:`hasSubmoduleSettings(): bool` - Return true if any of the submodules has settings to be rendered
|
||||
(can be used to render settings in a central place)
|
||||
|
||||
Example implementation
|
||||
++++++++++++++++++++++
|
||||
|
||||
:php:`\TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule`::
|
||||
|
||||
|
||||
public function setSubModules(array $subModules): void
|
||||
{
|
||||
$this->subModules = $subModules;
|
||||
}
|
||||
|
||||
public function getSubModules(): array
|
||||
{
|
||||
return $this->subModules;
|
||||
}
|
||||
|
||||
public function hasSubmoduleSettings(): bool
|
||||
{
|
||||
$hasSettings = false;
|
||||
foreach ($this->subModules as $subModule) {
|
||||
if ($subModule instanceof ModuleSettingsProviderInterface) {
|
||||
$hasSettings = true;
|
||||
break;
|
||||
}
|
||||
if ($subModule instanceof SubmoduleProviderInterface) {
|
||||
$hasSettings = $subModule->hasSubmoduleSettings();
|
||||
}
|
||||
}
|
||||
return $hasSettings;
|
||||
}
|
||||
|
||||
|
||||
.. index:: Frontend, PHP-API, ext:adminpanel
|
||||
Reference in New Issue
Block a user