From 4e498f4ea3730cb3747f71b259df8b9549a3f6ad Mon Sep 17 00:00:00 2001
From: Benjamin Franzke <bfr@qbus.de>
Date: Mon, 20 Apr 2020 19:01:28 +0200
Subject: [PATCH] [TASK] Allow DI based services in localconf during extension
 install

We fix PackageManager and ContainerBuilder to allow
multiple container instances (including multiple
cache identifiers) to be live.
ExtensionManager will now load a new symfony container
after installing a new extension, and before reloading
all ext_localconf files. We need to ensure that possible
services in ext_localconf can be loaded when they
depend on symfony DI.

Note: This is not a BUGFIX as this was never
supported for extensions. But it may lead to hard-to-debug
bugs when extension developers add a service to ext
localconf but do not test the re-installation procedure
before publishing the extension to TER.

Releases: master
Resolves: #91150
Change-Id: I9b01feae6fe2f1637ca653403336cd7d216483bd
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/64263
Reviewed-by: Simon Gilli <typo3@gilbertsoft.org>
Reviewed-by: Raphael Zschorsch <rafu1987@gmail.com>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Raphael Zschorsch <rafu1987@gmail.com>
Tested-by: Simon Gilli <typo3@gilbertsoft.org>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
---
 .../DependencyInjection/ContainerBuilder.php  | 13 +++++-----
 .../core/Classes/Package/PackageManager.php   |  2 ++
 .../Classes/Utility/InstallUtility.php        | 24 ++++++++++++++++++-
 .../Tests/Unit/Utility/InstallUtilityTest.php | 12 +++++++++-
 4 files changed, 43 insertions(+), 8 deletions(-)

diff --git a/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php b/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php
index 944b523ec3d4..278c6f763ed6 100644
--- a/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php
+++ b/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php
@@ -35,9 +35,9 @@ use TYPO3\CMS\Core\Package\PackageManager;
 class ContainerBuilder
 {
     /**
-     * @var string
+     * @var array
      */
-    protected $cacheIdentifier;
+    protected $cacheIdentifiers;
 
     /**
      * @var array
@@ -163,15 +163,16 @@ class ContainerBuilder
      */
     protected function getCacheIdentifier(PackageManager $packageManager): string
     {
-        return $this->cacheIdentifier ?? $this->createCacheIdentifier($packageManager->getCacheIdentifier());
+        $packageManagerCacheIdentifier = $packageManager->getCacheIdentifier() ?? '';
+        return $this->cacheIdentifiers[$packageManagerCacheIdentifier] ?? $this->createCacheIdentifier($packageManagerCacheIdentifier);
     }
 
     /**
-     * @param string|null $additionalIdentifier
+     * @param string $additionalIdentifier
      * @return string
      */
-    protected function createCacheIdentifier(string $additionalIdentifier = null): string
+    protected function createCacheIdentifier(string $additionalIdentifier): string
     {
-        return $this->cacheIdentifier = 'DependencyInjectionContainer_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . ($additionalIdentifier ?? '') . 'DependencyInjectionContainer');
+        return $this->cacheIdentifiers[$additionalIdentifier] = 'DependencyInjectionContainer_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . $additionalIdentifier . 'DependencyInjectionContainer');
     }
 }
diff --git a/typo3/sysext/core/Classes/Package/PackageManager.php b/typo3/sysext/core/Classes/Package/PackageManager.php
index e63846301c25..6934849f1c9c 100644
--- a/typo3/sysext/core/Classes/Package/PackageManager.php
+++ b/typo3/sysext/core/Classes/Package/PackageManager.php
@@ -761,6 +761,8 @@ class PackageManager implements SingletonInterface
         }
         $packageStatesCode = "<?php\n$fileDescription\nreturn " . ArrayUtility::arrayExport($this->packageStatesConfiguration) . ";\n";
         GeneralUtility::writeFile($this->packageStatesPathAndFilename, $packageStatesCode, true);
+        // Cache identifier depends on package states file, therefore we invalidate the identifier
+        $this->cacheIdentifier = null;
 
         GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive($this->packageStatesPathAndFilename);
     }
diff --git a/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php b/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php
index 8609def641bf..af072b9e1dc9 100644
--- a/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php
+++ b/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php
@@ -44,6 +44,7 @@ use TYPO3\CMS\Extensionmanager\Event\AfterExtensionStaticDatabaseContentHasBeenI
 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
 use TYPO3\CMS\Impexp\Import;
 use TYPO3\CMS\Impexp\Utility\ImportExportUtility;
+use TYPO3\CMS\Install\Service\LateBootService;
 
 /**
  * Extension Manager Install Utility
@@ -93,6 +94,11 @@ class InstallUtility implements SingletonInterface, LoggerAwareInterface
      */
     protected $eventDispatcher;
 
+    /**
+     * @var LateBootService
+     */
+    protected $lateBootService;
+
     public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher)
     {
         $this->eventDispatcher = $eventDispatcher;
@@ -154,6 +160,14 @@ class InstallUtility implements SingletonInterface, LoggerAwareInterface
         $this->registry = $registry;
     }
 
+    /**
+     * @param  LateBootService $lateBootService
+     */
+    public function injectLateBootService(LateBootService $lateBootService)
+    {
+        $this->lateBootService = $lateBootService;
+    }
+
     /**
      * Helper function to install an extension
      * also processes db updates and clears the cache if the extension asks for it
@@ -178,13 +192,21 @@ class InstallUtility implements SingletonInterface, LoggerAwareInterface
         } else {
             $this->cacheManager->flushCachesInGroup('system');
         }
+
+        // Load a new container as reloadCaches will load ext_localconf
+        $container = $this->lateBootService->getContainer();
+        $backup = $this->lateBootService->makeCurrent($container);
+
         $this->reloadCaches();
         $this->updateDatabase();
 
         foreach ($extensionKeys as $extensionKey) {
             $this->processExtensionSetup($extensionKey);
-            $this->eventDispatcher->dispatch(new AfterPackageActivationEvent($extensionKey, 'typo3-cms-extension', $this));
+            $container->get(EventDispatcherInterface::class)->dispatch(new AfterPackageActivationEvent($extensionKey, 'typo3-cms-extension', $this));
         }
+
+        // Reset to the original container instance
+        $this->lateBootService->makeCurrent(null, $backup);
     }
 
     /**
diff --git a/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php b/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php
index 4325ea654e96..ce3ace2bec1c 100644
--- a/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php
+++ b/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php
@@ -18,6 +18,7 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Extensionmanager\Tests\Unit\Utility;
 
 use Prophecy\Argument;
+use Psr\Container\ContainerInterface;
 use Psr\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\Yaml\Yaml;
 use TYPO3\CMS\Core\Cache\CacheManager;
@@ -29,6 +30,7 @@ use TYPO3\CMS\Core\Utility\StringUtility;
 use TYPO3\CMS\Extensionmanager\Utility\DependencyUtility;
 use TYPO3\CMS\Extensionmanager\Utility\InstallUtility;
 use TYPO3\CMS\Extensionmanager\Utility\ListUtility;
+use TYPO3\CMS\Install\Service\LateBootService;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
 /**
@@ -85,7 +87,15 @@ class InstallUtilityTest extends UnitTestCase
                 'importInitialFiles',
             ]
         );
-        $this->installMock->injectEventDispatcher($this->prophesize(EventDispatcherInterface::class)->reveal());
+        $eventDispatcherProphecy = $this->prophesize(EventDispatcherInterface::class);
+        $this->installMock->injectEventDispatcher($eventDispatcherProphecy->reveal());
+        $this->installMock->injectLateBootService($this->prophesize(LateBootService::class)->reveal());
+        $containerProphecy = $this->prophesize(ContainerInterface::class);
+        $containerProphecy->get(EventDispatcherInterface::class)->willReturn($eventDispatcherProphecy->reveal());
+        $lateBootServiceProphecy = $this->prophesize(LateBootService::class);
+        $lateBootServiceProphecy->getContainer()->willReturn($containerProphecy->reveal());
+        $lateBootServiceProphecy->makeCurrent(Argument::cetera())->willReturn([]);
+        $this->installMock->injectLateBootService($lateBootServiceProphecy->reveal());
         $dependencyUtility = $this->getMockBuilder(DependencyUtility::class)->getMock();
         $this->installMock->_set('dependencyUtility', $dependencyUtility);
         $this->installMock->expects(self::any())
-- 
GitLab