9e2b1883dca61f2798c9c1b6c129bf3d9a86e74e
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Controller / Action / Tool / UpgradeWizard.php
1 <?php
2 namespace TYPO3\CMS\Install\Controller\Action\Tool;
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
17 use TYPO3\CMS\Core\Utility\VersionNumberUtility;
18 use TYPO3\CMS\Install\Controller\Action;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Install\Updates\AbstractUpdate;
21
22 /**
23 * Handle update wizards
24 */
25 class UpgradeWizard extends Action\AbstractAction {
26
27 /**
28 * There are tables and fields missing in the database
29 *
30 * @var bool
31 */
32 protected $needsInitialUpdateDatabaseSchema = FALSE;
33
34 /**
35 * Executes the tool
36 *
37 * @return string Rendered content
38 */
39 protected function executeAction() {
40 // ext_localconf, db and ext_tables must be loaded for the updates
41 $this->loadExtLocalconfDatabaseAndExtTables();
42
43 // To make sure initialUpdateDatabaseSchema is first wizard, it is added here instead of ext_localconf.php
44 $initialUpdateDatabaseSchemaUpdateObject = $this->getUpdateObjectInstance(\TYPO3\CMS\Install\Updates\InitialDatabaseSchemaUpdate::class, 'initialUpdateDatabaseSchema');
45 if ($initialUpdateDatabaseSchemaUpdateObject->shouldRenderWizard()) {
46 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] = array_merge(
47 array('initialUpdateDatabaseSchema' => \TYPO3\CMS\Install\Updates\InitialDatabaseSchemaUpdate::class),
48 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']
49 );
50 $this->needsInitialUpdateDatabaseSchema = TRUE;
51 }
52
53 // To make sure finalUpdateDatabaseSchema is last wizard, it is added here instead of ext_localconf.php
54 $finalUpdateDatabaseSchemaUpdateObject = $this->getUpdateObjectInstance(\TYPO3\CMS\Install\Updates\FinalDatabaseSchemaUpdate::class, 'finalUpdateDatabaseSchema');
55 if ($finalUpdateDatabaseSchemaUpdateObject->shouldRenderWizard()) {
56 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['finalUpdateDatabaseSchema'] = \TYPO3\CMS\Install\Updates\FinalDatabaseSchemaUpdate::class;
57 }
58
59 // Perform silent cache framework table upgrade
60 $this->silentCacheFrameworkTableSchemaMigration();
61
62 $actionMessages = array();
63
64 if (isset($this->postValues['set']['getUserInput'])) {
65 $actionMessages[] = $this->getUserInputForUpdate();
66 $this->view->assign('updateAction', 'getUserInput');
67 } elseif (isset($this->postValues['set']['performUpdate'])) {
68 $actionMessages[] = $this->performUpdate();
69 $this->view->assign('updateAction', 'performUpdate');
70 } else {
71 $actionMessages[] = $this->listUpdates();
72 $this->view->assign('updateAction', 'listUpdates');
73 }
74
75 $this->view->assign('actionMessages', $actionMessages);
76
77 return $this->view->render();
78 }
79
80 /**
81 * List of available updates
82 *
83 * @return \TYPO3\CMS\Install\Status\StatusInterface
84 */
85 protected function listUpdates() {
86 if (empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'])) {
87 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
88 $message = $this->objectManager->get(\TYPO3\CMS\Install\Status\WarningStatus::class);
89 $message->setTitle('No update wizards registered');
90 return $message;
91 }
92
93 $availableUpdates = array();
94 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) {
95 $updateObject = $this->getUpdateObjectInstance($className, $identifier);
96 if ($updateObject->shouldRenderWizard()) {
97 // $explanation is changed by reference in Update objects!
98 $explanation = '';
99 $updateObject->checkForUpdate($explanation);
100 $availableUpdates[$identifier] = array(
101 'identifier' => $identifier,
102 'title' => $updateObject->getTitle(),
103 'explanation' => $explanation,
104 'renderNext' => FALSE,
105 );
106 if ($identifier === 'initialUpdateDatabaseSchema') {
107 $availableUpdates['initialUpdateDatabaseSchema']['renderNext'] = $this->needsInitialUpdateDatabaseSchema;
108 } elseif ($identifier === 'finalUpdateDatabaseSchema') {
109 // Okay to check here because finalUpdateDatabaseSchema is last element in array
110 $availableUpdates['finalUpdateDatabaseSchema']['renderNext'] = count($availableUpdates) === 1;
111 } elseif (!$this->needsInitialUpdateDatabaseSchema && $updateObject->shouldRenderNextButton()) {
112 // There are Updates that only show text and don't want to be executed
113 $availableUpdates[$identifier]['renderNext'] = TRUE;
114 }
115 }
116 }
117
118 $this->view->assign('availableUpdates', $availableUpdates);
119
120 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
121 $message = $this->objectManager->get(\TYPO3\CMS\Install\Status\OkStatus::class);
122 $message->setTitle('Show available update wizards');
123 return $message;
124 }
125
126 /**
127 * Get user input of update wizard
128 *
129 * @return \TYPO3\CMS\Install\Status\StatusInterface
130 */
131 protected function getUserInputForUpdate() {
132 $wizardIdentifier = $this->postValues['values']['identifier'];
133
134 $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardIdentifier];
135 $updateObject = $this->getUpdateObjectInstance($className, $wizardIdentifier);
136 $wizardHtml = '';
137 if (method_exists($updateObject, 'getUserInput')) {
138 $wizardHtml = $updateObject->getUserInput('install[values][' . $wizardIdentifier . ']');
139 }
140
141 $updateData = array(
142 'identifier' => $wizardIdentifier,
143 'title' => $updateObject->getTitle(),
144 'wizardHtml' => $wizardHtml,
145 );
146
147 $this->view->assign('updateData', $updateData);
148
149 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
150 $message = $this->objectManager->get(\TYPO3\CMS\Install\Status\OkStatus::class);
151 $message->setTitle('Show wizard options');
152 return $message;
153 }
154
155 /**
156 * Perform update of a specific wizard
157 *
158 * @throws \TYPO3\CMS\Install\Exception
159 * @return \TYPO3\CMS\Install\Status\StatusInterface
160 */
161 protected function performUpdate() {
162 $this->getDatabaseConnection()->store_lastBuiltQuery = TRUE;
163
164 $wizardIdentifier = $this->postValues['values']['identifier'];
165 $className = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][$wizardIdentifier];
166 $updateObject = $this->getUpdateObjectInstance($className, $wizardIdentifier);
167
168 $wizardData = array(
169 'identifier' => $wizardIdentifier,
170 'title' => $updateObject->getTitle(),
171 );
172
173 // $wizardInputErrorMessage is given as reference to wizard object!
174 $wizardInputErrorMessage = '';
175 if (method_exists($updateObject, 'checkUserInput') && !$updateObject->checkUserInput($wizardInputErrorMessage)) {
176 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
177 $message = $this->objectManager->get(\TYPO3\CMS\Install\Status\ErrorStatus::class);
178 $message->setTitle('Input parameter broken');
179 $message->setMessage($wizardInputErrorMessage ?: 'Something went wrong!');
180 $wizardData['wizardInputBroken'] = TRUE;
181 } else {
182 if (!method_exists($updateObject, 'performUpdate')) {
183 throw new \TYPO3\CMS\Install\Exception(
184 'No performUpdate method in update wizard with identifier ' . $wizardIdentifier,
185 1371035200
186 );
187 }
188
189 // Both variables are used by reference in performUpdate()
190 $customOutput = '';
191 $databaseQueries = array();
192 $performResult = $updateObject->performUpdate($databaseQueries, $customOutput);
193
194 if ($performResult) {
195 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
196 $message = $this->objectManager->get(\TYPO3\CMS\Install\Status\OkStatus::class);
197 $message->setTitle('Update successful');
198 } else {
199 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
200 $message = $this->objectManager->get(\TYPO3\CMS\Install\Status\ErrorStatus::class);
201 $message->setTitle('Update failed!');
202 if ($customOutput) {
203 $message->setMessage($customOutput);
204 }
205 }
206
207 if ($this->postValues['values']['showDatabaseQueries'] == 1) {
208 $wizardData['queries'] = $databaseQueries;
209 }
210 }
211
212 $this->view->assign('wizardData', $wizardData);
213
214 $this->getDatabaseConnection()->store_lastBuiltQuery = FALSE;
215
216 // Next update wizard, if available
217 $nextUpdate = $this->getNextUpdateInstance($updateObject);
218 $nextUpdateIdentifier = '';
219 if ($nextUpdate) {
220 $nextUpdateIdentifier = $nextUpdate->getIdentifier();
221 }
222 $this->view->assign('nextUpdateIdentifier', $nextUpdateIdentifier);
223
224 return $message;
225 }
226
227 /**
228 * Creates instance of an Update object
229 *
230 * @param string $className The class name
231 * @param string $identifier The identifier of Update object - needed to fetch user input
232 * @return AbstractUpdate Newly instantiated Update object
233 */
234 protected function getUpdateObjectInstance($className, $identifier) {
235 $userInput = $this->postValues['values'][$identifier];
236 $versionAsInt = VersionNumberUtility::convertVersionNumberToInteger(TYPO3_version);
237 return GeneralUtility::makeInstance($className, $identifier, $versionAsInt, $userInput, $this);
238 }
239
240 /**
241 * Returns the next Update object
242 * Used to show the link/button to the next Update
243 *
244 * @param AbstractUpdate $currentUpdate Current Update object
245 * @return AbstractUpdate|NULL
246 */
247 protected function getNextUpdateInstance(AbstractUpdate $currentUpdate) {
248 $isPreviousRecord = TRUE;
249 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $identifier => $className) {
250 // Find the current update wizard, and then start validating the next ones
251 if ($currentUpdate->getIdentifier() === $identifier) {
252 $isPreviousRecord = FALSE;
253 // For the updateDatabaseSchema-wizards verify they do not have to be executed again
254 if ($identifier !== 'initialUpdateDatabaseSchema' && $identifier !== 'finalUpdateDatabaseSchema') {
255 continue;
256 }
257 }
258 if (!$isPreviousRecord) {
259 $nextUpdate = $this->getUpdateObjectInstance($className, $identifier);
260 if ($nextUpdate->shouldRenderWizard()) {
261 return $nextUpdate;
262 }
263 }
264 }
265 return NULL;
266 }
267
268 /**
269 * Force creation / update of caching framework tables that are needed by some update wizards
270 *
271 * @TODO: See also the other remarks on this topic in the abstract class, this whole area needs improvements
272 * @return void
273 */
274 protected function silentCacheFrameworkTableSchemaMigration() {
275 /** @var $sqlHandler \TYPO3\CMS\Install\Service\SqlSchemaMigrationService */
276 $sqlHandler = $this->objectManager->get(\TYPO3\CMS\Install\Service\SqlSchemaMigrationService::class);
277
278 /** @var \TYPO3\CMS\Install\Service\CachingFrameworkDatabaseSchemaService $cachingFrameworkDatabaseSchemaService */
279 $cachingFrameworkDatabaseSchemaService = $this->objectManager->get(\TYPO3\CMS\Install\Service\CachingFrameworkDatabaseSchemaService::class);
280 $expectedSchemaString = $cachingFrameworkDatabaseSchemaService->getCachingFrameworkRequiredDatabaseSchema();
281 $cleanedExpectedSchemaString = implode(LF, $sqlHandler->getStatementArray($expectedSchemaString, TRUE, '^CREATE TABLE '));
282 $neededTableDefinition = $sqlHandler->getFieldDefinitions_fileContent($cleanedExpectedSchemaString);
283 $currentTableDefinition = $sqlHandler->getFieldDefinitions_database();
284 $updateTableDefinition = $sqlHandler->getDatabaseExtra($neededTableDefinition, $currentTableDefinition);
285 $updateStatements = $sqlHandler->getUpdateSuggestions($updateTableDefinition);
286 if (isset($updateStatements['create_table']) && count($updateStatements['create_table']) > 0) {
287 $sqlHandler->performUpdateQueries($updateStatements['create_table'], $updateStatements['create_table']);
288 }
289 if (isset($updateStatements['add']) && count($updateStatements['add']) > 0) {
290 $sqlHandler->performUpdateQueries($updateStatements['add'], $updateStatements['add']);
291 }
292 if (isset($updateStatements['change']) && count($updateStatements['change']) > 0) {
293 $sqlHandler->performUpdateQueries($updateStatements['change'], $updateStatements['change']);
294 }
295 }
296
297 /**
298 * Overwrite getDatabase method of abstract!
299 *
300 * Returns $GLOBALS['TYPO3_DB'] directly, since this global is instantiated properly in update wizards
301 *
302 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
303 */
304 protected function getDatabaseConnection() {
305 return $GLOBALS['TYPO3_DB'];
306 }
307
308 }