08e364589f9731e13ddd1acf598b6a2d0e52fd11
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Controller / Action / Tool / ImportantActions.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\Core\Bootstrap;
18 use TYPO3\CMS\Core\Crypto\Random;
19 use TYPO3\CMS\Core\Service\OpcodeCacheService;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Install\Controller\Action;
22
23 /**
24 * Handle important actions
25 */
26 class ImportantActions extends Action\AbstractAction
27 {
28 /**
29 * Executes the tool
30 *
31 * @return string Rendered content
32 */
33 protected function executeAction()
34 {
35 if (isset($this->postValues['set']['changeEncryptionKey'])) {
36 $this->setNewEncryptionKeyAndLogOut();
37 }
38
39 $actionMessages = array();
40 if (isset($this->postValues['set']['changeInstallToolPassword'])) {
41 $actionMessages[] = $this->changeInstallToolPassword();
42 }
43 if (isset($this->postValues['set']['changeSiteName'])) {
44 $actionMessages[] = $this->changeSiteName();
45 }
46 if (isset($this->postValues['set']['createAdministrator'])) {
47 $actionMessages[] = $this->createAdministrator();
48 }
49 if (isset($this->postValues['set']['clearAllCache'])) {
50 $actionMessages[] = $this->clearAllCache();
51 }
52 if (isset($this->postValues['set']['clearOpcodeCache'])) {
53 $actionMessages[] = $this->clearOpcodeCache();
54 }
55
56 // Database analyzer handling
57 if (isset($this->postValues['set']['databaseAnalyzerExecute'])
58 || isset($this->postValues['set']['databaseAnalyzerAnalyze'])
59 ) {
60 $this->loadExtLocalconfDatabaseAndExtTables();
61 }
62 if (isset($this->postValues['set']['databaseAnalyzerExecute'])) {
63 $actionMessages = array_merge($actionMessages, $this->databaseAnalyzerExecute());
64 }
65 if (isset($this->postValues['set']['databaseAnalyzerAnalyze'])) {
66 $actionMessages[] = $this->databaseAnalyzerAnalyze();
67 }
68
69 $this->view->assign('actionMessages', $actionMessages);
70
71 $operatingSystem = TYPO3_OS === 'WIN' ? 'Windows' : 'Unix';
72
73 $opcodeCacheService = GeneralUtility::makeInstance(OpcodeCacheService::class);
74
75 /** @var \TYPO3\CMS\Install\Service\CoreUpdateService $coreUpdateService */
76 $coreUpdateService = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\CoreUpdateService::class);
77 $this->view
78 ->assign('enableCoreUpdate', $coreUpdateService->isCoreUpdateEnabled())
79 ->assign('composerMode', Bootstrap::usesComposerClassLoading())
80 ->assign('operatingSystem', $operatingSystem)
81 ->assign('cgiDetected', GeneralUtility::isRunningOnCgiServerApi())
82 ->assign('databaseName', $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname'])
83 ->assign('databaseUsername', $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'])
84 ->assign('databaseHost', $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'])
85 ->assign('databasePort', $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['port'])
86 ->assign('databaseSocket', $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['unix_socket'])
87 ->assign('databaseNumberOfTables', count($this->getDatabaseConnection()->admin_get_tables()))
88 ->assign('extensionCompatibilityTesterProtocolFile', GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . 'typo3temp/assets/ExtensionCompatibilityTester.txt')
89 ->assign('extensionCompatibilityTesterErrorProtocolFile', GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . 'typo3temp/assets/ExtensionCompatibilityTesterErrors.json')
90 ->assign('extensionCompatibilityTesterMessages', $this->getExtensionCompatibilityTesterMessages())
91 ->assign('listOfOpcodeCaches', $opcodeCacheService->getAllActive());
92
93 return $this->view->render();
94 }
95
96 /**
97 * Set new password if requested
98 *
99 * @return \TYPO3\CMS\Install\Status\StatusInterface
100 */
101 protected function changeInstallToolPassword()
102 {
103 $values = $this->postValues['values'];
104 if ($values['newInstallToolPassword'] !== $values['newInstallToolPasswordCheck']) {
105 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
106 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
107 $message->setTitle('Install tool password not changed');
108 $message->setMessage('Given passwords do not match.');
109 } elseif (strlen($values['newInstallToolPassword']) < 8) {
110 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
111 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
112 $message->setTitle('Install tool password not changed');
113 $message->setMessage('Given password must be at least eight characters long.');
114 } else {
115 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
116 $configurationManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
117 $configurationManager->setLocalConfigurationValueByPath(
118 'BE/installToolPassword',
119 $this->getHashedPassword($values['newInstallToolPassword'])
120 );
121 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
122 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\OkStatus::class);
123 $message->setTitle('Install tool password changed');
124 }
125 return $message;
126 }
127
128 /**
129 * Set new site name
130 *
131 * @return \TYPO3\CMS\Install\Status\StatusInterface
132 */
133 protected function changeSiteName()
134 {
135 $values = $this->postValues['values'];
136 if (isset($values['newSiteName']) && $values['newSiteName'] !== '') {
137 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
138 $configurationManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
139 $configurationManager->setLocalConfigurationValueByPath('SYS/sitename', $values['newSiteName']);
140 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
141 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\OkStatus::class);
142 $message->setTitle('Site name changed');
143 $this->view->assign('siteName', $values['newSiteName']);
144 } else {
145 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
146 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
147 $message->setTitle('Site name not changed');
148 $message->setMessage('Site name must be at least one character long.');
149 }
150 return $message;
151 }
152
153 /**
154 * Clear all caches
155 *
156 * @return \TYPO3\CMS\Install\Status\StatusInterface
157 */
158 protected function clearAllCache()
159 {
160 /** @var \TYPO3\CMS\Install\Service\ClearCacheService $clearCacheService */
161 $clearCacheService = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\ClearCacheService::class);
162 $clearCacheService->clearAll();
163 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\OkStatus::class);
164 $message->setTitle('Successfully cleared all caches');
165 return $message;
166 }
167
168 /**
169 * Clear PHP opcode cache
170 *
171 * @return \TYPO3\CMS\Install\Status\StatusInterface
172 */
173 protected function clearOpcodeCache()
174 {
175 GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
176 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\OkStatus::class);
177 $message->setTitle('Successfully cleared all available opcode caches');
178 return $message;
179 }
180
181 /**
182 * Set new encryption key
183 *
184 * @return void
185 */
186 protected function setNewEncryptionKeyAndLogOut()
187 {
188 $newKey = GeneralUtility::makeInstance(Random::class)->generateRandomHexString(96);
189 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
190 $configurationManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
191 $configurationManager->setLocalConfigurationValueByPath('SYS/encryptionKey', $newKey);
192 /** @var $formProtection \TYPO3\CMS\Core\FormProtection\InstallToolFormProtection */
193 $formProtection = \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get(
194 \TYPO3\CMS\Core\FormProtection\InstallToolFormProtection::class
195 );
196 $formProtection->clean();
197 /** @var \TYPO3\CMS\Install\Service\SessionService $session */
198 $session = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\SessionService::class);
199 $session->destroySession();
200 \TYPO3\CMS\Core\Utility\HttpUtility::redirect('Install.php?install[context]=' . $this->getContext());
201 }
202
203 /**
204 * Create administrator user
205 *
206 * @return \TYPO3\CMS\Install\Status\StatusInterface
207 */
208 protected function createAdministrator()
209 {
210 $values = $this->postValues['values'];
211 $username = preg_replace('/\\s/i', '', $values['newUserUsername']);
212 $password = $values['newUserPassword'];
213 $passwordCheck = $values['newUserPasswordCheck'];
214
215 if (strlen($username) < 1) {
216 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
217 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
218 $message->setTitle('Administrator user not created');
219 $message->setMessage('No valid username given.');
220 } elseif ($password !== $passwordCheck) {
221 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
222 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
223 $message->setTitle('Administrator user not created');
224 $message->setMessage('Passwords do not match.');
225 } elseif (strlen($password) < 8) {
226 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
227 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
228 $message->setTitle('Administrator user not created');
229 $message->setMessage('Password must be at least eight characters long.');
230 } else {
231 $database = $this->getDatabaseConnection();
232 $userExists = $database->exec_SELECTcountRows(
233 'uid',
234 'be_users',
235 'username=' . $database->fullQuoteStr($username, 'be_users')
236 );
237 if ($userExists) {
238 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
239 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
240 $message->setTitle('Administrator user not created');
241 $message->setMessage('A user with username "' . $username . '" exists already.');
242 } else {
243 $hashedPassword = $this->getHashedPassword($password);
244 $adminUserFields = array(
245 'username' => $username,
246 'password' => $hashedPassword,
247 'admin' => 1,
248 'tstamp' => $GLOBALS['EXEC_TIME'],
249 'crdate' => $GLOBALS['EXEC_TIME']
250 );
251 $database->exec_INSERTquery('be_users', $adminUserFields);
252 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
253 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\OkStatus::class);
254 $message->setTitle('Administrator created with username "' . $username . '".');
255 }
256 }
257
258 return $message;
259 }
260
261 /**
262 * Execute database migration
263 *
264 * @return array<\TYPO3\CMS\Install\Status\StatusInterface>
265 */
266 protected function databaseAnalyzerExecute()
267 {
268 $messages = array();
269
270 // Early return in case no update was selected
271 if (empty($this->postValues['values'])) {
272 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
273 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\WarningStatus::class);
274 $message->setTitle('No database changes selected');
275 $messages[] = $message;
276 return $messages;
277 }
278
279 /** @var \TYPO3\CMS\Install\Service\SqlSchemaMigrationService $schemaMigrationService */
280 $schemaMigrationService = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\SqlSchemaMigrationService::class);
281 /** @var \TYPO3\CMS\Install\Service\SqlExpectedSchemaService $expectedSchemaService */
282 $expectedSchemaService = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\SqlExpectedSchemaService::class);
283 $expectedSchema = $expectedSchemaService->getExpectedDatabaseSchema();
284 $currentSchema = $schemaMigrationService->getFieldDefinitions_database();
285
286 $statementHashesToPerform = $this->postValues['values'];
287
288 $results = array();
289
290 // Difference from expected to current
291 $addCreateChange = $schemaMigrationService->getDatabaseExtra($expectedSchema, $currentSchema);
292 $addCreateChange = $schemaMigrationService->getUpdateSuggestions($addCreateChange);
293 $results[] = $schemaMigrationService->performUpdateQueries($addCreateChange['add'], $statementHashesToPerform);
294 $results[] = $schemaMigrationService->performUpdateQueries($addCreateChange['change'], $statementHashesToPerform);
295 $results[] = $schemaMigrationService->performUpdateQueries($addCreateChange['create_table'], $statementHashesToPerform);
296
297 // Difference from current to expected
298 $dropRename = $schemaMigrationService->getDatabaseExtra($currentSchema, $expectedSchema);
299 $dropRename = $schemaMigrationService->getUpdateSuggestions($dropRename, 'remove');
300 $results[] = $schemaMigrationService->performUpdateQueries($dropRename['change'], $statementHashesToPerform);
301 $results[] = $schemaMigrationService->performUpdateQueries($dropRename['drop'], $statementHashesToPerform);
302 $results[] = $schemaMigrationService->performUpdateQueries($dropRename['change_table'], $statementHashesToPerform);
303 $results[] = $schemaMigrationService->performUpdateQueries($dropRename['drop_table'], $statementHashesToPerform);
304
305 // Create error flash messages if any
306 foreach ($results as $resultSet) {
307 if (is_array($resultSet)) {
308 foreach ($resultSet as $errorMessage) {
309 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
310 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\ErrorStatus::class);
311 $message->setTitle('Database update failed');
312 $message->setMessage('Error: ' . $errorMessage);
313 $messages[] = $message;
314 }
315 }
316 }
317
318 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
319 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\OkStatus::class);
320 $message->setTitle('Executed database updates');
321 $messages[] = $message;
322
323 return $messages;
324 }
325
326 /**
327 * "Compare" action of analyzer
328 *
329 * @TODO: The SchemaMigration API is a mess and should be refactored
330 * @TODO: Refactoring this should aim to make EM and dbal independent from ext:install by moving SchemaMigration to ext:core
331 * @return \TYPO3\CMS\Install\Status\StatusInterface
332 */
333 protected function databaseAnalyzerAnalyze()
334 {
335 /** @var \TYPO3\CMS\Install\Service\SqlSchemaMigrationService $schemaMigrationService */
336 $schemaMigrationService = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\SqlSchemaMigrationService::class);
337 /** @var \TYPO3\CMS\Install\Service\SqlExpectedSchemaService $expectedSchemaService */
338 $expectedSchemaService = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\SqlExpectedSchemaService::class);
339 $expectedSchema = $expectedSchemaService->getExpectedDatabaseSchema();
340
341 $currentSchema = $schemaMigrationService->getFieldDefinitions_database();
342
343 $databaseAnalyzerSuggestion = array();
344
345 // Difference from expected to current
346 $addCreateChange = $schemaMigrationService->getDatabaseExtra($expectedSchema, $currentSchema);
347 $addCreateChange = $schemaMigrationService->getUpdateSuggestions($addCreateChange);
348 if (isset($addCreateChange['create_table'])) {
349 $databaseAnalyzerSuggestion['addTable'] = array();
350 foreach ($addCreateChange['create_table'] as $hash => $statement) {
351 $databaseAnalyzerSuggestion['addTable'][$hash] = array(
352 'hash' => $hash,
353 'statement' => $statement,
354 );
355 }
356 }
357 if (isset($addCreateChange['add'])) {
358 $databaseAnalyzerSuggestion['addField'] = array();
359 foreach ($addCreateChange['add'] as $hash => $statement) {
360 $databaseAnalyzerSuggestion['addField'][$hash] = array(
361 'hash' => $hash,
362 'statement' => $statement,
363 );
364 }
365 }
366 if (isset($addCreateChange['change'])) {
367 $databaseAnalyzerSuggestion['change'] = array();
368 foreach ($addCreateChange['change'] as $hash => $statement) {
369 $databaseAnalyzerSuggestion['change'][$hash] = array(
370 'hash' => $hash,
371 'statement' => $statement,
372 );
373 if (isset($addCreateChange['change_currentValue'][$hash])) {
374 $databaseAnalyzerSuggestion['change'][$hash]['current'] = $addCreateChange['change_currentValue'][$hash];
375 }
376 }
377 }
378
379 // Difference from current to expected
380 $dropRename = $schemaMigrationService->getDatabaseExtra($currentSchema, $expectedSchema);
381 $dropRename = $schemaMigrationService->getUpdateSuggestions($dropRename, 'remove');
382 if (isset($dropRename['change_table'])) {
383 $databaseAnalyzerSuggestion['renameTableToUnused'] = array();
384 foreach ($dropRename['change_table'] as $hash => $statement) {
385 $databaseAnalyzerSuggestion['renameTableToUnused'][$hash] = array(
386 'hash' => $hash,
387 'statement' => $statement,
388 );
389 if (!empty($dropRename['tables_count'][$hash])) {
390 $databaseAnalyzerSuggestion['renameTableToUnused'][$hash]['count'] = $dropRename['tables_count'][$hash];
391 }
392 }
393 }
394 if (isset($dropRename['change'])) {
395 $databaseAnalyzerSuggestion['renameTableFieldToUnused'] = array();
396 foreach ($dropRename['change'] as $hash => $statement) {
397 $databaseAnalyzerSuggestion['renameTableFieldToUnused'][$hash] = array(
398 'hash' => $hash,
399 'statement' => $statement,
400 );
401 }
402 }
403 if (isset($dropRename['drop'])) {
404 $databaseAnalyzerSuggestion['deleteField'] = array();
405 foreach ($dropRename['drop'] as $hash => $statement) {
406 $databaseAnalyzerSuggestion['deleteField'][$hash] = array(
407 'hash' => $hash,
408 'statement' => $statement,
409 );
410 }
411 }
412 if (isset($dropRename['drop_table'])) {
413 $databaseAnalyzerSuggestion['deleteTable'] = array();
414 foreach ($dropRename['drop_table'] as $hash => $statement) {
415 $databaseAnalyzerSuggestion['deleteTable'][$hash] = array(
416 'hash' => $hash,
417 'statement' => $statement,
418 );
419 if (!empty($dropRename['tables_count'][$hash])) {
420 $databaseAnalyzerSuggestion['deleteTable'][$hash]['count'] = $dropRename['tables_count'][$hash];
421 }
422 }
423 }
424
425 $this->view->assign('databaseAnalyzerSuggestion', $databaseAnalyzerSuggestion);
426
427 /** @var $message \TYPO3\CMS\Install\Status\StatusInterface */
428 $message = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Status\OkStatus::class);
429 $message->setTitle('Analyzed current database');
430 return $message;
431 }
432 }