[BUGFIX] Improve dependency check in extension manager 42/39242/3
authorNicole Cordes <typo3@cordes.co>
Sun, 3 May 2015 14:18:09 +0000 (16:18 +0200)
committerNicole Cordes <typo3@cordes.co>
Tue, 5 May 2015 12:38:44 +0000 (14:38 +0200)
This patch solves multiple problems currently occurring
if you try to install an extension:

* check dependencies recursively and merge errors
* prevent download if dependency errors occur
* prevent downgrading an extension
* improve information and error messages
* catch exceptions and display flash message

Releases: master, 6.2
Resolves: #52051
Resolves: #65332
Resolves: #65479
Resolves: #65916
Resolves: #60777
Change-Id: I0e9715d920e2fb43adb77fd61fde52938229431a
Reviewed-on: http://review.typo3.org/39242
Reviewed-by: Nicole Cordes <typo3@cordes.co>
Tested-by: Nicole Cordes <typo3@cordes.co>
typo3/sysext/extensionmanager/Classes/Controller/DownloadController.php
typo3/sysext/extensionmanager/Classes/Domain/Model/DownloadQueue.php
typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php
typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php
typo3/sysext/extensionmanager/Classes/Utility/ListUtility.php
typo3/sysext/extensionmanager/Classes/Utility/Repository/Helper.php
typo3/sysext/extensionmanager/Resources/Private/Language/locallang.xlf

index 88ba50f..d3ff14d 100644 (file)
@@ -73,7 +73,9 @@ class DownloadController extends AbstractController {
                                foreach ($dependencyTypes as $dependencyType => $dependencies) {
                                        $extensions = '';
                                        foreach ($dependencies as $extensionKey => $dependency) {
-                                               $extensions .= htmlspecialchars($extensionKey) . '<br />';
+                                               $extensions .= $this->translate('downloadExtension.dependencies.extensionWithVersion', array(
+                                                               $extensionKey, $dependency->getVersion()
+                                                       )) . '<br />';
                                        }
                                        $message .= $this->translate('downloadExtension.dependencies.typeHeadline',
                                                array(
index 8cc4e19..936ec7e 100644 (file)
@@ -13,6 +13,9 @@ namespace TYPO3\CMS\Extensionmanager\Domain\Model;
  *
  * The TYPO3 project - inspiring people to share!
  */
+
+use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
+
 /**
  * Download Queue - storage for extensions to be downloaded
  *
@@ -59,15 +62,16 @@ class DownloadQueue implements \TYPO3\CMS\Core\SingletonInterface {
         */
        public function addExtensionToQueue(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension, $stack = 'download') {
                if (!is_string($stack) || !in_array($stack, array('download', 'update'))) {
-                       throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Stack has to be either "download" or "update"', 1342432103);
+                       throw new ExtensionManagerException('Stack has to be either "download" or "update"', 1342432103);
                }
                if (!isset($this->extensionStorage[$stack])) {
                        $this->extensionStorage[$stack] = array();
                }
                if (array_key_exists($extension->getExtensionKey(), $this->extensionStorage[$stack])) {
                        if ($this->extensionStorage[$stack][$extension->getExtensionKey()] !== $extension) {
-                               throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException(
-                                       $extension->getExtensionKey() . ' was requested to be downloaded in different versions.',
+                               throw new ExtensionManagerException(
+                                       $extension->getExtensionKey() . ' was requested to be downloaded in different versions (' . $extension->getVersion()
+                                               . ' and ' . $this->extensionStorage[$stack][$extension->getExtensionKey()]->getVersion() . ').',
                                        1342432101
                                );
                        }
@@ -92,7 +96,7 @@ class DownloadQueue implements \TYPO3\CMS\Core\SingletonInterface {
         */
        public function removeExtensionFromQueue(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension, $stack = 'download') {
                if (!is_string($stack) || !in_array($stack, array('download', 'update'))) {
-                       throw new \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException('Stack has to be either "download" or "update"', 1342432104);
+                       throw new ExtensionManagerException('Stack has to be either "download" or "update"', 1342432104);
                }
                if (array_key_exists($stack, $this->extensionStorage) && is_array($this->extensionStorage[$stack])) {
                        if (array_key_exists($extension->getExtensionKey(), $this->extensionStorage[$stack])) {
index c343fec..c27849a 100644 (file)
@@ -95,7 +95,9 @@ class ExtensionManagementService implements \TYPO3\CMS\Core\SingletonInterface {
                // We have to check for dependencies of the extension first, before marking it for download
                // because this extension might have dependencies, which need to be downloaded and installed first
                $this->dependencyUtility->checkDependencies($extension);
-               $this->downloadQueue->addExtensionToQueue($extension);
+               if (!$this->dependencyUtility->hasDependencyErrors()) {
+                       $this->downloadQueue->addExtensionToQueue($extension);
+               }
        }
 
        /**
index c42066f..54147fd 100644 (file)
@@ -241,15 +241,25 @@ class DependencyUtility implements \TYPO3\CMS\Core\SingletonInterface {
                        $isLoadedVersionCompatible = $this->isLoadedVersionCompatible($dependency);
                        if ($isLoadedVersionCompatible === TRUE) {
                                return TRUE;
-                       } else {
+                       }
+                       $extension = $this->listUtility->getExtension($extensionKey);
+                       $loadedVersion = $extension->getPackageMetaData()->getVersion();
+                       if (version_compare($loadedVersion, $dependency->getHighestVersion()) === -1) {
                                try {
                                        $this->getExtensionFromRepository($extensionKey, $dependency);
                                } catch (Exception\UnresolvedDependencyException $e) {
                                        throw new Exception\MissingVersionDependencyException(
-                                               'The extension ' . $dependency->getIdentifier() . ' is needed in version ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion() . ', but could not be fetched from TER',
+                                               'The extension ' . $extensionKey . ' is installed in version ' . $loadedVersion
+                                                       . ' but needed in version ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion() . ' and could not be fetched from TER',
                                                1396302624
                                        );
                                }
+                       } else {
+                               throw new Exception\MissingVersionDependencyException(
+                                       'The extension ' . $extensionKey . ' is installed in version ' . $loadedVersion .
+                                       ' but needed in version ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion(),
+                                       1430561927
+                               );
                        }
                } else {
                        $extensionIsAvailable = $this->isDependentExtensionAvailable($extensionKey);
@@ -260,10 +270,30 @@ class DependencyUtility implements \TYPO3\CMS\Core\SingletonInterface {
                                        $this->managementService->markExtensionForInstallation($extensionKey);
                                        $this->dependencyErrors = array_merge($unresolvedDependencyErrors, $this->dependencyErrors);
                                } else {
-                                       $this->getExtensionFromRepository($extensionKey, $dependency);
+                                       $extension = $this->listUtility->getExtension($extensionKey);
+                                       $availableVersion = $extension->getPackageMetaData()->getVersion();
+                                       if (version_compare($availableVersion, $dependency->getHighestVersion()) === -1) {
+                                               try {
+                                                       $this->getExtensionFromRepository($extensionKey, $dependency);
+                                               } catch (Exception\MissingExtensionDependencyException $e) {
+                                                       throw new Exception\MissingVersionDependencyException(
+                                                               'The extension ' . $extensionKey . ' is available in version ' . $availableVersion
+                                                                       . ' but is needed in version ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion() . ' and could not be fetched from TER',
+                                                               1430560390
+                                                       );
+                                               }
+                                       } else {
+                                               throw new Exception\MissingVersionDependencyException(
+                                                       'The extension ' . $extensionKey . ' is available in version ' . $availableVersion
+                                                               . ' but is needed in version ' . $dependency->getLowestVersion() . ' - ' . $dependency->getHighestVersion(),
+                                                       1430562374
+                                               );
+                                       }
                                }
                        } else {
+                               $unresolvedDependencyErrors = $this->dependencyErrors;
                                $this->getExtensionFromRepository($extensionKey, $dependency);
+                               $this->dependencyErrors = array_merge($unresolvedDependencyErrors, $this->dependencyErrors);
                        }
                }
                return FALSE;
@@ -276,6 +306,7 @@ class DependencyUtility implements \TYPO3\CMS\Core\SingletonInterface {
         * @param string $extensionKey
         * @param Dependency $dependency
         * @return void
+        * @throws Exception\UnresolvedDependencyException
         */
        protected function getExtensionFromRepository($extensionKey, Dependency $dependency) {
                if (!$this->getExtensionFromInExtensionRepository($extensionKey, $dependency)) {
@@ -314,10 +345,17 @@ class DependencyUtility implements \TYPO3\CMS\Core\SingletonInterface {
                $isExtensionDownloadableFromTer = $this->isExtensionDownloadableFromTer($extensionKey);
                if (!$isExtensionDownloadableFromTer) {
                        if (!$this->skipSystemDependencyCheck) {
-                               throw new Exception\MissingExtensionDependencyException(
-                                       'The extension ' . $extensionKey . ' is not available from TER.',
-                                       1399161266
-                               );
+                               if ($this->extensionRepository->countAll() > 0) {
+                                       throw new Exception\MissingExtensionDependencyException(
+                                               'The extension ' . $extensionKey . ' is not available from TER.',
+                                               1399161266
+                                       );
+                               } else {
+                                       throw new Exception\MissingExtensionDependencyException(
+                                               'The extension ' . $extensionKey . ' could not be checked. Please update your Extension-List from TYPO3 Extension Repository (TER).',
+                                               1430580308
+                                       );
+                               }
                        }
                        return;
                }
index e1a0ffb..89d7a9e 100644 (file)
@@ -88,6 +88,15 @@ class ListUtility implements \TYPO3\CMS\Core\SingletonInterface {
        }
 
        /**
+        * @param string $extensionKey
+        * @return \TYPO3\Flow\Package\PackageInterface
+        * @throws \TYPO3\Flow\Package\Exception\UnknownPackageException if the specified package is unknown
+        */
+       public function getExtension($extensionKey) {
+               return $this->packageManager->getPackage($extensionKey);
+       }
+
+       /**
         * Emits packages may have changed signal
         */
        protected function emitPackagesMayHaveChangedSignal() {
index 0262db2..443f1d1 100644 (file)
@@ -103,6 +103,7 @@ class Helper implements \TYPO3\CMS\Core\SingletonInterface {
         *
         * @access public
         * @return void
+        * @throws ExtensionManagerException
         * @see fetchFile()
         */
        public function fetchExtListFile() {
@@ -116,6 +117,7 @@ class Helper implements \TYPO3\CMS\Core\SingletonInterface {
         *
         * @access public
         * @return void
+        * @throws ExtensionManagerException
         * @see fetchFile()
         */
        public function fetchMirrorListFile() {
@@ -217,6 +219,7 @@ class Helper implements \TYPO3\CMS\Core\SingletonInterface {
         * @access public
         * @param boolean $forcedUpdateFromRemote if boolean TRUE, mirror configuration will always retrieved from remote server
         * @return \TYPO3\CMS\Extensionmanager\Domain\Model\Mirrors instance of repository mirrors class
+        * @throws ExtensionManagerException
         */
        public function getMirrors($forcedUpdateFromRemote = TRUE) {
                $assignedMirror = $this->repository->getMirrors();
index 7de8115..2bbee67 100644 (file)
 
                                </source>
                        </trans-unit>
+                       <trans-unit id="downloadExtension.dependencies.extensionWithVersion">
+                               <source>%1$s (new version %2$s)</source>
+                       </trans-unit>
                        <trans-unit id="downloadExtension.dependencies.typeHeadline" xml:space="preserve">
                                <source>&lt;h3&gt;Extensions marked for %s:&lt;/h3&gt;
 %s