[TASK] Migrate form upgrade wizard to new API
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Command / UpgradeWizardRunCommand.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Install\Command;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use Symfony\Component\Console\Command\Command;
20 use Symfony\Component\Console\Input\InputArgument;
21 use Symfony\Component\Console\Input\InputInterface;
22 use Symfony\Component\Console\Output\OutputInterface;
23 use Symfony\Component\Console\Question\ConfirmationQuestion;
24 use Symfony\Component\Console\Style\SymfonyStyle;
25 use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication;
26 use TYPO3\CMS\Core\Core\Bootstrap;
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
28 use TYPO3\CMS\Install\Service\UpgradeWizardsService;
29 use TYPO3\CMS\Install\Updates\ChattyInterface;
30 use TYPO3\CMS\Install\Updates\ConfirmableInterface;
31 use TYPO3\CMS\Install\Updates\PrerequisiteCollection;
32 use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
33
34 /**
35 * Upgrade wizard command for running wizards
36 *
37 * @internal
38 */
39 class UpgradeWizardRunCommand extends Command
40 {
41 /**
42 * @var UpgradeWizardsService
43 */
44 private $upgradeWizardsService;
45
46 /**
47 * @var OutputInterface|\Symfony\Component\Console\Style\StyleInterface
48 */
49 private $output;
50
51 /**
52 * @var InputInterface
53 */
54 private $input;
55
56 /**
57 * Bootstrap running of upgrade wizard,
58 * ensure database is utf-8
59 */
60 protected function bootstrap(): void
61 {
62 Bootstrap::loadTypo3LoadedExtAndExtLocalconf(false);
63 Bootstrap::unsetReservedGlobalVariables();
64 Bootstrap::loadBaseTca(false);
65 Bootstrap::loadExtTables(false);
66 Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
67 Bootstrap::initializeBackendAuthentication();
68 $this->upgradeWizardsService = new UpgradeWizardsService();
69 $this->upgradeWizardsService->isDatabaseCharsetUtf8() ?: $this->upgradeWizardsService->setDatabaseCharsetUtf8();
70 $this->upgradeWizardsService->silentCacheFrameworkTableSchemaMigration();
71 }
72
73 /**
74 * Configure the command by defining the name, options and arguments
75 */
76 protected function configure()
77 {
78 $this->setDescription('Run upgrade wizard. Without arguments all available wizards will be run.')
79 ->addArgument(
80 'wizardName',
81 InputArgument::OPTIONAL
82 )->setHelp(
83 'This command allows running upgrade wizards on CLI. To run a single wizard add the ' .
84 'identifier of the wizard as argument. The identifier of the wizard is the name it is ' .
85 'registered with in ext_localconf.'
86 );
87 }
88
89 /**
90 * Update language packs of all active languages for all active extensions
91 *
92 * @param InputInterface $input
93 * @param \Symfony\Component\Console\Output\OutputInterface $output
94 * @return int
95 */
96 protected function execute(InputInterface $input, OutputInterface $output)
97 {
98 $this->output = new SymfonyStyle($input, $output);
99 $this->input = $input;
100 $this->bootstrap();
101
102 $result = 0;
103 if ($input->getArgument('wizardName')) {
104 $wizardToExecute = $input->getArgument('wizardName');
105 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardToExecute])) {
106 $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardToExecute];
107 $upgradeWizard = $this->getWizard($className, $wizardToExecute);
108 if ($upgradeWizard !== null) {
109 $prerequisitesFulfilled = $this->handlePrerequisites([$upgradeWizard]);
110 if ($prerequisitesFulfilled === true) {
111 $result = $this->runSingleWizard($upgradeWizard);
112 } else {
113 $result = 1;
114 }
115 }
116 } else {
117 $this->output->error('No such wizard: ' . $wizardToExecute);
118 $result = 1;
119 }
120 } else {
121 $result = $this->runAllWizards();
122 }
123 return $result;
124 }
125
126 /**
127 * Get Wizard instance by class name and identifier
128 * Returns null if wizard is already done
129 *
130 * @param $className
131 * @param $identifier
132 * @return \TYPO3\CMS\Install\Updates\UpgradeWizardInterface|null
133 */
134 protected function getWizard(string $className, string $identifier): ?UpgradeWizardInterface
135 {
136 // already done
137 if ($this->upgradeWizardsService->isWizardDone($identifier)) {
138 return null;
139 }
140
141 $wizardInstance = GeneralUtility::makeInstance($className);
142 if ($wizardInstance instanceof ChattyInterface) {
143 $wizardInstance->setOutput($this->output);
144 }
145
146 if (!($wizardInstance instanceof UpgradeWizardInterface)) {
147 $this->output->writeln(
148 'Wizard ' .
149 $identifier .
150 ' needs to be manually run from the install tool, as it does not implement ' .
151 UpgradeWizardInterface::class
152 );
153 return null;
154 }
155
156 if ($wizardInstance->updateNecessary()) {
157 return $wizardInstance;
158 }
159 $this->output->note('Wizard ' . $identifier . ' does not need to make changes. Marking wizard as done.');
160 $this->upgradeWizardsService->markWizardAsDone($identifier);
161 return null;
162 }
163
164 /**
165 * Handles prerequisites of update wizards, allows a more flexible definition and declaration of dependencies
166 * Currently implemented prerequisites include "database needs to be up-to-date" and "referenceIndex needs to be up-
167 * to-date"
168 * At the moment the install tool automatically displays the database updates when necessary but can't do more
169 * prerequisites
170 *
171 * @param UpgradeWizardInterface[] $instances
172 * @return bool
173 */
174 protected function handlePrerequisites(array $instances): bool
175 {
176 $prerequisites = GeneralUtility::makeInstance(PrerequisiteCollection::class);
177 foreach ($instances as $instance) {
178 foreach ($instance->getPrerequisites() as $prerequisite) {
179 $prerequisites->add($prerequisite);
180 }
181 }
182 $result = true;
183 foreach ($prerequisites as $prerequisite) {
184 if ($prerequisite instanceof ChattyInterface) {
185 $prerequisite->setOutput($this->output);
186 }
187 if (!$prerequisite->isFulfilled()) {
188 $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" not fulfilled, will ensure.');
189 $result = $prerequisite->ensure();
190 if ($result === false) {
191 $this->output->error(
192 '<error>Error running ' .
193 $prerequisite->getTitle() .
194 '. Please ensure this prerequisite manually and try again.</error>'
195 );
196 break;
197 }
198 } else {
199 $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" fulfilled.');
200 }
201 }
202 return $result;
203 }
204
205 /**
206 * @param UpgradeWizardInterface $instance
207 * @return int
208 */
209 protected function runSingleWizard(
210 UpgradeWizardInterface $instance
211 ): int {
212 $this->output->title('Running Wizard ' . $instance->getTitle());
213 if ($instance instanceof ConfirmableInterface) {
214 $confirmation = $instance->getConfirmation();
215 $defaultString = $confirmation->getDefaultValue() ? '(Y/n)' : '(y/N)';
216 $question = new ConfirmationQuestion(
217 sprintf(
218 '<info>%s</info>' . LF . '%s %s',
219 $confirmation->getTitle(),
220 $confirmation->getMessage(),
221 $defaultString
222 ),
223 $confirmation->getDefaultValue()
224 );
225 $helper = $this->getHelper('question');
226 if (!$helper->ask($this->input, $this->output, $question)) {
227 $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
228 $this->output->note('No changes applied, marking wizard as done.');
229 return 0;
230 }
231 }
232 if ($instance->executeUpdate()) {
233 $this->output->success('Successfully ran wizard ' . $instance->getTitle());
234 $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
235 return 0;
236 }
237 $this->output->error('<error>Something went wrong while running ' . $instance->getTitle() . '</error>');
238 return 1;
239 }
240
241 /**
242 * Get list of registered upgrade wizards.
243 *
244 * @return int 0 if all wizards were successful, 1 on error
245 */
246 public function runAllWizards(): int
247 {
248 $returnCode = 1;
249 $wizardInstances = [];
250 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $class) {
251 $wizardInstances[] = $this->getWizard($class, $identifier);
252 }
253 $wizardInstances = array_filter($wizardInstances);
254 if (count($wizardInstances) > 0) {
255 $prerequisitesResult = $this->handlePrerequisites($wizardInstances);
256 if ($prerequisitesResult === false) {
257 $returnCode = 1;
258 $this->output->error('Error handling prerequisites, aborting.');
259 } else {
260 $this->output->title('Found ' . count($wizardInstances) . ' wizard(s) to run.');
261 foreach ($wizardInstances as $wizardInstance) {
262 $result = $this->runSingleWizard($wizardInstance);
263 if ($result > 0) {
264 $returnCode = 1;
265 }
266 }
267 }
268 } else {
269 $this->output->success('No wizards left to run.');
270 }
271 return $returnCode;
272 }
273 }