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