[FEATURE] Refactor AdminPanel API to use composition 87/57987/14
authorSusanne Moog <susanne.moog@typo3.org>
Sun, 26 Aug 2018 14:51:26 +0000 (16:51 +0200)
committerBenni Mack <benni@typo3.org>
Thu, 30 Aug 2018 16:58:49 +0000 (18:58 +0200)
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 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 is a preparation
for a standalone adminpanel that can be implemented separately.

Resolves: #86003
Releases: master
Change-Id: I88236a86e612dee9085b9f6ad7da34f90770d5ed
Reviewed-on: https://review.typo3.org/57987
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benjamin Kott <benjamin.kott@outlook.com>
Tested-by: Benjamin Kott <benjamin.kott@outlook.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
56 files changed:
typo3/sysext/adminpanel/Classes/Controller/MainController.php
typo3/sysext/adminpanel/Classes/Hooks/RenderHook.php [deleted file]
typo3/sysext/adminpanel/Classes/Middleware/AdminPanelDataPersister.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/Middleware/AdminPanelInitiator.php
typo3/sysext/adminpanel/Classes/Middleware/AdminPanelRenderer.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/Middleware/SqlLogging.php
typo3/sysext/adminpanel/Classes/ModuleApi/AbstractModule.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/AbstractSubModule.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/ConfigurableInterface.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/ContentProviderInterface.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/DataProviderInterface.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/InitializableInterface.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/ModuleData.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/ModuleDataStorageCollection.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/ModuleInterface.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/ModuleSettingsProviderInterface.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/OnSubmitActorInterface.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/PageSettingsProviderInterface.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/ResourceProviderInterface.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/ShortInfoProviderInterface.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/ModuleApi/SubmoduleProviderInterface.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/Modules/AbstractModule.php [deleted file]
typo3/sysext/adminpanel/Classes/Modules/AbstractSubModule.php [deleted file]
typo3/sysext/adminpanel/Classes/Modules/AdminPanelModuleInterface.php [deleted file]
typo3/sysext/adminpanel/Classes/Modules/AdminPanelSubModuleInterface.php [deleted file]
typo3/sysext/adminpanel/Classes/Modules/CacheModule.php
typo3/sysext/adminpanel/Classes/Modules/Debug/Log.php
typo3/sysext/adminpanel/Classes/Modules/Debug/QueryInformation.php
typo3/sysext/adminpanel/Classes/Modules/DebugModule.php
typo3/sysext/adminpanel/Classes/Modules/EditModule.php
typo3/sysext/adminpanel/Classes/Modules/Info/GeneralInformation.php
typo3/sysext/adminpanel/Classes/Modules/Info/PhpInformation.php
typo3/sysext/adminpanel/Classes/Modules/Info/RequestInformation.php
typo3/sysext/adminpanel/Classes/Modules/InfoModule.php
typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php
typo3/sysext/adminpanel/Classes/Modules/TsDebug/TypoScriptWaterfall.php
typo3/sysext/adminpanel/Classes/Modules/TsDebugModule.php
typo3/sysext/adminpanel/Classes/Service/ConfigurationService.php
typo3/sysext/adminpanel/Classes/Service/ModuleLoader.php
typo3/sysext/adminpanel/Classes/Utility/ResourceUtility.php [new file with mode: 0644]
typo3/sysext/adminpanel/Classes/Utility/StateUtility.php
typo3/sysext/adminpanel/Classes/View/AdminPanelView.php
typo3/sysext/adminpanel/Classes/ViewHelpers/SubModuleRenderViewHelper.php [new file with mode: 0644]
typo3/sysext/adminpanel/Configuration/RequestMiddlewares.php
typo3/sysext/adminpanel/Resources/Private/Layouts/Default.html
typo3/sysext/adminpanel/Resources/Private/Partials/Modules/Item.html
typo3/sysext/adminpanel/Resources/Private/Templates/Main.html
typo3/sysext/adminpanel/Tests/Unit/Fixtures/DisabledMainModuleFixture.php
typo3/sysext/adminpanel/Tests/Unit/Fixtures/MainModuleFixture.php
typo3/sysext/adminpanel/Tests/Unit/Fixtures/SubModuleFixture.php
typo3/sysext/adminpanel/Tests/Unit/Middleware/AdminPanelInitiatorTest.php
typo3/sysext/adminpanel/Tests/Unit/Service/ModuleLoaderTest.php
typo3/sysext/adminpanel/Tests/Unit/Utility/StateUtilityTest.php
typo3/sysext/adminpanel/ext_localconf.php
typo3/sysext/core/Documentation/Changelog/master/Feature-84584-Re-DesignTheAdminPanel.rst
typo3/sysext/core/Documentation/Changelog/master/Feature-86003-AdminpanelCompositionApi.rst [new file with mode: 0644]

index b347901..c22a08b 100644 (file)
@@ -17,15 +17,23 @@ namespace TYPO3\CMS\Adminpanel\Controller;
  */
 
 use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Adminpanel\Modules\AdminPanelModuleInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ConfigurableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\InitializableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleDataStorageCollection;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\PageSettingsProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ShortInfoProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\SubmoduleProviderInterface;
 use TYPO3\CMS\Adminpanel\Service\ConfigurationService;
 use TYPO3\CMS\Adminpanel\Service\ModuleLoader;
+use TYPO3\CMS\Adminpanel\Utility\ResourceUtility;
+use TYPO3\CMS\Adminpanel\Utility\StateUtility;
 use TYPO3\CMS\Adminpanel\View\AdminPanelView;
-use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
+use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\SingletonInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\PathUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 
 /**
@@ -36,7 +44,7 @@ use TYPO3\CMS\Fluid\View\StandaloneView;
 class MainController implements SingletonInterface
 {
     /**
-     * @var AdminPanelModuleInterface[]
+     * @var \TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface[]
      */
     protected $modules = [];
 
@@ -72,7 +80,9 @@ class MainController implements SingletonInterface
     ) {
         $this->moduleLoader = $moduleLoader ?? GeneralUtility::makeInstance(ModuleLoader::class);
         $this->uriBuilder = $uriBuilder ?? GeneralUtility::makeInstance(UriBuilder::class);
-        $this->configurationService = $configurationService ?? GeneralUtility::makeInstance(ConfigurationService::class);
+        $this->configurationService = $configurationService
+                                      ??
+                                      GeneralUtility::makeInstance(ConfigurationService::class);
         $this->adminPanelModuleConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules'] ?? [];
     }
 
@@ -86,37 +96,27 @@ class MainController implements SingletonInterface
         $this->modules = $this->moduleLoader->validateSortAndInitializeModules(
             $this->adminPanelModuleConfiguration
         );
-        $this->configurationService->saveConfiguration($this->modules, $request);
 
-        if ($this->isAdminPanelActivated()) {
-            foreach ($this->modules as $module) {
-                if ($module->isEnabled()) {
-                    $subModules = $this->moduleLoader->validateSortAndInitializeSubModules(
-                        $this->adminPanelModuleConfiguration[$module->getIdentifier()]['submodules'] ?? []
-                    );
-                    foreach ($subModules as $subModule) {
-                        $subModule->initializeModule($request);
-                    }
-                    $module->setSubModules($subModules);
-                    $module->initializeModule($request);
-                }
-            }
+        if (StateUtility::isActivatedForUser()) {
+            $this->initializeModules($request, $this->modules);
         }
     }
 
     /**
-     * Renders the admin panel
+     * Renders the admin panel - Called in PSR-15 Middleware
      *
+     * @see \TYPO3\CMS\Adminpanel\Middleware\AdminPanelRenderer
+     * @param \Psr\Http\Message\ServerRequestInterface $request
      * @return string
      */
-    public function render(): string
+    public function render(ServerRequestInterface $request): string
     {
         // legacy handling, deprecated, will be removed in TYPO3 v10.0.
         $adminPanelView = GeneralUtility::makeInstance(AdminPanelView::class);
         $hookObjectContent = $adminPanelView->callDeprecatedHookObject();
         // end legacy handling
 
-        $resources = $this->getResources();
+        $resources = ResourceUtility::getResources();
 
         $view = GeneralUtility::makeInstance(StandaloneView::class);
         $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Main.html';
@@ -128,25 +128,61 @@ class MainController implements SingletonInterface
             [
                 'toggleActiveUrl' => $this->generateBackendUrl('ajax_adminPanel_toggle'),
                 'resources' => $resources,
-                'adminPanelActive' => $this->isAdminPanelActivated(),
+                'adminPanelActive' => StateUtility::isOpen(),
             ]
         );
-        if ($this->isAdminPanelActivated()) {
-            $moduleResources = $this->getAdditionalResourcesForModules($this->modules);
+        if (StateUtility::isOpen()) {
+            $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('adminpanel_requestcache');
+            $requestId = $request->getAttribute('adminPanelRequestId');
+            $data = $cache->get($requestId);
+            $moduleResources = ResourceUtility::getAdditionalResourcesForModules($this->modules);
+            $settingsModules = array_filter($this->modules, function (ModuleInterface $module) {
+                return $module instanceof PageSettingsProviderInterface;
+            });
+            $parentModules = array_filter(
+                $this->modules,
+                function (ModuleInterface $module) {
+                    return $module instanceof SubmoduleProviderInterface && $module instanceof ShortInfoProviderInterface;
+                }
+            );
             $view->assignMultiple(
                 [
                     'modules' => $this->modules,
+                    'settingsModules' => $settingsModules,
+                    'parentModules' => $parentModules,
                     'hookObjectContent' => $hookObjectContent,
                     'saveUrl' => $this->generateBackendUrl('ajax_adminPanel_saveForm'),
                     'moduleResources' => $moduleResources,
+                    'requestId' => $requestId,
+                    'data' => $data ?? [],
                 ]
             );
         }
-
         return $view->render();
     }
 
     /**
+     * Stores data for admin panel in cache - Called in PSR-15 Middleware
+     *
+     * @see \TYPO3\CMS\Adminpanel\Middleware\AdminPanelDataPersister
+     * @param \Psr\Http\Message\ServerRequestInterface $request
+     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
+     */
+    public function storeData(ServerRequestInterface $request): void
+    {
+        if (StateUtility::isOpen()) {
+            $data = $this->storeDataPerModule(
+                $request,
+                $this->modules,
+                GeneralUtility::makeInstance(ModuleDataStorageCollection::class)
+            );
+            $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('adminpanel_requestcache');
+            $cache->set($request->getAttribute('adminPanelRequestId'), $data);
+            $cache->collectGarbage();
+        }
+    }
+
+    /**
      * Generate a url to a backend route
      *
      * @param string $route
@@ -158,113 +194,50 @@ class MainController implements SingletonInterface
     }
 
     /**
-     * Get additional resources (css, js) from modules and merge it to
-     * one array - returns an array of full html tags
-     *
-     * @param AdminPanelModuleInterface[] $modules
-     * @return array
+     * @param \Psr\Http\Message\ServerRequestInterface $request
+     * @param \TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface[] $modules
      */
-    protected function getAdditionalResourcesForModules(array $modules): array
+    protected function initializeModules(ServerRequestInterface $request, array $modules): void
     {
-        $result = [
-            'js' => '',
-            'css' => '',
-        ];
         foreach ($modules as $module) {
-            foreach ($module->getJavaScriptFiles() as $file) {
-                $result['js'] .= $this->getJsTag($file);
+            if (
+                ($module instanceof InitializableInterface)
+                && (
+                    (($module instanceof ConfigurableInterface) && $module->isEnabled())
+                    || (!($module instanceof ConfigurableInterface))
+                )
+            ) {
+                $module->initializeModule($request);
             }
-            foreach ($module->getCssFiles() as $file) {
-                $result['css'] .= $this->getCssTag($file);
+            if ($module instanceof SubmoduleProviderInterface) {
+                $this->initializeModules($request, $module->getSubModules());
             }
         }
-        return $result;
-    }
-
-    /**
-     * Returns a link tag with the admin panel stylesheet
-     * defined using TBE_STYLES
-     *
-     * @return string
-     */
-    protected function getAdminPanelStylesheet(): string
-    {
-        $result = '';
-        if (!empty($GLOBALS['TBE_STYLES']['stylesheets']['admPanel'])) {
-            $stylesheet = GeneralUtility::locationHeaderUrl($GLOBALS['TBE_STYLES']['stylesheets']['admPanel']);
-            $result = '<link rel="stylesheet" type="text/css" href="' .
-                      htmlspecialchars($stylesheet, ENT_QUOTES | ENT_HTML5) . '" />';
-        }
-        return $result;
-    }
-
-    /**
-     * Returns the current BE user.
-     *
-     * @return FrontendBackendUserAuthentication
-     */
-    protected function getBackendUser(): FrontendBackendUserAuthentication
-    {
-        return $GLOBALS['BE_USER'];
-    }
-
-    /**
-     * Get a css tag for file - with absolute web path resolving
-     *
-     * @param string $cssFileLocation
-     * @return string
-     */
-    protected function getCssTag(string $cssFileLocation): string
-    {
-        $css = '<link type="text/css" rel="stylesheet" href="' .
-               htmlspecialchars(
-                   PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($cssFileLocation)),
-                   ENT_QUOTES | ENT_HTML5
-               ) .
-               '" media="all" />';
-        return $css;
     }
 
     /**
-     * Get a script tag for JavaScript with absolute paths
-     *
-     * @param string $jsFileLocation
-     * @return string
+     * @param \Psr\Http\Message\ServerRequestInterface $request
+     * @param \TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface[] $modules
+     * @param ModuleDataStorageCollection $data
+     * @return ModuleDataStorageCollection
      */
-    protected function getJsTag(string $jsFileLocation): string
+    protected function storeDataPerModule(ServerRequestInterface $request, array $modules, ModuleDataStorageCollection $data): ModuleDataStorageCollection
     {
-        $js = '<script type="text/javascript" src="' .
-              htmlspecialchars(
-                  PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($jsFileLocation)),
-                  ENT_QUOTES | ENT_HTML5
-              ) .
-              '"></script>';
-        return $js;
-    }
-
-    /**
-     * Return a string with tags for main admin panel resources
-     *
-     * @return string
-     */
-    protected function getResources(): string
-    {
-        $jsFileLocation = 'EXT:adminpanel/Resources/Public/JavaScript/AdminPanel.js';
-        $js = $this->getJsTag($jsFileLocation);
-        $cssFileLocation = 'EXT:adminpanel/Resources/Public/Css/adminpanel.css';
-        $css = $this->getCssTag($cssFileLocation);
-
-        return $css . $this->getAdminPanelStylesheet() . $js;
-    }
+        foreach ($modules as $module) {
+            if (
+                ($module instanceof DataProviderInterface)
+                && (
+                    (($module instanceof ConfigurableInterface) && $module->isEnabled())
+                    || (!($module instanceof ConfigurableInterface))
+                )
+            ) {
+                $data->addModuleData($module, $module->getDataToStore($request));
+            }
 
-    /**
-     * Returns true if admin panel was activated
-     * (switched "on" via GUI)
-     *
-     * @return bool
-     */
-    protected function isAdminPanelActivated(): bool
-    {
-        return (bool)($this->getBackendUser()->uc['AdminPanel']['display_top'] ?? false);
+            if ($module instanceof SubmoduleProviderInterface) {
+                $this->storeDataPerModule($request, $module->getSubModules(), $data);
+            }
+        }
+        return $data;
     }
 }
diff --git a/typo3/sysext/adminpanel/Classes/Hooks/RenderHook.php b/typo3/sysext/adminpanel/Classes/Hooks/RenderHook.php
deleted file mode 100644 (file)
index 21d4db5..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-declare(strict_types = 1);
-
-namespace TYPO3\CMS\Adminpanel\Hooks;
-
-/*
- * 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!
- */
-
-use TYPO3\CMS\Adminpanel\Controller\MainController;
-use TYPO3\CMS\Adminpanel\Utility\StateUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
-
-/**
- * Hook to render the admin panel
- *
- * @internal
- */
-class RenderHook
-{
-    /**
-     * Hook to render the admin panel
-     * We use a hook this late in the project to make sure all data is collected and can be displayed
-     * As the main content is already rendered, we use a string replace on the content to append the adminPanel
-     * to the HTML body.
-     *
-     * @param array $params
-     * @param TypoScriptFrontendController $pObj
-     */
-    public function renderAdminPanel(array $params, TypoScriptFrontendController $pObj): void
-    {
-        if (
-            StateUtility::isActivated() &&
-            !$GLOBALS['BE_USER']->extAdminConfig['hide'] && $pObj->config['config']['admPanel']
-        ) {
-            $mainController = GeneralUtility::makeInstance(MainController::class);
-
-            $pObj->content = str_ireplace(
-                '</body>',
-                $mainController->render() . '</body>',
-                $pObj->content
-            );
-        }
-    }
-}
diff --git a/typo3/sysext/adminpanel/Classes/Middleware/AdminPanelDataPersister.php b/typo3/sysext/adminpanel/Classes/Middleware/AdminPanelDataPersister.php
new file mode 100644 (file)
index 0000000..24fb844
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\Middleware;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Adminpanel\Controller\MainController;
+use TYPO3\CMS\Adminpanel\Utility\StateUtility;
+use TYPO3\CMS\Core\Http\NullResponse;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+
+/**
+ * Store request data of current request in cache for rendering in admin panel
+ *
+ * @internal
+ */
+class AdminPanelDataPersister implements MiddlewareInterface
+{
+    /**
+     * Render the admin panel if activated
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $response = $handler->handle($request);
+        if (
+            !($response instanceof NullResponse)
+            && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController
+            && $GLOBALS['TSFE']->isOutputting()
+            && StateUtility::isActivatedForUser()
+            && StateUtility::isActivatedInTypoScript()
+            && !StateUtility::isHiddenForUser()
+        ) {
+            $mainController = GeneralUtility::makeInstance(MainController::class);
+            $mainController->storeData($request);
+        }
+        return $response;
+    }
+}
index 9c6ca7f..d31bcbb 100644 (file)
@@ -44,7 +44,8 @@ class AdminPanelInitiator implements MiddlewareInterface
      */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
-        if (StateUtility::isActivated() && StateUtility::isOpen()) {
+        if (StateUtility::isActivatedForUser() && StateUtility::isOpen()) {
+            $request = $request->withAttribute('adminPanelRequestId', substr(md5(uniqid('', true)), 0, 13));
             $adminPanelController = GeneralUtility::makeInstance(
                 MainController::class
             );
diff --git a/typo3/sysext/adminpanel/Classes/Middleware/AdminPanelRenderer.php b/typo3/sysext/adminpanel/Classes/Middleware/AdminPanelRenderer.php
new file mode 100644 (file)
index 0000000..628f2a0
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\Middleware;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Adminpanel\Controller\MainController;
+use TYPO3\CMS\Adminpanel\Utility\StateUtility;
+use TYPO3\CMS\Core\Http\NullResponse;
+use TYPO3\CMS\Core\Http\Stream;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+
+/**
+ * Render the admin panel via PSR-15 middleware
+ *
+ * @internal
+ */
+class AdminPanelRenderer implements MiddlewareInterface
+{
+
+    /**
+     * Render the admin panel if activated
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $response = $handler->handle($request);
+        if (
+            !($response instanceof NullResponse)
+            && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController
+            && $GLOBALS['TSFE']->isOutputting()
+            && StateUtility::isActivatedForUser()
+            && StateUtility::isActivatedInTypoScript()
+            && !StateUtility::isHiddenForUser()
+        ) {
+            $mainController = GeneralUtility::makeInstance(MainController::class);
+            $body = $response->getBody();
+            $body->rewind();
+            $contents = $response->getBody()->getContents();
+            $content = str_ireplace(
+                '</body>',
+                $mainController->render($request) . '</body>',
+                $contents
+            );
+            $body = new Stream('php://temp', 'rw');
+            $body->write($content);
+            $response = $response->withBody($body);
+        }
+        return $response;
+    }
+}
index cb19610..720d7c8 100644 (file)
@@ -38,7 +38,7 @@ class SqlLogging implements MiddlewareInterface
      */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
-        if (StateUtility::isActivated() && StateUtility::isOpen()) {
+        if (StateUtility::isActivatedForUser() && StateUtility::isOpen()) {
             $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
             $connection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
             $connection->getConfiguration()->setSQLLogger(GeneralUtility::makeInstance(DoctrineSqlLogger::class));
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractModule.php b/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractModule.php
new file mode 100644 (file)
index 0000000..6058fb6
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Adminpanel\Service\ConfigurationService;
+use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Abstract base class for Admin Panel Modules containing helper methods and default interface implementations
+ * Extend this class when writing own admin panel modules (or implement the Interface directly)
+ */
+abstract class AbstractModule implements ModuleInterface, ConfigurableInterface, SubmoduleProviderInterface
+{
+
+    /**
+     * @var ModuleInterface[]
+     */
+    protected $subModules = [];
+
+    /**
+     * Main Configuration (from UserTSConfig, admPanel)
+     *
+     * @var array
+     */
+    protected $mainConfiguration;
+
+    /**
+     * @var ConfigurationService
+     */
+    protected $configurationService;
+
+    public function __construct()
+    {
+        $this->configurationService = GeneralUtility::makeInstance(ConfigurationService::class);
+        $this->mainConfiguration = $this->configurationService->getMainConfiguration();
+    }
+
+    /**
+     * 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 admin 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;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setSubModules(array $subModules): void
+    {
+        $this->subModules = $subModules;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getSubModules(): array
+    {
+        return $this->subModules;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    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;
+    }
+
+    /**
+     * Returns the current BE user.
+     *
+     * @return BackendUserAuthentication|FrontendBackendUserAuthentication
+     */
+    protected function getBackendUser(): BackendUserAuthentication
+    {
+        return $GLOBALS['BE_USER'];
+    }
+
+    /**
+     * Returns LanguageService
+     *
+     * @return LanguageService
+     */
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
+
+    /**
+     * Returns true if TSConfig admPanel.enable is set for this module (or all modules)
+     *
+     * @return bool
+     */
+    protected function isEnabledViaTsConfig(): bool
+    {
+        $result = false;
+        $identifier = $this->getIdentifier();
+        if (!empty($this->mainConfiguration['enable.']['all'])) {
+            $result = true;
+        } elseif (!empty($this->mainConfiguration['enable.'][$identifier])) {
+            $result = true;
+        }
+        return $result;
+    }
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractSubModule.php b/typo3/sysext/adminpanel/Classes/ModuleApi/AbstractSubModule.php
new file mode 100644 (file)
index 0000000..dd58e3b
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Localization\LanguageService;
+
+/**
+ * Abstract SubModule - Base class for sub modules in the admin panel
+ *
+ * Extend this class when writing own sub modules
+ */
+abstract class AbstractSubModule implements ModuleInterface
+{
+
+    /**
+     * Returns LanguageService
+     *
+     * @return LanguageService
+     */
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/ConfigurableInterface.php b/typo3/sysext/adminpanel/Classes/ModuleApi/ConfigurableInterface.php
new file mode 100644 (file)
index 0000000..80adf8a
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+/**
+ * AdminPanel ConfigurableInterface
+ *
+ * Used to indicate that an adminpanel module can be enabled or disabled via configuration
+ *
+ * Usual implementation is done via user tsconfig
+ * @see \TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule::isEnabled()
+ */
+interface ConfigurableInterface
+{
+    /**
+     * Module is enabled
+     * -> should be initialized
+     * A module may be enabled but not shown
+     * -> only the initializeModule() method
+     * will be called
+     *
+     * @return bool
+     */
+    public function isEnabled(): bool;
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/ContentProviderInterface.php b/typo3/sysext/adminpanel/Classes/ModuleApi/ContentProviderInterface.php
new file mode 100644 (file)
index 0000000..52fa0ba
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+/**
+ * Adminpanel interface to denote that a module has content to be rendered
+ *
+ * In general there are two main types of admin panel modules: those providing settings influencing page rendering
+ * (for example the preview module provides settings for displaying hidden pages or records) and those rendering
+ * information regarding the current request (for example the log module).
+ *
+ * Modules implementing this interface denote that they render module content.
+ */
+interface ContentProviderInterface
+{
+    /**
+     * Main method for content generation of an admin panel module.
+     * Return content as HTML. For modules implementing the DataProviderInterface
+     * the "ModuleData" object is automatically filled with the stored data - if
+     * no data is given a "fresh" ModuleData object is injected.
+     *
+     * @param \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData $data
+     * @return string
+     */
+    public function getContent(ModuleData $data): string;
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/DataProviderInterface.php b/typo3/sysext/adminpanel/Classes/ModuleApi/DataProviderInterface.php
new file mode 100644 (file)
index 0000000..9257adc
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ServerRequestInterface;
+
+/**
+ * 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.
+ */
+interface DataProviderInterface
+{
+
+    /**
+     * @param \Psr\Http\Message\ServerRequestInterface $request
+     * @return \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData
+     */
+    public function getDataToStore(ServerRequestInterface $request): ModuleData;
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/InitializableInterface.php b/typo3/sysext/adminpanel/Classes/ModuleApi/InitializableInterface.php
new file mode 100644 (file)
index 0000000..9377e1a
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ServerRequestInterface;
+
+/**
+ * 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 admin panel log collection early
+ * in the rendering process.
+ *
+ * Initialize is called in the PSR-15 middleware stack through admin panel initialisation via the AdminPanel MainController.
+ *
+ * @see \TYPO3\CMS\Adminpanel\Middleware\AdminPanelInitiator::process()
+ * @see \TYPO3\CMS\Adminpanel\Controller\MainController::initialize()
+ */
+interface InitializableInterface
+{
+    /**
+     * Initialize the module - runs early in a TYPO3 request
+     *
+     * @param ServerRequestInterface $request
+     */
+    public function initializeModule(ServerRequestInterface $request): void;
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleData.php b/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleData.php
new file mode 100644 (file)
index 0000000..432f5b2
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+declare(strict_types = 1);
+
+/*
+ * 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!
+ */
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/**
+ * ModuleData is a simple wrapper object which extends ArrayObject
+ * which is used to hold Adminpanel module data
+ *
+ * It's a separate class to add semantic meaning to its' usage
+ */
+class ModuleData extends \ArrayObject
+{
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleDataStorageCollection.php b/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleDataStorageCollection.php
new file mode 100644 (file)
index 0000000..8480f35
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+/**
+ * ModuleDataStorageCollection is an object storage for adminpanel modules and their data
+ */
+class ModuleDataStorageCollection extends \SplObjectStorage
+{
+    public function addModuleData(DataProviderInterface $module, ModuleData $moduleData): void
+    {
+        $this->attach($module, $moduleData);
+    }
+
+    public function getHash($object)
+    {
+        if ($object instanceof ModuleInterface) {
+            return $object->getIdentifier();
+        }
+        throw new \InvalidArgumentException(
+            'Only modules implementing ' . ModuleInterface::class . ' are allowed to be stored',
+            1535301628
+        );
+    }
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleInterface.php b/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleInterface.php
new file mode 100644 (file)
index 0000000..41f169e
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+/**
+ * Adminpanel interface for all modules
+ *
+ * Every adminpanel module needs to implement this interface as a bare minimum.
+ */
+interface ModuleInterface
+{
+    /**
+     * Identifier for this module,
+     * for example "preview" or "cache"
+     *
+     * @return string
+     */
+    public function getIdentifier(): string;
+
+    /**
+     * Module label
+     *
+     * @return string
+     */
+    public function getLabel(): string;
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleSettingsProviderInterface.php b/typo3/sysext/adminpanel/Classes/ModuleApi/ModuleSettingsProviderInterface.php
new file mode 100644 (file)
index 0000000..c9150cc
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+/**
+ * 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.
+ *
+ * @see \TYPO3\CMS\Adminpanel\ModuleApi\PageSettingsProviderInterface
+ */
+interface ModuleSettingsProviderInterface
+{
+    /**
+     * @return string
+     */
+    public function getSettings(): string;
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/OnSubmitActorInterface.php b/typo3/sysext/adminpanel/Classes/ModuleApi/OnSubmitActorInterface.php
new file mode 100644 (file)
index 0000000..57fd841
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+use Psr\Http\Message\ServerRequestInterface;
+
+/**
+ * Adminpanel interface for modules that need to react on changed configuration
+ * (for example if fluid debug settings change, the frontend cache should be cleared)
+ */
+interface OnSubmitActorInterface
+{
+    /**
+     * Executed on saving / submit of the configuration form
+     * Can be used to react to changed settings
+     * (for example: clearing a specific cache)
+     *
+     * @param array $configurationToSave
+     * @param ServerRequestInterface $request
+     */
+    public function onSubmit(array $configurationToSave, ServerRequestInterface $request): void;
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/PageSettingsProviderInterface.php b/typo3/sysext/adminpanel/Classes/ModuleApi/PageSettingsProviderInterface.php
new file mode 100644 (file)
index 0000000..13e0750
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+/**
+ * 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.
+ *
+ * @see \TYPO3\CMS\Adminpanel\ModuleApi\ModuleSettingsProviderInterface
+ */
+interface PageSettingsProviderInterface
+{
+    /**
+     * @return string
+     */
+    public function getPageSettings(): string;
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/ResourceProviderInterface.php b/typo3/sysext/adminpanel/Classes/ModuleApi/ResourceProviderInterface.php
new file mode 100644 (file)
index 0000000..34fb639
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+/**
+ * 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
+ */
+interface ResourceProviderInterface
+{
+    /**
+     * Returns a string array with javascript files that will be rendered after the module
+     *
+     * Example: return ['EXT:adminpanel/Resources/Public/JavaScript/Modules/Edit.js'];
+     *
+     * @return array
+     */
+    public function getJavaScriptFiles(): array;
+
+    /**
+     * Returns a string array with css files that will be rendered after the module
+     *
+     * Example: return ['EXT:adminpanel/Resources/Public/JavaScript/Modules/Edit.css'];
+     *
+     * @return array
+     */
+    public function getCssFiles(): array;
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/ShortInfoProviderInterface.php b/typo3/sysext/adminpanel/Classes/ModuleApi/ShortInfoProviderInterface.php
new file mode 100644 (file)
index 0000000..6ccb2e4
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+/**
+ * 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.
+ */
+interface ShortInfoProviderInterface
+{
+    /**
+     * Displayed directly in the bar
+     *
+     * @return string
+     */
+    public function getShortInfo(): string;
+
+    /**
+     * Icon identifier - needs to be registered in iconRegistry
+     *
+     * @return string
+     */
+    public function getIconIdentifier(): string;
+}
diff --git a/typo3/sysext/adminpanel/Classes/ModuleApi/SubmoduleProviderInterface.php b/typo3/sysext/adminpanel/Classes/ModuleApi/SubmoduleProviderInterface.php
new file mode 100644 (file)
index 0000000..674a6fd
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ModuleApi;
+
+/*
+ * 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!
+ */
+
+/**
+ * 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
+ *
+ * @see \TYPO3\CMS\Adminpanel\ModuleApi\ContentProviderInterface::getContent()
+ */
+interface SubmoduleProviderInterface
+{
+    /**
+     * Sets array of module instances (instances of `ModuleInterface`) as submodules
+     *
+     * @param \TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface[] $subModules
+     */
+    public function setSubModules(array $subModules): void;
+
+    /**
+     * Returns an array of module instances
+     *
+     * @return \TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface[]
+     */
+    public function getSubModules(): array;
+
+    /**
+     * Return true if any of the submodules has settings to be rendered
+     * (can be used to render settings in a central place)
+     *
+     * @return bool
+     */
+    public function hasSubmoduleSettings(): bool;
+}
diff --git a/typo3/sysext/adminpanel/Classes/Modules/AbstractModule.php b/typo3/sysext/adminpanel/Classes/Modules/AbstractModule.php
deleted file mode 100644 (file)
index a140075..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-<?php
-declare(strict_types = 1);
-
-namespace TYPO3\CMS\Adminpanel\Modules;
-
-/*
- * 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!
- */
-
-use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Adminpanel\Service\ConfigurationService;
-use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
-use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Localization\LanguageService;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-/**
- * Abstract base class for Admin Panel Modules containing helper methods and default interface implementations
- *
- * Extend this class when writing own admin panel modules (or implement the Interface directly)
- */
-abstract class AbstractModule implements AdminPanelModuleInterface
-{
-
-    /**
-     * @var AdminPanelSubModuleInterface[]
-     */
-    protected $subModules = [];
-
-    /**
-     * Main Configuration (from UserTSConfig, admPanel)
-     *
-     * @var array
-     */
-    protected $mainConfiguration;
-
-    /**
-     * @var ConfigurationService
-     */
-    protected $configurationService;
-
-    public function __construct()
-    {
-        $this->configurationService = GeneralUtility::makeInstance(ConfigurationService::class);
-        $this->mainConfiguration = $this->configurationService->getMainConfiguration();
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function getSettings(): string
-    {
-        return '';
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function getIconIdentifier(): string
-    {
-        return '';
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function initializeModule(ServerRequestInterface $request): void
-    {
-    }
-
-    /**
-     * 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 admin 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;
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function onSubmit(array $input, ServerRequestInterface $request): void
-    {
-    }
-
-    /**
-     * Returns the current BE user.
-     *
-     * @return BackendUserAuthentication|FrontendBackendUserAuthentication
-     */
-    protected function getBackendUser(): BackendUserAuthentication
-    {
-        return $GLOBALS['BE_USER'];
-    }
-
-    /**
-     * Returns LanguageService
-     *
-     * @return LanguageService
-     */
-    protected function getLanguageService(): LanguageService
-    {
-        return $GLOBALS['LANG'];
-    }
-
-    /**
-     * Returns true if TSConfig admPanel.enable is set for this module (or all modules)
-     *
-     * @return bool
-     */
-    protected function isEnabledViaTsConfig(): bool
-    {
-        $result = false;
-        $identifier = $this->getIdentifier();
-        if (!empty($this->mainConfiguration['enable.']['all'])) {
-            $result = true;
-        } elseif (!empty($this->mainConfiguration['enable.'][$identifier])) {
-            $result = true;
-        }
-        return $result;
-    }
-
-    /**
-     * @return array
-     */
-    public function getJavaScriptFiles(): array
-    {
-        return [];
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function getCssFiles(): array
-    {
-        return [];
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function getShortInfo(): string
-    {
-        return '';
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function setSubModules(array $subModules): void
-    {
-        $this->subModules = $subModules;
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function getSubModules(): array
-    {
-        return $this->subModules;
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function getHasSubmoduleSettings(): bool
-    {
-        foreach ($this->subModules as $subModule) {
-            if (!empty($subModule->getSettings())) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/typo3/sysext/adminpanel/Classes/Modules/AbstractSubModule.php b/typo3/sysext/adminpanel/Classes/Modules/AbstractSubModule.php
deleted file mode 100644 (file)
index be28541..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-declare(strict_types = 1);
-
-namespace TYPO3\CMS\Adminpanel\Modules;
-
-/*
- * 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!
- */
-
-use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Core\Localization\LanguageService;
-
-/**
- * Abstract SubModule - Base class for sub modules in the admin panel
- *
- * Extend this class when writing own sub modules
- */
-abstract class AbstractSubModule implements AdminPanelSubModuleInterface
-{
-    /**
-     * @inheritdoc
-     */
-    public function getSettings(): string
-    {
-        return '';
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function initializeModule(ServerRequestInterface $request): void
-    {
-    }
-
-    /**
-     * @inheritdoc
-     */
-    public function onSubmit(array $configurationToSave, ServerRequestInterface $request): void
-    {
-    }
-
-    /**
-     * Returns LanguageService
-     *
-     * @return LanguageService
-     */
-    protected function getLanguageService(): LanguageService
-    {
-        return $GLOBALS['LANG'];
-    }
-}
diff --git a/typo3/sysext/adminpanel/Classes/Modules/AdminPanelModuleInterface.php b/typo3/sysext/adminpanel/Classes/Modules/AdminPanelModuleInterface.php
deleted file mode 100644 (file)
index fee19e0..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-<?php
-declare(strict_types = 1);
-
-namespace TYPO3\CMS\Adminpanel\Modules;
-
-/*
- * 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!
- */
-
-use Psr\Http\Message\ServerRequestInterface;
-
-/**
- * Interface for admin panel modules registered via EXTCONF
- */
-interface AdminPanelModuleInterface
-{
-    /**
-     * Identifier for this module,
-     * for example "preview" or "cache"
-     *
-     * @return string
-     */
-    public function getIdentifier(): string;
-
-    /**
-     * Module label
-     *
-     * @return string
-     */
-    public function getLabel(): string;
-
-    /**
-     * Module Icon identifier - needs to be registered in iconRegistry
-     *
-     * @return string
-     */
-    public function getIconIdentifier(): string;
-
-    /**
-     * Displayed directly in the bar if module has content
-     *
-     * @return string
-     */
-    public function getShortInfo(): string;
-
-    /**
-     * @return string
-     */
-    public function getSettings(): string;
-
-    /**
-     * Initialize the module - runs early in a TYPO3 request
-     *
-     * @param ServerRequestInterface $request
-     */
-    public function initializeModule(ServerRequestInterface $request): void;
-
-    /**
-     * Module is enabled
-     * -> should be initialized
-     * A module may be enabled but not shown
-     * -> only the initializeModule() method
-     * will be called
-     *
-     * @return bool
-     */
-    public function isEnabled(): bool;
-
-    /**
-     * Executed on saving / submit of the configuration form
-     * Can be used to react to changed settings
-     * (for example: clearing a specific cache)
-     *
-     * @param array $configurationToSave
-     * @param ServerRequestInterface $request
-     */
-    public function onSubmit(array $configurationToSave, ServerRequestInterface $request): void;
-
-    /**
-     * Returns a string array with javascript files that will be rendered after the module
-     *
-     * @return array
-     */
-    public function getJavaScriptFiles(): array;
-
-    /**
-     * Returns a string array with css files that will be rendered after the module
-     *
-     * @return array
-     */
-    public function getCssFiles(): array;
-
-    /**
-     * Set SubModules for current module
-     *
-     * @param AdminPanelSubModuleInterface[] $subModules
-     */
-    public function setSubModules(array $subModules): void;
-
-    /**
-     * Get SubModules for current module
-     *
-     * @return AdminPanelSubModuleInterface[]
-     */
-    public function getSubModules(): array;
-
-    /**
-     * Returns true if submodule has own settings
-     *
-     * @return bool
-     */
-    public function getHasSubmoduleSettings(): bool;
-}
diff --git a/typo3/sysext/adminpanel/Classes/Modules/AdminPanelSubModuleInterface.php b/typo3/sysext/adminpanel/Classes/Modules/AdminPanelSubModuleInterface.php
deleted file mode 100644 (file)
index ba2aa5e..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-declare(strict_types = 1);
-
-namespace TYPO3\CMS\Adminpanel\Modules;
-
-/*
- * 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!
- */
-
-use Psr\Http\Message\ServerRequestInterface;
-
-/**
- * Interface for admin panel sub modules registered via EXTCONF
- */
-interface AdminPanelSubModuleInterface
-{
-
-    /**
-     * Initialize the module - runs early in a TYPO3 request
-     *
-     * @param ServerRequestInterface $request
-     */
-    public function initializeModule(ServerRequestInterface $request): void;
-
-    /**
-     * Identifier for this Sub-module,
-     * for example "preview" or "cache"
-     *
-     * @return string
-     */
-    public function getIdentifier(): string;
-
-    /**
-     * Sub-Module label
-     *
-     * @return string
-     */
-    public function getLabel(): string;
-
-    /**
-     * Sub-Module content as rendered HTML
-     *
-     * @return string
-     */
-    public function getContent(): string;
-
-    /**
-     * Settings as HTML form elements (without wrapping form tag or save button)
-     *
-     * @return string
-     */
-    public function getSettings(): string;
-
-    /**
-     * Executed on saving / submit of the configuration form
-     * Can be used to react to changed settings
-     * (for example: clearing a specific cache)
-     *
-     * @param array $configurationToSave
-     * @param ServerRequestInterface $request
-     */
-    public function onSubmit(array $configurationToSave, ServerRequestInterface $request): void;
-}
index dd93cef..5000d37 100644 (file)
@@ -17,12 +17,15 @@ namespace TYPO3\CMS\Adminpanel\Modules;
  */
 
 use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\InitializableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\PageSettingsProviderInterface;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
-class CacheModule extends AbstractModule
+class CacheModule extends AbstractModule implements PageSettingsProviderInterface, InitializableInterface
 {
     /**
      * @return string
@@ -35,7 +38,7 @@ class CacheModule extends AbstractModule
     /**
      * @return string
      */
-    public function getSettings(): string
+    public function getPageSettings(): string
     {
         $view = GeneralUtility::makeInstance(StandaloneView::class);
         $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/Settings/Cache.html';
index bc864ce..99acb23 100644 (file)
@@ -18,7 +18,12 @@ namespace TYPO3\CMS\Adminpanel\Modules\Debug;
 
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Adminpanel\Log\InMemoryLogWriter;
-use TYPO3\CMS\Adminpanel\Modules\AbstractSubModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\ContentProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\InitializableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleSettingsProviderInterface;
 use TYPO3\CMS\Adminpanel\Service\ConfigurationService;
 use TYPO3\CMS\Core\Log\LogLevel;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -26,8 +31,10 @@ use TYPO3\CMS\Fluid\View\StandaloneView;
 
 /**
  * Log Sub Module of the AdminPanel
+ *
+ * @internal
  */
-class Log extends AbstractSubModule
+class Log extends AbstractSubModule implements DataProviderInterface, ContentProviderInterface, ModuleSettingsProviderInterface, InitializableInterface
 {
     protected $logLevel = LogLevel::INFO;
 
@@ -62,7 +69,39 @@ class Log extends AbstractSubModule
     }
 
     /**
-     * @return string
+     * @inheritdoc
+     */
+    public function getDataToStore(ServerRequestInterface $request): ModuleData
+    {
+        $levels = [];
+        for ($i = 1; $i <= LogLevel::DEBUG; $i++) {
+            $levels[] = [
+                'level' => $i,
+                'levelName' => LogLevel::getName($i),
+            ];
+        }
+
+        $log = InMemoryLogWriter::$log;
+
+        $logArray = [];
+        /** @var \TYPO3\CMS\Core\Log\LogRecord $logRecord */
+        foreach ($log as $logRecord) {
+            $entry = $logRecord->toArray();
+            // store only necessary info
+            unset($entry['data']);
+            $logArray[] = $entry;
+        }
+        return new ModuleData(
+            [
+                'levels' => $levels,
+                'startLevel' => (int)$this->getConfigOption('startLevel'),
+                'log' => $logArray,
+            ]
+        );
+    }
+
+    /**
+     * @inheritdoc
      */
     public function getSettings(): string
     {
@@ -75,7 +114,7 @@ class Log extends AbstractSubModule
         for ($i = 1; $i <= LogLevel::DEBUG; $i++) {
             $levels[] = [
                 'level' => $i,
-                'levelName' => LogLevel::getName($i)
+                'levelName' => LogLevel::getName($i),
             ];
         }
         $view->assignMultiple(
@@ -93,56 +132,49 @@ class Log extends AbstractSubModule
     /**
      * Sub-Module content as rendered HTML
      *
+     * @param \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData $data
      * @return string
      */
-    public function getContent(): string
+    public function getContent(ModuleData $data): string
     {
+        $this->logLevel = $this->getConfigOption('startLevel');
         $view = GeneralUtility::makeInstance(StandaloneView::class);
         $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/Debug/Log.html';
         $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
         $view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
-
+        $sortedLog = [];
         // settings for this module
         $groupByComponent = $this->getConfigOption('groupByComponent');
         $groupByLevel = $this->getConfigOption('groupByLevel');
 
-        $log = InMemoryLogWriter::$log;
-
-        $sortedLog = [];
-        /** @var \TYPO3\CMS\Core\Log\LogRecord $logRecord */
-        foreach ($log as $logRecord) {
-            if ($logRecord->getLevel() > $this->logLevel) {
+        foreach ($data['log'] as $logRecord) {
+            if ($logRecord['level'] > $this->logLevel) {
                 continue;
             }
             if ($groupByComponent && $groupByLevel) {
-                $sortedLog[$logRecord->getComponent()][LogLevel::getName($logRecord->getLevel())][] = $logRecord;
+                $sortedLog[$logRecord['component']][LogLevel::getName($logRecord['level'])][] = $logRecord;
             } elseif ($groupByComponent) {
-                $sortedLog[$logRecord->getComponent()][] = $logRecord;
+                $sortedLog[$logRecord['component']][] = $logRecord;
             } elseif ($groupByLevel) {
-                $sortedLog[LogLevel::getName($logRecord->getLevel())][] = $logRecord;
+                $sortedLog[$logRecord['level']][] = $logRecord;
             } else {
                 $sortedLog[] = $logRecord;
             }
         }
-        $view->assignMultiple(
-            [
-                'log' => $sortedLog,
-                'groupByComponent' => $groupByComponent,
-                'groupByLevel' => $groupByLevel,
-            ]
-        );
+        $data['log'] = $sortedLog;
+        $data['groupByComponent'] = $groupByComponent;
+        $data['groupByLevel'] = $groupByLevel;
+        $view->assignMultiple($data->getArrayCopy());
 
         return $view->render();
     }
 
+    /**
+     * @inheritdoc
+     */
     public function initializeModule(ServerRequestInterface $request): void
     {
-        $this->logLevel = $this->getConfigOption('startLevel') ?: LogLevel::INFO;
-
-        // debug is set in ext_localconf as we do not have any config there yet but don't want to miss
-        // potentially relevant log entries
-        unset($GLOBALS['TYPO3_CONF_VARS']['LOG']['writerConfiguration'][LogLevel::DEBUG][InMemoryLogWriter::class]);
-        $GLOBALS['TYPO3_CONF_VARS']['LOG']['writerConfiguration'][$this->logLevel][InMemoryLogWriter::class] = [];
+        $this->logLevel = $this->getConfigOption('startLevel');
 
         // set inMemoryLogWriter recursively for all configured namespaces/areas so we don't lose log entries
         $configWithInMemoryWriter = $this->setLoggingConfigRecursive($GLOBALS['TYPO3_CONF_VARS']['LOG'] ?? []);
@@ -158,7 +190,7 @@ class Log extends AbstractSubModule
         foreach ($logConfig as $key => $value) {
             if ($key === 'writerConfiguration') {
                 $logConfig[$key] = $value;
-                $logConfig[$key][$this->logLevel][InMemoryLogWriter::class] = [];
+                $logConfig[$key][LogLevel::DEBUG][InMemoryLogWriter::class] = [];
             } elseif (is_array($value)) {
                 $logConfig[$key] = $this->setLoggingConfigRecursive($value);
             }
index 7a0b360..8685fa4 100644 (file)
@@ -16,16 +16,22 @@ namespace TYPO3\CMS\Adminpanel\Modules\Debug;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Adminpanel\Log\DoctrineSqlLogger;
-use TYPO3\CMS\Adminpanel\Modules\AbstractSubModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\ContentProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 
 /**
  * Admin Panel Query Information module for showing SQL Queries
+ *
+ * @internal
  */
-class QueryInformation extends AbstractSubModule
+class QueryInformation extends AbstractSubModule implements DataProviderInterface, ContentProviderInterface
 {
     /**
      * Identifier for this Sub-module,
@@ -51,28 +57,36 @@ class QueryInformation extends AbstractSubModule
     }
 
     /**
-     * @return string Returns content of admin panel
-     * @throws \UnexpectedValueException
-     * @throws \InvalidArgumentException
+     * @param ServerRequestInterface $request
+     * @return \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData
      * @throws \Doctrine\DBAL\DBALException
      */
-    public function getContent(): string
+    public function getDataToStore(ServerRequestInterface $request): ModuleData
     {
-        $view = new StandaloneView();
-        $view->setTemplatePathAndFilename(
-            'typo3/sysext/adminpanel/Resources/Private/Templates/Modules/Debug/QueryInformation.html'
-        );
         $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
         $connection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
         $logger = $connection->getConfiguration()->getSQLLogger();
-        $this->getLanguageService()->includeLLFile('EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf');
+        $data = [];
         if ($logger instanceof DoctrineSqlLogger) {
             $queries = $logger->getQueries();
-            $this->queryCount = \count($queries);
-            $groupedQueries = $this->groupQueries($queries);
-            $totalTime = array_sum(array_column($queries, 'executionMS')) * 1000;
-            $view->assign('queries', $groupedQueries ?? [])->assign('totalTime', $totalTime);
+            $data['queries'] = $this->groupQueries($queries) ?? [];
+            $data['totalTime'] = array_sum(array_column($queries, 'executionMS')) * 1000;
         }
+        return new ModuleData($data);
+    }
+
+    /**
+     * @param \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData $data
+     * @return string Returns content of admin panel
+     */
+    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();
     }
 
index 02efabc..effee2c 100644 (file)
@@ -17,12 +17,14 @@ namespace TYPO3\CMS\Adminpanel\Modules;
  */
 
 use TYPO3\CMS\Adminpanel\Log\InMemoryLogWriter;
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\ShortInfoProviderInterface;
 use TYPO3\CMS\Core\Log\LogRecord;
 
 /**
  * Debug Module of the AdminPanel
  */
-class DebugModule extends AbstractModule
+class DebugModule extends AbstractModule implements ShortInfoProviderInterface
 {
 
     /**
index fee1d00..08cfe93 100644 (file)
@@ -17,6 +17,10 @@ namespace TYPO3\CMS\Adminpanel\Modules;
  */
 
 use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\InitializableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\PageSettingsProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ResourceProviderInterface;
 use TYPO3\CMS\Adminpanel\Service\EditToolbarService;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
@@ -27,14 +31,14 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 /**
  * Admin Panel Edit Module
  */
-class EditModule extends AbstractModule
+class EditModule extends AbstractModule implements PageSettingsProviderInterface, InitializableInterface, ResourceProviderInterface
 {
     /**
      * Creates the content for the "edit" section ("module") of the Admin Panel
      *
      * @return string HTML content for the section. Consists of a string with table-rows with four columns.
      */
-    public function getSettings(): string
+    public function getPageSettings(): string
     {
         $editToolbarService = GeneralUtility::makeInstance(EditToolbarService::class);
         $toolbar = $editToolbarService->createToolbar();
@@ -136,4 +140,14 @@ class EditModule extends AbstractModule
     {
         return ['EXT:adminpanel/Resources/Public/JavaScript/Modules/Edit.js'];
     }
+
+    /**
+     * Returns a string array with css files that will be rendered after the module
+     *
+     * @return array
+     */
+    public function getCssFiles(): array
+    {
+        return [];
+    }
 }
index 99c76a0..696a9d7 100644 (file)
@@ -16,7 +16,11 @@ namespace TYPO3\CMS\Adminpanel\Modules\Info;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Adminpanel\Modules\AbstractSubModule;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\ContentProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
@@ -27,26 +31,20 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 /**
  * General information module displaying info about the current
  * request
+ *
+ * @internal
  */
-class GeneralInformation extends AbstractSubModule
+class GeneralInformation extends AbstractSubModule implements DataProviderInterface, ContentProviderInterface
 {
     /**
-     * Creates the content for the "info" section ("module") of the Admin Panel
-     *
-     * @return string HTML content for the section. Consists of a string with table-rows with four columns.
-     * @see display()
+     * @inheritdoc
      */
-    public function getContent(): string
+    public function getDataToStore(ServerRequestInterface $request): ModuleData
     {
         /** @var UserAspect $frontendUserAspect */
         $frontendUserAspect = GeneralUtility::makeInstance(Context::class)->getAspect('frontend.user');
-        $view = GeneralUtility::makeInstance(StandaloneView::class);
-        $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/Info/General.html';
-        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
-        $view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
         $tsfe = $this->getTypoScriptFrontendController();
-
-        $view->assignMultiple(
+        return new ModuleData(
             [
                 'post' => $_POST,
                 'get' => $_GET,
@@ -61,13 +59,30 @@ class GeneralInformation extends AbstractSubModule
                     'totalParsetime' => $this->getTimeTracker()->getParseTime(),
                     'feUser' => [
                         'uid' => $frontendUserAspect->get('id') ?: 0,
-                        'username' => $frontendUserAspect->get('username') ?: ''
+                        'username' => $frontendUserAspect->get('username') ?: '',
                     ],
                     'imagesOnPage' => $this->collectImagesOnPage(),
-                    'documentSize' => $this->collectDocumentSize()
-                ]
+                    'documentSize' => $this->collectDocumentSize(),
+                ],
             ]
         );
+    }
+
+    /**
+     * Creates the content for the "info" section ("module") of the Admin Panel
+     *
+     * @param \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData $data
+     * @return string HTML content for the section. Consists of a string with table-rows with four columns.
+     * @see display()
+     */
+    public function getContent(ModuleData $data): string
+    {
+        $view = GeneralUtility::makeInstance(StandaloneView::class);
+        $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/Info/General.html';
+        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
+        $view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
+
+        $view->assignMultiple($data->getArrayCopy());
 
         return $view->render();
     }
@@ -105,7 +120,7 @@ class GeneralInformation extends AbstractSubModule
             'files' => [],
             'total' => 0,
             'totalSize' => 0,
-            'totalSizeHuman' => GeneralUtility::formatSize(0)
+            'totalSizeHuman' => GeneralUtility::formatSize(0),
         ];
 
         if ($this->isNoCacheEnabled() === false) {
@@ -120,7 +135,7 @@ class GeneralInformation extends AbstractSubModule
                 $imagesOnPage['files'][] = [
                     'name' => $file,
                     'size' => $fileSize,
-                    'sizeHuman' => GeneralUtility::formatSize($fileSize)
+                    'sizeHuman' => GeneralUtility::formatSize($fileSize),
                 ];
                 $totalImageSize += $fileSize;
                 $count++;
index a8b3af7..6a6e444 100644 (file)
@@ -16,14 +16,20 @@ namespace TYPO3\CMS\Adminpanel\Modules\Info;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Adminpanel\Modules\AbstractSubModule;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\ContentProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 
 /**
  * PhpInformation admin panel sub module
+ *
+ * @internal
  */
-class PhpInformation extends AbstractSubModule
+class PhpInformation extends AbstractSubModule implements DataProviderInterface, ContentProviderInterface
 {
     /**
      * @inheritdoc
@@ -46,14 +52,9 @@ class PhpInformation extends AbstractSubModule
     /**
      * @inheritdoc
      */
-    public function getContent(): string
+    public function getDataToStore(ServerRequestInterface $request): ModuleData
     {
-        $view = GeneralUtility::makeInstance(StandaloneView::class);
-        $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/Info/PhpInfo.html';
-        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
-        $view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
-
-        $view->assignMultiple(
+        return new ModuleData(
             [
                 'general' => [
                     'PHP_VERSION' => PHP_VERSION,
@@ -65,6 +66,19 @@ class PhpInformation extends AbstractSubModule
                 'constants' => get_defined_constants(true),
             ]
         );
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getContent(ModuleData $data): string
+    {
+        $view = GeneralUtility::makeInstance(StandaloneView::class);
+        $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/Info/PhpInfo.html';
+        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
+        $view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
+
+        $view->assignMultiple($data->getArrayCopy());
 
         return $view->render();
     }
index cd2a46f..9888bcb 100644 (file)
@@ -16,14 +16,20 @@ namespace TYPO3\CMS\Adminpanel\Modules\Info;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Adminpanel\Modules\AbstractSubModule;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\ContentProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 
 /**
  * RequestInformation submodule of the admin panel
+ *
+ * @internal
  */
-class RequestInformation extends AbstractSubModule
+class RequestInformation extends AbstractSubModule implements DataProviderInterface, ContentProviderInterface
 {
     /**
      * @inheritdoc
@@ -46,14 +52,9 @@ class RequestInformation extends AbstractSubModule
     /**
      * @inheritdoc
      */
-    public function getContent(): string
+    public function getDataToStore(ServerRequestInterface $request): ModuleData
     {
-        $view = GeneralUtility::makeInstance(StandaloneView::class);
-        $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/Info/RequestInformation.html';
-        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
-        $view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
-
-        $view->assignMultiple(
+        return new ModuleData(
             [
                 'post' => $_POST,
                 'get' => $_GET,
@@ -62,6 +63,19 @@ class RequestInformation extends AbstractSubModule
                 'server' => $_SERVER,
             ]
         );
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getContent(ModuleData $data): string
+    {
+        $view = GeneralUtility::makeInstance(StandaloneView::class);
+        $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/Info/RequestInformation.html';
+        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
+        $view->setPartialRootPaths(['EXT:adminpanel/Resources/Private/Partials']);
+
+        $view->assignMultiple($data->getArrayCopy());
 
         return $view->render();
     }
index eb833b8..78b706a 100644 (file)
@@ -16,13 +16,15 @@ namespace TYPO3\CMS\Adminpanel\Modules;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\ShortInfoProviderInterface;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Admin Panel Info Module
  */
-class InfoModule extends AbstractModule
+class InfoModule extends AbstractModule implements ShortInfoProviderInterface
 {
     /**
      * @inheritdoc
index 31448b1..44d77ee 100644 (file)
@@ -17,6 +17,11 @@ namespace TYPO3\CMS\Adminpanel\Modules;
  */
 
 use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\InitializableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\OnSubmitActorInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\PageSettingsProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ResourceProviderInterface;
 use TYPO3\CMS\Adminpanel\Repositories\FrontendGroupsRepository;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Context\Context;
@@ -31,7 +36,7 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 /**
  * Admin Panel Preview Module
  */
-class PreviewModule extends AbstractModule
+class PreviewModule extends AbstractModule implements InitializableInterface, PageSettingsProviderInterface, OnSubmitActorInterface, ResourceProviderInterface
 {
     /**
      * module configuration, set on initialize
@@ -89,7 +94,7 @@ class PreviewModule extends AbstractModule
     /**
      * @inheritdoc
      */
-    public function getSettings(): string
+    public function getPageSettings(): string
     {
         $view = GeneralUtility::makeInstance(StandaloneView::class);
         $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/Settings/Preview.html';
@@ -117,7 +122,7 @@ class PreviewModule extends AbstractModule
     }
 
     /**
-     * Clear page cache if fluid debug output is enabled
+     * Clear page cache if fluid debug output setting is changed
      *
      * @param array $input
      * @param ServerRequestInterface $request
@@ -233,4 +238,14 @@ class PreviewModule extends AbstractModule
         }
         return $simTime ?? null;
     }
+
+    /**
+     * Returns a string array with css files that will be rendered after the module
+     *
+     * @return array
+     */
+    public function getCssFiles(): array
+    {
+        return [];
+    }
 }
index b1af252..bb240e0 100644 (file)
@@ -17,7 +17,11 @@ namespace TYPO3\CMS\Adminpanel\Modules\TsDebug;
  */
 
 use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Adminpanel\Modules\AbstractSubModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\ContentProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\InitializableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleSettingsProviderInterface;
 use TYPO3\CMS\Adminpanel\Service\ConfigurationService;
 use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
@@ -26,7 +30,12 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
-class TypoScriptWaterfall extends AbstractSubModule
+/**
+ * Class TypoScriptWaterfall
+ *
+ * @internal
+ */
+class TypoScriptWaterfall extends AbstractSubModule implements InitializableInterface, ContentProviderInterface, ModuleSettingsProviderInterface
 {
     /**
      * @var ConfigurationService
@@ -74,9 +83,10 @@ class TypoScriptWaterfall extends AbstractSubModule
     /**
      * Creates the content for the "tsdebug" section ("module") of the Admin Panel
      *
+     * @param ModuleData $data
      * @return string HTML
      */
-    public function getContent(): string
+    public function getContent(ModuleData $data): string
     {
         $view = GeneralUtility::makeInstance(StandaloneView::class);
         $templateNameAndPath = 'EXT:adminpanel/Resources/Private/Templates/Modules/TsDebug/TypoScript.html';
index 5990668..949615b 100644 (file)
@@ -16,13 +16,16 @@ namespace TYPO3\CMS\Adminpanel\Modules;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Adminpanel\ModuleApi\AbstractModule;
+use TYPO3\CMS\Adminpanel\ModuleApi\ResourceProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ShortInfoProviderInterface;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Admin Panel TypoScript Debug Module
  */
-class TsDebugModule extends AbstractModule
+class TsDebugModule extends AbstractModule implements ShortInfoProviderInterface, ResourceProviderInterface
 {
     /**
      * @inheritdoc
@@ -59,9 +62,12 @@ class TsDebugModule extends AbstractModule
         foreach ($this->getTimeTracker()->tsStackLog as $log) {
             $messageCount += count($log['message'] ?? []);
         }
-        return sprintf($this->getLanguageService()->sL(
-            'LLL:EXT:adminpanel/Resources/Private/Language/locallang_tsdebug.xlf:module.shortinfo'
-        ), $messageCount);
+        return sprintf(
+            $this->getLanguageService()->sL(
+                'LLL:EXT:adminpanel/Resources/Private/Language/locallang_tsdebug.xlf:module.shortinfo'
+            ),
+            $messageCount
+        );
     }
 
     /**
@@ -73,10 +79,18 @@ class TsDebugModule extends AbstractModule
     }
 
     /**
-     * @return array
+     * @inheritdoc
      */
     public function getJavaScriptFiles(): array
     {
         return ['EXT:adminpanel/Resources/Public/JavaScript/Modules/TsDebug.js'];
     }
+
+    /**
+     * @inheritdoc
+     */
+    public function getCssFiles(): array
+    {
+        return [];
+    }
 }
index 4434ab1..ef724e4 100644 (file)
@@ -17,7 +17,9 @@ namespace TYPO3\CMS\Adminpanel\Service;
  */
 
 use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Adminpanel\Modules\AdminPanelModuleInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ConfigurableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\OnSubmitActorInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\SubmoduleProviderInterface;
 use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\SingletonInterface;
@@ -77,24 +79,15 @@ class ConfigurationService implements SingletonInterface
      * triggers onSubmit method of modules to enable each module
      * to enhance the save action
      *
-     * @param AdminPanelModuleInterface[] $modules
+     * @param \TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface[] $modules
      * @param ServerRequestInterface $request
-     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
      */
     public function saveConfiguration(array $modules, ServerRequestInterface $request): void
     {
         $configurationToSave = $request->getParsedBody()['TSFE_ADMIN_PANEL'] ?? [];
         $beUser = $this->getBackendUser();
+        $this->triggerOnSubmitActors($modules, $request, $configurationToSave);
 
-        // Trigger onSubmit
-        foreach ($modules as $module) {
-            if ($module->isEnabled()) {
-                $module->onSubmit($configurationToSave, $request);
-                foreach ($module->getSubModules() as $subModule) {
-                    $subModule->onSubmit($configurationToSave, $request);
-                }
-            }
-        }
         // Settings
         $beUser->uc['AdminPanel'] = array_merge(
             !is_array($beUser->uc['AdminPanel']) ? [] : $beUser->uc['AdminPanel'],
@@ -114,4 +107,30 @@ class ConfigurationService implements SingletonInterface
     {
         return $GLOBALS['BE_USER'];
     }
+
+    /**
+     * @param array $modules
+     * @param \Psr\Http\Message\ServerRequestInterface $request
+     * @param $configurationToSave
+     */
+    protected function triggerOnSubmitActors(
+        array $modules,
+        ServerRequestInterface $request,
+        $configurationToSave
+    ): void {
+        foreach ($modules as $module) {
+            if (
+                $module instanceof OnSubmitActorInterface
+                && (
+                    ($module instanceof ConfigurableInterface && $module->isEnabled())
+                    || !($module instanceof ConfigurableInterface)
+                )
+             ) {
+                $module->onSubmit($configurationToSave, $request);
+            }
+            if ($module instanceof SubmoduleProviderInterface) {
+                $this->triggerOnSubmitActors($module->getSubModules(), $request, $configurationToSave);
+            }
+        }
+    }
 }
index 07f2897..f1a72a0 100644 (file)
@@ -17,8 +17,9 @@ namespace TYPO3\CMS\Adminpanel\Service;
  */
 
 use TYPO3\CMS\Adminpanel\Exceptions\InvalidConfigurationException;
-use TYPO3\CMS\Adminpanel\Modules\AdminPanelModuleInterface;
-use TYPO3\CMS\Adminpanel\Modules\AdminPanelSubModuleInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ConfigurableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\SubmoduleProviderInterface;
 use TYPO3\CMS\Core\Service\DependencyOrderingService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -34,11 +35,10 @@ class ModuleLoader
      * Validates, sorts and initiates the registered modules
      *
      * @param array $modules
-     * @param string $type
-     * @return AdminPanelModuleInterface[]|AdminPanelSubModuleInterface[]
+     * @return \TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface[]
      * @throws \RuntimeException
      */
-    public function validateSortAndInitializeModules(array $modules, string $type = 'main'): array
+    public function validateSortAndInitializeModules(array $modules): array
     {
         if (empty($modules)) {
             return [];
@@ -55,7 +55,7 @@ class ModuleLoader
                 !class_exists($configuration['module']) ||
                 !is_subclass_of(
                     $configuration['module'],
-                    ($type === 'main' ? AdminPanelModuleInterface::class : AdminPanelSubModuleInterface::class),
+                    ModuleInterface::class,
                     true
                 )
             ) {
@@ -63,7 +63,7 @@ class ModuleLoader
                     'The module "' .
                     $identifier .
                     '" defines an invalid module class. Ensure the class exists and implements the "' .
-                    AdminPanelModuleInterface::class .
+                    ModuleInterface::class .
                     '".',
                     1519490112
                 );
@@ -75,23 +75,22 @@ class ModuleLoader
         );
 
         $moduleInstances = [];
-        foreach ($orderedModules as $module) {
-            $module = GeneralUtility::makeInstance($module['module']);
-            if ($module instanceof AdminPanelSubModuleInterface || ($module instanceof AdminPanelModuleInterface && $module->isEnabled())) {
-                $moduleInstances[] = $module;
+        foreach ($orderedModules as $moduleConfiguration) {
+            $module = GeneralUtility::makeInstance($moduleConfiguration['module']);
+            if (
+                $module instanceof ModuleInterface
+                && (
+                    ($module instanceof ConfigurableInterface && $module->isEnabled())
+                    || !($module instanceof ConfigurableInterface)
+                )
+            ) {
+                $moduleInstances[$module->getIdentifier()] = $module;
+            }
+            if ($module instanceof SubmoduleProviderInterface) {
+                $subModuleInstances = $this->validateSortAndInitializeModules($moduleConfiguration['submodules'] ?? []);
+                $module->setSubModules($subModuleInstances);
             }
         }
         return $moduleInstances;
     }
-
-    /**
-     * Validates, sorts and initializes sub-modules
-     *
-     * @param array $modules
-     * @return AdminPanelSubModuleInterface[]
-     */
-    public function validateSortAndInitializeSubModules(array $modules): array
-    {
-        return $this->validateSortAndInitializeModules($modules, 'sub');
-    }
 }
diff --git a/typo3/sysext/adminpanel/Classes/Utility/ResourceUtility.php b/typo3/sysext/adminpanel/Classes/Utility/ResourceUtility.php
new file mode 100644 (file)
index 0000000..46f4002
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\Utility;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Adminpanel\ModuleApi\ResourceProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\SubmoduleProviderInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
+
+class ResourceUtility
+{
+    /**
+     * Get additional resources (css, js) from modules and merge it to
+     * one array - returns an array of full html tags
+     *
+     * @param \TYPO3\CMS\Adminpanel\ModuleApi\ResourceProviderInterface[] $modules
+     * @return array
+     */
+    public static function getAdditionalResourcesForModules(array $modules): array
+    {
+        $result = [
+            'js' => '',
+            'css' => '',
+        ];
+        foreach ($modules as $module) {
+            if ($module instanceof ResourceProviderInterface) {
+                foreach ($module->getJavaScriptFiles() as $file) {
+                    $result['js'] .= static::getJsTag($file);
+                }
+                foreach ($module->getCssFiles() as $file) {
+                    $result['css'] .= static::getCssTag($file);
+                }
+            }
+            if ($module instanceof SubmoduleProviderInterface) {
+                $subResult = self::getAdditionalResourcesForModules($module->getSubModules());
+                $result['js'] .= $subResult['js'];
+                $result['css'] .= $subResult['css'];
+            }
+        }
+        return $result;
+    }
+
+    public static function getAdditionalResourcesForModule(ResourceProviderInterface $module): array
+    {
+        $result = [
+            'js' => '',
+            'css' => '',
+        ];
+        foreach ($module->getJavaScriptFiles() as $file) {
+            $result['js'] .= static::getJsTag($file);
+        }
+        foreach ($module->getCssFiles() as $file) {
+            $result['css'] .= static::getCssTag($file);
+        }
+        return $result;
+    }
+
+    /**
+     * Returns a link tag with the admin panel stylesheet
+     * defined using TBE_STYLES
+     *
+     * @return string
+     */
+    protected static function getAdminPanelStylesheet(): string
+    {
+        $result = '';
+        if (!empty($GLOBALS['TBE_STYLES']['stylesheets']['admPanel'])) {
+            $stylesheet = GeneralUtility::locationHeaderUrl($GLOBALS['TBE_STYLES']['stylesheets']['admPanel']);
+            $result = '<link rel="stylesheet" type="text/css" href="' .
+                      htmlspecialchars($stylesheet, ENT_QUOTES | ENT_HTML5) . '" />';
+        }
+        return $result;
+    }
+
+    /**
+     * Get a css tag for file - with absolute web path resolving
+     *
+     * @param string $cssFileLocation
+     * @return string
+     */
+    protected static function getCssTag(string $cssFileLocation): string
+    {
+        $css = '<link type="text/css" rel="stylesheet" href="' .
+               htmlspecialchars(
+                   PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($cssFileLocation)),
+                   ENT_QUOTES | ENT_HTML5
+               ) .
+               '" media="all" />';
+        return $css;
+    }
+
+    /**
+     * Get a script tag for JavaScript with absolute paths
+     *
+     * @param string $jsFileLocation
+     * @return string
+     */
+    protected static function getJsTag(string $jsFileLocation): string
+    {
+        $js = '<script type="text/javascript" src="' .
+              htmlspecialchars(
+                  PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($jsFileLocation)),
+                  ENT_QUOTES | ENT_HTML5
+              ) .
+              '"></script>';
+        return $js;
+    }
+
+    /**
+     * Return a string with tags for main admin panel resources
+     *
+     * @return array
+     */
+    public static function getResources(): array
+    {
+        $jsFileLocation = 'EXT:adminpanel/Resources/Public/JavaScript/AdminPanel.js';
+        $js = ResourceUtility::getJsTag($jsFileLocation);
+        $cssFileLocation = 'EXT:adminpanel/Resources/Public/Css/adminpanel.css';
+        $css = ResourceUtility::getCssTag($cssFileLocation);
+
+        return [
+            'css' => $css . ResourceUtility::getAdminPanelStylesheet(),
+            'js' => $js,
+        ];
+    }
+}
index 7cb3ba5..b818416 100644 (file)
@@ -30,7 +30,7 @@ class StateUtility
      *
      * @return bool
      */
-    public static function isActivated(): bool
+    public static function isActivatedForUser(): bool
     {
         $beUser = $GLOBALS['BE_USER'] ?? null;
         if ($beUser instanceof FrontendBackendUserAuthentication) {
@@ -56,4 +56,14 @@ class StateUtility
         $beUser = $GLOBALS['BE_USER'] ?? null;
         return (bool)($beUser->uc['AdminPanel']['display_top'] ?? false);
     }
+
+    public static function isActivatedInTypoScript(): bool
+    {
+        return (bool)($GLOBALS['TSFE']->config['config']['admPanel'] ?? false);
+    }
+
+    public static function isHiddenForUser(): bool
+    {
+        return (bool)($GLOBALS['BE_USER']->extAdminConfig['hide'] ?? false);
+    }
 }
index 6fb6158..2f016e6 100644 (file)
@@ -15,8 +15,11 @@ namespace TYPO3\CMS\Adminpanel\View;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Adminpanel\Modules\AdminPanelModuleInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ConfigurableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface;
 use TYPO3\CMS\Adminpanel\Service\EditToolbarService;
+use TYPO3\CMS\Adminpanel\Utility\StateUtility;
 use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -39,7 +42,7 @@ class AdminPanelView
     /**
      * Array of adminPanel modules
      *
-     * @var AdminPanelModuleInterface[]
+     * @var \TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface[]
      */
     protected $modules = [];
 
@@ -61,17 +64,6 @@ class AdminPanelView
     }
 
     /**
-     * Returns true if admin panel was activated
-     * (switched "on" via GUI)
-     *
-     * @return bool
-     */
-    protected function isAdminPanelActivated(): bool
-    {
-        return $this->getBackendUser()->uc['AdminPanel']['display_top'] ?? false;
-    }
-
-    /**
      * Returns LanguageService
      *
      * @return \TYPO3\CMS\Core\Localization\LanguageService
@@ -106,7 +98,7 @@ class AdminPanelView
     public function callDeprecatedHookObject(): string
     {
         $moduleContent = '';
-        if ($this->isAdminPanelActivated()) {
+        if (StateUtility::isOpen()) {
             foreach ($this->modules as $module) {
                 $moduleContent .= $this->getModule($module);
             }
@@ -144,14 +136,14 @@ class AdminPanelView
      * Render a single module with header panel
      *
      * @deprecated Since TYPO3 v9 - only used in deprecated hook call (which triggers the corresponding deprecation error)
-     * @param AdminPanelModuleInterface $module
+     * @param ModuleInterface $module
      * @return string
      */
-    protected function getModule(AdminPanelModuleInterface $module): string
+    protected function getModule(ModuleInterface $module): string
     {
         $output = [];
 
-        if ($module->isEnabled()) {
+        if ($module instanceof ConfigurableInterface && $module->isEnabled()) {
             $output[] = '<div class="typo3-adminPanel-section typo3-adminPanel-section-open">';
             $output[] = '  <div class="typo3-adminPanel-section-title">';
             $output[] = '    ' . $this->getSectionOpenerLink($module);
@@ -162,11 +154,13 @@ class AdminPanelView
             $output[] = '</div>';
         }
 
-        foreach ($module->getJavaScriptFiles() as $javaScriptFile) {
-            $output[] =
-                '<script src="' .
-                PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($javaScriptFile)) .
-                '"></script>';
+        if ($module instanceof DataProviderInterface) {
+            foreach ($module->getJavaScriptFiles() as $javaScriptFile) {
+                $output[] =
+                    '<script src="' .
+                    PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($javaScriptFile)) .
+                    '"></script>';
+            }
         }
 
         return implode('', $output);
@@ -180,10 +174,10 @@ class AdminPanelView
      * Wraps a string in a link which will open/close a certain part of the Admin Panel
      *
      * @deprecated Since TYPO3 v9 - only used in deprecated hook call (which triggers the corresponding deprecation error)
-     * @param AdminPanelModuleInterface $module
+     * @param ModuleInterface $module
      * @return string
      */
-    protected function getSectionOpenerLink(AdminPanelModuleInterface $module): string
+    protected function getSectionOpenerLink(ModuleInterface $module): string
     {
         $identifier = $module->getIdentifier();
         $onclick = 'document.TSFE_ADMIN_PANEL_FORM[' .
@@ -247,7 +241,12 @@ class AdminPanelView
      */
     public function getAdminPanelHeaderData()
     {
-        trigger_error('AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Implement AdminPanelModules via the new API (see AdminPanelModuleInterface).', E_USER_DEPRECATED);
+        trigger_error(
+            'AdminPanelView->' .
+            __METHOD__ .
+            ' will be removed in TYPO3 v10.0. Implement AdminPanelModules via the new API (see AdminPanelModuleInterface).',
+            E_USER_DEPRECATED
+        );
         $result = '';
         if (!empty($GLOBALS['TBE_STYLES']['stylesheets']['admPanel'])) {
             $stylesheet = GeneralUtility::locationHeaderUrl($GLOBALS['TBE_STYLES']['stylesheets']['admPanel']);
@@ -266,7 +265,12 @@ class AdminPanelView
      */
     public function isAdminModuleEnabled($key)
     {
-        trigger_error('AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Implement AdminPanelModules via the new API (see AdminPanelModuleInterface).', E_USER_DEPRECATED);
+        trigger_error(
+            'AdminPanelView->' .
+            __METHOD__ .
+            ' will be removed in TYPO3 v10.0. Implement AdminPanelModules via the new API (see AdminPanelModuleInterface).',
+            E_USER_DEPRECATED
+        );
         $result = false;
         // Returns TRUE if the module checked is "preview" and the forcePreview flag is set.
         if ($key === 'preview' && $this->ext_forcePreview) {
@@ -286,7 +290,12 @@ class AdminPanelView
      */
     public function saveConfigOptions()
     {
-        trigger_error('AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Implement AdminPanelModules via the new API (see AdminPanelModuleInterface).', E_USER_DEPRECATED);
+        trigger_error(
+            'AdminPanelView->' .
+            __METHOD__ .
+            ' will be removed in TYPO3 v10.0. Implement AdminPanelModules via the new API (see AdminPanelModuleInterface).',
+            E_USER_DEPRECATED
+        );
         $input = GeneralUtility::_GP('TSFE_ADMIN_PANEL');
         $beUser = $this->getBackendUser();
         if (is_array($input)) {
@@ -322,7 +331,12 @@ class AdminPanelView
      */
     public function extGetFeAdminValue($sectionName, $val = '')
     {
-        trigger_error('AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Implement AdminPanelModules via the new API (see AdminPanelModuleInterface).', E_USER_DEPRECATED);
+        trigger_error(
+            'AdminPanelView->' .
+            __METHOD__ .
+            ' will be removed in TYPO3 v10.0. Implement AdminPanelModules via the new API (see AdminPanelModuleInterface).',
+            E_USER_DEPRECATED
+        );
         trigger_error(
             'Deprecated since TYPO3 v9 - ',
             E_USER_DEPRECATED
@@ -370,7 +384,12 @@ class AdminPanelView
      */
     public function forcePreview()
     {
-        trigger_error('AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Use new AdminPanel Preview Module instead.', E_USER_DEPRECATED);
+        trigger_error(
+            'AdminPanelView->' .
+            __METHOD__ .
+            ' will be removed in TYPO3 v10.0. Use new AdminPanel Preview Module instead.',
+            E_USER_DEPRECATED
+        );
         $this->ext_forcePreview = true;
     }
 
@@ -383,7 +402,10 @@ class AdminPanelView
      */
     public function isAdminModuleOpen($key)
     {
-        trigger_error('AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Use new AdminPanel API instead.', E_USER_DEPRECATED);
+        trigger_error(
+            'AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Use new AdminPanel API instead.',
+            E_USER_DEPRECATED
+        );
         return $this->getBackendUser()->uc['AdminPanel']['display_top'] &&
                $this->getBackendUser()->uc['AdminPanel']['display_' . $key];
     }
@@ -403,7 +425,10 @@ class AdminPanelView
      */
     public function extGetItem($title, $content = '', $checkbox = '', $outerDivClass = null, $innerDivClass = null)
     {
-        trigger_error('AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Use new AdminPanel API instead.', E_USER_DEPRECATED);
+        trigger_error(
+            'AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Use new AdminPanel API instead.',
+            E_USER_DEPRECATED
+        );
         $title = $title ? '<label for="' . htmlspecialchars($title) . '">' . $this->extGetLL($title) . '</label>' : '';
         $out = '';
         $out .= (string)$outerDivClass ? '<div class="' . htmlspecialchars($outerDivClass) . '">' : '<div>';
@@ -424,7 +449,10 @@ class AdminPanelView
      */
     public function extGetHead($sectionSuffix)
     {
-        trigger_error('AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Use new AdminPanel API instead.', E_USER_DEPRECATED);
+        trigger_error(
+            'AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Use new AdminPanel API instead.',
+            E_USER_DEPRECATED
+        );
         return $this->linkSectionHeader($sectionSuffix, $this->extGetLL($sectionSuffix));
     }
 
@@ -440,7 +468,10 @@ class AdminPanelView
      */
     public function linkSectionHeader($sectionSuffix, $sectionTitle, $className = '')
     {
-        trigger_error('AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Use new AdminPanel API instead.', E_USER_DEPRECATED);
+        trigger_error(
+            'AdminPanelView->' . __METHOD__ . ' will be removed in TYPO3 v10.0. Use new AdminPanel API instead.',
+            E_USER_DEPRECATED
+        );
         $onclick = 'document.TSFE_ADMIN_PANEL_FORM[' .
                    GeneralUtility::quoteJSvalue('TSFE_ADMIN_PANEL[display_' . $sectionSuffix . ']') .
                    '].value=' .
diff --git a/typo3/sysext/adminpanel/Classes/ViewHelpers/SubModuleRenderViewHelper.php b/typo3/sysext/adminpanel/Classes/ViewHelpers/SubModuleRenderViewHelper.php
new file mode 100644 (file)
index 0000000..77096a3
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Adminpanel\ViewHelpers;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Adminpanel\ModuleApi\ContentProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleDataStorageCollection;
+use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
+use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
+use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
+
+/**
+ * Render submodule content
+ *
+ * @internal
+ */
+class SubModuleRenderViewHelper extends AbstractViewHelper
+{
+    use CompileWithRenderStatic;
+
+    /**
+     * First level cache of user names
+     *
+     * @var array
+     */
+    protected static $usernameRuntimeCache = [];
+
+    /**
+     * Initializes the arguments
+     */
+    public function initializeArguments(): void
+    {
+        $this->registerArgument(
+            'module',
+            ContentProviderInterface::class,
+            'SubModule instance to be rendered',
+            true
+        );
+        $this->registerArgument('data', ModuleDataStorageCollection::class, 'Data to be used for rendering', true);
+    }
+
+    /**
+     * Resolve user name from backend user id.
+     *
+     * @param array $arguments
+     * @param \Closure $renderChildrenClosure
+     * @param RenderingContextInterface $renderingContext
+     * @return string Username or an empty string if there is no user with that UID
+     */
+    public static function renderStatic(
+        array $arguments,
+        \Closure $renderChildrenClosure,
+        RenderingContextInterface $renderingContext
+    ): string {
+        $module = $arguments['module'];
+        /** @var \TYPO3\CMS\Adminpanel\ModuleApi\ModuleDataStorageCollection $data */
+        $data = $arguments['data'];
+        $moduleData = $data->contains($module) ? $data->offsetGet($module) : new ModuleData();
+        return $module->getContent($moduleData);
+    }
+}
index 24e22fb..0bf26c5 100644 (file)
@@ -12,6 +12,9 @@ return [
     'frontend' => [
         'typo3/cms-adminpanel/initiator' => [
             'target' => \TYPO3\CMS\Adminpanel\Middleware\AdminPanelInitiator::class,
+            'before' => [
+                'typo3/cms-frontend/prepare-tsfe-rendering'
+            ],
             'after' => [
                 'typo3/cms-frontend/tsfe',
                 'typo3/cms-frontend/authentication',
@@ -21,9 +24,26 @@ return [
         'typo3/cms-adminpanel/sql-logging' => [
             'target' => \TYPO3\CMS\Adminpanel\Middleware\SqlLogging::class,
             'after' => [
-                'typo3/cms-frontend/tsfe',
                 'typo3/cms-frontend/authentication',
                 'typo3/cms-frontend/backend-user-authentication',
+            ],
+            'before' => [
+                'typo3/cms-frontend/site'
+            ]
+        ],
+        'typo3/cms-adminpanel/data-persister' => [
+            'target' => \TYPO3\CMS\Adminpanel\Middleware\AdminPanelDataPersister::class,
+            'after' => [
+                'typo3/cms-adminpanel/initiator',
+                'typo3/cms-frontend/content-length-headers',
+                'typo3/cms-adminpanel/renderer'
+            ],
+        ],
+        'typo3/cms-adminpanel/renderer' => [
+            'target' => \TYPO3\CMS\Adminpanel\Middleware\AdminPanelRenderer::class,
+            'after' => [
+                'typo3/cms-adminpanel/initiator',
+                'typo3/cms-frontend/content-length-headers'
             ]
         ],
     ]
index 492dd21..dc23864 100644 (file)
@@ -1,6 +1,7 @@
 <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers" data-namespace-typo3-fluid="true">
 <!-- TYPO3 admin panel start -->
-<f:format.raw>{resources}</f:format.raw>
+<f:format.raw>{resources.css}</f:format.raw>
+<f:format.raw>{resources.js}</f:format.raw>
 <f:if condition="{adminPanelActive}">
     <f:format.raw>{moduleResources.css}</f:format.raw>
     <f:format.raw>{moduleResources.js}</f:format.raw>
index 9862f98..adf3e93 100644 (file)
@@ -1,4 +1,7 @@
-<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers" data-namespace-typo3-fluid="true">
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
+      xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
+      xmlns:adm="http://typo3.org/ns/TYPO3/CMS/Adminpanel/ViewHelpers"
+      data-namespace-typo3-fluid="true">
 <div class="typo3-adminPanel-module{f:if(condition: uid, then: ' typo3-adminPanel-module-{uid}')}">
     <div class="typo3-adminPanel-module-trigger" data-typo3-role="typo3-adminPanel-module-trigger">
         <f:if condition="{icon}"><span class="typo3-adminPanel-module-trigger-icon">{icon -> f:format.raw()}</span></f:if>
@@ -17,7 +20,7 @@
                         <a href="#" class="typo3-adminPanel-content-header-item{f:if(condition: iteration.isFirst, then: ' typo3-adminPanel-content-header-item-active')}" data-typo3-role="typo3-adminPanel-content-tab" data-typo3-tab-target="{sub.identifier}">{sub.label}</a>
                     </f:for>
                 </div>
-                <f:if condition="{module.hasSubmoduleSettings}">
+                <f:if condition="{module.submoduleSettings}">
                     <button type="button" class="typo3-adminPanel-content-header-item typo3-adminPanel-content-header-settings" data-typo3-role="typo3-adminPanel-content-settings">
                         <core:icon identifier="actions-system-extension-configure" alternativeMarkupIdentifier="inline"/>
                     </button>
@@ -26,7 +29,7 @@
                     <core:icon identifier="actions-close" alternativeMarkupIdentifier="inline"/>
                 </button>
             </div>
-            <f:if condition="{module.hasSubmoduleSettings}">
+            <f:if condition="{module.submoduleSettings}">
                 <div class="typo3-adminPanel-content-settings">
                     <f:for each="{module.subModules}" as="sub">
                         <f:if condition="{sub.settings}">
@@ -53,7 +56,9 @@
                             <f:for each="{module.subModules}" as="sub" iteration="subModulesIterator">
                                 <div class="typo3-adminPanel-content-panes-item{f:if(condition:'{subModulesIterator.isFirst}',then:' typo3-adminPanel-content-panes-item-active')}" data-typo3-role="typo3-adminPanel-content-pane" data-typo3-tab-id="{sub.identifier}">
                                     <f:if condition="{sub.label}"><h1 class="typo3-adminPanel-headline">{sub.label}</h1></f:if>
-                                    <f:format.raw>{sub.content}</f:format.raw>
+                                    <f:format.raw>
+                                      <adm:subModuleRender data="{data}" module="{sub}"/>
+                                    </f:format.raw>
                                 </div>
                             </f:for>
                         </div>
index 205fd58..0533306 100644 (file)
@@ -1,15 +1,12 @@
 <html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers" data-namespace-typo3-fluid="true">
 <f:layout name="Default"/>
 <f:section name="Module">
-
-    <f:for each="{modules}" as="module" iteration="iteration">
-        <f:if condition="{module.subModules}">
-            <f:variable name="uid">{iteration.index}</f:variable>
-            <f:variable name="icon"><core:icon identifier="{module.iconIdentifier}" alternativeMarkupIdentifier="inline"/></f:variable>
-            <f:variable name="label">{module.label}</f:variable>
-            <f:variable name="information">{module.shortInfo}</f:variable>
-            <f:render partial="Modules/Item" arguments="{module: module, uid: uid, icon: icon, label: label, information: information}" debug="false"/>
-        </f:if>
+    <f:for each="{parentModules}" as="module" iteration="iteration">
+        <f:variable name="uid">{iteration.index}</f:variable>
+        <f:variable name="icon"><core:icon identifier="{module.iconIdentifier}" alternativeMarkupIdentifier="inline"/></f:variable>
+        <f:variable name="label">{module.label}</f:variable>
+        <f:variable name="information">{module.shortInfo}</f:variable>
+        <f:render partial="Modules/Item" arguments="{module: module, uid: uid, icon: icon, label: label, information: information, data: data}" debug="false"/>
     </f:for>
 
     <f:if condition="{hookObjectModuleContent}">
@@ -17,7 +14,7 @@
         <f:variable name="icon"><core:icon identifier="actions-window-open" alternativeMarkupIdentifier="inline"/></f:variable>
         <f:variable name="label"><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang.xlf:deprecatedModuleLabel"/></f:variable>
         <f:variable name="mainContent">{hookObjectModuleContent}</f:variable>
-        <f:render partial="Modules/Item" arguments="{uid: uid, icon: icon, label: label, mainContent: mainContent}" debug="false"/>
+        <f:render partial="Modules/Item" arguments="{uid: uid, icon: icon, label: label, mainContent: mainContent, data: data}}" debug="false"/>
     </f:if>
 
 </f:section>
@@ -30,8 +27,8 @@
     <f:variable name="mainContent">
         <h1 class="typo3-adminPanel-headline"><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang.xlf:settings.label"/></h1>
         <div class="typo3-adminPanel-card-group">
-            <f:for each="{modules}" as="module">
-                <f:if condition="{module.settings}">
+            <f:for each="{settingsModules}" as="module">
+                <f:if condition="{module.pageSettings}">
                     <div class="typo3-adminPanel-card">
                         <div class="typo3-adminPanel-card-header">
                             <h2 class="typo3-adminPanel-card-header-headline">
@@ -39,7 +36,7 @@
                                 <span class="typo3-adminPanel-card-header-text">{module.label}</span>
                             </h2>
                         </div>
-                        <div class="typo3-adminPanel-card-body"><f:format.raw>{module.settings}</f:format.raw></div>
+                        <div class="typo3-adminPanel-card-body"><f:format.raw>{module.pageSettings}</f:format.raw></div>
                     </div>
                 </f:if>
             </f:for>
@@ -50,6 +47,6 @@
             </button>
         </div>
     </f:variable>
-    <f:render partial="Modules/Item" arguments="{uid: uid, icon: icon, label: label, information: information, mainContent: mainContent}" debug="false"/>
+    <f:render partial="Modules/Item" arguments="{uid: uid, icon: icon, label: label, information: information, mainContent: mainContent, data: data}" debug="false"/>
 </f:section>
 </html>
index 3969eec..aeadc6d 100644 (file)
@@ -18,6 +18,11 @@ namespace TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures;
 
 class DisabledMainModuleFixture extends MainModuleFixture
 {
+    public function getIdentifier(): string
+    {
+        return 'example-disabled';
+    }
+
     public function isEnabled(): bool
     {
         return false;
index 3633f78..ebcd4c1 100644 (file)
@@ -16,11 +16,25 @@ namespace TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures;
  * The TYPO3 project - inspiring people to share!
  */
 
-use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Adminpanel\Modules\AdminPanelModuleInterface;
-use TYPO3\CMS\Adminpanel\Modules\AdminPanelSubModuleInterface;
-
-class MainModuleFixture implements AdminPanelModuleInterface
+use     Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ConfigurableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\InitializableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\OnSubmitActorInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\PageSettingsProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ResourceProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ShortInfoProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\SubmoduleProviderInterface;
+
+class MainModuleFixture implements
+    ModuleInterface,
+    ShortInfoProviderInterface,
+    SubmoduleProviderInterface,
+    InitializableInterface,
+               ResourceProviderInterface,
+    PageSettingsProviderInterface,
+    OnSubmitActorInterface,
+    ConfigurableInterface
 {
 
     /**
@@ -67,7 +81,7 @@ class MainModuleFixture implements AdminPanelModuleInterface
     /**
      * @return string
      */
-    public function getSettings(): string
+    public function getPageSettings(): string
     {
         return 'example settings';
     }
@@ -130,7 +144,7 @@ class MainModuleFixture implements AdminPanelModuleInterface
     /**
      * Set SubModules for current module
      *
-     * @param AdminPanelSubModuleInterface[] $subModules
+     * @param ModuleInterface[] $subModules
      */
     public function setSubModules(array $subModules): void
     {
@@ -139,7 +153,7 @@ class MainModuleFixture implements AdminPanelModuleInterface
     /**
      * Get SubModules for current module
      *
-     * @return AdminPanelSubModuleInterface[]
+     * @return ModuleInterface[]
      */
     public function getSubModules(): array
     {
@@ -147,11 +161,9 @@ class MainModuleFixture implements AdminPanelModuleInterface
     }
 
     /**
-     * Returns true if submodule has own settings
-     *
-     * @return bool
+     * @inheritdoc
      */
-    public function getHasSubmoduleSettings(): bool
+    public function hasSubmoduleSettings(): bool
     {
         return false;
     }
index 880fde4..73069e2 100644 (file)
@@ -17,9 +17,16 @@ namespace TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures;
  */
 
 use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ContentProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\InitializableInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\ModuleSettingsProviderInterface;
+use TYPO3\CMS\Adminpanel\ModuleApi\OnSubmitActorInterface;
 use TYPO3\CMS\Adminpanel\Modules\AdminPanelSubModuleInterface;
 
-class SubModuleFixture implements AdminPanelSubModuleInterface
+class SubModuleFixture implements ModuleInterface, InitializableInterface, ContentProviderInterface, ModuleSettingsProviderInterface, OnSubmitActorInterface, DataProviderInterface
 {
 
     /**
@@ -55,9 +62,10 @@ class SubModuleFixture implements AdminPanelSubModuleInterface
     /**
      * Sub-Module content as rendered HTML
      *
+     * @param \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData $data
      * @return string
      */
-    public function getContent(): string
+    public function getContent(ModuleData $data): string
     {
         return 'content';
     }
@@ -83,4 +91,13 @@ class SubModuleFixture implements AdminPanelSubModuleInterface
     public function onSubmit(array $configurationToSave, ServerRequestInterface $request): void
     {
     }
+
+    /**
+     * @param \Psr\Http\Message\ServerRequestInterface $request
+     * @return \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData
+     */
+    public function getDataToStore(ServerRequestInterface $request): ModuleData
+    {
+        return new ModuleData(['foo' => 'bar']);
+    }
 }
index 9c69401..d76ef5e 100644 (file)
@@ -22,7 +22,6 @@ use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\RequestHandlerInterface;
 use TYPO3\CMS\Adminpanel\Controller\MainController;
 use TYPO3\CMS\Adminpanel\Middleware\AdminPanelInitiator;
-use TYPO3\CMS\Adminpanel\View\AdminPanelView;
 use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
@@ -65,9 +64,9 @@ class AdminPanelInitiatorTest extends UnitTestCase
 
         $controller = $this->prophesize(MainController::class);
         GeneralUtility::setSingletonInstance(MainController::class, $controller->reveal());
-        GeneralUtility::addInstance(AdminPanelView::class, $this->prophesize(AdminPanelView::class)->reveal());
         $handler = $this->prophesizeHandler();
         $request = $this->prophesize(ServerRequestInterface::class);
+        $request->withAttribute(Argument::cetera())->willReturn($request);
         // Act
         $adminPanelInitiator = new AdminPanelInitiator();
         $adminPanelInitiator->process(
index f951c65..0c61005 100644 (file)
@@ -20,7 +20,6 @@ use TYPO3\CMS\Adminpanel\Exceptions\InvalidConfigurationException;
 use TYPO3\CMS\Adminpanel\Service\ModuleLoader;
 use TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures\DisabledMainModuleFixture;
 use TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures\MainModuleFixture;
-use TYPO3\CMS\Adminpanel\Tests\Unit\Fixtures\SubModuleFixture;
 use TYPO3\CMS\Core\Service\DependencyOrderingService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
@@ -82,11 +81,6 @@ class ModuleLoaderTest extends UnitTestCase
                 [
                     'modulename' => ['module' => \stdClass::class],
                 ],
-            ],
-            'submodule class name given when main modules requested' => [
-                [
-                    'modulename' => ['module' => SubModuleFixture::class]
-                ]
             ]
         ];
     }
@@ -105,27 +99,8 @@ class ModuleLoaderTest extends UnitTestCase
     }
 
     /**
-     * @test
-     * @dataProvider  invalidConfigurationDataProvider
-     */
-    public function validateSortAndInitializeModulesThrowsExceptionIfSubModuleRequestedButMainModuleGiven($configuration): void
-    {
-        $config = [
-            'module1' => [
-                'module' => MainModuleFixture::class
-            ]
-        ];
-
-        $this->expectException(InvalidConfigurationException::class);
-        $this->expectExceptionCode(1519490112);
-
-        $moduleLoader = new ModuleLoader();
-        $moduleLoader->validateSortAndInitializeModules($config, 'sub');
-    }
-
-    /**
-     * @test
-     */
+    * @test
+    */
     public function validateSortAndInitializeModulesOrdersModulesWithDependencyOrderingService(): void
     {
         $config = [
@@ -166,33 +141,7 @@ class ModuleLoaderTest extends UnitTestCase
         $result = $moduleLoader->validateSortAndInitializeModules($config);
 
         self::assertCount(1, $result);
-        self::assertInstanceOf(MainModuleFixture::class, $result[0]);
-        self::assertNotInstanceOf(DisabledMainModuleFixture::class, $result[0]);
-    }
-
-    /**
-     * @test
-     */
-    public function validateSortAndInitializeSubModulesInstantiatesSubModules(): void
-    {
-        $config = [
-            'module1' => [
-                'module' => SubModuleFixture::class
-            ],
-            'module2' => [
-                'module' => SubModuleFixture::class
-            ]
-        ];
-
-        $dependencyOrderingServiceProphecy = $this->prophesize(DependencyOrderingService::class);
-        GeneralUtility::addInstance(DependencyOrderingService::class, $dependencyOrderingServiceProphecy->reveal());
-        $dependencyOrderingServiceProphecy->orderByDependencies($config)->willReturn($config);
-
-        $moduleLoader = new ModuleLoader();
-        $result = $moduleLoader->validateSortAndInitializeSubModules($config);
-
-        self::assertCount(2, $result);
-        self::assertInstanceOf(SubModuleFixture::class, $result[0]);
-        self::assertInstanceOf(SubModuleFixture::class, $result[1]);
+        self::assertInstanceOf(MainModuleFixture::class, $result['example']);
+        self::assertArrayNotHasKey('example-disabled', $result);
     }
 }
index 622464b..ab5cef5 100644 (file)
@@ -30,7 +30,7 @@ class StateUtilityTest extends UnitTestCase
     public function isEnabledReturnsFalseIfNoBackendUserExists(): void
     {
         $GLOBALS['BE_USER'] = false;
-        $isEnabled = StateUtility::isActivated();
+        $isEnabled = StateUtility::isActivatedForUser();
         self::assertFalse($isEnabled);
     }
 
@@ -40,7 +40,7 @@ class StateUtilityTest extends UnitTestCase
     public function isEnabledReturnsFalseIfNoBackendUserInFrontendContextIsLoggedIn(): void
     {
         $GLOBALS['BE_USER'] = $this->prophesize(BackendUserAuthentication::class)->reveal();
-        $isEnabled = StateUtility::isActivated();
+        $isEnabled = StateUtility::isActivatedForUser();
         self::assertFalse($isEnabled);
     }
 
@@ -78,7 +78,7 @@ class StateUtilityTest extends UnitTestCase
         $beUserProphecy = $this->prophesize(FrontendBackendUserAuthentication::class);
         $beUserProphecy->getTSConfig()->willReturn($tsConfig);
         $GLOBALS['BE_USER'] = $beUserProphecy->reveal();
-        $isEnabled = StateUtility::isActivated();
+        $isEnabled = StateUtility::isActivatedForUser();
         self::assertTrue($isEnabled);
     }
 
@@ -115,7 +115,7 @@ class StateUtilityTest extends UnitTestCase
         $beUserProphecy = $this->prophesize(FrontendBackendUserAuthentication::class);
         $beUserProphecy->getTSConfig()->willReturn($tsConfig);
         $GLOBALS['BE_USER'] = $beUserProphecy->reveal();
-        $isEnabled = StateUtility::isActivated();
+        $isEnabled = StateUtility::isActivatedForUser();
         self::assertFalse($isEnabled);
     }
 }
index 4f0a0c7..a54a653 100644 (file)
@@ -2,9 +2,6 @@
 
 defined('TYPO3_MODE') or die();
 
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_eofe'][]
-    = \TYPO3\CMS\Adminpanel\Hooks\RenderHook::class . '->renderAdminPanel';
-
 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules'] = [
     'preview' => [
         'module' => \TYPO3\CMS\Adminpanel\Modules\PreviewModule::class,
@@ -60,3 +57,7 @@ $GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include']['adminPanel_save']
     = \TYPO3\CMS\Adminpanel\Controller\AjaxController::class . '::saveDataAction';
 
 $GLOBALS['TYPO3_CONF_VARS']['LOG']['writerConfiguration'][\TYPO3\CMS\Core\Log\LogLevel::DEBUG][\TYPO3\CMS\Adminpanel\Log\InMemoryLogWriter::class] = [];
+
+if (!is_array($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['adminpanel_requestcache'])) {
+    $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['adminpanel_requestcache'] = [];
+}
index 515c96f..5316026 100644 (file)
@@ -11,21 +11,6 @@ Description
 
 The admin panel got a complete overhaul regarding its design as well as the underlying code and extensibility.
 
-The panel is now structurally separated in modules and submodules. Each module is completely responsible for its own rendering, collection of information, settings and behaviour - dependencies to other parts of the core were resolved. Extension authors can now write their own modules or add submodules to existing modules.
-
-An admin panel module commonly has
-- an icon, identifier, a short info and a label
-- initializeModule and onSubmit methods for initialization (done early in the TYPO3 Request) and reacting to changes (onSubmit is executed when the settings are updated)
-- settings that influence page rendering or page display
-- methods to provide custom CSS and JavaScript files
-- submodules
-
-An admin panel submodule has
-- an identifier and a label
-- initializeModule and onSubmit methods for initialization (done early in the TYPO3 Request) and reacting to changes (onSubmit is executed when the settings are updated)
-- module content (for example the Info submodules display information about the current page or server configuration)
-- settings influencing their module content (for example the TypoScript Time / Rendering sub module has settings that influence whether to display messages or not)
-
 UI wise the following changes were done
 - Ajax is used to save configuration options, so only a single reload is triggered even if multiple settings changed.
 - Settings influencing page rendering should be grouped together
@@ -36,76 +21,8 @@ UI wise the following changes were done
 Impact
 ======
 
-The new admin panel provides a better look and feel as well as more convenient access to information and more flexible extensibility. For backwards compatibility enabling and disabling modules or options for editors is still possible via User TSConfig.
-
-Creating additional modules
-===========================
-
-To create your own admin panel module, create a new PHP class extending `\TYPO3\CMS\Adminpanel\Modules\AbstractModule`.
-
-Implement at least the following methods:
-- `getIdentifier` - A unique identifier for your module (for example `mynamespace_modulename`)
-- `getIconIdentifier` - An icon identifier (resolved via the icon API, make sure to use a registered icon here)
-- `getLabel` - Speaking label for the module - you can access language files via `$this->getLanguageService()`
-- `getShortInfo` - Displayed next to the module label, may contain aggregated infos (such as `Total Parse Time: 200ms`)
-
-Register your module by adding the following in your `ext_localconf.php`::
-
-    $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']['mynamespace_modulename'] => [
-        'module' => \Your\Namespace\Adminpanel\Modules\YourModule::class,
-        'before' => ['cache'],
-    ],
-
-via `before` or `after` you can influence where your module will be displayed in the module menu by referencing the
-identifier / array key of other modules.
-
-Modules themselves do provide settings for the page rendering or global actions (like preview settings, clearing caches or
-adding action buttons for editing the page) but do not provide further content. If you want to display additional content
-in the admin panel (like rendering times or backtraces or whatever you wish), you have to add a submodule to your main
-module.
-
-To provide settings in a module, implement the method `getSettings`, which has to return rendered HTML form elements
-(but without the form tag), ready to be used in the admin panel main form.
-
-Adding a sub-module
-===================
-
-First of all: As soon as a module has a submodule it will be displayed in the main admin panel. Modules without submodules
-may only provide settings and aren't displayed in the module bar but only in the settings overview.
-
-Adding a submodule is similar to adding a module. First, create a new class that extends `AbstractSubModule`. Implement
-at least the following methods:
-
-- `getIdentifier` - A unique identifier for your sub module (for example `submodulename`)
-- `getLabel` - Speaking label for the module - you can access language files via `$this->getLanguageService()`
-- `getContent` - The rendered HTML content for your module
-
-Register your sub module by adding the following in your `ext_localconf.php`::
-
-    $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']['mynamespace_modulename']['submodules']['submodulename'] => [
-      'module' => \Your\Namespace\Adminpanel\Modules\YourModule\Submodule::class
-    ]
-
-where `mynamespace_modulename` references the main module where you want to add your submodule, and `submodulename` is the
-identifier of your sub module.
-
-This way, you can also register new custom sub modules to existing main modules.
-
-Advanced Usage
---------------
-
-You can also implement the logic completely on your own not using the `AbstractModule` / `AbstractSubModule` by implementing
-the corresponding interfaces `AdminPanelModuleInterface` and `AdminPanelSubModuleInterface`.
-
-Examples
-========
-
-You can find examples for main and sub modules and their registration in the admin panel extension. Short ones for a quick
-look are:
-
-- `adminpanel/Classes/Modules/Info/PhpInformation.php` (Submodule)
-- `adminpanel/Classes/Modules/InfoModule.php` (Main module, serves as submodule wrapper only)
-- `adminpanel/Classes/Modules/EditModule.php` (Main module, custom rendering settings)
+The new admin panel provides a better look and feel as well as more convenient access to information and more flexible extensibility.
+For backwards compatibility enabling and disabling modules or options for editors is still possible via User TSConfig.
 
 
 .. index:: Frontend, PHP-API, ext:adminpanel
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-86003-AdminpanelCompositionApi.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-86003-AdminpanelCompositionApi.rst
new file mode 100644 (file)
index 0000000..f3c87ce
--- /dev/null
@@ -0,0 +1,505 @@
+.. include:: ../../Includes.txt
+
+==========================================================
+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 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:
+
+- `AdminPanelInitiator` - Called early in the request stack to allow initialisation of modules to catch most of the request data (for example log entries)
+- `AdminPanelDataPersister` - Called at nearly the end of a frontend request to store the collected data (this is where module data gets saved)
+- `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 `$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 `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:
+
+1. Create a class implementing the basic `ModuleInterface`
+2. Register the class in `ext_localconf.php`
+3. Implement further interfaces for additional capabilities
+
+
+1. Create module class
+----------------------
+
+To create your own admin panel module, create a new PHP class implementing `\TYPO3\CMS\Adminpanel\ModuleApi\ModuleInterface`.
+The interface denotes your class as an adminpanel module and requires the implementation of `getIdentifier` and `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 `ext_localconf.php`::
+
+    $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']['mynamespace_modulename'] => [
+        'module' => \Your\Namespace\Adminpanel\Modules\YourModule::class,
+        'before' => ['cache'],
+    ];
+
+via `before` or `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 `ext_localconf.php`::
+
+    $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']['info']['submodules']['mynamespace_modulename'] => [
+        'module' => \Your\Namespace\Adminpanel\Modules\YourModule::class
+    ];
+
+Note the `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
++++++++
+
+- `getIdentifier` - Returns `string` identifier of a module (for example `mynamespace_modulename`)
+- `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
++++++++
+
+- `isEnabled` - Returns `bool` depending on whether the module is enabled.
+
+Example implementation
+++++++++++++++++++++++
+
+ `\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
++++++++
+
+- `getContent(ModuleData $data)` - Return content as HTML. For modules implementing the `DataProviderInterface`
+the "ModuleData" object is automatically filled with the stored data - if
+no data is given a "fresh" ModuleData object is injected.
+
+Example implementation
+++++++++++++++++++++++
+
+ `\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
++++++++
+
+- `getDataToStore(ServerRequestInterface $request): ModuleData` - Return a `ModuleData` instance with the data to store
+
+Example implementation
+++++++++++++++++++++++
+
+ `\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
++++++++
+
+- `initializeModule(ServerRequestInterface $request)` - Called on adminpanel initialization
+
+Example implementation
+++++++++++++++++++++++
+
+ `\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
++++++++
+
+- `getSettings(): string` - Return settings as rendered HTML form elements
+
+Example implementation
+++++++++++++++++++++++
+
+ `\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
++++++++
+
+- `onSubmit(array $configurationToSave, ServerRequestInterface $request)` - Can act when configuration gets saved. Configuration form vars are provided in `$configurationToSave` as an array.
+
+Example implementation
+++++++++++++++++++++++
+
+ `\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
++++++++
+
+- `getSettings(): string` - Return HTML form elements for settings
+
+Example implementation
+++++++++++++++++++++++
+
+ `\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
++++++++
+
+- `getJavaScriptFiles(): array` - Returns a string array with javascript files that will be rendered after the module
+- `getCssFiles(): array` - Returns a string array with CSS files that will be rendered after the module
+
+Example implementation
+++++++++++++++++++++++
+
+ `\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
++++++++
+
+- `getShortInfo(): string` - Info string (no HTML) that should be rendered
+- `getIconIdentifier(): string` - An icon for this info line, needs to be registered in `IconRegistry`
+
+Example implementation
+++++++++++++++++++++++
+
+ `\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
++++++++
+
+- `setSubModules(array $subModules)` - Sets array of module instances (instances of `ModuleInterface`) as submodules
+- `getSubModules(): array` - Returns an array of module instances
+- `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
+++++++++++++++++++++++
+
+ `\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