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