Passed
Branch master (6c65a4)
by Christian
27:15 queued 11:09
created

MaintenanceController::cacheClearAllAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
namespace TYPO3\CMS\Install\Controller;
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
21
use TYPO3\CMS\Core\Core\Bootstrap;
22
use TYPO3\CMS\Core\Core\ClassLoadingInformation;
23
use TYPO3\CMS\Core\Database\ConnectionPool;
24
use TYPO3\CMS\Core\Database\Schema\Exception\StatementException;
25
use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
26
use TYPO3\CMS\Core\Database\Schema\SqlReader;
27
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
28
use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
29
use TYPO3\CMS\Core\Http\JsonResponse;
30
use TYPO3\CMS\Core\Messaging\FlashMessage;
31
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
32
use TYPO3\CMS\Core\Service\OpcodeCacheService;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
use TYPO3\CMS\Install\Service\ClearCacheService;
35
use TYPO3\CMS\Install\Service\ClearTableService;
36
use TYPO3\CMS\Install\Service\Typo3tempFileService;
37
use TYPO3\CMS\Saltedpasswords\Salt\SaltFactory;
38
39
/**
40
 * Maintenance controller
41
 */
42
class MaintenanceController extends AbstractController
43
{
44
    /**
45
     * Main "show the cards" view
46
     *
47
     * @param ServerRequestInterface $request
48
     * @return ResponseInterface
49
     */
50
    public function cardsAction(ServerRequestInterface $request): ResponseInterface
51
    {
52
        $view = $this->initializeStandaloneView($request, 'Maintenance/Cards.html');
53
        $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
54
        $view->assignMultiple([
55
            'clearAllCacheOpcodeCaches' => (new OpcodeCacheService())->getAllActive(),
56
            'clearTablesClearToken' => $formProtection->generateToken('installTool', 'clearTablesClear'),
57
            'clearTypo3tempFilesToken' => $formProtection->generateToken('installTool', 'clearTypo3tempFiles'),
58
            'createAdminToken' => $formProtection->generateToken('installTool', 'createAdmin'),
59
            'databaseAnalyzerExecuteToken' => $formProtection->generateToken('installTool', 'databaseAnalyzerExecute'),
60
        ]);
61
        return new JsonResponse([
62
            'success' => true,
63
            'html' => $view->render(),
64
        ]);
65
    }
66
67
    /**
68
     * Clear cache framework and opcode caches
69
     *
70
     * @return ResponseInterface
71
     */
72
    public function cacheClearAllAction(): ResponseInterface
73
    {
74
        GeneralUtility::makeInstance(ClearCacheService::class)->clearAll();
75
        GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
76
        $messageQueue = (new FlashMessageQueue('install'))->enqueue(
77
            new FlashMessage('Successfully cleared all caches and all available opcode caches.')
78
        );
79
        return new JsonResponse([
80
            'success' => true,
81
            'status' => $messageQueue,
82
        ]);
83
    }
84
85
    /**
86
     * Clear typo3temp files statistics action
87
     *
88
     * @return ResponseInterface
89
     */
90
    public function clearTypo3tempFilesStatsAction(): ResponseInterface
91
    {
92
        return new JsonResponse(
93
            [
94
                'success' => true,
95
                'stats' => (new Typo3tempFileService())->getDirectoryStatistics(),
96
            ]
97
        );
98
    }
99
100
    /**
101
     * Clear Processed Files
102
     *
103
     * @param ServerRequestInterface $request
104
     * @return ResponseInterface
105
     */
106
    public function clearTypo3tempFilesAction(ServerRequestInterface $request): ResponseInterface
107
    {
108
        $messageQueue = new FlashMessageQueue('install');
109
        $typo3tempFileService = new Typo3tempFileService();
110
        $folder = $request->getParsedBody()['install']['folder'];
111
        if ($folder === '_processed_') {
112
            $failedDeletions = $typo3tempFileService->clearProcessedFiles();
113
            if ($failedDeletions) {
114
                $messageQueue->enqueue(new FlashMessage(
115
                    'Failed to delete ' . $failedDeletions . ' processed files. See TYPO3 log (by default typo3temp/var/logs/typo3_*.log)',
116
                    '',
117
                    FlashMessage::ERROR
118
                ));
119
            } else {
120
                $messageQueue->enqueue(new FlashMessage('Cleared processed files'));
121
            }
122
        } else {
123
            $typo3tempFileService->clearAssetsFolder($folder);
124
            $messageQueue->enqueue(new FlashMessage('Cleared files in "' . $folder . '" folder'));
125
        }
126
        return new JsonResponse([
127
            'success' => true,
128
            'status' => $messageQueue,
129
        ]);
130
    }
131
132
    /**
133
     * Dump autoload information
134
     *
135
     * @return ResponseInterface
136
     */
137
    public function dumpAutoloadAction(): ResponseInterface
138
    {
139
        $messageQueue = new FlashMessageQueue('install');
140
        if (Bootstrap::usesComposerClassLoading()) {
141
            $messageQueue->enqueue(new FlashMessage(
142
                '',
143
                'Skipped generating additional class loading information in composer mode.',
144
                FlashMessage::NOTICE
145
            ));
146
        } else {
147
            ClassLoadingInformation::dumpClassLoadingInformation();
148
            $messageQueue->enqueue(new FlashMessage(
149
                '',
150
                'Successfully dumped class loading information for extensions.'
151
            ));
152
        }
153
        return new JsonResponse([
154
            'success' => true,
155
            'status' => $messageQueue,
156
        ]);
157
    }
158
159
    /**
160
     * Analyze current database situation
161
     *
162
     * @return ResponseInterface
163
     */
164
    public function databaseAnalyzerAnalyzeAction(): ResponseInterface
165
    {
166
        $this->loadExtLocalconfDatabaseAndExtTables();
167
        $messageQueue = new FlashMessageQueue('install');
168
169
        $suggestions = [];
170
        try {
171
            $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
172
            $sqlStatements = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
173
            $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
174
            $addCreateChange = $schemaMigrationService->getUpdateSuggestions($sqlStatements);
175
176
            // Aggregate the per-connection statements into one flat array
177
            $addCreateChange = array_merge_recursive(...array_values($addCreateChange));
0 ignored issues
show
Bug introduced by
array_values($addCreateChange) is expanded, but the parameter $array1 of array_merge_recursive() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

177
            $addCreateChange = array_merge_recursive(/** @scrutinizer ignore-type */ ...array_values($addCreateChange));
Loading history...
178
            if (!empty($addCreateChange['create_table'])) {
179
                $suggestion = [
180
                    'key' => 'addTable',
181
                    'label' => 'Add tables',
182
                    'enabled' => true,
183
                    'children' => [],
184
                ];
185
                foreach ($addCreateChange['create_table'] as $hash => $statement) {
186
                    $suggestion['children'][] = [
187
                        'hash' => $hash,
188
                        'statement' => $statement,
189
                    ];
190
                }
191
                $suggestions[] = $suggestion;
192
            }
193
            if (!empty($addCreateChange['add'])) {
194
                $suggestion = [
195
                    'key' => 'addField',
196
                    'label' => 'Add fields to tables',
197
                    'enabled' => true,
198
                    'children' => [],
199
                ];
200
                foreach ($addCreateChange['add'] as $hash => $statement) {
201
                    $suggestion['children'][] = [
202
                        'hash' => $hash,
203
                        'statement' => $statement,
204
                    ];
205
                }
206
                $suggestions[] = $suggestion;
207
            }
208
            if (!empty($addCreateChange['change'])) {
209
                $suggestion = [
210
                    'key' => 'change',
211
                    'label' => 'Change fields',
212
                    'enabled' => false,
213
                    'children' => [],
214
                ];
215
                foreach ($addCreateChange['change'] as $hash => $statement) {
216
                    $child = [
217
                        'hash' => $hash,
218
                        'statement' => $statement,
219
                    ];
220
                    if (isset($addCreateChange['change_currentValue'][$hash])) {
221
                        $child['current'] = $addCreateChange['change_currentValue'][$hash];
222
                    }
223
                    $suggestion['children'][] = $child;
224
                }
225
                $suggestions[] = $suggestion;
226
            }
227
228
            // Difference from current to expected
229
            $dropRename = $schemaMigrationService->getUpdateSuggestions($sqlStatements, true);
230
231
            // Aggregate the per-connection statements into one flat array
232
            $dropRename = array_merge_recursive(...array_values($dropRename));
233
            if (!empty($dropRename['change_table'])) {
234
                $suggestion = [
235
                    'key' => 'renameTableToUnused',
236
                    'label' => 'Remove tables (rename with prefix)',
237
                    'enabled' => false,
238
                    'children' => [],
239
                ];
240
                foreach ($dropRename['change_table'] as $hash => $statement) {
241
                    $child = [
242
                        'hash' => $hash,
243
                        'statement' => $statement,
244
                    ];
245
                    if (!empty($dropRename['tables_count'][$hash])) {
246
                        $child['rowCount'] = $dropRename['tables_count'][$hash];
247
                    }
248
                    $suggestion['children'][] = $child;
249
                }
250
                $suggestions[] = $suggestion;
251
            }
252
            if (!empty($dropRename['change'])) {
253
                $suggestion = [
254
                    'key' => 'renameTableFieldToUnused',
255
                    'label' => 'Remove unused fields (rename with prefix)',
256
                    'enabled' => false,
257
                    'children' => [],
258
                ];
259
                foreach ($dropRename['change'] as $hash => $statement) {
260
                    $suggestion['children'][] = [
261
                        'hash' => $hash,
262
                        'statement' => $statement,
263
                    ];
264
                }
265
                $suggestions[] = $suggestion;
266
            }
267
            if (!empty($dropRename['drop'])) {
268
                $suggestion = [
269
                    'key' => 'deleteField',
270
                    'label' => 'Drop fields (really!)',
271
                    'enabled' => false,
272
                    'children' => [],
273
                ];
274
                foreach ($dropRename['drop'] as $hash => $statement) {
275
                    $suggestion['children'][] = [
276
                        'hash' => $hash,
277
                        'statement' => $statement,
278
                    ];
279
                }
280
                $suggestions[] = $suggestion;
281
            }
282
            if (!empty($dropRename['drop_table'])) {
283
                $suggestion = [
284
                    'key' => 'deleteTable',
285
                    'label' => 'Drop tables (really!)',
286
                    'enabled' => false,
287
                    'children' => [],
288
                ];
289
                foreach ($dropRename['drop_table'] as $hash => $statement) {
290
                    $child = [
291
                        'hash' => $hash,
292
                        'statement' => $statement,
293
                    ];
294
                    if (!empty($dropRename['tables_count'][$hash])) {
295
                        $child['rowCount'] = $dropRename['tables_count'][$hash];
296
                    }
297
                    $suggestion['children'][] = $child;
298
                }
299
                $suggestions[] = $suggestion;
300
            }
301
302
            $messageQueue->enqueue(new FlashMessage(
303
                '',
304
                'Analyzed current database'
305
            ));
306
        } catch (StatementException $e) {
307
            $messageQueue->enqueue(new FlashMessage(
308
                '',
309
                'Database analysis failed',
310
                FlashMessage::ERROR
311
            ));
312
        }
313
        return new JsonResponse([
314
            'success' => true,
315
            'status' => $messageQueue,
316
            'suggestions' => $suggestions,
317
        ]);
318
    }
319
320
    /**
321
     * Apply selected database changes
322
     *
323
     * @param ServerRequestInterface $request
324
     * @return ResponseInterface
325
     */
326
    public function databaseAnalyzerExecuteAction(ServerRequestInterface $request): ResponseInterface
327
    {
328
        $this->loadExtLocalconfDatabaseAndExtTables();
329
        $messageQueue = new FlashMessageQueue('install');
330
        $selectedHashes = $request->getParsedBody()['install']['hashes'] ?? [];
331
        if (empty($selectedHashes)) {
332
            $messageQueue->enqueue(new FlashMessage(
333
                '',
334
                'No database changes selected',
335
                FlashMessage::WARNING
336
            ));
337
        } else {
338
            $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
339
            $sqlStatements = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
340
            $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
341
            $statementHashesToPerform = array_flip($selectedHashes);
342
            $results = $schemaMigrationService->migrate($sqlStatements, $statementHashesToPerform);
343
            // Create error flash messages if any
344
            foreach ($results as $errorMessage) {
345
                $messageQueue->enqueue(new FlashMessage(
346
                    'Error: ' . $errorMessage,
347
                    'Database update failed',
348
                    FlashMessage::ERROR
349
                ));
350
            }
351
            $messageQueue->enqueue(new FlashMessage(
352
                '',
353
                'Executed database updates'
354
            ));
355
        }
356
        return new JsonResponse([
357
            'success' => true,
358
            'status' => $messageQueue,
359
        ]);
360
    }
361
362
    /**
363
     * Clear table overview statistics action
364
     *
365
     * @return ResponseInterface
366
     */
367
    public function clearTablesStatsAction(): ResponseInterface
368
    {
369
        return new JsonResponse([
370
            'success' => true,
371
            'stats' => (new ClearTableService())->getTableStatistics(),
372
        ]);
373
    }
374
375
    /**
376
     * Truncate a specific table
377
     *
378
     * @param ServerRequestInterface $request
379
     * @return ResponseInterface
380
     */
381
    public function clearTablesClearAction(ServerRequestInterface $request): ResponseInterface
382
    {
383
        $table = $request->getParsedBody()['install']['table'];
384
        if (empty($table)) {
385
            throw new \RuntimeException(
386
                'No table name given',
387
                1501944076
388
            );
389
        }
390
        (new ClearTableService())->clearSelectedTable($table);
391
        $messageQueue = (new FlashMessageQueue('install'))->enqueue(
392
            new FlashMessage('Cleared table')
393
        );
394
        return new JsonResponse([
395
            'success' => true,
396
            'status' => $messageQueue
397
        ]);
398
    }
399
400
    /**
401
     * Create a backend administrator from given username and password
402
     *
403
     * @param ServerRequestInterface $request
404
     * @return ResponseInterface
405
     */
406
    public function createAdminAction(ServerRequestInterface $request): ResponseInterface
407
    {
408
        $username = preg_replace('/\\s/i', '', $request->getParsedBody()['install']['userName']);
409
        $password = $request->getParsedBody()['install']['userPassword'];
410
        $passwordCheck = $request->getParsedBody()['install']['userPasswordCheck'];
411
        $isSystemMaintainer = ((bool)$request->getParsedBody()['install']['userSystemMaintainer'] == '1') ? true : false;
412
413
        $messages = new FlashMessageQueue('install');
414
415
        if (strlen($username) < 1) {
416
            $messages->enqueue(new FlashMessage(
417
                'No valid username given.',
418
                'Administrator user not created',
419
                FlashMessage::ERROR
420
            ));
421
        } elseif ($password !== $passwordCheck) {
422
            $messages->enqueue(new FlashMessage(
423
                'Passwords do not match.',
424
                'Administrator user not created',
425
                FlashMessage::ERROR
426
            ));
427
        } elseif (strlen($password) < 8) {
428
            $messages->enqueue(new FlashMessage(
429
                'Password must be at least eight characters long.',
430
                'Administrator user not created',
431
                FlashMessage::ERROR
432
            ));
433
        } else {
434
            $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
435
            $userExists = $connectionPool->getConnectionForTable('be_users')
436
                ->count(
437
                    'uid',
438
                    'be_users',
439
                    ['username' => $username]
440
                );
441
            if ($userExists) {
442
                $messages->enqueue(new FlashMessage(
443
                    'A user with username "' . $username . '" exists already.',
444
                    'Administrator user not created',
445
                    FlashMessage::ERROR
446
                ));
447
            } else {
448
                $saltFactory = SaltFactory::getSaltingInstance(null, 'BE');
449
                $hashedPassword = $saltFactory->getHashedPassword($password);
450
                $adminUserFields = [
451
                    'username' => $username,
452
                    'password' => $hashedPassword,
453
                    'admin' => 1,
454
                    'tstamp' => $GLOBALS['EXEC_TIME'],
455
                    'crdate' => $GLOBALS['EXEC_TIME']
456
                ];
457
                $connectionPool->getConnectionForTable('be_users')->insert('be_users', $adminUserFields);
458
459
                if ($isSystemMaintainer) {
460
461
                    // Get the new admin user uid juste created
462
                    $newAdminUserUid = (int)$connectionPool->getConnectionForTable('be_users')->lastInsertId('be_users');
463
464
                    // Get the list of the existing systemMaintainer
465
                    $existingSystemMaintainersList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? [];
466
467
                    // Add the new admin user to the existing systemMaintainer list
468
                    $newSystemMaintainersList = $existingSystemMaintainersList;
469
                    $newSystemMaintainersList[] = $newAdminUserUid;
470
471
                    // Update the LocalConfiguration.php file with the new list
472
                    $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
473
                    $configurationManager->setLocalConfigurationValuesByPathValuePairs(
474
                        [ 'SYS/systemMaintainers' => $newSystemMaintainersList ]
475
                    );
476
                }
477
478
                $messages->enqueue(new FlashMessage(
479
                    '',
480
                    'Administrator created with username "' . $username . '".'
481
                ));
482
            }
483
        }
484
        return new JsonResponse([
485
            'success' => true,
486
            'status' => $messages,
487
        ]);
488
    }
489
490
    /**
491
     * Set 'uc' field of all backend users to empty string
492
     *
493
     * @return ResponseInterface
494
     */
495
    public function resetBackendUserUcAction(): ResponseInterface
496
    {
497
        GeneralUtility::makeInstance(ConnectionPool::class)
498
            ->getQueryBuilderForTable('be_users')
499
            ->update('be_users')
500
            ->set('uc', '')
501
            ->execute();
502
        $messageQueue = new FlashMessageQueue('install');
503
        $messageQueue->enqueue(new FlashMessage(
504
            '',
505
            'Reset all backend users preferences'
506
        ));
507
        return new JsonResponse([
508
            'success' => true,
509
            'status' => $messageQueue
510
        ]);
511
    }
512
}
513