From ac0864d09b257afdacac79297a0c8ebe26348129 Mon Sep 17 00:00:00 2001
From: Simon Gilli <typo3@gilbertsoft.org>
Date: Thu, 23 Apr 2020 13:18:06 +0200
Subject: [PATCH] [BUGFIX] Fully check dependencies of dependencies

This will check the dependencies of dependencies during the install of
an extension with the extension manager.

This solves especially issues with dependent extensions having various
major versions for different TYPO3 major versions e.g. ext:powermail.

Resolves: #91179
Releases: master, 9.5
Change-Id: I98e6c019066aab303e78b5455ac4dfb58f78cd50
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/64308
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Benjamin Franzke <bfr@qbus.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Benjamin Franzke <bfr@qbus.de>
---
 .../Classes/Utility/DependencyUtility.php     | 28 ++++++++++++++++++-
 ...ompatibleExtensionObjectStorageFixture.php | 16 +++++++----
 .../Unit/Utility/DependencyUtilityTest.php    | 19 ++++++++++++-
 3 files changed, 55 insertions(+), 8 deletions(-)

diff --git a/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php b/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php
index a805c950a163..b85386a0001e 100644
--- a/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php
+++ b/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php
@@ -524,6 +524,32 @@ class DependencyUtility implements SingletonInterface
         return !empty($count);
     }
 
+    /**
+     * Get the latest compatible version of an extension that's
+     * compatible with the current core and PHP version.
+     *
+     * @param iterable $extensions
+     * @return Extension|null
+     */
+    protected function getCompatibleExtension(iterable $extensions): ?Extension
+    {
+        foreach ($extensions as $extension) {
+            /** @var Extension $extension */
+            $this->checkDependencies($extension);
+            $extensionKey = $extension->getExtensionKey();
+
+            if (isset($this->dependencyErrors[$extensionKey])) {
+                // reset dependencyErrors and continue with next version
+                unset($this->dependencyErrors[$extensionKey]);
+                continue;
+            }
+
+            return $extension;
+        }
+
+        return null;
+    }
+
     /**
      * Get the latest compatible version of an extension that
      * fulfills the given dependency from TER
@@ -539,7 +565,7 @@ class DependencyUtility implements SingletonInterface
             $versions['lowestIntegerVersion'],
             $versions['highestIntegerVersion']
         );
-        return $compatibleDataSets->getFirst();
+        return $this->getCompatibleExtension($compatibleDataSets);
     }
 
     /**
diff --git a/typo3/sysext/extensionmanager/Tests/Unit/Fixtures/LatestCompatibleExtensionObjectStorageFixture.php b/typo3/sysext/extensionmanager/Tests/Unit/Fixtures/LatestCompatibleExtensionObjectStorageFixture.php
index a861396576b6..b6967ac1774b 100644
--- a/typo3/sysext/extensionmanager/Tests/Unit/Fixtures/LatestCompatibleExtensionObjectStorageFixture.php
+++ b/typo3/sysext/extensionmanager/Tests/Unit/Fixtures/LatestCompatibleExtensionObjectStorageFixture.php
@@ -18,18 +18,22 @@ namespace TYPO3\CMS\Extensionmanager\Tests\Unit\Fixtures;
 /**
  * Latest compatible extension object storage fixture
  */
-class LatestCompatibleExtensionObjectStorageFixture
+class LatestCompatibleExtensionObjectStorageFixture implements \IteratorAggregate
 {
     /**
-     * @var array
+     * @var int
      */
-    public $extensions = [];
+    private $position = 0;
 
     /**
-     * @return \TYPO3\CMS\Extensionmanager\Domain\Model\Extension
+     * @var array<int, \TYPO3\CMS\Extensionmanager\Domain\Model\Extension>
      */
-    public function getFirst()
+    public $extensions = [];
+
+    public function getIterator(): \Generator
     {
-        return $this->extensions[0];
+        foreach ($this->extensions as $extension) {
+            yield $extension;
+        }
     }
 }
diff --git a/typo3/sysext/extensionmanager/Tests/Unit/Utility/DependencyUtilityTest.php b/typo3/sysext/extensionmanager/Tests/Unit/Utility/DependencyUtilityTest.php
index 7000af2e2f30..1de583dedcbd 100644
--- a/typo3/sysext/extensionmanager/Tests/Unit/Utility/DependencyUtilityTest.php
+++ b/typo3/sysext/extensionmanager/Tests/Unit/Utility/DependencyUtilityTest.php
@@ -479,12 +479,29 @@ class DependencyUtilityTest extends UnitTestCase
      */
     public function getLatestCompatibleExtensionByIntegerVersionDependencyWillReturnExtensionModelOfLatestExtension(): void
     {
+        $suitableDependency = new Dependency();
+        $suitableDependency->setIdentifier('typo3');
+        $suitableDependency->setLowestVersion('3.6.1');
+
+        $suitableDependencies = new \SplObjectStorage();
+        $suitableDependencies->attach($suitableDependency);
+
+        $unsuitableDependency = new Dependency();
+        $unsuitableDependency->setIdentifier('typo3');
+        $unsuitableDependency->setHighestVersion('4.3.0');
+
+        $unsuitableDependencies = new \SplObjectStorage();
+        $unsuitableDependencies->attach($unsuitableDependency);
+
         $extension1 = new Extension();
         $extension1->setExtensionKey('foo');
         $extension1->setVersion('1.0.0');
+        $extension1->setDependencies($unsuitableDependencies);
+
         $extension2 = new Extension();
         $extension2->setExtensionKey('bar');
         $extension2->setVersion('1.0.42');
+        $extension2->setDependencies($suitableDependencies);
 
         $myStorage = new LatestCompatibleExtensionObjectStorageFixture();
         $myStorage->extensions[] = $extension1;
@@ -505,7 +522,7 @@ class DependencyUtilityTest extends UnitTestCase
         $extension = $dependencyUtility->_call('getLatestCompatibleExtensionByIntegerVersionDependency', $dependency);
 
         self::assertInstanceOf(Extension::class, $extension);
-        self::assertSame('foo', $extension->getExtensionKey());
+        self::assertSame('bar', $extension->getExtensionKey());
     }
 
     /**
-- 
GitLab