Completed
Push — master ( 238fb0...a9a2cf )
by
unknown
15:40
created

UpgradeWizardRunCommand::handlePrerequisites()   B

Complexity

Conditions 7
Paths 21

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 20
nc 21
nop 1
dl 0
loc 29
rs 8.6666
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Install\Command;
19
20
use Symfony\Component\Console\Command\Command;
21
use Symfony\Component\Console\Input\InputArgument;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\Console\Question\ConfirmationQuestion;
25
use Symfony\Component\Console\Style\SymfonyStyle;
26
use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication;
27
use TYPO3\CMS\Core\Core\Bootstrap;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Install\Service\LateBootService;
30
use TYPO3\CMS\Install\Service\UpgradeWizardsService;
31
use TYPO3\CMS\Install\Updates\ChattyInterface;
32
use TYPO3\CMS\Install\Updates\ConfirmableInterface;
33
use TYPO3\CMS\Install\Updates\PrerequisiteCollection;
34
use TYPO3\CMS\Install\Updates\RepeatableInterface;
35
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
36
37
/**
38
 * Upgrade wizard command for running wizards
39
 *
40
 * @internal
41
 */
42
class UpgradeWizardRunCommand extends Command
43
{
44
    /**
45
     * @var LateBootService
46
     */
47
    private $lateBootService;
48
49
    /**
50
     * @var UpgradeWizardsService
51
     */
52
    private $upgradeWizardsService;
53
54
    /**
55
     * @var OutputInterface|\Symfony\Component\Console\Style\StyleInterface
56
     */
57
    private $output;
58
59
    /**
60
     * @var InputInterface
61
     */
62
    private $input;
63
64
    public function __construct(
65
        string $name,
66
        LateBootService $lateBootService,
67
        UpgradeWizardsService $upgradeWizardsService
68
    ) {
69
        $this->lateBootService = $lateBootService;
70
        $this->upgradeWizardsService = $upgradeWizardsService;
71
        parent::__construct($name);
72
    }
73
74
    /**
75
     * Bootstrap running of upgrade wizard,
76
     * ensure database is utf-8
77
     */
78
    protected function bootstrap(): void
79
    {
80
        $this->lateBootService->loadExtLocalconfDatabaseAndExtTables();
81
        Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
82
        Bootstrap::initializeBackendAuthentication();
83
        $this->upgradeWizardsService->isDatabaseCharsetUtf8() ?: $this->upgradeWizardsService->setDatabaseCharsetUtf8();
84
    }
85
86
    /**
87
     * Configure the command by defining the name, options and arguments
88
     */
89
    protected function configure()
90
    {
91
        $this->setDescription('Run upgrade wizard. Without arguments all available wizards will be run.')
92
            ->addArgument(
93
                'wizardName',
94
                InputArgument::OPTIONAL
95
            )->setHelp(
96
                'This command allows running upgrade wizards on CLI. To run a single wizard add the ' .
97
                'identifier of the wizard as argument. The identifier of the wizard is the name it is ' .
98
                'registered with in ext_localconf.'
99
            );
100
    }
101
102
    /**
103
     * Update language packs of all active languages for all active extensions
104
     *
105
     * @param InputInterface $input
106
     * @param \Symfony\Component\Console\Output\OutputInterface $output
107
     * @return int
108
     */
109
    protected function execute(InputInterface $input, OutputInterface $output)
110
    {
111
        $this->output = new SymfonyStyle($input, $output);
112
        $this->input = $input;
113
        $this->bootstrap();
114
115
        $result = 0;
116
        if ($input->getArgument('wizardName')) {
117
            $wizardToExecute = $input->getArgument('wizardName');
118
            $wizardToExecute = is_string($wizardToExecute) ? $wizardToExecute : '';
119
            if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardToExecute])) {
120
                $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardToExecute];
121
                $upgradeWizard = $this->getWizard($className, $wizardToExecute);
122
                if ($upgradeWizard !== null) {
123
                    $prerequisitesFulfilled = $this->handlePrerequisites([$upgradeWizard]);
124
                    if ($prerequisitesFulfilled === true) {
125
                        $result = $this->runSingleWizard($upgradeWizard);
126
                    } else {
127
                        $result = 1;
128
                    }
129
                }
130
            } else {
131
                $this->output->error('No such wizard: ' . $wizardToExecute);
132
                $result = 1;
133
            }
134
        } else {
135
            $result = $this->runAllWizards();
136
        }
137
        return $result;
138
    }
139
140
    /**
141
     * Get Wizard instance by class name and identifier
142
     * Returns null if wizard is already done
143
     *
144
     * @param string $className
145
     * @param string $identifier
146
     * @return \TYPO3\CMS\Install\Updates\UpgradeWizardInterface|null
147
     */
148
    protected function getWizard(string $className, string $identifier): ?UpgradeWizardInterface
149
    {
150
        // already done
151
        if ($this->upgradeWizardsService->isWizardDone($identifier)) {
152
            return null;
153
        }
154
155
        $wizardInstance = GeneralUtility::makeInstance($className);
156
        if ($wizardInstance instanceof ChattyInterface) {
157
            $wizardInstance->setOutput($this->output);
158
        }
159
160
        if (!($wizardInstance instanceof UpgradeWizardInterface)) {
161
            $this->output->writeln(
162
                'Wizard ' .
163
                $identifier .
164
                ' needs to be manually run from the install tool, as it does not implement ' .
165
                UpgradeWizardInterface::class
166
            );
167
            return null;
168
        }
169
170
        if ($wizardInstance->updateNecessary()) {
171
            return $wizardInstance;
172
        }
173
        if ($wizardInstance instanceof RepeatableInterface) {
174
            $this->output->note('Wizard ' . $identifier . ' does not need to make changes.');
0 ignored issues
show
Bug introduced by
The method note() does not exist on Symfony\Component\Console\Output\OutputInterface. It seems like you code against a sub-type of Symfony\Component\Console\Output\OutputInterface such as Symfony\Component\Console\Style\OutputStyle. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

174
            $this->output->/** @scrutinizer ignore-call */ 
175
                           note('Wizard ' . $identifier . ' does not need to make changes.');
Loading history...
175
        } else {
176
            $this->output->note('Wizard ' . $identifier . ' does not need to make changes. Marking wizard as done.');
177
            $this->upgradeWizardsService->markWizardAsDone($identifier);
178
        }
179
        return null;
180
    }
181
182
    /**
183
     * Handles prerequisites of update wizards, allows a more flexible definition and declaration of dependencies
184
     * Currently implemented prerequisites include "database needs to be up-to-date" and "referenceIndex needs to be up-
185
     * to-date"
186
     * At the moment the install tool automatically displays the database updates when necessary but can't do more
187
     * prerequisites
188
     *
189
     * @param UpgradeWizardInterface[] $instances
190
     * @return bool
191
     */
192
    protected function handlePrerequisites(array $instances): bool
193
    {
194
        $prerequisites = GeneralUtility::makeInstance(PrerequisiteCollection::class);
195
        foreach ($instances as $instance) {
196
            foreach ($instance->getPrerequisites() as $prerequisite) {
197
                $prerequisites->add($prerequisite);
198
            }
199
        }
200
        $result = true;
201
        foreach ($prerequisites as $prerequisite) {
202
            if ($prerequisite instanceof ChattyInterface) {
203
                $prerequisite->setOutput($this->output);
204
            }
205
            if (!$prerequisite->isFulfilled()) {
206
                $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" not fulfilled, will ensure.');
207
                $result = $prerequisite->ensure();
208
                if ($result === false) {
209
                    $this->output->error(
0 ignored issues
show
Bug introduced by
The method error() does not exist on Symfony\Component\Console\Output\OutputInterface. It seems like you code against a sub-type of Symfony\Component\Console\Output\OutputInterface such as Symfony\Component\Console\Style\OutputStyle. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

209
                    $this->output->/** @scrutinizer ignore-call */ 
210
                                   error(
Loading history...
210
                        '<error>Error running ' .
211
                        $prerequisite->getTitle() .
212
                        '. Please ensure this prerequisite manually and try again.</error>'
213
                    );
214
                    break;
215
                }
216
            } else {
217
                $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" fulfilled.');
218
            }
219
        }
220
        return $result;
221
    }
222
223
    /**
224
     * @param UpgradeWizardInterface $instance
225
     * @return int
226
     */
227
    protected function runSingleWizard(
228
        UpgradeWizardInterface $instance
229
    ): int {
230
        $this->output->title('Running Wizard ' . $instance->getTitle());
0 ignored issues
show
Bug introduced by
The method title() does not exist on Symfony\Component\Console\Output\OutputInterface. It seems like you code against a sub-type of Symfony\Component\Console\Output\OutputInterface such as Symfony\Component\Console\Style\OutputStyle. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

230
        $this->output->/** @scrutinizer ignore-call */ 
231
                       title('Running Wizard ' . $instance->getTitle());
Loading history...
231
        if ($instance instanceof ConfirmableInterface) {
232
            $confirmation = $instance->getConfirmation();
233
            $defaultString = $confirmation->getDefaultValue() ? 'Y/n' : 'y/N';
234
            $question = new ConfirmationQuestion(
235
                sprintf(
236
                    '<info>%s</info>' . LF . '%s' . LF . '%s %s (%s)',
237
                    $confirmation->getTitle(),
238
                    $confirmation->getMessage(),
239
                    $confirmation->getConfirm(),
240
                    $confirmation->getDeny(),
241
                    $defaultString
242
                ),
243
                $confirmation->getDefaultValue()
244
            );
245
            $helper = $this->getHelper('question');
246
            if (!$helper->ask($this->input, $this->output, $question)) {
247
                if ($confirmation->isRequired()) {
248
                    $this->output->error('You have to acknowledge this wizard to continue');
249
                    return 1;
250
                }
251
                if ($instance instanceof RepeatableInterface) {
252
                    $this->output->note('No changes applied.');
253
                } else {
254
                    $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
255
                    $this->output->note('No changes applied, marking wizard as done.');
256
                }
257
                return 0;
258
            }
259
        }
260
        if ($instance->executeUpdate()) {
261
            $this->output->success('Successfully ran wizard ' . $instance->getTitle());
0 ignored issues
show
Bug introduced by
The method success() does not exist on Symfony\Component\Console\Output\OutputInterface. It seems like you code against a sub-type of Symfony\Component\Console\Output\OutputInterface such as Symfony\Component\Console\Style\OutputStyle. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

261
            $this->output->/** @scrutinizer ignore-call */ 
262
                           success('Successfully ran wizard ' . $instance->getTitle());
Loading history...
262
            if (!$instance instanceof RepeatableInterface) {
263
                $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
264
            }
265
            return 0;
266
        }
267
        $this->output->error('<error>Something went wrong while running ' . $instance->getTitle() . '</error>');
268
        return 1;
269
    }
270
271
    /**
272
     * Get list of registered upgrade wizards.
273
     *
274
     * @return int 0 if all wizards were successful, 1 on error
275
     */
276
    public function runAllWizards(): int
277
    {
278
        $returnCode = 0;
279
        $wizardInstances = [];
280
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $class) {
281
            $wizardInstances[] = $this->getWizard($class, $identifier);
282
        }
283
        $wizardInstances = array_filter($wizardInstances);
284
        if (count($wizardInstances) > 0) {
285
            $prerequisitesResult = $this->handlePrerequisites($wizardInstances);
286
            if ($prerequisitesResult === false) {
287
                $returnCode = 1;
288
                $this->output->error('Error handling prerequisites, aborting.');
289
            } else {
290
                $this->output->title('Found ' . count($wizardInstances) . ' wizard(s) to run.');
291
                foreach ($wizardInstances as $wizardInstance) {
292
                    $result = $this->runSingleWizard($wizardInstance);
293
                    if ($result > 0) {
294
                        $returnCode = 1;
295
                    }
296
                }
297
            }
298
        } else {
299
            $this->output->success('No wizards left to run.');
300
        }
301
        return $returnCode;
302
    }
303
}
304