d48bc8efe80ca4ac161fadcf057990cf868282c0
[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 $this->handlePrerequisites([$upgradeWizard]);
110 $result = $this->runSingleWizard($upgradeWizard);
111 } else {
112 $this->output->warning('Wizard ' . $wizardToExecute . ' is already done.');
113 }
114 } else {
115 $this->output->error('No such wizard: ' . $wizardToExecute);
116 $result = 1;
117 }
118 } else {
119 $result = $this->runAllWizards();
120 }
121 return $result;
122 }
123
124 /**
125 * Get Wizard instance by class name and identifier
126 * Returns null if wizard is already done
127 *
128 * @param $className
129 * @param $identifier
130 * @return \TYPO3\CMS\Install\Updates\UpgradeWizardInterface|null
131 */
132 protected function getWizard(string $className, string $identifier): ?UpgradeWizardInterface
133 {
134 // already done
135 if ($this->upgradeWizardsService->isWizardDone($identifier)) {
136 return null;
137 }
138
139 $wizardInstance = GeneralUtility::makeInstance($className);
140 if ($wizardInstance instanceof ChattyInterface) {
141 $wizardInstance->setOutput($this->output);
142 }
143
144 if (!($wizardInstance instanceof UpgradeWizardInterface)) {
145 $this->output->writeln(
146 'Wizard ' .
147 $identifier .
148 ' needs to be manually run from the install tool, as it does not implement ' .
149 UpgradeWizardInterface::class
150 );
151 return null;
152 }
153
154 return $wizardInstance->updateNecessary() ? $wizardInstance : null;
155 }
156
157 /**
158 * Handles prerequisites of update wizards, allows a more flexible definition and declaration of dependencies
159 * Currently implemented prerequisites include "database needs to be up-to-date" and "referenceIndex needs to be up-
160 * to-date"
161 * At the moment the install tool automatically displays the database updates when necessary but can't do more
162 * prerequisites
163 *
164 * @param UpgradeWizardInterface[] $instances
165 * @return bool
166 */
167 protected function handlePrerequisites(array $instances): bool
168 {
169 $prerequisites = GeneralUtility::makeInstance(PrerequisiteCollection::class);
170 foreach ($instances as $instance) {
171 foreach ($instance->getPrerequisites() as $prerequisite) {
172 $prerequisites->add($prerequisite);
173 }
174 }
175 $result = true;
176 foreach ($prerequisites as $prerequisite) {
177 if ($prerequisite instanceof ChattyInterface) {
178 $prerequisite->setOutput($this->output);
179 }
180 if (!$prerequisite->isFulfilled()) {
181 $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" not fulfilled, will ensure.');
182 $result = $prerequisite->ensure();
183 if ($result === false) {
184 $this->output->error(
185 '<error>Error running ' .
186 $prerequisite->getTitle() .
187 '. Please ensure this prerequisite manually and try again.</error>'
188 );
189 break;
190 }
191 } else {
192 $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" fulfilled.');
193 }
194 }
195 return $result;
196 }
197
198 /**
199 * @param UpgradeWizardInterface $instance
200 * @return int
201 */
202 protected function runSingleWizard(
203 UpgradeWizardInterface $instance
204 ): int {
205 $this->output->title('Running Wizard ' . $instance->getTitle());
206 if ($instance instanceof ConfirmableInterface) {
207 $confirmation = $instance->getConfirmation();
208 $defaultString = $confirmation->getDefaultValue() ? '(Y/n)' : '(y/N)';
209 $question = new ConfirmationQuestion(
210 sprintf(
211 '<info>%s</info>' . LF . '%s %s',
212 $confirmation->getTitle(),
213 $confirmation->getMessage(),
214 $defaultString
215 ),
216 $confirmation->getDefaultValue()
217 );
218 $helper = $this->getHelper('question');
219 if (!$helper->ask($this->input, $this->output, $question)) {
220 $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
221 return 0;
222 }
223 }
224 if ($instance->executeUpdate()) {
225 $this->output->success('Successfully ran wizard ' . $instance->getTitle());
226 $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
227 return 0;
228 }
229 $this->output->error('<error>Something went wrong while running ' . $instance->getTitle() . '</error>');
230 return 1;
231 }
232
233 /**
234 * Get list of registered upgrade wizards.
235 *
236 * @return int 0 if all wizards were successful, 1 on error
237 */
238 public function runAllWizards(): int
239 {
240 $returnCode = 1;
241 $wizardInstances = [];
242 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $class) {
243 $wizardInstances[] = $this->getWizard($class, $identifier);
244 }
245 $wizardInstances = array_filter($wizardInstances);
246 if (count($wizardInstances) > 0) {
247 $prerequisitesResult = $this->handlePrerequisites($wizardInstances);
248 if ($prerequisitesResult === false) {
249 $returnCode = 1;
250 $this->output->error('Error handling prerequisites, aborting.');
251 } else {
252 $this->output->title('Found ' . count($wizardInstances) . ' wizard(s) to run.');
253 foreach ($wizardInstances as $wizardInstance) {
254 $result = $this->runSingleWizard($wizardInstance);
255 if ($result > 0) {
256 $returnCode = 1;
257 }
258 }
259 }
260 } else {
261 $this->output->success('No wizards left to run.');
262 }
263 return $returnCode;
264 }
265 }