[TASK] Cleanup UpgradeWizard API 28/58128/10
authorSusanne Moog <susanne.moog@typo3.org>
Sun, 2 Sep 2018 15:50:06 +0000 (17:50 +0200)
committerSusanne Moog <susanne.moog@typo3.org>
Mon, 3 Sep 2018 12:34:30 +0000 (14:34 +0200)
The upgrade wizard API has been in need of some cleanups
both architectural as well as functional. As a result the
API is now easier to use and more streamlined, and
commands available can also be listed.

Resolves: #86106
Resolves: #86101
Releases: master
Change-Id: I1b5eb3459f0650ac1ed9e85718bfa34eb07e403d
Reviewed-on: https://review.typo3.org/58128
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
16 files changed:
typo3/sysext/install/Classes/Command/UpgradeWizardCommand.php [deleted file]
typo3/sysext/install/Classes/Command/UpgradeWizardListCommand.php [new file with mode: 0644]
typo3/sysext/install/Classes/Command/UpgradeWizardRunCommand.php [new file with mode: 0644]
typo3/sysext/install/Classes/Service/UpgradeWizardsService.php
typo3/sysext/install/Classes/Updates/ChattyInterface.php
typo3/sysext/install/Classes/Updates/ConfirmableInterface.php
typo3/sysext/install/Classes/Updates/Confirmation.php [new file with mode: 0644]
typo3/sysext/install/Classes/Updates/DatabaseUpdatedPrerequisite.php
typo3/sysext/install/Classes/Updates/MigratePagesLanguageOverlayBeGroupsAccessRights.php
typo3/sysext/install/Classes/Updates/Prerequisite.php
typo3/sysext/install/Classes/Updates/PrerequisiteCollection.php
typo3/sysext/install/Classes/Updates/ReferenceIndexUpdatedPrerequisite.php
typo3/sysext/install/Classes/Updates/RepeatableInterface.php
typo3/sysext/install/Classes/Updates/UpgradeWizardInterface.php
typo3/sysext/install/Configuration/Commands.php
typo3/sysext/install/ext_localconf.php

diff --git a/typo3/sysext/install/Classes/Command/UpgradeWizardCommand.php b/typo3/sysext/install/Classes/Command/UpgradeWizardCommand.php
deleted file mode 100644 (file)
index 4e01867..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-<?php
-declare(strict_types = 1);
-
-namespace TYPO3\CMS\Install\Command;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputArgument;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Console\Question\ConfirmationQuestion;
-use TYPO3\CMS\Core\Registry;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Install\Service\UpgradeWizardsService;
-use TYPO3\CMS\Install\Updates\ChattyInterface;
-use TYPO3\CMS\Install\Updates\ConfirmableInterface;
-use TYPO3\CMS\Install\Updates\PrerequisiteCollection;
-use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
-
-/**
- * Core function for updating language packs
- */
-class UpgradeWizardCommand extends Command
-{
-    /**
-     * @var UpgradeWizardsService
-     */
-    private $upgradeWizardsService;
-
-    protected function bootstrap(): void
-    {
-        \TYPO3\CMS\Core\Core\Bootstrap::loadTypo3LoadedExtAndExtLocalconf(false);
-        \TYPO3\CMS\Core\Core\Bootstrap::unsetReservedGlobalVariables();
-        \TYPO3\CMS\Core\Core\Bootstrap::loadBaseTca(false);
-        \TYPO3\CMS\Core\Core\Bootstrap::loadExtTables(false);
-    }
-
-    /**
-     * Configure the command by defining the name, options and arguments
-     */
-    protected function configure()
-    {
-        $this->setAliases(['install:wizards']);
-        $this->setDescription('Update the language files of all activated extensions')
-            ->addArgument(
-                'wizardName',
-                InputArgument::OPTIONAL
-            )->addOption('dry-run', 'd');
-    }
-
-    /**
-     * Update language packs of all active languages for all active extensions
-     *
-     * @param InputInterface $input
-     * @param OutputInterface $output
-     */
-    protected function execute(InputInterface $input, OutputInterface $output)
-    {
-        $this->bootstrap();
-        $this->upgradeWizardsService = new UpgradeWizardsService();
-
-        if ($input->getArgument('wizardName')) {
-            $wizardToExecute = $input->getArgument('wizardName');
-            $this->runSingleWizard($input, $output, $wizardToExecute);
-        } else {
-            $this->runAllWizards($input, $output);
-        }
-    }
-
-    /**
-     * Handles prerequisites of update wizards, allows a more flexible definition and declaration of dependencies
-     * Currently implemented prerequisites include "database needs to be up-to-date" and "referenceIndex needs to be up-
-     * to-date"
-     * At the moment the install tool automatically displays the database updates when necessary but can't do more
-     * prerequisites
-     *
-     * @todo handle in install tool
-     * @param \Symfony\Component\Console\Output\OutputInterface $output
-     * @param UpgradeWizardInterface[]
-     */
-    protected function handlePrerequisites(OutputInterface $output, array $instances): void
-    {
-        $collection = GeneralUtility::makeInstance(PrerequisiteCollection::class);
-        foreach ($instances as $instance) {
-            foreach ($instance->getPrerequisites() as $prerequisite) {
-                $collection->addPrerequisite($prerequisite);
-            }
-        }
-        foreach ($collection->getPrerequisites() as $prerequisite) {
-            if (!$prerequisite->met()) {
-                $output->writeln('Prerequisite "' . $prerequisite->getName() . '" not met, will ensure.');
-                $prerequisite->ensure();
-            } else {
-                $output->writeln('Prerequisite "' . $prerequisite->getName() . '" met.');
-            }
-        }
-    }
-
-    /**
-     * @param \Symfony\Component\Console\Input\InputInterface $input
-     * @param \Symfony\Component\Console\Output\OutputInterface $output
-     * @param UpgradeWizardInterface $instance
-     * @return int
-     */
-    protected function runSingleWizard(
-        InputInterface $input,
-        OutputInterface $output,
-        UpgradeWizardInterface $instance
-    ): int {
-        $output->writeln('Running Wizard ' . $instance->getTitle());
-        if ($instance instanceof ConfirmableInterface) {
-            $defaultString = $instance->getConfirmationDefault() ? '(Y/n)' : '(y/N)';
-            $question = new ConfirmationQuestion(
-                '<info>' .
-                $instance->getConfirmationTitle() .
-                '</info>' .
-                "\r\n" .
-                $instance->getConfirmationMessage() .
-                ' ' .
-                $defaultString,
-                $instance->getConfirmationDefault()
-            );
-            $helper = $this->getHelper('question');
-            if (!$helper->ask($input, $output, $question)) {
-                $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
-                return 0;
-            }
-        }
-        if ($instance->executeUpdate()) {
-            $output->writeln('<info>Successfully ran wizard ' . $instance->getTitle() . '</info>');
-            $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
-            return 0;
-        }
-        $output->writeln('<error>Something went wrong while running ' . $instance->getTitle() . '</error>');
-        return 1;
-    }
-
-    /**
-     * Get list of registered upgrade wizards.
-     *
-     * @param InputInterface $input
-     * @param OutputInterface $output
-     * @return array List of upgrade wizards in correct order with detail information
-     */
-    public function runAllWizards(InputInterface $input, OutputInterface $output): array
-    {
-        $wizards = [];
-        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $class) {
-            $wizardInstance = GeneralUtility::makeInstance($class);
-            $registry = GeneralUtility::makeInstance(Registry::class);
-            $markedDoneInRegistry = $registry->get(
-                'installUpdate',
-                $identifier,
-                false
-            );
-            // already done
-            if ($markedDoneInRegistry) {
-                continue;
-            }
-            // not cli runnable
-            if (!($wizardInstance instanceof UpgradeWizardInterface)) {
-                continue;
-            }
-
-            if ($wizardInstance instanceof ChattyInterface) {
-                $wizardInstance->setOutput($output);
-            }
-
-            if ($wizardInstance->updateNecessary()) {
-                $wizardInstances = [];
-                if (!($wizardInstance instanceof UpgradeWizardInterface)) {
-                    $output->writeln(
-                        'Wizard ' .
-                        $class .
-                        ' needs to be manually run from the install tool, as it does not implement ' .
-                        UpgradeWizardInterface::class
-                    );
-                } else {
-                    $wizardInstances[] = $wizardInstance;
-                }
-                if (count($wizardInstances) > 0) {
-                    $this->handlePrerequisites($output, $wizardInstances);
-                    foreach ($wizardInstances as $wizardInstance) {
-                        $this->runSingleWizard($input, $output, $wizardInstance);
-                    }
-                }
-            }
-        }
-        return $wizards;
-    }
-}
diff --git a/typo3/sysext/install/Classes/Command/UpgradeWizardListCommand.php b/typo3/sysext/install/Classes/Command/UpgradeWizardListCommand.php
new file mode 100644 (file)
index 0000000..c53d8db
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Install\Command;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+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\Registry;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Install\Service\UpgradeWizardsService;
+use TYPO3\CMS\Install\Updates\ChattyInterface;
+use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
+
+/**
+ * Upgrade wizard command for listing wizards
+ *
+ * @internal
+ */
+class UpgradeWizardListCommand extends Command
+{
+    /**
+     * @var UpgradeWizardsService
+     */
+    private $upgradeWizardsService;
+
+    /**
+     * @var OutputInterface|\Symfony\Component\Console\Style\StyleInterface
+     */
+    private $output;
+
+    /**
+     * @var InputInterface
+     */
+    private $input;
+
+    /**
+     * Bootstrap running of upgradeWizards
+     */
+    protected function bootstrap(): void
+    {
+        Bootstrap::loadTypo3LoadedExtAndExtLocalconf(false);
+        Bootstrap::unsetReservedGlobalVariables();
+        Bootstrap::loadBaseTca(false);
+        Bootstrap::loadExtTables(false);
+        Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
+        Bootstrap::initializeBackendAuthentication();
+    }
+
+    /**
+     * Configure the command by defining the name, options and arguments
+     */
+    protected function configure()
+    {
+        $this->setDescription('List available upgrade wizards.')
+            ->addOption(
+                'all',
+                'a',
+                InputOption::VALUE_NONE,
+                'Include wizards already done.'
+            );
+    }
+
+    /**
+     * List available upgrade wizards. If -all is given, already done wizards are listed, too.
+     *
+     * @param InputInterface $input
+     * @param \Symfony\Component\Console\Output\OutputInterface $output
+     * @return int
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $this->output = new SymfonyStyle($input, $output);
+        $this->input = $input;
+        $this->bootstrap();
+        $this->upgradeWizardsService = new UpgradeWizardsService();
+
+        $result = 0;
+        $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);
+            if ($upgradeWizard !== null) {
+                $wizardInfo = [
+                    'identifier' => $upgradeWizard->getIdentifier(),
+                    'title' => $upgradeWizard->getTitle(),
+                    'description' => wordwrap($upgradeWizard->getDescription()),
+                ];
+                if ($all === true) {
+                    $wizardInfo['status'] = $this->upgradeWizardsService->isWizardDone($identifier) ? 'DONE' : 'AVAILABLE';
+                }
+                $wizards[] = $wizardInfo;
+            }
+        }
+        if (empty($wizards)) {
+            $this->output->success('No wizards available.');
+        } else {
+            if ($all === true) {
+                $this->output->table(['Identifier', 'Title', 'Description', 'Status'], $wizards);
+            } else {
+                $this->output->table(['Identifier', 'Title', 'Description'], $wizards);
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * Get Wizard instance by class name and identifier
+     * Returns null if wizard is already done
+     *
+     * @param string $className
+     * @param string $identifier
+     * @param bool $all
+     * @return \TYPO3\CMS\Install\Updates\UpgradeWizardInterface|null
+     */
+    protected function getWizard(string $className, string $identifier, $all = false): ?UpgradeWizardInterface
+    {
+        $registry = GeneralUtility::makeInstance(Registry::class);
+        $markedDoneInRegistry = $registry->get(
+            'installUpdate',
+            $identifier,
+            false
+        );
+        // already done
+        if (!$all && $markedDoneInRegistry) {
+            return null;
+        }
+
+        $wizardInstance = GeneralUtility::makeInstance($className);
+        if ($wizardInstance instanceof ChattyInterface) {
+            $wizardInstance->setOutput($this->output);
+        }
+
+        if (!($wizardInstance instanceof UpgradeWizardInterface)) {
+            return null;
+        }
+
+        return !$all ? $wizardInstance->updateNecessary() ? $wizardInstance : null : $wizardInstance;
+    }
+}
diff --git a/typo3/sysext/install/Classes/Command/UpgradeWizardRunCommand.php b/typo3/sysext/install/Classes/Command/UpgradeWizardRunCommand.php
new file mode 100644 (file)
index 0000000..10ed7e6
--- /dev/null
@@ -0,0 +1,272 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Install\Command;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ConfirmationQuestion;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication;
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Registry;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Install\Service\UpgradeWizardsService;
+use TYPO3\CMS\Install\Updates\ChattyInterface;
+use TYPO3\CMS\Install\Updates\ConfirmableInterface;
+use TYPO3\CMS\Install\Updates\PrerequisiteCollection;
+use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
+
+/**
+ * Upgrade wizard command for running wizards
+ *
+ * @internal
+ */
+class UpgradeWizardRunCommand extends Command
+{
+    /**
+     * @var UpgradeWizardsService
+     */
+    private $upgradeWizardsService;
+
+    /**
+     * @var OutputInterface|\Symfony\Component\Console\Style\StyleInterface
+     */
+    private $output;
+
+    /**
+     * @var InputInterface
+     */
+    private $input;
+
+    /**
+     * Bootstrap running of upgrade wizard,
+     * ensure database is utf-8
+     */
+    protected function bootstrap(): void
+    {
+        Bootstrap::loadTypo3LoadedExtAndExtLocalconf(false);
+        Bootstrap::unsetReservedGlobalVariables();
+        Bootstrap::loadBaseTca(false);
+        Bootstrap::loadExtTables(false);
+        Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
+        Bootstrap::initializeBackendAuthentication();
+        $this->upgradeWizardsService = new UpgradeWizardsService();
+        $this->upgradeWizardsService->isDatabaseCharsetUtf8() ?: $this->upgradeWizardsService->setDatabaseCharsetUtf8();
+        $this->upgradeWizardsService->silentCacheFrameworkTableSchemaMigration();
+    }
+
+    /**
+     * Configure the command by defining the name, options and arguments
+     */
+    protected function configure()
+    {
+        $this->setDescription('Run upgrade wizard. Without arguments all available wizards will be run.')
+            ->addArgument(
+                'wizardName',
+                InputArgument::OPTIONAL
+            )->setHelp(
+                'This command allows running upgrade wizards on CLI. To run a single wizard add the ' .
+                'identifier of the wizard as argument. The identifier of the wizard is the name it is ' .
+                'registered with in ext_localconf.'
+            );
+    }
+
+    /**
+     * Update language packs of all active languages for all active extensions
+     *
+     * @param InputInterface $input
+     * @param \Symfony\Component\Console\Output\OutputInterface $output
+     * @return int
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $this->output = new SymfonyStyle($input, $output);
+        $this->input = $input;
+        $this->bootstrap();
+
+        $result = 0;
+        if ($input->getArgument('wizardName')) {
+            $wizardToExecute = $input->getArgument('wizardName');
+            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) {
+                    $this->handlePrerequisites([$upgradeWizard]);
+                    $result = $this->runSingleWizard($upgradeWizard);
+                } else {
+                    $this->output->warning('Wizard ' . $wizardToExecute . ' is already done.');
+                }
+            } else {
+                $this->output->error('No such wizard: ' . $wizardToExecute);
+                $result = 1;
+            }
+        } else {
+            $result = $this->runAllWizards();
+        }
+        return $result;
+    }
+
+    /**
+     * Get Wizard instance by class name and identifier
+     * Returns null if wizard is already done
+     *
+     * @param $className
+     * @param $identifier
+     * @return \TYPO3\CMS\Install\Updates\UpgradeWizardInterface|null
+     */
+    protected function getWizard(string $className, string $identifier): ?UpgradeWizardInterface
+    {
+        $registry = GeneralUtility::makeInstance(Registry::class);
+        $markedDoneInRegistry = $registry->get(
+            'installUpdate',
+            $identifier,
+            false
+        );
+        // already done
+        if ($markedDoneInRegistry) {
+            return null;
+        }
+
+        $wizardInstance = GeneralUtility::makeInstance($className);
+        if ($wizardInstance instanceof ChattyInterface) {
+            $wizardInstance->setOutput($this->output);
+        }
+
+        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;
+        }
+
+        return $wizardInstance->updateNecessary() ? $wizardInstance : null;
+    }
+
+    /**
+     * Handles prerequisites of update wizards, allows a more flexible definition and declaration of dependencies
+     * Currently implemented prerequisites include "database needs to be up-to-date" and "referenceIndex needs to be up-
+     * to-date"
+     * At the moment the install tool automatically displays the database updates when necessary but can't do more
+     * prerequisites
+     *
+     * @param UpgradeWizardInterface[] $instances
+     * @return bool
+     */
+    protected function handlePrerequisites(array $instances): bool
+    {
+        $prerequisites = GeneralUtility::makeInstance(PrerequisiteCollection::class);
+        foreach ($instances as $instance) {
+            foreach ($instance->getPrerequisites() as $prerequisite) {
+                $prerequisites->add($prerequisite);
+            }
+        }
+        $result = true;
+        foreach ($prerequisites as $prerequisite) {
+            if ($prerequisite instanceof ChattyInterface) {
+                $prerequisite->setOutput($this->output);
+            }
+            if (!$prerequisite->isFulfilled()) {
+                $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" not fulfilled, will ensure.');
+                $result = $prerequisite->ensure();
+                if ($result === false) {
+                    $this->output->error(
+                        '<error>Error running ' .
+                        $prerequisite->getTitle() .
+                        '. Please ensure this prerequisite manually and try again.</error>'
+                    );
+                    break;
+                }
+            } else {
+                $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" fulfilled.');
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * @param UpgradeWizardInterface $instance
+     * @return int
+     */
+    protected function runSingleWizard(
+        UpgradeWizardInterface $instance
+    ): int {
+        $this->output->title('Running Wizard ' . $instance->getTitle());
+        if ($instance instanceof ConfirmableInterface) {
+            $confirmation = $instance->getConfirmation();
+            $defaultString = $confirmation->getDefaultValue() ? '(Y/n)' : '(y/N)';
+            $question = new ConfirmationQuestion(
+                sprintf(
+                    '<info>%s</info>' . LF . '%s %s',
+                    $confirmation->getTitle(),
+                    $confirmation->getMessage(),
+                    $defaultString
+                ),
+                $confirmation->getDefaultValue()
+            );
+            $helper = $this->getHelper('question');
+            if (!$helper->ask($this->input, $this->output, $question)) {
+                $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
+                return 0;
+            }
+        }
+        if ($instance->executeUpdate()) {
+            $this->output->success('Successfully ran wizard ' . $instance->getTitle());
+            $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
+            return 0;
+        }
+        $this->output->error('<error>Something went wrong while running ' . $instance->getTitle() . '</error>');
+        return 1;
+    }
+
+    /**
+     * Get list of registered upgrade wizards.
+     *
+     * @return int 0 if all wizards were successful, 1 on error
+     */
+    public function runAllWizards(): int
+    {
+        $returnCode = 1;
+        $wizardInstances = [];
+        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $class) {
+            $wizardInstances[] = $this->getWizard($class, $identifier);
+        }
+        $wizardInstances = array_filter($wizardInstances);
+        if (count($wizardInstances) > 0) {
+            $prerequisitesResult = $this->handlePrerequisites($wizardInstances);
+            if ($prerequisitesResult === false) {
+                $returnCode = 1;
+                $this->output->error('Error handling prerequisites, aborting.');
+            } else {
+                $this->output->title('Found ' . count($wizardInstances) . ' wizard(s) to run.');
+                foreach ($wizardInstances as $wizardInstance) {
+                    $result = $this->runSingleWizard($wizardInstance);
+                    if ($result > 0) {
+                        $returnCode = 1;
+                    }
+                }
+            }
+        } else {
+            $this->output->success('No wizards left to run.');
+        }
+        return $returnCode;
+    }
+}
index e98f985..27f0388 100644 (file)
@@ -19,6 +19,7 @@ namespace TYPO3\CMS\Install\Service;
 use Doctrine\DBAL\Platforms\MySqlPlatform;
 use Doctrine\DBAL\Schema\Column;
 use Doctrine\DBAL\Schema\Table;
+use Symfony\Component\Console\Output\Output;
 use Symfony\Component\Console\Output\StreamOutput;
 use TYPO3\CMS\Core\Cache\DatabaseSchemaService;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -44,7 +45,7 @@ class UpgradeWizardsService
 
     public function __construct()
     {
-        $this->output = new StreamOutput(fopen('php://temp', 'wb'));
+        $this->output = new StreamOutput(fopen('php://temp', 'wb'), Output::VERBOSITY_NORMAL, false);
     }
 
     /**
@@ -209,12 +210,12 @@ class UpgradeWizardsService
     /**
      * Add missing tables, indexes and fields to DB.
      */
-    public function addMissingTablesAndFields()
+    public function addMissingTablesAndFields(): array
     {
         $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
         $databaseDefinitions = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
         $schemaMigrator = GeneralUtility::makeInstance(SchemaMigrator::class);
-        $schemaMigrator->install($databaseDefinitions, true);
+        return $schemaMigrator->install($databaseDefinitions, true);
     }
 
     /**
@@ -289,6 +290,7 @@ class UpgradeWizardsService
                     $wizardInstance->setOutput($this->output);
                 }
                 $shouldRenderWizard = $wizardInstance->updateNecessary();
+                $explanation = $wizardInstance->getDescription();
             }
 
             $wizards[] = [
@@ -334,11 +336,11 @@ class UpgradeWizardsService
             $wizardHtml = '
             <div class="panel panel-danger">
                 <div class="panel-heading">' .
-                          htmlspecialchars($updateObject->getConfirmationTitle()) .
+                          htmlspecialchars($updateObject->getConfirmation()->getTitle()) .
                           '</div>
                 <div class="panel-body">
                     ' .
-                          nl2br(htmlspecialchars($updateObject->getConfirmationMessage())) .
+                          nl2br(htmlspecialchars($updateObject->getConfirmation()->getMessage())) .
                           '
                     <div class="btn-group clearfix" data-toggle="buttons">
                         <label class="btn btn-default active">
@@ -483,14 +485,6 @@ class UpgradeWizardsService
     }
 
     /**
-     * @param string $identifier
-     */
-    public function markWizardAsUndone(string $identifier): void
-    {
-        GeneralUtility::makeInstance(Registry::class)->set('installUpdate', $identifier, 1);
-    }
-
-    /**
      * Checks if this wizard has been "done" before
      *
      * @param string $identifier
index d4a4caa..5af9899 100644 (file)
@@ -25,5 +25,10 @@ use Symfony\Component\Console\Output\OutputInterface;
  */
 interface ChattyInterface
 {
+    /**
+     * Setter injection for output into upgrade wizards
+     *
+     * @param \Symfony\Component\Console\Output\OutputInterface $output
+     */
     public function setOutput(OutputInterface $output): void;
 }
index 63e8aec..beb9d87 100644 (file)
@@ -23,7 +23,10 @@ namespace TYPO3\CMS\Install\Updates;
  */
 interface ConfirmableInterface
 {
-    public function getConfirmationTitle(): string;
-    public function getConfirmationMessage(): string;
-    public function getConfirmationDefault(): bool;
+    /**
+     * Return a confirmation message instance
+     *
+     * @return \TYPO3\CMS\Install\Updates\Confirmation
+     */
+    public function getConfirmation(): Confirmation;
 }
diff --git a/typo3/sysext/install/Classes/Updates/Confirmation.php b/typo3/sysext/install/Classes/Updates/Confirmation.php
new file mode 100644 (file)
index 0000000..b430869
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Install\Updates;
+
+/*
+ * 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!
+ */
+
+class Confirmation
+{
+    /**
+     * @var bool
+     */
+    protected $defaultValue = false;
+
+    /**
+     * @var string
+     */
+    protected $title = '';
+
+    /**
+     * @var string
+     */
+    protected $message = '';
+
+    /**
+     * @param string $title
+     * @param string $message
+     * @param bool $defaultValue
+     */
+    public function __construct(string $title, string $message, bool $defaultValue = false)
+    {
+        $this->title = $title;
+        $this->message = $message;
+        $this->defaultValue = $defaultValue;
+    }
+
+    /**
+     * @return bool
+     */
+    public function getDefaultValue(): bool
+    {
+        return $this->defaultValue;
+    }
+
+    /**
+     * @return string
+     */
+    public function getTitle(): string
+    {
+        return $this->title;
+    }
+
+    /**
+     * @return string
+     */
+    public function getMessage(): string
+    {
+        return $this->message;
+    }
+}
index 2d71d44..6c4353f 100644 (file)
@@ -3,39 +3,78 @@ declare(strict_types = 1);
 
 namespace TYPO3\CMS\Install\Updates;
 
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Symfony\Component\Console\Output\OutputInterface;
 use TYPO3\CMS\Install\Service\UpgradeWizardsService;
 
-class DatabaseUpdatedPrerequisite implements Prerequisite
+/**
+ * Prerequisite for upgrade wizards to ensure the database is up-to-date
+ *
+ * @internal
+ */
+class DatabaseUpdatedPrerequisite implements Prerequisite, ChattyInterface
 {
+    /**
+     * @var UpgradeWizardsService
+     */
     protected $upgradeWizardsService;
+    /**
+     * @var \Symfony\Component\Console\Output\OutputInterface
+     */
+    protected $output;
 
     public function __construct()
     {
         $this->upgradeWizardsService = new UpgradeWizardsService();
     }
 
-    public function getName(): string
+    /**
+     * @return string
+     */
+    public function getTitle(): string
     {
         return 'Database Up-to-Date';
     }
 
-    public function ensure(): void
+    /**
+     */
+    public function ensure(): bool
     {
         $adds = $this->upgradeWizardsService->getBlockingDatabaseAdds();
-
+        $result = null;
         if (count($adds) > 0) {
-            $this->upgradeWizardsService->addMissingTablesAndFields();
+            $this->output->writeln('Performing ' . count($adds) . ' database operations.');
+            $result = $this->upgradeWizardsService->addMissingTablesAndFields();
         }
+        return $result === null;
     }
 
-    public function met(): bool
+    /**
+     * @return bool
+     */
+    public function isFulfilled(): bool
     {
         $adds = $this->upgradeWizardsService->getBlockingDatabaseAdds();
-        return $adds === 0;
+        return count($adds) === 0;
     }
 
-    public function getIdentifier(): string
+    /**
+     * @param \Symfony\Component\Console\Output\OutputInterface $output
+     */
+    public function setOutput(OutputInterface $output): void
     {
-        return 'databaseUpdatePrerequisite';
+        $this->output = $output;
     }
 }
index 011a72f..c2a0bff 100644 (file)
@@ -27,7 +27,7 @@ class MigratePagesLanguageOverlayBeGroupsAccessRights implements UpgradeWizardIn
 {
     public function getIdentifier(): string
     {
-        return self::class;
+        return 'pagesLanguageOverlayBeGroupsAccessRights';
     }
 
     public function getTitle(): string
@@ -99,11 +99,17 @@ class MigratePagesLanguageOverlayBeGroupsAccessRights implements UpgradeWizardIn
         return true;
     }
 
+    /**
+     * @return bool
+     */
     public function updateNecessary(): bool
     {
         return !(new UpgradeWizardsService())->isWizardDone($this->getIdentifier());
     }
 
+    /**
+     * @return string[]
+     */
     public function getPrerequisites(): array
     {
         return [
@@ -118,18 +124,16 @@ class MigratePagesLanguageOverlayBeGroupsAccessRights implements UpgradeWizardIn
                'access restrictions to pages_language_overlay into pages.';
     }
 
-    public function getConfirmationTitle(): string
+    /**
+     * @return Confirmation
+     */
+    public function getConfirmation(): Confirmation
     {
-        return 'Are you sure?';
-    }
-
-    public function getConfirmationMessage(): string
-    {
-        return 'Do you want to continue?';
-    }
-
-    public function getConfirmationDefault(): bool
-    {
-        return false;
+        return GeneralUtility::makeInstance(
+            Confirmation::class,
+            'Are you sure?',
+            'Do you want to continue?',
+            false
+        );
     }
 }
index 556b8dc..f467708 100644 (file)
@@ -23,8 +23,34 @@ namespace TYPO3\CMS\Install\Updates;
  */
 interface Prerequisite
 {
-    public function getIdentifier(): string;
-    public function getName(): string;
-    public function ensure(): void;
-    public function met(): bool;
+    /**
+     * Get speaking name of this prerequisite
+     *
+     * @return string
+     */
+    public function getTitle(): string;
+
+    /**
+     * Ensure this prerequisite is fulfilled
+     *
+     * Gets called if "isFulfilled" returns false
+     * and should ensure the prerequisite
+     *
+     * Returns true on success, false on error
+     *
+     * @see isFulfilled
+     * @return bool
+     */
+    public function ensure(): bool;
+
+    /**
+     * Is this prerequisite met?
+     *
+     * Checks whether this prerequisite is fulfilled. If it is not,
+     * ensure should be called to fulfill it.
+     *
+     * @see ensure
+     * @return bool
+     */
+    public function isFulfilled(): bool;
 }
index ccf062a..3dbbdf1 100644 (file)
@@ -1,30 +1,53 @@
 <?php
 declare(strict_types = 1);
-
 namespace TYPO3\CMS\Install\Updates;
 
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
-class PrerequisiteCollection
+class PrerequisiteCollection implements \IteratorAggregate
 {
+    /**
+     * @var \ArrayObject
+     */
     protected $prerequisites;
 
     public function __construct()
     {
-        $this->prerequisites = new \SplObjectStorage();
+        $this->prerequisites = new \ArrayObject();
     }
 
-    public function addPrerequisite(string $prerequisiteClass): void
+    /**
+     * @param string $prerequisiteClass
+     */
+    public function add(string $prerequisiteClass): void
     {
-        if (class_exists($prerequisiteClass) && is_a($prerequisiteClass, Prerequisite::class, true)) {
-            $instance = GeneralUtility::makeInstance($prerequisiteClass);
-            if (!$this->prerequisites->contains($instance)) {
-                $this->prerequisites->attach($instance);
-            }
+        if (
+            !($this->prerequisites[$prerequisiteClass] ?? false)
+            && is_a($prerequisiteClass, Prerequisite::class, true)
+        ) {
+            $this->prerequisites[$prerequisiteClass] = GeneralUtility::makeInstance(
+                $prerequisiteClass
+            );
         }
     }
 
-    public function getPrerequisites(): \SplObjectStorage
+    /**
+     * @return \ArrayObject|\Traversable|Prerequisite[]
+     */
+    public function getIterator()
     {
         return $this->prerequisites;
     }
index 031f1a6..f360069 100644 (file)
@@ -3,38 +3,90 @@ declare(strict_types = 1);
 
 namespace TYPO3\CMS\Install\Updates;
 
-use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Core\Bootstrap;
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Symfony\Component\Console\Output\OutputInterface;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
-class ReferenceIndexUpdatedPrerequisite implements Prerequisite
+/**
+ * ReferenceIndex Prerequisite
+ *
+ * Defines that the reference index needs to be up-to-date before an upgrade wizard may be run
+ *
+ * @internal
+ */
+class ReferenceIndexUpdatedPrerequisite implements Prerequisite, ChattyInterface
 {
+    /**
+     * @var object|\TYPO3\CMS\Core\Database\ReferenceIndex
+     */
     private $referenceIndex;
+    /**
+     * @var \Symfony\Component\Console\Output\OutputInterface
+     */
+    protected $output;
 
+    /**
+     * ReferenceIndexUpdatedPrerequisite constructor
+     */
     public function __construct()
     {
-        if (!($GLOBALS['BE_USER'] instanceof BackendUserAuthentication)) {
-            Bootstrap::initializeBackendAuthentication();
-        }
         $this->referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
     }
 
-    public function getName(): string
+    /**
+     * @inheritdoc
+     */
+    public function getTitle(): string
     {
         return 'Reference Index Up-to-Date';
     }
 
-    public function ensure(): void
+    /**
+     * Updates the reference index
+     *
+     * @return bool
+     */
+    public function ensure(): bool
     {
         $this->referenceIndex->enableRuntimeCache();
-        $this->referenceIndex->updateIndex(false, false);
+        ob_clean();
+        ob_start();
+        $result = $this->referenceIndex->updateIndex(false, true);
+        $output = ob_get_clean();
+        $this->output->write($output);
+        return $result[2] === 0;
     }
 
-    public function met(): bool
+    /**
+     * Checks whether there are reference index updates to be done
+     *
+     * @return bool
+     */
+    public function isFulfilled(): bool
     {
         $this->referenceIndex->enableRuntimeCache();
         $result = $this->referenceIndex->updateIndex(true, false);
         return $result['errorCount'] === 0;
     }
+
+    /**
+     * @param \Symfony\Component\Console\Output\OutputInterface $output
+     */
+    public function setOutput(OutputInterface $output): void
+    {
+        $this->output = $output;
+    }
 }
index b70a98c..a2a9fad 100644 (file)
@@ -17,7 +17,7 @@ namespace TYPO3\CMS\Install\Updates;
  */
 
 /**
- * Use if wizard may be run multiple times
+ * Use if wizard may be run multiple times (and should not be disabled after one run)
  *
  * Semantic interface only
  *
index e6a59f3..fbac900 100644 (file)
@@ -1,6 +1,5 @@
 <?php
 declare(strict_types = 1);
-
 namespace TYPO3\CMS\Install\Updates;
 
 /*
@@ -23,15 +22,54 @@ 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
+     *
+     * @return string
+     */
     public function getIdentifier(): string;
 
+    /**
+     * Return the speaking name of this wizard
+     *
+     * @return string
+     */
     public function getTitle(): string;
 
+    /**
+     * Return the description for this wizard
+     *
+     * @return string
+     */
     public function getDescription(): string;
 
+    /**
+     * Execute the update
+     *
+     * Called when a wizard reports that an update is necessary
+     *
+     * @return bool
+     */
     public function executeUpdate(): bool;
 
+    /**
+     * Is an update necessary?
+     *
+     * Is used to determine whether a wizard needs to be run.
+     * Check if data for migration exists.
+     *
+     * @return bool
+     */
     public function updateNecessary(): bool;
 
+    /**
+     * Returns an array of class names of Prerequisite classes
+     *
+     * This way a wizard can define dependencies like "database up-to-date" or
+     * "reference index updated"
+     *
+     * @return string[]
+     */
     public function getPrerequisites(): array;
 }
index e745903..872a356 100644 (file)
@@ -11,7 +11,10 @@ return [
     'language:update' => [
         'class' => \TYPO3\CMS\Install\Command\LanguagePackCommand::class
     ],
-    'install:update' => [
-        'class' => \TYPO3\CMS\Install\Command\UpgradeWizardCommand::class
+    'upgrade:run' => [
+        'class' => \TYPO3\CMS\Install\Command\UpgradeWizardRunCommand::class
+    ],
+    'upgrade:list' => [
+        'class' => \TYPO3\CMS\Install\Command\UpgradeWizardListCommand::class
     ]
 ];
index 5312f59..f9c0638 100644 (file)
@@ -40,8 +40,6 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['rtehtmlareaE
     = \TYPO3\CMS\Install\Updates\RteHtmlAreaExtractionUpdate::class;
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysLanguageSorting']
     = \TYPO3\CMS\Install\Updates\LanguageSortingUpdate::class;
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['dummy']
-    = \TYPO3\CMS\Install\Updates\DummyWizard::class;
 
 // Add update wizards below this line
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['typo3DbLegacyExtension']