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\Utility\GeneralUtility
;
18 use TYPO3\CMS\Core\Utility\VersionNumberUtility
;
19 use TYPO3\CMS\Install\Controller\Action
;
20 use TYPO3\CMS\Install\Updates\AbstractUpdate
;
23 * Handle update wizards
25 class UpgradeWizard
extends Action\AbstractAction
28 * There are tables and fields missing in the database
32 protected $needsInitialUpdateDatabaseSchema = false;
37 * @return string Rendered content
39 protected function executeAction()
41 // ext_localconf, db and ext_tables must be loaded for the updates
42 $this->loadExtLocalconfDatabaseAndExtTables();
44 if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'])) {
45 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] = [];
48 // To make sure DatabaseCharsetUpdate and initialUpdateDatabaseSchema are first wizards, they are added here instead of ext_localconf.php
49 $databaseCharsetUpdateObject = $this->getUpdateObjectInstance(\TYPO3\CMS\Install\Updates\DatabaseCharsetUpdate
::class, 'databaseCharsetUpdate');
50 if ($databaseCharsetUpdateObject->shouldRenderWizard()) {
51 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] = array_merge(
52 array('databaseCharsetUpdate' => \TYPO3\CMS\Install\Updates\DatabaseCharsetUpdate
::class),
53 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']
56 $initialUpdateDatabaseSchemaUpdateObject = $this->getUpdateObjectInstance(\TYPO3\CMS\Install\Updates\InitialDatabaseSchemaUpdate
::class, 'initialUpdateDatabaseSchema');
57 if ($initialUpdateDatabaseSchemaUpdateObject->shouldRenderWizard()) {
58 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] = array_merge(
59 array('initialUpdateDatabaseSchema' => \TYPO3\CMS\Install\Updates\InitialDatabaseSchemaUpdate
::class),
60 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']
62 $this->needsInitialUpdateDatabaseSchema
= true;
65 // To make sure finalUpdateDatabaseSchema is last wizard, it is added here instead of ext_localconf.php
66 $finalUpdateDatabaseSchemaUpdateObject = $this->getUpdateObjectInstance(\TYPO3\CMS\Install\Updates\FinalDatabaseSchemaUpdate
::class, 'finalUpdateDatabaseSchema');
67 if ($finalUpdateDatabaseSchemaUpdateObject->shouldRenderWizard()) {
68 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['finalUpdateDatabaseSchema'] = \TYPO3\CMS\Install\Updates\FinalDatabaseSchemaUpdate
::class;
71 // Perform silent cache framework table upgrade
72 $this->silentCacheFrameworkTableSchemaMigration();
74 $actionMessages = array();
76 if (isset($this->postValues
['set']['getUserInput'])) {
77 $actionMessages[] = $this->getUserInputForUpdate();
78 $this->view
->assign('updateAction', 'getUserInput');
79 } elseif (isset($this->postValues
['set']['performUpdate'])) {
80 $actionMessages[] = $this->performUpdate();
81 $this->view
->assign('updateAction', 'performUpdate');
84 $this->view
->assign('updateAction', 'listUpdates');
87 $this->view
->assign('actionMessages', $actionMessages);
89 return $this->view
->render();
93 * List of available updates
97 protected function listUpdates()
99 if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'])) {
100 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
101 $message = GeneralUtility
::makeInstance(\TYPO3\CMS\Install\Status\WarningStatus
::class);
102 $message->setTitle('No update wizards registered');
106 $availableUpdates = array();
107 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) {
108 $updateObject = $this->getUpdateObjectInstance($className, $identifier);
109 if ($updateObject->shouldRenderWizard()) {
110 // $explanation is changed by reference in Update objects!
112 $updateObject->checkForUpdate($explanation);
113 $availableUpdates[$identifier] = array(
114 'identifier' => $identifier,
115 'title' => $updateObject->getTitle(),
116 'explanation' => $explanation,
117 'renderNext' => false,
119 if ($identifier === 'initialUpdateDatabaseSchema') {
120 $availableUpdates['initialUpdateDatabaseSchema']['renderNext'] = $this->needsInitialUpdateDatabaseSchema
;
121 // initialUpdateDatabaseSchema is always the first update
122 // we stop immediately here as the remaining updates may
123 // require the new fields to be present in order to avoid SQL errors
125 } elseif ($identifier === 'finalUpdateDatabaseSchema') {
126 // Okay to check here because finalUpdateDatabaseSchema is last element in array
127 $availableUpdates['finalUpdateDatabaseSchema']['renderNext'] = count($availableUpdates) === 1;
128 } elseif (!$this->needsInitialUpdateDatabaseSchema
&& $updateObject->shouldRenderNextButton()) {
129 // There are Updates that only show text and don't want to be executed
130 $availableUpdates[$identifier]['renderNext'] = true;
135 $this->view
->assign('availableUpdates', $availableUpdates);
137 // compute done wizards for statistics
139 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) {
140 /** @var AbstractUpdate $updateObject */
141 $updateObject = $this->getUpdateObjectInstance($className, $identifier);
142 if ($updateObject->shouldRenderWizard() !== true) {
143 $wizardsDone[] = $updateObject;
146 $this->view
->assign('wizardsDone', $wizardsDone);
148 $wizardsTotal = (count($wizardsDone) +
count($availableUpdates));
149 $this->view
->assign('wizardsTotal', $wizardsTotal);
151 $this->view
->assign('wizardsPercentageDone', floor(($wizardsTotal - count($availableUpdates)) * 100 / $wizardsTotal));
155 * Get user input of update wizard
157 * @return \TYPO3\CMS\Install\Status\StatusInterface
159 protected function getUserInputForUpdate()
161 $wizardIdentifier = $this->postValues
['values']['identifier'];
163 $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardIdentifier];
164 $updateObject = $this->getUpdateObjectInstance($className, $wizardIdentifier);
166 if (method_exists($updateObject, 'getUserInput')) {
167 $wizardHtml = $updateObject->getUserInput('install[values][' . $wizardIdentifier . ']');
171 'identifier' => $wizardIdentifier,
172 'title' => $updateObject->getTitle(),
173 'wizardHtml' => $wizardHtml,
176 $this->view
->assign('updateData', $updateData);
178 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
179 $message = GeneralUtility
::makeInstance(\TYPO3\CMS\Install\Status\OkStatus
::class);
180 $message->setTitle('Show wizard options');
185 * Perform update of a specific wizard
187 * @throws \TYPO3\CMS\Install\Exception
188 * @return \TYPO3\CMS\Install\Status\StatusInterface
190 protected function performUpdate()
192 $this->getDatabaseConnection()->store_lastBuiltQuery
= true;
194 $wizardIdentifier = $this->postValues
['values']['identifier'];
195 $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardIdentifier];
196 $updateObject = $this->getUpdateObjectInstance($className, $wizardIdentifier);
199 'identifier' => $wizardIdentifier,
200 'title' => $updateObject->getTitle(),
203 // $wizardInputErrorMessage is given as reference to wizard object!
204 $wizardInputErrorMessage = '';
205 if (method_exists($updateObject, 'checkUserInput') && !$updateObject->checkUserInput($wizardInputErrorMessage)) {
206 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
207 $message = GeneralUtility
::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus
::class);
208 $message->setTitle('Input parameter broken');
209 $message->setMessage($wizardInputErrorMessage ?
: 'Something went wrong!');
210 $wizardData['wizardInputBroken'] = true;
212 if (!method_exists($updateObject, 'performUpdate')) {
213 throw new \TYPO3\CMS\Install\
Exception(
214 'No performUpdate method in update wizard with identifier ' . $wizardIdentifier,
219 // Both variables are used by reference in performUpdate()
221 $databaseQueries = array();
222 $performResult = $updateObject->performUpdate($databaseQueries, $customOutput);
224 if ($performResult) {
225 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
226 $message = GeneralUtility
::makeInstance(\TYPO3\CMS\Install\Status\OkStatus
::class);
227 $message->setTitle('Update successful');
229 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
230 $message = GeneralUtility
::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus
::class);
231 $message->setTitle('Update failed!');
233 $message->setMessage($customOutput);
237 if ($this->postValues
['values']['showDatabaseQueries'] == 1) {
238 $wizardData['queries'] = $databaseQueries;
242 $this->view
->assign('wizardData', $wizardData);
244 $this->getDatabaseConnection()->store_lastBuiltQuery
= false;
246 // Next update wizard, if available
247 $nextUpdate = $this->getNextUpdateInstance($updateObject);
248 $nextUpdateIdentifier = '';
250 $nextUpdateIdentifier = $nextUpdate->getIdentifier();
252 $this->view
->assign('nextUpdateIdentifier', $nextUpdateIdentifier);
258 * Creates instance of an Update object
260 * @param string $className The class name
261 * @param string $identifier The identifier of Update object - needed to fetch user input
262 * @return AbstractUpdate Newly instantiated Update object
264 protected function getUpdateObjectInstance($className, $identifier)
266 $userInput = $this->postValues
['values'][$identifier];
267 $versionAsInt = VersionNumberUtility
::convertVersionNumberToInteger(TYPO3_version
);
268 return GeneralUtility
::makeInstance($className, $identifier, $versionAsInt, $userInput, $this);
272 * Returns the next Update object
273 * Used to show the link/button to the next Update
275 * @param AbstractUpdate $currentUpdate Current Update object
276 * @return AbstractUpdate|NULL
278 protected function getNextUpdateInstance(AbstractUpdate
$currentUpdate)
280 $isPreviousRecord = true;
281 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) {
282 // Find the current update wizard, and then start validating the next ones
283 if ($currentUpdate->getIdentifier() === $identifier) {
284 $isPreviousRecord = false;
285 // For the updateDatabaseSchema-wizards verify they do not have to be executed again
286 if ($identifier !== 'initialUpdateDatabaseSchema' && $identifier !== 'finalUpdateDatabaseSchema') {
290 if (!$isPreviousRecord) {
291 $nextUpdate = $this->getUpdateObjectInstance($className, $identifier);
292 if ($nextUpdate->shouldRenderWizard()) {
301 * Force creation / update of caching framework tables that are needed by some update wizards
303 * @TODO: See also the other remarks on this topic in the abstract class, this whole area needs improvements
306 protected function silentCacheFrameworkTableSchemaMigration()
308 /** @var $sqlHandler \TYPO3\CMS\Install\Service\SqlSchemaMigrationService */
309 $sqlHandler = GeneralUtility
::makeInstance(\TYPO3\CMS\Install\Service\SqlSchemaMigrationService
::class);
311 /** @var \TYPO3\CMS\Core\Cache\DatabaseSchemaService $cachingFrameworkDatabaseSchemaService */
312 $cachingFrameworkDatabaseSchemaService = GeneralUtility
::makeInstance(\TYPO3\CMS\Core\Cache\DatabaseSchemaService
::class);
313 $expectedSchemaString = $cachingFrameworkDatabaseSchemaService->getCachingFrameworkRequiredDatabaseSchema();
314 $cleanedExpectedSchemaString = implode(LF
, $sqlHandler->getStatementArray($expectedSchemaString, true, '^CREATE TABLE '));
315 $neededTableDefinition = $sqlHandler->getFieldDefinitions_fileContent($cleanedExpectedSchemaString);
316 $currentTableDefinition = $sqlHandler->getFieldDefinitions_database();
317 $updateTableDefinition = $sqlHandler->getDatabaseExtra($neededTableDefinition, $currentTableDefinition);
318 $updateStatements = $sqlHandler->getUpdateSuggestions($updateTableDefinition);
319 if (isset($updateStatements['create_table']) && !empty($updateStatements['create_table'])) {
320 $sqlHandler->performUpdateQueries($updateStatements['create_table'], $updateStatements['create_table']);
322 if (isset($updateStatements['add']) && !empty($updateStatements['add'])) {
323 $sqlHandler->performUpdateQueries($updateStatements['add'], $updateStatements['add']);
325 if (isset($updateStatements['change']) && !empty($updateStatements['change'])) {
326 $sqlHandler->performUpdateQueries($updateStatements['change'], $updateStatements['change']);
331 * Overwrite getDatabase method of abstract!
333 * Returns $GLOBALS['TYPO3_DB'] directly, since this global is instantiated properly in update wizards
335 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
337 protected function getDatabaseConnection()
339 return $GLOBALS['TYPO3_DB'];