‪TYPO3CMS  9.5
UpgradeWizardsService.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types = 1);
3 
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 Doctrine\DBAL\Platforms\MySqlPlatform;
20 use Doctrine\DBAL\Schema\Column;
21 use Doctrine\DBAL\Schema\Table;
22 use Symfony\Component\Console\Output\Output;
23 use Symfony\Component\Console\Output\StreamOutput;
36 
42 {
43  private ‪$output;
44 
45  public function ‪__construct()
46  {
47  $this->output = new StreamOutput(fopen('php://temp', 'wb'), Output::VERBOSITY_NORMAL, false);
48  }
49 
53  public function ‪listOfWizardsDone(): array
54  {
55  $wizardsDoneInRegistry = [];
56  $registry = GeneralUtility::makeInstance(Registry::class);
57  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) {
58  if ($registry->get('installUpdate', $className, false)) {
59  $wizardInstance = GeneralUtility::makeInstance($className);
60  $wizardsDoneInRegistry[] = [
61  'class' => $className,
62  'identifier' => $identifier,
63  'title' => $wizardInstance->getTitle(),
64  ];
65  }
66  }
67  return $wizardsDoneInRegistry;
68  }
69 
74  public function ‪listOfRowUpdatersDone(): array
75  {
76  $registry = GeneralUtility::makeInstance(Registry::class);
77  $rowUpdatersDoneClassNames = $registry->get('installUpdateRows', 'rowUpdatersDone', []);
78  $rowUpdatersDone = [];
79  foreach ($rowUpdatersDoneClassNames as $rowUpdaterClassName) {
80  // Silently skip non existing DatabaseRowsUpdateWizards
81  if (!class_exists($rowUpdaterClassName)) {
82  continue;
83  }
85  $rowUpdater = GeneralUtility::makeInstance($rowUpdaterClassName);
86  if (!$rowUpdater instanceof ‪RowUpdaterInterface) {
87  throw new \RuntimeException(
88  'Row updater must implement RowUpdaterInterface',
89  1484152906
90  );
91  }
92  $rowUpdatersDone[] = [
93  'class' => $rowUpdaterClassName,
94  'identifier' => $rowUpdaterClassName,
95  'title' => $rowUpdater->getTitle(),
96  ];
97  }
98  return $rowUpdatersDone;
99  }
100 
109  public function ‪markWizardUndone(string $identifier): bool
110  {
111  $this->‪assertIdentifierIsValid($identifier);
112 
113  $registry = GeneralUtility::makeInstance(Registry::class);
114  $aWizardHasBeenMarkedUndone = false;
115  foreach ($this->‪listOfWizardsDone() as $wizard) {
116  if ($wizard['identifier'] === $identifier) {
117  $aWizardHasBeenMarkedUndone = true;
118  $registry->set('installUpdate', $wizard['class'], 0);
119  }
120  }
121  if (!$aWizardHasBeenMarkedUndone) {
122  $rowUpdatersDoneList = $this->‪listOfRowUpdatersDone();
123  $registryArray = $registry->get('installUpdateRows', 'rowUpdatersDone', []);
124  foreach ($rowUpdatersDoneList as $rowUpdater) {
125  if ($rowUpdater['identifier'] === $identifier) {
126  $aWizardHasBeenMarkedUndone = true;
127  foreach ($registryArray as $rowUpdaterMarkedAsDonePosition => $rowUpdaterMarkedAsDone) {
128  if ($rowUpdaterMarkedAsDone === $rowUpdater['class']) {
129  unset($registryArray[$rowUpdaterMarkedAsDonePosition]);
130  break;
131  }
132  }
133  $registry->set('installUpdateRows', 'rowUpdatersDone', $registryArray);
134  }
135  }
136  }
137  return $aWizardHasBeenMarkedUndone;
138  }
139 
145  public function ‪getBlockingDatabaseAdds(): array
146  {
147  $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
148  $databaseDefinitions = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
149 
150  $schemaMigrator = GeneralUtility::makeInstance(SchemaMigrator::class);
151  $databaseDifferences = $schemaMigrator->getSchemaDiffs($databaseDefinitions);
152 
153  $adds = [];
154  foreach ($databaseDifferences as $schemaDiff) {
155  foreach ($schemaDiff->newTables as $newTable) {
157  if (!is_array($adds['tables'])) {
158  $adds['tables'] = [];
159  }
160  $adds['tables'][] = [
161  'table' => $newTable->getName(),
162  ];
163  }
164  foreach ($schemaDiff->changedTables as $changedTable) {
165  foreach ($changedTable->addedColumns as $addedColumn) {
167  if (!is_array($adds['columns'])) {
168  $adds['columns'] = [];
169  }
170  $adds['columns'][] = [
171  'table' => $changedTable->name,
172  'field' => $addedColumn->getName(),
173  ];
174  }
175  foreach ($changedTable->addedIndexes as $addedIndex) {
177  if (!is_array($adds['indexes'])) {
178  $adds['indexes'] = [];
179  }
180  $adds['indexes'][] = [
181  'table' => $changedTable->name,
182  'index' => $addedIndex->getName(),
183  ];
184  }
185  }
186  }
187 
188  return $adds;
189  }
190 
194  public function ‪addMissingTablesAndFields(): array
195  {
196  $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
197  $databaseDefinitions = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
198  $schemaMigrator = GeneralUtility::makeInstance(SchemaMigrator::class);
199  return $schemaMigrator->install($databaseDefinitions, true);
200  }
201 
207  public function ‪isDatabaseCharsetUtf8(): bool
208  {
210  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
211  ->getConnectionByName(‪ConnectionPool::DEFAULT_CONNECTION_NAME);
212 
213  $isDefaultConnectionMysql = ($connection->getDatabasePlatform() instanceof MySqlPlatform);
214 
215  if (!$isDefaultConnectionMysql) {
216  // Not tested on non mysql
217  $charsetOk = true;
218  } else {
219  $queryBuilder = $connection->createQueryBuilder();
220  $charset = (string)$queryBuilder->select('DEFAULT_CHARACTER_SET_NAME')
221  ->from('information_schema.SCHEMATA')
222  ->where(
223  $queryBuilder->expr()->eq(
224  'SCHEMA_NAME',
225  $queryBuilder->createNamedParameter($connection->getDatabase(), \PDO::PARAM_STR)
226  )
227  )
228  ->setMaxResults(1)
229  ->execute()
230  ->fetchColumn();
231  // check if database charset is utf-8, also allows utf8mb4
232  $charsetOk = strpos($charset, 'utf8') === 0;
233  }
234  return $charsetOk;
235  }
236 
241  public function ‪setDatabaseCharsetUtf8()
242  {
243  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
244  ->getConnectionByName(‪ConnectionPool::DEFAULT_CONNECTION_NAME);
245  $sql = 'ALTER DATABASE ' . $connection->quoteIdentifier($connection->getDatabase()) . ' CHARACTER SET utf8';
246  $connection->exec($sql);
247  }
248 
254  public function ‪getUpgradeWizardsList(): array
255  {
256  $wizards = [];
257  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $class) {
258  if ($this->‪isWizardDone($identifier)) {
259  continue;
260  }
262  $wizardInstance = GeneralUtility::makeInstance($class);
263  $explanation = '';
264 
265  // $explanation is changed by reference in Update objects!
266  $shouldRenderWizard = false;
267  if ($wizardInstance instanceof ‪UpgradeWizardInterface) {
268  if ($wizardInstance instanceof ‪ChattyInterface) {
269  $wizardInstance->setOutput($this->output);
270  }
271  $shouldRenderWizard = $wizardInstance->updateNecessary();
272  $explanation = $wizardInstance->getDescription();
273  }
274 
275  $wizards[] = [
276  'class' => $class,
277  'identifier' => $identifier,
278  'title' => $wizardInstance->getTitle(),
279  'shouldRenderWizard' => $shouldRenderWizard,
280  'explanation' => $explanation,
281  ];
282  }
283  return $wizards;
284  }
285 
293  public function ‪getWizardUserInput(string $identifier): array
294  {
295  $this->‪assertIdentifierIsValid($identifier);
296 
297  $class = ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier];
298  $updateObject = GeneralUtility::makeInstance($class);
299  $wizardHtml = '';
300  if (method_exists($updateObject, 'getUserInput')) {
301  $wizardHtml = $updateObject->getUserInput('install[values][' . htmlspecialchars($identifier) . ']');
302  trigger_error(
303  'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use ConfirmableInterface directly.',
304  E_USER_DEPRECATED
305  );
306  } elseif ($updateObject instanceof ‪UpgradeWizardInterface && $updateObject instanceof ‪ConfirmableInterface) {
307  $markup = [];
308  $radioAttributes = [
309  'type' => 'radio',
310  'name' => 'install[values][' . $updateObject->getIdentifier() . '][install]',
311  'value' => 0
312  ];
313  $markup[] = '<div class="panel panel-danger">';
314  $markup[] = ' <div class="panel-heading">';
315  $markup[] = htmlspecialchars($updateObject->getConfirmation()->getTitle());
316  $markup[] = ' </div>';
317  $markup[] = ' <div class="panel-body">';
318  $markup[] = ' <p>' . nl2br(htmlspecialchars($updateObject->getConfirmation()->getMessage())) . '</p>';
319  $markup[] = ' <div class="btn-group" data-toggle="buttons">';
320  if (!$updateObject->getConfirmation()->isRequired()) {
321  $markup[] = ' <label class="btn btn-default active"><input ' . GeneralUtility::implodeAttributes($radioAttributes, true) . ' checked="checked" />' . $updateObject->getConfirmation()->getDeny() . '</label>';
322  }
323  $radioAttributes['value'] = 1;
324  $markup[] = ' <label class="btn btn-default"><input ' . GeneralUtility::implodeAttributes($radioAttributes, true) . ' />' . $updateObject->getConfirmation()->getConfirm() . '</label>';
325  $markup[] = ' </div>';
326  $markup[] = ' </div>';
327  $markup[] = '</div>';
328  $wizardHtml = implode('', $markup);
329  }
330 
331  $result = [
332  'identifier' => $identifier,
333  'title' => $updateObject->getTitle(),
334  'wizardHtml' => $wizardHtml,
335  ];
336 
337  return $result;
338  }
339 
347  public function ‪executeWizard(string $identifier): ‪FlashMessageQueue
348  {
349  $this->‪assertIdentifierIsValid($identifier);
350 
351  $class = ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier];
352  $updateObject = GeneralUtility::makeInstance($class);
353 
354  if ($updateObject instanceof ‪ChattyInterface) {
355  $updateObject->setOutput($this->output);
356  }
357  $messages = new ‪FlashMessageQueue('install');
358  // $wizardInputErrorMessage is given as reference to wizard object!
359  $wizardInputErrorMessage = '';
360  if (method_exists($updateObject, 'checkUserInput') &&
361  !$updateObject->checkUserInput($wizardInputErrorMessage)) {
362  trigger_error(
363  'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use ConfirmableInterface.',
364  E_USER_DEPRECATED
365  );
366  $messages->enqueue(
367  new ‪FlashMessage(
368  $wizardInputErrorMessage ?: 'Something went wrong!',
369  'Input parameter broken',
371  )
372  );
373  } else {
374  if ($updateObject instanceof ‪UpgradeWizardInterface) {
375  $requestParams = GeneralUtility::_GP('install');
376  if ($updateObject instanceof ‪ConfirmableInterface) {
377  // value is set in request but is empty
378  $isSetButEmpty = isset($requestParams['values'][$updateObject->getIdentifier()]['install'])
379  && empty($requestParams['values'][$updateObject->getIdentifier()]['install']);
380 
381  $checkValue = (int)$requestParams['values'][$updateObject->getIdentifier()]['install'];
382 
383  if ($checkValue === 1) {
384  // confirmation = yes, we do the update
385  $performResult = $updateObject->executeUpdate();
386  } elseif ($updateObject->getConfirmation()->isRequired()) {
387  // confirmation = no, but is required, we do *not* the update and fail
388  $performResult = false;
389  } elseif ($isSetButEmpty) {
390  // confirmation = no, but it is *not* required, we do *not* the update, but mark the wizard as done
391  $this->output->writeln('No changes applied, marking wizard as done.');
392  // confirmation was set to "no"
393  $performResult = true;
394  }
395  } else {
396  // confirmation yes or non-confirmable
397  $performResult = $updateObject->executeUpdate();
398  }
399  }
400 
401  $stream = $this->output->getStream();
402  rewind($stream);
403  if ($performResult) {
404  if ($updateObject instanceof ‪UpgradeWizardInterface && !($updateObject instanceof ‪RepeatableInterface)) {
405  // mark wizard as done if it's not repeatable and was successful
406  $this->‪markWizardAsDone($updateObject->getIdentifier());
407  }
408  $messages->enqueue(
409  new ‪FlashMessage(
410  stream_get_contents($stream),
411  'Update successful'
412  )
413  );
414  } else {
415  $messages->enqueue(
416  new ‪FlashMessage(
417  stream_get_contents($stream),
418  'Update failed!',
420  )
421  );
422  }
423  }
424  return $messages;
425  }
426 
434  public function ‪markWizardAsDone(string $identifier): void
435  {
436  $this->‪assertIdentifierIsValid($identifier);
437 
438  $class = ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier];
439  GeneralUtility::makeInstance(Registry::class)->set('installUpdate', $class, 1);
440  }
441 
449  public function ‪isWizardDone(string $identifier): bool
450  {
451  $this->‪assertIdentifierIsValid($identifier);
452 
453  $class = ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier];
454  return (bool)GeneralUtility::makeInstance(Registry::class)->get('installUpdate', $class, false);
455  }
456 
463  protected function ‪assertIdentifierIsValid(string $identifier): void
464  {
465  if ($identifier === '' || (!isset(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$identifier]) && !is_subclass_of($identifier, RowUpdaterInterface::class))) {
466  throw new \RuntimeException('No valid wizard identifier given', 1502721731);
467  }
468  }
469 }
‪TYPO3\CMS\Install\Updates\RepeatableInterface
Definition: RepeatableInterface.php:25
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\getUpgradeWizardsList
‪array getUpgradeWizardsList()
Definition: UpgradeWizardsService.php:254
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\getBlockingDatabaseAdds
‪array getBlockingDatabaseAdds()
Definition: UpgradeWizardsService.php:145
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\__construct
‪__construct()
Definition: UpgradeWizardsService.php:45
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\setDatabaseCharsetUtf8
‪setDatabaseCharsetUtf8()
Definition: UpgradeWizardsService.php:241
‪TYPO3\CMS\Install\Updates\RowUpdater\RowUpdaterInterface
Definition: RowUpdaterInterface.php:22
‪TYPO3\CMS\Install\Updates\ChattyInterface
Definition: ChattyInterface.php:25
‪TYPO3\CMS\Core\Registry
Definition: Registry.php:32
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\listOfWizardsDone
‪array listOfWizardsDone()
Definition: UpgradeWizardsService.php:53
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\getWizardUserInput
‪array getWizardUserInput(string $identifier)
Definition: UpgradeWizardsService.php:293
‪TYPO3\CMS\Core\Database\Schema\SqlReader
Definition: SqlReader.php:29
‪TYPO3\CMS\Core\Database\ConnectionPool\DEFAULT_CONNECTION_NAME
‪const DEFAULT_CONNECTION_NAME
Definition: ConnectionPool.php:48
‪TYPO3\CMS\Core\Database\Schema\SchemaMigrator
Definition: SchemaMigrator.php:35
‪TYPO3\CMS\Install\Service\UpgradeWizardsService
Definition: UpgradeWizardsService.php:42
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\assertIdentifierIsValid
‪assertIdentifierIsValid(string $identifier)
Definition: UpgradeWizardsService.php:463
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\markWizardUndone
‪bool markWizardUndone(string $identifier)
Definition: UpgradeWizardsService.php:109
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\$output
‪$output
Definition: UpgradeWizardsService.php:43
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\listOfRowUpdatersDone
‪array listOfRowUpdatersDone()
Definition: UpgradeWizardsService.php:74
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\addMissingTablesAndFields
‪addMissingTablesAndFields()
Definition: UpgradeWizardsService.php:194
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\isWizardDone
‪bool isWizardDone(string $identifier)
Definition: UpgradeWizardsService.php:449
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\markWizardAsDone
‪markWizardAsDone(string $identifier)
Definition: UpgradeWizardsService.php:434
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\isDatabaseCharsetUtf8
‪bool isDatabaseCharsetUtf8()
Definition: UpgradeWizardsService.php:207
‪TYPO3\CMS\Install\Updates\UpgradeWizardInterface
Definition: UpgradeWizardInterface.php:22
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:22
‪TYPO3\CMS\Install\Service\UpgradeWizardsService\executeWizard
‪FlashMessageQueue executeWizard(string $identifier)
Definition: UpgradeWizardsService.php:347
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:44
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Messaging\FlashMessageQueue
Definition: FlashMessageQueue.php:25
‪TYPO3\CMS\Install\Service
Definition: ClearCacheService.php:2
‪TYPO3\CMS\Core\Messaging\AbstractMessage\ERROR
‪const ERROR
Definition: AbstractMessage.php:29
‪TYPO3\CMS\Install\Updates\ConfirmableInterface
Definition: ConfirmableInterface.php:23