[FEATURE] New API for UpgradeWizards 14/58114/5
authorSusanne Moog <susanne.moog@typo3.org>
Thu, 30 Aug 2018 21:45:52 +0000 (23:45 +0200)
committerSusanne Moog <susanne.moog@typo3.org>
Sat, 1 Sep 2018 20:50:25 +0000 (22:50 +0200)
Build a new Interface based API for UpgradeWizards that
can be used as a base for future improvements.

Resolves: #86076
Releases: master
Change-Id: Ia059dc295e00f5f39849d8dfbc009bf7d8652aba
Reviewed-on: https://review.typo3.org/58114
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
15 files changed:
typo3/sysext/core/Documentation/Changelog/master/Feature-86076-NewAPIForUpgradeWizards.rst [new file with mode: 0644]
typo3/sysext/install/Classes/Command/UpgradeWizardCommand.php [new file with mode: 0644]
typo3/sysext/install/Classes/Service/UpgradeWizardsService.php
typo3/sysext/install/Classes/Updates/AbstractUpdate.php
typo3/sysext/install/Classes/Updates/ChattyInterface.php [new file with mode: 0644]
typo3/sysext/install/Classes/Updates/ConfirmableInterface.php [new file with mode: 0644]
typo3/sysext/install/Classes/Updates/DatabaseUpdatedPrerequisite.php [new file with mode: 0644]
typo3/sysext/install/Classes/Updates/MigratePagesLanguageOverlayBeGroupsAccessRights.php
typo3/sysext/install/Classes/Updates/Prerequisite.php [new file with mode: 0644]
typo3/sysext/install/Classes/Updates/PrerequisiteCollection.php [new file with mode: 0644]
typo3/sysext/install/Classes/Updates/ReferenceIndexUpdatedPrerequisite.php [new file with mode: 0644]
typo3/sysext/install/Classes/Updates/RepeatableInterface.php [new file with mode: 0644]
typo3/sysext/install/Classes/Updates/UpgradeWizardInterface.php [new file with mode: 0644]
typo3/sysext/install/Configuration/Commands.php
typo3/sysext/install/ext_localconf.php

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-86076-NewAPIForUpgradeWizards.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-86076-NewAPIForUpgradeWizards.rst
new file mode 100644 (file)
index 0000000..663a4f3
--- /dev/null
@@ -0,0 +1,30 @@
+.. include:: ../../Includes.txt
+
+============================================
+Feature: #86076 - New API for UpgradeWizards
+============================================
+
+See :issue:`86076`
+
+Description
+===========
+
+Up until now the UpgradeWizards were based on an abstract class `AbstractUpdate`. 
+An interface based API has been introduced. This API is currently internal and will be refined by 
+using it in the core update wizards. Once it is stabilized it will be made public and official.
+
+Currently the API contains of
+
+- `UpgradeWizardInterface` - main interface for UpgradeWizards
+- `RepeatableInterface` - semantic interface to denote wizards that can be repeated
+- `ChattyInterface` - interface for wizards generating output
+- `ConfirmableInterface` - interface for wizards that need user confirmation
+
+
+Impact
+======
+
+The new interface classes are available in the core and will be used in the core update 
+wizards as a next step. They should not yet be used by third parties.
+
+.. index:: PHP-API, ext:install
\ No newline at end of file
diff --git a/typo3/sysext/install/Classes/Command/UpgradeWizardCommand.php b/typo3/sysext/install/Classes/Command/UpgradeWizardCommand.php
new file mode 100644 (file)
index 0000000..5690e30
--- /dev/null
@@ -0,0 +1,204 @@
+<?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
+     * @throws \InvalidArgumentException
+     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
+     * @return int
+     */
+    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 $wizardToExecute
+     * @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.
+     *
+     * @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;
+    }
+}
index 976a130..e98f985 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 declare(strict_types = 1);
+
 namespace TYPO3\CMS\Install\Service;
 
 /*
@@ -18,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\StreamOutput;
 use TYPO3\CMS\Core\Cache\DatabaseSchemaService;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
@@ -27,13 +29,24 @@ use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
 use TYPO3\CMS\Core\Registry;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Install\Updates\AbstractUpdate;
+use TYPO3\CMS\Install\Updates\ChattyInterface;
+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;
 
 /**
  * Service class helping managing upgrade wizards
  */
 class UpgradeWizardsService
 {
+    private $output;
+
+    public function __construct()
+    {
+        $this->output = new StreamOutput(fopen('php://temp', 'wb'));
+    }
+
     /**
      * Force creation / update of caching framework tables that are needed by some update wizards
      *
@@ -158,7 +171,7 @@ class UpgradeWizardsService
         $adds = [];
         foreach ($databaseDifferences as $schemaDiff) {
             foreach ($schemaDiff->newTables as $newTable) {
-                /** @var Table $newTable*/
+                /** @var Table $newTable */
                 if (!is_array($adds['tables'])) {
                     $adds['tables'] = [];
                 }
@@ -264,15 +277,30 @@ class UpgradeWizardsService
             $wizardInstance = GeneralUtility::makeInstance($class);
 
             // $explanation is changed by reference in Update objects!
+            // @todo deprecate once all wizards are migrated
             $explanation = '';
-            $wizardInstance->checkForUpdate($explanation);
+            $shouldRenderWizard = false;
+            if (!($wizardInstance instanceof UpgradeWizardInterface) && $wizardInstance instanceof AbstractUpdate) {
+                $wizardInstance->checkForUpdate($explanation);
+                $shouldRenderWizard = $wizardInstance->shouldRenderWizard();
+            }
+            if ($wizardInstance instanceof UpgradeWizardInterface) {
+                if ($wizardInstance instanceof ChattyInterface) {
+                    $wizardInstance->setOutput($this->output);
+                }
+                $shouldRenderWizard = $wizardInstance->updateNecessary();
+            }
 
             $wizards[] = [
                 'class' => $class,
                 'identifier' => $identifier,
                 'title' => $wizardInstance->getTitle(),
-                'shouldRenderWizard' => $wizardInstance->shouldRenderWizard(),
-                'markedDoneInRegistry' => GeneralUtility::makeInstance(Registry::class)->get('installUpdate', $class, false),
+                'shouldRenderWizard' => $shouldRenderWizard,
+                'markedDoneInRegistry' => GeneralUtility::makeInstance(Registry::class)->get(
+                    'installUpdate',
+                    $class,
+                    false
+                ),
                 'explanation' => $explanation,
             ];
         }
@@ -301,7 +329,32 @@ class UpgradeWizardsService
         $updateObject = GeneralUtility::makeInstance($class);
         $wizardHtml = '';
         if (method_exists($updateObject, 'getUserInput')) {
-            $wizardHtml = $updateObject->getUserInput('install[values][' . $identifier . ']');
+            $wizardHtml = $updateObject->getUserInput('install[values][' . htmlspecialchars($identifier) . ']');
+        } elseif ($updateObject instanceof UpgradeWizardInterface && $updateObject instanceof ConfirmableInterface) {
+            $wizardHtml = '
+            <div class="panel panel-danger">
+                <div class="panel-heading">' .
+                          htmlspecialchars($updateObject->getConfirmationTitle()) .
+                          '</div>
+                <div class="panel-body">
+                    ' .
+                          nl2br(htmlspecialchars($updateObject->getConfirmationMessage())) .
+                          '
+                    <div class="btn-group clearfix" data-toggle="buttons">
+                        <label class="btn btn-default active">
+                            <input type="radio" name="install[values][' .
+                          htmlspecialchars($updateObject->getIdentifier()) .
+                          '][install]" value="0" checked="checked" /> no
+                        </label>
+                        <label class="btn btn-default">
+                            <input type="radio" name="install[values][' .
+                          htmlspecialchars($updateObject->getIdentifier()) .
+                          '][install]" value="1" /> yes
+                        </label>
+                    </div>
+                </div>
+            </div>
+        ';
         }
 
         $result = [
@@ -337,14 +390,18 @@ class UpgradeWizardsService
         $messages = new FlashMessageQueue('install');
         // $wizardInputErrorMessage is given as reference to wizard object!
         $wizardInputErrorMessage = '';
-        if (method_exists($updateObject, 'checkUserInput') && !$updateObject->checkUserInput($wizardInputErrorMessage)) {
-            $messages->enqueue(new FlashMessage(
-                $wizardInputErrorMessage ?: 'Something went wrong!',
-                'Input parameter broken',
-                FlashMessage::ERROR
-            ));
+        if (method_exists($updateObject, 'checkUserInput') &&
+            !$updateObject->checkUserInput($wizardInputErrorMessage)) {
+            // @todo deprecate, unused
+            $messages->enqueue(
+                new FlashMessage(
+                    $wizardInputErrorMessage ?: 'Something went wrong!',
+                    'Input parameter broken',
+                    FlashMessage::ERROR
+                )
+            );
         } else {
-            if (!method_exists($updateObject, 'performUpdate')) {
+            if (!($updateObject instanceof UpgradeWizardInterface) && !method_exists($updateObject, 'performUpdate')) {
                 throw new \RuntimeException(
                     'No performUpdate method in update wizard with identifier ' . $identifier,
                     1371035200
@@ -354,30 +411,93 @@ class UpgradeWizardsService
             // Both variables are used by reference in performUpdate()
             $message = '';
             $databaseQueries = [];
-            $performResult = $updateObject->performUpdate($databaseQueries, $message);
+            if ($updateObject instanceof UpgradeWizardInterface) {
+                $requestParams = GeneralUtility::_GP('install');
+                if ($updateObject instanceof ConfirmableInterface
+                    && (
+                        isset($requestParams['values'][$updateObject->getIdentifier()]['install'])
+                        && empty($requestParams['values'][$updateObject->getIdentifier()]['install'])
+                    )
+                ) {
+                    // confirmation was set to "no"
+                    $performResult = true;
+                } else {
+                    // confirmation yes or non-confirmable
+                    if ($updateObject instanceof ChattyInterface) {
+                        $updateObject->setOutput($this->output);
+                    }
+                    $performResult = $updateObject->executeUpdate();
+                }
+            } else {
+                // @todo deprecate
+                $performResult = $updateObject->performUpdate($databaseQueries, $message);
+            }
 
+            $stream = $this->output->getStream();
+            rewind($stream);
             if ($performResult) {
-                $messages->enqueue(new FlashMessage(
-                    '',
-                    'Update successful'
-                ));
+                if ($updateObject instanceof UpgradeWizardInterface && !($updateObject instanceof RepeatableInterface)) {
+                    // mark wizard as done if it's not repeatable and was successful
+                    $this->markWizardAsDone($updateObject->getIdentifier());
+                }
+                $messages->enqueue(
+                    new FlashMessage(
+                        stream_get_contents($stream),
+                        'Update successful'
+                    )
+                );
             } else {
-                $messages->enqueue(new FlashMessage(
-                    '',
-                    'Update failed!',
-                    FlashMessage::ERROR
-                ));
+                $messages->enqueue(
+                    new FlashMessage(
+                        stream_get_contents($stream),
+                        'Update failed!',
+                        FlashMessage::ERROR
+                    )
+                );
             }
             if ($showDatabaseQueries) {
+                // @todo deprecate
                 foreach ($databaseQueries as $query) {
-                    $messages->enqueue(new FlashMessage(
-                        $query,
-                        '',
-                        FlashMessage::INFO
-                    ));
+                    $messages->enqueue(
+                        new FlashMessage(
+                            $query,
+                            '',
+                            FlashMessage::INFO
+                        )
+                    );
                 }
             }
         }
         return $messages;
     }
+
+    /**
+     * Marks some wizard as being "seen" so that it not shown again.
+     * Writes the info in LocalConfiguration.php
+     *
+     * @param string $identifier
+     */
+    public function markWizardAsDone(string $identifier): void
+    {
+        GeneralUtility::makeInstance(Registry::class)->set('installUpdate', $identifier, 1);
+    }
+
+    /**
+     * @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
+     * @return bool TRUE if wizard has been done before, FALSE otherwise
+     */
+    public function isWizardDone(string $identifier): bool
+    {
+        return (bool)GeneralUtility::makeInstance(Registry::class)->get('installUpdate', $identifier, false);
+    }
 }
index 0b7106e..e3488ac 100644 (file)
@@ -50,7 +50,7 @@ abstract class AbstractUpdate
      *
      * @return string The title of this update wizard
      */
-    public function getTitle()
+    public function getTitle(): string
     {
         if ($this->title) {
             return $this->title;
@@ -73,7 +73,7 @@ abstract class AbstractUpdate
      *
      * @return string The identifier of this update wizard
      */
-    public function getIdentifier()
+    public function getIdentifier(): string
     {
         return $this->identifier;
     }
diff --git a/typo3/sysext/install/Classes/Updates/ChattyInterface.php b/typo3/sysext/install/Classes/Updates/ChattyInterface.php
new file mode 100644 (file)
index 0000000..d4a4caa
--- /dev/null
@@ -0,0 +1,29 @@
+<?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 Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Is this upgradeWizard chatty aka does it need to output things?
+ *
+ * @internal
+ */
+interface ChattyInterface
+{
+    public function setOutput(OutputInterface $output): void;
+}
diff --git a/typo3/sysext/install/Classes/Updates/ConfirmableInterface.php b/typo3/sysext/install/Classes/Updates/ConfirmableInterface.php
new file mode 100644 (file)
index 0000000..63e8aec
--- /dev/null
@@ -0,0 +1,29 @@
+<?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 if upgrade wizard needs confirmation
+ *
+ * @internal
+ */
+interface ConfirmableInterface
+{
+    public function getConfirmationTitle(): string;
+    public function getConfirmationMessage(): string;
+    public function getConfirmationDefault(): bool;
+}
diff --git a/typo3/sysext/install/Classes/Updates/DatabaseUpdatedPrerequisite.php b/typo3/sysext/install/Classes/Updates/DatabaseUpdatedPrerequisite.php
new file mode 100644 (file)
index 0000000..2d71d44
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Install\Updates;
+
+use TYPO3\CMS\Install\Service\UpgradeWizardsService;
+
+class DatabaseUpdatedPrerequisite implements Prerequisite
+{
+    protected $upgradeWizardsService;
+
+    public function __construct()
+    {
+        $this->upgradeWizardsService = new UpgradeWizardsService();
+    }
+
+    public function getName(): string
+    {
+        return 'Database Up-to-Date';
+    }
+
+    public function ensure(): void
+    {
+        $adds = $this->upgradeWizardsService->getBlockingDatabaseAdds();
+
+        if (count($adds) > 0) {
+            $this->upgradeWizardsService->addMissingTablesAndFields();
+        }
+    }
+
+    public function met(): bool
+    {
+        $adds = $this->upgradeWizardsService->getBlockingDatabaseAdds();
+        return $adds === 0;
+    }
+
+    public function getIdentifier(): string
+    {
+        return 'databaseUpdatePrerequisite';
+    }
+}
index 3f6ec2e..011a72f 100644 (file)
@@ -17,52 +17,29 @@ namespace TYPO3\CMS\Install\Updates;
 
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Install\Service\UpgradeWizardsService;
 
 /**
  * Merge access rights from be_groups concerning pages_language_overlay
  * into pages
  */
-class MigratePagesLanguageOverlayBeGroupsAccessRights extends AbstractUpdate
+class MigratePagesLanguageOverlayBeGroupsAccessRights implements UpgradeWizardInterface, ConfirmableInterface
 {
-    /**
-     * The human-readable title of the upgrade wizard
-     *
-     * @var string
-     */
-    protected $title = 'Merge be_groups access rights from pages_language_overlay to pages';
-
-    /**
-     * Checks whether updates are required.
-     *
-     * @param string &$description The description for the update
-     * @return bool Whether an update is required (TRUE) or not (FALSE)
-     */
-    public function checkForUpdate(&$description)
+    public function getIdentifier(): string
     {
-        $description = 'The table pages_language_overlay will be removed to align the translation ' .
-            'handling for pages with the rest of the core. This wizard transfers all be_groups with ' .
-            'access restrictions to pages_language_overlay into pages.';
-
-        $updateNeeded = false;
-
-        if (!$this->isWizardDone()) {
-            $updateNeeded = true;
-        }
+        return self::class;
+    }
 
-        return $updateNeeded;
+    public function getTitle(): string
+    {
+        return 'Merge be_groups access rights from pages_language_overlay to pages';
     }
 
-    /**
-     * Performs the accordant updates.
-     *
-     * @param array &$dbQueries Queries done in this update
-     * @param string &$customMessage Custom message
-     * @return bool Whether everything went smoothly or not
-     * @throws \InvalidArgumentException
-     */
-    public function performUpdate(array &$dbQueries, &$customMessage)
+    public function executeUpdate(): bool
     {
-        $beGroupsQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_groups');
+        $beGroupsQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(
+            'be_groups'
+        );
         $beGroupsQueryBuilder->getRestrictions()->removeAll();
         $beGroupsRows = $beGroupsQueryBuilder
             ->select('uid', 'non_exclude_fields', 'tables_modify')
@@ -104,7 +81,8 @@ class MigratePagesLanguageOverlayBeGroupsAccessRights extends AbstractUpdate
                 $newExcludeFields = [];
             }
             if ($updateNeeded) {
-                $updateBeGroupsQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_groups');
+                $updateBeGroupsQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getQueryBuilderForTable('be_groups');
                 $updateBeGroupsQueryBuilder
                     ->update('be_groups')
                     ->set('tables_modify', implode(',', $newTablesArray))
@@ -118,7 +96,40 @@ class MigratePagesLanguageOverlayBeGroupsAccessRights extends AbstractUpdate
                     ->execute();
             }
         }
-        $this->markWizardAsDone();
         return true;
     }
+
+    public function updateNecessary(): bool
+    {
+        return !(new UpgradeWizardsService())->isWizardDone($this->getIdentifier());
+    }
+
+    public function getPrerequisites(): array
+    {
+        return [
+            DatabaseUpdatedPrerequisite::class
+        ];
+    }
+
+    public function getDescription(): string
+    {
+        return 'The table pages_language_overlay will be removed to align the translation ' .
+               'handling for pages with the rest of the core. This wizard transfers all be_groups with ' .
+               'access restrictions to pages_language_overlay into pages.';
+    }
+
+    public function getConfirmationTitle(): string
+    {
+        return 'Are you sure?';
+    }
+
+    public function getConfirmationMessage(): string
+    {
+        return 'Do you want to continue?';
+    }
+
+    public function getConfirmationDefault(): bool
+    {
+        return false;
+    }
 }
diff --git a/typo3/sysext/install/Classes/Updates/Prerequisite.php b/typo3/sysext/install/Classes/Updates/Prerequisite.php
new file mode 100644 (file)
index 0000000..556b8dc
--- /dev/null
@@ -0,0 +1,30 @@
+<?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!
+ */
+
+/**
+ * UpgradeWizard Prerequisites
+ *
+ * @internal
+ */
+interface Prerequisite
+{
+    public function getIdentifier(): string;
+    public function getName(): string;
+    public function ensure(): void;
+    public function met(): bool;
+}
diff --git a/typo3/sysext/install/Classes/Updates/PrerequisiteCollection.php b/typo3/sysext/install/Classes/Updates/PrerequisiteCollection.php
new file mode 100644 (file)
index 0000000..ccf062a
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Install\Updates;
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+class PrerequisiteCollection
+{
+    protected $prerequisites;
+
+    public function __construct()
+    {
+        $this->prerequisites = new \SplObjectStorage();
+    }
+
+    public function addPrerequisite(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);
+            }
+        }
+    }
+
+    public function getPrerequisites(): \SplObjectStorage
+    {
+        return $this->prerequisites;
+    }
+}
diff --git a/typo3/sysext/install/Classes/Updates/ReferenceIndexUpdatedPrerequisite.php b/typo3/sysext/install/Classes/Updates/ReferenceIndexUpdatedPrerequisite.php
new file mode 100644 (file)
index 0000000..031f1a6
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Install\Updates;
+
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Database\ReferenceIndex;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+class ReferenceIndexUpdatedPrerequisite implements Prerequisite
+{
+    private $referenceIndex;
+
+    public function __construct()
+    {
+        if (!($GLOBALS['BE_USER'] instanceof BackendUserAuthentication)) {
+            Bootstrap::initializeBackendAuthentication();
+        }
+        $this->referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
+    }
+
+    public function getName(): string
+    {
+        return 'Reference Index Up-to-Date';
+    }
+
+    public function ensure(): void
+    {
+        $this->referenceIndex->enableRuntimeCache();
+        $this->referenceIndex->updateIndex(false, false);
+    }
+
+    public function met(): bool
+    {
+        $this->referenceIndex->enableRuntimeCache();
+        $result = $this->referenceIndex->updateIndex(true, false);
+        return $result['errorCount'] === 0;
+    }
+}
diff --git a/typo3/sysext/install/Classes/Updates/RepeatableInterface.php b/typo3/sysext/install/Classes/Updates/RepeatableInterface.php
new file mode 100644 (file)
index 0000000..b70a98c
--- /dev/null
@@ -0,0 +1,28 @@
+<?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 if wizard may be run multiple times
+ *
+ * Semantic interface only
+ *
+ * @internal
+ */
+interface RepeatableInterface
+{
+}
diff --git a/typo3/sysext/install/Classes/Updates/UpgradeWizardInterface.php b/typo3/sysext/install/Classes/Updates/UpgradeWizardInterface.php
new file mode 100644 (file)
index 0000000..e6a59f3
--- /dev/null
@@ -0,0 +1,37 @@
+<?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!
+ */
+
+/**
+ * Interface UpgradeWizardInterface
+ *
+ * @internal
+ */
+interface UpgradeWizardInterface
+{
+    public function getIdentifier(): string;
+
+    public function getTitle(): string;
+
+    public function getDescription(): string;
+
+    public function executeUpdate(): bool;
+
+    public function updateNecessary(): bool;
+
+    public function getPrerequisites(): array;
+}
index 538fd5d..e745903 100644 (file)
@@ -10,5 +10,8 @@
 return [
     'language:update' => [
         'class' => \TYPO3\CMS\Install\Command\LanguagePackCommand::class
+    ],
+    'install:update' => [
+        'class' => \TYPO3\CMS\Install\Command\UpgradeWizardCommand::class
     ]
 ];
index f9c0638..5312f59 100644 (file)
@@ -40,6 +40,8 @@ $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']