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