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