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