‪TYPO3CMS  ‪main
UpgradeWizardRunCommand.php
Go to the documentation of this file.
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 
19 
20 use Symfony\Component\Console\Command\Command;
21 use Symfony\Component\Console\Helper\QuestionHelper;
22 use Symfony\Component\Console\Input\InputArgument;
23 use Symfony\Component\Console\Input\InputInterface;
24 use Symfony\Component\Console\Output\OutputInterface;
25 use Symfony\Component\Console\Question\ConfirmationQuestion;
26 use Symfony\Component\Console\Style\SymfonyStyle;
31 use TYPO3\CMS\Install\Service\DatabaseUpgradeWizardsService;
42 
48 class ‪UpgradeWizardRunCommand extends Command
49 {
50  private const ‪MAX_SILENT_UPGRADE_TRIES = 100;
52 
56  private ‪$output;
57 
61  private ‪$input;
62 
63  public function ‪__construct(
64  string $name,
65  private readonly ‪LateBootService $lateBootService,
66  private readonly DatabaseUpgradeWizardsService $databaseUpgradeWizardsService,
67  private readonly ‪SilentConfigurationUpgradeService $configurationUpgradeService
68  ) {
69  parent::__construct($name);
70  }
71 
76  protected function ‪bootstrap(): void
77  {
78  $this->upgradeWizardsService = $this->lateBootService
79  ->loadExtLocalconfDatabaseAndExtTables(false)
80  ->get(UpgradeWizardsService::class);
81  ‪Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
83  $this->databaseUpgradeWizardsService->isDatabaseCharsetUtf8()
84  ?: $this->databaseUpgradeWizardsService->setDatabaseCharsetUtf8();
85  // Ensure SilentConfigurationUpdates are also run on CLI, if necessary. Due to the fact that single silent
86  // upgrade tasks throwing a `ConfigurationChangedException` on changes and stopping the execution of following
87  // upgrades, we need to handle this in a loop handling this exception. Other errors leading to a direct stop,
88  // with an additionally concrete handling for readonly `settings.php`. To be safe against future end-less loops,
89  // a max-try check is used.
90  $loopSafety = 0;
91  do {
92  try {
93  $this->configurationUpgradeService->execute();
94  $success = true;
95  } catch (ConfigurationChangedException) {
96  // Due to the fact that single silent upgrade tasks emits and stops the upgrade chain, we need to
97  // handle this as not successful and continue processing the upgrade chain. Therefore, the loop.
98  $success = false;
99  } catch (SettingsWriteException $e) {
100  // Readonly or not-writable `settings.php`. Throw a more meaning full exception and stop the upgrade.
101  throw new SilentConfigurationUpgradeReadonlyException(1688462973, $e);
102  }
103  $loopSafety++;
104  } while ($success === false && $loopSafety < self::MAX_SILENT_UPGRADE_TRIES);
105  }
106 
110  protected function ‪configure()
111  {
112  $this->setDescription('Run upgrade wizard. Without arguments all available wizards will be run.')
113  ->addArgument(
114  'wizardName',
115  InputArgument::OPTIONAL
116  )->setHelp(
117  'This command allows running upgrade wizards on CLI. To run a single wizard add the ' .
118  'identifier of the wizard as argument. The identifier of the wizard is the name it is ' .
119  'registered with in ext_localconf.'
120  );
121  }
122 
126  protected function ‪execute(InputInterface ‪$input, OutputInterface ‪$output): int
127  {
128  $this->output = new SymfonyStyle(‪$input, ‪$output);
129  $this->input = ‪$input;
130  $this->‪bootstrap();
131 
132  if ($input->getArgument('wizardName')) {
133  $wizardToExecute = ‪$input->getArgument('wizardName');
134  $wizardToExecute = is_string($wizardToExecute) ? $wizardToExecute : '';
135  if ($this->upgradeWizardsService->isWizardDone($wizardToExecute)) {
136  $this->output->note(sprintf('Wizard %s marked as done. Skipped.', $wizardToExecute));
137  $result = Command::SUCCESS;
138  } elseif (($upgradeWizard = $this->‪getWizard($wizardToExecute)) !== null) {
139  $prerequisitesFulfilled = $this->‪handlePrerequisites([$upgradeWizard]);
140  if ($prerequisitesFulfilled === true) {
141  $result = $this->‪runSingleWizard($upgradeWizard);
142  } else {
143  $result = Command::FAILURE;
144  }
145  } else {
146  $this->output->error('No such wizard: ' . $wizardToExecute);
147  $result = Command::FAILURE;
148  }
149  } else {
150  $result = $this->‪runAllWizards();
151  }
152  return $result;
153  }
154 
159  protected function ‪getWizard(string ‪$identifier): ?UpgradeWizardInterface
160  {
161  // already done
162  if ($this->upgradeWizardsService->isWizardDone(‪$identifier)) {
163  return null;
164  }
165 
166  $wizard = $this->upgradeWizardsService->getUpgradeWizard(‪$identifier);
167  if ($wizard === null) {
168  return null;
169  }
170 
171  if ($wizard instanceof ChattyInterface) {
172  $wizard->setOutput($this->output);
173  }
174 
175  if ($wizard->updateNecessary()) {
176  return $wizard;
177  }
178  if ($wizard instanceof RepeatableInterface) {
179  $this->output->note('Wizard ' . ‪$identifier . ' does not need to make changes.');
180  } else {
181  $this->output->note('Wizard ' . ‪$identifier . ' does not need to make changes. Marking wizard as done.');
182  $this->upgradeWizardsService->markWizardAsDone($wizard);
183  }
184  return null;
185  }
186 
196  protected function ‪handlePrerequisites(array $instances): bool
197  {
198  $prerequisites = GeneralUtility::makeInstance(PrerequisiteCollection::class);
199  foreach ($instances as $instance) {
200  foreach ($instance->getPrerequisites() as $prerequisite) {
201  $prerequisites->add($prerequisite);
202  }
203  }
204  $result = true;
205  foreach ($prerequisites as $prerequisite) {
206  if ($prerequisite instanceof ChattyInterface) {
207  $prerequisite->setOutput($this->output);
208  }
209  if (!$prerequisite->isFulfilled()) {
210  $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" not fulfilled, will ensure.');
211  $result = $prerequisite->ensure();
212  if ($result === false) {
213  $this->output->error(
214  '<error>Error running ' .
215  $prerequisite->getTitle() .
216  '. Please ensure this prerequisite manually and try again.</error>'
217  );
218  break;
219  }
220  } else {
221  $this->output->writeln('Prerequisite "' . $prerequisite->getTitle() . '" fulfilled.');
222  }
223  }
224  return $result;
225  }
226 
227  protected function ‪runSingleWizard(
228  ‪UpgradeWizardInterface $instance
229  ): int {
230  $this->output->title('Running Wizard "' . $instance->‪getTitle() . '"');
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  );
246  $helper = $this->getHelper('question');
247  if (!$helper->ask($this->input, $this->output, $question)) {
248  if ($confirmation->isRequired()) {
249  $this->output->error('You have to acknowledge this wizard to continue');
250  return Command::FAILURE;
251  }
252  if ($instance instanceof ‪RepeatableInterface) {
253  $this->output->note('No changes applied.');
254  } else {
255  $this->upgradeWizardsService->markWizardAsDone($instance);
256  $this->output->note('No changes applied, marking wizard as done.');
257  }
258  return Command::SUCCESS;
259  }
260  }
261  if ($instance->‪executeUpdate()) {
262  $this->output->success('Successfully ran wizard ' . $instance->‪getTitle());
263  if (!$instance instanceof RepeatableInterface) {
264  $this->upgradeWizardsService->markWizardAsDone($instance);
265  }
266  return Command::SUCCESS;
267  }
268  $this->output->error('<error>Something went wrong while running ' . $instance->‪getTitle() . '</error>');
269  return Command::FAILURE;
270  }
271 
277  public function ‪runAllWizards(): int
278  {
279  $returnCode = Command::SUCCESS;
280  $wizardInstances = [];
281  foreach ($this->upgradeWizardsService->getUpgradeWizardIdentifiers() as ‪$identifier) {
282  $wizardInstances[] = $this->‪getWizard($identifier);
283  }
284  $wizardInstances = array_filter($wizardInstances);
285  if (count($wizardInstances) > 0) {
286  $prerequisitesResult = $this->‪handlePrerequisites($wizardInstances);
287  if ($prerequisitesResult === false) {
288  $returnCode = Command::FAILURE;
289  $this->output->error('Error handling prerequisites, aborting.');
290  } else {
291  $this->output->title('Found ' . count($wizardInstances) . ' wizard(s) to run.');
292  foreach ($wizardInstances as $wizardInstance) {
293  $result = $this->‪runSingleWizard($wizardInstance);
294  if ($result > 0) {
295  $returnCode = Command::FAILURE;
296  }
297  }
298  }
299  } else {
300  $this->output->success('No wizards left to run.');
301  }
302  return $returnCode;
303  }
304 }
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\handlePrerequisites
‪handlePrerequisites(array $instances)
Definition: UpgradeWizardRunCommand.php:194
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\__construct
‪__construct(string $name, private readonly LateBootService $lateBootService, private readonly DatabaseUpgradeWizardsService $databaseUpgradeWizardsService, private readonly SilentConfigurationUpgradeService $configurationUpgradeService)
Definition: UpgradeWizardRunCommand.php:61
‪TYPO3\CMS\Install\Updates\RepeatableInterface
Definition: RepeatableInterface.php:25
‪TYPO3\CMS\Install\Updates\UpgradeWizardInterface\getTitle
‪getTitle()
‪TYPO3\CMS\Install\Service\Exception\ConfigurationChangedException
Definition: ConfigurationChangedException.php:25
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\bootstrap
‪bootstrap()
Definition: UpgradeWizardRunCommand.php:74
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\execute
‪execute(InputInterface $input, OutputInterface $output)
Definition: UpgradeWizardRunCommand.php:124
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\$input
‪InputInterface $input
Definition: UpgradeWizardRunCommand.php:59
‪TYPO3\CMS\Install\Service\SilentConfigurationUpgradeService
Definition: SilentConfigurationUpgradeService.php:45
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\MAX_SILENT_UPGRADE_TRIES
‪const MAX_SILENT_UPGRADE_TRIES
Definition: UpgradeWizardRunCommand.php:50
‪TYPO3\CMS\Core\Configuration\Exception\SettingsWriteException
Definition: SettingsWriteException.php:25
‪TYPO3\CMS\Install\Updates\ChattyInterface
Definition: ChattyInterface.php:26
‪TYPO3\CMS\Install\Service\Exception\SilentConfigurationUpgradeReadonlyException
Definition: SilentConfigurationUpgradeReadonlyException.php:28
‪TYPO3\CMS\Install\Service\UpgradeWizardsService
Definition: UpgradeWizardsService.php:40
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\configure
‪configure()
Definition: UpgradeWizardRunCommand.php:108
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\getWizard
‪getWizard(string $identifier)
Definition: UpgradeWizardRunCommand.php:157
‪TYPO3\CMS\Install\Command
Definition: LanguagePackCommand.php:18
‪TYPO3\CMS\Core\Core\Bootstrap\initializeBackendUser
‪static initializeBackendUser($className=BackendUserAuthentication::class, ServerRequestInterface $request=null)
Definition: Bootstrap.php:513
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\$output
‪OutputInterface Symfony Component Console Style StyleInterface $output
Definition: UpgradeWizardRunCommand.php:55
‪TYPO3\CMS\Install\Updates\UpgradeWizardInterface\executeUpdate
‪executeUpdate()
‪TYPO3\CMS\Install\Service\LateBootService
Definition: LateBootService.php:27
‪TYPO3\CMS\Install\Updates\UpgradeWizardInterface
Definition: UpgradeWizardInterface.php:24
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand
Definition: UpgradeWizardRunCommand.php:49
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\runSingleWizard
‪runSingleWizard(UpgradeWizardInterface $instance)
Definition: UpgradeWizardRunCommand.php:225
‪TYPO3\CMS\Core\Core\Bootstrap
Definition: Bootstrap.php:63
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Install\Updates\PrerequisiteCollection
Definition: PrerequisiteCollection.php:23
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\$upgradeWizardsService
‪UpgradeWizardsService $upgradeWizardsService
Definition: UpgradeWizardRunCommand.php:51
‪TYPO3\CMS\Install\Command\UpgradeWizardRunCommand\runAllWizards
‪int runAllWizards()
Definition: UpgradeWizardRunCommand.php:275
‪TYPO3\CMS\Core\Core\Bootstrap\initializeBackendAuthentication
‪static initializeBackendAuthentication()
Definition: Bootstrap.php:528
‪TYPO3\CMS\Install\Updates\ConfirmableInterface
Definition: ConfirmableInterface.php:24
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication
Definition: CommandLineUserAuthentication.php:31