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