From ff401fc1c7a6d1287bb261e120e0ef662341d0c2 Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Wed, 25 Aug 2021 11:00:55 +0200
Subject: [PATCH] [TASK] Improve Package Manager API

This change is a precursor to
https://review.typo3.org/c/Packages/TYPO3.CMS/+/69174

This change effectively:
* Centralizes checks to PackageStates.php in Bootstrap
* Centralizes some specific Install-Tool functionality in PackageManager
* Adds a cache identifier for ext_localconf/TCA/ext_tables.php from the packagemanager

The last adaption allows to have custom cache identifier
when the active packages have been updated.

Resolves: #94987
Releases: master
Change-Id: Ibc34fe1084ee42ef0a86e2942c6aed3a345b7188
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70752
Tested-by: Helmut Hummel <typo3@helhum.io>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Helmut Hummel <typo3@helhum.io>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack <benni@typo3.org>
---
 .../backend/Classes/Http/Application.php      | 15 ++---------
 .../Classes/Console/CommandApplication.php    | 13 +---------
 typo3/sysext/core/Classes/Core/Bootstrap.php  |  2 +-
 .../Package/FailsafePackageManager.php        | 21 ++++++++++++++++
 .../Utility/ExtensionManagementUtility.php    |  6 ++---
 .../frontend/Classes/Http/Application.php     | 15 ++---------
 .../Controller/InstallerController.php        | 14 +----------
 .../Classes/Middleware/Maintenance.php        | 25 ++-----------------
 8 files changed, 33 insertions(+), 78 deletions(-)

diff --git a/typo3/sysext/backend/Classes/Http/Application.php b/typo3/sysext/backend/Classes/Http/Application.php
index 09ffdbade45a..c11c847bcbde 100644
--- a/typo3/sysext/backend/Classes/Http/Application.php
+++ b/typo3/sysext/backend/Classes/Http/Application.php
@@ -24,7 +24,7 @@ use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Context\DateTimeAspect;
 use TYPO3\CMS\Core\Context\VisibilityAspect;
-use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
 use TYPO3\CMS\Core\Http\AbstractApplication;
 use TYPO3\CMS\Core\Http\RedirectResponse;
@@ -56,7 +56,7 @@ class Application extends AbstractApplication
 
     public function handle(ServerRequestInterface $request): ResponseInterface
     {
-        if (!$this->checkIfEssentialConfigurationExists()) {
+        if (!Bootstrap::checkIfEssentialConfigurationExists($this->configurationManager)) {
             return $this->installToolRedirect();
         }
 
@@ -72,17 +72,6 @@ class Application extends AbstractApplication
         return parent::handle($request);
     }
 
-    /**
-     * Check if LocalConfiguration.php and PackageStates.php exist
-     *
-     * @return bool TRUE when the essential configuration is available, otherwise FALSE
-     */
-    protected function checkIfEssentialConfigurationExists(): bool
-    {
-        return file_exists($this->configurationManager->getLocalConfigurationFileLocation())
-            && file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php');
-    }
-
     /**
      * Create a PSR-7 Response that redirects to the install tool
      *
diff --git a/typo3/sysext/core/Classes/Console/CommandApplication.php b/typo3/sysext/core/Classes/Console/CommandApplication.php
index 0f5f965980d3..7a785956ef12 100644
--- a/typo3/sysext/core/Classes/Console/CommandApplication.php
+++ b/typo3/sysext/core/Classes/Console/CommandApplication.php
@@ -98,7 +98,7 @@ class CommandApplication implements ApplicationInterface
             $isLowLevelCommandShortcut = $realName !== null && !$this->wantsFullBoot($realName);
             // Load ext_localconf, except if a low level command shortcut was found
             // or if essential configuration is missing
-            if (!$isLowLevelCommandShortcut && $this->essentialConfigurationExists()) {
+            if (!$isLowLevelCommandShortcut && Bootstrap::checkIfEssentialConfigurationExists($this->configurationManager)) {
                 $this->bootService->loadExtLocalconfDatabaseAndExtTables();
             }
         }
@@ -160,17 +160,6 @@ class CommandApplication implements ApplicationInterface
         return $input->getFirstArgument() ?? 'list';
     }
 
-    /**
-     * Check if LocalConfiguration.php and PackageStates.php exist
-     *
-     * @return bool TRUE when the essential configuration is available, otherwise FALSE
-     */
-    protected function essentialConfigurationExists(): bool
-    {
-        return file_exists($this->configurationManager->getLocalConfigurationFileLocation())
-            && file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php');
-    }
-
     /**
      * Check the script is called from a cli environment.
      */
diff --git a/typo3/sysext/core/Classes/Core/Bootstrap.php b/typo3/sysext/core/Classes/Core/Bootstrap.php
index 7e988f73116c..24ac7eb580a5 100644
--- a/typo3/sysext/core/Classes/Core/Bootstrap.php
+++ b/typo3/sysext/core/Classes/Core/Bootstrap.php
@@ -222,7 +222,7 @@ class Bootstrap
      * @return bool TRUE when the essential configuration is available, otherwise FALSE
      * @internal This is not a public API method, do not use in own extensions
      */
-    protected static function checkIfEssentialConfigurationExists(ConfigurationManager $configurationManager): bool
+    public static function checkIfEssentialConfigurationExists(ConfigurationManager $configurationManager): bool
     {
         return file_exists($configurationManager->getLocalConfigurationFileLocation())
             && file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php');
diff --git a/typo3/sysext/core/Classes/Package/FailsafePackageManager.php b/typo3/sysext/core/Classes/Package/FailsafePackageManager.php
index f228056b23f5..739d1914b4a6 100644
--- a/typo3/sysext/core/Classes/Package/FailsafePackageManager.php
+++ b/typo3/sysext/core/Classes/Package/FailsafePackageManager.php
@@ -64,4 +64,25 @@ class FailsafePackageManager extends PackageManager
         parent::sortActivePackagesByDependencies();
         parent::savePackageStates();
     }
+
+    /**
+     * Create PackageStates.php if missing and LocalConfiguration exists, used to have a Install Tool session running
+     *
+     * It is fired if PackageStates.php is deleted on a running instance,
+     * all packages marked as "part of minimal system" are activated in this case.
+     * @param bool $useFactoryDefault if true, use the "isPartOfFactoryDefault" otherwise use "isPartOfMinimalUsableSystem"
+     * @internal
+     */
+    public function recreatePackageStatesFileIfMissing(bool $useFactoryDefault = false): void
+    {
+        if (!file_exists($this->packageStatesPathAndFilename)) {
+            $packages = $this->getAvailablePackages();
+            foreach ($packages as $package) {
+                if ($package instanceof PackageInterface && ($useFactoryDefault ? $package->isPartOfFactoryDefault() : $package->isPartOfMinimalUsableSystem())) {
+                    $this->activatePackage($package->getPackageKey());
+                }
+            }
+            $this->forceSortAndSavePackageStates();
+        }
+    }
 }
diff --git a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
index 0392b33073a9..fed41fcfa926 100644
--- a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
+++ b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
@@ -1561,7 +1561,7 @@ tt_content.' . $key . $suffix . ' {
      */
     protected static function getExtLocalconfCacheIdentifier()
     {
-        return 'ext_localconf_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'extLocalconf');
+        return 'ext_localconf_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'extLocalconf' . self::$packageManager->getCacheIdentifier());
     }
 
     /**
@@ -1702,7 +1702,7 @@ tt_content.' . $key . $suffix . ' {
      */
     protected static function getBaseTcaCacheIdentifier()
     {
-        return 'tca_base_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'tca_code');
+        return 'tca_base_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'tca_code' . self::$packageManager->getCacheIdentifier());
     }
 
     /**
@@ -1790,7 +1790,7 @@ tt_content.' . $key . $suffix . ' {
      */
     protected static function getExtTablesCacheIdentifier()
     {
-        return 'ext_tables_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'extTables');
+        return 'ext_tables_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'extTables' . self::$packageManager->getCacheIdentifier());
     }
 
     /**
diff --git a/typo3/sysext/frontend/Classes/Http/Application.php b/typo3/sysext/frontend/Classes/Http/Application.php
index 4a42afe832b4..3797ca7e656e 100644
--- a/typo3/sysext/frontend/Classes/Http/Application.php
+++ b/typo3/sysext/frontend/Classes/Http/Application.php
@@ -26,7 +26,7 @@ use TYPO3\CMS\Core\Context\DateTimeAspect;
 use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Context\VisibilityAspect;
 use TYPO3\CMS\Core\Context\WorkspaceAspect;
-use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
 use TYPO3\CMS\Core\Http\AbstractApplication;
 use TYPO3\CMS\Core\Http\RedirectResponse;
@@ -58,7 +58,7 @@ class Application extends AbstractApplication
 
     public function handle(ServerRequestInterface $request): ResponseInterface
     {
-        if (!$this->checkIfEssentialConfigurationExists()) {
+        if (!Bootstrap::checkIfEssentialConfigurationExists($this->configurationManager)) {
             return $this->installToolRedirect();
         }
 
@@ -69,17 +69,6 @@ class Application extends AbstractApplication
         return parent::handle($request);
     }
 
-    /**
-     * Check if LocalConfiguration.php and PackageStates.php exist
-     *
-     * @return bool TRUE when the essential configuration is available, otherwise FALSE
-     */
-    protected function checkIfEssentialConfigurationExists(): bool
-    {
-        return file_exists($this->configurationManager->getLocalConfigurationFileLocation())
-            && file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php');
-    }
-
     /**
      * Create a PSR-7 Response that redirects to the install tool
      *
diff --git a/typo3/sysext/install/Classes/Controller/InstallerController.php b/typo3/sysext/install/Classes/Controller/InstallerController.php
index 7eee2b5f39b5..e502e69fe7af 100644
--- a/typo3/sysext/install/Classes/Controller/InstallerController.php
+++ b/typo3/sysext/install/Classes/Controller/InstallerController.php
@@ -49,7 +49,6 @@ use TYPO3\CMS\Core\Information\Typo3Version;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
 use TYPO3\CMS\Core\Package\FailsafePackageManager;
-use TYPO3\CMS\Core\Package\PackageInterface;
 use TYPO3\CMS\Core\Registry;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
@@ -241,19 +240,8 @@ class InstallerController
 
         if (@is_dir(Environment::getLegacyConfigPath())) {
             $this->configurationManager->createLocalConfigurationFromFactoryConfiguration();
-
             // Create a PackageStates.php with all packages activated marked as "part of factory default"
-            if (!file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php')) {
-                $packages = $this->packageManager->getAvailablePackages();
-                foreach ($packages as $package) {
-                    if ($package instanceof PackageInterface
-                        && $package->isPartOfFactoryDefault()
-                    ) {
-                        $this->packageManager->activatePackage($package->getPackageKey());
-                    }
-                }
-                $this->packageManager->forceSortAndSavePackageStates();
-            }
+            $this->packageManager->recreatePackageStatesFileIfMissing(true);
             $extensionConfiguration = new ExtensionConfiguration();
             $extensionConfiguration->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions();
 
diff --git a/typo3/sysext/install/Classes/Middleware/Maintenance.php b/typo3/sysext/install/Classes/Middleware/Maintenance.php
index b769925c3ec9..eb2b56515b88 100644
--- a/typo3/sysext/install/Classes/Middleware/Maintenance.php
+++ b/typo3/sysext/install/Classes/Middleware/Maintenance.php
@@ -24,7 +24,6 @@ use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\Configuration\Features;
-use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
 use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
@@ -34,7 +33,6 @@ use TYPO3\CMS\Core\Http\Security\ReferrerEnforcer;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
 use TYPO3\CMS\Core\Package\FailsafePackageManager;
-use TYPO3\CMS\Core\Package\PackageInterface;
 use TYPO3\CMS\Install\Authentication\AuthenticationService;
 use TYPO3\CMS\Install\Controller\AbstractController;
 use TYPO3\CMS\Install\Controller\EnvironmentController;
@@ -233,7 +231,7 @@ class Maintenance implements MiddlewareInterface
                     1505215756
                 );
             }
-            $this->recreatePackageStatesFileIfMissing();
+            $this->packageManager->recreatePackageStatesFileIfMissing();
             $className = $this->controllers[$controllerName];
             /** @var AbstractController $controller */
             $controller = $this->container->get($className);
@@ -333,7 +331,7 @@ class Maintenance implements MiddlewareInterface
     }
 
     /**
-     * Check if LocalConfiguration.php and PackageStates.php exist
+     * Check if LocalConfiguration.php exists (PackageStates is optional)
      *
      * @return bool TRUE when the essential configuration is available, otherwise FALSE
      */
@@ -342,25 +340,6 @@ class Maintenance implements MiddlewareInterface
         return file_exists($this->configurationManager->getLocalConfigurationFileLocation());
     }
 
-    /**
-     * Create PackageStates.php if missing and LocalConfiguration exists.
-     *
-     * It is fired if PackageStates.php is deleted on a running instance,
-     * all packages marked as "part of minimal system" are activated in this case.
-     */
-    protected function recreatePackageStatesFileIfMissing(): void
-    {
-        if (!file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php')) {
-            $packages = $this->packageManager->getAvailablePackages();
-            foreach ($packages as $package) {
-                if ($package instanceof PackageInterface && $package->isPartOfMinimalUsableSystem()) {
-                    $this->packageManager->activatePackage($package->getPackageKey());
-                }
-            }
-            $this->packageManager->forceSortAndSavePackageStates();
-        }
-    }
-
     /**
      * Evaluates HTTP `Referer` header (which is denied by client to be a custom
      * value) - attempts to ensure the value is given using a HTML client refresh.
-- 
GitLab