2 namespace TYPO3\CMS\Install\Controller\Action\Tool
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Core\Cache\DatabaseSchemaService
;
18 use TYPO3\CMS\Core\Database\Schema\Exception\StatementException
;
19 use TYPO3\CMS\Core\Database\Schema\SchemaMigrator
;
20 use TYPO3\CMS\Core\Database\Schema\SqlReader
;
21 use TYPO3\CMS\Core\Utility\GeneralUtility
;
22 use TYPO3\CMS\Core\Utility\VersionNumberUtility
;
23 use TYPO3\CMS\Install\Controller\Action
;
24 use TYPO3\CMS\Install\Status\ErrorStatus
;
25 use TYPO3\CMS\Install\Updates\AbstractUpdate
;
28 * Handle update wizards
30 class UpgradeWizard
extends Action\AbstractAction
33 * There are tables and fields missing in the database
37 protected $needsInitialUpdateDatabaseSchema = false;
42 * @return string Rendered content
44 protected function executeAction()
46 // ext_localconf, db and ext_tables must be loaded for the updates
47 $this->loadExtLocalconfDatabaseAndExtTables();
49 if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'])) {
50 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] = [];
56 // To make sure DatabaseCharsetUpdate and initialUpdateDatabaseSchema are first wizards, they are added here instead of ext_localconf.php
57 $databaseCharsetUpdateObject = $this->getUpdateObjectInstance(\TYPO3\CMS\Install\Updates\DatabaseCharsetUpdate
::class, 'databaseCharsetUpdate');
58 if ($databaseCharsetUpdateObject->shouldRenderWizard()) {
59 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] = array_merge(
60 ['databaseCharsetUpdate' => \TYPO3\CMS\Install\Updates\DatabaseCharsetUpdate
::class],
61 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']
64 $initialUpdateDatabaseSchemaUpdateObject = $this->getUpdateObjectInstance(\TYPO3\CMS\Install\Updates\InitialDatabaseSchemaUpdate
::class, 'initialUpdateDatabaseSchema');
65 if ($initialUpdateDatabaseSchemaUpdateObject->shouldRenderWizard()) {
66 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] = array_merge(
67 ['initialUpdateDatabaseSchema' => \TYPO3\CMS\Install\Updates\InitialDatabaseSchemaUpdate
::class],
68 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']
70 $this->needsInitialUpdateDatabaseSchema
= true;
73 // To make sure finalUpdateDatabaseSchema is last wizard, it is added here instead of ext_localconf.php
74 $finalUpdateDatabaseSchemaUpdateObject = $this->getUpdateObjectInstance(\TYPO3\CMS\Install\Updates\FinalDatabaseSchemaUpdate
::class, 'finalUpdateDatabaseSchema');
75 if ($finalUpdateDatabaseSchemaUpdateObject->shouldRenderWizard()) {
76 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['finalUpdateDatabaseSchema'] = \TYPO3\CMS\Install\Updates\FinalDatabaseSchemaUpdate
::class;
78 } catch (StatementException
$exception) {
79 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
80 $message = GeneralUtility
::makeInstance(ErrorStatus
::class);
81 $message->setTitle('SQL error');
82 $message->setMessage($exception->getMessage());
83 $actionMessages[] = $message;
86 // Perform silent cache framework table upgrade
87 $this->silentCacheFrameworkTableSchemaMigration();
89 if (isset($this->postValues
['set']['getUserInput'])) {
90 $actionMessages[] = $this->getUserInputForUpdate();
91 $this->view
->assign('updateAction', 'getUserInput');
92 } elseif (isset($this->postValues
['set']['performUpdate'])) {
93 $actionMessages[] = $this->performUpdate();
94 $this->view
->assign('updateAction', 'performUpdate');
97 $this->view
->assign('updateAction', 'listUpdates');
100 $this->view
->assign('actionMessages', $actionMessages);
102 return $this->view
->render();
106 * List of available updates
110 protected function listUpdates()
112 if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'])) {
113 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
114 $message = GeneralUtility
::makeInstance(\TYPO3\CMS\Install\Status\WarningStatus
::class);
115 $message->setTitle('No update wizards registered');
119 $availableUpdates = [];
120 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) {
121 $updateObject = $this->getUpdateObjectInstance($className, $identifier);
122 if ($updateObject->shouldRenderWizard()) {
123 // $explanation is changed by reference in Update objects!
125 $updateObject->checkForUpdate($explanation);
126 $availableUpdates[$identifier] = [
127 'identifier' => $identifier,
128 'title' => $updateObject->getTitle(),
129 'explanation' => $explanation,
130 'renderNext' => false,
132 if ($identifier === 'initialUpdateDatabaseSchema') {
133 $availableUpdates['initialUpdateDatabaseSchema']['renderNext'] = $this->needsInitialUpdateDatabaseSchema
;
134 // initialUpdateDatabaseSchema is always the first update
135 // we stop immediately here as the remaining updates may
136 // require the new fields to be present in order to avoid SQL errors
138 } elseif ($identifier === 'finalUpdateDatabaseSchema') {
139 // Okay to check here because finalUpdateDatabaseSchema is last element in array
140 $availableUpdates['finalUpdateDatabaseSchema']['renderNext'] = count($availableUpdates) === 1;
141 } elseif (!$this->needsInitialUpdateDatabaseSchema
&& $updateObject->shouldRenderNextButton()) {
142 // There are Updates that only show text and don't want to be executed
143 $availableUpdates[$identifier]['renderNext'] = true;
148 $this->view
->assign('availableUpdates', $availableUpdates);
150 // compute done wizards for statistics
152 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) {
153 /** @var AbstractUpdate $updateObject */
154 $updateObject = $this->getUpdateObjectInstance($className, $identifier);
155 if ($updateObject->shouldRenderWizard() !== true) {
156 $wizardsDone[] = $updateObject;
159 $this->view
->assign('wizardsDone', $wizardsDone);
161 $wizardsTotal = (count($wizardsDone) +
count($availableUpdates));
162 $this->view
->assign('wizardsTotal', $wizardsTotal);
164 $this->view
->assign('wizardsPercentageDone', floor(($wizardsTotal - count($availableUpdates)) * 100 / $wizardsTotal));
168 * Get user input of update wizard
170 * @return \TYPO3\CMS\Install\Status\StatusInterface
172 protected function getUserInputForUpdate()
174 $wizardIdentifier = $this->postValues
['values']['identifier'];
176 $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardIdentifier];
177 $updateObject = $this->getUpdateObjectInstance($className, $wizardIdentifier);
179 if (method_exists($updateObject, 'getUserInput')) {
180 $wizardHtml = $updateObject->getUserInput('install[values][' . $wizardIdentifier . ']');
184 'identifier' => $wizardIdentifier,
185 'title' => $updateObject->getTitle(),
186 'wizardHtml' => $wizardHtml,
189 $this->view
->assign('updateData', $updateData);
191 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
192 $message = GeneralUtility
::makeInstance(\TYPO3\CMS\Install\Status\OkStatus
::class);
193 $message->setTitle('Show wizard options');
198 * Perform update of a specific wizard
200 * @throws \TYPO3\CMS\Install\Exception
201 * @return \TYPO3\CMS\Install\Status\StatusInterface
203 protected function performUpdate()
205 $this->getDatabaseConnection()->store_lastBuiltQuery
= true;
207 $wizardIdentifier = $this->postValues
['values']['identifier'];
208 $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardIdentifier];
209 $updateObject = $this->getUpdateObjectInstance($className, $wizardIdentifier);
212 'identifier' => $wizardIdentifier,
213 'title' => $updateObject->getTitle(),
216 // $wizardInputErrorMessage is given as reference to wizard object!
217 $wizardInputErrorMessage = '';
218 if (method_exists($updateObject, 'checkUserInput') && !$updateObject->checkUserInput($wizardInputErrorMessage)) {
219 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
220 $message = GeneralUtility
::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus
::class);
221 $message->setTitle('Input parameter broken');
222 $message->setMessage($wizardInputErrorMessage ?
: 'Something went wrong!');
223 $wizardData['wizardInputBroken'] = true;
225 if (!method_exists($updateObject, 'performUpdate')) {
226 throw new \TYPO3\CMS\Install\
Exception(
227 'No performUpdate method in update wizard with identifier ' . $wizardIdentifier,
232 // Both variables are used by reference in performUpdate()
234 $databaseQueries = [];
235 $performResult = $updateObject->performUpdate($databaseQueries, $customOutput);
237 if ($performResult) {
238 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
239 $message = GeneralUtility
::makeInstance(\TYPO3\CMS\Install\Status\OkStatus
::class);
240 $message->setTitle('Update successful');
242 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
243 $message = GeneralUtility
::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus
::class);
244 $message->setTitle('Update failed!');
246 $message->setMessage($customOutput);
250 if ($this->postValues
['values']['showDatabaseQueries'] == 1) {
251 $wizardData['queries'] = $databaseQueries;
255 $this->view
->assign('wizardData', $wizardData);
257 $this->getDatabaseConnection()->store_lastBuiltQuery
= false;
259 // Next update wizard, if available
260 $nextUpdate = $this->getNextUpdateInstance($updateObject);
261 $nextUpdateIdentifier = '';
263 $nextUpdateIdentifier = $nextUpdate->getIdentifier();
265 $this->view
->assign('nextUpdateIdentifier', $nextUpdateIdentifier);
271 * Creates instance of an Update object
273 * @param string $className The class name
274 * @param string $identifier The identifier of Update object - needed to fetch user input
275 * @return AbstractUpdate Newly instantiated Update object
277 protected function getUpdateObjectInstance($className, $identifier)
279 $userInput = $this->postValues
['values'][$identifier];
280 $versionAsInt = VersionNumberUtility
::convertVersionNumberToInteger(TYPO3_version
);
281 return GeneralUtility
::makeInstance($className, $identifier, $versionAsInt, $userInput, $this);
285 * Returns the next Update object
286 * Used to show the link/button to the next Update
288 * @param AbstractUpdate $currentUpdate Current Update object
289 * @return AbstractUpdate|NULL
291 protected function getNextUpdateInstance(AbstractUpdate
$currentUpdate)
293 $isPreviousRecord = true;
294 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) {
295 // Find the current update wizard, and then start validating the next ones
296 if ($currentUpdate->getIdentifier() === $identifier) {
297 $isPreviousRecord = false;
298 // For the updateDatabaseSchema-wizards verify they do not have to be executed again
299 if ($identifier !== 'initialUpdateDatabaseSchema' && $identifier !== 'finalUpdateDatabaseSchema') {
303 if (!$isPreviousRecord) {
304 $nextUpdate = $this->getUpdateObjectInstance($className, $identifier);
305 if ($nextUpdate->shouldRenderWizard()) {
314 * Force creation / update of caching framework tables that are needed by some update wizards
316 * @TODO: See also the other remarks on this topic in the abstract class, this whole area needs improvements
318 * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException
319 * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException
320 * @throws \TYPO3\CMS\Core\Database\Schema\Exception\UnexpectedSignalReturnValueTypeException
321 * @throws \TYPO3\CMS\Core\Database\Schema\Exception\StatementException
322 * @throws \RuntimeException
323 * @throws \Doctrine\DBAL\Schema\SchemaException
324 * @throws \InvalidArgumentException
325 * @throws \Doctrine\DBAL\DBALException
327 protected function silentCacheFrameworkTableSchemaMigration()
329 $sqlReader = GeneralUtility
::makeInstance(SqlReader
::class);
330 $cachingFrameworkDatabaseSchemaService = GeneralUtility
::makeInstance(DatabaseSchemaService
::class);
331 $createTableStatements = $sqlReader->getStatementArray(
332 $cachingFrameworkDatabaseSchemaService->getCachingFrameworkRequiredDatabaseSchema()
335 $schemaMigrationService = GeneralUtility
::makeInstance(SchemaMigrator
::class);
336 $schemaMigrationService->install($createTableStatements);