Commit 84ceebc2 authored by Christian Kuhn's avatar Christian Kuhn Committed by Benjamin Franzke
Browse files

[FEATURE] Add composer package name to backend routes

We have three files that define backend routes:

* Configuration/Backend/Routes.php
* Configuration/Backend/AjaxRoutes.php
* Configuration/Backend/Modules.php

Routes defined by these files now contain the
composer package name ('name' attribute in composer.json)
of the package that defined the route as option.

Backend modules now have a getPackageName()
method.

This is useful as shown with #96962 and will
likely find further usages in the future.

Change-Id: Ifa20f11ee7caa470eb2a1eed9e4ddf3ede088a54
Resolves: #96961
Related: #96962
Related: #96733
Releases: main
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/73191

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
parent bb2fca15
......@@ -25,6 +25,8 @@ namespace TYPO3\CMS\Backend\Module;
abstract class BaseModule
{
protected string $identifier;
protected string $packageName = '';
protected string $absolutePackagePath = '';
protected string $path = '';
protected string $iconIdentifier = '';
protected string $title = '';
......@@ -199,6 +201,8 @@ abstract class BaseModule
public static function createFromConfiguration(string $identifier, array $configuration): static
{
$obj = new static($identifier);
$obj->packageName = (string)($configuration['packageName'] ?? '');
$obj->absolutePackagePath = (string)($configuration['absolutePackagePath'] ?? '');
$obj->path = '/' . ltrim((string)$configuration['path'], '/');
$obj->standalone = (bool)($configuration['standalone'] ?? false);
......
......@@ -55,6 +55,8 @@ class ExtbaseModule extends BaseModule implements ModuleInterface
{
return [
'module' => $this,
'packageName' => $this->packageName,
'absolutePackagePath' => $this->absolutePackagePath,
'access' => $this->access,
'target' => Bootstrap::class . '::handleBackendRequest',
];
......
......@@ -39,6 +39,8 @@ class Module extends BaseModule implements ModuleInterface
}
return [
'module' => $this,
'packageName' => $this->packageName,
'absolutePackagePath' => $this->absolutePackagePath,
'access' => $this->access,
'target' => $this->routes['_default']['target'],
];
......
......@@ -51,6 +51,11 @@ class ServiceProvider extends AbstractServiceProvider
return __DIR__ . '/../';
}
protected static function getPackageName(): string
{
return 'typo3/cms-backend';
}
public function getFactories(): array
{
return [
......
......@@ -69,7 +69,10 @@ class BackendModuleValidatorTest extends FunctionalTestCase
{
$module = $this->getContainer()->get(ModuleFactory::class)->createModule(
'web_layout',
['path' => '/module/web/layout']
[
'packageName' => 'typo3/cms-testing',
'path' => '/module/web/layout',
]
);
$response = $this->subject->process(
......@@ -88,6 +91,7 @@ class BackendModuleValidatorTest extends FunctionalTestCase
$module = $this->getContainer()->get(ModuleFactory::class)->createModule(
'web_layout',
[
'packageName' => 'typo3/cms-testing',
'path' => '/module/web/layout',
'moduleData' => [
'sort' => 'name',
......@@ -116,7 +120,10 @@ class BackendModuleValidatorTest extends FunctionalTestCase
{
$module = $this->getContainer()->get(ModuleFactory::class)->createModule(
'some_module',
['path' => '/some/module']
[
'packageName' => 'typo3/cms-testing',
'path' => '/some/module',
]
);
$this->expectException(\RuntimeException::class);
......@@ -138,7 +145,10 @@ class BackendModuleValidatorTest extends FunctionalTestCase
// site_configuration requires admin access
$module = $this->getContainer()->get(ModuleFactory::class)->createModule(
'site_configuration',
['path' => '/module/site/configuration']
[
'packageName' => 'typo3/cms-testing',
'path' => '/module/site/configuration',
]
);
$this->expectException(\RuntimeException::class);
......@@ -157,7 +167,10 @@ class BackendModuleValidatorTest extends FunctionalTestCase
{
$module = $this->getContainer()->get(ModuleFactory::class)->createModule(
'web_layout',
['path' => '/module/web/layout']
[
'packageName' => 'typo3/cms-testing',
'path' => '/module/web/layout',
]
);
$this->expectException(\RuntimeException::class);
......@@ -178,7 +191,10 @@ class BackendModuleValidatorTest extends FunctionalTestCase
{
$module = $this->getContainer()->get(ModuleFactory::class)->createModule(
'web_layout',
['path' => '/module/web/layout']
[
'packageName' => 'typo3/cms-testing',
'path' => '/module/web/layout',
]
);
$response = $this->subject->process(
......
......@@ -66,6 +66,7 @@ class ModuleDataTest extends FunctionalTestCase
$module = $this->getContainer()->get(ModuleFactory::class)->createModule(
'my_module',
[
'packageName' => 'typo3/cms-testing',
'path' => '/module/my/module',
'moduleData' => $defaultValues,
]
......
......@@ -38,6 +38,13 @@ abstract class AbstractServiceProvider implements ServiceProviderInterface
*/
abstract protected static function getPackagePath(): string;
/**
* Return the composer package name. This is the 'name' attribute in composer.json.
* Note composer.json existence for 'extensions' is still not mandatory
* in non-composer mode, the method returns empty string in this case.
*/
abstract protected static function getPackageName(): string;
/**
* @return array
*/
......@@ -60,7 +67,7 @@ abstract class AbstractServiceProvider implements ServiceProviderInterface
/**
* @param ContainerInterface $container
* @param ArrayObject $middlewares
* @param string $path supplied when invoked internally through PseudoServiceProvider
* @param string|null $path supplied when invoked internally through PseudoServiceProvider
* @return ArrayObject
*/
public static function configureMiddlewares(ContainerInterface $container, ArrayObject $middlewares, string $path = null): ArrayObject
......@@ -80,15 +87,22 @@ abstract class AbstractServiceProvider implements ServiceProviderInterface
* @param ContainerInterface $container
* @param ArrayObject $routes
* @param string|null $path supplied when invoked internally through PseudoServiceProvider
* @param string|null $packageName supplied when invoked internally through PseudoServiceProvider
* @return ArrayObject
*/
public static function configureBackendRoutes(ContainerInterface $container, ArrayObject $routes, string $path = null): ArrayObject
public static function configureBackendRoutes(ContainerInterface $container, ArrayObject $routes, string $path = null, string $packageName = null): ArrayObject
{
$path = $path ?? static::getPackagePath();
$packageName = $packageName ?? static::getPackageName();
$routesFileNameForPackage = $path . 'Configuration/Backend/Routes.php';
if (file_exists($routesFileNameForPackage)) {
$definedRoutesInPackage = require $routesFileNameForPackage;
if (is_array($definedRoutesInPackage)) {
array_walk($definedRoutesInPackage, static function (&$options) use ($packageName, $path) {
// Add packageName and absolutePackagePath to all routes
$options['packageName'] = $packageName;
$options['absolutePackagePath'] = $path;
});
$routes->exchangeArray(array_merge($routes->getArrayCopy(), $definedRoutesInPackage));
}
}
......@@ -99,6 +113,8 @@ abstract class AbstractServiceProvider implements ServiceProviderInterface
foreach ($definedRoutesInPackage as $routeIdentifier => $routeOptions) {
// prefix the route with "ajax_" as "namespace"
$routeOptions['path'] = '/ajax' . $routeOptions['path'];
$routeOptions['packageName'] = $packageName;
$routeOptions['absolutePackagePath'] = $path;
$routes['ajax_' . $routeIdentifier] = $routeOptions;
$routes['ajax_' . $routeIdentifier]['ajax'] = true;
}
......@@ -130,15 +146,22 @@ abstract class AbstractServiceProvider implements ServiceProviderInterface
* @param ContainerInterface $container
* @param ArrayObject $modules
* @param string|null $path supplied when invoked internally through PseudoServiceProvider
* @param string|null $packageName supplied when invoked internally through PseudoServiceProvider
* @return ArrayObject
*/
public static function configureBackendModules(ContainerInterface $container, ArrayObject $modules, string $path = null): ArrayObject
public static function configureBackendModules(ContainerInterface $container, ArrayObject $modules, string $path = null, string $packageName = null): ArrayObject
{
$path = $path ?? static::getPackagePath();
$packageName = $packageName ?? static::getPackageName();
$modulesFileNameForPackage = $path . 'Configuration/Backend/Modules.php';
if (file_exists($modulesFileNameForPackage)) {
$definedModulesInPackage = require $modulesFileNameForPackage;
if (is_array($definedModulesInPackage)) {
array_walk($definedModulesInPackage, static function (&$module) use ($packageName, $path) {
// Add packageName and absolutePackagePath to all modules
$module['packageName'] = $packageName;
$module['absolutePackagePath'] = $path;
});
$modules->exchangeArray(array_merge($modules->getArrayCopy(), $definedModulesInPackage));
}
}
......
......@@ -45,6 +45,11 @@ final class PseudoServiceProvider extends AbstractServiceProvider
throw new \BadMethodCallException('PseudoServiceProvider does not support the getPackagePath() method.', 1562354465);
}
protected static function getPackageName(): string
{
throw new \BadMethodCallException('PseudoServiceProvider does not support the getPackageName() method.', 1643372902);
}
/**
* @return array
*/
......@@ -59,6 +64,9 @@ final class PseudoServiceProvider extends AbstractServiceProvider
public function getExtensions(): array
{
$packagePath = $this->package->getPackagePath();
// Fallback to empty string if dealing with an extension in non-composer mode
// that still does not provide composer.json.
$packageName = $this->package->getValueFromComposerManifest('name') ?? '';
$extensions = parent::getExtensions();
// The static configure*() methods in AbstractServiceProvider use the
......@@ -69,9 +77,10 @@ final class PseudoServiceProvider extends AbstractServiceProvider
// AbstractServiceProvider configure methods are aware of this and
// provide an optional third parameter which is forwarded as
// dynamic path to getPackagePath().
// Same logic for $packageName.
foreach ($extensions as $serviceName => $previousCallable) {
$extensions[$serviceName] = static function (ContainerInterface $container, $value) use ($previousCallable, $packagePath) {
return ($previousCallable)($container, $value, $packagePath);
$extensions[$serviceName] = static function (ContainerInterface $container, $value) use ($previousCallable, $packagePath, $packageName) {
return ($previousCallable)($container, $value, $packagePath, $packageName);
};
}
......
......@@ -40,6 +40,11 @@ class ServiceProvider extends AbstractServiceProvider
return __DIR__ . '/../';
}
protected static function getPackageName(): string
{
return 'typo3/cms-core';
}
public function getFactories(): array
{
return [
......
.. include:: ../../Includes.txt
==============================================================
Feature: #96961 - Backend routes contain composer package name
==============================================================
See :issue:`96961`
Description
===========
Request objects in the backend already contain the resolved route
object as attribute. These route objects now contain the composer
package name of the package ("extension") that defined the route as option:
.. code-block:: php
/** @var \TYPO3\CMS\Backend\Routing\Route $route */
$route = $request->getAttribute('route');
// Example return: "typo3/cms-backend" when EXT:backend defined that route.
$packageName = $route->getOption('packageName');
Impact
======
The package name can be useful for filesystem lookups
or to bind configuration based on package name to it.
.. index:: Backend, PHP-API, ext:backend
......@@ -166,10 +166,12 @@ class TcaItemsProcessorFunctionsTest extends UnitTestCase
'aModule' => $moduleFactory->createModule('aModule', [
'iconIdentifier' => 'a-module',
'labels' => 'LLL:EXT:a-module/locallang',
'packageName' => 'typo3/cms-testing',
]),
'bModule' => $moduleFactory->createModule('bModule', [
'iconIdentifier' => 'b-module',
'labels' => 'LLL:EXT:b-module/locallang',
'packageName' => 'typo3/cms-testing',
]),
]);
......
......@@ -78,6 +78,7 @@ class AbstractServiceProviderTest extends UnitTestCase
$package2 = $this->prophesize(Package::class);
$package2->getPackagePath()->willReturn(__DIR__ . '/../Http/Fixtures/Package2/');
$package2->getValueFromComposerManifest('name')->willReturn('typo3/cms-testing');
$package2ServiceProvider = new PseudoServiceProvider($package2->reveal());
$middlewares = new ArrayObject();
......@@ -113,6 +114,7 @@ class AbstractServiceProviderTest extends UnitTestCase
$package2 = $this->prophesize(Package::class);
$package2->getPackagePath()->willReturn(__DIR__ . '/../Http/Fixtures/Package2Disables1/');
$package2->getValueFromComposerManifest('name')->willReturn('typo3/cms-testing');
$package2ServiceProvider = new PseudoServiceProvider($package2->reveal());
$middlewares = new ArrayObject();
......@@ -149,6 +151,7 @@ class AbstractServiceProviderTest extends UnitTestCase
$package2 = $this->prophesize(Package::class);
$package2->getPackagePath()->willReturn(__DIR__ . '/../Http/Fixtures/Package2Replaces1/');
$package2->getValueFromComposerManifest('name')->willReturn('typo3/cms-testing');
$package2ServiceProvider = new PseudoServiceProvider($package2->reveal());
$middlewares = new ArrayObject();
......
......@@ -26,6 +26,11 @@ class Package1ServiceProviderMock extends AbstractServiceProvider
return __DIR__ . '/../../Http/Fixtures/Package1/';
}
protected static function getPackageName(): string
{
return 'typo3/testing-package1';
}
public function getFactories(): array
{
return [];
......
......@@ -26,6 +26,11 @@ class Package2ServiceProviderMock extends AbstractServiceProvider
return __DIR__ . '/../../Http/Fixtures/Package2/';
}
protected static function getPackageName(): string
{
return 'typo3/testing-package1';
}
public function getFactories(): array
{
return [];
......
......@@ -38,6 +38,11 @@ class ServiceProvider extends AbstractServiceProvider
return __DIR__ . '/../';
}
protected static function getPackageName(): string
{
return 'typo3/cms-dashboard';
}
public function getFactories(): array
{
return [
......
......@@ -34,6 +34,11 @@ class ServiceProvider extends AbstractServiceProvider
return __DIR__ . '/../';
}
protected static function getPackageName(): string
{
return 'typo3/cms-extbase';
}
public function getFactories(): array
{
return [
......
......@@ -44,6 +44,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -76,6 +77,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -109,6 +111,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -181,6 +184,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -239,6 +243,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -292,6 +297,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -326,6 +332,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -381,6 +388,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -415,6 +423,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -453,6 +462,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -487,6 +497,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -518,6 +529,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -552,6 +564,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -589,6 +602,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -623,6 +637,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -661,6 +676,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -697,6 +713,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......@@ -736,6 +753,7 @@ class RequestBuilderTest extends FunctionalTestCase
$pluginName = 'blog';
$module = ExtbaseModule::createFromConfiguration($pluginName, [
'packageName' => 'typo3/cms-blog-example',
'path' => '/blog-example',
'extensionName' => $extensionName,
'controllerActions' => [
......
......@@ -31,6 +31,11 @@ class ServiceProvider extends AbstractServiceProvider
return __DIR__ . '/../';
}
protected static function getPackageName(): string
{
return 'typo3/cms-fluid';
}
public function getFactories(): array
{
return [
......
......@@ -39,6 +39,11 @@ class ServiceProvider extends AbstractServiceProvider
return __DIR__ . '/../';
}
protected static function getPackageName(): string
{
return 'typo3/cms-frontend';
}
public function getFactories(): array
{
return [
......
......@@ -53,6 +53,11 @@ class ServiceProvider extends AbstractServiceProvider
return __DIR__ . '/../';
}
protected static function getPackageName(): string
{
return 'typo3/cms-install';
}
public function getFactories(): array
{
return [
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment