Completed
Push — master ( e28c49...c020e9 )
by
unknown
19:48
created

MaintenanceController::clearTablesStatsAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 1
dl 0
loc 15
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
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
namespace TYPO3\CMS\Install\Controller;
19
20
use Psr\Http\Message\ResponseInterface;
21
use Psr\Http\Message\ServerRequestInterface;
22
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
23
use TYPO3\CMS\Core\Core\ClassLoadingInformation;
24
use TYPO3\CMS\Core\Core\Environment;
25
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
26
use TYPO3\CMS\Core\Database\ConnectionPool;
27
use TYPO3\CMS\Core\Database\Schema\Exception\StatementException;
28
use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
29
use TYPO3\CMS\Core\Database\Schema\SqlReader;
30
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
31
use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
32
use TYPO3\CMS\Core\Http\JsonResponse;
33
use TYPO3\CMS\Core\Localization\Locales;
34
use TYPO3\CMS\Core\Messaging\FlashMessage;
35
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
36
use TYPO3\CMS\Core\Service\OpcodeCacheService;
37
use TYPO3\CMS\Core\Utility\GeneralUtility;
38
use TYPO3\CMS\Install\Service\ClearCacheService;
39
use TYPO3\CMS\Install\Service\ClearTableService;
40
use TYPO3\CMS\Install\Service\LanguagePackService;
41
use TYPO3\CMS\Install\Service\LateBootService;
42
use TYPO3\CMS\Install\Service\Typo3tempFileService;
43
44
/**
45
 * Maintenance controller
46
 * @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
47
 */
48
class MaintenanceController extends AbstractController
49
{
50
    /**
51
     * @var LateBootService
52
     */
53
    private $lateBootService;
54
55
    /**
56
     * @var ClearCacheService
57
     */
58
    private $clearCacheService;
59
60
    /**
61
     * @var LanguagePackService
62
     */
63
    private $languagePackService;
64
65
    /**
66
     * @var Typo3tempFileService
67
     */
68
    private $typo3tempFileService;
69
70
    /**
71
     * @var ConfigurationManager
72
     */
73
    private $configurationManager;
74
75
    /**
76
     * @var PasswordHashFactory
77
     */
78
    private $passwordHashFactory;
79
80
    /**
81
     * @var Locales
82
     */
83
    private $locales;
84
85
    public function __construct(
86
        LateBootService $lateBootService,
87
        ClearCacheService $clearCacheService,
88
        LanguagePackService $languagePackService,
89
        Typo3tempFileService $typo3tempFileService,
90
        ConfigurationManager $configurationManager,
91
        PasswordHashFactory $passwordHashFactory,
92
        Locales $locales
93
    ) {
94
        $this->lateBootService = $lateBootService;
95
        $this->clearCacheService = $clearCacheService;
96
        $this->languagePackService = $languagePackService;
97
        $this->typo3tempFileService = $typo3tempFileService;
98
        $this->configurationManager = $configurationManager;
99
        $this->passwordHashFactory = $passwordHashFactory;
100
        $this->locales = $locales;
101
    }
102
    /**
103
     * Main "show the cards" view
104
     *
105
     * @param ServerRequestInterface $request
106
     * @return ResponseInterface
107
     */
108
    public function cardsAction(ServerRequestInterface $request): ResponseInterface
109
    {
110
        $view = $this->initializeStandaloneView($request, 'Maintenance/Cards.html');
111
        return new JsonResponse([
112
            'success' => true,
113
            'html' => $view->render(),
114
        ]);
115
    }
116
117
    /**
118
     * Clear cache framework and opcode caches
119
     *
120
     * @return ResponseInterface
121
     */
122
    public function cacheClearAllAction(): ResponseInterface
123
    {
124
        $this->clearCacheService->clearAll();
125
        GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
126
        $messageQueue = (new FlashMessageQueue('install'))->enqueue(
127
            new FlashMessage('Successfully cleared all caches and all available opcode caches.', 'Caches cleared')
128
        );
129
        return new JsonResponse([
130
            'success' => true,
131
            'status' => $messageQueue,
132
        ]);
133
    }
134
135
    /**
136
     * Clear typo3temp files statistics action
137
     *
138
     * @param ServerRequestInterface $request
139
     * @return ResponseInterface
140
     */
141
    public function clearTypo3tempFilesStatsAction(ServerRequestInterface $request): ResponseInterface
142
    {
143
        $this->lateBootService->loadExtLocalconfDatabaseAndExtTables();
144
        $view = $this->initializeStandaloneView($request, 'Maintenance/ClearTypo3tempFiles.html');
145
        $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
146
        $view->assignMultiple([
147
            'clearTypo3tempFilesToken' => $formProtection->generateToken('installTool', 'clearTypo3tempFiles'),
148
        ]);
149
        return new JsonResponse(
150
            [
151
                'success' => true,
152
                'stats' => $this->typo3tempFileService->getDirectoryStatistics(),
153
                'html' => $view->render(),
154
                'buttons' => [
155
                    [
156
                        'btnClass' => 'btn-default t3js-clearTypo3temp-stats',
157
                        'text' => 'Scan again',
158
                    ],
159
                ],
160
            ]
161
        );
162
    }
163
164
    /**
165
     * Clear typo3temp/assets or FAL processed Files
166
     *
167
     * @param ServerRequestInterface $request
168
     * @return ResponseInterface
169
     */
170
    public function clearTypo3tempFilesAction(ServerRequestInterface $request): ResponseInterface
171
    {
172
        $this->lateBootService->loadExtLocalconfDatabaseAndExtTables();
173
        $messageQueue = new FlashMessageQueue('install');
174
        $folder = $request->getParsedBody()['install']['folder'];
175
        // storageUid is an optional post param if FAL storages should be cleaned
176
        $storageUid = $request->getParsedBody()['install']['storageUid'] ?? null;
177
        if ($storageUid === null) {
178
            $this->typo3tempFileService->clearAssetsFolder($folder);
179
            $messageQueue->enqueue(new FlashMessage('Cleared files in "' . $folder . '" folder'));
180
        } else {
181
            $storageUid = (int)$storageUid;
182
            $failedDeletions = $this->typo3tempFileService->clearProcessedFiles($storageUid);
183
            if ($failedDeletions) {
184
                $messageQueue->enqueue(new FlashMessage(
185
                    'Failed to delete ' . $failedDeletions . ' processed files. See TYPO3 log (by default typo3temp/var/log/typo3_*.log)',
186
                    '',
187
                    FlashMessage::ERROR
188
                ));
189
            } else {
190
                $messageQueue->enqueue(new FlashMessage('Cleared processed files'));
191
            }
192
        }
193
        return new JsonResponse([
194
            'success' => true,
195
            'status' => $messageQueue,
196
        ]);
197
    }
198
199
    /**
200
     * Dump autoload information
201
     *
202
     * @return ResponseInterface
203
     */
204
    public function dumpAutoloadAction(): ResponseInterface
205
    {
206
        $messageQueue = new FlashMessageQueue('install');
207
        if (Environment::isComposerMode()) {
208
            $messageQueue->enqueue(new FlashMessage(
209
                'Skipped generating additional class loading information in composer mode.',
210
                '',
211
                FlashMessage::NOTICE
212
            ));
213
        } else {
214
            ClassLoadingInformation::dumpClassLoadingInformation();
215
            $messageQueue->enqueue(new FlashMessage(
216
                'Successfully dumped class loading information for extensions.'
217
            ));
218
        }
219
        return new JsonResponse([
220
            'success' => true,
221
            'status' => $messageQueue,
222
        ]);
223
    }
224
225
    /**
226
     * Get main database analyzer modal HTML
227
     *
228
     * @param ServerRequestInterface $request
229
     * @return ResponseInterface
230
     */
231
    public function databaseAnalyzerAction(ServerRequestInterface $request): ResponseInterface
232
    {
233
        $view = $this->initializeStandaloneView($request, 'Maintenance/DatabaseAnalyzer.html');
234
        $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
235
        $view->assignMultiple([
236
            'databaseAnalyzerExecuteToken' => $formProtection->generateToken('installTool', 'databaseAnalyzerExecute'),
237
        ]);
238
        return new JsonResponse([
239
            'success' => true,
240
            'html' => $view->render(),
241
            'buttons' => [
242
                [
243
                    'btnClass' => 'btn-default t3js-databaseAnalyzer-analyze',
244
                    'text' => 'Run database compare again',
245
                ], [
246
                    'btnClass' => 'btn-warning t3js-databaseAnalyzer-execute',
247
                    'text' => 'Apply selected changes',
248
                ],
249
            ],
250
        ]);
251
    }
252
253
    /**
254
     * Analyze current database situation
255
     *
256
     * @param ServerRequestInterface $request
257
     * @return ResponseInterface
258
     */
259
    public function databaseAnalyzerAnalyzeAction(ServerRequestInterface $request): ResponseInterface
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

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

259
    public function databaseAnalyzerAnalyzeAction(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): ResponseInterface

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
260
    {
261
        $container = $this->lateBootService->loadExtLocalconfDatabaseAndExtTables();
262
        $messageQueue = new FlashMessageQueue('install');
263
        $suggestions = [];
264
        try {
265
            $sqlReader = $container->get(SqlReader::class);
266
            $sqlStatements = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
267
            $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
268
            $addCreateChange = $schemaMigrationService->getUpdateSuggestions($sqlStatements);
269
270
            // Aggregate the per-connection statements into one flat array
271
            $addCreateChange = array_merge_recursive(...array_values($addCreateChange));
272
            if (!empty($addCreateChange['create_table'])) {
273
                $suggestion = [
274
                    'key' => 'addTable',
275
                    'label' => 'Add tables',
276
                    'enabled' => true,
277
                    'children' => [],
278
                ];
279
                foreach ($addCreateChange['create_table'] as $hash => $statement) {
280
                    $suggestion['children'][] = [
281
                        'hash' => $hash,
282
                        'statement' => $statement,
283
                    ];
284
                }
285
                $suggestions[] = $suggestion;
286
            }
287
            if (!empty($addCreateChange['add'])) {
288
                $suggestion = [
289
                    'key' => 'addField',
290
                    'label' => 'Add fields to tables',
291
                    'enabled' => true,
292
                    'children' => [],
293
                ];
294
                foreach ($addCreateChange['add'] as $hash => $statement) {
295
                    $suggestion['children'][] = [
296
                        'hash' => $hash,
297
                        'statement' => $statement,
298
                    ];
299
                }
300
                $suggestions[] = $suggestion;
301
            }
302
            if (!empty($addCreateChange['change'])) {
303
                $suggestion = [
304
                    'key' => 'change',
305
                    'label' => 'Change fields',
306
                    'enabled' => false,
307
                    'children' => [],
308
                ];
309
                foreach ($addCreateChange['change'] as $hash => $statement) {
310
                    $child = [
311
                        'hash' => $hash,
312
                        'statement' => $statement,
313
                    ];
314
                    if (isset($addCreateChange['change_currentValue'][$hash])) {
315
                        $child['current'] = $addCreateChange['change_currentValue'][$hash];
316
                    }
317
                    $suggestion['children'][] = $child;
318
                }
319
                $suggestions[] = $suggestion;
320
            }
321
322
            // Difference from current to expected
323
            $dropRename = $schemaMigrationService->getUpdateSuggestions($sqlStatements, true);
324
325
            // Aggregate the per-connection statements into one flat array
326
            $dropRename = array_merge_recursive(...array_values($dropRename));
327
            if (!empty($dropRename['change_table'])) {
328
                $suggestion = [
329
                    'key' => 'renameTableToUnused',
330
                    'label' => 'Remove tables (rename with prefix)',
331
                    'enabled' => false,
332
                    'children' => [],
333
                ];
334
                foreach ($dropRename['change_table'] as $hash => $statement) {
335
                    $child = [
336
                        'hash' => $hash,
337
                        'statement' => $statement,
338
                    ];
339
                    if (!empty($dropRename['tables_count'][$hash])) {
340
                        $child['rowCount'] = $dropRename['tables_count'][$hash];
341
                    }
342
                    $suggestion['children'][] = $child;
343
                }
344
                $suggestions[] = $suggestion;
345
            }
346
            if (!empty($dropRename['change'])) {
347
                $suggestion = [
348
                    'key' => 'renameTableFieldToUnused',
349
                    'label' => 'Remove unused fields (rename with prefix)',
350
                    'enabled' => false,
351
                    'children' => [],
352
                ];
353
                foreach ($dropRename['change'] as $hash => $statement) {
354
                    $suggestion['children'][] = [
355
                        'hash' => $hash,
356
                        'statement' => $statement,
357
                    ];
358
                }
359
                $suggestions[] = $suggestion;
360
            }
361
            if (!empty($dropRename['drop'])) {
362
                $suggestion = [
363
                    'key' => 'deleteField',
364
                    'label' => 'Drop fields (really!)',
365
                    'enabled' => false,
366
                    'children' => [],
367
                ];
368
                foreach ($dropRename['drop'] as $hash => $statement) {
369
                    $suggestion['children'][] = [
370
                        'hash' => $hash,
371
                        'statement' => $statement,
372
                    ];
373
                }
374
                $suggestions[] = $suggestion;
375
            }
376
            if (!empty($dropRename['drop_table'])) {
377
                $suggestion = [
378
                    'key' => 'deleteTable',
379
                    'label' => 'Drop tables (really!)',
380
                    'enabled' => false,
381
                    'children' => [],
382
                ];
383
                foreach ($dropRename['drop_table'] as $hash => $statement) {
384
                    $child = [
385
                        'hash' => $hash,
386
                        'statement' => $statement,
387
                    ];
388
                    if (!empty($dropRename['tables_count'][$hash])) {
389
                        $child['rowCount'] = $dropRename['tables_count'][$hash];
390
                    }
391
                    $suggestion['children'][] = $child;
392
                }
393
                $suggestions[] = $suggestion;
394
            }
395
        } catch (StatementException $e) {
396
            $messageQueue->enqueue(new FlashMessage(
397
                $e->getMessage(),
398
                'Database analysis failed',
399
                FlashMessage::ERROR
400
            ));
401
        }
402
        return new JsonResponse([
403
            'success' => true,
404
            'status' => $messageQueue,
405
            'suggestions' => $suggestions,
406
        ]);
407
    }
408
409
    /**
410
     * Apply selected database changes
411
     *
412
     * @param ServerRequestInterface $request
413
     * @return ResponseInterface
414
     */
415
    public function databaseAnalyzerExecuteAction(ServerRequestInterface $request): ResponseInterface
416
    {
417
        $container = $this->lateBootService->loadExtLocalconfDatabaseAndExtTables();
418
        $messageQueue = new FlashMessageQueue('install');
419
        $selectedHashes = $request->getParsedBody()['install']['hashes'] ?? [];
420
        if (empty($selectedHashes)) {
421
            $messageQueue->enqueue(new FlashMessage(
422
                '',
423
                'No database changes selected',
424
                FlashMessage::WARNING
425
            ));
426
        } else {
427
            $sqlReader = $container->get(SqlReader::class);
428
            $sqlStatements = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
429
            $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
430
            $statementHashesToPerform = array_flip($selectedHashes);
431
            $results = $schemaMigrationService->migrate($sqlStatements, $statementHashesToPerform);
432
            // Create error flash messages if any
433
            foreach ($results as $errorMessage) {
434
                $messageQueue->enqueue(new FlashMessage(
435
                    'Error: ' . $errorMessage,
436
                    'Database update failed',
437
                    FlashMessage::ERROR
438
                ));
439
            }
440
            $messageQueue->enqueue(new FlashMessage(
441
                'Executed database updates',
442
                'Executed database updates'
443
            ));
444
        }
445
        return new JsonResponse([
446
            'success' => true,
447
            'status' => $messageQueue,
448
        ]);
449
    }
450
451
    /**
452
     * Clear table overview statistics action
453
     *
454
     * @param ServerRequestInterface $request
455
     * @return ResponseInterface
456
     */
457
    public function clearTablesStatsAction(ServerRequestInterface $request): ResponseInterface
458
    {
459
        $view = $this->initializeStandaloneView($request, 'Maintenance/ClearTables.html');
460
        $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
461
        $view->assignMultiple([
462
            'clearTablesClearToken' => $formProtection->generateToken('installTool', 'clearTablesClear'),
463
        ]);
464
        return new JsonResponse([
465
            'success' => true,
466
            'stats' => (new ClearTableService())->getTableStatistics(),
467
            'html' => $view->render(),
468
            'buttons' => [
469
                [
470
                    'btnClass' => 'btn-default t3js-clearTables-stats',
471
                    'text' => 'Scan again',
472
                ],
473
            ],
474
        ]);
475
    }
476
477
    /**
478
     * Truncate a specific table
479
     *
480
     * @param ServerRequestInterface $request
481
     * @return ResponseInterface
482
     * @throws \RuntimeException
483
     */
484
    public function clearTablesClearAction(ServerRequestInterface $request): ResponseInterface
485
    {
486
        $table = $request->getParsedBody()['install']['table'];
487
        if (empty($table)) {
488
            throw new \RuntimeException(
489
                'No table name given',
490
                1501944076
491
            );
492
        }
493
        (new ClearTableService())->clearSelectedTable($table);
494
        $messageQueue = (new FlashMessageQueue('install'))->enqueue(
495
            new FlashMessage('Cleared table')
496
        );
497
        return new JsonResponse([
498
            'success' => true,
499
            'status' => $messageQueue
500
        ]);
501
    }
502
    /**
503
     * Create Admin Get Data action
504
     *
505
     * @param ServerRequestInterface $request
506
     * @return ResponseInterface
507
     */
508
    public function createAdminGetDataAction(ServerRequestInterface $request): ResponseInterface
509
    {
510
        $view = $this->initializeStandaloneView($request, 'Maintenance/CreateAdmin.html');
511
        $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
512
        $view->assignMultiple([
513
            'createAdminToken' => $formProtection->generateToken('installTool', 'createAdmin'),
514
        ]);
515
        return new JsonResponse([
516
            'success' => true,
517
            'html' => $view->render(),
518
            'buttons' => [
519
                [
520
                    'btnClass' => 'btn-default t3js-createAdmin-create',
521
                    'text' => 'Create administrator user',
522
                ],
523
            ],
524
        ]);
525
    }
526
527
    /**
528
     * Create a backend administrator from given username and password
529
     *
530
     * @param ServerRequestInterface $request
531
     * @return ResponseInterface
532
     */
533
    public function createAdminAction(ServerRequestInterface $request): ResponseInterface
534
    {
535
        $username = preg_replace('/\\s/i', '', $request->getParsedBody()['install']['userName']);
536
        $password = $request->getParsedBody()['install']['userPassword'];
537
        $passwordCheck = $request->getParsedBody()['install']['userPasswordCheck'];
538
        $email = $request->getParsedBody()['install']['userEmail'] ?? '';
539
        $isSystemMaintainer = ((bool)$request->getParsedBody()['install']['userSystemMaintainer'] == '1') ? true : false;
540
541
        $messages = new FlashMessageQueue('install');
542
543
        if (strlen($username) < 1) {
544
            $messages->enqueue(new FlashMessage(
545
                'No valid username given.',
546
                'Administrator user not created',
547
                FlashMessage::ERROR
548
            ));
549
        } elseif ($password !== $passwordCheck) {
550
            $messages->enqueue(new FlashMessage(
551
                'Passwords do not match.',
552
                'Administrator user not created',
553
                FlashMessage::ERROR
554
            ));
555
        } elseif (strlen($password) < 8) {
556
            $messages->enqueue(new FlashMessage(
557
                'Password must be at least eight characters long.',
558
                'Administrator user not created',
559
                FlashMessage::ERROR
560
            ));
561
        } else {
562
            $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
563
            $userExists = $connectionPool->getConnectionForTable('be_users')
564
                ->count(
565
                    'uid',
566
                    'be_users',
567
                    ['username' => $username]
568
                );
569
            if ($userExists) {
570
                $messages->enqueue(new FlashMessage(
571
                    'A user with username "' . $username . '" exists already.',
572
                    'Administrator user not created',
573
                    FlashMessage::ERROR
574
                ));
575
            } else {
576
                $hashInstance = $this->passwordHashFactory->getDefaultHashInstance('BE');
577
                $hashedPassword = $hashInstance->getHashedPassword($password);
578
                $adminUserFields = [
579
                    'username' => $username,
580
                    'password' => $hashedPassword,
581
                    'admin' => 1,
582
                    'tstamp' => $GLOBALS['EXEC_TIME'],
583
                    'crdate' => $GLOBALS['EXEC_TIME']
584
                ];
585
                if (GeneralUtility::validEmail($email)) {
586
                    $adminUserFields['email'] = $email;
587
                }
588
                $connectionPool->getConnectionForTable('be_users')->insert('be_users', $adminUserFields);
589
590
                if ($isSystemMaintainer) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
591
592
                    // Get the new admin user uid just created
593
                    $newAdminUserUid = (int)$connectionPool->getConnectionForTable('be_users')->lastInsertId('be_users');
594
595
                    // Get the list of the existing systemMaintainer
596
                    $existingSystemMaintainersList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? [];
597
598
                    // Add the new admin user to the existing systemMaintainer list
599
                    $newSystemMaintainersList = $existingSystemMaintainersList;
600
                    $newSystemMaintainersList[] = $newAdminUserUid;
601
602
                    // Update the LocalConfiguration.php file with the new list
603
                    $this->configurationManager->setLocalConfigurationValuesByPathValuePairs(
604
                        ['SYS/systemMaintainers' => $newSystemMaintainersList]
605
                    );
606
                }
607
608
                $messages->enqueue(new FlashMessage(
609
                    '',
610
                    'Administrator created with username "' . $username . '".'
611
                ));
612
            }
613
        }
614
        return new JsonResponse([
615
            'success' => true,
616
            'status' => $messages,
617
        ]);
618
    }
619
620
    /**
621
     * Entry action of language packs module gets
622
     * * list of available languages with details like active or not and last update
623
     * * list of loaded extensions
624
     *
625
     * @param ServerRequestInterface $request
626
     * @return ResponseInterface
627
     */
628
    public function languagePacksGetDataAction(ServerRequestInterface $request): ResponseInterface
629
    {
630
        $view = $this->initializeStandaloneView($request, 'Maintenance/LanguagePacks.html');
631
        $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
632
        $view->assignMultiple([
633
            'languagePacksActivateLanguageToken' => $formProtection->generateToken('installTool', 'languagePacksActivateLanguage'),
634
            'languagePacksDeactivateLanguageToken' => $formProtection->generateToken('installTool', 'languagePacksDeactivateLanguage'),
635
            'languagePacksUpdatePackToken' => $formProtection->generateToken('installTool', 'languagePacksUpdatePack'),
636
            'languagePacksUpdateIsoTimesToken' => $formProtection->generateToken('installTool', 'languagePacksUpdateIsoTimes'),
637
        ]);
638
        // This action needs TYPO3_CONF_VARS for full GeneralUtility::getUrl() config
639
        $this->lateBootService->loadExtLocalconfDatabaseAndExtTables();
640
        $this->languagePackService->updateMirrorBaseUrl();
641
        $extensions = $this->languagePackService->getExtensionLanguagePackDetails();
642
        return new JsonResponse([
643
            'success' => true,
644
            'languages' => $this->languagePackService->getLanguageDetails(),
645
            'extensions' => $extensions,
646
            'activeLanguages' => $this->languagePackService->getActiveLanguages(),
647
            'activeExtensions' => array_column($extensions, 'key'),
648
            'html' => $view->render(),
649
        ]);
650
    }
651
652
    /**
653
     * Activate a language and any possible dependency it may have
654
     *
655
     * @param ServerRequestInterface $request
656
     * @return ResponseInterface
657
     */
658
    public function languagePacksActivateLanguageAction(ServerRequestInterface $request): ResponseInterface
659
    {
660
        $messageQueue = new FlashMessageQueue('install');
661
        $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
662
        $availableLanguages = $languagePackService->getAvailableLanguages();
663
        $activeLanguages = $languagePackService->getActiveLanguages();
664
        $iso = $request->getParsedBody()['install']['iso'];
665
        $activateArray = [];
666
        foreach ($availableLanguages as $availableIso => $name) {
667
            if ($availableIso === $iso && !in_array($availableIso, $activeLanguages, true)) {
668
                $activateArray[] = $iso;
669
                $dependencies = $this->locales->getLocaleDependencies($availableIso);
670
                if (!empty($dependencies)) {
671
                    foreach ($dependencies as $dependency) {
672
                        if (!in_array($dependency, $activeLanguages, true)) {
673
                            $activateArray[] = $dependency;
674
                        }
675
                    }
676
                }
677
            }
678
        }
679
        if (!empty($activateArray)) {
680
            $activeLanguages = array_merge($activeLanguages, $activateArray);
681
            sort($activeLanguages);
682
            $this->configurationManager->setLocalConfigurationValueByPath(
683
                'EXTCONF/lang',
684
                ['availableLanguages' => $activeLanguages]
685
            );
686
            $activationArray = [];
687
            foreach ($activateArray as $activateIso) {
688
                $activationArray[] = $availableLanguages[$activateIso] . ' (' . $activateIso . ')';
689
            }
690
            $messageQueue->enqueue(
691
                new FlashMessage(
692
                    'These languages have been activated: ' . implode(', ', $activationArray)
693
                )
694
            );
695
        } else {
696
            $messageQueue->enqueue(
697
                new FlashMessage('Language with ISO code "' . $iso . '" not found or already active.', '', FlashMessage::ERROR)
698
            );
699
        }
700
        return new JsonResponse([
701
            'success' => true,
702
            'status' => $messageQueue,
703
        ]);
704
    }
705
706
    /**
707
     * Deactivate a language if no other active language depends on it
708
     *
709
     * @param ServerRequestInterface $request
710
     * @return ResponseInterface
711
     * @throws \RuntimeException
712
     */
713
    public function languagePacksDeactivateLanguageAction(ServerRequestInterface $request): ResponseInterface
714
    {
715
        $messageQueue = new FlashMessageQueue('install');
716
        $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
717
        $availableLanguages = $languagePackService->getAvailableLanguages();
718
        $activeLanguages = $languagePackService->getActiveLanguages();
719
        $iso = $request->getParsedBody()['install']['iso'];
720
        if (empty($iso)) {
721
            throw new \RuntimeException('No iso code given', 1520109807);
722
        }
723
        $otherActiveLanguageDependencies = [];
724
        foreach ($activeLanguages as $activeLanguage) {
725
            if ($activeLanguage === $iso) {
726
                continue;
727
            }
728
            $dependencies = $this->locales->getLocaleDependencies($activeLanguage);
729
            if (in_array($iso, $dependencies, true)) {
730
                $otherActiveLanguageDependencies[] = $activeLanguage;
731
            }
732
        }
733
        if (!empty($otherActiveLanguageDependencies)) {
734
            // Error: Must disable dependencies first
735
            $dependentArray = [];
736
            foreach ($otherActiveLanguageDependencies as $dependency) {
737
                $dependentArray[] = $availableLanguages[$dependency] . ' (' . $dependency . ')';
738
            }
739
            $messageQueue->enqueue(
740
                new FlashMessage(
741
                    'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" can not be deactivated. These'
742
                    . ' other languages depend on it and need to be deactivated before:'
743
                    . implode(', ', $dependentArray),
744
                    '',
745
                    FlashMessage::ERROR
746
                )
747
            );
748
        } else {
749
            if (in_array($iso, $activeLanguages, true)) {
750
                // Deactivate this language
751
                $newActiveLanguages = [];
752
                foreach ($activeLanguages as $activeLanguage) {
753
                    if ($activeLanguage === $iso) {
754
                        continue;
755
                    }
756
                    $newActiveLanguages[] = $activeLanguage;
757
                }
758
                $this->configurationManager->setLocalConfigurationValueByPath(
759
                    'EXTCONF/lang',
760
                    ['availableLanguages' => $newActiveLanguages]
761
                );
762
                $messageQueue->enqueue(
763
                    new FlashMessage(
764
                        'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" has been deactivated'
765
                    )
766
                );
767
            } else {
768
                $messageQueue->enqueue(
769
                    new FlashMessage(
770
                        'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" has not been deactivated',
771
                        '',
772
                        FlashMessage::ERROR
773
                    )
774
                );
775
            }
776
        }
777
        return new JsonResponse([
778
            'success' => true,
779
            'status' => $messageQueue,
780
        ]);
781
    }
782
783
    /**
784
     * Update a pack of one extension and one language
785
     *
786
     * @param ServerRequestInterface $request
787
     * @return ResponseInterface
788
     * @throws \RuntimeException
789
     */
790
    public function languagePacksUpdatePackAction(ServerRequestInterface $request): ResponseInterface
791
    {
792
        $this->lateBootService->loadExtLocalconfDatabaseAndExtTables();
793
        $iso = $request->getParsedBody()['install']['iso'];
794
        $key = $request->getParsedBody()['install']['extension'];
795
        $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
796
        return new JsonResponse([
797
            'success' => true,
798
            'packResult' => $languagePackService->languagePackDownload($key, $iso)
799
        ]);
800
    }
801
802
    /**
803
     * Set "last updated" time in registry for fully updated language packs.
804
     *
805
     * @param ServerRequestInterface $request
806
     * @return ResponseInterface
807
     */
808
    public function languagePacksUpdateIsoTimesAction(ServerRequestInterface $request): ResponseInterface
809
    {
810
        $isos = $request->getParsedBody()['install']['isos'];
811
        $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
812
        $languagePackService->setLastUpdatedIsoCode($isos);
813
814
        // The cache manager is already instantiated in the install tool
815
        // with some hacked settings to disable caching of extbase and fluid.
816
        // We want a "fresh" object here to operate on a different cache setup.
817
        // cacheManager implements SingletonInterface, so the only way to get a "fresh"
818
        // instance is by circumventing makeInstance and/or the objectManager and
819
        // using new directly!
820
        $cacheManager = new \TYPO3\CMS\Core\Cache\CacheManager();
821
        $cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
822
        $cacheManager->getCache('l10n')->flush();
823
824
        return new JsonResponse(['success' => true]);
825
    }
826
827
    /**
828
     * Set 'uc' field of all backend users to empty string
829
     *
830
     * @return ResponseInterface
831
     */
832
    public function resetBackendUserUcAction(): ResponseInterface
833
    {
834
        GeneralUtility::makeInstance(ConnectionPool::class)
835
            ->getQueryBuilderForTable('be_users')
836
            ->update('be_users')
837
            ->set('uc', '')
838
            ->execute();
839
        $messageQueue = new FlashMessageQueue('install');
840
        $messageQueue->enqueue(new FlashMessage(
841
            'Preferences of all backend users have been reset',
842
            'Reset preferences of all backend users'
843
        ));
844
        return new JsonResponse([
845
            'success' => true,
846
            'status' => $messageQueue
847
        ]);
848
    }
849
}
850