From f1475e8d1acfd2f1e3bacbf2329160645b9b3d63 Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Thu, 2 Nov 2017 15:03:23 +0100
Subject: [PATCH] [TASK] Unify Backend module registration for Extbase modules

This patch unifies the Backend module registration for Extbase modules
like it has already been done for all other Backend modules using PSR-7
entry-points. This way backend route dispatching and module registration
has been simplified.

The entrypoint for Extbase Backend modules is now
\TYPO3\CMS\Extbase\Core\Bootstrap->handleBackendRequest()
which returns a PSR-7 response object.

The following functionality has been marked as deprecated as it was
solely built to handle Extbase modules when conf.php and index.php were
still in style:

ExtensionManagementUtility::configureModule()
$GLOBALS['TBE_MODULES']['_configuration'][$name]['configureModuleFunction']

Releases: master
Resolves: #82902
Related: #58621
Change-Id: I7956b350d650ed52bc7b5d83db20df386d79eb65
Reviewed-on: https://review.typo3.org/54531
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Henning Liebe <h.liebe@neusta.de>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
---
 .../backend/Classes/Http/RouteDispatcher.php  | 43 ++-----------
 .../backend/Classes/Module/ModuleLoader.php   | 11 +---
 .../Utility/ExtensionManagementUtility.php    |  2 +
 ...CustomBackendModuleRegistrationMethods.rst | 40 ++++++++++++
 .../sysext/extbase/Classes/Core/Bootstrap.php | 61 +++++++++++++++++++
 .../extbase/Classes/Mvc/Web/Response.php      | 22 +++++++
 .../Classes/Utility/ExtensionUtility.php      |  5 +-
 .../Php/MethodCallStaticMatcher.php           |  7 +++
 8 files changed, 143 insertions(+), 48 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Deprecation-82902-CustomBackendModuleRegistrationMethods.rst

diff --git a/typo3/sysext/backend/Classes/Http/RouteDispatcher.php b/typo3/sysext/backend/Classes/Http/RouteDispatcher.php
index d6d456016b23..94d7fa7e5fa8 100644
--- a/typo3/sysext/backend/Classes/Http/RouteDispatcher.php
+++ b/typo3/sysext/backend/Classes/Http/RouteDispatcher.php
@@ -55,7 +55,7 @@ class RouteDispatcher extends Dispatcher implements DispatcherInterface
         }
 
         if ($route->getOption('module')) {
-            return $this->dispatchModule($request, $response);
+            $this->addAndValidateModuleConfiguration($request, $route);
         }
         $targetIdentifier = $route->getOption('target');
         $target = $this->getCallableFromTarget($targetIdentifier);
@@ -92,18 +92,18 @@ class RouteDispatcher extends Dispatcher implements DispatcherInterface
     }
 
     /**
-     * Executes the modules configured via Extbase
+     * Adds configuration for a module and checks module permissions for the
+     * current user.
      *
      * @param ServerRequestInterface $request
-     * @param ResponseInterface $response
-     * @return ResponseInterface A PSR-7 response object
+     * @param Route $route
      * @throws \RuntimeException
      */
-    protected function dispatchModule(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
+    protected function addAndValidateModuleConfiguration(ServerRequestInterface $request, Route $route)
     {
-        $route = $request->getAttribute('route');
         $moduleName = $route->getOption('moduleName');
         $moduleConfiguration = $this->getModuleConfiguration($moduleName);
+        $route->setOption('moduleConfiguration', $moduleConfiguration);
 
         $backendUserAuthentication = $GLOBALS['BE_USER'];
 
@@ -123,37 +123,6 @@ class RouteDispatcher extends Dispatcher implements DispatcherInterface
                 }
             }
         }
-
-        // Use regular Dispatching
-        // @todo: unify with the code above
-        $targetIdentifier = $route->getOption('target');
-        if (!empty($targetIdentifier)) {
-            // @internal routeParameters are a helper construct for the install tool only.
-            // @todo: remove this, after sub-actions in install tool can be addressed directly
-            if (!empty($moduleConfiguration['routeParameters'])) {
-                $request = $request->withQueryParams(array_merge_recursive(
-                    $request->getQueryParams(),
-                    $moduleConfiguration['routeParameters']
-                ));
-            }
-            return parent::dispatch($request, $response);
-        }
-        // extbase module
-        $configuration = [
-                'extensionName' => $moduleConfiguration['extensionName'],
-                'pluginName' => $moduleName
-            ];
-        if (isset($moduleConfiguration['vendorName'])) {
-            $configuration['vendorName'] = $moduleConfiguration['vendorName'];
-        }
-
-        // Run Extbase
-        $bootstrap = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Core\Bootstrap::class);
-        $content = $bootstrap->run('', $configuration);
-
-        $response->getBody()->write($content);
-
-        return $response;
     }
 
     /**
diff --git a/typo3/sysext/backend/Classes/Module/ModuleLoader.php b/typo3/sysext/backend/Classes/Module/ModuleLoader.php
index ea1a91f830d0..e742979ca2ba 100644
--- a/typo3/sysext/backend/Classes/Module/ModuleLoader.php
+++ b/typo3/sysext/backend/Classes/Module/ModuleLoader.php
@@ -140,6 +140,7 @@ class ModuleLoader
     {
         // Check for own way of configuring module
         if (is_array($GLOBALS['TBE_MODULES']['_configuration'][$name]['configureModuleFunction'])) {
+            trigger_error('Registering a module using "configureModuleFunction" is deprecated and will be removed in TYPO3 v10.', E_USER_DEPRECATED);
             $obj = $GLOBALS['TBE_MODULES']['_configuration'][$name]['configureModuleFunction'];
             if (is_callable($obj)) {
                 $MCONF = call_user_func($obj, $name);
@@ -170,14 +171,8 @@ class ModuleLoader
         // Language processing. This will add module labels and image reference to the internal ->moduleLabels array of the LANG object.
         $this->addLabelsForModule($name, ($finalModuleConfiguration['labels'] ?? $setupInformation['labels']));
 
-        // Default script setup
-        if ($setupInformation['configuration']['script'] === '_DISPATCH' || isset($setupInformation['configuration']['routeTarget'])) {
-            if ($setupInformation['configuration']['extbase']) {
-                $finalModuleConfiguration['script'] = BackendUtility::getModuleUrl('Tx_' . $name);
-            } else {
-                // just go through BackendModuleRequestHandler where the routeTarget is resolved
-                $finalModuleConfiguration['script'] = BackendUtility::getModuleUrl($name);
-            }
+        if (isset($setupInformation['configuration']['routeTarget'])) {
+            $finalModuleConfiguration['script'] = BackendUtility::getModuleUrl($name);
         } else {
             $finalModuleConfiguration['script'] = BackendUtility::getModuleUrl('dummy');
         }
diff --git a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
index 773f15b010a8..49ea69eabf66 100644
--- a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
+++ b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
@@ -801,9 +801,11 @@ class ExtensionManagementUtility
      *
      * @param string $moduleSignature The module name
      * @return array Configuration of the module
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10, addModule() works the same way nowadays.
      */
     public static function configureModule($moduleSignature)
     {
+        trigger_error('This method will be removed in TYPO3 v10, as the same functionality is found in addModule() as well.', E_USER_DEPRECATED);
         $moduleConfiguration = $GLOBALS['TBE_MODULES']['_configuration'][$moduleSignature];
 
         // Register the icon and move it too "iconIdentifier"
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-82902-CustomBackendModuleRegistrationMethods.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-82902-CustomBackendModuleRegistrationMethods.rst
new file mode 100644
index 000000000000..d66025f1efd7
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-82902-CustomBackendModuleRegistrationMethods.rst
@@ -0,0 +1,40 @@
+.. include:: ../../Includes.txt
+
+================================================================
+Deprecation: #82902 - Custom Backend Module registration methods
+================================================================
+
+See :issue:`82902`
+
+Description
+===========
+
+The internal API to register backend modules via ``ExtensionManagementUtility::configureModule()`` and
+``configureModuleFunction`` has been marked as deprecated.
+
+It was solely introduced to allow script-based dispatching of backend modules used in TYPO3 v6.2 which
+had multiple entry-points (mod1/conf.php and mod1/index.php).
+
+Since TYPO3 v7 Backend Routing is available, thus the old registration API is no longer needed.
+
+Impact
+======
+
+Registering a `configureModuleFunction` will trigger a deprecation warning.
+
+Calling ``ExtensionManagementUtility::configureModule()`` will trigger a deprecation warning.
+
+
+Affected Installations
+======================
+
+Installations with legacy and/or custom Backend modules in extensions.
+
+
+Migration
+=========
+
+Use either ``ExtensionManagementUtility::addModule()`` or Extbase's
+``ExtensionUtility::registerModule()`` to register a module, always providing a ``routeTarget``.
+
+.. index:: Backend, PHP-API, PartiallyScanned
diff --git a/typo3/sysext/extbase/Classes/Core/Bootstrap.php b/typo3/sysext/extbase/Classes/Core/Bootstrap.php
index 6b9d9427a193..7ca3ef805712 100644
--- a/typo3/sysext/extbase/Classes/Core/Bootstrap.php
+++ b/typo3/sysext/extbase/Classes/Core/Bootstrap.php
@@ -14,6 +14,11 @@ namespace TYPO3\CMS\Extbase\Core;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Backend\Routing\Route;
+use TYPO3\CMS\Extbase\Mvc\Web\Response;
+
 /**
  * Creates a request an dispatches it to the controller which was specified
  * by TS Setup, flexForm and returns the content to the v4 framework.
@@ -171,6 +176,62 @@ class Bootstrap implements \TYPO3\CMS\Extbase\Core\BootstrapInterface
         return $content;
     }
 
+    /**
+     * Entrypoint for backend modules, handling PSR-7 requests/responses
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     * @internal
+     */
+    public function handleBackendRequest(ServerRequestInterface $request): ResponseInterface
+    {
+        // build the configuration from the Server request / route
+        /** @var Route $route */
+        $route = $request->getAttribute('route');
+        $moduleConfiguration = $route->getOption('moduleConfiguration');
+        $configuration = [
+            'extensionName' => $moduleConfiguration['extensionName'],
+            'pluginName' => $route->getOption('moduleName')
+        ];
+        if (isset($moduleConfiguration['vendorName'])) {
+            $configuration['vendorName'] = $moduleConfiguration['vendorName'];
+        }
+
+        $this->initialize($configuration);
+
+        /** @var $requestHandlerResolver \TYPO3\CMS\Extbase\Mvc\RequestHandlerResolver */
+        $requestHandlerResolver = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\RequestHandlerResolver::class);
+        $requestHandler = $requestHandlerResolver->resolveRequestHandler();
+        /** @var Response $extbaseResponse */
+        $extbaseResponse = $requestHandler->handleRequest();
+
+        // Convert to PSR-7 response and hand it back to TYPO3 Core
+        $response = $this->convertExtbaseResponseToPsr7Response($extbaseResponse);
+        $this->resetSingletons();
+        $this->objectManager->get(\TYPO3\CMS\Extbase\Service\CacheService::class)->clearCachesOfRegisteredPageIds();
+        return $response;
+    }
+
+    /**
+     * Converts a Extbase response object into a PSR-7 Response
+     *
+     * @param Response $extbaseResponse
+     * @return ResponseInterface
+     */
+    protected function convertExtbaseResponseToPsr7Response(Response $extbaseResponse): ResponseInterface
+    {
+        $response = new \TYPO3\CMS\Core\Http\Response(
+            'php://temp',
+            $extbaseResponse->getStatusCode(),
+            $extbaseResponse->getUnpreparedHeaders()
+        );
+        $content = $extbaseResponse->getContent();
+        if ($content !== null) {
+            $response->getBody()->write($content);
+        }
+        return $response;
+    }
+
     /**
      * Resets global singletons for the next plugin
      */
diff --git a/typo3/sysext/extbase/Classes/Mvc/Web/Response.php b/typo3/sysext/extbase/Classes/Mvc/Web/Response.php
index ea744ce6b393..348e874d169a 100644
--- a/typo3/sysext/extbase/Classes/Mvc/Web/Response.php
+++ b/typo3/sysext/extbase/Classes/Mvc/Web/Response.php
@@ -157,6 +157,17 @@ class Response extends \TYPO3\CMS\Extbase\Mvc\Response
         return $this->statusCode . ' ' . $this->statusMessage;
     }
 
+    /**
+     * Returns the status code, if not set, uses the OK status code 200
+     *
+     * @return int
+     * @internal only use for backend module handling
+     */
+    public function getStatusCode()
+    {
+        return $this->statusCode ?: 200;
+    }
+
     /**
      * Sets the specified HTTP header
      *
@@ -200,6 +211,17 @@ class Response extends \TYPO3\CMS\Extbase\Mvc\Response
         return $preparedHeaders;
     }
 
+    /**
+     * Returns the HTTP headers grouped by name without the status header
+     *
+     * @return array all headers set for this request
+     * @internal only used within TYPO3 Core to convert to PSR-7 response headers
+     */
+    public function getUnpreparedHeaders(): array
+    {
+        return $this->headers;
+    }
+
     /**
      * Sends the HTTP headers.
      *
diff --git a/typo3/sysext/extbase/Classes/Utility/ExtensionUtility.php b/typo3/sysext/extbase/Classes/Utility/ExtensionUtility.php
index f3f3e59b9dd6..5ecadfd807a4 100644
--- a/typo3/sysext/extbase/Classes/Utility/ExtensionUtility.php
+++ b/typo3/sysext/extbase/Classes/Utility/ExtensionUtility.php
@@ -193,8 +193,7 @@ tt_content.' . $pluginSignature . ' {
             $moduleConfiguration['vendorName'] = $vendorName;
         }
         $moduleConfiguration['extensionName'] = $extensionName;
-        $moduleConfiguration['configureModuleFunction'] = [\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::class, 'configureModule'];
-        $GLOBALS['TBE_MODULES']['_configuration'][$moduleSignature] = $moduleConfiguration;
+        $moduleConfiguration['routeTarget'] = \TYPO3\CMS\Extbase\Core\Bootstrap::class . '::handleBackendRequest';
         if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['modules'][$moduleSignature])) {
             $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$extensionName]['modules'][$moduleSignature] = [];
         }
@@ -203,7 +202,7 @@ tt_content.' . $pluginSignature . ' {
                 'actions' => \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $actions)
             ];
         }
-        \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule($mainModuleName, $subModuleName, $position);
+        \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule($mainModuleName, $subModuleName, $position, null, $moduleConfiguration);
     }
 
     /**
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
index 6788f44a156d..ac5210ae926f 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
@@ -491,4 +491,11 @@ return [
             'Deprecation-82899-ExtensionManagementUtilityMethods.rst',
         ],
     ],
+    'TYPO3\CMS\Core\Utility\ExtensionManagementUtility::configureModule' => [
+        'numberOfMandatoryArguments' => 0,
+        'maximumNumberOfArguments' => 0,
+        'restFiles' => [
+            'Deprecation-82902-CustomBackendModuleRegistrationMethods.rst',
+        ],
+    ],
 ];
-- 
GitLab