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