diff --git a/Build/phpstan/phpstan-baseline.neon b/Build/phpstan/phpstan-baseline.neon index 1f65fab763bdc1a2dbc175ffb2b94cf3997dc57b..6fccf167dab6f8b68a85db038aef187a2affde20 100644 --- a/Build/phpstan/phpstan-baseline.neon +++ b/Build/phpstan/phpstan-baseline.neon @@ -2880,11 +2880,6 @@ parameters: count: 1 path: ../../typo3/sysext/indexed_search/Classes/Utility/DoubleMetaPhoneUtility.php - - - message: "#^Property TYPO3\\\\CMS\\\\Install\\\\Command\\\\UpgradeWizardListCommand\\:\\:\\$input is never read, only written\\.$#" - count: 1 - path: ../../typo3/sysext/install/Classes/Command/UpgradeWizardListCommand.php - - message: "#^Call to an undefined method TYPO3\\\\CMS\\\\Core\\\\Authentication\\\\AbstractAuthenticationService\\:\\:authUser\\(\\)\\.$#" count: 1 diff --git a/typo3/sysext/core/Documentation/Changelog/12.2/Deprecation-99586-RegistrationOfUpgradeWizardsViaGLOBALS.rst b/typo3/sysext/core/Documentation/Changelog/12.2/Deprecation-99586-RegistrationOfUpgradeWizardsViaGLOBALS.rst new file mode 100644 index 0000000000000000000000000000000000000000..aab395ecdcff0127e44970b7ce9b327a02d5679a --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.2/Deprecation-99586-RegistrationOfUpgradeWizardsViaGLOBALS.rst @@ -0,0 +1,76 @@ +.. include:: /Includes.rst.txt + +.. _deprecation-99586-1673990657: + +================================================================== +Deprecation: #99586 - Registration of upgrade wizards via $GLOBALS +================================================================== + +See :issue:`99586` + +Description +=========== + +Registration of upgrade wizards via :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']`, +usually placed in an extensions :file:`ext_localconf.php` has been deprecated +in favour of the :ref:`new service tag <feature-99586-1673989775>`. + +Additionally, the :php:`UpgradeWizardInterface`, which all upgrade wizards must +implement, does no longer require the :php:`getIdentifier()` method. TYPO3 does +not use this method anymore since an upgrade wizards identifier is now +defined using the new service tag. + + +Impact +====== + +Upgrade wizards, registered via :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']` +will no longer be recognized with TYPO3 v13. + +Definition of the :php:`getIdentifier()` method does no longer have any effect. + + +Affected installations +====================== + +All installations registering custom upgrade wizards using +:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']`. + +All installations implementing the :php:`getIdentifier()` method in their +upgrade wizards. + + +Migration +========= + +Use the new service tag to register custom upgrade wizards and remove the +registration via :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']`. + +Before +~~~~~~ + +.. code-block:: php + + // ext_localconf.php + + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['myUpgradeWizard'] = \Vendor\Extension\Updates\MyUpgradeWizard::class; + +After +~~~~~ + +.. code-block:: php + + // Classes/Updates/MyUpgradeWizard.php + + use TYPO3\CMS\Install\Attribute\UpgradeWizard; + use TYPO3\CMS\Install\Updates\UpgradeWizardInterface; + + #[UpgradeWizard('myUpgradeWizard')] + class MyUpgradeWizard implements UpgradeWizardInterface + { + + } + +Drop any :php:`getIdentifier()` method in custom upgrade wizards. + +.. index:: Backend, PHP-API, FullyScanned, ext:install diff --git a/typo3/sysext/core/Documentation/Changelog/12.2/Feature-99586-RegistrationOfUpgradeWizardsViaServiceTag.rst b/typo3/sysext/core/Documentation/Changelog/12.2/Feature-99586-RegistrationOfUpgradeWizardsViaServiceTag.rst new file mode 100644 index 0000000000000000000000000000000000000000..78350d2ca7bddf604ecae62d04ae0c0c8796740e --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.2/Feature-99586-RegistrationOfUpgradeWizardsViaServiceTag.rst @@ -0,0 +1,50 @@ +.. include:: /Includes.rst.txt + +.. _feature-99586-1673989775: + +================================================================= +Feature: #99586 - Registration of upgrade wizards via service tag +================================================================= + +See :issue:`99586` + +Description +=========== + +Upgrade wizards are usually used to execute one time migrations when +updating a TYPO3 installation. The registration was previously done +in an extensions :php:`ext_localconf.php` file. This has now been +improved by introducing the custom PHP attribute +:php:`TYPO3\CMS\Install\Attribute\UpgradeWizard`. All upgrade wizards, +defining the new attribute, are automatically tagged and registered +in the service container. The registration via +:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']` +has been deprecated. + +The registration of an upgrade wizard is therefore now be done +directly in the class by adding the new attribute with the upgrade +wizards' unique identifier as constructor argument: + +.. code-block:: php + + use TYPO3\CMS\Install\Attribute\UpgradeWizard; + use TYPO3\CMS\Install\Updates\UpgradeWizardInterface; + + #[UpgradeWizard('myUpgradeWizard')] + class MyUpgradeWizard implements UpgradeWizardInterface + { + + } + +.. note:: + + All upgrade wizards have to implement the :php:`UpgradeWizardInterface`. + +Impact +====== + +It's now possible to tag upgrade wizards with the PHP attribute +:php:`TYPO3\CMS\Install\Attribute\UpgradeWizard` to have them +auto-configured and auto-registered. + +.. index:: Backend, PHP-API, ext:install diff --git a/typo3/sysext/install/Classes/Attribute/UpgradeWizard.php b/typo3/sysext/install/Classes/Attribute/UpgradeWizard.php new file mode 100644 index 0000000000000000000000000000000000000000..8faaa5a57701019fc87fb3de912d7e11c4297ce5 --- /dev/null +++ b/typo3/sysext/install/Classes/Attribute/UpgradeWizard.php @@ -0,0 +1,34 @@ +<?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\Install\Attribute; + +use Attribute; + +/** + * Service tag to autoconfigure upgrade wizards + */ +#[Attribute(Attribute::TARGET_CLASS)] +class UpgradeWizard +{ + public const TAG_NAME = 'install.upgradewizard'; + + public function __construct( + public string $identifier + ) { + } +} diff --git a/typo3/sysext/install/Classes/Command/SetupCommand.php b/typo3/sysext/install/Classes/Command/SetupCommand.php index 1d4036f54f30aa811ba0bc6aff14889bd53794c5..3657f54d556227bcacf1ff26e7504a73b9f2af6e 100644 --- a/typo3/sysext/install/Classes/Command/SetupCommand.php +++ b/typo3/sysext/install/Classes/Command/SetupCommand.php @@ -32,6 +32,7 @@ use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException; use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Install\FolderStructure\DefaultFactory; +use TYPO3\CMS\Install\Service\LateBootService; use TYPO3\CMS\Install\Service\SetupDatabaseService; use TYPO3\CMS\Install\Service\SetupService; @@ -52,6 +53,7 @@ class SetupCommand extends Command protected SetupDatabaseService $setupDatabaseService; protected SetupService $setupService; protected ConfigurationManager $configurationManager; + protected LateBootService $lateBootService; protected OutputInterface $output; protected InputInterface $input; protected QuestionHelper $questionHelper; @@ -61,10 +63,12 @@ class SetupCommand extends Command SetupDatabaseService $setupDatabaseService, SetupService $setupService, ConfigurationManager $configurationManager, + LateBootService $lateBootService ) { $this->setupDatabaseService = $setupDatabaseService; $this->setupService = $setupService; $this->configurationManager = $configurationManager; + $this->lateBootService = $lateBootService; parent::__construct($name); } @@ -250,7 +254,8 @@ EOT $this->setupService->createSiteConfiguration('main', (int)$pageUid, $siteUrl); } - $this->setupDatabaseService->markWizardsDone(); + $container = $this->lateBootService->loadExtLocalconfDatabaseAndExtTables(); + $this->setupDatabaseService->markWizardsDone($container); $this->writeSuccess('Congratulations - TYPO3 Setup is done.'); return Command::SUCCESS; diff --git a/typo3/sysext/install/Classes/Command/UpgradeWizardListCommand.php b/typo3/sysext/install/Classes/Command/UpgradeWizardListCommand.php index 46b1b3f40ab19320801d255292e2fed9af5f4c4f..a0e7f4181c74c873dbc1bc97a99e41fa57a613f1 100644 --- a/typo3/sysext/install/Classes/Command/UpgradeWizardListCommand.php +++ b/typo3/sysext/install/Classes/Command/UpgradeWizardListCommand.php @@ -24,7 +24,6 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication; use TYPO3\CMS\Core\Core\Bootstrap; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Install\Service\LateBootService; use TYPO3\CMS\Install\Service\UpgradeWizardsService; use TYPO3\CMS\Install\Updates\ChattyInterface; @@ -44,11 +43,6 @@ class UpgradeWizardListCommand extends Command */ private $output; - /** - * @var InputInterface - */ - private $input; - public function __construct(string $name, private readonly LateBootService $lateBootService) { parent::__construct($name); @@ -86,16 +80,15 @@ class UpgradeWizardListCommand extends Command protected function execute(InputInterface $input, OutputInterface $output): int { $this->output = new SymfonyStyle($input, $output); - $this->input = $input; $this->bootstrap(); $wizards = []; $all = $input->getOption('all'); - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $wizardToExecute) { - $upgradeWizard = $this->getWizard($wizardToExecute, $identifier, (bool)$all); + foreach ($this->upgradeWizardsService->getUpgradeWizardIdentifiers() as $identifier) { + $upgradeWizard = $this->getWizard($identifier, (bool)$all); if ($upgradeWizard !== null) { $wizardInfo = [ - 'identifier' => $upgradeWizard->getIdentifier(), + 'identifier' => $identifier, 'title' => $upgradeWizard->getTitle(), 'description' => wordwrap($upgradeWizard->getDescription()), ]; @@ -107,38 +100,34 @@ class UpgradeWizardListCommand extends Command } if (empty($wizards)) { $this->output->success('No wizards available.'); + } elseif ($all === true) { + $this->output->table(['Identifier', 'Title', 'Description', 'Status'], $wizards); } else { - if ($all === true) { - $this->output->table(['Identifier', 'Title', 'Description', 'Status'], $wizards); - } else { - $this->output->table(['Identifier', 'Title', 'Description'], $wizards); - } + $this->output->table(['Identifier', 'Title', 'Description'], $wizards); } return Command::SUCCESS; } /** - * Get Wizard instance by class name and identifier + * Get Wizard instance by identifier * Returns null if wizard is already done - * - * @param bool $all */ - protected function getWizard(string $className, string $identifier, $all = false): ?UpgradeWizardInterface + protected function getWizard(string $identifier, bool $all = false): ?UpgradeWizardInterface { // already done if (!$all && $this->upgradeWizardsService->isWizardDone($identifier)) { return null; } - $wizardInstance = GeneralUtility::makeInstance($className); - if ($wizardInstance instanceof ChattyInterface) { - $wizardInstance->setOutput($this->output); + $wizard = $this->upgradeWizardsService->getUpgradeWizard($identifier); + if ($wizard === null) { + return null; } - if (!($wizardInstance instanceof UpgradeWizardInterface)) { - return null; + if ($wizard instanceof ChattyInterface) { + $wizard->setOutput($this->output); } - return !$all ? $wizardInstance->updateNecessary() ? $wizardInstance : null : $wizardInstance; + return !$all ? $wizard->updateNecessary() ? $wizard : null : $wizard; } } diff --git a/typo3/sysext/install/Classes/Command/UpgradeWizardRunCommand.php b/typo3/sysext/install/Classes/Command/UpgradeWizardRunCommand.php index bad921f19202c3321fc41f55b67365e9f057bb4f..48cf2d81f4db64bef431a26e68b2fc9b597a42b1 100644 --- a/typo3/sysext/install/Classes/Command/UpgradeWizardRunCommand.php +++ b/typo3/sysext/install/Classes/Command/UpgradeWizardRunCommand.php @@ -103,20 +103,15 @@ class UpgradeWizardRunCommand extends Command $this->input = $input; $this->bootstrap(); - $result = Command::SUCCESS; if ($input->getArgument('wizardName')) { $wizardToExecute = $input->getArgument('wizardName'); $wizardToExecute = is_string($wizardToExecute) ? $wizardToExecute : ''; - if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardToExecute])) { - $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardToExecute]; - $upgradeWizard = $this->getWizard($className, $wizardToExecute); - if ($upgradeWizard !== null) { - $prerequisitesFulfilled = $this->handlePrerequisites([$upgradeWizard]); - if ($prerequisitesFulfilled === true) { - $result = $this->runSingleWizard($upgradeWizard); - } else { - $result = Command::FAILURE; - } + if (($upgradeWizard = $this->getWizard($wizardToExecute)) !== null) { + $prerequisitesFulfilled = $this->handlePrerequisites([$upgradeWizard]); + if ($prerequisitesFulfilled === true) { + $result = $this->runSingleWizard($upgradeWizard); + } else { + $result = Command::FAILURE; } } else { $this->output->error('No such wizard: ' . $wizardToExecute); @@ -132,36 +127,30 @@ class UpgradeWizardRunCommand extends Command * Get Wizard instance by class name and identifier * Returns null if wizard is already done */ - protected function getWizard(string $className, string $identifier): ?UpgradeWizardInterface + protected function getWizard(string $identifier): ?UpgradeWizardInterface { // already done if ($this->upgradeWizardsService->isWizardDone($identifier)) { return null; } - $wizardInstance = GeneralUtility::makeInstance($className); - if ($wizardInstance instanceof ChattyInterface) { - $wizardInstance->setOutput($this->output); + $wizard = $this->upgradeWizardsService->getUpgradeWizard($identifier); + if ($wizard === null) { + return null; } - if (!($wizardInstance instanceof UpgradeWizardInterface)) { - $this->output->writeln( - 'Wizard ' . - $identifier . - ' needs to be manually run from the install tool, as it does not implement ' . - UpgradeWizardInterface::class - ); - return null; + if ($wizard instanceof ChattyInterface) { + $wizard->setOutput($this->output); } - if ($wizardInstance->updateNecessary()) { - return $wizardInstance; + if ($wizard->updateNecessary()) { + return $wizard; } - if ($wizardInstance instanceof RepeatableInterface) { + if ($wizard instanceof RepeatableInterface) { $this->output->note('Wizard ' . $identifier . ' does not need to make changes.'); } else { $this->output->note('Wizard ' . $identifier . ' does not need to make changes. Marking wizard as done.'); - $this->upgradeWizardsService->markWizardAsDone($identifier); + $this->upgradeWizardsService->markWizardAsDone($wizard); } return null; } @@ -234,7 +223,7 @@ class UpgradeWizardRunCommand extends Command if ($instance instanceof RepeatableInterface) { $this->output->note('No changes applied.'); } else { - $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier()); + $this->upgradeWizardsService->markWizardAsDone($instance); $this->output->note('No changes applied, marking wizard as done.'); } return Command::SUCCESS; @@ -243,7 +232,7 @@ class UpgradeWizardRunCommand extends Command if ($instance->executeUpdate()) { $this->output->success('Successfully ran wizard ' . $instance->getTitle()); if (!$instance instanceof RepeatableInterface) { - $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier()); + $this->upgradeWizardsService->markWizardAsDone($instance); } return Command::SUCCESS; } @@ -260,8 +249,8 @@ class UpgradeWizardRunCommand extends Command { $returnCode = Command::SUCCESS; $wizardInstances = []; - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $class) { - $wizardInstances[] = $this->getWizard($class, $identifier); + foreach ($this->upgradeWizardsService->getUpgradeWizardIdentifiers() as $identifier) { + $wizardInstances[] = $this->getWizard($identifier); } $wizardInstances = array_filter($wizardInstances); if (count($wizardInstances) > 0) { diff --git a/typo3/sysext/install/Classes/Controller/InstallerController.php b/typo3/sysext/install/Classes/Controller/InstallerController.php index a9c2246817d764eab04e78c86def3e3bdea24e1a..5578af9193459509a803376bb06b6aa374ba2753 100644 --- a/typo3/sysext/install/Classes/Controller/InstallerController.php +++ b/typo3/sysext/install/Classes/Controller/InstallerController.php @@ -636,7 +636,7 @@ final class InstallerController } // Mark upgrade wizards as done - $this->setupDatabaseService->markWizardsDone(); + $this->setupDatabaseService->markWizardsDone($container); $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($configurationValues); diff --git a/typo3/sysext/install/Classes/Service/SetupDatabaseService.php b/typo3/sysext/install/Classes/Service/SetupDatabaseService.php index 48bbcbaa3ec0547939c0c65bee43e07086d60d31..7a79cc25467cd509e93483e624887b91f6bf8092 100644 --- a/typo3/sysext/install/Classes/Service/SetupDatabaseService.php +++ b/typo3/sysext/install/Classes/Service/SetupDatabaseService.php @@ -20,6 +20,7 @@ namespace TYPO3\CMS\Install\Service; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Exception\ConnectionException; +use Psr\Container\ContainerInterface; use TYPO3\CMS\Core\Configuration\ConfigurationManager; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Crypto\Random; @@ -41,7 +42,6 @@ use TYPO3\CMS\Install\Configuration\Exception; use TYPO3\CMS\Install\Database\PermissionsCheck; use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck; use TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard; -use TYPO3\CMS\Install\Updates\RepeatableInterface; /** * Service class helping to manage database related settings and operations required to set up TYPO3 @@ -699,15 +699,10 @@ class SetupDatabaseService ]); } - public function markWizardsDone(): void + public function markWizardsDone(ContainerInterface $container): void { - // Mark upgrade wizards as done - if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'])) { - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $updateClassName) { - if (!in_array(RepeatableInterface::class, class_implements($updateClassName) ?: [], true)) { - $this->registry->set('installUpdate', $updateClassName, 1); - } - } + foreach ($container->get(UpgradeWizardsService::class)->getNonRepeatableUpgradeWizards() as $className) { + $this->registry->set('installUpdate', $className, 1); } $this->registry->set('installUpdateRows', 'rowUpdatersDone', GeneralUtility::makeInstance(DatabaseRowsUpdateWizard::class)->getAvailableRowUpdater()); } diff --git a/typo3/sysext/install/Classes/Service/UpgradeWizardsService.php b/typo3/sysext/install/Classes/Service/UpgradeWizardsService.php index eba3b2ed48ded75f1a5c6ce08053a0df2ccfe9fb..857539eb15efad021461ffff120422bfc39523d2 100644 --- a/typo3/sysext/install/Classes/Service/UpgradeWizardsService.php +++ b/typo3/sysext/install/Classes/Service/UpgradeWizardsService.php @@ -29,6 +29,7 @@ use TYPO3\CMS\Install\Updates\ConfirmableInterface; use TYPO3\CMS\Install\Updates\RepeatableInterface; use TYPO3\CMS\Install\Updates\RowUpdater\RowUpdaterInterface; use TYPO3\CMS\Install\Updates\UpgradeWizardInterface; +use TYPO3\CMS\Install\Updates\UpgradeWizardRegistry; /** * Service class helping managing upgrade wizards @@ -41,7 +42,7 @@ class UpgradeWizardsService */ private $output; - public function __construct() + public function __construct(private readonly UpgradeWizardRegistry $upgradeWizardRegistry) { $fileName = 'php://temp'; if (($stream = fopen($fileName, 'wb')) === false) { @@ -57,13 +58,13 @@ class UpgradeWizardsService { $wizardsDoneInRegistry = []; $registry = GeneralUtility::makeInstance(Registry::class); - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) { - if ($registry->get('installUpdate', $className, false)) { - $wizardInstance = GeneralUtility::makeInstance($className); + foreach ($this->upgradeWizardRegistry->getUpgradeWizards() as $identifier => $serviceName) { + if ($registry->get('installUpdate', $serviceName, false)) { $wizardsDoneInRegistry[] = [ - 'class' => $className, + 'class' => $serviceName, 'identifier' => $identifier, - 'title' => $wizardInstance->getTitle(), + // @todo fetching the service to get the title should be improved + 'title' => $this->upgradeWizardRegistry->getUpgradeWizard($identifier)->getTitle(), ]; } } @@ -148,8 +149,7 @@ class UpgradeWizardsService public function getUpgradeWizardsList(): array { $wizards = []; - foreach (array_keys($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']) as $identifier) { - $identifier = (string)$identifier; + foreach (array_keys($this->upgradeWizardRegistry->getUpgradeWizards()) as $identifier) { if ($this->isWizardDone($identifier)) { continue; } @@ -161,31 +161,18 @@ class UpgradeWizardsService public function getWizardInformationByIdentifier(string $identifier): array { - if (class_exists($identifier)) { - $class = $identifier; - } else { - $class = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier]; - } - /** @var UpgradeWizardInterface $wizardInstance */ - $wizardInstance = GeneralUtility::makeInstance($class); - $explanation = ''; - - // $explanation is changed by reference in Update objects! - $shouldRenderWizard = false; - if ($wizardInstance instanceof UpgradeWizardInterface) { - if ($wizardInstance instanceof ChattyInterface) { - $wizardInstance->setOutput($this->output); - } - $shouldRenderWizard = $wizardInstance->updateNecessary(); - $explanation = $wizardInstance->getDescription(); + $wizard = $this->upgradeWizardRegistry->getUpgradeWizard($identifier); + + if ($wizard instanceof ChattyInterface) { + $wizard->setOutput($this->output); } return [ - 'class' => $class, + 'class' => $wizard::class, 'identifier' => $identifier, - 'title' => $wizardInstance->getTitle(), - 'shouldRenderWizard' => $shouldRenderWizard, - 'explanation' => $explanation, + 'title' => $wizard->getTitle(), + 'shouldRenderWizard' => $wizard->updateNecessary(), + 'explanation' => $wizard->getDescription(), ]; } @@ -198,31 +185,30 @@ class UpgradeWizardsService { $this->assertIdentifierIsValid($identifier); - $class = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier]; - $updateObject = GeneralUtility::makeInstance($class); + $wizard = $this->upgradeWizardRegistry->getUpgradeWizard($identifier); $wizardHtml = ''; - if ($updateObject instanceof UpgradeWizardInterface && $updateObject instanceof ConfirmableInterface) { + if ($wizard instanceof ConfirmableInterface) { $markup = []; $radioAttributes = [ 'type' => 'radio', 'class' => 'btn-check', - 'name' => 'install[values][' . $updateObject->getIdentifier() . '][install]', + 'name' => 'install[values][' . $identifier . '][install]', 'value' => '0', ]; $markup[] = '<div class="panel panel-danger">'; $markup[] = ' <div class="panel-heading">'; - $markup[] = htmlspecialchars($updateObject->getConfirmation()->getTitle()); + $markup[] = htmlspecialchars($wizard->getConfirmation()->getTitle()); $markup[] = ' </div>'; $markup[] = ' <div class="panel-body">'; - $markup[] = ' <p>' . nl2br(htmlspecialchars($updateObject->getConfirmation()->getMessage())) . '</p>'; + $markup[] = ' <p>' . nl2br(htmlspecialchars($wizard->getConfirmation()->getMessage())) . '</p>'; $markup[] = ' <div class="btn-group">'; - if (!$updateObject->getConfirmation()->isRequired()) { + if (!$wizard->getConfirmation()->isRequired()) { $markup[] = ' <input ' . GeneralUtility::implodeAttributes($radioAttributes, true) . ' checked id="upgrade-wizard-deny">'; - $markup[] = ' <label class="btn btn-default" for="upgrade-wizard-deny">' . $updateObject->getConfirmation()->getDeny() . '</label>'; + $markup[] = ' <label class="btn btn-default" for="upgrade-wizard-deny">' . $wizard->getConfirmation()->getDeny() . '</label>'; } $radioAttributes['value'] = '1'; $markup[] = ' <input ' . GeneralUtility::implodeAttributes($radioAttributes, true) . ' id="upgrade-wizard-confirm">'; - $markup[] = ' <label class="btn btn-default" for="upgrade-wizard-confirm">' . $updateObject->getConfirmation()->getConfirm() . '</label>'; + $markup[] = ' <label class="btn btn-default" for="upgrade-wizard-confirm">' . $wizard->getConfirmation()->getConfirm() . '</label>'; $markup[] = ' </div>'; $markup[] = ' </div>'; $markup[] = '</div>'; @@ -231,8 +217,8 @@ class UpgradeWizardsService $result = [ 'identifier' => $identifier, - 'title' => $updateObject->getTitle(), - 'description' => $updateObject->getDescription(), + 'title' => $wizard->getTitle(), + 'description' => $wizard->getDescription(), 'wizardHtml' => $wizardHtml, ]; @@ -249,47 +235,44 @@ class UpgradeWizardsService $performResult = false; $this->assertIdentifierIsValid($identifier); - $class = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier]; - $updateObject = GeneralUtility::makeInstance($class); + $wizard = $this->upgradeWizardRegistry->getUpgradeWizard($identifier); - if ($updateObject instanceof ChattyInterface) { - $updateObject->setOutput($this->output); + if ($wizard instanceof ChattyInterface) { + $wizard->setOutput($this->output); } $messages = new FlashMessageQueue('install'); - if ($updateObject instanceof UpgradeWizardInterface) { - $requestParams = GeneralUtility::_GP('install'); - if ($updateObject instanceof ConfirmableInterface) { - // value is set in request but is empty - $isSetButEmpty = isset($requestParams['values'][$updateObject->getIdentifier()]['install']) - && empty($requestParams['values'][$updateObject->getIdentifier()]['install']); - - $checkValue = (int)$requestParams['values'][$updateObject->getIdentifier()]['install']; - - if ($checkValue === 1) { - // confirmation = yes, we do the update - $performResult = $updateObject->executeUpdate(); - } elseif ($updateObject->getConfirmation()->isRequired()) { - // confirmation = no, but is required, we do *not* the update and fail - $performResult = false; - } elseif ($isSetButEmpty) { - // confirmation = no, but it is *not* required, we do *not* the update, but mark the wizard as done - $this->output->writeln('No changes applied, marking wizard as done.'); - // confirmation was set to "no" - $performResult = true; - } - } else { - // confirmation yes or non-confirmable - $performResult = $updateObject->executeUpdate(); + $requestParams = GeneralUtility::_GP('install'); + if ($wizard instanceof ConfirmableInterface) { + // value is set in request but is empty + $isSetButEmpty = isset($requestParams['values'][$identifier]['install']) + && empty($requestParams['values'][$identifier]['install']); + + $checkValue = (int)$requestParams['values'][$identifier]['install']; + + if ($checkValue === 1) { + // confirmation = yes, we do the update + $performResult = $wizard->executeUpdate(); + } elseif ($wizard->getConfirmation()->isRequired()) { + // confirmation = no, but is required, we do *not* the update and fail + $performResult = false; + } elseif ($isSetButEmpty) { + // confirmation = no, but it is *not* required, we do *not* the update, but mark the wizard as done + $this->output->writeln('No changes applied, marking wizard as done.'); + // confirmation was set to "no" + $performResult = true; } + } else { + // confirmation yes or non-confirmable + $performResult = $wizard->executeUpdate(); } $stream = $this->output->getStream(); rewind($stream); if ($performResult) { - if ($updateObject instanceof UpgradeWizardInterface && !($updateObject instanceof RepeatableInterface)) { + if (!$wizard instanceof RepeatableInterface) { // mark wizard as done if it's not repeatable and was successful - $this->markWizardAsDone($updateObject->getIdentifier()); + $this->markWizardAsDone($wizard); } $messages->enqueue( new FlashMessage( @@ -315,12 +298,9 @@ class UpgradeWizardsService * * @throws \RuntimeException */ - public function markWizardAsDone(string $identifier): void + public function markWizardAsDone(UpgradeWizardInterface $upgradeWizard): void { - $this->assertIdentifierIsValid($identifier); - - $class = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier]; - GeneralUtility::makeInstance(Registry::class)->set('installUpdate', $class, 1); + GeneralUtility::makeInstance(Registry::class)->set('installUpdate', $upgradeWizard::class, 1); } /** @@ -333,8 +313,39 @@ class UpgradeWizardsService { $this->assertIdentifierIsValid($identifier); - $class = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier]; - return (bool)GeneralUtility::makeInstance(Registry::class)->get('installUpdate', $class, false); + return (bool)GeneralUtility::makeInstance(Registry::class)->get( + 'installUpdate', + $this->upgradeWizardRegistry->getUpgradeWizard($identifier)::class, + false + ); + } + + /** + * Wrapper to catch \UnexpectedValueException for backwards compatibility reasons + */ + public function getUpgradeWizard(string $identifier): ?UpgradeWizardInterface + { + try { + return $this->upgradeWizardRegistry->getUpgradeWizard($identifier); + } catch (\UnexpectedValueException) { + return null; + } + } + + public function getUpgradeWizardIdentifiers(): array + { + return array_keys($this->upgradeWizardRegistry->getUpgradeWizards()); + } + + public function getNonRepeatableUpgradeWizards(): array + { + $nonRepeatableUpgradeWizards = []; + foreach ($this->upgradeWizardRegistry->getUpgradeWizards() as $identifier => $updateClassName) { + if (!in_array(RepeatableInterface::class, class_implements($updateClassName) ?: [], true)) { + $nonRepeatableUpgradeWizards[$identifier] = $updateClassName; + } + } + return $nonRepeatableUpgradeWizards; } /** @@ -347,12 +358,11 @@ class UpgradeWizardsService if ($identifier === '') { throw new \RuntimeException('Empty upgrade wizard identifier given', 1650579934); } - if ( - !isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier]) - && !is_subclass_of($identifier, RowUpdaterInterface::class) + if (!is_subclass_of($identifier, RowUpdaterInterface::class) + && !$this->upgradeWizardRegistry->hasUpgradeWizard($identifier) ) { throw new \RuntimeException( - 'The upgrade wizard identifier "' . $identifier . '" must either be found in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'ext/install\'][\'update\'] or it must implement TYPO3\CMS\Install\Updates\RowUpdater\RowUpdaterInterface', + 'The upgrade wizard identifier "' . $identifier . '" must either be registered as upgrade wizard or it must implement TYPO3\CMS\Install\Updates\RowUpdater\RowUpdaterInterface', 1650546252 ); } diff --git a/typo3/sysext/install/Classes/ServiceProvider.php b/typo3/sysext/install/Classes/ServiceProvider.php index 9b091f523246f9d9f4b9e46b750931b3f32d0301..f7e43dc634f3a88bdc41ffb39aadaa5f446103ee 100644 --- a/typo3/sysext/install/Classes/ServiceProvider.php +++ b/typo3/sysext/install/Classes/ServiceProvider.php @@ -44,6 +44,7 @@ use TYPO3\CMS\Core\TypoScript\AST\CommentAwareAstBuilder; use TYPO3\CMS\Core\TypoScript\AST\Traverser\AstTraverser; use TYPO3\CMS\Core\TypoScript\Tokenizer\LosslessTokenizer; use TYPO3\CMS\Install\Database\PermissionsCheck; +use TYPO3\CMS\Install\Service\LateBootService; use TYPO3\CMS\Install\Service\SetupDatabaseService; use TYPO3\CMS\Install\Service\SetupService; use TYPO3\CMS\Install\Service\WebServerConfigurationFileService; @@ -346,7 +347,8 @@ class ServiceProvider extends AbstractServiceProvider 'setup', $container->get(Service\SetupDatabaseService::class), $container->get(Service\SetupService::class), - $container->get(ConfigurationManager::class) + $container->get(ConfigurationManager::class), + $container->get(LateBootService::class) ); } diff --git a/typo3/sysext/install/Classes/Updates/BackendGroupsExplicitAllowDenyMigration.php b/typo3/sysext/install/Classes/Updates/BackendGroupsExplicitAllowDenyMigration.php index cc8ce159363ceaf05770ca1f52c12a3d6d9201a4..011ed75e556c48953abc396185940984bd1afa07 100644 --- a/typo3/sysext/install/Classes/Updates/BackendGroupsExplicitAllowDenyMigration.php +++ b/typo3/sysext/install/Classes/Updates/BackendGroupsExplicitAllowDenyMigration.php @@ -22,21 +22,18 @@ use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; /** * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. */ +#[UpgradeWizard('backendGroupsExplicitAllowDenyMigration')] final class BackendGroupsExplicitAllowDenyMigration implements UpgradeWizardInterface, ChattyInterface { private const TABLE_NAME = 'be_groups'; private OutputInterface $output; - public function getIdentifier(): string - { - return 'backendGroupsExplicitAllowDenyMigration'; - } - public function getTitle(): string { return 'Migrate backend groups "explicit_allowdeny" field to simplified format.'; diff --git a/typo3/sysext/install/Classes/Updates/BackendModulePermissionMigration.php b/typo3/sysext/install/Classes/Updates/BackendModulePermissionMigration.php index e46789317cd24add86bbc2287bc2b36b9284b44e..0be7a70aef9972d53deb3a95c33d1aa2649ab9d8 100644 --- a/typo3/sysext/install/Classes/Updates/BackendModulePermissionMigration.php +++ b/typo3/sysext/install/Classes/Updates/BackendModulePermissionMigration.php @@ -21,10 +21,12 @@ use TYPO3\CMS\Backend\Module\ModuleRegistry; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; /** * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. */ +#[UpgradeWizard('backendModulePermission')] class BackendModulePermissionMigration implements UpgradeWizardInterface { protected array $aliases = []; @@ -34,11 +36,6 @@ class BackendModulePermissionMigration implements UpgradeWizardInterface $this->aliases = GeneralUtility::makeInstance(ModuleRegistry::class)->getModuleAliases(); } - public function getIdentifier(): string - { - return 'backendModulePermission'; - } - public function getTitle(): string { return 'Migrate backend user and groups to new module names.'; diff --git a/typo3/sysext/install/Classes/Updates/BackendUserLanguageMigration.php b/typo3/sysext/install/Classes/Updates/BackendUserLanguageMigration.php index 04970ab619bde862bd17287602eb17e22c865951..deb838c950e9381a3b3a52f2ba61d96581427b0a 100644 --- a/typo3/sysext/install/Classes/Updates/BackendUserLanguageMigration.php +++ b/typo3/sysext/install/Classes/Updates/BackendUserLanguageMigration.php @@ -20,19 +20,16 @@ namespace TYPO3\CMS\Install\Updates; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; /** * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. */ +#[UpgradeWizard('backendUserLanguage')] class BackendUserLanguageMigration implements UpgradeWizardInterface { private const TABLE_NAME = 'be_users'; - public function getIdentifier(): string - { - return 'backendUserLanguage'; - } - public function getTitle(): string { return 'Migrate backend users\' selected UI languages to new format.'; diff --git a/typo3/sysext/install/Classes/Updates/CollectionsExtractionUpdate.php b/typo3/sysext/install/Classes/Updates/CollectionsExtractionUpdate.php index 90a9a453cb04caadf15b35b333cb207b37fce514..a6a5913818a7d4e9cf2d610541eb6fb5f21c9167 100644 --- a/typo3/sysext/install/Classes/Updates/CollectionsExtractionUpdate.php +++ b/typo3/sysext/install/Classes/Updates/CollectionsExtractionUpdate.php @@ -20,11 +20,13 @@ namespace TYPO3\CMS\Install\Updates; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; /** * Installs and downloads EXT:legacy_collections if requested * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. */ +#[UpgradeWizard('legacyCollectionsExtension')] class CollectionsExtractionUpdate extends AbstractDownloadExtensionUpdate { /** @@ -62,15 +64,6 @@ class CollectionsExtractionUpdate extends AbstractDownloadExtensionUpdate return $this->confirmation; } - /** - * Return the identifier for this wizard - * This should be the same string as used in the ext_localconf class registration - */ - public function getIdentifier(): string - { - return 'legacyCollectionsExtension'; - } - /** * Return the speaking name of this wizard */ diff --git a/typo3/sysext/install/Classes/Updates/DatabaseRowsUpdateWizard.php b/typo3/sysext/install/Classes/Updates/DatabaseRowsUpdateWizard.php index 83217b8e8593756218da19608dfc151662cef5c8..06379ae49d8773af63bdd62f4c8060cc98267cc4 100644 --- a/typo3/sysext/install/Classes/Updates/DatabaseRowsUpdateWizard.php +++ b/typo3/sysext/install/Classes/Updates/DatabaseRowsUpdateWizard.php @@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Registry; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; use TYPO3\CMS\Install\Updates\RowUpdater\L18nDiffsourceToJsonMigration; use TYPO3\CMS\Install\Updates\RowUpdater\RowUpdaterInterface; use TYPO3\CMS\Install\Updates\RowUpdater\SysRedirectRootPageMoveMigration; @@ -45,6 +46,7 @@ use TYPO3\CMS\Install\Updates\RowUpdater\WorkspaceNewPlaceholderRemovalMigration * the job can restart at the position it stopped. * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. */ +#[UpgradeWizard('databaseRowsUpdateWizard')] class DatabaseRowsUpdateWizard implements UpgradeWizardInterface, RepeatableInterface { /** @@ -66,14 +68,6 @@ class DatabaseRowsUpdateWizard implements UpgradeWizardInterface, RepeatableInte return $this->rowUpdater; } - /** - * @return string Unique identifier of this updater - */ - public function getIdentifier(): string - { - return 'databaseRowsUpdateWizard'; - } - /** * @return string Title of this updater */ diff --git a/typo3/sysext/install/Classes/Updates/FeLoginModeExtractionUpdate.php b/typo3/sysext/install/Classes/Updates/FeLoginModeExtractionUpdate.php index 5743a718a64ac68896cf22a76cf149eb6ef71865..d1c88ec279d073cf1290d704592586a247e2bf40 100644 --- a/typo3/sysext/install/Classes/Updates/FeLoginModeExtractionUpdate.php +++ b/typo3/sysext/install/Classes/Updates/FeLoginModeExtractionUpdate.php @@ -20,11 +20,13 @@ namespace TYPO3\CMS\Install\Updates; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; /** * Installs and downloads EXT:fe_login_mode * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. */ +#[UpgradeWizard('feLoginModeExtension')] class FeLoginModeExtractionUpdate extends AbstractDownloadExtensionUpdate { private const TABLE_NAME = 'pages'; @@ -57,15 +59,6 @@ class FeLoginModeExtractionUpdate extends AbstractDownloadExtensionUpdate return $this->confirmation; } - /** - * Return the identifier for this wizard - * This should be the same string as used in the ext_localconf class registration - */ - public function getIdentifier(): string - { - return 'feLoginModeExtension'; - } - /** * Return the speaking name of this wizard */ diff --git a/typo3/sysext/install/Classes/Updates/MigrateSiteSettingsConfigUpdate.php b/typo3/sysext/install/Classes/Updates/MigrateSiteSettingsConfigUpdate.php index a91aeb431816c93228df7e0d51f0ca80d41179b3..a0f9c58aa7248b411584b047851c08ad6b0c49ed 100644 --- a/typo3/sysext/install/Classes/Updates/MigrateSiteSettingsConfigUpdate.php +++ b/typo3/sysext/install/Classes/Updates/MigrateSiteSettingsConfigUpdate.php @@ -20,12 +20,14 @@ namespace TYPO3\CMS\Install\Updates; use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException; use TYPO3\CMS\Core\Configuration\SiteConfiguration; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; /** * @internal * * The upgrade wizard cuts the settings part of the config.yaml and moves it into settings.yaml. */ +#[UpgradeWizard('migrateSiteSettings')] class MigrateSiteSettingsConfigUpdate implements UpgradeWizardInterface { protected const SETTINGS_FILENAME = 'settings.yaml'; @@ -39,11 +41,6 @@ class MigrateSiteSettingsConfigUpdate implements UpgradeWizardInterface $this->sitePathsToMigrate = $this->getSitePathsToMigrate(); } - public function getIdentifier(): string - { - return 'migrateSiteSettings'; - } - public function getTitle(): string { return 'Migrate site settings to separate file'; diff --git a/typo3/sysext/install/Classes/Updates/ShortcutRecordsMigration.php b/typo3/sysext/install/Classes/Updates/ShortcutRecordsMigration.php index cd8e9ec520d5ef58f9a1b4dedcff820c2ce5af2c..7ab45a633aba1477406b9426bdf85df6fb53e45b 100644 --- a/typo3/sysext/install/Classes/Updates/ShortcutRecordsMigration.php +++ b/typo3/sysext/install/Classes/Updates/ShortcutRecordsMigration.php @@ -22,10 +22,12 @@ use TYPO3\CMS\Backend\Routing\Router; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; /** * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. */ +#[UpgradeWizard('shortcutRecordsMigration')] class ShortcutRecordsMigration implements UpgradeWizardInterface { private const TABLE_NAME = 'sys_be_shortcuts'; @@ -33,11 +35,6 @@ class ShortcutRecordsMigration implements UpgradeWizardInterface protected ?ModuleProvider $moduleProvider = null; protected ?Router $router = null; - public function getIdentifier(): string - { - return 'shortcutRecordsMigration'; - } - public function getTitle(): string { return 'Migrate shortcut records to new format.'; diff --git a/typo3/sysext/install/Classes/Updates/SvgFilesSanitization.php b/typo3/sysext/install/Classes/Updates/SvgFilesSanitization.php index 3a3660fd69d25a6ac30287c696719b44f0abf920..af179759651f2483840297476a43edaabd5ecab9 100644 --- a/typo3/sysext/install/Classes/Updates/SvgFilesSanitization.php +++ b/typo3/sysext/install/Classes/Updates/SvgFilesSanitization.php @@ -24,7 +24,12 @@ use TYPO3\CMS\Core\Resource\ResourceStorage; use TYPO3\CMS\Core\Resource\Security\SvgSanitizer; use TYPO3\CMS\Core\Resource\StorageRepository; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; +/** + * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. + */ +#[UpgradeWizard('svgFilesSanitization')] class SvgFilesSanitization implements UpgradeWizardInterface, ConfirmableInterface { /** @@ -50,15 +55,6 @@ class SvgFilesSanitization implements UpgradeWizardInterface, ConfirmableInterfa ); } - /** - * Return the identifier for this wizard - * This should be the same string as used in the ext_localconf class registration - */ - public function getIdentifier(): string - { - return 'svgFilesSanitization'; - } - /** * Return the speaking name of this wizard */ diff --git a/typo3/sysext/install/Classes/Updates/SysFileMountIdentifierMigration.php b/typo3/sysext/install/Classes/Updates/SysFileMountIdentifierMigration.php index 02bb195c1acceaafbfd1dda804a3928dc38999b2..31f5713cfa23c0eb1c97d339023fc858593c8596 100644 --- a/typo3/sysext/install/Classes/Updates/SysFileMountIdentifierMigration.php +++ b/typo3/sysext/install/Classes/Updates/SysFileMountIdentifierMigration.php @@ -21,16 +21,16 @@ use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; +/** + * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. + */ +#[UpgradeWizard('sysFileMountIdentifierMigration')] class SysFileMountIdentifierMigration implements UpgradeWizardInterface { protected const TABLE_NAME = 'sys_filemounts'; - public function getIdentifier(): string - { - return 'sysFileMountIdentifierMigration'; - } - public function getTitle(): string { return 'Migrate base and path to the new identifier property of the "sys_filemounts" table.'; diff --git a/typo3/sysext/install/Classes/Updates/SysLogChannel.php b/typo3/sysext/install/Classes/Updates/SysLogChannel.php index 94491d4d25f9abf6027e261d98ffb325b2cba411..fb9f901dfbd57feaeabcd63a60ada54e09125b35 100644 --- a/typo3/sysext/install/Classes/Updates/SysLogChannel.php +++ b/typo3/sysext/install/Classes/Updates/SysLogChannel.php @@ -25,8 +25,13 @@ use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\SysLog\Type; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; use TYPO3\CMS\Install\Service\ClearCacheService; +/** + * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. + */ +#[UpgradeWizard('sysLogChannel')] class SysLogChannel implements UpgradeWizardInterface { protected Connection $sysLogTable; @@ -36,11 +41,6 @@ class SysLogChannel implements UpgradeWizardInterface $this->sysLogTable = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_log'); } - public function getIdentifier(): string - { - return 'sysLogChannel'; - } - public function getTitle(): string { return 'Populates a new channel column of the sys_log table.'; diff --git a/typo3/sysext/install/Classes/Updates/SysLogSerializationUpdate.php b/typo3/sysext/install/Classes/Updates/SysLogSerializationUpdate.php index b02aac0f0ad182dcf968aa9e0456030942908d66..f54714cddc197ae8ec2619a891a33455ebcfe31a 100644 --- a/typo3/sysext/install/Classes/Updates/SysLogSerializationUpdate.php +++ b/typo3/sysext/install/Classes/Updates/SysLogSerializationUpdate.php @@ -21,20 +21,17 @@ use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Log\LogDataTrait; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; /** * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. */ +#[UpgradeWizard('sysLogSerialization')] class SysLogSerializationUpdate implements UpgradeWizardInterface { use LogDataTrait; private const TABLE_NAME = 'sys_log'; - public function getIdentifier(): string - { - return 'sysLogSerialization'; - } - public function getTitle(): string { return 'Migrate sys_log entries to a JSON formatted value.'; diff --git a/typo3/sysext/install/Classes/Updates/SysTemplateNoWorkspaceMigration.php b/typo3/sysext/install/Classes/Updates/SysTemplateNoWorkspaceMigration.php index 8a095b81c9795ffcc54d475bc7679ffbb42f74d4..3b89430a69071926e5b892fc772eab6e2d3da6f3 100644 --- a/typo3/sysext/install/Classes/Updates/SysTemplateNoWorkspaceMigration.php +++ b/typo3/sysext/install/Classes/Updates/SysTemplateNoWorkspaceMigration.php @@ -22,19 +22,16 @@ use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; /** * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. */ +#[UpgradeWizard('sysTemplateNoWorkspaceMigration')] final class SysTemplateNoWorkspaceMigration implements UpgradeWizardInterface { private const TABLE_NAME = 'sys_template'; - public function getIdentifier(): string - { - return 'sysTemplateNoWorkspaceMigration'; - } - public function getTitle(): string { return 'Set workspace records in table "sys_template" to deleted.'; diff --git a/typo3/sysext/install/Classes/Updates/UpgradeWizardInterface.php b/typo3/sysext/install/Classes/Updates/UpgradeWizardInterface.php index 5e6a254ea11ee3490384fe730e2172fb5967999e..84410153fc6323dbfb7e42bb8a8393908ace1bb0 100644 --- a/typo3/sysext/install/Classes/Updates/UpgradeWizardInterface.php +++ b/typo3/sysext/install/Classes/Updates/UpgradeWizardInterface.php @@ -22,12 +22,6 @@ namespace TYPO3\CMS\Install\Updates; */ interface UpgradeWizardInterface { - /** - * Return the identifier for this wizard - * This should be the same string as used in the ext_localconf class registration - */ - public function getIdentifier(): string; - /** * Return the speaking name of this wizard */ diff --git a/typo3/sysext/install/Classes/Updates/UpgradeWizardRegistry.php b/typo3/sysext/install/Classes/Updates/UpgradeWizardRegistry.php new file mode 100644 index 0000000000000000000000000000000000000000..a09f41a27b3d57a96efe6edcd2afa203afd91d22 --- /dev/null +++ b/typo3/sysext/install/Classes/Updates/UpgradeWizardRegistry.php @@ -0,0 +1,106 @@ +<?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\Install\Updates; + +use Symfony\Component\DependencyInjection\ServiceLocator; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Registry for upgrade wizards. The registry receives all services, tagged with "install.upgradewizard". + * The tagging of upgrade wizards is automatically done based on the PHP Attribute UpgradeWizard. + * + * @internal + */ +class UpgradeWizardRegistry +{ + public function __construct( + private readonly ServiceLocator $upgradeWizards + ) { + } + + /** + * Whether a registered upgrade wizard exists for the given identifier + */ + public function hasUpgradeWizard(string $identifier): bool + { + return $this->upgradeWizards->has($identifier) || $this->getLegacyUpgradeWizardClassName($identifier) !== null; + } + + /** + * Get registered upgrade wizard by identifier + */ + public function getUpgradeWizard(string $identifier): UpgradeWizardInterface + { + if (!$this->hasUpgradeWizard($identifier)) { + throw new \UnexpectedValueException('Upgrade wizard with identifier ' . $identifier . ' is not registered.', 1673964964); + } + + return $this->getLegacyUpgradeWizard($identifier) ?? $this->upgradeWizards->get($identifier); + } + + /** + * Get all registered upgrade wizards + * + * @return array + */ + public function getUpgradeWizards(): array + { + return array_replace( + $this->upgradeWizards->getProvidedServices(), + $this->getLegacyUpgradeWizards() + ); + } + + /** + * @deprecated Remove with TYPO3 v13 + */ + private function getLegacyUpgradeWizardClassName(string $identifier): ?string + { + if (class_exists($identifier)) { + return $identifier; + } + if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier]) + && class_exists($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier]) + ) { + return $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier]; + } + return null; + } + + /** + * @deprecated Remove with TYPO3 v13 + */ + private function getLegacyUpgradeWizard(string $identifier): ?UpgradeWizardInterface + { + $className = $this->getLegacyUpgradeWizardClassName($identifier); + if ($className === null) { + return null; + } + + $instance = GeneralUtility::makeInstance($className); + return $instance instanceof UpgradeWizardInterface ? $instance : null; + } + + /** + * @deprecated Remove with TYPO3 v13 + */ + private function getLegacyUpgradeWizards(): array + { + return $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] ?? []; + } +} diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php index ee61615b05f690c3406fdadf2a2ee0f7816e09cd..1edb3099de1924c4897516c2a93b40a9f7fae7ef 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php @@ -951,4 +951,9 @@ return [ 'Deprecation-99075-Fe_usersAndFe_groupsTSconfig.rst', ], ], + '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'ext/install\'][\'update\']' => [ + 'restFiles' => [ + 'Deprecation-99586-RegistrationOfUpgradeWizardsViaGLOBALS.rst', + ], + ], ]; diff --git a/typo3/sysext/install/Configuration/Services.php b/typo3/sysext/install/Configuration/Services.php new file mode 100644 index 0000000000000000000000000000000000000000..6ac424d53a844e1b9fa743334dbd510fd7ba4f40 --- /dev/null +++ b/typo3/sysext/install/Configuration/Services.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace TYPO3\CMS\Install; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use TYPO3\CMS\Install\Attribute\UpgradeWizard; + +return static function (ContainerConfigurator $container, ContainerBuilder $containerBuilder) { + $containerBuilder->registerAttributeForAutoconfiguration( + UpgradeWizard::class, + static function (ChildDefinition $definition, UpgradeWizard $attribute): void { + $definition->addTag(UpgradeWizard::TAG_NAME, ['identifier' => $attribute->identifier]); + } + ); +}; diff --git a/typo3/sysext/install/Configuration/Services.yaml b/typo3/sysext/install/Configuration/Services.yaml index f7c5103b8b9949e2e707cb0d19607e72ecea8d6b..83376c4ebee50b4f2148aceba5511efc653d088e 100644 --- a/typo3/sysext/install/Configuration/Services.yaml +++ b/typo3/sysext/install/Configuration/Services.yaml @@ -12,6 +12,9 @@ services: autoconfigure: true public: false + TYPO3\CMS\Install\Updates\: + resource: '../Classes/Updates/*' + TYPO3\CMS\Install\Controller\BackendModuleController: tags: ['backend.controller'] @@ -35,3 +38,7 @@ services: TYPO3\CMS\Install\Service\UpgradeWizardsService: public: true + + TYPO3\CMS\Install\Updates\UpgradeWizardRegistry: + arguments: + $upgradeWizards: !tagged_locator { tag: 'install.upgradewizard', index_by: 'identifier' } diff --git a/typo3/sysext/install/ext_localconf.php b/typo3/sysext/install/ext_localconf.php deleted file mode 100644 index 780249d30cf0e274fab6ac5ce6dc5ba0c86063fd..0000000000000000000000000000000000000000 --- a/typo3/sysext/install/ext_localconf.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php - -declare(strict_types=1); - -use TYPO3\CMS\Install\Updates\BackendGroupsExplicitAllowDenyMigration; -use TYPO3\CMS\Install\Updates\BackendModulePermissionMigration; -use TYPO3\CMS\Install\Updates\BackendUserLanguageMigration; -use TYPO3\CMS\Install\Updates\CollectionsExtractionUpdate; -use TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard; -use TYPO3\CMS\Install\Updates\FeLoginModeExtractionUpdate; -use TYPO3\CMS\Install\Updates\MigrateSiteSettingsConfigUpdate; -use TYPO3\CMS\Install\Updates\ShortcutRecordsMigration; -use TYPO3\CMS\Install\Updates\SvgFilesSanitization; -use TYPO3\CMS\Install\Updates\SysFileMountIdentifierMigration; -use TYPO3\CMS\Install\Updates\SysLogChannel; -use TYPO3\CMS\Install\Updates\SysLogSerializationUpdate; -use TYPO3\CMS\Install\Updates\SysTemplateNoWorkspaceMigration; - -defined('TYPO3') or die(); - -// Row updater wizard scans all table rows for update jobs. -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['databaseRowsUpdateWizard'] = DatabaseRowsUpdateWizard::class; - -// v10->v11 wizards below this line -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['svgFilesSanitization'] = SvgFilesSanitization::class; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['shortcutRecordsMigration'] = ShortcutRecordsMigration::class; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['legacyCollectionsExtension'] = CollectionsExtractionUpdate::class; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['backendUserLanguage'] = BackendUserLanguageMigration::class; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysLogChannel'] = SysLogChannel::class; - -// v11->v12 wizards below this line -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['feLoginModeExtension'] = FeLoginModeExtractionUpdate::class; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysLogSerialization'] = SysLogSerializationUpdate::class; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['backendGroupsExplicitAllowDenyMigration'] = BackendGroupsExplicitAllowDenyMigration::class; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysFileMountIdentifierMigration'] = SysFileMountIdentifierMigration::class; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['backendModulePermission'] = BackendModulePermissionMigration::class; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysTemplateNoWorkspaceMigration'] = SysTemplateNoWorkspaceMigration::class; -$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['migrateSiteSettings'] = MigrateSiteSettingsConfigUpdate::class;