Test Failed
Branch master (7b1793)
by Tymoteusz
15:35
created

databaseAnalyzerAnalyzeAction()   F

Complexity

Conditions 19
Paths > 20000

Size

Total Lines 153
Code Lines 109

Duplication

Lines 117
Ratio 76.47 %

Importance

Changes 0
Metric Value
cc 19
eloc 109
nc 28676
nop 0
dl 117
loc 153
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Core\Bootstrap;
21
use TYPO3\CMS\Core\Core\ClassLoadingInformation;
22
use TYPO3\CMS\Core\Database\ConnectionPool;
23
use TYPO3\CMS\Core\Database\Schema\Exception\StatementException;
24
use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
25
use TYPO3\CMS\Core\Database\Schema\SqlReader;
26
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
27
use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
28
use TYPO3\CMS\Core\Http\JsonResponse;
29
use TYPO3\CMS\Core\Messaging\FlashMessage;
30
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
31
use TYPO3\CMS\Core\Service\OpcodeCacheService;
32
use TYPO3\CMS\Core\Utility\GeneralUtility;
33
use TYPO3\CMS\Install\Service\ClearCacheService;
34
use TYPO3\CMS\Install\Service\ClearTableService;
35
use TYPO3\CMS\Install\Service\Typo3tempFileService;
36
use TYPO3\CMS\Saltedpasswords\Salt\SaltFactory;
37
38
/**
39
 * Maintenance controller
40
 */
41
class MaintenanceController extends AbstractController
42
{
43
    /**
44
     * Main "show the cards" view
45
     *
46
     * @param ServerRequestInterface $request
47
     * @return ResponseInterface
48
     */
49
    public function cardsAction(ServerRequestInterface $request): ResponseInterface
50
    {
51
        $view = $this->initializeStandaloneView($request, 'Maintenance/Cards.html');
52
        $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
53
        $view->assignMultiple([
54
            'clearAllCacheOpcodeCaches' => (new OpcodeCacheService())->getAllActive(),
55
            'clearTablesClearToken' => $formProtection->generateToken('installTool', 'clearTablesClear'),
56
            'clearTypo3tempFilesToken' => $formProtection->generateToken('installTool', 'clearTypo3tempFiles'),
57
            'createAdminToken' => $formProtection->generateToken('installTool', 'createAdmin'),
58
            'databaseAnalyzerExecuteToken' => $formProtection->generateToken('installTool', 'databaseAnalyzerExecute'),
59
        ]);
60
        return new JsonResponse([
61
            'success' => true,
62
            'html' => $view->render(),
63
        ]);
64
    }
65
66
    /**
67
     * Clear cache framework and opcode caches
68
     *
69
     * @return ResponseInterface
70
     */
71
    public function cacheClearAllAction(): ResponseInterface
72
    {
73
        GeneralUtility::makeInstance(ClearCacheService::class)->clearAll();
74
        GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
75
        $messageQueue = (new FlashMessageQueue('install'))->enqueue(
76
            new FlashMessage('Successfully cleared all caches and all available opcode caches.')
77
        );
78
        return new JsonResponse([
79
            'success' => true,
80
            'status' => $messageQueue,
81
        ]);
82
    }
83
84
    /**
85
     * Clear typo3temp files statistics action
86
     *
87
     * @return ResponseInterface
88
     */
89
    public function clearTypo3tempFilesStatsAction(): ResponseInterface
90
    {
91
        return new JsonResponse(
92
            [
93
                'success' => true,
94
                'stats' => (new Typo3tempFileService())->getDirectoryStatistics(),
95
            ]
96
        );
97
    }
98
99
    /**
100
     * Clear Processed Files
101
     *
102
     * @param ServerRequestInterface $request
103
     * @return ResponseInterface
104
     */
105
    public function clearTypo3tempFilesAction(ServerRequestInterface $request): ResponseInterface
106
    {
107
        $messageQueue = new FlashMessageQueue('install');
108
        $typo3tempFileService = new Typo3tempFileService();
109
        $folder = $request->getParsedBody()['install']['folder'];
110
        if ($folder === '_processed_') {
111
            $failedDeletions = $typo3tempFileService->clearProcessedFiles();
112
            if ($failedDeletions) {
113
                $messageQueue->enqueue(new FlashMessage(
114
                    'Failed to delete ' . $failedDeletions . ' processed files. See TYPO3 log (by default typo3temp/var/logs/typo3_*.log)',
115
                    '',
116
                    FlashMessage::ERROR
117
                ));
118
            } else {
119
                $messageQueue->enqueue(new FlashMessage('Cleared processed files'));
120
            }
121
        } else {
122
            $typo3tempFileService->clearAssetsFolder($folder);
123
            $messageQueue->enqueue(new FlashMessage('Cleared files in "' . $folder . '" folder'));
124
        }
125
        return new JsonResponse([
126
            'success' => true,
127
            'status' => $messageQueue,
128
        ]);
129
    }
130
131
    /**
132
     * Dump autoload information
133
     *
134
     * @return ResponseInterface
135
     */
136
    public function dumpAutoloadAction(): ResponseInterface
137
    {
138
        $messageQueue = new FlashMessageQueue('install');
139
        if (Bootstrap::usesComposerClassLoading()) {
140
            $messageQueue->enqueue(new FlashMessage(
141
                '',
142
                'Skipped generating additional class loading information in composer mode.',
143
                FlashMessage::NOTICE
144
            ));
145
        } else {
146
            ClassLoadingInformation::dumpClassLoadingInformation();
147
            $messageQueue->enqueue(new FlashMessage(
148
                '',
149
                'Successfully dumped class loading information for extensions.'
150
            ));
151
        }
152
        return new JsonResponse([
153
            'success' => true,
154
            'status' => $messageQueue,
155
        ]);
156
    }
157
158
    /**
159
     * Analyze current database situation
160
     *
161
     * @return ResponseInterface
162
     */
163
    public function databaseAnalyzerAnalyzeAction(): ResponseInterface
164
    {
165
        $this->loadExtLocalconfDatabaseAndExtTables();
166
        $messageQueue = new FlashMessageQueue('install');
167
168
        $suggestions = [];
169
        try {
170
            $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
171
            $sqlStatements = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
172
            $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
173
            $addCreateChange = $schemaMigrationService->getUpdateSuggestions($sqlStatements);
174
175
            // Aggregate the per-connection statements into one flat array
176
            $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

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