33af40e22ac785299f23ff9379b7c7bb36eaf127
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Controller / InstallerController.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 Doctrine\DBAL\DBALException;
19 use Doctrine\DBAL\DriverManager;
20 use Psr\Container\ContainerInterface;
21 use Psr\Http\Message\ResponseInterface;
22 use Psr\Http\Message\ServerRequestInterface;
23 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
24 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
25 use TYPO3\CMS\Core\Configuration\SiteConfiguration;
26 use TYPO3\CMS\Core\Core\Environment;
27 use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash;
28 use TYPO3\CMS\Core\Crypto\PasswordHashing\BcryptPasswordHash;
29 use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface;
30 use TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash;
31 use TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash;
32 use TYPO3\CMS\Core\Crypto\Random;
33 use TYPO3\CMS\Core\Database\Connection;
34 use TYPO3\CMS\Core\Database\ConnectionPool;
35 use TYPO3\CMS\Core\Database\Schema\Exception\StatementException;
36 use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
37 use TYPO3\CMS\Core\Database\Schema\SqlReader;
38 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
39 use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
40 use TYPO3\CMS\Core\Http\HtmlResponse;
41 use TYPO3\CMS\Core\Http\JsonResponse;
42 use TYPO3\CMS\Core\Http\NormalizedParams;
43 use TYPO3\CMS\Core\Messaging\FlashMessage;
44 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
45 use TYPO3\CMS\Core\Package\PackageInterface;
46 use TYPO3\CMS\Core\Package\PackageManager;
47 use TYPO3\CMS\Core\Registry;
48 use TYPO3\CMS\Core\Utility\GeneralUtility;
49 use TYPO3\CMS\Fluid\View\StandaloneView;
50 use TYPO3\CMS\Install\Configuration\FeatureManager;
51 use TYPO3\CMS\Install\FolderStructure\DefaultFactory;
52 use TYPO3\CMS\Install\Service\EnableFileService;
53 use TYPO3\CMS\Install\Service\Exception\ConfigurationChangedException;
54 use TYPO3\CMS\Install\Service\LateBootService;
55 use TYPO3\CMS\Install\Service\SilentConfigurationUpgradeService;
56 use TYPO3\CMS\Install\SystemEnvironment\Check;
57 use TYPO3\CMS\Install\SystemEnvironment\SetupCheck;
58
59 /**
60 * Install step controller, dispatcher class of step actions.
61 * @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
62 */
63 class InstallerController
64 {
65 /**
66 * Init action loads <head> with JS initiating further stuff
67 *
68 * @return ResponseInterface
69 */
70 public function initAction(): ResponseInterface
71 {
72 $bust = $GLOBALS['EXEC_TIME'];
73 if (!GeneralUtility::getApplicationContext()->isDevelopment()) {
74 $bust = GeneralUtility::hmac(TYPO3_version . Environment::getProjectPath());
75 }
76 $view = $this->initializeStandaloneView('Installer/Init.html');
77 $view->assign('bust', $bust);
78 return new HtmlResponse(
79 $view->render(),
80 200,
81 [
82 'Cache-Control' => 'no-cache, must-revalidate',
83 'Pragma' => 'no-cache'
84 ]
85 );
86 }
87
88 /**
89 * Main layout with progress bar, header
90 *
91 * @return ResponseInterface
92 */
93 public function mainLayoutAction(): ResponseInterface
94 {
95 $view = $this->initializeStandaloneView('Installer/MainLayout.html');
96 return new JsonResponse([
97 'success' => true,
98 'html' => $view->render(),
99 ]);
100 }
101
102 /**
103 * Render "FIRST_INSTALL file need to exist" view
104 *
105 * @return ResponseInterface
106 */
107 public function showInstallerNotAvailableAction(): ResponseInterface
108 {
109 $view = $this->initializeStandaloneView('Installer/ShowInstallerNotAvailable.html');
110 return new JsonResponse([
111 'success' => true,
112 'html' => $view->render(),
113 ]);
114 }
115
116 /**
117 * Check if "environment and folders" should be shown
118 *
119 * @return ResponseInterface
120 */
121 public function checkEnvironmentAndFoldersAction(): ResponseInterface
122 {
123 return new JsonResponse([
124 'success' => @is_file(GeneralUtility::makeInstance(ConfigurationManager::class)->getLocalConfigurationFileLocation()),
125 ]);
126 }
127
128 /**
129 * Render "environment and folders"
130 *
131 * @return ResponseInterface
132 */
133 public function showEnvironmentAndFoldersAction(): ResponseInterface
134 {
135 $view = $this->initializeStandaloneView('Installer/ShowEnvironmentAndFolders.html');
136 $systemCheckMessageQueue = new FlashMessageQueue('install');
137 $checkMessages = (new Check())->getStatus();
138 foreach ($checkMessages as $message) {
139 $systemCheckMessageQueue->enqueue($message);
140 }
141 $setupCheckMessages = (new SetupCheck())->getStatus();
142 foreach ($setupCheckMessages as $message) {
143 $systemCheckMessageQueue->enqueue($message);
144 }
145 $folderStructureFactory = GeneralUtility::makeInstance(DefaultFactory::class);
146 $structureFacade = $folderStructureFactory->getStructure();
147 $structureMessageQueue = $structureFacade->getStatus();
148 return new JsonResponse([
149 'success' => true,
150 'html' => $view->render(),
151 'environmentStatusErrors' => $systemCheckMessageQueue->getAllMessages(FlashMessage::ERROR),
152 'environmentStatusWarnings' => $systemCheckMessageQueue->getAllMessages(FlashMessage::WARNING),
153 'structureErrors' => $structureMessageQueue->getAllMessages(FlashMessage::ERROR),
154 ]);
155 }
156
157 /**
158 * Create main folder layout, LocalConfiguration, PackageStates
159 *
160 * @return ResponseInterface
161 */
162 public function executeEnvironmentAndFoldersAction(): ResponseInterface
163 {
164 $folderStructureFactory = GeneralUtility::makeInstance(DefaultFactory::class);
165 $structureFacade = $folderStructureFactory->getStructure();
166 $structureFixMessageQueue = $structureFacade->fix();
167 $errorsFromStructure = $structureFixMessageQueue->getAllMessages(FlashMessage::ERROR);
168
169 if (@is_dir(Environment::getLegacyConfigPath())) {
170 $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
171 $configurationManager->createLocalConfigurationFromFactoryConfiguration();
172
173 // Create a PackageStates.php with all packages activated marked as "part of factory default"
174 if (!file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php')) {
175 $packageManager = GeneralUtility::makeInstance(PackageManager::class);
176 $packages = $packageManager->getAvailablePackages();
177 foreach ($packages as $package) {
178 if ($package instanceof PackageInterface
179 && $package->isPartOfFactoryDefault()
180 ) {
181 $packageManager->activatePackage($package->getPackageKey());
182 }
183 }
184 $packageManager->forceSortAndSavePackageStates();
185 }
186 $extensionConfiguration = new ExtensionConfiguration();
187 $extensionConfiguration->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions();
188
189 return new JsonResponse([
190 'success' => true,
191 ]);
192 }
193 return new JsonResponse([
194 'success' => false,
195 'status' => $errorsFromStructure,
196 ]);
197 }
198
199 /**
200 * Check if trusted hosts pattern needs to be adjusted
201 *
202 * @return ResponseInterface
203 */
204 public function checkTrustedHostsPatternAction(): ResponseInterface
205 {
206 return new JsonResponse([
207 'success' => GeneralUtility::hostHeaderValueMatchesTrustedHostsPattern($_SERVER['HTTP_HOST']),
208 ]);
209 }
210
211 /**
212 * Adjust trusted hosts pattern to '.*' if it does not match yet
213 *
214 * @return ResponseInterface
215 */
216 public function executeAdjustTrustedHostsPatternAction(): ResponseInterface
217 {
218 if (!GeneralUtility::hostHeaderValueMatchesTrustedHostsPattern($_SERVER['HTTP_HOST'])) {
219 $configurationManager = new ConfigurationManager();
220 $configurationManager->setLocalConfigurationValueByPath('SYS/trustedHostsPattern', '.*');
221 }
222 return new JsonResponse([
223 'success' => true,
224 ]);
225 }
226
227 /**
228 * Execute silent configuration update. May be called multiple times until success = true is returned.
229 *
230 * @return ResponseInterface success = true if no change has been done
231 */
232 public function executeSilentConfigurationUpdateAction(): ResponseInterface
233 {
234 $silentUpdate = new SilentConfigurationUpgradeService();
235 $success = true;
236 try {
237 $silentUpdate->execute();
238 } catch (ConfigurationChangedException $e) {
239 $success = false;
240 }
241 return new JsonResponse([
242 'success' => $success,
243 ]);
244 }
245
246 /**
247 * Check if database connect step needs to be shown
248 *
249 * @return ResponseInterface
250 */
251 public function checkDatabaseConnectAction(): ResponseInterface
252 {
253 return new JsonResponse([
254 'success' => $this->isDatabaseConnectSuccessful() && $this->isDatabaseConfigurationComplete(),
255 ]);
256 }
257
258 /**
259 * Show database connect step
260 *
261 * @return ResponseInterface
262 */
263 public function showDatabaseConnectAction(): ResponseInterface
264 {
265 $view = $this->initializeStandaloneView('Installer/ShowDatabaseConnect.html');
266 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
267 $hasAtLeastOneOption = false;
268 $activeAvailableOption = '';
269 if (extension_loaded('mysqli')) {
270 $hasAtLeastOneOption = true;
271 $view->assign('hasMysqliManualConfiguration', true);
272 $mysqliManualConfigurationOptions = [
273 'username' => $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'] ?? '',
274 'password' => $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'] ?? '',
275 'port' => $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['port'] ?? 3306,
276 ];
277 $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'] ?? '127.0.0.1';
278 if ($host === 'localhost') {
279 $host = '127.0.0.1';
280 }
281 $mysqliManualConfigurationOptions['host'] = $host;
282 $view->assign('mysqliManualConfigurationOptions', $mysqliManualConfigurationOptions);
283 $activeAvailableOption = 'mysqliManualConfiguration';
284
285 $view->assign('hasMysqliSocketManualConfiguration', true);
286 $view->assign(
287 'mysqliSocketManualConfigurationOptions',
288 [
289 'username' => $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'] ?? '',
290 'password' => $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'] ?? '',
291 'socket' => $this->getDatabaseConfiguredMysqliSocket(),
292 ]
293 );
294 if ($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driver'] === 'mysqli'
295 && $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'] === 'localhost') {
296 $activeAvailableOption = 'mysqliSocketManualConfiguration';
297 }
298 }
299 if (extension_loaded('pdo_pgsql')) {
300 $hasAtLeastOneOption = true;
301 $view->assign('hasPostgresManualConfiguration', true);
302 $view->assign(
303 'postgresManualConfigurationOptions',
304 [
305 'username' => $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'] ?? '',
306 'password' => $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'] ?? '',
307 'host' => $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'] ?? '127.0.0.1',
308 'port' => $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['port'] ?? 5432,
309 'database' => $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname'] ?? '',
310 ]
311 );
312 if ($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driver'] === 'pdo_pgsql') {
313 $activeAvailableOption = 'postgresManualConfiguration';
314 }
315 }
316 if (extension_loaded('pdo_sqlite')) {
317 $hasAtLeastOneOption = true;
318 $view->assign('hasSqliteManualConfiguration', true);
319 $view->assign(
320 'sqliteManualConfigurationOptions',
321 []
322 );
323 if ($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driver'] === 'pdo_sqlite') {
324 $activeAvailableOption = 'sqliteManualConfiguration';
325 }
326 }
327
328 if (!empty($this->getDatabaseConfigurationFromEnvironment())) {
329 $hasAtLeastOneOption = true;
330 $activeAvailableOption = 'configurationFromEnvironment';
331 $view->assign('hasConfigurationFromEnvironment', true);
332 }
333
334 $view->assignMultiple([
335 'hasAtLeastOneOption' => $hasAtLeastOneOption,
336 'activeAvailableOption' => $activeAvailableOption,
337 'executeDatabaseConnectToken' => $formProtection->generateToken('installTool', 'executeDatabaseConnect'),
338 ]);
339
340 return new JsonResponse([
341 'success' => true,
342 'html' => $view->render(),
343 ]);
344 }
345
346 /**
347 * Test database connect data
348 *
349 * @param ServerRequestInterface $request
350 * @return ResponseInterface
351 */
352 public function executeDatabaseConnectAction(ServerRequestInterface $request): ResponseInterface
353 {
354 $messages = [];
355 $postValues = $request->getParsedBody()['install']['values'];
356 $defaultConnectionSettings = [];
357
358 if ($postValues['availableSet'] === 'configurationFromEnvironment') {
359 $defaultConnectionSettings = $this->getDatabaseConfigurationFromEnvironment();
360 } else {
361 if (isset($postValues['driver'])) {
362 $validDrivers = [
363 'mysqli',
364 'pdo_mysql',
365 'pdo_pgsql',
366 'mssql',
367 'pdo_sqlite',
368 ];
369 if (in_array($postValues['driver'], $validDrivers, true)) {
370 $defaultConnectionSettings['driver'] = $postValues['driver'];
371 } else {
372 $messages[] = new FlashMessage(
373 'Given driver must be one of ' . implode(', ', $validDrivers),
374 'Database driver unknown',
375 FlashMessage::ERROR
376 );
377 }
378 }
379 if (isset($postValues['username'])) {
380 $value = $postValues['username'];
381 if (strlen($value) <= 50) {
382 $defaultConnectionSettings['user'] = $value;
383 } else {
384 $messages[] = new FlashMessage(
385 'Given username must be shorter than fifty characters.',
386 'Database username not valid',
387 FlashMessage::ERROR
388 );
389 }
390 }
391 if (isset($postValues['password'])) {
392 $defaultConnectionSettings['password'] = $postValues['password'];
393 }
394 if (isset($postValues['host'])) {
395 $value = $postValues['host'];
396 if (preg_match('/^[a-zA-Z0-9_\\.-]+(:.+)?$/', $value) && strlen($value) <= 255) {
397 $defaultConnectionSettings['host'] = $value;
398 } else {
399 $messages[] = new FlashMessage(
400 'Given host is not alphanumeric (a-z, A-Z, 0-9 or _-.:) or longer than 255 characters.',
401 'Database host not valid',
402 FlashMessage::ERROR
403 );
404 }
405 }
406 if (isset($postValues['port']) && $postValues['host'] !== 'localhost') {
407 $value = $postValues['port'];
408 if (preg_match('/^[0-9]+(:.+)?$/', $value) && $value > 0 && $value <= 65535) {
409 $defaultConnectionSettings['port'] = (int)$value;
410 } else {
411 $messages[] = new FlashMessage(
412 'Given port is not numeric or within range 1 to 65535.',
413 'Database port not valid',
414 FlashMessage::ERROR
415 );
416 }
417 }
418 if (isset($postValues['socket']) && $postValues['socket'] !== '') {
419 if (@file_exists($postValues['socket'])) {
420 $defaultConnectionSettings['unix_socket'] = $postValues['socket'];
421 } else {
422 $messages[] = new FlashMessage(
423 'Given socket location does not exist on server.',
424 'Socket does not exist',
425 FlashMessage::ERROR
426 );
427 }
428 }
429 if (isset($postValues['database'])) {
430 $value = $postValues['database'];
431 if (strlen($value) <= 50) {
432 $defaultConnectionSettings['dbname'] = $value;
433 } else {
434 $messages[] = new FlashMessage(
435 'Given database name must be shorter than fifty characters.',
436 'Database name not valid',
437 FlashMessage::ERROR
438 );
439 }
440 }
441 // For sqlite a db path is automatically calculated
442 if (isset($postValues['driver']) && $postValues['driver'] === 'pdo_sqlite') {
443 $dbFilename = '/cms-' . (new Random())->generateRandomHexString(8) . '.sqlite';
444 // If the var/ folder exists outside of document root, put it into var/sqlite/
445 // Otherwise simply into typo3conf/
446 if (Environment::getProjectPath() !== Environment::getPublicPath()) {
447 GeneralUtility::mkdir_deep(Environment::getVarPath() . '/sqlite');
448 $defaultConnectionSettings['path'] = Environment::getVarPath() . '/sqlite' . $dbFilename;
449 } else {
450 $defaultConnectionSettings['path'] = Environment::getConfigPath() . $dbFilename;
451 }
452 }
453 // For mysql, set utf8mb4 as default charset
454 if (isset($postValues['driver']) && in_array($postValues['driver'], ['mysqli', 'pdo_mysql'])) {
455 $defaultConnectionSettings['charset'] = 'utf8mb4';
456 $defaultConnectionSettings['tableoptions'] = [
457 'charset' => 'utf8mb4',
458 'collate' => 'utf8mb4_unicode_ci',
459 ];
460 }
461 }
462
463 $success = false;
464 if (!empty($defaultConnectionSettings)) {
465 // Test connection settings and write to config if connect is successful
466 try {
467 $connectionParams = $defaultConnectionSettings;
468 $connectionParams['wrapperClass'] = Connection::class;
469 if (!isset($connectionParams['charset'])) {
470 // utf-8 as default for non mysql
471 $connectionParams['charset'] = 'utf-8';
472 }
473 DriverManager::getConnection($connectionParams)->ping();
474 $success = true;
475 } catch (DBALException $e) {
476 $messages[] = new FlashMessage(
477 'Connecting to the database with given settings failed: ' . $e->getMessage(),
478 'Database connect not successful',
479 FlashMessage::ERROR
480 );
481 }
482 $localConfigurationPathValuePairs = [];
483 foreach ($defaultConnectionSettings as $settingsName => $value) {
484 $localConfigurationPathValuePairs['DB/Connections/Default/' . $settingsName] = $value;
485 }
486 $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
487 // Remove full default connection array
488 $configurationManager->removeLocalConfigurationKeysByPath(['DB/Connections/Default']);
489 // Write new values
490 $configurationManager->setLocalConfigurationValuesByPathValuePairs($localConfigurationPathValuePairs);
491 }
492
493 return new JsonResponse([
494 'success' => $success,
495 'status' => $messages,
496 ]);
497 }
498
499 /**
500 * Check if a database needs to be selected
501 *
502 * @return ResponseInterface
503 */
504 public function checkDatabaseSelectAction(): ResponseInterface
505 {
506 $success = false;
507 if ((string)$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname'] !== ''
508 || (string)$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['path'] !== ''
509 ) {
510 try {
511 $success = GeneralUtility::makeInstance(ConnectionPool::class)
512 ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME)
513 ->ping();
514 } catch (DBALException $e) {
515 }
516 }
517 return new JsonResponse([
518 'success' => $success,
519 ]);
520 }
521
522 /**
523 * Render "select a database"
524 *
525 * @return ResponseInterface
526 */
527 public function showDatabaseSelectAction(): ResponseInterface
528 {
529 $view = $this->initializeStandaloneView('Installer/ShowDatabaseSelect.html');
530 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
531 $errors = [];
532 try {
533 $view->assign('databaseList', $this->getDatabaseList());
534 } catch (\Exception $exception) {
535 $errors[] = $exception->getMessage();
536 }
537 $view->assignMultiple([
538 'errors' => $errors,
539 'executeDatabaseSelectToken' => $formProtection->generateToken('installTool', 'executeDatabaseSelect'),
540 ]);
541 return new JsonResponse([
542 'success' => true,
543 'html' => $view->render(),
544 ]);
545 }
546
547 /**
548 * Select / create and test a database
549 *
550 * @param ServerRequestInterface $request
551 * @return ResponseInterface
552 */
553 public function executeDatabaseSelectAction(ServerRequestInterface $request): ResponseInterface
554 {
555 $postValues = $request->getParsedBody()['install']['values'];
556 if ($postValues['type'] === 'new') {
557 $status = $this->createNewDatabase($postValues['new']);
558 if ($status->getSeverity() === FlashMessage::ERROR) {
559 return new JsonResponse([
560 'success' => false,
561 'status' => [$status],
562 ]);
563 }
564 } elseif ($postValues['type'] === 'existing' && !empty($postValues['existing'])) {
565 $status = $this->checkExistingDatabase($postValues['existing']);
566 if ($status->getSeverity() === FlashMessage::ERROR) {
567 return new JsonResponse([
568 'success' => false,
569 'status' => [$status],
570 ]);
571 }
572 } else {
573 return new JsonResponse([
574 'success' => false,
575 'status' => [
576 new FlashMessage(
577 'You must select a database.',
578 'No Database selected',
579 FlashMessage::ERROR
580 ),
581 ],
582 ]);
583 }
584 return new JsonResponse([
585 'success' => true,
586 ]);
587 }
588
589 /**
590 * Check if initial data needs to be imported
591 *
592 * @return ResponseInterface
593 */
594 public function checkDatabaseDataAction(): ResponseInterface
595 {
596 $existingTables = GeneralUtility::makeInstance(ConnectionPool::class)
597 ->getConnectionByName('Default')
598 ->getSchemaManager()
599 ->listTableNames();
600 return new JsonResponse([
601 'success' => !empty($existingTables),
602 ]);
603 }
604
605 /**
606 * Render "import initial data"
607 *
608 * @return ResponseInterface
609 */
610 public function showDatabaseDataAction(): ResponseInterface
611 {
612 $view = $this->initializeStandaloneView('Installer/ShowDatabaseData.html');
613 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
614 $view->assignMultiple([
615 'siteName' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
616 'executeDatabaseDataToken' => $formProtection->generateToken('installTool', 'executeDatabaseData'),
617 ]);
618 return new JsonResponse([
619 'success' => true,
620 'html' => $view->render(),
621 ]);
622 }
623
624 /**
625 * Create main db layout
626 *
627 * @param ServerRequestInterface $request
628 * @return ResponseInterface
629 */
630 public function executeDatabaseDataAction(ServerRequestInterface $request): ResponseInterface
631 {
632 $messages = [];
633 $configurationManager = new ConfigurationManager();
634 $postValues = $request->getParsedBody()['install']['values'];
635 $username = (string)$postValues['username'] !== '' ? $postValues['username'] : 'admin';
636 // Check password and return early if not good enough
637 $password = $postValues['password'];
638 if (empty($password) || strlen($password) < 8) {
639 $messages[] = new FlashMessage(
640 'You are setting an important password here! It gives an attacker full control over your instance if cracked.'
641 . ' It should be strong (include lower and upper case characters, special characters and numbers) and must be at least eight characters long.',
642 'Administrator password not secure enough!',
643 FlashMessage::ERROR
644 );
645 return new JsonResponse([
646 'success' => false,
647 'status' => $messages,
648 ]);
649 }
650 // Set site name
651 if (!empty($postValues['sitename'])) {
652 $configurationManager->setLocalConfigurationValueByPath('SYS/sitename', $postValues['sitename']);
653 }
654 try {
655 $messages = $this->importDatabaseData();
656 if (!empty($messages)) {
657 return new JsonResponse([
658 'success' => false,
659 'status' => $messages,
660 ]);
661 }
662 } catch (StatementException $exception) {
663 $messages[] = new FlashMessage(
664 'Error detected in SQL statement:' . LF . $exception->getMessage(),
665 'Import of database data could not be performed',
666 FlashMessage::ERROR
667 );
668 return new JsonResponse([
669 'success' => false,
670 'status' => $messages,
671 ]);
672 }
673 // Insert admin user
674 $adminUserFields = [
675 'username' => $username,
676 'password' => $this->getHashedPassword($password),
677 'admin' => 1,
678 'tstamp' => $GLOBALS['EXEC_TIME'],
679 'crdate' => $GLOBALS['EXEC_TIME']
680 ];
681 $databaseConnection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users');
682 try {
683 $databaseConnection->insert('be_users', $adminUserFields);
684 $adminUserUid = (int)$databaseConnection->lastInsertId('be_users');
685 } catch (DBALException $exception) {
686 $messages[] = new FlashMessage(
687 'The administrator account could not be created. The following error occurred:' . LF
688 . $exception->getPrevious()->getMessage(),
689 'Administrator account not created!',
690 FlashMessage::ERROR
691 );
692 return new JsonResponse([
693 'success' => false,
694 'status' => $messages,
695 ]);
696 }
697 // Set password as install tool password, add admin user to system maintainers
698 $configurationManager->setLocalConfigurationValuesByPathValuePairs([
699 'BE/installToolPassword' => $this->getHashedPassword($password),
700 'SYS/systemMaintainers' => [$adminUserUid]
701 ]);
702 return new JsonResponse([
703 'success' => true,
704 'status' => $messages,
705 ]);
706 }
707
708 /**
709 * Show last "create empty site / install distribution"
710 *
711 * @return ResponseInterface
712 */
713 public function showDefaultConfigurationAction(): ResponseInterface
714 {
715 $view = $this->initializeStandaloneView('Installer/ShowDefaultConfiguration.html');
716 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
717 $view->assignMultiple([
718 'composerMode' => Environment::isComposerMode(),
719 'executeDefaultConfigurationToken' => $formProtection->generateToken('installTool', 'executeDefaultConfiguration'),
720 ]);
721 return new JsonResponse([
722 'success' => true,
723 'html' => $view->render(),
724 ]);
725 }
726
727 /**
728 * Last step execution: clean up, remove FIRST_INSTALL file, ...
729 *
730 * @param ServerRequestInterface $request
731 * @return ResponseInterface
732 */
733 public function executeDefaultConfigurationAction(ServerRequestInterface $request): ResponseInterface
734 {
735 $featureManager = new FeatureManager();
736 // Get best matching configuration presets
737 $configurationValues = $featureManager->getBestMatchingConfigurationForAllFeatures();
738 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
739
740 // Let the admin user redirect to the distributions page on first login
741 switch ($request->getParsedBody()['install']['values']['sitesetup']) {
742 // Update the admin backend user to show the distribution management on login
743 case 'loaddistribution':
744 $adminUserFirstLogin = [
745 'startModuleOnFirstLogin' => 'tools_ExtensionmanagerExtensionmanager->tx_extensionmanager_tools_extensionmanagerextensionmanager%5Baction%5D=distributions&tx_extensionmanager_tools_extensionmanagerextensionmanager%5Bcontroller%5D=List',
746 'ucSetByInstallTool' => '1',
747 ];
748 $connectionPool->getConnectionForTable('be_users')->update(
749 'be_users',
750 ['uc' => serialize($adminUserFirstLogin)],
751 ['admin' => 1]
752 );
753 break;
754
755 // Create a page with UID 1 and PID1 and fluid_styled_content for page TS config, respect ownership
756 case 'createsite':
757 $databaseConnectionForPages = $connectionPool->getConnectionForTable('pages');
758 $databaseConnectionForPages->insert(
759 'pages',
760 [
761 'pid' => 0,
762 'crdate' => time(),
763 'cruser_id' => 1,
764 'tstamp' => time(),
765 'title' => 'Home',
766 'slug' => '/',
767 'doktype' => 1,
768 'is_siteroot' => 1,
769 'perms_userid' => 1,
770 'perms_groupid' => 1,
771 'perms_user' => 31,
772 'perms_group' => 31,
773 'perms_everybody' => 1
774 ]
775 );
776 $pageUid = $databaseConnectionForPages->lastInsertId('pages');
777
778 // add a root sys_template with fluid_styled_content and a default PAGE typoscript snippet
779 $connectionPool->getConnectionForTable('sys_template')->insert(
780 'sys_template',
781 [
782 'pid' => $pageUid,
783 'crdate' => time(),
784 'cruser_id' => 1,
785 'tstamp' => time(),
786 'title' => 'Main TypoScript Rendering',
787 'sitetitle' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
788 'root' => 1,
789 'clear' => 1,
790 'include_static_file' => 'EXT:fluid_styled_content/Configuration/TypoScript/,EXT:fluid_styled_content/Configuration/TypoScript/Styling/',
791 'constants' => '',
792 'config' => 'page = PAGE
793 page.10 = TEXT
794 page.10.value (
795 <div style="width: 800px; margin: 15% auto;">
796 <div style="width: 300px;">
797 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 42"><path d="M60.2 14.4v27h-3.8v-27h-6.7v-3.3h17.1v3.3h-6.6zm20.2 12.9v14h-3.9v-14l-7.7-16.2h4.1l5.7 12.2 5.7-12.2h3.9l-7.8 16.2zm19.5 2.6h-3.6v11.4h-3.8V11.1s3.7-.3 7.3-.3c6.6 0 8.5 4.1 8.5 9.4 0 6.5-2.3 9.7-8.4 9.7m.4-16c-2.4 0-4.1.3-4.1.3v12.6h4.1c2.4 0 4.1-1.6 4.1-6.3 0-4.4-1-6.6-4.1-6.6m21.5 27.7c-7.1 0-9-5.2-9-15.8 0-10.2 1.9-15.1 9-15.1s9 4.9 9 15.1c.1 10.6-1.8 15.8-9 15.8m0-27.7c-3.9 0-5.2 2.6-5.2 12.1 0 9.3 1.3 12.4 5.2 12.4 3.9 0 5.2-3.1 5.2-12.4 0-9.4-1.3-12.1-5.2-12.1m19.9 27.7c-2.1 0-5.3-.6-5.7-.7v-3.1c1 .2 3.7.7 5.6.7 2.2 0 3.6-1.9 3.6-5.2 0-3.9-.6-6-3.7-6H138V24h3.1c3.5 0 3.7-3.6 3.7-5.3 0-3.4-1.1-4.8-3.2-4.8-1.9 0-4.1.5-5.3.7v-3.2c.5-.1 3-.7 5.2-.7 4.4 0 7 1.9 7 8.3 0 2.9-1 5.5-3.3 6.3 2.6.2 3.8 3.1 3.8 7.3 0 6.6-2.5 9-7.3 9"/><path fill="#FF8700" d="M31.7 28.8c-.6.2-1.1.2-1.7.2-5.2 0-12.9-18.2-12.9-24.3 0-2.2.5-3 1.3-3.6C12 1.9 4.3 4.2 1.9 7.2 1.3 8 1 9.1 1 10.6c0 9.5 10.1 31 17.3 31 3.3 0 8.8-5.4 13.4-12.8M28.4.5c6.6 0 13.2 1.1 13.2 4.8 0 7.6-4.8 16.7-7.2 16.7-4.4 0-9.9-12.1-9.9-18.2C24.5 1 25.6.5 28.4.5"/></svg>
798 </div>
799 <h4 style="font-family: sans-serif;">Welcome to a default website made with <a href="https://typo3.org">TYPO3</a></h4>
800 </div>
801 )
802 page.100 = CONTENT
803 page.100 {
804 table = tt_content
805 select {
806 orderBy = sorting
807 where = {#colPos}=0
808 }
809 }
810 ',
811 'description' => 'This is an Empty Site Package TypoScript template.
812
813 For each website you need a TypoScript template on the main page of your website (on the top level). For better maintenance all TypoScript should be extracted into external files via @import \'EXT:site_myproject/Configuration/TypoScript/setup.typoscript\''
814 ]
815 );
816
817 $this->createSiteConfiguration('main', (int)$pageUid, $request);
818 break;
819 }
820
821 // Mark upgrade wizards as done
822 $this->loadExtLocalconfDatabaseAndExtTables();
823 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'])) {
824 $registry = GeneralUtility::makeInstance(Registry::class);
825 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'] as $updateClassName) {
826 $registry->set('installUpdate', $updateClassName, 1);
827 }
828 }
829
830 $configurationManager = new ConfigurationManager();
831 $configurationManager->setLocalConfigurationValuesByPathValuePairs($configurationValues);
832
833 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
834 $formProtection->clean();
835
836 EnableFileService::removeFirstInstallFile();
837
838 return new JsonResponse([
839 'success' => true,
840 'redirect' => GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir . 'index.php',
841 ]);
842 }
843
844 /**
845 * Helper method to initialize a standalone view instance.
846 *
847 * @param string $templatePath
848 * @return StandaloneView
849 * @internal param string $template
850 */
851 protected function initializeStandaloneView(string $templatePath): StandaloneView
852 {
853 $viewRootPath = GeneralUtility::getFileAbsFileName('EXT:install/Resources/Private/');
854 $view = GeneralUtility::makeInstance(StandaloneView::class);
855 $view->getRequest()->setControllerExtensionName('Install');
856 $view->setTemplatePathAndFilename($viewRootPath . 'Templates/' . $templatePath);
857 $view->setLayoutRootPaths([$viewRootPath . 'Layouts/']);
858 $view->setPartialRootPaths([$viewRootPath . 'Partials/']);
859 return $view;
860 }
861
862 /**
863 * Test connection with given credentials and return exception message if exception trown
864 *
865 * @return bool
866 */
867 protected function isDatabaseConnectSuccessful(): bool
868 {
869 try {
870 GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionByName('Default')->ping();
871 } catch (DBALException $e) {
872 return false;
873 }
874 return true;
875 }
876
877 /**
878 * Check LocalConfiguration.php for required database settings:
879 * - 'username' and 'password' are mandatory, but may be empty
880 * - if 'driver' is pdo_sqlite and 'path' is set, its ok, too
881 *
882 * @return bool TRUE if required settings are present
883 */
884 protected function isDatabaseConfigurationComplete()
885 {
886 $configurationComplete = true;
887 if (!isset($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'])) {
888 $configurationComplete = false;
889 }
890 if (!isset($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'])) {
891 $configurationComplete = false;
892 }
893 if (isset($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driver'])
894 && $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driver'] === 'pdo_sqlite'
895 && !empty($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['path'])
896 ) {
897 $configurationComplete = true;
898 }
899 return $configurationComplete;
900 }
901
902 /**
903 * Returns configured socket, if set.
904 *
905 * @return string
906 */
907 protected function getDatabaseConfiguredMysqliSocket()
908 {
909 $socket = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['unix_socket'] ?? '';
910 if ($socket === '') {
911 // If no configured socket, use default php socket
912 $defaultSocket = (string)ini_get('mysqli.default_socket');
913 if ($defaultSocket !== '') {
914 $socket = $defaultSocket;
915 }
916 }
917 return $socket;
918 }
919
920 /**
921 * Try to fetch db credentials from a .env file and see if connect works
922 *
923 * @return array Empty array if no file is found or connect is not successful, else working credentials
924 */
925 protected function getDatabaseConfigurationFromEnvironment(): array
926 {
927 $envCredentials = [];
928 foreach (['driver', 'host', 'user', 'password', 'port', 'dbname', 'unix_socket'] as $value) {
929 $envVar = 'TYPO3_INSTALL_DB_' . strtoupper($value);
930 if (getenv($envVar) !== false) {
931 $envCredentials[$value] = getenv($envVar);
932 }
933 }
934 if (!empty($envCredentials)) {
935 $connectionParams = $envCredentials;
936 $connectionParams['wrapperClass'] = Connection::class;
937 $connectionParams['charset'] = 'utf-8';
938 try {
939 DriverManager::getConnection($connectionParams)->ping();
940 return $envCredentials;
941 } catch (DBALException $e) {
942 return [];
943 }
944 }
945 return [];
946 }
947
948 /**
949 * Returns list of available databases (with access-check based on username/password)
950 *
951 * @return array List of available databases
952 */
953 protected function getDatabaseList()
954 {
955 $connectionParams = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME];
956 unset($connectionParams['dbname']);
957
958 // Establishing the connection using the Doctrine DriverManager directly
959 // as we need a connection without selecting a database right away. Otherwise
960 // an invalid database name would lead to exceptions which would prevent
961 // changing the currently configured database.
962 $connection = DriverManager::getConnection($connectionParams);
963 $databaseArray = $connection->getSchemaManager()->listDatabases();
964 $connection->close();
965
966 // Remove organizational tables from database list
967 $reservedDatabaseNames = ['mysql', 'information_schema', 'performance_schema'];
968 $allPossibleDatabases = array_diff($databaseArray, $reservedDatabaseNames);
969
970 // In first installation we show all databases but disable not empty ones (with tables)
971 $databases = [];
972 foreach ($allPossibleDatabases as $databaseName) {
973 // Reestablising the connection for each database since there is no
974 // portable way to switch databases on the same Doctrine connection.
975 // Directly using the Doctrine DriverManager here to avoid messing with
976 // the $GLOBALS database configuration array.
977 $connectionParams['dbname'] = $databaseName;
978 $connection = DriverManager::getConnection($connectionParams);
979
980 $databases[] = [
981 'name' => $databaseName,
982 'tables' => count($connection->getSchemaManager()->listTableNames()),
983 ];
984 $connection->close();
985 }
986
987 return $databases;
988 }
989
990 /**
991 * Creates a new database on the default connection
992 *
993 * @param string $dbName name of database
994 * @return FlashMessage
995 */
996 protected function createNewDatabase($dbName)
997 {
998 if (!$this->isValidDatabaseName($dbName)) {
999 return new FlashMessage(
1000 'Given database name must be shorter than fifty characters'
1001 . ' and consist solely of basic latin letters (a-z), digits (0-9), dollar signs ($)'
1002 . ' and underscores (_).',
1003 'Database name not valid',
1004 FlashMessage::ERROR
1005 );
1006 }
1007 try {
1008 GeneralUtility::makeInstance(ConnectionPool::class)
1009 ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME)
1010 ->getSchemaManager()
1011 ->createDatabase($dbName);
1012 GeneralUtility::makeInstance(ConfigurationManager::class)
1013 ->setLocalConfigurationValueByPath('DB/Connections/Default/dbname', $dbName);
1014 } catch (DBALException $e) {
1015 return new FlashMessage(
1016 'Database with name "' . $dbName . '" could not be created.'
1017 . ' Either your database name contains a reserved keyword or your database'
1018 . ' user does not have sufficient permissions to create it or the database already exists.'
1019 . ' Please choose an existing (empty) database, choose another name or contact administration.',
1020 'Unable to create database',
1021 FlashMessage::ERROR
1022 );
1023 }
1024 return new FlashMessage(
1025 '',
1026 'Database created'
1027 );
1028 }
1029
1030 /**
1031 * Validate the database name against the lowest common denominator of valid identifiers across different DBMS
1032 *
1033 * @param string $databaseName
1034 * @return bool
1035 */
1036 protected function isValidDatabaseName($databaseName)
1037 {
1038 return strlen($databaseName) <= 50 && preg_match('/^[a-zA-Z0-9\$_]*$/', $databaseName);
1039 }
1040
1041 /**
1042 * Checks whether an existing database on the default connection
1043 * can be used for a TYPO3 installation. The database name is only
1044 * persisted to the local configuration if the database is empty.
1045 *
1046 * @param string $dbName name of the database
1047 * @return FlashMessage
1048 */
1049 protected function checkExistingDatabase($dbName)
1050 {
1051 $result = new FlashMessage('');
1052 $localConfigurationPathValuePairs = [];
1053 $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
1054
1055 $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][ConnectionPool::DEFAULT_CONNECTION_NAME]['dbname'] = $dbName;
1056 try {
1057 $connection = GeneralUtility::makeInstance(ConnectionPool::class)
1058 ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
1059
1060 if (!empty($connection->getSchemaManager()->listTableNames())) {
1061 $result = new FlashMessage(
1062 sprintf('Cannot use database "%s"', $dbName)
1063 . ', because it already contains tables. Please select a different database or choose to create one!',
1064 'Selected database is not empty!',
1065 FlashMessage::ERROR
1066 );
1067 }
1068 } catch (\Exception $e) {
1069 $result = new FlashMessage(
1070 sprintf('Could not connect to database "%s"', $dbName)
1071 . '! Make sure it really exists and your database user has the permissions to select it!',
1072 'Could not connect to selected database!',
1073 FlashMessage::ERROR
1074 );
1075 }
1076
1077 if ($result->getSeverity() === FlashMessage::OK) {
1078 $localConfigurationPathValuePairs['DB/Connections/Default/dbname'] = $dbName;
1079 }
1080
1081 // check if database charset is utf-8 - also allow utf8mb4
1082 $defaultDatabaseCharset = $this->getDefaultDatabaseCharset($dbName);
1083 if (strpos($defaultDatabaseCharset, 'utf8') !== 0) {
1084 $result = new FlashMessage(
1085 'Your database uses character set "' . $defaultDatabaseCharset . '", '
1086 . 'but only "utf8" and "utf8mb4" are supported with TYPO3. You probably want to change this before proceeding.',
1087 'Invalid Charset',
1088 FlashMessage::ERROR
1089 );
1090 }
1091
1092 if ($result->getSeverity() === FlashMessage::OK && !empty($localConfigurationPathValuePairs)) {
1093 $configurationManager->setLocalConfigurationValuesByPathValuePairs($localConfigurationPathValuePairs);
1094 }
1095
1096 return $result;
1097 }
1098
1099 /**
1100 * Retrieves the default character set of the database.
1101 *
1102 * @todo this function is MySQL specific. If the core has migrated to Doctrine it should be reexamined
1103 * whether this function and the check in $this->checkExistingDatabase could be deleted and utf8 otherwise
1104 * enforced (guaranteeing compatibility with other database servers).
1105 *
1106 * @param string $dbName
1107 * @return string
1108 */
1109 protected function getDefaultDatabaseCharset(string $dbName): string
1110 {
1111 $connection = GeneralUtility::makeInstance(ConnectionPool::class)
1112 ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
1113 $queryBuilder = $connection->createQueryBuilder();
1114 $defaultDatabaseCharset = $queryBuilder->select('DEFAULT_CHARACTER_SET_NAME')
1115 ->from('information_schema.SCHEMATA')
1116 ->where(
1117 $queryBuilder->expr()->eq(
1118 'SCHEMA_NAME',
1119 $queryBuilder->createNamedParameter($dbName, \PDO::PARAM_STR)
1120 )
1121 )
1122 ->setMaxResults(1)
1123 ->execute()
1124 ->fetchColumn();
1125
1126 return (string)$defaultDatabaseCharset;
1127 }
1128
1129 /**
1130 * This function returns a salted hashed key for new backend user password and install tool password.
1131 *
1132 * This method is executed during installation *before* the preset did set up proper hash method
1133 * selection in LocalConfiguration. So PasswordHashFactory is not usable at this point. We thus loop through
1134 * the four default hash mechanisms and select the first one that works. The preset calculation of step
1135 * executeDefaultConfigurationAction() basically does the same later.
1136 *
1137 * @param string $password Plain text password
1138 * @return string Hashed password
1139 * @throws \LogicException If no hash method has been found, should never happen PhpassPasswordHash is always available
1140 */
1141 protected function getHashedPassword($password)
1142 {
1143 $okHashMethods = [
1144 Argon2iPasswordHash::class,
1145 BcryptPasswordHash::class,
1146 Pbkdf2PasswordHash::class,
1147 PhpassPasswordHash::class,
1148 ];
1149 foreach ($okHashMethods as $className) {
1150 /** @var PasswordHashInterface $instance */
1151 $instance = GeneralUtility::makeInstance($className);
1152 if ($instance->isAvailable()) {
1153 return $instance->getHashedPassword($password);
1154 }
1155 }
1156 throw new \LogicException('No suitable hash method found', 1533988846);
1157 }
1158
1159 /**
1160 * Create tables and import static rows
1161 *
1162 * @return FlashMessage[]
1163 */
1164 protected function importDatabaseData()
1165 {
1166 // Will load ext_localconf and ext_tables. This is pretty safe here since we are
1167 // in first install (database empty), so it is very likely that no extension is loaded
1168 // that could trigger a fatal at this point.
1169 $container = $this->loadExtLocalconfDatabaseAndExtTables();
1170
1171 $sqlReader = $container->get(SqlReader::class);
1172 $sqlCode = $sqlReader->getTablesDefinitionString(true);
1173 $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
1174 $createTableStatements = $sqlReader->getCreateTableStatementArray($sqlCode);
1175 $results = $schemaMigrationService->install($createTableStatements);
1176
1177 // Only keep statements with error messages
1178 $results = array_filter($results);
1179 if (count($results) === 0) {
1180 $insertStatements = $sqlReader->getInsertStatementArray($sqlCode);
1181 $results = $schemaMigrationService->importStaticData($insertStatements);
1182 }
1183 foreach ($results as $statement => &$message) {
1184 if ($message === '') {
1185 unset($results[$statement]);
1186 continue;
1187 }
1188 $message = new FlashMessage(
1189 'Query:' . LF . ' ' . $statement . LF . 'Error:' . LF . ' ' . $message,
1190 'Database query failed!',
1191 FlashMessage::ERROR
1192 );
1193 }
1194 return array_values($results);
1195 }
1196
1197 /**
1198 * Some actions like the database analyzer and the upgrade wizards need additional
1199 * bootstrap actions performed.
1200 *
1201 * Those actions can potentially fatal if some old extension is loaded that triggers
1202 * a fatal in ext_localconf or ext_tables code! Use only if really needed.
1203 *
1204 * @return ContainerInterface
1205 */
1206 protected function loadExtLocalconfDatabaseAndExtTables(): ContainerInterface
1207 {
1208 return GeneralUtility::makeInstance(LateBootService::class)->loadExtLocalconfDatabaseAndExtTables();
1209 }
1210
1211 /**
1212 * Creates a site configuration with one language "English" which is the de-facto default language for TYPO3 in general.
1213 *
1214 * @param string $identifier
1215 * @param int $rootPageId
1216 * @param ServerRequestInterface $request
1217 * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
1218 */
1219 protected function createSiteConfiguration(string $identifier, int $rootPageId, ServerRequestInterface $request)
1220 {
1221 $normalizedParams = $request->getAttribute('normalizedParams', null);
1222 if (!($normalizedParams instanceof NormalizedParams)) {
1223 $normalizedParams = new NormalizedParams(
1224 $request->getServerParams(),
1225 $GLOBALS['TYPO3_CONF_VARS']['SYS'],
1226 Environment::getCurrentScript(),
1227 Environment::getPublicPath()
1228 );
1229 }
1230
1231 // Create a default site configuration called "main" as best practice
1232 $siteConfiguration = GeneralUtility::makeInstance(
1233 SiteConfiguration::class,
1234 Environment::getConfigPath() . '/sites'
1235 );
1236 $siteConfiguration->createNewBasicSite($identifier, $rootPageId, $normalizedParams->getSiteUrl());
1237 }
1238 }