[TASK] Improve database analyzer UX
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Controller / MaintenanceController.php
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\ClassLoadingInformation;
22 use TYPO3\CMS\Core\Core\Environment;
23 use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
24 use TYPO3\CMS\Core\Database\ConnectionPool;
25 use TYPO3\CMS\Core\Database\Schema\Exception\StatementException;
26 use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
27 use TYPO3\CMS\Core\Database\Schema\SqlReader;
28 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
29 use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
30 use TYPO3\CMS\Core\Http\JsonResponse;
31 use TYPO3\CMS\Core\Localization\Locales;
32 use TYPO3\CMS\Core\Messaging\FlashMessage;
33 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
34 use TYPO3\CMS\Core\Service\OpcodeCacheService;
35 use TYPO3\CMS\Core\Utility\GeneralUtility;
36 use TYPO3\CMS\Install\Service\ClearCacheService;
37 use TYPO3\CMS\Install\Service\ClearTableService;
38 use TYPO3\CMS\Install\Service\LanguagePackService;
39 use TYPO3\CMS\Install\Service\Typo3tempFileService;
40
41 /**
42 * Maintenance controller
43 */
44 class MaintenanceController extends AbstractController
45 {
46 /**
47 * Main "show the cards" view
48 *
49 * @param ServerRequestInterface $request
50 * @return ResponseInterface
51 */
52 public function cardsAction(ServerRequestInterface $request): ResponseInterface
53 {
54 $view = $this->initializeStandaloneView($request, 'Maintenance/Cards.html');
55 return new JsonResponse([
56 'success' => true,
57 'html' => $view->render(),
58 ]);
59 }
60
61 /**
62 * Clear cache framework and opcode caches
63 *
64 * @return ResponseInterface
65 */
66 public function cacheClearAllAction(): ResponseInterface
67 {
68 GeneralUtility::makeInstance(ClearCacheService::class)->clearAll();
69 GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive();
70 $messageQueue = (new FlashMessageQueue('install'))->enqueue(
71 new FlashMessage('Successfully cleared all caches and all available opcode caches.', 'Caches cleared')
72 );
73 return new JsonResponse([
74 'success' => true,
75 'status' => $messageQueue,
76 ]);
77 }
78
79 /**
80 * Clear typo3temp files statistics action
81 *
82 * @param ServerRequestInterface $request
83 * @return ResponseInterface
84 */
85 public function clearTypo3tempFilesStatsAction(ServerRequestInterface $request): ResponseInterface
86 {
87 $view = $this->initializeStandaloneView($request, 'Maintenance/ClearTypo3tempFiles.html');
88 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
89 $view->assignMultiple([
90 'clearTypo3tempFilesToken' => $formProtection->generateToken('installTool', 'clearTypo3tempFiles'),
91 ]);
92 return new JsonResponse(
93 [
94 'success' => true,
95 'stats' => (new Typo3tempFileService())->getDirectoryStatistics(),
96 'html' => $view->render(),
97 ]
98 );
99 }
100
101 /**
102 * Clear typo3temp/assets or FAL processed Files
103 *
104 * @param ServerRequestInterface $request
105 * @return ResponseInterface
106 */
107 public function clearTypo3tempFilesAction(ServerRequestInterface $request): ResponseInterface
108 {
109 $messageQueue = new FlashMessageQueue('install');
110 $typo3tempFileService = new Typo3tempFileService();
111 $folder = $request->getParsedBody()['install']['folder'];
112 // storageUid is an optional post param if FAL storages should be cleaned
113 $storageUid = $request->getParsedBody()['install']['storageUid'] ?? null;
114 if ($storageUid === null) {
115 $typo3tempFileService->clearAssetsFolder($folder);
116 $messageQueue->enqueue(new FlashMessage('Cleared files in "' . $folder . '" folder'));
117 } else {
118 $storageUid = (int)$storageUid;
119 $failedDeletions = $typo3tempFileService->clearProcessedFiles($storageUid);
120 if ($failedDeletions) {
121 $messageQueue->enqueue(new FlashMessage(
122 'Failed to delete ' . $failedDeletions . ' processed files. See TYPO3 log (by default typo3temp/var/log/typo3_*.log)',
123 '',
124 FlashMessage::ERROR
125 ));
126 } else {
127 $messageQueue->enqueue(new FlashMessage('Cleared processed files'));
128 }
129 }
130 return new JsonResponse([
131 'success' => true,
132 'status' => $messageQueue,
133 ]);
134 }
135
136 /**
137 * Dump autoload information
138 *
139 * @return ResponseInterface
140 */
141 public function dumpAutoloadAction(): ResponseInterface
142 {
143 $messageQueue = new FlashMessageQueue('install');
144 if (Environment::isComposerMode()) {
145 $messageQueue->enqueue(new FlashMessage(
146 'Skipped generating additional class loading information in composer mode.',
147 '',
148 FlashMessage::NOTICE
149 ));
150 } else {
151 ClassLoadingInformation::dumpClassLoadingInformation();
152 $messageQueue->enqueue(new FlashMessage(
153 'Successfully dumped class loading information for extensions.'
154 ));
155 }
156 return new JsonResponse([
157 'success' => true,
158 'status' => $messageQueue,
159 ]);
160 }
161
162 /**
163 * Get main database analyzer modal HTML
164 *
165 * @param ServerRequestInterface $request
166 * @return ResponseInterface
167 */
168 public function databaseAnalyzerAction(ServerRequestInterface $request): ResponseInterface
169 {
170 $view = $this->initializeStandaloneView($request, 'Maintenance/DatabaseAnalyzer.html');
171 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
172 $view->assignMultiple([
173 'databaseAnalyzerExecuteToken' => $formProtection->generateToken('installTool', 'databaseAnalyzerExecute'),
174 ]);
175 return new JsonResponse([
176 'success' => true,
177 'html' => $view->render(),
178 ]);
179 }
180
181 /**
182 * Analyze current database situation
183 *
184 * @param ServerRequestInterface $request
185 * @return ResponseInterface
186 */
187 public function databaseAnalyzerAnalyzeAction(ServerRequestInterface $request): ResponseInterface
188 {
189 $this->loadExtLocalconfDatabaseAndExtTables();
190 $messageQueue = new FlashMessageQueue('install');
191 $suggestions = [];
192 try {
193 $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
194 $sqlStatements = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
195 $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
196 $addCreateChange = $schemaMigrationService->getUpdateSuggestions($sqlStatements);
197
198 // Aggregate the per-connection statements into one flat array
199 $addCreateChange = array_merge_recursive(...array_values($addCreateChange));
200 if (!empty($addCreateChange['create_table'])) {
201 $suggestion = [
202 'key' => 'addTable',
203 'label' => 'Add tables',
204 'enabled' => true,
205 'children' => [],
206 ];
207 foreach ($addCreateChange['create_table'] as $hash => $statement) {
208 $suggestion['children'][] = [
209 'hash' => $hash,
210 'statement' => $statement,
211 ];
212 }
213 $suggestions[] = $suggestion;
214 }
215 if (!empty($addCreateChange['add'])) {
216 $suggestion = [
217 'key' => 'addField',
218 'label' => 'Add fields to tables',
219 'enabled' => true,
220 'children' => [],
221 ];
222 foreach ($addCreateChange['add'] as $hash => $statement) {
223 $suggestion['children'][] = [
224 'hash' => $hash,
225 'statement' => $statement,
226 ];
227 }
228 $suggestions[] = $suggestion;
229 }
230 if (!empty($addCreateChange['change'])) {
231 $suggestion = [
232 'key' => 'change',
233 'label' => 'Change fields',
234 'enabled' => false,
235 'children' => [],
236 ];
237 foreach ($addCreateChange['change'] as $hash => $statement) {
238 $child = [
239 'hash' => $hash,
240 'statement' => $statement,
241 ];
242 if (isset($addCreateChange['change_currentValue'][$hash])) {
243 $child['current'] = $addCreateChange['change_currentValue'][$hash];
244 }
245 $suggestion['children'][] = $child;
246 }
247 $suggestions[] = $suggestion;
248 }
249
250 // Difference from current to expected
251 $dropRename = $schemaMigrationService->getUpdateSuggestions($sqlStatements, true);
252
253 // Aggregate the per-connection statements into one flat array
254 $dropRename = array_merge_recursive(...array_values($dropRename));
255 if (!empty($dropRename['change_table'])) {
256 $suggestion = [
257 'key' => 'renameTableToUnused',
258 'label' => 'Remove tables (rename with prefix)',
259 'enabled' => false,
260 'children' => [],
261 ];
262 foreach ($dropRename['change_table'] as $hash => $statement) {
263 $child = [
264 'hash' => $hash,
265 'statement' => $statement,
266 ];
267 if (!empty($dropRename['tables_count'][$hash])) {
268 $child['rowCount'] = $dropRename['tables_count'][$hash];
269 }
270 $suggestion['children'][] = $child;
271 }
272 $suggestions[] = $suggestion;
273 }
274 if (!empty($dropRename['change'])) {
275 $suggestion = [
276 'key' => 'renameTableFieldToUnused',
277 'label' => 'Remove unused fields (rename with prefix)',
278 'enabled' => false,
279 'children' => [],
280 ];
281 foreach ($dropRename['change'] as $hash => $statement) {
282 $suggestion['children'][] = [
283 'hash' => $hash,
284 'statement' => $statement,
285 ];
286 }
287 $suggestions[] = $suggestion;
288 }
289 if (!empty($dropRename['drop'])) {
290 $suggestion = [
291 'key' => 'deleteField',
292 'label' => 'Drop fields (really!)',
293 'enabled' => false,
294 'children' => [],
295 ];
296 foreach ($dropRename['drop'] as $hash => $statement) {
297 $suggestion['children'][] = [
298 'hash' => $hash,
299 'statement' => $statement,
300 ];
301 }
302 $suggestions[] = $suggestion;
303 }
304 if (!empty($dropRename['drop_table'])) {
305 $suggestion = [
306 'key' => 'deleteTable',
307 'label' => 'Drop tables (really!)',
308 'enabled' => false,
309 'children' => [],
310 ];
311 foreach ($dropRename['drop_table'] as $hash => $statement) {
312 $child = [
313 'hash' => $hash,
314 'statement' => $statement,
315 ];
316 if (!empty($dropRename['tables_count'][$hash])) {
317 $child['rowCount'] = $dropRename['tables_count'][$hash];
318 }
319 $suggestion['children'][] = $child;
320 }
321 $suggestions[] = $suggestion;
322 }
323 } catch (StatementException $e) {
324 $messageQueue->enqueue(new FlashMessage(
325 $e->getMessage(),
326 'Database analysis failed',
327 FlashMessage::ERROR
328 ));
329 }
330 return new JsonResponse([
331 'success' => true,
332 'status' => $messageQueue,
333 'suggestions' => $suggestions,
334 ]);
335 }
336
337 /**
338 * Apply selected database changes
339 *
340 * @param ServerRequestInterface $request
341 * @return ResponseInterface
342 */
343 public function databaseAnalyzerExecuteAction(ServerRequestInterface $request): ResponseInterface
344 {
345 $this->loadExtLocalconfDatabaseAndExtTables();
346 $messageQueue = new FlashMessageQueue('install');
347 $selectedHashes = $request->getParsedBody()['install']['hashes'] ?? [];
348 if (empty($selectedHashes)) {
349 $messageQueue->enqueue(new FlashMessage(
350 '',
351 'No database changes selected',
352 FlashMessage::WARNING
353 ));
354 } else {
355 $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
356 $sqlStatements = $sqlReader->getCreateTableStatementArray($sqlReader->getTablesDefinitionString());
357 $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
358 $statementHashesToPerform = array_flip($selectedHashes);
359 $results = $schemaMigrationService->migrate($sqlStatements, $statementHashesToPerform);
360 // Create error flash messages if any
361 foreach ($results as $errorMessage) {
362 $messageQueue->enqueue(new FlashMessage(
363 'Error: ' . $errorMessage,
364 'Database update failed',
365 FlashMessage::ERROR
366 ));
367 }
368 $messageQueue->enqueue(new FlashMessage(
369 'Executed database updates',
370 'Executed database updates'
371 ));
372 }
373 return new JsonResponse([
374 'success' => true,
375 'status' => $messageQueue,
376 ]);
377 }
378
379 /**
380 * Clear table overview statistics action
381 *
382 * @param ServerRequestInterface $request
383 * @return ResponseInterface
384 */
385 public function clearTablesStatsAction(ServerRequestInterface $request): ResponseInterface
386 {
387 $view = $this->initializeStandaloneView($request, 'Maintenance/ClearTables.html');
388 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
389 $view->assignMultiple([
390 'clearTablesClearToken' => $formProtection->generateToken('installTool', 'clearTablesClear'),
391 ]);
392 return new JsonResponse([
393 'success' => true,
394 'stats' => (new ClearTableService())->getTableStatistics(),
395 'html' => $view->render(),
396 ]);
397 }
398
399 /**
400 * Truncate a specific table
401 *
402 * @param ServerRequestInterface $request
403 * @return ResponseInterface
404 * @throws \RuntimeException
405 */
406 public function clearTablesClearAction(ServerRequestInterface $request): ResponseInterface
407 {
408 $table = $request->getParsedBody()['install']['table'];
409 if (empty($table)) {
410 throw new \RuntimeException(
411 'No table name given',
412 1501944076
413 );
414 }
415 (new ClearTableService())->clearSelectedTable($table);
416 $messageQueue = (new FlashMessageQueue('install'))->enqueue(
417 new FlashMessage('Cleared table')
418 );
419 return new JsonResponse([
420 'success' => true,
421 'status' => $messageQueue
422 ]);
423 }
424 /**
425 * Create Admin Get Data action
426 *
427 * @param ServerRequestInterface $request
428 * @return ResponseInterface
429 */
430 public function createAdminGetDataAction(ServerRequestInterface $request): ResponseInterface
431 {
432 $view = $this->initializeStandaloneView($request, 'Maintenance/CreateAdmin.html');
433 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
434 $view->assignMultiple([
435 'createAdminToken' => $formProtection->generateToken('installTool', 'createAdmin'),
436 ]);
437 return new JsonResponse([
438 'success' => true,
439 'html' => $view->render(),
440 ]);
441 }
442
443 /**
444 * Create a backend administrator from given username and password
445 *
446 * @param ServerRequestInterface $request
447 * @return ResponseInterface
448 */
449 public function createAdminAction(ServerRequestInterface $request): ResponseInterface
450 {
451 $username = preg_replace('/\\s/i', '', $request->getParsedBody()['install']['userName']);
452 $password = $request->getParsedBody()['install']['userPassword'];
453 $passwordCheck = $request->getParsedBody()['install']['userPasswordCheck'];
454 $isSystemMaintainer = ((bool)$request->getParsedBody()['install']['userSystemMaintainer'] == '1') ? true : false;
455
456 $messages = new FlashMessageQueue('install');
457
458 if (strlen($username) < 1) {
459 $messages->enqueue(new FlashMessage(
460 'No valid username given.',
461 'Administrator user not created',
462 FlashMessage::ERROR
463 ));
464 } elseif ($password !== $passwordCheck) {
465 $messages->enqueue(new FlashMessage(
466 'Passwords do not match.',
467 'Administrator user not created',
468 FlashMessage::ERROR
469 ));
470 } elseif (strlen($password) < 8) {
471 $messages->enqueue(new FlashMessage(
472 'Password must be at least eight characters long.',
473 'Administrator user not created',
474 FlashMessage::ERROR
475 ));
476 } else {
477 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
478 $userExists = $connectionPool->getConnectionForTable('be_users')
479 ->count(
480 'uid',
481 'be_users',
482 ['username' => $username]
483 );
484 if ($userExists) {
485 $messages->enqueue(new FlashMessage(
486 'A user with username "' . $username . '" exists already.',
487 'Administrator user not created',
488 FlashMessage::ERROR
489 ));
490 } else {
491 $hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE');
492 $hashedPassword = $hashInstance->getHashedPassword($password);
493 $adminUserFields = [
494 'username' => $username,
495 'password' => $hashedPassword,
496 'admin' => 1,
497 'tstamp' => $GLOBALS['EXEC_TIME'],
498 'crdate' => $GLOBALS['EXEC_TIME']
499 ];
500 $connectionPool->getConnectionForTable('be_users')->insert('be_users', $adminUserFields);
501
502 if ($isSystemMaintainer) {
503
504 // Get the new admin user uid juste created
505 $newAdminUserUid = (int)$connectionPool->getConnectionForTable('be_users')->lastInsertId('be_users');
506
507 // Get the list of the existing systemMaintainer
508 $existingSystemMaintainersList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? [];
509
510 // Add the new admin user to the existing systemMaintainer list
511 $newSystemMaintainersList = $existingSystemMaintainersList;
512 $newSystemMaintainersList[] = $newAdminUserUid;
513
514 // Update the LocalConfiguration.php file with the new list
515 $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
516 $configurationManager->setLocalConfigurationValuesByPathValuePairs(
517 ['SYS/systemMaintainers' => $newSystemMaintainersList]
518 );
519 }
520
521 $messages->enqueue(new FlashMessage(
522 '',
523 'Administrator created with username "' . $username . '".'
524 ));
525 }
526 }
527 return new JsonResponse([
528 'success' => true,
529 'status' => $messages,
530 ]);
531 }
532
533 /**
534 * Entry action of language packs module gets
535 * * list of available languages with details like active or not and last update
536 * * list of loaded extensions
537 *
538 * @param ServerRequestInterface $request
539 * @return ResponseInterface
540 */
541 public function languagePacksGetDataAction(ServerRequestInterface $request): ResponseInterface
542 {
543 $view = $this->initializeStandaloneView($request, 'Maintenance/LanguagePacks.html');
544 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
545 $view->assignMultiple([
546 'languagePacksActivateLanguageToken' => $formProtection->generateToken('installTool', 'languagePacksActivateLanguage'),
547 'languagePacksDeactivateLanguageToken' => $formProtection->generateToken('installTool', 'languagePacksDeactivateLanguage'),
548 'languagePacksUpdatePackToken' => $formProtection->generateToken('installTool', 'languagePacksUpdatePack'),
549 'languagePacksUpdateIsoTimesToken' => $formProtection->generateToken('installTool', 'languagePacksUpdateIsoTimes'),
550 ]);
551 // This action needs TYPO3_CONF_VARS for full GeneralUtility::getUrl() config
552 $this->loadExtLocalconfDatabaseAndExtTables();
553 $languagePacksService = GeneralUtility::makeInstance(LanguagePackService::class);
554 $languagePacksService->updateMirrorBaseUrl();
555 $extensions = $languagePacksService->getExtensionLanguagePackDetails();
556 return new JsonResponse([
557 'success' => true,
558 'languages' => $languagePacksService->getLanguageDetails(),
559 'extensions' => $extensions,
560 'activeLanguages' => $languagePacksService->getActiveLanguages(),
561 'activeExtensions' => array_column($extensions, 'key'),
562 'html' => $view->render(),
563 ]);
564 }
565
566 /**
567 * Activate a language and any possible dependency it may have
568 *
569 * @param ServerRequestInterface $request
570 * @return ResponseInterface
571 */
572 public function languagePacksActivateLanguageAction(ServerRequestInterface $request): ResponseInterface
573 {
574 $messageQueue = new FlashMessageQueue('install');
575 $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
576 $locales = GeneralUtility::makeInstance(Locales::class);
577 $availableLanguages = $languagePackService->getAvailableLanguages();
578 $activeLanguages = $languagePackService->getActiveLanguages();
579 $iso = $request->getParsedBody()['install']['iso'];
580 $activateArray = [];
581 foreach ($availableLanguages as $availableIso => $name) {
582 if ($availableIso === $iso && !in_array($availableIso, $activeLanguages, true)) {
583 $activateArray[] = $iso;
584 $dependencies = $locales->getLocaleDependencies($availableIso);
585 if (!empty($dependencies)) {
586 foreach ($dependencies as $dependency) {
587 if (!in_array($dependency, $activeLanguages, true)) {
588 $activateArray[] = $dependency;
589 }
590 }
591 }
592 }
593 }
594 if (!empty($activateArray)) {
595 $activeLanguages = array_merge($activeLanguages, $activateArray);
596 sort($activeLanguages);
597 $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
598 $configurationManager->setLocalConfigurationValueByPath(
599 'EXTCONF/lang',
600 ['availableLanguages' => $activeLanguages]
601 );
602 $activationArray = [];
603 foreach ($activateArray as $activateIso) {
604 $activationArray[] = $availableLanguages[$activateIso] . ' (' . $activateIso . ')';
605 }
606 $messageQueue->enqueue(
607 new FlashMessage(
608 'These languages have been activated: ' . implode(', ', $activationArray)
609 )
610 );
611 } else {
612 $messageQueue->enqueue(
613 new FlashMessage('Language with ISO code "' . $iso . '" not found or already active.', '', FlashMessage::ERROR)
614 );
615 }
616 return new JsonResponse([
617 'success' => true,
618 'status' => $messageQueue,
619 ]);
620 }
621
622 /**
623 * Deactivate a language if no other active language depends on it
624 *
625 * @param ServerRequestInterface $request
626 * @return ResponseInterface
627 * @throws \RuntimeException
628 */
629 public function languagePacksDeactivateLanguageAction(ServerRequestInterface $request): ResponseInterface
630 {
631 $messageQueue = new FlashMessageQueue('install');
632 $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
633 $locales = GeneralUtility::makeInstance(Locales::class);
634 $availableLanguages = $languagePackService->getAvailableLanguages();
635 $activeLanguages = $languagePackService->getActiveLanguages();
636 $iso = $request->getParsedBody()['install']['iso'];
637 if (empty($iso)) {
638 throw new \RuntimeException('No iso code given', 1520109807);
639 }
640 $otherActiveLanguageDependencies = [];
641 foreach ($activeLanguages as $activeLanguage) {
642 if ($activeLanguage === $iso) {
643 continue;
644 }
645 $dependencies = $locales->getLocaleDependencies($activeLanguage);
646 if (in_array($iso, $dependencies, true)) {
647 $otherActiveLanguageDependencies[] = $activeLanguage;
648 }
649 }
650 if (!empty($otherActiveLanguageDependencies)) {
651 // Error: Must disable dependencies first
652 $dependentArray = [];
653 foreach ($otherActiveLanguageDependencies as $dependency) {
654 $dependentArray[] = $availableLanguages[$dependency] . ' (' . $dependency . ')';
655 }
656 $messageQueue->enqueue(
657 new FlashMessage(
658 'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" can not be deactivated. These'
659 . ' other languages depend on it and need to be deactivated before:'
660 . implode(', ', $dependentArray),
661 '',
662 FlashMessage::ERROR
663 )
664 );
665 } else {
666 if (in_array($iso, $activeLanguages, true)) {
667 // Deactivate this language
668 $newActiveLanguages = [];
669 foreach ($activeLanguages as $activeLanguage) {
670 if ($activeLanguage === $iso) {
671 continue;
672 }
673 $newActiveLanguages[] = $activeLanguage;
674 }
675 $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
676 $configurationManager->setLocalConfigurationValueByPath(
677 'EXTCONF/lang',
678 ['availableLanguages' => $newActiveLanguages]
679 );
680 $messageQueue->enqueue(
681 new FlashMessage(
682 'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" has been deactivated'
683 )
684 );
685 } else {
686 $messageQueue->enqueue(
687 new FlashMessage(
688 'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" has not been deactivated',
689 '',
690 FlashMessage::ERROR
691 )
692 );
693 }
694 }
695 return new JsonResponse([
696 'success' => true,
697 'status' => $messageQueue,
698 ]);
699 }
700
701 /**
702 * Update a pack of one extension and one language
703 *
704 * @param ServerRequestInterface $request
705 * @return ResponseInterface
706 * @throws \RuntimeException
707 */
708 public function languagePacksUpdatePackAction(ServerRequestInterface $request): ResponseInterface
709 {
710 $this->loadExtLocalconfDatabaseAndExtTables();
711 $iso = $request->getParsedBody()['install']['iso'];
712 $key = $request->getParsedBody()['install']['extension'];
713 $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
714 return new JsonResponse([
715 'success' => true,
716 'packResult' => $languagePackService->languagePackDownload($key, $iso)
717 ]);
718 }
719
720 /**
721 * Set "last updated" time in registry for fully updated language packs.
722 *
723 * @param ServerRequestInterface $request
724 * @return ResponseInterface
725 */
726 public function languagePacksUpdateIsoTimesAction(ServerRequestInterface $request): ResponseInterface
727 {
728 $isos = $request->getParsedBody()['install']['isos'];
729 $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
730 $languagePackService->setLastUpdatedIsoCode($isos);
731
732 // The cache manager is already instantiated in the install tool
733 // with some hacked settings to disable caching of extbase and fluid.
734 // We want a "fresh" object here to operate on a different cache setup.
735 // cacheManager implements SingletonInterface, so the only way to get a "fresh"
736 // instance is by circumventing makeInstance and/or the objectManager and
737 // using new directly!
738 $cacheManager = new \TYPO3\CMS\Core\Cache\CacheManager();
739 $cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
740 $cacheManager->getCache('l10n')->flush();
741
742 return new JsonResponse(['success' => true]);
743 }
744
745 /**
746 * Set 'uc' field of all backend users to empty string
747 *
748 * @return ResponseInterface
749 */
750 public function resetBackendUserUcAction(): ResponseInterface
751 {
752 GeneralUtility::makeInstance(ConnectionPool::class)
753 ->getQueryBuilderForTable('be_users')
754 ->update('be_users')
755 ->set('uc', '')
756 ->execute();
757 $messageQueue = new FlashMessageQueue('install');
758 $messageQueue->enqueue(new FlashMessage(
759 'All backend users preferences has been reseted',
760 'Reset all backend users preferences'
761 ));
762 return new JsonResponse([
763 'success' => true,
764 'status' => $messageQueue
765 ]);
766 }
767 }