TYPO3 CMS  TYPO3_8-7
UpgradeWizard.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
31 
36 {
43 
49  protected function executeAction()
50  {
51  // ext_localconf, db and ext_tables must be loaded for the updates
53 
54  if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'])) {
55  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] = [];
56  }
57 
58  $actionMessages = [];
59 
60  try {
61  // To make sure DatabaseCharsetUpdate and initialUpdateDatabaseSchema are first wizards, they are added here instead of ext_localconf.php
62  $databaseCharsetUpdateObject = $this->getUpdateObjectInstance(\TYPO3\CMS\Install\Updates\DatabaseCharsetUpdate::class, 'databaseCharsetUpdate');
63  if ($databaseCharsetUpdateObject->shouldRenderWizard()) {
64  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] = array_merge(
65  ['databaseCharsetUpdate' => \TYPO3\CMS\Install\Updates\DatabaseCharsetUpdate::class],
66  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']
67  );
68  }
69  $initialUpdateDatabaseSchemaUpdateObject = $this->getUpdateObjectInstance(\TYPO3\CMS\Install\Updates\InitialDatabaseSchemaUpdate::class, 'initialUpdateDatabaseSchema');
70  if ($initialUpdateDatabaseSchemaUpdateObject->shouldRenderWizard()) {
71  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] = array_merge(
72  ['initialUpdateDatabaseSchema' => \TYPO3\CMS\Install\Updates\InitialDatabaseSchemaUpdate::class],
73  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']
74  );
75  $this->needsInitialUpdateDatabaseSchema = true;
76  }
77 
78  // To make sure finalUpdateDatabaseSchema is last wizard, it is added here instead of ext_localconf.php
79  $finalUpdateDatabaseSchemaUpdateObject = $this->getUpdateObjectInstance(\TYPO3\CMS\Install\Updates\FinalDatabaseSchemaUpdate::class, 'finalUpdateDatabaseSchema');
80  if ($finalUpdateDatabaseSchemaUpdateObject->shouldRenderWizard()) {
81  $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['finalUpdateDatabaseSchema'] = \TYPO3\CMS\Install\Updates\FinalDatabaseSchemaUpdate::class;
82  }
83  } catch (StatementException $exception) {
85  $message = GeneralUtility::makeInstance(ErrorStatus::class);
86  $message->setTitle('SQL error');
87  $message->setMessage($exception->getMessage());
88  $actionMessages[] = $message;
89  }
90 
91  // Perform silent cache framework table upgrade
93 
94  if (isset($this->postValues['set']['getUserInput'])) {
95  $actionMessages[] = $this->getUserInputForUpdate();
96  $this->view->assign('updateAction', 'getUserInput');
97  } elseif (isset($this->postValues['set']['performUpdate'])) {
98  $actionMessages[] = $this->performUpdate();
99  $this->view->assign('updateAction', 'performUpdate');
100  } elseif (isset($this->postValues['set']['recheckWizards'])) {
101  $actionMessages[] = $this->recheckWizardsAndRowUpdaters();
102  if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'])) {
104  $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\WarningStatus::class);
105  $message->setTitle('No update wizards registered');
106  $actionMessages[] = $message;
107  }
108  $this->listUpdates();
109  $this->view->assign('updateAction', 'listUpdates');
110  } else {
111  if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'])) {
113  $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\WarningStatus::class);
114  $message->setTitle('No update wizards registered');
115  $actionMessages[] = $message;
116  }
117  $this->listUpdates();
118  $this->view->assign('updateAction', 'listUpdates');
119  }
120 
121  $this->view->assign('actionMessages', $actionMessages);
122 
123  return $this->view->render();
124  }
125 
129  protected function listUpdates()
130  {
131  $availableUpdates = [];
132  $markedWizardsDoneInRegistry = [];
133  $markedWizardsDoneByCallingShouldRenderWizard = [];
134  $registry = GeneralUtility::makeInstance(Registry::class);
135  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) {
136  $updateObject = $this->getUpdateObjectInstance($className, $identifier);
137  $markedDoneInRegistry = $registry->get('installUpdate', $className, false);
138  if ($markedDoneInRegistry) {
139  $markedWizardsDoneInRegistry[] = [
140  'identifier' => $identifier,
141  'title' => $updateObject->getTitle(),
142  ];
143  } else {
144  if ($updateObject->shouldRenderWizard()) {
145  // $explanation is changed by reference in Update objects!
146  $explanation = '';
147  $updateObject->checkForUpdate($explanation);
148  $availableUpdates[$identifier] = [
149  'identifier' => $identifier,
150  'title' => $updateObject->getTitle(),
151  'explanation' => $explanation,
152  'renderNext' => false,
153  ];
154  if ($identifier === 'initialUpdateDatabaseSchema') {
155  $availableUpdates['initialUpdateDatabaseSchema']['renderNext'] = $this->needsInitialUpdateDatabaseSchema;
156  // initialUpdateDatabaseSchema is always the first update
157  // we stop immediately here as the remaining updates may
158  // require the new fields to be present in order to avoid SQL errors
159  break;
160  }
161  if ($identifier === 'finalUpdateDatabaseSchema') {
162  // Okay to check here because finalUpdateDatabaseSchema is last element in array
163  $availableUpdates['finalUpdateDatabaseSchema']['renderNext'] = count($availableUpdates) === 1;
164  } elseif (!$this->needsInitialUpdateDatabaseSchema && $updateObject->shouldRenderNextButton()) {
165  // There are Updates that only show text and don't want to be executed
166  $availableUpdates[$identifier]['renderNext'] = true;
167  }
168  } else {
169  $markedWizardsDoneByCallingShouldRenderWizard[] = [
170  'identifier' => $identifier,
171  'title' => $updateObject->getTitle(),
172  ];
173  }
174  }
175  }
176 
177  // List of row updaters marked as done from "DatabaseRowsUpdateWizard"
178  $rowUpdatersDoneClassNames = GeneralUtility::makeInstance(Registry::class)->get('installUpdateRows', 'rowUpdatersDone', []);
179  $rowUpdatersDone = [];
180  foreach ($rowUpdatersDoneClassNames as $rowUpdaterClassName) {
181  // Silently skip non existing DatabaseRowsUpdateWizard's
182  if (!class_exists($rowUpdaterClassName)) {
183  continue;
184  }
186  $rowUpdater = GeneralUtility::makeInstance($rowUpdaterClassName);
187  if (!$rowUpdater instanceof RowUpdaterInterface) {
188  throw new \RuntimeException(
189  'Row updater must implement RowUpdaterInterface',
190  1484152906
191  );
192  }
193  $rowUpdatersDone[] = [
194  'identifier' => $rowUpdaterClassName,
195  'title' => $rowUpdater->getTitle(),
196  ];
197  }
198 
199  $wizardsTotal = (count($markedWizardsDoneInRegistry) + count($markedWizardsDoneByCallingShouldRenderWizard) + count($availableUpdates));
200  $percentageDone = floor(($wizardsTotal - count($availableUpdates)) * 100 / $wizardsTotal);
201 
202  $this->view->assign('wizardsDone', $markedWizardsDoneInRegistry);
203  $this->view->assign('rowUpdatersDone', $rowUpdatersDone);
204  $this->view->assign('availableUpdates', $availableUpdates);
205  $this->view->assign('wizardsTotal', $wizardsTotal);
206  $this->view->assign('wizardsPercentageDone', $percentageDone);
207  }
208 
214  protected function getUserInputForUpdate()
215  {
216  $wizardIdentifier = $this->postValues['values']['identifier'];
217 
218  $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardIdentifier];
219  $updateObject = $this->getUpdateObjectInstance($className, $wizardIdentifier);
220  $wizardHtml = '';
221  if (method_exists($updateObject, 'getUserInput')) {
222  $wizardHtml = $updateObject->getUserInput('install[values][' . $wizardIdentifier . ']');
223  }
224 
225  $updateData = [
226  'identifier' => $wizardIdentifier,
227  'title' => $updateObject->getTitle(),
228  'wizardHtml' => $wizardHtml,
229  ];
230 
231  $this->view->assign('updateData', $updateData);
232 
234  $message = GeneralUtility::makeInstance(OkStatus::class);
235  $message->setTitle('Show wizard options');
236  return $message;
237  }
238 
244  protected function recheckWizardsAndRowUpdaters()
245  {
246  if (empty($this->postValues['values']['recheck']) && empty($this->postValues['values']['recheckRowUpdater'])) {
247  $message = GeneralUtility::makeInstance(NoticeStatus::class);
248  $message->setTitle('No wizards selected to recheck');
249  return $message;
250  }
251  $registry = GeneralUtility::makeInstance(Registry::class);
252  if (!empty($this->postValues['values']['recheck'])) {
253  foreach ($this->postValues['values']['recheck'] as $wizardIdentifier => $value) {
254  $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardIdentifier];
255  $updateObject = $this->getUpdateObjectInstance($className, $wizardIdentifier);
256  $registry->set('installUpdate', get_class($updateObject), 0);
257  }
258  }
259  if (!empty($this->postValues['values']['recheckRowUpdater'])) {
260  $rowUpdatersToRecheck = $this->postValues['values']['recheckRowUpdater'];
261  $rowUpdatersMarkedAsDone = $registry->get('installUpdateRows', 'rowUpdatersDone', []);
262  foreach ($rowUpdatersToRecheck as $rowUpdaterToReCheckClassName => $value) {
263  foreach ($rowUpdatersMarkedAsDone as $rowUpdaterMarkedAsDonePosition => $rowUpdaterMarkedAsDone) {
264  if ($rowUpdaterMarkedAsDone === $rowUpdaterToReCheckClassName) {
265  unset($rowUpdatersMarkedAsDone[$rowUpdaterMarkedAsDonePosition]);
266  break;
267  }
268  }
269  }
270  $registry->set('installUpdateRows', 'rowUpdatersDone', $rowUpdatersMarkedAsDone);
271  }
272 
273  $message = GeneralUtility::makeInstance(OkStatus::class);
274  $message->setTitle('Successfully rechecked');
275  return $message;
276  }
277 
284  protected function performUpdate()
285  {
286  $wizardIdentifier = $this->postValues['values']['identifier'];
287  $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardIdentifier];
288  $updateObject = $this->getUpdateObjectInstance($className, $wizardIdentifier);
289 
290  $wizardData = [
291  'identifier' => $wizardIdentifier,
292  'title' => $updateObject->getTitle(),
293  ];
294 
295  // $wizardInputErrorMessage is given as reference to wizard object!
296  $wizardInputErrorMessage = '';
297  if (method_exists($updateObject, 'checkUserInput') && !$updateObject->checkUserInput($wizardInputErrorMessage)) {
299  $message = GeneralUtility::makeInstance(ErrorStatus::class);
300  $message->setTitle('Input parameter broken');
301  $message->setMessage($wizardInputErrorMessage ?: 'Something went wrong!');
302  $wizardData['wizardInputBroken'] = true;
303  } else {
304  if (!method_exists($updateObject, 'performUpdate')) {
305  throw new \TYPO3\CMS\Install\Exception(
306  'No performUpdate method in update wizard with identifier ' . $wizardIdentifier,
307  1371035200
308  );
309  }
310 
311  // Both variables are used by reference in performUpdate()
312  $customOutput = '';
313  $databaseQueries = [];
314  $performResult = $updateObject->performUpdate($databaseQueries, $customOutput);
315 
316  if ($performResult) {
318  $message = GeneralUtility::makeInstance(OkStatus::class);
319  $message->setTitle('Update successful');
320  } else {
322  $message = GeneralUtility::makeInstance(ErrorStatus::class);
323  $message->setTitle('Update failed!');
324  if ($customOutput) {
325  $message->setMessage($customOutput);
326  }
327  }
328 
329  if ($this->postValues['values']['showDatabaseQueries'] == 1) {
330  $wizardData['queries'] = $databaseQueries;
331  }
332  }
333 
334  $this->view->assign('wizardData', $wizardData);
335 
336  // Next update wizard, if available
337  $nextUpdate = $this->getNextUpdateInstance($updateObject);
338  $nextUpdateIdentifier = '';
339  if ($nextUpdate) {
340  $nextUpdateIdentifier = $nextUpdate->getIdentifier();
341  }
342  $this->view->assign('nextUpdateIdentifier', $nextUpdateIdentifier);
343 
344  return $message;
345  }
346 
354  protected function getUpdateObjectInstance($className, $identifier)
355  {
356  $userInput = $this->postValues['values'][$identifier];
357  $versionAsInt = VersionNumberUtility::convertVersionNumberToInteger(TYPO3_version);
358  return GeneralUtility::makeInstance($className, $identifier, $versionAsInt, $userInput, $this);
359  }
360 
368  protected function getNextUpdateInstance(AbstractUpdate $currentUpdate)
369  {
370  $isPreviousRecord = true;
371  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) {
372  // Find the current update wizard, and then start validating the next ones
373  if ($currentUpdate->getIdentifier() === $identifier) {
374  $isPreviousRecord = false;
375  // For the updateDatabaseSchema-wizards verify they do not have to be executed again
376  if ($identifier !== 'initialUpdateDatabaseSchema' && $identifier !== 'finalUpdateDatabaseSchema') {
377  continue;
378  }
379  }
380  if (!$isPreviousRecord) {
381  $nextUpdate = $this->getUpdateObjectInstance($className, $identifier);
382  if ($nextUpdate->shouldRenderWizard()) {
383  return $nextUpdate;
384  }
385  }
386  }
387  return null;
388  }
389 
404  {
405  $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
406  $cachingFrameworkDatabaseSchemaService = GeneralUtility::makeInstance(DatabaseSchemaService::class);
407  $createTableStatements = $sqlReader->getStatementArray(
408  $cachingFrameworkDatabaseSchemaService->getCachingFrameworkRequiredDatabaseSchema()
409  );
410 
411  if (!empty($createTableStatements)) {
412  $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
413  $schemaMigrationService->install($createTableStatements);
414  }
415  }
416 }
static makeInstance($className,... $constructorArguments)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']