diff --git a/composer.json b/composer.json index d6e9010cf6c151b9b053a29363154b236787d361..0abdb839279a940545fcdfc7ac17917e4546b638 100644 --- a/composer.json +++ b/composer.json @@ -311,6 +311,7 @@ "TYPO3Tests\\TestIrreForeignfield\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_irre_foreignfield/Classes/", "TYPO3Tests\\TestLogger\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_logger/Classes/", "TYPO3Tests\\TestMeta\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_meta/Classes/", + "TYPO3Tests\\TestTcaEvent\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Classes/", "TYPO3Tests\\TestTyposcriptAstFunctionEvent\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Classes/", "TYPO3Tests\\TestTyposcriptPagetsconfigfactory\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/", "TYPO3Tests\\TestTsconfigEvent\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tsconfig_event/Classes/", diff --git a/typo3/sysext/core/Classes/Configuration/Event/BeforeTcaOverridesEvent.php b/typo3/sysext/core/Classes/Configuration/Event/BeforeTcaOverridesEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..db15c6e0ea4282760f31596c5c5784aa18011239 --- /dev/null +++ b/typo3/sysext/core/Classes/Configuration/Event/BeforeTcaOverridesEvent.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Configuration\Event; + +/** + * Event before $GLOBALS['TCA'] is overridden by TCA/Overrides to allow to manipulate $tca, before overrides are merged. + * + * Side note: It is possible to check against the original TCA as this is stored within $GLOBALS['TCA'] + * before this event is fired. + */ +final class BeforeTcaOverridesEvent +{ + public function __construct(private array $tca) + { + } + + public function getTca(): array + { + return $this->tca; + } + + public function setTca(array $tca): void + { + $this->tca = $tca; + } +} diff --git a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php index 4ea383ef7c5aa1118b7996cfa220f78d326690dc..3d2de19c1897102c262df2d11453165edb0d7710 100644 --- a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php +++ b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php @@ -22,6 +22,7 @@ use Symfony\Component\Finder\Finder; use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Configuration\Event\AfterTcaCompilationEvent; +use TYPO3\CMS\Core\Configuration\Event\BeforeTcaOverridesEvent; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\DataHandling\PageDoktypeRegistry; use TYPO3\CMS\Core\Migrations\TcaMigration; @@ -1318,6 +1319,8 @@ tt_content.' . $key . $suffix . ' { } } + static::dispatchBaseTcaIsBeingBuiltEvent($GLOBALS['TCA']); + // To require TCA Overrides in a safe scoped environment avoiding local variable clashes. // @see TYPO3\CMS\Core\Tests\Functional\Utility\ExtensionManagementUtility\ExtensionManagementUtilityTcaOverrideRequireTest $scopedRequire = static function (string $filename): void { @@ -1355,6 +1358,14 @@ tt_content.' . $key . $suffix . ' { static::dispatchTcaIsBeingBuiltEvent($GLOBALS['TCA']); } + /** + * Triggers an event for manipulating the TCA before overrides are applied. + */ + protected static function dispatchBaseTcaIsBeingBuiltEvent(array $tca): void + { + $GLOBALS['TCA'] = static::$eventDispatcher->dispatch(new BeforeTcaOverridesEvent($tca))->getTca(); + } + /** * Triggers an event for manipulating the final TCA */ diff --git a/typo3/sysext/core/Documentation/Changelog/13.0/Feature-102067-BeforeTcaOverridesEvent.rst b/typo3/sysext/core/Documentation/Changelog/13.0/Feature-102067-BeforeTcaOverridesEvent.rst new file mode 100644 index 0000000000000000000000000000000000000000..18666109a7bdbf371f151737a9887d111c408e00 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/13.0/Feature-102067-BeforeTcaOverridesEvent.rst @@ -0,0 +1,59 @@ +.. include:: /Includes.rst.txt + +.. _feature-102067-1695985288: + +================================================= +Feature: #102067 - PSR-14 BeforeTcaOverridesEvent +================================================= + +See :issue:`102067` + +Description +=========== + +A new PSR-14 :php:`\TYPO3\CMS\Core\Configuration\Event\BeforeTcaOverridesEvent` +has been introduced, enabling developers to listen to the state between loaded +base TCA and merging of TCA overrides. + +Example +------- + +.. code-block:: php + + <?php + + declare(strict_types=1); + + namespace Vendor\MyExtension\EventListener; + + use TYPO3\CMS\Core\Attribute\AsEventListener; + use TYPO3\CMS\Core\Configuration\Event\BeforeTcaOverridesEvent; + + final class AddTcaBeforeTcaOverrides + { + #[AsEventListener('vendor/my-extension/before-tca-overrides')] + public function __invoke(BeforeTcaOverridesEvent $event): void + { + $tca = $event->getTca(); + $tca['tt_content']['columns']['header']['config']['max'] = 100; + $event->setTca($tca); + } + } + + +Impact +====== + +The new PSR-14 can be used to dynamically generate TCA and add it as additional +base TCA. This is especially useful for "TCA generator" extensions, which add +TCA based on another resource, while still enabling users to override TCA via +the known TCA overrides API. + +.. note:: + + Please note that TCA is always "runtime cached". This means that dynamic + additions must never depend on runtime state, e.g. the current PSR-7 + request or similar, because such information might not even exist when + the first call is e.g. done from CLI. + +.. index:: Backend, PHP-API, TCA, ext:core diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Classes/EventListener/AddTcaAfterTcaCompilation.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Classes/EventListener/AddTcaAfterTcaCompilation.php new file mode 100644 index 0000000000000000000000000000000000000000..cfdfe1cc02f6fdfd3513f211387e4061354142ff --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Classes/EventListener/AddTcaAfterTcaCompilation.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3Tests\TestTcaEvent\EventListener; + +use TYPO3\CMS\Core\Attribute\AsEventListener; +use TYPO3\CMS\Core\Configuration\Event\AfterTcaCompilationEvent; + +#[AsEventListener(identifier: 'typo3tests/test-tca-event/after-tca-compilation')] +final class AddTcaAfterTcaCompilation +{ + public function __invoke(AfterTcaCompilationEvent $event): void + { + $tca = $event->getTca(); + $tca['fruit']['ctrl']['title'] = 'Vegetable'; + $event->setTca($tca); + } +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Classes/EventListener/AddTcaBeforeTcaOverrides.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Classes/EventListener/AddTcaBeforeTcaOverrides.php new file mode 100644 index 0000000000000000000000000000000000000000..482d5a3a7d1396d4c8fe8f1c6fd5ba3bcc09effb --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Classes/EventListener/AddTcaBeforeTcaOverrides.php @@ -0,0 +1,33 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3Tests\TestTcaEvent\EventListener; + +use TYPO3\CMS\Core\Attribute\AsEventListener; +use TYPO3\CMS\Core\Configuration\Event\BeforeTcaOverridesEvent; + +#[AsEventListener(identifier: 'typo3tests/test-tca-event/before-tca-overrides')] +final class AddTcaBeforeTcaOverrides +{ + public function __invoke(BeforeTcaOverridesEvent $event): void + { + $tca = $event->getTca(); + $tca['fruit']['columns']['name']['config']['type'] = 'number'; + $tca['fruit']['columns']['name']['config']['label'] = 'Monstera'; + $event->setTca($tca); + } +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Configuration/Services.yaml b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Configuration/Services.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c0a548a7bf09ba8420297e3630f25c9e3d323799 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Configuration/Services.yaml @@ -0,0 +1,8 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + TYPO3Tests\TestTcaEvent\: + resource: '../Classes/*' diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Configuration/TCA/Overrides/fruit.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Configuration/TCA/Overrides/fruit.php new file mode 100644 index 0000000000000000000000000000000000000000..4e3b2c2346d2ba7a561883555ee10747487afb8e --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Configuration/TCA/Overrides/fruit.php @@ -0,0 +1,6 @@ +<?php + +declare(strict_types=1); + +$GLOBALS['TCA']['fruit']['ctrl']['title'] = 'Plant'; +$GLOBALS['TCA']['fruit']['columns']['name']['config']['type'] = 'text'; diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Configuration/TCA/fruit.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Configuration/TCA/fruit.php new file mode 100644 index 0000000000000000000000000000000000000000..fb8c81fe04e485eafd8c4ec9d071b6706faf8e7f --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/Configuration/TCA/fruit.php @@ -0,0 +1,21 @@ +<?php + +return [ + 'ctrl' => [ + 'title' => 'Fruit', + 'label' => 'name', + ], + 'types' => [ + '1' => [ + 'showitem' => 'name', + ], + ], + 'columns' => [ + 'name' => [ + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + ], + ], + ], +]; diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/composer.json b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..494520e0475d251b1f2ecb1f3607f813b90053a7 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/composer.json @@ -0,0 +1,19 @@ +{ + "name": "typo3tests/test-tca-event", + "type": "typo3-cms-extension", + "description": "This extension defines event listeners for TCA modification.", + "license": "GPL-2.0-or-later", + "require": { + "typo3/cms-core": "13.0.*@dev" + }, + "extra": { + "typo3/cms": { + "extension-key": "test_tca_event" + } + }, + "autoload": { + "psr-4": { + "TYPO3Tests\\TestTcaEvent\\": "Classes/" + } + } +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/ext_emconf.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/ext_emconf.php new file mode 100644 index 0000000000000000000000000000000000000000..9602fb464fa159749a0d229c82fbeb5d92833383 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event/ext_emconf.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +$EM_CONF[$_EXTKEY] = [ + 'title' => 'This extension defines event listeners for TCA modification.', + 'description' => 'This extension defines event listeners for TCA modification.', + 'category' => 'example', + 'version' => '13.0.0', + 'state' => 'beta', + 'author' => 'Nikita Hovratov', + 'author_email' => 'info@nikita-hovratov.de', + 'author_company' => '', + 'constraints' => [ + 'depends' => [ + 'typo3' => '13.0.0', + ], + 'conflicts' => [], + 'suggests' => [], + ], +]; diff --git a/typo3/sysext/core/Tests/Functional/Tca/AfterTcaCompilationEventTest.php b/typo3/sysext/core/Tests/Functional/Tca/AfterTcaCompilationEventTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d1d384621d547342d70061a7f9da6d50a605c9c8 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Tca/AfterTcaCompilationEventTest.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Tests\Functional\Tca; + +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +final class AfterTcaCompilationEventTest extends FunctionalTestCase +{ + protected array $testExtensionsToLoad = [ + 'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event', + ]; + + /** + * @test + */ + public function addedTcaOverridesAnythingElse(): void + { + self::assertSame('Vegetable', $GLOBALS['TCA']['fruit']['ctrl']['title']); + } +} diff --git a/typo3/sysext/core/Tests/Functional/Tca/BeforeTcaOverridesEventTest.php b/typo3/sysext/core/Tests/Functional/Tca/BeforeTcaOverridesEventTest.php new file mode 100644 index 0000000000000000000000000000000000000000..484f4977422a55ed70eebf722637f0efae9d4e62 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Tca/BeforeTcaOverridesEventTest.php @@ -0,0 +1,43 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Tests\Functional\Tca; + +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +final class BeforeTcaOverridesEventTest extends FunctionalTestCase +{ + protected array $testExtensionsToLoad = [ + 'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_tca_event', + ]; + + /** + * @test + */ + public function cannotOverrideTcaOverrides(): void + { + self::assertSame('text', $GLOBALS['TCA']['fruit']['columns']['name']['config']['type']); + } + + /** + * @test + */ + public function canOverrideBaseTca(): void + { + self::assertSame('Monstera', $GLOBALS['TCA']['fruit']['columns']['name']['config']['label']); + } +} diff --git a/typo3/sysext/core/Tests/Unit/Utility/AccessibleProxies/ExtensionManagementUtilityAccessibleProxy.php b/typo3/sysext/core/Tests/Unit/Utility/AccessibleProxies/ExtensionManagementUtilityAccessibleProxy.php index 991c87099bb7c2edd55abcce8ddaa9259636b05c..070e0fde1818c20e386bd8c61ce4a97aea395cac 100644 --- a/typo3/sysext/core/Tests/Unit/Utility/AccessibleProxies/ExtensionManagementUtilityAccessibleProxy.php +++ b/typo3/sysext/core/Tests/Unit/Utility/AccessibleProxies/ExtensionManagementUtilityAccessibleProxy.php @@ -77,6 +77,10 @@ class ExtensionManagementUtilityAccessibleProxy extends ExtensionManagementUtili $GLOBALS['TCA'] = []; } + public static function dispatchBaseTcaIsBeingBuiltEvent(array $tca): void + { + } + public static function dispatchTcaIsBeingBuiltEvent(array $tca): void { }