[FEATURE] Add email address to installation process
[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 $email = $request->getParsedBody()['install']['userEmail'] ?? '';
485 $isSystemMaintainer = ((bool)$request->getParsedBody()['install']['userSystemMaintainer'] == '1') ? true : false;
486
487 $messages = new FlashMessageQueue('install');
488
489 if (strlen($username) < 1) {
490 $messages->enqueue(new FlashMessage(
491 'No valid username given.',
492 'Administrator user not created',
493 FlashMessage::ERROR
494 ));
495 } elseif ($password !== $passwordCheck) {
496 $messages->enqueue(new FlashMessage(
497 'Passwords do not match.',
498 'Administrator user not created',
499 FlashMessage::ERROR
500 ));
501 } elseif (strlen($password) < 8) {
502 $messages->enqueue(new FlashMessage(
503 'Password must be at least eight characters long.',
504 'Administrator user not created',
505 FlashMessage::ERROR
506 ));
507 } else {
508 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
509 $userExists = $connectionPool->getConnectionForTable('be_users')
510 ->count(
511 'uid',
512 'be_users',
513 ['username' => $username]
514 );
515 if ($userExists) {
516 $messages->enqueue(new FlashMessage(
517 'A user with username "' . $username . '" exists already.',
518 'Administrator user not created',
519 FlashMessage::ERROR
520 ));
521 } else {
522 $hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE');
523 $hashedPassword = $hashInstance->getHashedPassword($password);
524 $adminUserFields = [
525 'username' => $username,
526 'password' => $hashedPassword,
527 'admin' => 1,
528 'tstamp' => $GLOBALS['EXEC_TIME'],
529 'crdate' => $GLOBALS['EXEC_TIME']
530 ];
531 if (GeneralUtility::validEmail($email)) {
532 $adminUserFields['email'] = $email;
533 }
534 $connectionPool->getConnectionForTable('be_users')->insert('be_users', $adminUserFields);
535
536 if ($isSystemMaintainer) {
537
538 // Get the new admin user uid juste created
539 $newAdminUserUid = (int)$connectionPool->getConnectionForTable('be_users')->lastInsertId('be_users');
540
541 // Get the list of the existing systemMaintainer
542 $existingSystemMaintainersList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? [];
543
544 // Add the new admin user to the existing systemMaintainer list
545 $newSystemMaintainersList = $existingSystemMaintainersList;
546 $newSystemMaintainersList[] = $newAdminUserUid;
547
548 // Update the LocalConfiguration.php file with the new list
549 $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
550 $configurationManager->setLocalConfigurationValuesByPathValuePairs(
551 ['SYS/systemMaintainers' => $newSystemMaintainersList]
552 );
553 }
554
555 $messages->enqueue(new FlashMessage(
556 '',
557 'Administrator created with username "' . $username . '".'
558 ));
559 }
560 }
561 return new JsonResponse([
562 'success' => true,
563 'status' => $messages,
564 ]);
565 }
566
567 /**
568 * Entry action of language packs module gets
569 * * list of available languages with details like active or not and last update
570 * * list of loaded extensions
571 *
572 * @param ServerRequestInterface $request
573 * @return ResponseInterface
574 */
575 public function languagePacksGetDataAction(ServerRequestInterface $request): ResponseInterface
576 {
577 $view = $this->initializeStandaloneView($request, 'Maintenance/LanguagePacks.html');
578 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
579 $view->assignMultiple([
580 'languagePacksActivateLanguageToken' => $formProtection->generateToken('installTool', 'languagePacksActivateLanguage'),
581 'languagePacksDeactivateLanguageToken' => $formProtection->generateToken('installTool', 'languagePacksDeactivateLanguage'),
582 'languagePacksUpdatePackToken' => $formProtection->generateToken('installTool', 'languagePacksUpdatePack'),
583 'languagePacksUpdateIsoTimesToken' => $formProtection->generateToken('installTool', 'languagePacksUpdateIsoTimes'),
584 ]);
585 // This action needs TYPO3_CONF_VARS for full GeneralUtility::getUrl() config
586 $this->loadExtLocalconfDatabaseAndExtTables();
587 $languagePacksService = GeneralUtility::makeInstance(LanguagePackService::class);
588 $languagePacksService->updateMirrorBaseUrl();
589 $extensions = $languagePacksService->getExtensionLanguagePackDetails();
590 return new JsonResponse([
591 'success' => true,
592 'languages' => $languagePacksService->getLanguageDetails(),
593 'extensions' => $extensions,
594 'activeLanguages' => $languagePacksService->getActiveLanguages(),
595 'activeExtensions' => array_column($extensions, 'key'),
596 'html' => $view->render(),
597 ]);
598 }
599
600 /**
601 * Activate a language and any possible dependency it may have
602 *
603 * @param ServerRequestInterface $request
604 * @return ResponseInterface
605 */
606 public function languagePacksActivateLanguageAction(ServerRequestInterface $request): ResponseInterface
607 {
608 $messageQueue = new FlashMessageQueue('install');
609 $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
610 $locales = GeneralUtility::makeInstance(Locales::class);
611 $availableLanguages = $languagePackService->getAvailableLanguages();
612 $activeLanguages = $languagePackService->getActiveLanguages();
613 $iso = $request->getParsedBody()['install']['iso'];
614 $activateArray = [];
615 foreach ($availableLanguages as $availableIso => $name) {
616 if ($availableIso === $iso && !in_array($availableIso, $activeLanguages, true)) {
617 $activateArray[] = $iso;
618 $dependencies = $locales->getLocaleDependencies($availableIso);
619 if (!empty($dependencies)) {
620 foreach ($dependencies as $dependency) {
621 if (!in_array($dependency, $activeLanguages, true)) {
622 $activateArray[] = $dependency;
623 }
624 }
625 }
626 }
627 }
628 if (!empty($activateArray)) {
629 $activeLanguages = array_merge($activeLanguages, $activateArray);
630 sort($activeLanguages);
631 $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
632 $configurationManager->setLocalConfigurationValueByPath(
633 'EXTCONF/lang',
634 ['availableLanguages' => $activeLanguages]
635 );
636 $activationArray = [];
637 foreach ($activateArray as $activateIso) {
638 $activationArray[] = $availableLanguages[$activateIso] . ' (' . $activateIso . ')';
639 }
640 $messageQueue->enqueue(
641 new FlashMessage(
642 'These languages have been activated: ' . implode(', ', $activationArray)
643 )
644 );
645 } else {
646 $messageQueue->enqueue(
647 new FlashMessage('Language with ISO code "' . $iso . '" not found or already active.', '', FlashMessage::ERROR)
648 );
649 }
650 return new JsonResponse([
651 'success' => true,
652 'status' => $messageQueue,
653 ]);
654 }
655
656 /**
657 * Deactivate a language if no other active language depends on it
658 *
659 * @param ServerRequestInterface $request
660 * @return ResponseInterface
661 * @throws \RuntimeException
662 */
663 public function languagePacksDeactivateLanguageAction(ServerRequestInterface $request): ResponseInterface
664 {
665 $messageQueue = new FlashMessageQueue('install');
666 $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
667 $locales = GeneralUtility::makeInstance(Locales::class);
668 $availableLanguages = $languagePackService->getAvailableLanguages();
669 $activeLanguages = $languagePackService->getActiveLanguages();
670 $iso = $request->getParsedBody()['install']['iso'];
671 if (empty($iso)) {
672 throw new \RuntimeException('No iso code given', 1520109807);
673 }
674 $otherActiveLanguageDependencies = [];
675 foreach ($activeLanguages as $activeLanguage) {
676 if ($activeLanguage === $iso) {
677 continue;
678 }
679 $dependencies = $locales->getLocaleDependencies($activeLanguage);
680 if (in_array($iso, $dependencies, true)) {
681 $otherActiveLanguageDependencies[] = $activeLanguage;
682 }
683 }
684 if (!empty($otherActiveLanguageDependencies)) {
685 // Error: Must disable dependencies first
686 $dependentArray = [];
687 foreach ($otherActiveLanguageDependencies as $dependency) {
688 $dependentArray[] = $availableLanguages[$dependency] . ' (' . $dependency . ')';
689 }
690 $messageQueue->enqueue(
691 new FlashMessage(
692 'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" can not be deactivated. These'
693 . ' other languages depend on it and need to be deactivated before:'
694 . implode(', ', $dependentArray),
695 '',
696 FlashMessage::ERROR
697 )
698 );
699 } else {
700 if (in_array($iso, $activeLanguages, true)) {
701 // Deactivate this language
702 $newActiveLanguages = [];
703 foreach ($activeLanguages as $activeLanguage) {
704 if ($activeLanguage === $iso) {
705 continue;
706 }
707 $newActiveLanguages[] = $activeLanguage;
708 }
709 $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
710 $configurationManager->setLocalConfigurationValueByPath(
711 'EXTCONF/lang',
712 ['availableLanguages' => $newActiveLanguages]
713 );
714 $messageQueue->enqueue(
715 new FlashMessage(
716 'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" has been deactivated'
717 )
718 );
719 } else {
720 $messageQueue->enqueue(
721 new FlashMessage(
722 'Language "' . $availableLanguages[$iso] . ' (' . $iso . ')" has not been deactivated',
723 '',
724 FlashMessage::ERROR
725 )
726 );
727 }
728 }
729 return new JsonResponse([
730 'success' => true,
731 'status' => $messageQueue,
732 ]);
733 }
734
735 /**
736 * Update a pack of one extension and one language
737 *
738 * @param ServerRequestInterface $request
739 * @return ResponseInterface
740 * @throws \RuntimeException
741 */
742 public function languagePacksUpdatePackAction(ServerRequestInterface $request): ResponseInterface
743 {
744 $this->loadExtLocalconfDatabaseAndExtTables();
745 $iso = $request->getParsedBody()['install']['iso'];
746 $key = $request->getParsedBody()['install']['extension'];
747 $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
748 return new JsonResponse([
749 'success' => true,
750 'packResult' => $languagePackService->languagePackDownload($key, $iso)
751 ]);
752 }
753
754 /**
755 * Set "last updated" time in registry for fully updated language packs.
756 *
757 * @param ServerRequestInterface $request
758 * @return ResponseInterface
759 */
760 public function languagePacksUpdateIsoTimesAction(ServerRequestInterface $request): ResponseInterface
761 {
762 $isos = $request->getParsedBody()['install']['isos'];
763 $languagePackService = GeneralUtility::makeInstance(LanguagePackService::class);
764 $languagePackService->setLastUpdatedIsoCode($isos);
765
766 // The cache manager is already instantiated in the install tool
767 // with some hacked settings to disable caching of extbase and fluid.
768 // We want a "fresh" object here to operate on a different cache setup.
769 // cacheManager implements SingletonInterface, so the only way to get a "fresh"
770 // instance is by circumventing makeInstance and/or the objectManager and
771 // using new directly!
772 $cacheManager = new \TYPO3\CMS\Core\Cache\CacheManager();
773 $cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
774 $cacheManager->getCache('l10n')->flush();
775
776 return new JsonResponse(['success' => true]);
777 }
778
779 /**
780 * Set 'uc' field of all backend users to empty string
781 *
782 * @return ResponseInterface
783 */
784 public function resetBackendUserUcAction(): ResponseInterface
785 {
786 GeneralUtility::makeInstance(ConnectionPool::class)
787 ->getQueryBuilderForTable('be_users')
788 ->update('be_users')
789 ->set('uc', '')
790 ->execute();
791 $messageQueue = new FlashMessageQueue('install');
792 $messageQueue->enqueue(new FlashMessage(
793 'Preferences of all backend users have been reset',
794 'Reset preferences of all backend users'
795 ));
796 return new JsonResponse([
797 'success' => true,
798 'status' => $messageQueue
799 ]);
800 }
801 }