1.. include:: ../../Includes.txt
2
3==========================================================
4Feature: #86003 - Composition based API for the Adminpanel
5==========================================================
6
7See :issue:`86003`
8
9Description
10===========
11
12A new API to extend the adminpanel for extension authors has been introduced.
13Enabling future enhancements for the adminpanel without having to make breaking changes for existing module providers
14is a key ingredient for providing a future proof extensible solution. Using single big interfaces that need to change on
15updates break backwards compatibility and do not provide sufficient feature encapsulation.
16
17The adminpanel APIs have been refactored to use a composition pattern to allow modules more flexibility.
18Modules can now only implement the interfaces they need instead of implementing all functionality.
19For example an adminpanel module that only provides page related settings does no longer have to implement
20the :php:`getContent()` method.
21
22Small interfaces have been provided as building blocks for modules with rich functionality.
23Easy addition of new interfaces that _can_ (not must) be implemented allow future improvements.
24
25Additionally the API has been modified to allow a more object-oriented approach using simple DTOs instead
26of associative arrays for better type-hinting and a better developer experience. Storing and rendering data
27have been separated in two steps allowing to completely disconnect the rendered adminpanel from the current page.
28This can be used as a base for building a complete standalone adminpanel without API changes.
29
30General Request Flow and the Adminpanel
31=======================================
32
33To better understand how the adminpanel stores and renders data, let's take a short look at how the adminpanel
34is initialized and rendered.
35
36Since TYPO3 v9 TYPO3 uses PSR-15 middlewares. The adminpanel brings three that are relevant to its rendering process:
37
38* :php:`AdminPanelInitiator` - Called early in the request stack to allow initialisation of modules to catch most of the request data (for example log entries)
39* :php:`AdminPanelDataPersister` - Called at nearly the end of a frontend request to store the collected data (this is where module data gets saved)
40* :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)
41
42When building own modules keep in mind at which step your modules` methods get called.
43In the last step for example (the rendering), you should not depend on any data outside of
44that provided to the module directly (for example do not rely on :php:`$GLOBALS` to be filled).
45
46Current Design Considerations
47------------------------------
48
49While the API of the adminpanel is very flexible in combining interfaces, the UI has a fixed structure
50and therefor a few things to consider when implementing own modules.
51
52* The bottom bar of the adminpanel will only be rendered for modules that have submodules and implement the :php:`SubmoduleProviderInterface`
53* ShortInfo (see below) is only displayed for "TopLevel" modules
54* Content is only rendered for submodules
55
56
57How-To add own modules
58======================
59
60Adding custom adminpanel modules always follows these steps:
61
62#. Create a class implementing the basic :php:`ModuleInterface`
63#. Register the class in :file:`ext_localconf.php`
64#. Implement further interfaces for additional capabilities
65
66
671. Create module class
68----------------------
69
70To create your own admin panel module, create a
71new PHP class implementing :php:`\TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface`.
72The interface denotes your class as an adminpanel module and requires the
73implementation of :php:`getIdentifier()` and :php:`getLabel()` as a minimum of methods for a module.
74
752. Register your module
76------------------------
77
78Displayed as a top level module:
79++++++++++++++++++++++++++++++++
80
81Register your module by adding the following in your :file:`ext_localconf.php`::
82
83    $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']['mynamespace_modulename'] => [
84        'module' => \Your\Namespace\Adminpanel\Modules\YourModule::class,
85        'before' => ['cache'],
86    ];
87
88via :php:`before` or :php:`after` you can influence where your module will be displayed in the module menu
89by referencing the identifier / array key of other modules.
90
91Displayed as a sub module:
92++++++++++++++++++++++++++
93
94Register your module by adding the following in your :file:`ext_localconf.php`::
95
96    $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']['info']['submodules']['mynamespace_modulename'] => [
97        'module' => \Your\Namespace\Adminpanel\Modules\YourModule::class
98    ];
99
100Note the :php:`submodules` key in the array allowing you to introduce hierarchical configuration.
101
102
1033. Add additional interfaces
104----------------------------
105
106Your module is currently registered but is not doing anything yet, as it has no additional capabilities.
107The adminpanel provides additional separate interfaces (see list below).
108By implementing multiple interfaces you have fine-grained control over how your module behaves, which
109data it stores and how it gets rendered.
110
111Adminpanel Interfaces
112=====================
113
114ModuleInterface
115---------------
116
117Purpose
118+++++++
119Base interface all adminpanel modules share, defines common methods.
120
121Methods
122+++++++
123
124- :php:`getIdentifier()` - Returns :php:`string` identifier of a module (for example `mynamespace_modulename`)
125- :php:`getLabel()` - Returns speaking label of a module (for example `My Module`)
126
127
128ConfigurableInterface
129---------------------
130
131Purpose
132+++++++
133Used to indicate that an adminpanel module can be enabled or disabled via configuration
134
135Methods
136+++++++
137
138- :php:`isEnabled` - Returns :php:`bool` depending on whether the module is enabled.
139
140Example implementation
141++++++++++++++++++++++
142
143:php:`\TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule::isEnabled()`::
144
145    /**
146     * Returns true if the module is
147     * -> either enabled via TSConfig admPanel.enable
148     * -> or any setting is overridden
149     * override is a way to use functionality of the admin panel without displaying the panel to users
150     * for example: hidden records or pages can be displayed by default
151     *
152     * @return bool
153     */
154    public function isEnabled(): bool
155    {
156        $identifier = $this->getIdentifier();
157        $result = $this->isEnabledViaTsConfig();
158        if ($this->mainConfiguration['override.'][$identifier] ?? false) {
159            $result = (bool)$this->mainConfiguration['override.'][$identifier];
160        }
161        return $result;
162    }
163
164
165
166ContentProviderInterface
167------------------------
168
169Purpose
170+++++++
171Adminpanel interface to denote that a module has content to be rendered
172
173Methods
174+++++++
175
176* :php:`getContent(ModuleData $data)` - Return content as HTML. For modules implementing the :php:`DataProviderInterface`
177  the "ModuleData" object is automatically filled with the stored data - if no data is given a "fresh" ModuleData object is injected.
178
179Example implementation
180++++++++++++++++++++++
181
182:php:`\TYPO3\CMS\Adminpanel\Modules\Debug\QueryInformation::getContent`::
183
184    public function getContent(ModuleData $data): string
185    {
186        $view = new StandaloneView();
187        $view->setTemplatePathAndFilename(
188            'typo3/sysext/adminpanel/Resources/Private/Templates/Modules/Debug/QueryInformation.html'
189        );
190        $this->getLanguageService()->includeLLFile('EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf');
191        $view->assignMultiple($data->getArrayCopy());
192        return $view->render();
193    }
194
195
196DataProviderInterface
197------------------------
198
199Purpose
200+++++++
201Adminpanel interface to denote that a module provides data to be stored for the current request.
202
203Adminpanel modules can save data to the adminpanel request cache and access this data in the rendering process.
204Data necessary for rendering the module content has to be returned via this interface implementation, as this allows
205for separate data collection and rendering and is a pre-requisite for a standalone debug tool.
206
207Methods
208+++++++
209
210- :php:`getDataToStore(ServerRequestInterface $request): ModuleData` - Return a `ModuleData` instance with the data to store
211
212Example implementation
213++++++++++++++++++++++
214
215:php:`\TYPO3\CMS\Adminpanel\Modules\Info\RequestInformation::getDataToStore`::
216
217    public function getDataToStore(ServerRequestInterface $request): ModuleData
218    {
219        return new ModuleData(
220            [
221                'post' => $_POST,
222                'get' => $_GET,
223                'cookie' => $_COOKIE,
224                'session' => $_SESSION,
225                'server' => $_SERVER,
226            ]
227        );
228    }
229
230InitializableInterface
231------------------------
232
233Purpose
234+++++++
235
236Adminpanel interface to denote that a module has tasks to perform on initialization of the request.
237
238Modules that need to set data / options early in the rendering process to be able to collect data, should implement
239this interface - for example the log module uses the initialization to register the adminpanel log collection early
240in the rendering process.
241
242Initialize is called in the PSR-15 middleware stack through adminpanel initialisation via the AdminPanel MainController.
243
244Methods
245+++++++
246
247* :php:`initializeModule(ServerRequestInterface $request)` - Called on adminpanel initialization
248
249Example implementation
250++++++++++++++++++++++
251
252:php:`\TYPO3\CMS\Adminpanel\Modules\CacheModule::initializeModule`::
253
254    public function initializeModule(ServerRequestInterface $request): void
255    {
256        if ($this->configurationService->getConfigurationOption('cache', 'noCache')) {
257            $this->getTypoScriptFrontendController()->set_no_cache('Admin Panel: No Caching', true);
258        }
259    }
260
261
262ModuleSettingsProviderInterface
263-------------------------------
264
265Purpose
266+++++++
267
268Adminpanel module settings interface denotes that a module has own settings.
269
270The adminpanel knows two types of settings:
271
272* ModuleSettings are relevant for the module itself and its representation (for example the log module provides settings
273  where displayed log level and grouping of the module can be configured)
274
275* PageSettings are relevant for rendering the page (for example the preview module provides settings showing or hiding
276  hidden content elements or simulating a specific rendering time)
277
278If a module provides settings relevant to its own content, use this interface.
279
280Methods
281+++++++
282
283* :php:`getSettings(): string` - Return settings as rendered HTML form elements
284
285Example implementation
286++++++++++++++++++++++
287
288:php:`\TYPO3\CMS\Adminpanel\Modules\TsDebug\TypoScriptWaterfall::getSettings`::
289
290    public function getSettings(): string
291    {
292        $view = GeneralUtility::makeInstance(StandaloneView::class);
293        $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/TsDebug/TypoScriptSettings.html';
294        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
295        $view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
296
297        $view->assignMultiple(
298            [
299                'tree' => (int)$this->getConfigurationOption('tree'),
300                ...
301            ]
302        );
303
304        return $view->render();
305    }
306
307
308OnSubmitActorInterface
309-----------------------
310
311Purpose
312+++++++
313
314Adminpanel interface for modules that need to react on changed configuration
315(for example if fluid debug settings change, the frontend cache should be cleared).
316
317OnSubmitActors are currently called upon persisting new configuration _before_ the page is reloaded.
318
319Methods
320+++++++
321
322* :php:`onSubmit(array $configurationToSave, ServerRequestInterface $request)` - Can act when configuration gets saved.
323  Configuration form vars are provided in :php:`$configurationToSave` as an array.
324
325Example implementation
326++++++++++++++++++++++
327
328:php:`\TYPO3\CMS\Adminpanel\Modules\PreviewModule::onSubmit`::
329
330    /**
331     * Clear page cache if fluid debug output setting is changed
332     *
333     * @param array $input
334     * @param ServerRequestInterface $request
335     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
336     */
337    public function onSubmit(array $input, ServerRequestInterface $request): void
338    {
339        $activeConfiguration = (int)$this->getConfigOptionForModule('showFluidDebug');
340        if (isset($input['preview_showFluidDebug']) && (int)$input['preview_showFluidDebug'] !== $activeConfiguration) {
341            $pageId = (int)$request->getParsedBody()['TSFE_ADMIN_PANEL']['preview_clearCacheId'];
342            $cacheManager = GeneralUtility::makeInstance(CacheManager::class);
343            $cacheManager->getCache('cache_pages')->flushByTag('pageId_' . $pageId);
344            $cacheManager->getCache('fluid_template')->flush();
345        }
346    }
347
348
349
350PageSettingsProviderInterface
351-----------------------------
352
353Purpose
354+++++++
355
356Adminpanel page settings interface denotes that a module has settings regarding the page rendering.
357
358The adminpanel knows two types of settings:
359
360* ModuleSettings are relevant for the module itself and its representation (for example the log module provides settings
361  where displayed log level and grouping of the module can be configured)
362
363* PageSettings are relevant for rendering the page (for example the preview module provides settings showing or hiding
364  hidden content elements or simulating a specific rendering time)
365
366If a module provides settings changing the rendering of the main page request, use this interface.
367
368Methods
369+++++++
370
371* :php:`getSettings(): string` - Return HTML form elements for settings
372
373Example implementation
374++++++++++++++++++++++
375
376:php:`\TYPO3\CMS\Adminpanel\Modules\EditModule::getPageSettings`::
377
378    public function getPageSettings(): string
379    {
380        $editToolbarService = GeneralUtility::makeInstance(EditToolbarService::class);
381        $toolbar = $editToolbarService->createToolbar();
382        $view = GeneralUtility::makeInstance(StandaloneView::class);
383        $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/Settings/Edit.html';
384        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
385        $view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
386        $view->assignMultiple(
387            [
388                'feEdit' => ExtensionManagementUtility::isLoaded('feedit'),
389                ...
390            ]
391        );
392        return $view->render();
393
394
395PageSettingsProviderInterface
396-----------------------------
397
398Purpose
399+++++++
400
401Adminpanel interface to denote that a module has own resource files.
402
403An adminpanel module implementing this interface may deliver custom JavaScript and Css files to provide additional
404styling and JavaScript functionality
405
406Methods
407+++++++
408
409* :php:`getJavaScriptFiles(): array` - Returns a string array with javascript files that will be rendered after the module
410* :php:`getCssFiles(): array` - Returns a string array with CSS files that will be rendered after the module
411
412Example implementation
413++++++++++++++++++++++
414
415:php:`\TYPO3\CMS\Adminpanel\Modules\TsDebugModule`::
416
417    public function getJavaScriptFiles(): array
418    {
419        return ['EXT:adminpanel/Resources/Public/JavaScript/Modules/TsDebug.js'];
420    }
421
422    public function getCssFiles(): array
423    {
424        return [];
425    }
426
427
428ShortInfoProviderInterface
429-----------------------------
430
431Purpose
432+++++++
433
434Adminpanel shortinfo provider interface can be used to add the module to the short info bar of the adminpanel.
435
436Modules providing shortinfo will be displayed in the bottom bar of the adminpanel and may provide "at a glance" info
437about the current state (for example the log module provides the number of warnings and errors directly).
438
439Be aware that modules with submodules at the moment can only render one short info (the one of the "parent" module).
440This will likely change in v10.
441
442Methods
443+++++++
444
445* :php:`getShortInfo(): string` - Info string (no HTML) that should be rendered
446* :php:`getIconIdentifier(): string` - An icon for this info line, needs to be registered in :php:`IconRegistry`
447
448Example implementation
449++++++++++++++++++++++
450
451:php:`\TYPO3\CMS\Adminpanel\Modules\InfoModule`::
452
453    public function getShortInfo(): string
454    {
455        $parseTime = $this->getTimeTracker()->getParseTime();
456        return sprintf($this->getLanguageService()->sL(
457            'LLL:EXT:adminpanel/Resources/Private/Language/locallang_info.xlf:module.shortinfo'
458        ), $parseTime);
459    }
460
461    public function getIconIdentifier(): string
462    {
463        return 'actions-document-info';
464    }
465
466
467
468SubmoduleProviderInterface
469-----------------------------
470
471Purpose
472+++++++
473
474Adminpanel interface providing hierarchical functionality for modules.
475
476A module implementing this interface may have submodules. Be aware that the current implementation of the adminpanel
477renders a maximum level of 2 for modules. If you need to render more levels, write your own module and implement
478multi-hierarchical rendering in the getContent method.
479
480Methods
481+++++++
482
483* :php:`setSubModules(array $subModules)` - Sets array of module instances (instances of :php:`ModuleInterface`) as submodules
484* :php:`getSubModules(): array` - Returns an array of module instances
485* :php:`hasSubmoduleSettings(): bool` - Return true if any of the submodules has settings to be rendered
486  (can be used to render settings in a central place)
487
488Example implementation
489++++++++++++++++++++++
490
491:php:`\TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule`::
492
493
494    public function setSubModules(array $subModules): void
495    {
496        $this->subModules = $subModules;
497    }
498
499    public function getSubModules(): array
500    {
501        return $this->subModules;
502    }
503
504    public function hasSubmoduleSettings(): bool
505    {
506        $hasSettings = false;
507        foreach ($this->subModules as $subModule) {
508            if ($subModule instanceof ModuleSettingsProviderInterface) {
509                $hasSettings = true;
510                break;
511            }
512            if ($subModule instanceof SubmoduleProviderInterface) {
513                $hasSettings = $subModule->hasSubmoduleSettings();
514            }
515        }
516        return $hasSettings;
517    }
518
519
520.. index:: Frontend, PHP-API, ext:adminpanel
521