[!!!][TASK] Migrate modules to regular backend routing 81/53881/13
authorMatthias Vogel <typo3@kanti.de>
Sat, 9 Sep 2017 11:04:20 +0000 (13:04 +0200)
committerBenni Mack <benni@typo3.org>
Sat, 9 Sep 2017 17:43:09 +0000 (19:43 +0200)
This patch removes the separate request handler for backend modules,
which was accessed via "&M=moduleName" GET parameter. This is now
migrated into the RouteDispatcher which can dispatch modules as well.

Now, modules are called via the "&route" parameter like all other routes.

Additionally, the requested URLs for modules were requested with the additional
"moduleToken" which is now called "token".

This way, special treatment for modules when dispatching is removed,
however the security checks are still in place so this is kept as is.

All places where URLs are generated can now still be accessed via
`BackendUtility::getModuleUrl()` which can deal with routes, module names
and routePaths (from the URL) to keep backwards-compatibility.

Next Steps:
- Migration wizard for bookmarks + Streamline bookmarks code (see todos)
- Check what needs to be added in ExtensionManagementUtility
- Introduce slugs in routes for BE, e.g. /file-edit/{fileId}/ and /module/page/view/{id}
- Document reserved GET parameters "id", "route" and "token"
- Cleanup usage of determineScriptId and getModuleUrl to use new API

Resolves: #82406
Releases: master
Change-Id: If11c3d5289e14bc9ea766468b8e94cce95c23c71
Reviewed-on: https://review.typo3.org/53881
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Tested-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Reviewed-by: Matthias Vogel <typo3@kanti.de>
Tested-by: Matthias Vogel <typo3@kanti.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
38 files changed:
typo3/sysext/backend/Classes/Backend/ToolbarItems/ShortcutToolbarItem.php
typo3/sysext/backend/Classes/Controller/EditDocumentController.php
typo3/sysext/backend/Classes/Controller/PageLayoutController.php
typo3/sysext/backend/Classes/Http/Application.php
typo3/sysext/backend/Classes/Http/BackendModuleRequestHandler.php [deleted file]
typo3/sysext/backend/Classes/Http/RequestHandler.php
typo3/sysext/backend/Classes/Http/RouteDispatcher.php
typo3/sysext/backend/Classes/RecordList/AbstractRecordList.php
typo3/sysext/backend/Classes/Routing/UriBuilder.php
typo3/sysext/backend/Classes/Template/Components/Buttons/Action/ShortcutButton.php
typo3/sysext/backend/Classes/Template/DocumentTemplate.php
typo3/sysext/backend/Classes/Template/ModuleTemplate.php
typo3/sysext/backend/Classes/Tree/View/AbstractTreeView.php
typo3/sysext/backend/Classes/Utility/BackendUtility.php
typo3/sysext/beuser/Classes/Controller/BackendUserActionController.php
typo3/sysext/beuser/Classes/Controller/PermissionController.php
typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-82406-RoutingBackendModulesRunThroughRegularDispatcher.rst [new file with mode: 0644]
typo3/sysext/cshmanual/Classes/Controller/HelpController.php
typo3/sysext/extbase/Classes/Mvc/Web/Routing/UriBuilder.php
typo3/sysext/extbase/Tests/Unit/Mvc/Web/Routing/UriBuilderTest.php
typo3/sysext/filelist/Classes/FileList.php
typo3/sysext/fluid/Classes/ViewHelpers/Be/Buttons/ShortcutViewHelper.php
typo3/sysext/form/Classes/Controller/FormManagerController.php
typo3/sysext/info/Classes/Controller/InfoModuleController.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
typo3/sysext/lowlevel/Classes/Utility/ArrayBrowser.php
typo3/sysext/recordlist/Classes/Browser/AbstractElementBrowser.php
typo3/sysext/recordlist/Classes/Controller/AbstractLinkBrowserController.php
typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php
typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php
typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php
typo3/sysext/reports/Classes/Controller/ReportController.php
typo3/sysext/tstemplate/Classes/Controller/TypoScriptTemplateModuleController.php
typo3/sysext/viewpage/Classes/Controller/ViewModuleController.php
typo3/sysext/workspaces/Classes/Controller/PreviewController.php
typo3/sysext/workspaces/Classes/Controller/ReviewController.php
typo3/sysext/workspaces/Classes/Service/WorkspaceService.php

index c7f8b60..8a1d390 100644 (file)
@@ -307,6 +307,7 @@ class ShortcutToolbarItem implements ToolbarItemInterface
 
     /**
      * Adds the correct token, if the url is an index.php script
+     * @todo: this needs love
      *
      * @param string $url
      * @return string
@@ -320,16 +321,20 @@ class ShortcutToolbarItem implements ToolbarItemInterface
         if (isset($parameters['returnUrl'])) {
             $parsedReturnUrl = parse_url($parameters['returnUrl']);
             parse_str($parsedReturnUrl['query'], $returnUrlParameters);
-            if (strpos($parsedReturnUrl['path'], 'index.php') !== false && isset($returnUrlParameters['M'])) {
-                $module = $returnUrlParameters['M'];
+            if (strpos($parsedReturnUrl['path'], 'index.php') !== false && !empty($returnUrlParameters['route'])) {
+                $module = $returnUrlParameters['route'];
                 $returnUrl = BackendUtility::getModuleUrl($module, $returnUrlParameters);
                 $parameters['returnUrl'] = $returnUrl;
                 $url = $parsedUrl['path'] . '?' . http_build_query($parameters, '', '&', PHP_QUERY_RFC3986);
             }
         }
+        if (isset($parameters['M']) && empty($parameters['route'])) {
+            $parameters['route'] = $parameters['M'];
+            unset($parameters['M']);
+        }
 
-        if (strpos($parsedUrl['path'], 'index.php') !== false && isset($parameters['M'])) {
-            $module = $parameters['M'];
+        if (strpos($parsedUrl['path'], 'index.php') !== false && isset($parameters['route'])) {
+            $module = $parameters['route'];
             $url = BackendUtility::getModuleUrl($module, $parameters);
         } elseif (strpos($parsedUrl['path'], 'index.php') !== false && isset($parameters['route'])) {
             $routePath = $parameters['route'];
index 952a8f5..7b2502c 100644 (file)
@@ -1287,13 +1287,13 @@ class EditDocumentController extends AbstractModule
                     $returnUrl = $this->retUrl;
                     if ($this->firstEl['table'] === 'pages') {
                         parse_str((string)parse_url($returnUrl, PHP_URL_QUERY), $queryParams);
-                        if (isset($queryParams['M'])
+                        if (isset($queryParams['route'])
                             && isset($queryParams['id'])
                             && (string)$this->firstEl['uid'] === (string)$queryParams['id']
                         ) {
                             // TODO: Use the page's pid instead of 0, this requires a clean API to manipulate the page
                             // tree from the outside to be able to mark the pid as active
-                            $returnUrl = BackendUtility::getModuleUrl($queryParams['M'], ['id' => 0]);
+                            $returnUrl = BackendUtility::getModuleUrl($queryParams['route'], ['id' => 0]);
                         }
                     }
                     $deleteButton = $buttonBar->makeLinkButton()
index fdf5f59..ddfff72 100644 (file)
@@ -975,7 +975,7 @@ class PageLayoutController
             ->setModuleName($this->moduleName)
             ->setGetVariables([
                 'id',
-                'M',
+                'route',
                 'edit_record',
                 'pointer',
                 'new_unique_uid',
index 683a1b3..fe59180 100644 (file)
@@ -34,17 +34,11 @@ class Application implements ApplicationInterface
     protected $entryPointLevel = 1;
 
     /**
-     * @var \Psr\Http\Message\ServerRequestInterface
-     */
-    protected $request;
-
-    /**
      * All available request handlers that can handle backend requests (non-CLI)
      * @var array
      */
     protected $availableRequestHandlers = [
         \TYPO3\CMS\Backend\Http\RequestHandler::class,
-        \TYPO3\CMS\Backend\Http\BackendModuleRequestHandler::class,
         \TYPO3\CMS\Backend\Http\AjaxRequestHandler::class
     ];
 
@@ -81,12 +75,7 @@ class Application implements ApplicationInterface
      */
     public function run(callable $execute = null)
     {
-        $this->request = \TYPO3\CMS\Core\Http\ServerRequestFactory::fromGlobals();
-        if (isset($this->request->getQueryParams()['M'])) {
-            $this->request = $this->request->withAttribute('isModuleRequest', true);
-        }
-
-        $this->bootstrap->handleRequest($this->request);
+        $this->bootstrap->handleRequest(\TYPO3\CMS\Core\Http\ServerRequestFactory::fromGlobals());
 
         if ($execute !== null) {
             call_user_func($execute);
diff --git a/typo3/sysext/backend/Classes/Http/BackendModuleRequestHandler.php b/typo3/sysext/backend/Classes/Http/BackendModuleRequestHandler.php
deleted file mode 100644 (file)
index 38c5d07..0000000
+++ /dev/null
@@ -1,222 +0,0 @@
-<?php
-namespace TYPO3\CMS\Backend\Http;
-
-/*
- * 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\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Core\Bootstrap;
-use TYPO3\CMS\Core\Exception;
-use TYPO3\CMS\Core\FormProtection\BackendFormProtection;
-use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
-use TYPO3\CMS\Core\Http\Dispatcher;
-use TYPO3\CMS\Core\Http\RequestHandlerInterface;
-use TYPO3\CMS\Core\Http\Response;
-use TYPO3\CMS\Core\Type\Bitmask\Permission;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\MathUtility;
-
-/**
- * Handles the request for backend modules and wizards
- * Juggles with $GLOBALS['TBE_MODULES']
- */
-class BackendModuleRequestHandler implements RequestHandlerInterface
-{
-    /**
-     * @var Bootstrap
-     */
-    protected $bootstrap;
-
-    /**
-     * @var array
-     */
-    protected $moduleRegistry = [];
-
-    /**
-     * @var BackendUserAuthentication
-     */
-    protected $backendUserAuthentication;
-
-    /**
-     * Instance of the current Http Request
-     * @var ServerRequestInterface
-     */
-    protected $request;
-
-    /**
-     * Constructor handing over the bootstrap and the original request
-     *
-     * @param Bootstrap $bootstrap
-     */
-    public function __construct(Bootstrap $bootstrap)
-    {
-        $this->bootstrap = $bootstrap;
-    }
-
-    /**
-     * Handles the request, evaluating the configuration and executes the module accordingly
-     *
-     * @param ServerRequestInterface $request
-     * @return NULL|\Psr\Http\Message\ResponseInterface
-     * @throws Exception
-     */
-    public function handleRequest(ServerRequestInterface $request)
-    {
-        $this->request = $request;
-        $this->boot();
-
-        $this->moduleRegistry = $GLOBALS['TBE_MODULES'];
-
-        if (!$this->isValidModuleRequest()) {
-            throw new Exception('The CSRF protection token for the requested module is missing or invalid', 1417988921);
-        }
-
-        $this->backendUserAuthentication = $GLOBALS['BE_USER'];
-
-        $moduleName = (string)$this->request->getQueryParams()['M'];
-        return $this->dispatchModule($moduleName);
-    }
-
-    /**
-     * Execute TYPO3 bootstrap
-     */
-    protected function boot()
-    {
-        $this->bootstrap->checkLockedBackendAndRedirectOrDie()
-            ->checkBackendIpOrDie()
-            ->checkSslBackendAndRedirectIfNeeded()
-            ->initializeBackendRouter()
-            ->loadExtTables()
-            ->initializeBackendUser()
-            ->initializeBackendAuthentication()
-            ->initializeLanguageObject()
-            ->initializeBackendTemplate()
-            ->endOutputBufferingAndCleanPreviousOutput()
-            ->initializeOutputCompression()
-            ->sendHttpHeaders();
-    }
-
-    /**
-     * This request handler can handle any backend request coming from index.php
-     *
-     * @param ServerRequestInterface $request
-     * @return bool
-     */
-    public function canHandleRequest(ServerRequestInterface $request)
-    {
-        return $request->getAttribute('isModuleRequest', false);
-    }
-
-    /**
-     * Checks if all parameters are met.
-     *
-     * @return bool
-     */
-    protected function isValidModuleRequest()
-    {
-        return $this->getFormProtection() instanceof BackendFormProtection
-            && $this->getFormProtection()->validateToken((string)$this->request->getQueryParams()['moduleToken'], 'moduleCall', (string)$this->request->getQueryParams()['M']);
-    }
-
-    /**
-     * Executes the modules configured via Extbase
-     *
-     * @param string $moduleName
-     * @return Response A PSR-7 response object
-     * @throws \RuntimeException
-     */
-    protected function dispatchModule($moduleName)
-    {
-        $moduleConfiguration = $this->getModuleConfiguration($moduleName);
-
-        $response = GeneralUtility::makeInstance(Response::class);
-
-        // Check permissions and exit if the user has no permission for entry
-        $this->backendUserAuthentication->modAccess($moduleConfiguration, true);
-        $id = isset($this->request->getQueryParams()['id']) ? $this->request->getQueryParams()['id'] : $this->request->getParsedBody()['id'];
-        if ($id && MathUtility::canBeInterpretedAsInteger($id)) {
-            $permClause = $this->backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
-            // Check page access
-            $access = is_array(BackendUtility::readPageAccess((int)$id, $permClause));
-            if (!$access) {
-                // Check if page has been deleted
-                $deleteField = $GLOBALS['TCA']['pages']['ctrl']['delete'];
-                $pageInfo = BackendUtility::getRecord('pages', (int)$id, $deleteField, $permClause ? ' AND ' . $permClause : '', false);
-                if (!$pageInfo[$deleteField]) {
-                    throw new \RuntimeException('You don\'t have access to this page', 1289917924);
-                }
-            }
-        }
-
-        // Use Core Dispatching
-        if (isset($moduleConfiguration['routeTarget'])) {
-            $dispatcher = GeneralUtility::makeInstance(Dispatcher::class);
-            $this->request = $this->request->withAttribute('target', $moduleConfiguration['routeTarget']);
-            $response = $dispatcher->dispatch($this->request, $response);
-        } else {
-            // 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;
-    }
-
-    /**
-     * Returns the module configuration which is provided during module registration
-     *
-     * @param string $moduleName
-     * @return array
-     * @throws \RuntimeException
-     */
-    protected function getModuleConfiguration($moduleName)
-    {
-        if (!isset($this->moduleRegistry['_configuration'][$moduleName])) {
-            throw new \RuntimeException('Module ' . $moduleName . ' is not configured.', 1289918325);
-        }
-        return $this->moduleRegistry['_configuration'][$moduleName];
-    }
-
-    /**
-     * Returns the priority - how eager the handler is to actually handle the request.
-     *
-     * @return int The priority of the request handler.
-     */
-    public function getPriority()
-    {
-        return 90;
-    }
-
-    /**
-     * Wrapper method for static form protection utility
-     *
-     * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection
-     */
-    protected function getFormProtection()
-    {
-        return FormProtectionFactory::get();
-    }
-}
index 084e891..a24905f 100644 (file)
@@ -59,14 +59,22 @@ class RequestHandler implements RequestHandlerInterface
      */
     public function handleRequest(ServerRequestInterface $request)
     {
+        // Check if a module URL is requested and deprecate this call
+        $moduleName = $request->getQueryParams()['M'] ?? $request->getParsedBody()['M'] ?? null;
         // Allow the login page to be displayed if routing is not used and on index.php
-        $pathToRoute = (string)$request->getQueryParams()['route'] ?: '/login';
+        $pathToRoute = $request->getQueryParams()['route'] ?? $request->getParsedBody()['route'] ?? $moduleName ?? '/login';
         $request = $request->withAttribute('routePath', $pathToRoute);
 
         // skip the BE user check on the login page
         // should be handled differently in the future by checking the Bootstrap directly
         $this->boot($pathToRoute === '/login');
 
+        if ($moduleName !== null) {
+            trigger_error('Calling the TYPO3 Backend with "M" GET parameter will be removed in TYPO3 v10,'
+                . ' the calling code calls this script with "&M=' . $moduleName . '" and needs to be adapted'
+                . ' to use the TYPO3 API.', E_USER_DEPRECATED);
+        }
+
         // Check if the router has the available route and dispatch.
         try {
             return $this->dispatch($request);
index 957f85c..bfd9bc6 100644 (file)
@@ -19,9 +19,12 @@ use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Backend\Routing\Exception\InvalidRequestTokenException;
 use TYPO3\CMS\Backend\Routing\Route;
 use TYPO3\CMS\Backend\Routing\Router;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
 use TYPO3\CMS\Core\Http\Dispatcher;
 use TYPO3\CMS\Core\Http\DispatcherInterface;
+use TYPO3\CMS\Core\Http\Response;
+use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -45,10 +48,14 @@ class RouteDispatcher extends Dispatcher implements DispatcherInterface
         /** @var Route $route */
         $route = $router->matchRequest($request);
         $request = $request->withAttribute('route', $route);
+        $request = $request->withAttribute('target', $route->getOption('target'));
         if (!$this->isValidRequest($request)) {
             throw new InvalidRequestTokenException('Invalid request for route "' . $route->getPath() . '"', 1425389455);
         }
 
+        if ($route->getOption('module')) {
+            return $this->dispatchModule($request, $response);
+        }
         $targetIdentifier = $route->getOption('target');
         $target = $this->getCallableFromTarget($targetIdentifier);
         return call_user_func_array($target, [$request, $response]);
@@ -82,4 +89,84 @@ class RouteDispatcher extends Dispatcher implements DispatcherInterface
         $token = (string)(isset($request->getParsedBody()['token']) ? $request->getParsedBody()['token'] : $request->getQueryParams()['token']);
         return $this->getFormProtection()->validateToken($token, 'route', $route->getOption('_identifier'));
     }
+
+    /**
+     * Executes the modules configured via Extbase
+     *
+     * @param ServerRequestInterface $request
+     * @param ResponseInterface $response
+     * @return ResponseInterface A PSR-7 response object
+     * @throws \RuntimeException
+     */
+    protected function dispatchModule(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
+    {
+        $route = $request->getAttribute('route');
+        $moduleName = $route->getOption('moduleName');
+        $moduleConfiguration = $this->getModuleConfiguration($moduleName);
+
+        $backendUserAuthentication = $GLOBALS['BE_USER'];
+
+        // Check permissions and exit if the user has no permission for entry
+        // @todo please do not use "true" here, what a bad coding paradigm
+        $backendUserAuthentication->modAccess($moduleConfiguration, true);
+        $id = (int)$request->getQueryParams()['id'] ?? $request->getParsedBody()['id'];
+        if ($id) {
+            $permClause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
+            // Check page access
+            if (!is_array(BackendUtility::readPageAccess($id, $permClause))) {
+                // Check if page has been deleted
+                $deleteField = $GLOBALS['TCA']['pages']['ctrl']['delete'];
+                $pageInfo = BackendUtility::getRecord('pages', $id, $deleteField, $permClause ? ' AND ' . $permClause : '', false);
+                if (!$pageInfo[$deleteField]) {
+                    throw new \RuntimeException('You don\'t have access to this page', 1289917924);
+                }
+            }
+        }
+
+        // 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;
+    }
+
+    /**
+     * Returns the module configuration which is provided during module registration
+     *
+     * @param string $moduleName
+     * @return array
+     * @throws \RuntimeException
+     */
+    protected function getModuleConfiguration($moduleName)
+    {
+        if (!isset($GLOBALS['TBE_MODULES']['_configuration'][$moduleName])) {
+            throw new \RuntimeException('Module ' . $moduleName . ' is not configured.', 1289918325);
+        }
+        return $GLOBALS['TBE_MODULES']['_configuration'][$moduleName];
+    }
 }
index 21d9a61..2bb2b17 100644 (file)
@@ -15,9 +15,7 @@ namespace TYPO3\CMS\Backend\RecordList;
  */
 
 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
-use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
@@ -215,12 +213,8 @@ abstract class AbstractRecordList
     protected function determineScriptUrl()
     {
         if ($routePath = GeneralUtility::_GP('route')) {
-            $router = GeneralUtility::makeInstance(Router::class);
-            $route = $router->match($routePath);
             $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
-        } elseif ($moduleName = GeneralUtility::_GP('M')) {
-            $this->thisScript = BackendUtility::getModuleUrl($moduleName);
+            $this->thisScript = (string)$uriBuilder->buildUriFromRoutePath($routePath);
         } else {
             $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
         }
index 93e5fad..f2a9d31 100644 (file)
@@ -55,6 +55,25 @@ class UriBuilder
     }
 
     /**
+     * Generates a URL or path for a specific route based on the given rout.
+     * Currently used to link to the current script, it is encouraged to use "buildUriFromRoute" if possible.
+     *
+     * If there is no route with the given name, the generator throws the RouteNotFoundException.
+     *
+     * @param string $pathInfo The path to the route
+     * @param array $parameters An array of parameters
+     * @param string $referenceType The type of reference to be generated (one of the constants)
+     * @return Uri The generated Uri
+     * @throws RouteNotFoundException If the named route doesn't exist
+     */
+    public function buildUriFromRoutePath($pathInfo, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
+    {
+        $router = GeneralUtility::makeInstance(Router::class);
+        $route = $router->match($pathInfo);
+        return $this->buildUriFromRoute($route->getOption('_identifier'), $parameters, $referenceType);
+    }
+
+    /**
      * Generates a URL or path for a specific route based on the given parameters.
      * When the route is configured with "access=public" then the token generation is left out.
      *
@@ -106,8 +125,8 @@ class UriBuilder
     public function buildUriFromModule($moduleName, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
     {
         $parameters = [
-            'M' => $moduleName,
-            'moduleToken' => FormProtectionFactory::get('backend')->generateToken('moduleCall', $moduleName)
+            'route' => $moduleName,
+            'token' => FormProtectionFactory::get('backend')->generateToken('route', $moduleName)
         ] + $parameters;
         return $this->buildUri($parameters, $referenceType);
     }
index 14db2a1..b3b8150 100644 (file)
@@ -239,7 +239,7 @@ class ShortcutButton implements ButtonInterface, PositionInterface
 
         // Set default GET parameters
         if ($emptyGetVariables) {
-            $this->getVariables = ['id', 'M'];
+            $this->getVariables = ['id', 'route'];
         }
 
         // Automatically determine module name in Extbase context
index 02365ea..446bf20 100644 (file)
@@ -251,7 +251,7 @@ function jumpToUrl(URL) {
         $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
 
         // Setting default scriptID, trim forward slash from route
-        $this->scriptID = GeneralUtility::_GET('M') !== null ? GeneralUtility::_GET('M') : ltrim(GeneralUtility::_GET('route'), '/');
+        $this->scriptID = ltrim(GeneralUtility::_GET('route'), '/');
         $this->bodyTagId = preg_replace('/[^A-Za-z0-9-]/', '-', $this->scriptID);
         // Individual configuration per script? If so, make a recursive merge of the arrays:
         if (is_array($GLOBALS['TBE_STYLES']['scriptIDindex'][$this->scriptID])) {
index f8b6622..aa55258 100644 (file)
@@ -510,8 +510,9 @@ class ModuleTemplate
         // since this is used for icons.
         $moduleName = $modName === 'xMOD_alt_doc.php' ? 'record_edit' : $modName;
         // Add the module identifier automatically if typo3/index.php is used:
-        if (GeneralUtility::_GET('M') !== null) {
-            $storeUrl = '&M=' . $moduleName . $storeUrl;
+        // @todo: routing
+        if (GeneralUtility::_GET('route') !== null) {
+            $storeUrl = '&route=' . $moduleName . $storeUrl;
         }
         if ((int)$motherModName === 1) {
             $motherModule = 'top.currentModuleLoaded';
index 2564e31..13dab28 100644 (file)
@@ -14,7 +14,6 @@ namespace TYPO3\CMS\Backend\Tree\View;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Tree\Pagetree\Commands;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
@@ -289,12 +288,8 @@ abstract class AbstractTreeView
     protected function determineScriptUrl()
     {
         if ($routePath = GeneralUtility::_GP('route')) {
-            $router = GeneralUtility::makeInstance(Router::class);
-            $route = $router->match($routePath);
             $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
-        } elseif ($moduleName = GeneralUtility::_GP('M')) {
-            $this->thisScript = BackendUtility::getModuleUrl($moduleName);
+            $this->thisScript = (string)$uriBuilder->buildUriFromRoutePath($routePath);
         } else {
             $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
         }
index 103a2fe..46af96a 100644 (file)
@@ -2950,14 +2950,10 @@ class BackendUtility
             $script = basename(PATH_thisScript);
         }
 
-        if (GeneralUtility::_GP('route')) {
-            $router = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\Router::class);
-            $route = $router->match(GeneralUtility::_GP('route'));
+        if ($routePath = GeneralUtility::_GP('route')) {
             $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
-            $scriptUrl = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
+            $scriptUrl = (string)$uriBuilder->buildUriFromRoutePath($routePath, $mainParams);
             $scriptUrl .= $addParams;
-        } elseif ($script === 'index.php' && GeneralUtility::_GET('M')) {
-            $scriptUrl = self::getModuleUrl(GeneralUtility::_GET('M'), $mainParams) . $addParams;
         } else {
             $scriptUrl = $script . '?' . GeneralUtility::implodeArrayForUrl('', $mainParams) . $addParams;
         }
@@ -3169,8 +3165,7 @@ class BackendUtility
         try {
             $uri = $uriBuilder->buildUriFromRoute($moduleName, $urlParameters);
         } catch (\TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException $e) {
-            // no route registered, use the fallback logic to check for a module
-            $uri = $uriBuilder->buildUriFromModule($moduleName, $urlParameters);
+            $uri = $uriBuilder->buildUriFromRoutePath($moduleName, $urlParameters);
         }
         return (string)$uri;
     }
index fe9bce3..61a1a70 100644 (file)
@@ -125,7 +125,7 @@ class BackendUserActionController extends ActionController
         $extensionName = $currentRequest->getControllerExtensionName();
         if (count($getVars) === 0) {
             $modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
-            $getVars = ['id', 'M', $modulePrefix];
+            $getVars = ['id', 'route', $modulePrefix];
         }
         $shortcutName = $this->getLanguageService()->sL('LLL:EXT:beuser/Resources/Private/Language/locallang.xml:backendUsers');
         if ($this->request->getControllerName() === 'BackendUser') {
index 81155f0..072dd3b 100644 (file)
@@ -158,7 +158,7 @@ class PermissionController extends ActionController
         $extensionName = $currentRequest->getControllerExtensionName();
         if (empty($getVars)) {
             $modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
-            $getVars = ['id', 'M', $modulePrefix];
+            $getVars = ['id', 'route', $modulePrefix];
         }
 
         if ($currentRequest->getControllerActionName() === 'edit') {
index c0f3b75..292524f 100644 (file)
@@ -858,9 +858,8 @@ class ExtensionManagementUtility
         }
 
         // add additional configuration
+        $fullModuleSignature = $main . ($sub ? '_' . $sub : '');
         if (is_array($moduleConfiguration) && !empty($moduleConfiguration)) {
-            $fullModuleSignature = $main . ($sub ? '_' . $sub : '');
-
             if (!empty($moduleConfiguration['icon'])) {
                 $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
                 $iconIdentifier = 'module-' . $fullModuleSignature;
@@ -876,6 +875,32 @@ class ExtensionManagementUtility
 
             $GLOBALS['TBE_MODULES']['_configuration'][$fullModuleSignature] = $moduleConfiguration;
         }
+
+        // Also register the module as regular route
+        // Build Route objects from the data
+        $name = $fullModuleSignature;
+        if (isset($moduleConfiguration['path'])) {
+            $path = $moduleConfiguration['path'];
+        } else {
+            $path = str_replace('_', '/', $name);
+        }
+        $path = '/' . trim($path, '/') . '/';
+
+        $options = [
+            'module' => true,
+            'moduleName' => $fullModuleSignature,
+            'access' => $moduleConfiguration['access'] ?: 'user,group'
+        ];
+        if ($moduleConfiguration['routeTarget']) {
+            $options['target'] = $moduleConfiguration['routeTarget'];
+        }
+
+        $router = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\Router::class);
+        $router->addRoute(
+            $name,
+            // @todo: see if we should do a "module route"
+            GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\Route::class, $path, $options)
+        );
     }
 
     /**
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-82406-RoutingBackendModulesRunThroughRegularDispatcher.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-82406-RoutingBackendModulesRunThroughRegularDispatcher.rst
new file mode 100644 (file)
index 0000000..fd79b8e
--- /dev/null
@@ -0,0 +1,49 @@
+.. include:: ../../Includes.txt
+
+==========================================================================
+Breaking: #82406 - Routing: Backend Modules run through regular dispatcher
+==========================================================================
+
+See :issue:`82406`
+
+Description
+===========
+
+Calling Backend modules was previously handled via a special `BackendModuleRequestHandler` which has
+been removed.
+
+When registering a Backend module, a route with the name of the module is automatically added to the
+Backend Router.
+
+When generating URLs for modules, the module is not added via the GET Parameter `&M=moduleName`
+anymore, but built like any other Backend Route (currently with the "route" and "token" parameters)
+
+All request handling functionality is now done by the regular Backend RequestHandler,
+which checks if the Route to be targeted is a module, and does extra module permission checks.
+
+
+Impact
+======
+
+Handling with the "&M" GET parameter in backend modules won't deliver the correct result anymore.
+
+Instantiating `BackendModuleRequestHandler` will result in a fatal PHP error.
+
+
+Affected Installations
+======================
+
+Installations with custom extensions including backend modules which work directly with the GET
+parameter "M".
+
+
+Migration
+=========
+
+If extensions use API methods like ``BackendUtility::getModuleUrl()`` are used, nothing needs to be
+modified.
+
+If a backend module is using the GET parameter "M" currently, the code needs to be adjusted to the
+GET "route" or use the UriBuilder directly.
+
+.. index:: Backend, PartiallyScanned
index e623c99..06e2577 100644 (file)
@@ -151,7 +151,7 @@ class HelpController extends ActionController
             $extensionName = $currentRequest->getControllerExtensionName();
             if (count($getVars) === 0) {
                 $modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
-                $getVars = ['id', 'M', $modulePrefix];
+                $getVars = ['id', 'route', $modulePrefix];
             }
             $shortcutButton = $buttonBar->makeShortcutButton()
                 ->setModuleName($moduleName)
index f27a506..1696441 100644 (file)
@@ -664,24 +664,32 @@ class UriBuilder
             }
         } else {
             $id = GeneralUtility::_GP('id');
-            $module = GeneralUtility::_GP('M');
+            $module = GeneralUtility::_GP('route');
             if ($id !== null) {
                 $arguments['id'] = $id;
             }
             if ($module !== null) {
-                $arguments['M'] = $module;
+                $arguments['route'] = $module;
             }
         }
         ArrayUtility::mergeRecursiveWithOverrule($arguments, $this->arguments);
         $arguments = $this->convertDomainObjectsToIdentityArrays($arguments);
         $this->lastArguments = $arguments;
-        $moduleName = $arguments['M'];
-        unset($arguments['M'], $arguments['moduleToken']);
+        $moduleName = $arguments['route'] ?? null;
+        unset($arguments['route'], $arguments['token']);
         $backendUriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
-        if ($this->request instanceof WebRequest && $this->createAbsoluteUri) {
-            $uri = (string)$backendUriBuilder->buildUriFromModule($moduleName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_URL);
+        if (!empty($moduleName)) {
+            if ($this->request instanceof WebRequest && $this->createAbsoluteUri) {
+                $uri = (string)$backendUriBuilder->buildUriFromRoutePath($moduleName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_URL);
+            } else {
+                $uri = (string)$backendUriBuilder->buildUriFromRoutePath($moduleName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_PATH);
+            }
         } else {
-            $uri = (string)$backendUriBuilder->buildUriFromModule($moduleName, $arguments);
+            if ($this->request instanceof WebRequest && $this->createAbsoluteUri) {
+                $uri = (string)$backendUriBuilder->buildUriFromModule($moduleName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_URL);
+            } else {
+                $uri = (string)$backendUriBuilder->buildUriFromModule($moduleName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_PATH);
+            }
         }
         if ($this->section !== '') {
             $uri .= '#' . $this->section;
index a86b73b..063f272 100644 (file)
@@ -13,6 +13,8 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Mvc\Web\Routing;
  *
  * The TYPO3 project - inspiring people to share!
  */
+use TYPO3\CMS\Backend\Routing\Route;
+use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
@@ -76,6 +78,9 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $this->uriBuilder->_set('configurationManager', $this->mockConfigurationManager);
         $this->uriBuilder->_set('extensionService', $this->mockExtensionService);
         $this->uriBuilder->_set('environmentService', $this->createMock(EnvironmentService::class));
+        $router = GeneralUtility::makeInstance(Router::class);
+        $router->addRoute('module_key', new Route('/test/Path', []));
+        $router->addRoute('module_key2', new Route('/test/Path2', []));
         // Mocking backend user is required for backend URI generation as BackendUtility::getModuleUrl() is called
         $backendUserMock = $this->createMock(BackendUserAuthentication::class);
         $backendUserMock->expects($this->any())->method('check')->will($this->returnValue(true));
@@ -207,12 +212,12 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
      */
     public function buildBackendUriKeepsQueryParametersIfAddQueryStringIsSet()
     {
-        GeneralUtility::_GETset(['M' => 'moduleKey', 'id' => 'pageId', 'foo' => 'bar']);
+        GeneralUtility::_GETset(['route' => '/test/Path', 'id' => 'pageId', 'foo' => 'bar']);
         $_POST = [];
         $_POST['foo2'] = 'bar2';
         $this->uriBuilder->setAddQueryString(true);
         $this->uriBuilder->setAddQueryStringMethod('GET,POST');
-        $expectedResult = '/typo3/index.php?M=moduleKey&moduleToken=dummyToken&id=pageId&foo=bar&foo2=bar2';
+        $expectedResult = '/typo3/index.php?route=%2Ftest%2FPath&token=dummyToken&id=pageId&foo=bar&foo2=bar2';
         $actualResult = $this->uriBuilder->buildBackendUri();
         $this->assertEquals($expectedResult, $actualResult);
     }
@@ -222,12 +227,12 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
      */
     public function buildBackendUriKeepsQueryParametersIfAddQueryStringMethodIsNotSet()
     {
-        GeneralUtility::_GETset(['M' => 'moduleKey', 'id' => 'pageId', 'foo' => 'bar']);
+        GeneralUtility::_GETset(['route' => '/test/Path', 'id' => 'pageId', 'foo' => 'bar']);
         $_POST = [];
         $_POST['foo2'] = 'bar2';
         $this->uriBuilder->setAddQueryString(true);
         $this->uriBuilder->setAddQueryStringMethod(null);
-        $expectedResult = '/typo3/index.php?M=moduleKey&moduleToken=dummyToken&id=pageId&foo=bar';
+        $expectedResult = '/typo3/index.php?route=%2Ftest%2FPath&token=dummyToken&id=pageId&foo=bar';
         $actualResult = $this->uriBuilder->buildBackendUri();
         $this->assertEquals($expectedResult, $actualResult);
     }
@@ -240,7 +245,7 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         return [
             'Arguments to be excluded in the beginning' => [
                 [
-                    'M' => 'moduleKey',
+                    'route' => '/test/Path',
                     'id' => 'pageId',
                     'foo' => 'bar'
                 ],
@@ -248,25 +253,25 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
                     'foo2' => 'bar2'
                 ],
                 [
-                    'M',
+                    'route',
                     'id'
                 ],
-                '/typo3/index.php?M=&moduleToken=dummyToken&foo=bar&foo2=bar2'
+                '/typo3/index.php?route=&token=dummyToken&foo=bar&foo2=bar2'
             ],
             'Arguments to be excluded in the end' => [
                 [
                     'foo' => 'bar',
                     'id' => 'pageId',
-                    'M' => 'moduleKey'
+                    'route' => '/test/Path'
                 ],
                 [
                     'foo2' => 'bar2'
                 ],
                 [
-                    'M',
+                    'route',
                     'id'
                 ],
-                '/typo3/index.php?M=&moduleToken=dummyToken&foo=bar&foo2=bar2'
+                '/typo3/index.php?route=&token=dummyToken&foo=bar&foo2=bar2'
             ],
             'Arguments in nested array to be excluded' => [
                 [
@@ -274,7 +279,7 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
                         'bar' => 'baz'
                     ],
                     'id' => 'pageId',
-                    'M' => 'moduleKey'
+                    'route' => '/test/Path'
                 ],
                 [
                     'foo2' => 'bar2'
@@ -283,7 +288,7 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
                     'id',
                     'tx_foo[bar]'
                 ],
-                '/typo3/index.php?M=moduleKey&moduleToken=dummyToken&foo2=bar2'
+                '/typo3/index.php?route=%2Ftest%2FPath&token=dummyToken&foo2=bar2'
             ],
             'Arguments in multidimensional array to be excluded' => [
                 [
@@ -293,7 +298,7 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
                         ]
                     ],
                     'id' => 'pageId',
-                    'M' => 'moduleKey'
+                    'route' => '/test/Path'
                 ],
                 [
                     'foo2' => 'bar2'
@@ -302,7 +307,7 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
                     'id',
                     'tx_foo[bar][baz]'
                 ],
-                '/typo3/index.php?M=moduleKey&moduleToken=dummyToken&foo2=bar2'
+                '/typo3/index.php?route=%2Ftest%2FPath&token=dummyToken&foo2=bar2'
             ],
         ];
     }
@@ -331,8 +336,8 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
      */
     public function buildBackendUriKeepsModuleQueryParametersIfAddQueryStringIsNotSet()
     {
-        GeneralUtility::_GETset(['M' => 'moduleKey', 'id' => 'pageId', 'foo' => 'bar']);
-        $expectedResult = '/typo3/index.php?M=moduleKey&moduleToken=dummyToken&id=pageId';
+        GeneralUtility::_GETset(['route' => '/test/Path', 'id' => 'pageId', 'foo' => 'bar']);
+        $expectedResult = '/typo3/index.php?route=%2Ftest%2FPath&token=dummyToken&id=pageId';
         $actualResult = $this->uriBuilder->buildBackendUri();
         $this->assertEquals($expectedResult, $actualResult);
     }
@@ -342,9 +347,9 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
      */
     public function buildBackendUriMergesAndOverrulesQueryParametersWithArguments()
     {
-        GeneralUtility::_GETset(['M' => 'moduleKey', 'id' => 'pageId', 'foo' => 'bar']);
-        $this->uriBuilder->setArguments(['M' => 'overwrittenModuleKey', 'somePrefix' => ['bar' => 'baz']]);
-        $expectedResult = '/typo3/index.php?M=overwrittenModuleKey&moduleToken=dummyToken&id=pageId&somePrefix%5Bbar%5D=baz';
+        GeneralUtility::_GETset(['route' => '/test/Path', 'id' => 'pageId', 'foo' => 'bar']);
+        $this->uriBuilder->setArguments(['route' => '/test/Path2', 'somePrefix' => ['bar' => 'baz']]);
+        $expectedResult = '/typo3/index.php?route=%2Ftest%2FPath2&token=dummyToken&id=pageId&somePrefix%5Bbar%5D=baz';
         $actualResult = $this->uriBuilder->buildBackendUri();
         $this->assertEquals($expectedResult, $actualResult);
     }
@@ -354,11 +359,11 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
      */
     public function buildBackendUriConvertsDomainObjectsAfterArgumentsHaveBeenMerged()
     {
-        GeneralUtility::_GETset(['M' => 'moduleKey']);
+        GeneralUtility::_GETset(['route' => '/test/Path']);
         $mockDomainObject = $this->getAccessibleMock(AbstractEntity::class, ['dummy']);
         $mockDomainObject->_set('uid', '123');
         $this->uriBuilder->setArguments(['somePrefix' => ['someDomainObject' => $mockDomainObject]]);
-        $expectedResult = '/typo3/index.php?M=moduleKey&moduleToken=dummyToken&somePrefix%5BsomeDomainObject%5D=123';
+        $expectedResult = '/typo3/index.php?route=%2Ftest%2FPath&token=dummyToken&somePrefix%5BsomeDomainObject%5D=123';
         $actualResult = $this->uriBuilder->buildBackendUri();
         $this->assertEquals($expectedResult, $actualResult);
     }
@@ -368,9 +373,9 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
      */
     public function buildBackendUriRespectsSection()
     {
-        GeneralUtility::_GETset(['M' => 'moduleKey']);
+        GeneralUtility::_GETset(['route' => '/test/Path']);
         $this->uriBuilder->setSection('someSection');
-        $expectedResult = '/typo3/index.php?M=moduleKey&moduleToken=dummyToken#someSection';
+        $expectedResult = '/typo3/index.php?route=%2Ftest%2FPath&token=dummyToken#someSection';
         $actualResult = $this->uriBuilder->buildBackendUri();
         $this->assertEquals($expectedResult, $actualResult);
     }
@@ -381,12 +386,12 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
     public function buildBackendUriCreatesAbsoluteUrisIfSpecified()
     {
         GeneralUtility::flushInternalRuntimeCaches();
-        GeneralUtility::_GETset(['M' => 'moduleKey']);
+        GeneralUtility::_GETset(['route' => '/test/Path']);
         $_SERVER['HTTP_HOST'] = 'baseuri';
         $_SERVER['SCRIPT_NAME'] = '/typo3/index.php';
         $this->mockRequest->expects($this->any())->method('getBaseUri')->will($this->returnValue('http://baseuri'));
         $this->uriBuilder->setCreateAbsoluteUri(true);
-        $expectedResult = 'http://baseuri/' . TYPO3_mainDir . 'index.php?M=moduleKey&moduleToken=dummyToken';
+        $expectedResult = 'http://baseuri/' . TYPO3_mainDir . 'index.php?route=%2Ftest%2FPath&token=dummyToken';
         $actualResult = $this->uriBuilder->buildBackendUri();
         $this->assertSame($expectedResult, $actualResult);
     }
@@ -419,7 +424,7 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         ];
         $this->uriBuilder->setAddQueryString(true);
         $this->uriBuilder->setAddQueryStringMethod('POST,GET');
-        $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('/typo3/index.php?M=&moduleToken=dummyToken&key1=POST1&key2=GET2&key3[key31]=POST31&key3[key32]=GET32&key3[key33][key331]=GET331&key3[key33][key332]=POST332');
+        $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('/typo3/index.php?route=&token=dummyToken&key1=POST1&key2=GET2&key3[key31]=POST31&key3[key32]=GET32&key3[key33][key331]=GET331&key3[key33][key332]=POST332');
         $actualResult = $this->uriBuilder->buildBackendUri();
         $this->assertEquals($expectedResult, $actualResult);
     }
@@ -452,7 +457,7 @@ class UriBuilderTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         ];
         $this->uriBuilder->setAddQueryString(true);
         $this->uriBuilder->setAddQueryStringMethod('GET,POST');
-        $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('/typo3/index.php?M=&moduleToken=dummyToken&key1=GET1&key2=POST2&key3[key31]=GET31&key3[key32]=POST32&key3[key33][key331]=POST331&key3[key33][key332]=GET332');
+        $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('/typo3/index.php?route=&token=dummyToken&key1=GET1&key2=POST2&key3[key31]=GET31&key3[key32]=POST32&key3[key33][key331]=POST331&key3[key33][key332]=GET332');
         $actualResult = $this->uriBuilder->buildBackendUri();
         $this->assertEquals($expectedResult, $actualResult);
     }
index fe821d9..207bd6b 100644 (file)
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Filelist;
 
 use TYPO3\CMS\Backend\Clipboard\Clipboard;
 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
-use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -1489,12 +1488,8 @@ class FileList
     protected function determineScriptUrl()
     {
         if ($routePath = GeneralUtility::_GP('route')) {
-            $router = GeneralUtility::makeInstance(Router::class);
-            $route = $router->match($routePath);
             $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
-        } elseif ($moduleName = GeneralUtility::_GP('M')) {
-            $this->thisScript = BackendUtility::getModuleUrl($moduleName);
+            $this->thisScript = (string)$uriBuilder->buildUriFromRoutePath($routePath);
         } else {
             $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
         }
index 9e838b9..12ba447 100644 (file)
@@ -34,7 +34,7 @@ use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
  * </output>
  *
  * <code title="Explicitly set parameters to be stored in the shortcut">
- * <f:be.buttons.shortcut getVars="{0: 'M', 1: 'myOwnPrefix'}" setVars="{0: 'function'}" />
+ * <f:be.buttons.shortcut getVars="{0: 'route', 1: 'myOwnPrefix'}" setVars="{0: 'function'}" />
  * </code>
  * <output>
  * Shortcut button as known from the TYPO3 backend.
@@ -99,7 +99,7 @@ class ShortcutViewHelper extends AbstractBackendViewHelper
             $moduleName = $currentRequest->getPluginName();
             if (count($getVars) === 0) {
                 $modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
-                $getVars = ['id', 'M', $modulePrefix];
+                $getVars = ['id', 'route', $modulePrefix];
             }
             $getList = implode(',', $getVars);
             $setList = implode(',', $setVars);
index 353eb05..80b473f 100644 (file)
@@ -427,7 +427,7 @@ class FormManagerController extends AbstractBackendController
             $extensionName = $currentRequest->getControllerExtensionName();
             if (count($getVars) === 0) {
                 $modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
-                $getVars = ['id', 'M', $modulePrefix];
+                $getVars = ['id', 'route', $modulePrefix];
             }
 
             $shortcutButton = $buttonBar->makeShortcutButton()
index 309fe69..24426a3 100644 (file)
@@ -172,7 +172,7 @@ class InfoModuleController extends BaseScriptClass
             ->setModuleName($this->moduleName)
             ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']])
             ->setGetVariables([
-                'M',
+                'route',
                 'id',
                 'edit_record',
                 'pointer',
index ba0746a..3f2af70 100644 (file)
@@ -379,6 +379,11 @@ return [
             'Breaking-55298-DecoupledHistoryFunctionality.rst',
         ],
     ],
+    'TYPO3\CMS\Backend\Http\BackendModuleRequestHandler' => [
+        'restFiles' => [
+            'Breaking-82406-RoutingBackendModulesRunThroughRegularDispatcher.rst',
+        ],
+    ],
 
     // Removed interfaces
     'TYPO3\CMS\Backend\Form\DatabaseFileIconsHookInterface' => [
index 01700ab..a705105 100644 (file)
@@ -109,7 +109,7 @@ class ArrayBrowser
             $output .= '<li' . ($isResult ? ' class="active"' : '') . '>';
             if ($isArray && !$this->expAll) {
                 $goto = 'a' . substr(md5($depth), 0, 6);
-                $output .= '<a class="list-tree-control' . ($isExpanded ? ' list-tree-control-open' : ' list-tree-control-closed') . '" id="' . $goto . '" href="' . htmlspecialchars((BackendUtility::getModuleUrl(GeneralUtility::_GP('M')) . '&node[' . $depth . ']=' . ($isExpanded ? 0 : 1) . '#' . $goto)) . '"><i class="fa"></i></a> ';
+                $output .= '<a class="list-tree-control' . ($isExpanded ? ' list-tree-control-open' : ' list-tree-control-closed') . '" id="' . $goto . '" href="' . htmlspecialchars((BackendUtility::getModuleUrl(GeneralUtility::_GP('route')) . '&node[' . $depth . ']=' . ($isExpanded ? 0 : 1) . '#' . $goto)) . '"><i class="fa"></i></a> ';
             }
             $output .= '<span class="list-tree-group">';
             $output .= $this->wrapArrayKey($key, $depth, !$isArray ? $value : '');
@@ -149,7 +149,7 @@ class ArrayBrowser
                 . (!MathUtility::canBeInterpretedAsInteger($theValue) ? '\''
                 . addslashes($theValue) . '\'' : $theValue) . '; ';
             $label = '<a class="list-tree-label" href="'
-                . htmlspecialchars((BackendUtility::getModuleUrl(GeneralUtility::_GP('M'))
+                . htmlspecialchars((BackendUtility::getModuleUrl(GeneralUtility::_GP('route'))
                 . '&varname=' . urlencode($variableName)))
                 . '#varname">' . $label . '</a>';
         }
index f1b277f..2e819da 100644 (file)
@@ -14,10 +14,8 @@ namespace TYPO3\CMS\Recordlist\Browser;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Template\DocumentTemplate;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
@@ -106,12 +104,8 @@ abstract class AbstractElementBrowser
     protected function determineScriptUrl()
     {
         if ($routePath = GeneralUtility::_GP('route')) {
-            $router = GeneralUtility::makeInstance(Router::class);
-            $route = $router->match($routePath);
             $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
-        } elseif ($moduleName = GeneralUtility::_GP('M')) {
-            $this->thisScript = BackendUtility::getModuleUrl($moduleName);
+            $this->thisScript = (string)$uriBuilder->buildUriFromRoutePath($routePath);
         } else {
             $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
         }
index c1dbc26..fe40625 100644 (file)
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Recordlist\Controller;
 
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Template\DocumentTemplate;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
@@ -209,12 +208,8 @@ abstract class AbstractLinkBrowserController
     protected function determineScriptUrl(ServerRequestInterface $request)
     {
         if ($routePath = $request->getQueryParams()['route']) {
-            $router = GeneralUtility::makeInstance(Router::class);
-            $route = $router->match($routePath);
             $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
-        } elseif ($moduleName = $request->getQueryParams()['M']) {
-            $this->thisScript = BackendUtility::getModuleUrl($moduleName);
+            $this->thisScript = (string)$uriBuilder->buildUriFromRoutePath($routePath);
         } else {
             $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
         }
index 1b3b1dd..672ff22 100644 (file)
@@ -15,7 +15,6 @@ namespace TYPO3\CMS\Recordlist\RecordList;
  */
 
 use TYPO3\CMS\Backend\RecordList\AbstractRecordList;
-use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Tree\View\PageTreeView;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
@@ -1189,12 +1188,8 @@ class AbstractDatabaseRecordList extends AbstractRecordList
         $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters);
 
         if ($routePath = GeneralUtility::_GP('route')) {
-            $router = GeneralUtility::makeInstance(Router::class);
-            $route = $router->match($routePath);
             $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            $url = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'), $urlParameters);
-        } elseif ($moduleName = GeneralUtility::_GP('M')) {
-            $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
+            $url = (string)$uriBuilder->buildUriFromRoutePath($routePath, $urlParameters);
         } else {
             $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&');
         }
index 4da1c9e..bd6d78f 100644 (file)
@@ -17,7 +17,6 @@ namespace TYPO3\CMS\Recordlist\RecordList;
 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
 use TYPO3\CMS\Backend\Module\BaseScriptClass;
 use TYPO3\CMS\Backend\RecordList\RecordListGetTableHookInterface;
-use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
 use TYPO3\CMS\Backend\Template\DocumentTemplate;
@@ -869,7 +868,7 @@ class DatabaseRecordList
                     ->setModuleName('web_list')
                     ->setGetVariables([
                         'id',
-                        'M',
+                        'route',
                         'imagemode',
                         'pointer',
                         'table',
@@ -3580,12 +3579,8 @@ class DatabaseRecordList
         $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters);
 
         if ($routePath = GeneralUtility::_GP('route')) {
-            $router = GeneralUtility::makeInstance(Router::class);
-            $route = $router->match($routePath);
             $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            $url = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'), $urlParameters);
-        } elseif ($moduleName = GeneralUtility::_GP('M')) {
-            $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
+            $url = (string)$uriBuilder->buildUriFromRoutePath($routePath, $urlParameters);
         } else {
             $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim(
                     GeneralUtility::implodeArrayForUrl('', $urlParameters),
@@ -4152,12 +4147,8 @@ class DatabaseRecordList
     protected function determineScriptUrl()
     {
         if ($routePath = GeneralUtility::_GP('route')) {
-            $router = GeneralUtility::makeInstance(Router::class);
-            $route = $router->match($routePath);
             $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-            $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
-        } elseif ($moduleName = GeneralUtility::_GP('M')) {
-            $this->thisScript = BackendUtility::getModuleUrl($moduleName);
+            $this->thisScript = (string)$uriBuilder->buildUriFromRoutePath($routePath);
         } else {
             $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
         }
index f6e98fc..a60914e 100644 (file)
@@ -148,7 +148,7 @@ class RecyclerModuleController extends ActionController
         $extensionName = $currentRequest->getControllerExtensionName();
         if (count($getVars) === 0) {
             $modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
-            $getVars = ['id', 'M', $modulePrefix];
+            $getVars = ['id', 'route', $modulePrefix];
         }
         $shortcutButton = $buttonBar->makeShortcutButton()
             ->setModuleName($moduleName)
index 36f1cf8..6a20542 100644 (file)
@@ -172,7 +172,7 @@ class ReportController extends ActionController
         $setVars = $this->request->hasArgument('setVars') ? $this->request->getArgument('setVars') : [];
         if (count($getVars) === 0) {
             $modulePrefix = strtolower('tx_' . $this->request->getControllerExtensionName() . '_' . $moduleName);
-            $getVars = ['id', 'M', $modulePrefix];
+            $getVars = ['id', 'route', $modulePrefix];
         }
         $shortcutButton = $buttonBar->makeShortcutButton()
             ->setModuleName($moduleName)
index 7b669e0..d3532ba 100644 (file)
@@ -362,7 +362,7 @@ class TypoScriptTemplateModuleController extends BaseScriptClass
         // Shortcut
         $shortcutButton = $buttonBar->makeShortcutButton()
             ->setModuleName($this->MCONF['name'])
-            ->setGetVariables(['id', 'M']);
+            ->setGetVariables(['id', 'route']);
         $buttonBar->addButton($shortcutButton);
     }
 
index 8c2725e..de87dd2 100644 (file)
@@ -103,7 +103,7 @@ class ViewModuleController extends ActionController
         $extensionName = $currentRequest->getControllerExtensionName();
         if (count($getVars) === 0) {
             $modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
-            $getVars = ['id', 'M', $modulePrefix];
+            $getVars = ['id', 'route', $modulePrefix];
         }
         $shortcutButton = $buttonBar->makeShortcutButton()
             ->setModuleName($moduleName)
index 079b4a8..7945dcc 100644 (file)
@@ -87,7 +87,7 @@ class PreviewController extends AbstractController
 
         // Remove the GET parameters related to the workspaces module and the page id
         unset($queryParameters['tx_workspaces_web_workspacesworkspaces']);
-        unset($queryParameters['M']);
+        unset($queryParameters['route']);
         unset($queryParameters['id']);
 
         // Assemble a query string from the retrieved parameters
index 22279a1..6c21f5a 100644 (file)
@@ -50,7 +50,7 @@ class ReviewController extends AbstractController
         $extensionName = $currentRequest->getControllerExtensionName();
         if (count($getVars) === 0) {
             $modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
-            $getVars = ['id', 'M', $modulePrefix];
+            $getVars = ['id', 'route', $modulePrefix];
         }
         $shortcutButton = $buttonBar->makeShortcutButton()
             ->setModuleName($moduleName)
index f03a1c4..ba25796 100644 (file)
@@ -863,10 +863,10 @@ class WorkspaceService implements SingletonInterface
         $uriBuilder = $this->getObjectManager()->get(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class);
         $redirect = 'index.php?redirect_url=';
         // @todo this should maybe be changed so that the extbase URI Builder can deal with module names directly
-        $originalM = GeneralUtility::_GET('M');
-        GeneralUtility::_GETset('web_WorkspacesWorkspaces', 'M');
+        $originalM = GeneralUtility::_GET('route');
+        GeneralUtility::_GETset('web_WorkspacesWorkspaces', 'route');
         $viewScript = $uriBuilder->uriFor('index', [], 'Preview', 'workspaces', 'web_workspacesworkspaces') . '&id=';
-        GeneralUtility::_GETset($originalM, 'M');
+        GeneralUtility::_GETset($originalM, 'route');
         if ($addDomain === true) {
             return BackendUtility::getViewDomain($uid) . $redirect . urlencode($viewScript) . $uid;
         }