From b89aad13d4859fb90f0a8dac923cf43ef01f6f8c Mon Sep 17 00:00:00 2001
From: Helmut Hummel <typo3@helhum.io>
Date: Sun, 19 Nov 2023 17:28:26 +0100
Subject: [PATCH] [BUGFIX] Create same configuration in web and cli install
 process
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The code for the web installer and the cli installer
is only partly aligned and shared, thus may easily
diverge, leading to different installation results.

The code to create system settings is now put
into SetupService and is used for both installation
methods, so that the cli install now also uses
FactoryConfiguration.php.

Additionally, the following cleanups are performed
to prepare further cleanup of the installation process:

* The cli setup does not set insecure trustedHostPattern any more.
* Silent config and template file changes are removed from setup,
  as conceptually these do nothing on newly created config and
  template files.
* New exceptions are created for config file generation instead
  of misusing a FAL-related exception.
* Absolute paths are made relative to project path before being
  passed to exception.

Resolves: #102403
Releases: main, 12.4
Change-Id: I069e9e9f4136bba56d630eaf09f676b6c5ea7904
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/81793
Tested-by: Benjamin Franzke <ben@bnf.dev>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Helmut Hummel <typo3@helhum.io>
Reviewed-by: Helmut Hummel <typo3@helhum.io>
Tested-by: core-ci <typo3@b13.com>
---
 Build/Sources/TypeScript/install/installer.ts | 30 +------
 .../install/Classes/Command/SetupCommand.php  | 13 +---
 .../Controller/InstallerController.php        | 78 ++-----------------
 ...gurationDirectoryDoesNotExistException.php | 25 ++++++
 ...onfigurationFileAlreadyExistsException.php | 25 ++++++
 .../install/Classes/Service/SetupService.php  | 66 ++++++++++++----
 .../install/Classes/ServiceProvider.php       |  6 +-
 .../Resources/Public/JavaScript/installer.js  |  2 +-
 8 files changed, 115 insertions(+), 130 deletions(-)
 create mode 100644 typo3/sysext/install/Classes/Service/Exception/ConfigurationDirectoryDoesNotExistException.php
 create mode 100644 typo3/sysext/install/Classes/Service/Exception/ConfigurationFileAlreadyExistsException.php

diff --git a/Build/Sources/TypeScript/install/installer.ts b/Build/Sources/TypeScript/install/installer.ts
index 6e72da364713..949b5ac2af70 100644
--- a/Build/Sources/TypeScript/install/installer.ts
+++ b/Build/Sources/TypeScript/install/installer.ts
@@ -224,7 +224,7 @@ class Installer {
       .then(async (response: AjaxResponse): Promise<void> => {
         const data = await response.resolve();
         if (data.success === true) {
-          this.executeSilentConfigurationUpdate();
+          this.checkDatabaseConnect();
         } else {
           this.executeAdjustTrustedHostsPattern();
         }
@@ -235,33 +235,7 @@ class Installer {
     (new AjaxRequest(this.getUrl('executeAdjustTrustedHostsPattern')))
       .get({ cache: 'no-cache' })
       .then((): void => {
-        this.executeSilentConfigurationUpdate();
-      });
-  }
-
-  private executeSilentConfigurationUpdate(): void {
-    (new AjaxRequest(this.getUrl('executeSilentConfigurationUpdate')))
-      .get({ cache: 'no-cache' })
-      .then(async (response: AjaxResponse): Promise<void> => {
-        const data = await response.resolve();
-        if (data.success === true) {
-          this.executeSilentTemplateFileUpdate();
-        } else {
-          this.executeSilentConfigurationUpdate();
-        }
-      });
-  }
-
-  private executeSilentTemplateFileUpdate(): void {
-    (new AjaxRequest(this.getUrl('executeSilentTemplateFileUpdate')))
-      .get({ cache: 'no-cache' })
-      .then(async (response: AjaxResponse): Promise<void> => {
-        const data = await response.resolve();
-        if (data.success === true) {
-          this.checkDatabaseConnect();
-        } else {
-          this.executeSilentTemplateFileUpdate();
-        }
+        this.checkDatabaseConnect();
       });
   }
 
diff --git a/typo3/sysext/install/Classes/Command/SetupCommand.php b/typo3/sysext/install/Classes/Command/SetupCommand.php
index 9874d17eb4ed..b62151c750b5 100644
--- a/typo3/sysext/install/Classes/Command/SetupCommand.php
+++ b/typo3/sysext/install/Classes/Command/SetupCommand.php
@@ -29,11 +29,9 @@ use Symfony\Component\Console\Question\ConfirmationQuestion;
 use Symfony\Component\Console\Question\Question;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Package\FailsafePackageManager;
-use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException;
 use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Install\FolderStructure\DefaultFactory;
+use TYPO3\CMS\Install\Service\Exception\ConfigurationFileAlreadyExistsException;
 use TYPO3\CMS\Install\Service\LateBootService;
 use TYPO3\CMS\Install\Service\SetupDatabaseService;
 use TYPO3\CMS\Install\Service\SetupService;
@@ -59,7 +57,6 @@ class SetupCommand extends Command
         private readonly SetupService $setupService,
         private readonly ConfigurationManager $configurationManager,
         private readonly LateBootService $lateBootService,
-        private readonly FailsafePackageManager $packageManager,
     ) {
         parent::__construct($name);
     }
@@ -209,16 +206,12 @@ EOT
         $questionHelper = $this->getHelper('question');
 
         // Ensure all required files and folders exist
-        $serverType = $this->getServerType($questionHelper, $input, $output);
-        $folderStructureFactory = GeneralUtility::makeInstance(DefaultFactory::class);
-        $folderStructureFactory->getStructure($serverType)->fix();
-        // Ensure existing PackageStates.php for non-composer installation.
-        $this->packageManager->recreatePackageStatesFileIfMissing(true);
+        $this->setupService->createDirectoryStructure($this->getServerType($questionHelper, $input, $output));
 
         try {
             $force = $input->getOption('force');
             $this->setupService->prepareSystemSettings($force);
-        } catch (ExistingTargetFileNameException $exception) {
+        } catch (ConfigurationFileAlreadyExistsException) {
             $configOverwriteQuestion = new ConfirmationQuestion(
                 'Configuration already exists do you want to overwrite it [default: no] ? ',
                 false
diff --git a/typo3/sysext/install/Classes/Controller/InstallerController.php b/typo3/sysext/install/Classes/Controller/InstallerController.php
index e0e81557cb62..2a313db9140b 100644
--- a/typo3/sysext/install/Classes/Controller/InstallerController.php
+++ b/typo3/sysext/install/Classes/Controller/InstallerController.php
@@ -23,8 +23,6 @@ use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Backend\Routing\RouteRedirect;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
-use TYPO3\CMS\Core\Configuration\Exception\SettingsWriteException;
-use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Crypto\HashService;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -45,17 +43,12 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\View\FluidViewAdapter;
 use TYPO3\CMS\Core\View\ViewInterface;
 use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory;
-use TYPO3\CMS\Install\Configuration\FeatureManager;
 use TYPO3\CMS\Install\FolderStructure\DefaultFactory;
 use TYPO3\CMS\Install\Service\EnableFileService;
-use TYPO3\CMS\Install\Service\Exception\ConfigurationChangedException;
-use TYPO3\CMS\Install\Service\Exception\SilentConfigurationUpgradeReadonlyException;
-use TYPO3\CMS\Install\Service\Exception\TemplateFileChangedException;
+use TYPO3\CMS\Install\Service\Exception\ConfigurationDirectoryDoesNotExistException;
 use TYPO3\CMS\Install\Service\LateBootService;
 use TYPO3\CMS\Install\Service\SetupDatabaseService;
 use TYPO3\CMS\Install\Service\SetupService;
-use TYPO3\CMS\Install\Service\SilentConfigurationUpgradeService;
-use TYPO3\CMS\Install\Service\SilentTemplateFileUpgradeService;
 use TYPO3\CMS\Install\SystemEnvironment\Check;
 use TYPO3\CMS\Install\SystemEnvironment\SetupCheck;
 use TYPO3\CMS\Install\WebserverType;
@@ -73,8 +66,6 @@ final class InstallerController
 
     public function __construct(
         private readonly LateBootService $lateBootService,
-        private readonly SilentConfigurationUpgradeService $silentConfigurationUpgradeService,
-        private readonly SilentTemplateFileUpgradeService $silentTemplateFileUpgradeService,
         private readonly ConfigurationManager $configurationManager,
         private readonly FailsafePackageManager $packageManager,
         private readonly VerifyHostHeader $verifyHostHeader,
@@ -184,27 +175,18 @@ final class InstallerController
      */
     public function executeEnvironmentAndFoldersAction(ServerRequestInterface $request): ResponseInterface
     {
-        $folderStructureFactory = GeneralUtility::makeInstance(DefaultFactory::class);
-        $structureFacade = $folderStructureFactory->getStructure(WebserverType::fromRequest($request));
-        $structureFixMessageQueue = $structureFacade->fix();
-        $errorsFromStructure = $structureFixMessageQueue->getAllMessages(ContextualFeedbackSeverity::ERROR);
-
-        if ($this->configurationManager->canWriteConfiguration()) {
-            $this->configurationManager->createLocalConfigurationFromFactoryConfiguration();
-            // Create a PackageStates.php with all packages activated marked as "part of factory default"
-            $this->packageManager->recreatePackageStatesFileIfMissing(true);
-            $extensionConfiguration = new ExtensionConfiguration();
-            $extensionConfiguration->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions();
-
+        $errorsFromStructure = $this->setupService->createDirectoryStructure(WebserverType::fromRequest($request));
+        try {
+            $this->setupService->prepareSystemSettings();
+        } catch (ConfigurationDirectoryDoesNotExistException) {
             return new JsonResponse([
-                'success' => true,
+                'success' => false,
+                'status' => $errorsFromStructure,
             ]);
         }
-        $errorsFromStructure[] = new FlashMessage('Unable to write configuration file', 'Error', ContextualFeedbackSeverity::ERROR);
 
         return new JsonResponse([
-            'success' => false,
-            'status' => $errorsFromStructure,
+            'success' => true,
         ]);
     }
 
@@ -237,44 +219,6 @@ final class InstallerController
         ]);
     }
 
-    /**
-     * Execute silent configuration update. May be called multiple times until success = true is returned.
-     *
-     * @return ResponseInterface success = true if no change has been done
-     */
-    public function executeSilentConfigurationUpdateAction(): ResponseInterface
-    {
-        $success = true;
-        try {
-            $this->silentConfigurationUpgradeService->execute();
-        } catch (ConfigurationChangedException) {
-            $success = false;
-        } catch (SettingsWriteException $e) {
-            throw new SilentConfigurationUpgradeReadonlyException(code: 1688464086, throwable: $e);
-        }
-        return new JsonResponse([
-            'success' => $success,
-        ]);
-    }
-
-    /**
-     * Execute silent template files update. May be called multiple times until success = true is returned.
-     *
-     * @return ResponseInterface success = true if no change has been done
-     */
-    public function executeSilentTemplateFileUpdateAction(): ResponseInterface
-    {
-        $success = true;
-        try {
-            $this->silentTemplateFileUpgradeService->execute();
-        } catch (TemplateFileChangedException $e) {
-            $success = false;
-        }
-        return new JsonResponse([
-            'success' => $success,
-        ]);
-    }
-
     /**
      * Check if database connect step needs to be shown
      */
@@ -606,10 +550,6 @@ final class InstallerController
      */
     public function executeDefaultConfigurationAction(ServerRequestInterface $request): ResponseInterface
     {
-        $featureManager = new FeatureManager();
-        // Get best matching configuration presets
-        $configurationValues = $featureManager->getBestMatchingConfigurationForAllFeatures();
-
         $container = $this->lateBootService->loadExtLocalconfDatabaseAndExtTables();
         // Use the container here instead of makeInstance() to use the factory of the container for building the UriBuilder
         $uriBuilder = $container->get(UriBuilder::class);
@@ -651,8 +591,6 @@ final class InstallerController
         // Mark upgrade wizards as done
         $this->setupDatabaseService->markWizardsDone($container);
 
-        $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($configurationValues);
-
         $formProtection = $this->formProtectionFactory->createFromRequest($request);
         $formProtection->clean();
 
diff --git a/typo3/sysext/install/Classes/Service/Exception/ConfigurationDirectoryDoesNotExistException.php b/typo3/sysext/install/Classes/Service/Exception/ConfigurationDirectoryDoesNotExistException.php
new file mode 100644
index 000000000000..ab575633e55e
--- /dev/null
+++ b/typo3/sysext/install/Classes/Service/Exception/ConfigurationDirectoryDoesNotExistException.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Install\Service\Exception;
+
+use TYPO3\CMS\Install\Service\Exception;
+
+/**
+ * An exception thrown during setup, when the config dir was not created, but config file must be written
+ */
+class ConfigurationDirectoryDoesNotExistException extends Exception {}
diff --git a/typo3/sysext/install/Classes/Service/Exception/ConfigurationFileAlreadyExistsException.php b/typo3/sysext/install/Classes/Service/Exception/ConfigurationFileAlreadyExistsException.php
new file mode 100644
index 000000000000..3e31ac09e526
--- /dev/null
+++ b/typo3/sysext/install/Classes/Service/Exception/ConfigurationFileAlreadyExistsException.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Install\Service\Exception;
+
+use TYPO3\CMS\Install\Service\Exception;
+
+/**
+ * An exception thrown during setup, when the config file should be written, but already exists
+ */
+class ConfigurationFileAlreadyExistsException extends Exception {}
diff --git a/typo3/sysext/install/Classes/Service/SetupService.php b/typo3/sysext/install/Classes/Service/SetupService.php
index 699d9dec5895..05108785a877 100644
--- a/typo3/sysext/install/Classes/Service/SetupService.php
+++ b/typo3/sysext/install/Classes/Service/SetupService.php
@@ -19,8 +19,10 @@ namespace TYPO3\CMS\Install\Service;
 
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
 use TYPO3\CMS\Core\Configuration\SiteWriter;
+use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2idPasswordHash;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\BcryptPasswordHash;
@@ -28,10 +30,16 @@ use TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface;
 use TYPO3\CMS\Core\Crypto\Random;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Package\FailsafePackageManager;
+use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Install\Command\BackendUserGroupType;
 use TYPO3\CMS\Install\Configuration\FeatureManager;
+use TYPO3\CMS\Install\FolderStructure\DefaultFactory;
+use TYPO3\CMS\Install\Service\Exception\ConfigurationDirectoryDoesNotExistException;
+use TYPO3\CMS\Install\Service\Exception\ConfigurationFileAlreadyExistsException;
+use TYPO3\CMS\Install\WebserverType;
 
 /**
  * Service class helping to manage parts of the setup process (set configuration,
@@ -44,8 +52,20 @@ readonly class SetupService
         private ConfigurationManager $configurationManager,
         private SiteWriter $siteWriter,
         private YamlFileLoader $yamlFileLoader,
+        private FailsafePackageManager $packageManager,
     ) {}
 
+    /**
+     * @param WebserverType $webserverType
+     * @return FlashMessage[]
+     */
+    public function createDirectoryStructure(WebserverType $webserverType): array
+    {
+        $folderStructureFactory = GeneralUtility::makeInstance(DefaultFactory::class);
+        $structureFixMessageQueue = $folderStructureFactory->getStructure($webserverType)->fix();
+        return $structureFixMessageQueue->getAllMessages(ContextualFeedbackSeverity::ERROR);
+    }
+
     public function setSiteName(string $name): bool
     {
         return $this->configurationManager->setLocalConfigurationValueByPath('SYS/sitename', $name);
@@ -126,34 +146,41 @@ readonly class SetupService
     }
 
     /**
-     * @throws ExistingTargetFileNameException
+     * @throws ConfigurationFileAlreadyExistsException
+     * @throws ConfigurationDirectoryDoesNotExistException
      */
     public function prepareSystemSettings(bool $forceOverwrite = false): void
     {
         $configurationFileLocation = $this->configurationManager->getSystemConfigurationFileLocation();
-        if (!$forceOverwrite && @is_file($configurationFileLocation)) {
-            throw new ExistingTargetFileNameException(
-                'Configuration file ' . $configurationFileLocation . ' already exists!',
-                1669747685,
+        $configDir = dirname($configurationFileLocation);
+        if (!is_dir($configDir)) {
+            throw new ConfigurationDirectoryDoesNotExistException(
+                'Configuration directory ' . $this->makePathRelativeToProjectDirectory($configDir) . ' does not exist!',
+                1700401774,
             );
         }
-
-        // @todo Remove once LocalConfiguration.php support was dropped.
-        // @todo Web installer creates default configuration based on default factory configuration. Recheck if we
-        //       should use this here too instead of an empty array.
-        // Ugly hack to write system/settings.php, to avoid fallback to
-        // LocalConfiguration.php causing issues because it does not exist!
-        @unlink($configurationFileLocation);
-        $this->configurationManager->writeLocalConfiguration([]);
+        if (@is_file($configurationFileLocation)) {
+            if (!$forceOverwrite) {
+                throw new ConfigurationFileAlreadyExistsException(
+                    'Configuration file ' . $this->makePathRelativeToProjectDirectory($configurationFileLocation) . ' already exists!',
+                    1669747685,
+                );
+            }
+            unlink($configurationFileLocation);
+        }
+        $this->configurationManager->createLocalConfigurationFromFactoryConfiguration();
+        $randomKey = GeneralUtility::makeInstance(Random::class)->generateRandomHexString(96);
+        $this->configurationManager->setLocalConfigurationValueByPath('SYS/encryptionKey', $randomKey);
+        $extensionConfiguration = new ExtensionConfiguration();
+        $extensionConfiguration->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions();
 
         // Get best matching configuration presets
         $featureManager = new FeatureManager();
         $configurationValues = $featureManager->getBestMatchingConfigurationForAllFeatures();
         $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($configurationValues);
 
-        $randomKey = GeneralUtility::makeInstance(Random::class)->generateRandomHexString(96);
-        $this->configurationManager->setLocalConfigurationValueByPath('SYS/encryptionKey', $randomKey);
-        $this->configurationManager->setLocalConfigurationValueByPath('SYS/trustedHostsPattern', '.*.*');
+        // In non Composer mode, create a PackageStates.php with all packages activated marked as "part of factory default"
+        $this->packageManager->recreatePackageStatesFileIfMissing(true);
     }
 
     public function createSite(): string
@@ -316,4 +343,9 @@ For each website you need a TypoScript record on the main page of your website (
             );
         }
     }
+
+    private function makePathRelativeToProjectDirectory(string $absolutePath): string
+    {
+        return str_replace(Environment::getProjectPath(), '', $absolutePath);
+    }
 }
diff --git a/typo3/sysext/install/Classes/ServiceProvider.php b/typo3/sysext/install/Classes/ServiceProvider.php
index 2a512cf16d89..332e45be82ff 100644
--- a/typo3/sysext/install/Classes/ServiceProvider.php
+++ b/typo3/sysext/install/Classes/ServiceProvider.php
@@ -238,7 +238,8 @@ class ServiceProvider extends AbstractServiceProvider
         return new Service\SetupService(
             $container->get(ConfigurationManager::class),
             $container->get(SiteWriter::class),
-            $container->get(YamlFileLoader::class)
+            $container->get(YamlFileLoader::class),
+            $container->get(FailsafePackageManager::class),
         );
     }
 
@@ -295,8 +296,6 @@ class ServiceProvider extends AbstractServiceProvider
     {
         return new Controller\InstallerController(
             $container->get(Service\LateBootService::class),
-            $container->get(Service\SilentConfigurationUpgradeService::class),
-            $container->get(Service\SilentTemplateFileUpgradeService::class),
             $container->get(ConfigurationManager::class),
             $container->get(FailsafePackageManager::class),
             $container->get(VerifyHostHeader::class),
@@ -413,7 +412,6 @@ class ServiceProvider extends AbstractServiceProvider
             $container->get(Service\SetupService::class),
             $container->get(ConfigurationManager::class),
             $container->get(LateBootService::class),
-            $container->get(FailsafePackageManager::class),
         );
     }
 
diff --git a/typo3/sysext/install/Resources/Public/JavaScript/installer.js b/typo3/sysext/install/Resources/Public/JavaScript/installer.js
index a42058587f9b..2e86c46901b4 100644
--- a/typo3/sysext/install/Resources/Public/JavaScript/installer.js
+++ b/typo3/sysext/install/Resources/Public/JavaScript/installer.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import DocumentService from"@typo3/core/document-service.js";import RegularEvent from"@typo3/core/event/regular-event.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import PasswordStrength from"@typo3/install/module/password-strength.js";import{InfoBox}from"@typo3/install/renderable/info-box.js";import"@typo3/backend/element/icon-element.js";import{selector}from"@typo3/core/literals.js";import"@typo3/backend/element/progress-bar-element.js";var Identifiers;!function(e){e.body=".t3js-body",e.moduleContent=".t3js-module-content",e.mainContent=".t3js-installer-content",e.progressBar=".t3js-installer-progress",e.databaseConnectOutput=".t3js-installer-databaseConnect-output",e.databaseSelectOutput=".t3js-installer-databaseSelect-output",e.databaseDataOutput=".t3js-installer-databaseData-output"}(Identifiers||(Identifiers={}));class Installer{constructor(){this.initializeEvents(),DocumentService.ready().then((()=>{this.initialize()}))}initializeEvents(){new RegularEvent("click",(e=>{e.preventDefault(),this.showEnvironmentAndFolders()})).delegateTo(document,".t3js-installer-environmentFolders-retry"),new RegularEvent("click",(e=>{e.preventDefault(),this.executeEnvironmentAndFolders()})).delegateTo(document,".t3js-installer-environmentFolders-execute"),new RegularEvent("click",(e=>{e.preventDefault(),this.executeDatabaseConnect()})).delegateTo(document,".t3js-installer-databaseConnect-execute"),new RegularEvent("click",(e=>{e.preventDefault(),this.executeDatabaseSelect()})).delegateTo(document,".t3js-installer-databaseSelect-execute"),new RegularEvent("click",(e=>{e.preventDefault(),this.executeDatabaseData()})).delegateTo(document,".t3js-installer-databaseData-execute"),new RegularEvent("click",(e=>{e.preventDefault(),this.executeDefaultConfiguration()})).delegateTo(document,".t3js-installer-defaultConfiguration-execute"),new RegularEvent("click",((e,t)=>{e.preventDefault();const a=document.querySelector(t.dataset.toggleTarget);"invisible"===t.dataset.toggleState?(t.dataset.toggleState="visible",a.setAttribute("type","text")):(t.dataset.toggleState="invisible",a.setAttribute("type","password"))})).delegateTo(document,".t3-install-form-password-toggle"),new RegularEvent("change",((e,t)=>{const a=t.value;document.querySelectorAll(".t3-install-driver-data").forEach((e=>e.setAttribute("hidden",""))),document.querySelectorAll(".t3-install-driver-data input").forEach((e=>e.setAttribute("disabled","disabled"))),document.querySelectorAll(selector`#${a} input`).forEach((e=>e.removeAttribute("disabled"))),document.querySelector("#"+a)?.removeAttribute("hidden")})).delegateTo(document,"#t3js-connect-database-driver")}initialize(){this.setProgress(0),this.getMainLayout()}getUrl(e){let t=location.href;return t=t.replace(location.search,""),void 0!==e&&(t=t+"?install[action]="+e),t}setProgress(e){const t=document.querySelector(Identifiers.progressBar);null!==t&&0!==e&&(t.value=e,t.label=`Step ${e} of 5 completed`)}getMainLayout(){new AjaxRequest(this.getUrl("mainLayout")).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();document.querySelector(Identifiers.body).innerHTML=t.html,this.checkInstallerAvailable()}))}checkInstallerAvailable(){new AjaxRequest(this.getUrl("checkInstallerAvailable")).get({cache:"no-cache"}).then((async e=>{(await e.resolve()).success?this.checkEnvironmentAndFolders():this.showInstallerNotAvailable()}))}showInstallerNotAvailable(){const e=document.querySelector(Identifiers.mainContent);new AjaxRequest(this.getUrl("showInstallerNotAvailable")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();!0===a.success&&(e.innerHTML=a.html)}))}checkEnvironmentAndFolders(){this.setProgress(1),new AjaxRequest(this.getUrl("checkEnvironmentAndFolders")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.checkTrustedHostsPattern():this.showEnvironmentAndFolders()}))}showEnvironmentAndFolders(){const e=document.querySelector(Identifiers.mainContent);new AjaxRequest(this.getUrl("showEnvironmentAndFolders")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();if(!0===a.success){e.innerHTML=a.html;const t=document.querySelector(".t3js-installer-environment-details");let s=!1;Array.isArray(a.environmentStatusErrors)&&a.environmentStatusErrors.forEach((e=>{s=!0,t.append(InfoBox.create(e.severity,e.title,e.message))})),Array.isArray(a.environmentStatusWarnings)&&a.environmentStatusWarnings.forEach((e=>{s=!0,t.append(InfoBox.create(e.severity,e.title,e.message))})),Array.isArray(a.structureErrors)&&a.structureErrors.forEach((e=>{s=!0,t.append(InfoBox.create(e.severity,e.title,e.message))})),s?(t.removeAttribute("hidden"),document.querySelectorAll(".t3js-installer-environmentFolders-bad").forEach((e=>e.removeAttribute("hidden")))):document.querySelectorAll(".t3js-installer-environmentFolders-good").forEach((e=>e.removeAttribute("hidden")))}}))}executeEnvironmentAndFolders(){new AjaxRequest(this.getUrl("executeEnvironmentAndFolders")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success&&this.checkTrustedHostsPattern()}))}checkTrustedHostsPattern(){new AjaxRequest(this.getUrl("checkTrustedHostsPattern")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.executeSilentConfigurationUpdate():this.executeAdjustTrustedHostsPattern()}))}executeAdjustTrustedHostsPattern(){new AjaxRequest(this.getUrl("executeAdjustTrustedHostsPattern")).get({cache:"no-cache"}).then((()=>{this.executeSilentConfigurationUpdate()}))}executeSilentConfigurationUpdate(){new AjaxRequest(this.getUrl("executeSilentConfigurationUpdate")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.executeSilentTemplateFileUpdate():this.executeSilentConfigurationUpdate()}))}executeSilentTemplateFileUpdate(){new AjaxRequest(this.getUrl("executeSilentTemplateFileUpdate")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.checkDatabaseConnect():this.executeSilentTemplateFileUpdate()}))}checkDatabaseConnect(){this.setProgress(2),new AjaxRequest(this.getUrl("checkDatabaseConnect")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.checkDatabaseSelect():this.showDatabaseConnect()}))}showDatabaseConnect(){const e=document.querySelector(Identifiers.mainContent);new AjaxRequest(this.getUrl("showDatabaseConnect")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();!0===a.success&&(e.innerHTML=a.html,document.querySelector("#t3js-connect-database-driver").dispatchEvent(new Event("change",{bubbles:!0})),PasswordStrength.initialize(document.querySelector(".t3-install-form-password-strength")))}))}executeDatabaseConnect(){const e=document.querySelector(Identifiers.databaseConnectOutput),t={"install[action]":"executeDatabaseConnect","install[token]":document.querySelector(Identifiers.moduleContent).dataset.installerDatabaseConnectExecuteToken};for(const[e,a]of new FormData(document.querySelector(Identifiers.body+" form")))t[e]=a.toString();new AjaxRequest(this.getUrl()).post(t).then((async t=>{const a=await t.resolve();!0===a.success?this.checkDatabaseSelect():Array.isArray(a.status)&&(e.replaceChildren(),a.status.forEach((t=>{e.append(InfoBox.create(t.severity,t.title,t.message))})))}))}checkDatabaseSelect(){this.setProgress(3),new AjaxRequest(this.getUrl("checkDatabaseSelect")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.checkDatabaseData():this.showDatabaseSelect()}))}showDatabaseSelect(){const e=document.querySelector(Identifiers.mainContent);new AjaxRequest(this.getUrl("showDatabaseSelect")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();!0===a.success&&(e.innerHTML=a.html)}))}executeDatabaseSelect(){const e=document.querySelector(Identifiers.databaseSelectOutput),t={"install[action]":"executeDatabaseSelect","install[token]":document.querySelector(Identifiers.moduleContent).dataset.installerDatabaseSelectExecuteToken};for(const[e,a]of new FormData(document.querySelector(Identifiers.body+" form")))t[e]=a.toString();new AjaxRequest(this.getUrl()).post(t).then((async t=>{const a=await t.resolve();!0===a.success?this.checkDatabaseRequirements():Array.isArray(a.status)&&a.status.forEach((t=>{e.replaceChildren(InfoBox.create(t.severity,t.title,t.message))}))}))}checkDatabaseRequirements(){const e=document.querySelector(Identifiers.databaseSelectOutput),t={"install[action]":"checkDatabaseRequirements","install[token]":document.querySelector(Identifiers.moduleContent).dataset.installerDatabaseCheckRequirementsExecuteToken};for(const[e,a]of new FormData(document.querySelector(Identifiers.body+" form")))t[e]=a.toString();new AjaxRequest(this.getUrl()).post(t).then((async t=>{const a=await t.resolve();!0===a.success?this.checkDatabaseData():Array.isArray(a.status)&&(e.replaceChildren(),a.status.forEach((t=>{e.append(InfoBox.create(t.severity,t.title,t.message))})))}))}checkDatabaseData(){this.setProgress(4),new AjaxRequest(this.getUrl("checkDatabaseData")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.showDefaultConfiguration():this.showDatabaseData()}))}showDatabaseData(){const e=document.querySelector(Identifiers.mainContent);new AjaxRequest(this.getUrl("showDatabaseData")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();!0===a.success&&(e.innerHTML=a.html,PasswordStrength.initialize(document.querySelector(".t3-install-form-password-strength")))}))}executeDatabaseData(){const e=document.querySelector(Identifiers.databaseDataOutput),t={"install[action]":"executeDatabaseData","install[token]":document.querySelector(Identifiers.moduleContent).dataset.installerDatabaseDataExecuteToken};for(const[e,a]of new FormData(document.querySelector(Identifiers.body+" form")))t[e]=a.toString();const a=document.createElement("typo3-backend-progress-bar");e.replaceChildren(a),new AjaxRequest(this.getUrl()).post(t).then((async t=>{const a=await t.resolve();!0===a.success?this.showDefaultConfiguration():Array.isArray(a.status)&&(e.replaceChildren(),a.status.forEach((t=>{e.append(InfoBox.create(t.severity,t.title,t.message))})))}))}showDefaultConfiguration(){const e=document.querySelector(Identifiers.mainContent);this.setProgress(5),new AjaxRequest(this.getUrl("showDefaultConfiguration")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();!0===a.success&&(e.innerHTML=a.html)}))}executeDefaultConfiguration(){const e={"install[action]":"executeDefaultConfiguration","install[token]":document.querySelector(Identifiers.moduleContent).dataset.installerDefaultConfigurationExecuteToken};for(const[t,a]of new FormData(document.querySelector(Identifiers.body+" form")))e[t]=a.toString();new AjaxRequest(this.getUrl()).post(e).then((async e=>{const t=await e.resolve();top.location.href=t.redirect}))}}export default new Installer;
\ No newline at end of file
+import DocumentService from"@typo3/core/document-service.js";import RegularEvent from"@typo3/core/event/regular-event.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import PasswordStrength from"@typo3/install/module/password-strength.js";import{InfoBox}from"@typo3/install/renderable/info-box.js";import"@typo3/backend/element/icon-element.js";import{selector}from"@typo3/core/literals.js";import"@typo3/backend/element/progress-bar-element.js";var Identifiers;!function(e){e.body=".t3js-body",e.moduleContent=".t3js-module-content",e.mainContent=".t3js-installer-content",e.progressBar=".t3js-installer-progress",e.databaseConnectOutput=".t3js-installer-databaseConnect-output",e.databaseSelectOutput=".t3js-installer-databaseSelect-output",e.databaseDataOutput=".t3js-installer-databaseData-output"}(Identifiers||(Identifiers={}));class Installer{constructor(){this.initializeEvents(),DocumentService.ready().then((()=>{this.initialize()}))}initializeEvents(){new RegularEvent("click",(e=>{e.preventDefault(),this.showEnvironmentAndFolders()})).delegateTo(document,".t3js-installer-environmentFolders-retry"),new RegularEvent("click",(e=>{e.preventDefault(),this.executeEnvironmentAndFolders()})).delegateTo(document,".t3js-installer-environmentFolders-execute"),new RegularEvent("click",(e=>{e.preventDefault(),this.executeDatabaseConnect()})).delegateTo(document,".t3js-installer-databaseConnect-execute"),new RegularEvent("click",(e=>{e.preventDefault(),this.executeDatabaseSelect()})).delegateTo(document,".t3js-installer-databaseSelect-execute"),new RegularEvent("click",(e=>{e.preventDefault(),this.executeDatabaseData()})).delegateTo(document,".t3js-installer-databaseData-execute"),new RegularEvent("click",(e=>{e.preventDefault(),this.executeDefaultConfiguration()})).delegateTo(document,".t3js-installer-defaultConfiguration-execute"),new RegularEvent("click",((e,t)=>{e.preventDefault();const a=document.querySelector(t.dataset.toggleTarget);"invisible"===t.dataset.toggleState?(t.dataset.toggleState="visible",a.setAttribute("type","text")):(t.dataset.toggleState="invisible",a.setAttribute("type","password"))})).delegateTo(document,".t3-install-form-password-toggle"),new RegularEvent("change",((e,t)=>{const a=t.value;document.querySelectorAll(".t3-install-driver-data").forEach((e=>e.setAttribute("hidden",""))),document.querySelectorAll(".t3-install-driver-data input").forEach((e=>e.setAttribute("disabled","disabled"))),document.querySelectorAll(selector`#${a} input`).forEach((e=>e.removeAttribute("disabled"))),document.querySelector("#"+a)?.removeAttribute("hidden")})).delegateTo(document,"#t3js-connect-database-driver")}initialize(){this.setProgress(0),this.getMainLayout()}getUrl(e){let t=location.href;return t=t.replace(location.search,""),void 0!==e&&(t=t+"?install[action]="+e),t}setProgress(e){const t=document.querySelector(Identifiers.progressBar);null!==t&&0!==e&&(t.value=e,t.label=`Step ${e} of 5 completed`)}getMainLayout(){new AjaxRequest(this.getUrl("mainLayout")).get({cache:"no-cache"}).then((async e=>{const t=await e.resolve();document.querySelector(Identifiers.body).innerHTML=t.html,this.checkInstallerAvailable()}))}checkInstallerAvailable(){new AjaxRequest(this.getUrl("checkInstallerAvailable")).get({cache:"no-cache"}).then((async e=>{(await e.resolve()).success?this.checkEnvironmentAndFolders():this.showInstallerNotAvailable()}))}showInstallerNotAvailable(){const e=document.querySelector(Identifiers.mainContent);new AjaxRequest(this.getUrl("showInstallerNotAvailable")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();!0===a.success&&(e.innerHTML=a.html)}))}checkEnvironmentAndFolders(){this.setProgress(1),new AjaxRequest(this.getUrl("checkEnvironmentAndFolders")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.checkTrustedHostsPattern():this.showEnvironmentAndFolders()}))}showEnvironmentAndFolders(){const e=document.querySelector(Identifiers.mainContent);new AjaxRequest(this.getUrl("showEnvironmentAndFolders")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();if(!0===a.success){e.innerHTML=a.html;const t=document.querySelector(".t3js-installer-environment-details");let s=!1;Array.isArray(a.environmentStatusErrors)&&a.environmentStatusErrors.forEach((e=>{s=!0,t.append(InfoBox.create(e.severity,e.title,e.message))})),Array.isArray(a.environmentStatusWarnings)&&a.environmentStatusWarnings.forEach((e=>{s=!0,t.append(InfoBox.create(e.severity,e.title,e.message))})),Array.isArray(a.structureErrors)&&a.structureErrors.forEach((e=>{s=!0,t.append(InfoBox.create(e.severity,e.title,e.message))})),s?(t.removeAttribute("hidden"),document.querySelectorAll(".t3js-installer-environmentFolders-bad").forEach((e=>e.removeAttribute("hidden")))):document.querySelectorAll(".t3js-installer-environmentFolders-good").forEach((e=>e.removeAttribute("hidden")))}}))}executeEnvironmentAndFolders(){new AjaxRequest(this.getUrl("executeEnvironmentAndFolders")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success&&this.checkTrustedHostsPattern()}))}checkTrustedHostsPattern(){new AjaxRequest(this.getUrl("checkTrustedHostsPattern")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.checkDatabaseConnect():this.executeAdjustTrustedHostsPattern()}))}executeAdjustTrustedHostsPattern(){new AjaxRequest(this.getUrl("executeAdjustTrustedHostsPattern")).get({cache:"no-cache"}).then((()=>{this.checkDatabaseConnect()}))}checkDatabaseConnect(){this.setProgress(2),new AjaxRequest(this.getUrl("checkDatabaseConnect")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.checkDatabaseSelect():this.showDatabaseConnect()}))}showDatabaseConnect(){const e=document.querySelector(Identifiers.mainContent);new AjaxRequest(this.getUrl("showDatabaseConnect")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();!0===a.success&&(e.innerHTML=a.html,document.querySelector("#t3js-connect-database-driver").dispatchEvent(new Event("change",{bubbles:!0})),PasswordStrength.initialize(document.querySelector(".t3-install-form-password-strength")))}))}executeDatabaseConnect(){const e=document.querySelector(Identifiers.databaseConnectOutput),t={"install[action]":"executeDatabaseConnect","install[token]":document.querySelector(Identifiers.moduleContent).dataset.installerDatabaseConnectExecuteToken};for(const[e,a]of new FormData(document.querySelector(Identifiers.body+" form")))t[e]=a.toString();new AjaxRequest(this.getUrl()).post(t).then((async t=>{const a=await t.resolve();!0===a.success?this.checkDatabaseSelect():Array.isArray(a.status)&&(e.replaceChildren(),a.status.forEach((t=>{e.append(InfoBox.create(t.severity,t.title,t.message))})))}))}checkDatabaseSelect(){this.setProgress(3),new AjaxRequest(this.getUrl("checkDatabaseSelect")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.checkDatabaseData():this.showDatabaseSelect()}))}showDatabaseSelect(){const e=document.querySelector(Identifiers.mainContent);new AjaxRequest(this.getUrl("showDatabaseSelect")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();!0===a.success&&(e.innerHTML=a.html)}))}executeDatabaseSelect(){const e=document.querySelector(Identifiers.databaseSelectOutput),t={"install[action]":"executeDatabaseSelect","install[token]":document.querySelector(Identifiers.moduleContent).dataset.installerDatabaseSelectExecuteToken};for(const[e,a]of new FormData(document.querySelector(Identifiers.body+" form")))t[e]=a.toString();new AjaxRequest(this.getUrl()).post(t).then((async t=>{const a=await t.resolve();!0===a.success?this.checkDatabaseRequirements():Array.isArray(a.status)&&a.status.forEach((t=>{e.replaceChildren(InfoBox.create(t.severity,t.title,t.message))}))}))}checkDatabaseRequirements(){const e=document.querySelector(Identifiers.databaseSelectOutput),t={"install[action]":"checkDatabaseRequirements","install[token]":document.querySelector(Identifiers.moduleContent).dataset.installerDatabaseCheckRequirementsExecuteToken};for(const[e,a]of new FormData(document.querySelector(Identifiers.body+" form")))t[e]=a.toString();new AjaxRequest(this.getUrl()).post(t).then((async t=>{const a=await t.resolve();!0===a.success?this.checkDatabaseData():Array.isArray(a.status)&&(e.replaceChildren(),a.status.forEach((t=>{e.append(InfoBox.create(t.severity,t.title,t.message))})))}))}checkDatabaseData(){this.setProgress(4),new AjaxRequest(this.getUrl("checkDatabaseData")).get({cache:"no-cache"}).then((async e=>{!0===(await e.resolve()).success?this.showDefaultConfiguration():this.showDatabaseData()}))}showDatabaseData(){const e=document.querySelector(Identifiers.mainContent);new AjaxRequest(this.getUrl("showDatabaseData")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();!0===a.success&&(e.innerHTML=a.html,PasswordStrength.initialize(document.querySelector(".t3-install-form-password-strength")))}))}executeDatabaseData(){const e=document.querySelector(Identifiers.databaseDataOutput),t={"install[action]":"executeDatabaseData","install[token]":document.querySelector(Identifiers.moduleContent).dataset.installerDatabaseDataExecuteToken};for(const[e,a]of new FormData(document.querySelector(Identifiers.body+" form")))t[e]=a.toString();const a=document.createElement("typo3-backend-progress-bar");e.replaceChildren(a),new AjaxRequest(this.getUrl()).post(t).then((async t=>{const a=await t.resolve();!0===a.success?this.showDefaultConfiguration():Array.isArray(a.status)&&(e.replaceChildren(),a.status.forEach((t=>{e.append(InfoBox.create(t.severity,t.title,t.message))})))}))}showDefaultConfiguration(){const e=document.querySelector(Identifiers.mainContent);this.setProgress(5),new AjaxRequest(this.getUrl("showDefaultConfiguration")).get({cache:"no-cache"}).then((async t=>{const a=await t.resolve();!0===a.success&&(e.innerHTML=a.html)}))}executeDefaultConfiguration(){const e={"install[action]":"executeDefaultConfiguration","install[token]":document.querySelector(Identifiers.moduleContent).dataset.installerDefaultConfigurationExecuteToken};for(const[t,a]of new FormData(document.querySelector(Identifiers.body+" form")))e[t]=a.toString();new AjaxRequest(this.getUrl()).post(e).then((async e=>{const t=await e.resolve();top.location.href=t.redirect}))}}export default new Installer;
\ No newline at end of file
-- 
GitLab