[TASK] Avoid processing empty entity ids in DataMapProcessor
[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\LateBootService;
29 use TYPO3\CMS\Install\Service\UpgradeWizardsService;
30 use TYPO3\CMS\Install\Updates\ChattyInterface;
31 use TYPO3\CMS\Install\Updates\ConfirmableInterface;
32 use TYPO3\CMS\Install\Updates\PrerequisiteCollection;
33 use TYPO3\CMS\Install\Updates\RepeatableInterface;
34 use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
35
36 /**
37 * Upgrade wizard command for running wizards
38 *
39 * @internal
40 */
41 class UpgradeWizardRunCommand extends Command
42 {
43 /**
44 * @var UpgradeWizardsService
45 */
46 private $upgradeWizardsService;
47
48 /**
49 * @var OutputInterface|\Symfony\Component\Console\Style\StyleInterface
50 */
51 private $output;
52
53 /**
54 * @var InputInterface
55 */
56 private $input;
57
58 /**
59 * Bootstrap running of upgrade wizard,
60 * ensure database is utf-8
61 */
62 protected function bootstrap(): void
63 {
64 GeneralUtility::makeInstance(LateBootService::class)->loadExtLocalconfDatabaseAndExtTables();
65 Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
66 Bootstrap::initializeBackendAuthentication();
67 $this->upgradeWizardsService = new UpgradeWizardsService();
68 $this->upgradeWizardsService->isDatabaseCharsetUtf8() ?: $this->upgradeWizardsService->setDatabaseCharsetUtf8();
69 }
70
71 /**
72 * Configure the command by defining the name, options and arguments
73 */
74 protected function configure()
75 {
76 $this->setDescription('Run upgrade wizard. Without arguments all available wizards will be run.')
77 ->addArgument(
78 'wizardName',
79 InputArgument::OPTIONAL
80 )->setHelp(
81 'This command allows running upgrade wizards on CLI. To run a single wizard add the ' .
82 'identifier of the wizard as argument. The identifier of the wizard is the name it is ' .
83 'registered with in ext_localconf.'
84 );
85 }
86
87 /**
88 * Update language packs of all active languages for all active extensions
89 *
90 * @param InputInterface $input
91 * @param \Symfony\Component\Console\Output\OutputInterface $output
92 * @return int
93 */
94 protected function execute(InputInterface $input, OutputInterface $output)
95 {
96 $this->output = new SymfonyStyle($input, $output);
97 $this->input = $input;
98 $this->bootstrap();
99
100 $result = 0;
101 if ($input->getArgument('wizardName')) {
102 $wizardToExecute = $input->getArgument('wizardName');
103 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardToExecute])) {
104 $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardToExecute];
105 $upgradeWizard = $this->getWizard($className, $wizardToExecute);
106 if ($upgradeWizard !== null) {
107 $prerequisitesFulfilled = $this->handlePrerequisites([$upgradeWizard]);
108 if ($prerequisitesFulfilled === true) {
109 $result = $this->runSingleWizard($upgradeWizard);
110 } else {
111 $result = 1;
112 }
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 if ($wizardInstance->updateNecessary()) {
155 return $wizardInstance;
156 }
157 if ($wizardInstance instanceof RepeatableInterface) {
158 $this->output->note('Wizard ' . $identifier . ' does not need to make changes.');
159 } else {
160 $this->output->note('Wizard ' . $identifier . ' does not need to make changes. Marking wizard as done.');
161 $this->upgradeWizardsService->markWizardAsDone($identifier);
162 }
163 return null;
164 }
165
166 /**
167 * Handles prerequisites of update wizards, allows a more flexible definition and declaration of dependencies
168 * Currently implemented prerequisites include "database needs to be up-to-date" and "referenceIndex needs to be up-
169 * to-date"
170 * At the moment the install tool automatically displays the database updates when necessary but can't do more
171 * prerequisites
172 *
173 * @param UpgradeWizardInterface[] $instances
174 * @return bool
175 */
176 protected function handlePrerequisites(array $instances): bool
177 {
178 $prerequisites = GeneralUtility::makeInstance(PrerequisiteCollection::class);
179 foreach ($instances as $instance) {
180 foreach ($instance->getPrerequisites() as $prerequisite) {
181 $prerequisites->add($prerequisite);
182 }
183 }
184 $result = true;
185 foreach ($prerequisites as $prerequisite) {
186 if ($prerequisite instanceof ChattyInterface) {
187 $prerequisite->setOutput($this->output);
188 }
189 if (!$prerequisite->isFulfilled()) {
190 $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" not fulfilled, will ensure.');
191 $result = $prerequisite->ensure();
192 if ($result === false) {
193 $this->output->error(
194 '<error>Error running ' .
195 $prerequisite->getTitle() .
196 '. Please ensure this prerequisite manually and try again.</error>'
197 );
198 break;
199 }
200 } else {
201 $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" fulfilled.');
202 }
203 }
204 return $result;
205 }
206
207 /**
208 * @param UpgradeWizardInterface $instance
209 * @return int
210 */
211 protected function runSingleWizard(
212 UpgradeWizardInterface $instance
213 ): int {
214 $this->output->title('Running Wizard ' . $instance->getTitle());
215 if ($instance instanceof ConfirmableInterface) {
216 $confirmation = $instance->getConfirmation();
217 $defaultString = $confirmation->getDefaultValue() ? 'Y/n' : 'y/N';
218 $question = new ConfirmationQuestion(
219 sprintf(
220 '<info>%s</info>' . LF . '%s' . LF . '%s %s (%s)',
221 $confirmation->getTitle(),
222 $confirmation->getMessage(),
223 $confirmation->getConfirm(),
224 $confirmation->getDeny(),
225 $defaultString
226 ),
227 $confirmation->getDefaultValue()
228 );
229 $helper = $this->getHelper('question');
230 if (!$helper->ask($this->input, $this->output, $question)) {
231 if ($confirmation->isRequired()) {
232 $this->output->error('You have to acknowledge this wizard to continue');
233 return 1;
234 }
235 if ($instance instanceof RepeatableInterface) {
236 $this->output->note('No changes applied.');
237 } else {
238 $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
239 $this->output->note('No changes applied, marking wizard as done.');
240 }
241 return 0;
242 }
243 }
244 if ($instance->executeUpdate()) {
245 $this->output->success('Successfully ran wizard ' . $instance->getTitle());
246 if (!$instance instanceof RepeatableInterface) {
247 $this->upgradeWizardsService->markWizardAsDone($instance->getIdentifier());
248 }
249 return 0;
250 }
251 $this->output->error('<error>Something went wrong while running ' . $instance->getTitle() . '</error>');
252 return 1;
253 }
254
255 /**
256 * Get list of registered upgrade wizards.
257 *
258 * @return int 0 if all wizards were successful, 1 on error
259 */
260 public function runAllWizards(): int
261 {
262 $returnCode = 0;
263 $wizardInstances = [];
264 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $class) {
265 $wizardInstances[] = $this->getWizard($class, $identifier);
266 }
267 $wizardInstances = array_filter($wizardInstances);
268 if (count($wizardInstances) > 0) {
269 $prerequisitesResult = $this->handlePrerequisites($wizardInstances);
270 if ($prerequisitesResult === false) {
271 $returnCode = 1;
272 $this->output->error('Error handling prerequisites, aborting.');
273 } else {
274 $this->output->title('Found ' . count($wizardInstances) . ' wizard(s) to run.');
275 foreach ($wizardInstances as $wizardInstance) {
276 $result = $this->runSingleWizard($wizardInstance);
277 if ($result > 0) {
278 $returnCode = 1;
279 }
280 }
281 }
282 } else {
283 $this->output->success('No wizards left to run.');
284 }
285 return $returnCode;
286 }
287 }