‪TYPO3CMS  ‪main
UpgradeWizardsService.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\Output\Output;
21 use Symfony\Component\Console\Output\StreamOutput;
33 
40 {
41  private StreamOutput ‪$output;
42 
43  public function ‪__construct(
44  private readonly ‪UpgradeWizardRegistry $upgradeWizardRegistry,
45  private readonly ‪Registry $registry,
46  ) {
47  $fileName = 'php://temp';
48  if (($stream = fopen($fileName, 'wb')) === false) {
49  throw new \RuntimeException('Unable to open stream "' . $fileName . '"', 1598341765);
50  }
51  $this->output = new StreamOutput($stream, Output::VERBOSITY_NORMAL, false);
52  }
53 
57  public function ‪listOfWizardsDone(): array
58  {
59  $wizardsDoneInRegistry = [];
60  foreach ($this->upgradeWizardRegistry->getUpgradeWizards() as ‪$identifier => $serviceName) {
61  if ($this->registry->get('installUpdate', $serviceName, false)) {
62  $wizardsDoneInRegistry[] = [
63  'class' => $serviceName,
64  'identifier' => ‪$identifier,
65  // @todo fetching the service to get the title should be improved
66  'title' => $this->upgradeWizardRegistry->getUpgradeWizard(‪$identifier)->getTitle(),
67  ];
68  }
69  }
70  return $wizardsDoneInRegistry;
71  }
72 
77  public function ‪listOfRowUpdatersDone(): array
78  {
79  $rowUpdatersDoneClassNames = $this->registry->get('installUpdateRows', 'rowUpdatersDone', []);
80  $rowUpdatersDone = [];
81  foreach ($rowUpdatersDoneClassNames as $rowUpdaterClassName) {
82  // Silently skip non-existing DatabaseRowsUpdateWizards
83  if (!class_exists($rowUpdaterClassName)) {
84  continue;
85  }
87  $rowUpdater = GeneralUtility::makeInstance($rowUpdaterClassName);
88  if (!$rowUpdater instanceof ‪RowUpdaterInterface) {
89  throw new \RuntimeException(
90  'Row updater must implement RowUpdaterInterface',
91  1484152906
92  );
93  }
94  $rowUpdatersDone[] = [
95  'class' => $rowUpdaterClassName,
96  'identifier' => $rowUpdaterClassName,
97  'title' => $rowUpdater->getTitle(),
98  ];
99  }
100  return $rowUpdatersDone;
101  }
102 
111  public function ‪markWizardUndone(string ‪$identifier): bool
112  {
113  $this->‪assertIdentifierIsValid($identifier);
114 
115  $aWizardHasBeenMarkedUndone = false;
116  foreach ($this->‪listOfWizardsDone() as $wizard) {
117  if ($wizard['identifier'] === ‪$identifier) {
118  $aWizardHasBeenMarkedUndone = true;
119  $this->registry->set('installUpdate', $wizard['class'], 0);
120  }
121  }
122  if (!$aWizardHasBeenMarkedUndone) {
123  $rowUpdatersDoneList = $this->‪listOfRowUpdatersDone();
124  $registryArray = $this->registry->get('installUpdateRows', 'rowUpdatersDone', []);
125  foreach ($rowUpdatersDoneList as $rowUpdater) {
126  if ($rowUpdater['identifier'] === ‪$identifier) {
127  $aWizardHasBeenMarkedUndone = true;
128  foreach ($registryArray as $rowUpdaterMarkedAsDonePosition => $rowUpdaterMarkedAsDone) {
129  if ($rowUpdaterMarkedAsDone === $rowUpdater['class']) {
130  unset($registryArray[$rowUpdaterMarkedAsDonePosition]);
131  break;
132  }
133  }
134  $this->registry->set('installUpdateRows', 'rowUpdatersDone', $registryArray);
135  }
136  }
137  }
138  return $aWizardHasBeenMarkedUndone;
139  }
140 
146  public function ‪getUpgradeWizardsList(): array
147  {
148  $wizards = [];
149  foreach (array_keys($this->upgradeWizardRegistry->getUpgradeWizards()) as ‪$identifier) {
150  if ($this->‪isWizardDone($identifier)) {
151  continue;
152  }
153 
154  $wizards[] = $this->‪getWizardInformationByIdentifier($identifier);
155  }
156  return $wizards;
157  }
158 
160  {
161  $this->‪assertIdentifierIsValid($identifier);
162 
163  if (is_subclass_of(‪$identifier, RowUpdaterInterface::class)) {
164  return [
165  'class' => ‪$identifier,
166  'identifier' => ‪$identifier,
167  'title' => ‪$identifier,
168  'shouldRenderWizard' => false,
169  'explanation' => '',
170  ];
171  }
172 
173  $wizard = $this->upgradeWizardRegistry->getUpgradeWizard(‪$identifier);
174 
175  if ($wizard instanceof ‪ChattyInterface) {
176  $wizard->setOutput($this->output);
177  }
178 
179  return [
180  'class' => $wizard::class,
181  'identifier' => ‪$identifier,
182  'title' => $wizard->getTitle(),
183  'shouldRenderWizard' => $wizard->updateNecessary(),
184  'explanation' => $wizard->getDescription(),
185  ];
186  }
187 
193  public function ‪getWizardUserInput(string ‪$identifier): array
194  {
195  $this->‪assertIdentifierIsValid($identifier);
196 
197  $wizard = $this->upgradeWizardRegistry->getUpgradeWizard(‪$identifier);
198  $wizardHtml = '';
199  if ($wizard instanceof ‪ConfirmableInterface) {
200  $markup = [];
201  $radioAttributes = [
202  'type' => 'radio',
203  'class' => 'btn-check',
204  'name' => 'install[values][' . ‪$identifier . '][install]',
205  'value' => '0',
206  ];
207  $markup[] = '<div class="panel panel-danger">';
208  $markup[] = ' <div class="panel-heading">';
209  $markup[] = htmlspecialchars($wizard->getConfirmation()->getTitle());
210  $markup[] = ' </div>';
211  $markup[] = ' <div class="panel-body">';
212  $markup[] = ' <p>' . nl2br(htmlspecialchars($wizard->getConfirmation()->getMessage())) . '</p>';
213  $markup[] = ' <div class="btn-group">';
214  if (!$wizard->getConfirmation()->isRequired()) {
215  $denyChecked = $wizard->getConfirmation()->getDefaultValue() === false ? ' checked' : '';
216  $markup[] = ' <input ' . GeneralUtility::implodeAttributes($radioAttributes, true) . $denyChecked . ' id="upgrade-wizard-deny">';
217  $markup[] = ' <label class="btn btn-default" for="upgrade-wizard-deny">' . $wizard->getConfirmation()->getDeny() . '</label>';
218  }
219  $radioAttributes['value'] = '1';
220  $confirmChecked = $wizard->getConfirmation()->getDefaultValue() === true ? ' checked' : '';
221  $markup[] = ' <input ' . GeneralUtility::implodeAttributes($radioAttributes, true) . $confirmChecked . ' id="upgrade-wizard-confirm">';
222  $markup[] = ' <label class="btn btn-default" for="upgrade-wizard-confirm">' . $wizard->getConfirmation()->getConfirm() . '</label>';
223  $markup[] = ' </div>';
224  $markup[] = ' </div>';
225  $markup[] = '</div>';
226  $wizardHtml = implode('', $markup);
227  }
228 
229  $result = [
230  'identifier' => ‪$identifier,
231  'title' => $wizard->getTitle(),
232  'description' => $wizard->getDescription(),
233  'wizardHtml' => $wizardHtml,
234  ];
235 
236  return $result;
237  }
238 
244  public function ‪executeWizard(string ‪$identifier, array $values): ‪FlashMessageQueue
245  {
246  $performResult = false;
247  $this->‪assertIdentifierIsValid($identifier);
248 
249  $wizard = $this->upgradeWizardRegistry->getUpgradeWizard(‪$identifier);
250 
251  if ($wizard instanceof ‪ChattyInterface) {
252  $wizard->setOutput($this->output);
253  }
254  $messages = new ‪FlashMessageQueue('install');
255 
256  if ($wizard instanceof ‪ConfirmableInterface) {
257  // value is set in request but is empty
258  $isSetButEmpty = isset($values[‪$identifier]['install']) && empty($values[‪$identifier]['install']);
259  $checkValue = (int)$values[‪$identifier]['install'];
260 
261  if ($checkValue === 1) {
262  // confirmation = yes, we do the update
263  $performResult = $wizard->executeUpdate();
264  } elseif ($wizard->getConfirmation()->isRequired()) {
265  // confirmation = no, but is required, we do *not* the update and fail
266  $performResult = false;
267  } elseif ($isSetButEmpty) {
268  // confirmation = no, but it is *not* required, we do *not* the update, but mark the wizard as done
269  $this->output->writeln('No changes applied, marking wizard as done.');
270  // confirmation was set to "no"
271  $performResult = true;
272  }
273  } else {
274  // confirmation yes or non-confirmable
275  $performResult = $wizard->executeUpdate();
276  }
277 
278  $stream = $this->output->getStream();
279  rewind($stream);
280  if ($performResult) {
281  if (!$wizard instanceof ‪RepeatableInterface) {
282  // mark wizard as done if it's not repeatable and was successful
283  $this->‪markWizardAsDone($wizard);
284  }
285  $messages->enqueue(
286  new ‪FlashMessage(
287  (string)stream_get_contents($stream),
288  'Update successful'
289  )
290  );
291  } else {
292  $messages->enqueue(
293  new ‪FlashMessage(
294  (string)stream_get_contents($stream),
295  'Update failed!',
296  ContextualFeedbackSeverity::ERROR
297  )
298  );
299  }
300  return $messages;
301  }
302 
309  public function ‪markWizardAsDone(‪UpgradeWizardInterface $upgradeWizard): void
310  {
311  $this->registry->set('installUpdate', $upgradeWizard::class, 1);
312  }
313 
320  public function ‪isWizardDone(string ‪$identifier): bool
321  {
322  $this->‪assertIdentifierIsValid($identifier);
323 
324  return (bool)$this->registry->get(
325  'installUpdate',
326  $this->upgradeWizardRegistry->getUpgradeWizard(‪$identifier)::class,
327  false
328  );
329  }
330 
335  {
336  try {
337  return $this->upgradeWizardRegistry->getUpgradeWizard(‪$identifier);
338  } catch (\UnexpectedValueException) {
339  return null;
340  }
341  }
342 
343  public function ‪getUpgradeWizardIdentifiers(): array
344  {
345  return array_keys($this->upgradeWizardRegistry->getUpgradeWizards());
346  }
347 
348  public function ‪getNonRepeatableUpgradeWizards(): array
349  {
350  $nonRepeatableUpgradeWizards = [];
351  foreach ($this->upgradeWizardRegistry->getUpgradeWizards() as ‪$identifier => $updateClassName) {
352  if (!in_array(RepeatableInterface::class, class_implements($updateClassName) ?: [], true)) {
353  $nonRepeatableUpgradeWizards[‪$identifier] = $updateClassName;
354  }
355  }
356  return $nonRepeatableUpgradeWizards;
357  }
358 
364  private function ‪assertIdentifierIsValid(string ‪$identifier): void
365  {
366  if (‪$identifier === '') {
367  throw new \RuntimeException('Empty upgrade wizard identifier given', 1650579934);
368  }
369  if (!is_subclass_of(‪$identifier, RowUpdaterInterface::class)
370  && !$this->upgradeWizardRegistry->hasUpgradeWizard(‪$identifier)
371  ) {
372  throw new \RuntimeException(
373  'The upgrade wizard identifier "' . ‪$identifier . '" must either be registered as upgrade wizard or it must implement TYPO3\CMS\Install\Updates\RowUpdater\RowUpdaterInterface',
374  1650546252
375  );
376  }
377  }
378 }
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\getNonRepeatableUpgradeWizards
‪getNonRepeatableUpgradeWizards()
Definition: UpgradeWizardsService.php:348
‪TYPO3\CMS\Install\Updates\RepeatableInterface
Definition: RepeatableInterface.php:25
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\getUpgradeWizardsList
‪array getUpgradeWizardsList()
Definition: UpgradeWizardsService.php:146
‪TYPO3\CMS\Install\Updates\RowUpdater\RowUpdaterInterface
Definition: RowUpdaterInterface.php:24
‪TYPO3\CMS\Install\Updates\ChattyInterface
Definition: ChattyInterface.php:26
‪TYPO3\CMS\Core\Registry
Definition: Registry.php:33
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\listOfWizardsDone
‪array listOfWizardsDone()
Definition: UpgradeWizardsService.php:57
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\getWizardUserInput
‪getWizardUserInput(string $identifier)
Definition: UpgradeWizardsService.php:193
‪TYPO3\CMS\Install\Service\UpgradeWizardsService
Definition: UpgradeWizardsService.php:40
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\assertIdentifierIsValid
‪assertIdentifierIsValid(string $identifier)
Definition: UpgradeWizardsService.php:364
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\markWizardUndone
‪bool markWizardUndone(string $identifier)
Definition: UpgradeWizardsService.php:111
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\listOfRowUpdatersDone
‪array listOfRowUpdatersDone()
Definition: UpgradeWizardsService.php:77
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\isWizardDone
‪bool isWizardDone(string $identifier)
Definition: UpgradeWizardsService.php:320
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\getUpgradeWizardIdentifiers
‪getUpgradeWizardIdentifiers()
Definition: UpgradeWizardsService.php:343
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\$output
‪StreamOutput $output
Definition: UpgradeWizardsService.php:41
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\getUpgradeWizard
‪getUpgradeWizard(string $identifier)
Definition: UpgradeWizardsService.php:334
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\getWizardInformationByIdentifier
‪getWizardInformationByIdentifier(string $identifier)
Definition: UpgradeWizardsService.php:159
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\executeWizard
‪executeWizard(string $identifier, array $values)
Definition: UpgradeWizardsService.php:244
‪TYPO3\CMS\Install\Updates\UpgradeWizardInterface
Definition: UpgradeWizardInterface.php:24
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:27
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\__construct
‪__construct(private readonly UpgradeWizardRegistry $upgradeWizardRegistry, private readonly Registry $registry,)
Definition: UpgradeWizardsService.php:43
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Messaging\FlashMessageQueue
Definition: FlashMessageQueue.php:29
‪TYPO3\CMS\Install\Updates\UpgradeWizardRegistry
Definition: UpgradeWizardRegistry.php:29
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\markWizardAsDone
‪markWizardAsDone(UpgradeWizardInterface $upgradeWizard)
Definition: UpgradeWizardsService.php:309
‪TYPO3\CMS\Install\Service
Definition: ClearCacheService.php:16
‪TYPO3\CMS\Install\Updates\ConfirmableInterface
Definition: ConfirmableInterface.php:24
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37