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