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