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