[TASK] EM: Add possibility to bypass system dependency checks 24/28924/15
authorNicole Cordes <typo3@cordes.co>
Thu, 27 Mar 2014 21:30:58 +0000 (22:30 +0100)
committerWouter Wolters <typo3@wouterwolters.nl>
Sat, 3 May 2014 12:38:47 +0000 (14:38 +0200)
This patch extends the extension manager in three different ways. First
all dependencies are checked and error messages are bundled to show all
problems to the user.

Secondly on uploading an extension file the installation process is
started automatically.

The main change of this patch is to introduce a new function to prevent
(system) dependency checks. This means the checks for TYPO3 and PHP
version don't throw an exception anymore. Required extensions are tried
to be fetched from TER but don't stop installation either.

If errors occur on first installation process, a link to force the
installation is added to the notification. Before any installation
can be run, a dialog with a "break warning" is shown and has to be
confirmed by clicking the unfocussed field.

To be able to skip the system dependency check, a new property for
ExtensionManagementService and DependencyUtility is introduced which
controls disabling the system dependency check. All extension
dependencies are still resolved and needed extensions are tried
to be fetched from TER.

Resolves: #54512
Releases: 6.2
Change-Id: Ia11b7770a2773538bda48d889282ff51bf187c84
Reviewed-on: https://review.typo3.org/28924
Reviewed-by: Sascha Wilking
Tested-by: Sascha Wilking
Reviewed-by: Wouter Wolters
Tested-by: Wouter Wolters
typo3/sysext/extensionmanager/Classes/Controller/AbstractController.php
typo3/sysext/extensionmanager/Classes/Controller/ActionController.php
typo3/sysext/extensionmanager/Classes/Controller/DownloadController.php
typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php
typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php
typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php
typo3/sysext/extensionmanager/Resources/Private/Language/locallang.xlf
typo3/sysext/extensionmanager/Resources/Public/JavaScript/main.js
typo3/sysext/extensionmanager/Resources/Public/JavaScript/ter.js
typo3/sysext/extensionmanager/ext_tables.php

index 9e18683..1acc59b 100644 (file)
@@ -87,4 +87,27 @@ class AbstractController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControl
 
                $this->view->assign('triggers', $triggers);
        }
+
+       /**
+        * Renders the link to force the installation of an extension. The handling is implemented in the controller.
+        *
+        * @param string $extensionKey
+        * @param string $controllerName
+        * @return string
+        */
+       protected function getForceInstallationMessage($extensionKey, $controllerName = NULL) {
+               $forceInstallationUri = $this->uriBuilder->uriFor(
+                       'installExtensionWithoutSystemDependencyCheck',
+                       array_merge(
+                               $this->request->getArguments(),
+                               array(
+                                       'extensionKey' => $extensionKey,
+                                       'skipSystemDependencyCheck' => 1
+                               )
+                       ),
+                       $controllerName
+               );
+
+               return '<br /><br /><a class="skipSystemDependencyCheck" href="' . htmlspecialchars($forceInstallationUri) . '">I know what I\'m doing, install anyway!</a>';
+       }
 }
index 396fa62..047bde0 100644 (file)
@@ -78,7 +78,7 @@ class ActionController extends AbstractController {
                                );
                        }
                } catch (\TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException $e) {
-                       $message = nl2br(htmlspecialchars($e->getMessage()));
+                       $message = nl2br(htmlspecialchars($e->getMessage())) . $this->getForceInstallationMessage($extensionKey);
                        $this->addFlashMessage($message, '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
                } catch (\TYPO3\Flow\Package\Exception\PackageStatesFileNotWritableException $e) {
                        $this->addFlashMessage(htmlspecialchars($e->getMessage()), '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
@@ -87,6 +87,17 @@ class ActionController extends AbstractController {
        }
 
        /**
+        * Install an extension and omit dependency checking
+        *
+        * @param string $extensionKey
+        * @return void
+        */
+       public function installExtensionWithoutSystemDependencyCheckAction($extensionKey) {
+               $this->managementService->setSkipSystemDependencyCheck(TRUE);
+               $this->forward('toggleExtensionInstallationState', NULL, NULL, array('extensionKey' => $extensionKey));
+       }
+
+       /**
         * Remove an extension (if it is still installed, uninstall it first)
         *
         * @param string $extension
index 7575be0..af532b6 100644 (file)
@@ -99,7 +99,7 @@ class DownloadController extends AbstractController {
                } catch (\Exception $e) {
                        $hasErrors = TRUE;
                        $title = $this->translate('downloadExtension.dependencies.errorTitle');
-                       $message = $e->getMessage();
+                       $message = nl2br($e->getMessage()) . $this->getForceInstallationMessage($extension->getExtensionKey());
                }
                $this->view->assign('extension', $extension)
                        ->assign('hasDependencies', $hasDependencies)
@@ -109,6 +109,17 @@ class DownloadController extends AbstractController {
        }
 
        /**
+        * Check extension dependencies with special dependencies
+        *
+        * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
+        * @throws \Exception
+        */
+       public function installExtensionWithoutSystemDependencyCheckAction(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension) {
+               $this->managementService->setSkipSystemDependencyCheck(TRUE);
+               $this->forward('installFromTer', NULL, NULL, array('extension' => $extension, 'downloadPath' => 'Local'));
+       }
+
+       /**
         * Install an extension from TER action
         *
         * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
index 7dd74e9..c4f4963 100644 (file)
@@ -55,6 +55,18 @@ class UploadExtensionFileController extends AbstractController {
        protected $installUtility;
 
        /**
+        * @var \TYPO3\CMS\Extensionmanager\Service\ExtensionManagementService
+        * @inject
+        */
+       protected $managementService;
+
+       /**
+        * @var \TYPO3\CMS\Extensionmanager\Utility\ExtensionModelUtility
+        * @inject
+        */
+       protected $extensionModelUtility;
+
+       /**
         * Render upload extension form
         *
         * @return void
@@ -121,7 +133,7 @@ class UploadExtensionFileController extends AbstractController {
                        throw new ExtensionManagerException($this->translate('extensionList.overwritingDisabled'), 1342864310);
                }
                $this->fileHandlingUtility->unpackExtensionFromExtensionDataArray($extensionData);
-               $this->installUtility->install($extensionData['extKey']);
+               $this->installExtension($extensionData['extKey']);
                return $extensionData;
        }
 
@@ -144,8 +156,36 @@ class UploadExtensionFileController extends AbstractController {
                        throw new ExtensionManagerException('Extension is already available and overwriting is disabled.', 1342864311);
                }
                $this->fileHandlingUtility->unzipExtensionFromFile($file, $extensionKey);
-               $this->installUtility->install($extensionKey);
+               $this->installExtension($extensionKey);
                return array('extKey' => $extensionKey);
        }
 
+       /**
+        * Install extension if not yet installed
+        *
+        * @param string $extensionKey
+        * @return bool
+        */
+       protected function installExtension($extensionKey) {
+               $installedExtensions = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getLoadedExtensionListArray();
+               if (in_array($extensionKey, $installedExtensions)) {
+                       return TRUE;
+               }
+               try {
+                       // install
+                       $this->managementService->resolveDependenciesAndInstall(
+                               $this->extensionModelUtility->mapExtensionArrayToModel(
+                                       $this->installUtility->enrichExtensionWithDetails($extensionKey)
+                               )
+                       );
+                       return TRUE;
+               } catch (ExtensionManagerException $e) {
+                       $message = nl2br(htmlspecialchars($e->getMessage())) . $this->getForceInstallationMessage($extensionKey, 'Action');
+                       $this->addFlashMessage($message, '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
+               } catch (\TYPO3\Flow\Package\Exception\PackageStatesFileNotWritableException $e) {
+                       $this->addFlashMessage(htmlspecialchars($e->getMessage()), '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
+               }
+
+               return FALSE;
+       }
 }
index 32d5d3f..fe602db 100644 (file)
@@ -72,6 +72,11 @@ class ExtensionManagementService implements \TYPO3\CMS\Core\SingletonInterface {
        protected $downloadUtility;
 
        /**
+        * @var bool
+        */
+       protected $skipSystemDependencyCheck = FALSE;
+
+       /**
         * @param string $extensionKey
         * @return void
         */
@@ -132,6 +137,8 @@ class ExtensionManagementService implements \TYPO3\CMS\Core\SingletonInterface {
                $this->downloadMainExtension($extension);
                $extensionKey = $extension->getExtensionKey();
                $this->setInExtensionRepository($extensionKey);
+
+               $this->dependencyUtility->setSkipSystemDependencyCheck($this->skipSystemDependencyCheck);
                $this->dependencyUtility->buildExtensionDependenciesTree($extension);
 
                $updatedDependencies = array();
@@ -253,6 +260,7 @@ class ExtensionManagementService implements \TYPO3\CMS\Core\SingletonInterface {
         * @return array
         */
        public function getAndResolveDependencies(Extension $extension) {
+               $this->dependencyUtility->setSkipSystemDependencyCheck($this->skipSystemDependencyCheck);
                $this->dependencyUtility->buildExtensionDependenciesTree($extension);
                $installQueue = $this->downloadQueue->getExtensionInstallStorage();
                if (is_array($installQueue) && count($installQueue) > 0) {
@@ -278,6 +286,15 @@ class ExtensionManagementService implements \TYPO3\CMS\Core\SingletonInterface {
        }
 
        /**
+        * Enables or disables the dependency check for system environment (PHP, TYPO3) before extension installation
+        *
+        * @param bool $skipSystemDependencyCheck
+        */
+       public function setSkipSystemDependencyCheck($skipSystemDependencyCheck) {
+               $this->skipSystemDependencyCheck = $skipSystemDependencyCheck;
+       }
+
+       /**
         * @param array $installQueue
         */
        protected function emitWillInstallExtensions(array $installQueue) {
index 5a0da13..32e6df1 100644 (file)
@@ -77,7 +77,18 @@ class DependencyUtility implements \TYPO3\CMS\Core\SingletonInterface {
        protected $localExtensionStorage = '';
 
        /**
+        * @var array
+        */
+       protected $dependencyErrors = array();
+
+       /**
+        * @var bool
+        */
+       protected $skipSystemDependencyCheck = FALSE;
+
+       /**
         * @param string $localExtensionStorage
+        * @return void
         */
        public function setLocalExtensionStorage($localExtensionStorage) {
                $this->localExtensionStorage = $localExtensionStorage;
@@ -103,6 +114,14 @@ class DependencyUtility implements \TYPO3\CMS\Core\SingletonInterface {
        }
 
        /**
+        * @param bool $skipSpecialDependencyCheck
+        * @return void
+        */
+       public function setSkipSystemDependencyCheck($skipSpecialDependencyCheck) {
+               $this->skipSystemDependencyCheck = $skipSpecialDependencyCheck;
+       }
+
+       /**
         * Checks dependencies for special cases (currently typo3 and php)
         *
         * @param \SplObjectStorage $dependencies
@@ -114,15 +133,30 @@ class DependencyUtility implements \TYPO3\CMS\Core\SingletonInterface {
                foreach ($dependencies as $dependency) {
                        /** @var Dependency $dependency */
                        $identifier = strtolower($dependency->getIdentifier());
-                       if (in_array($identifier, Dependency::$specialDependencies)) {
-                               $methodname = 'check' . ucfirst($identifier) . 'Dependency';
-                               $this->{$methodname}($dependency);
-                       } else {
-                               if ($dependency->getType() === 'depends') {
-                                       $dependenciesToResolve = !(bool) $this->checkExtensionDependency($dependency);
+                       try {
+                               if (in_array($identifier, Dependency::$specialDependencies)) {
+                                       if (!$this->skipSystemDependencyCheck) {
+                                               $methodName = 'check' . ucfirst($identifier) . 'Dependency';
+                                               $this->{$methodName}($dependency);
+                                       }
+                               } else {
+                                       if ($dependency->getType() === 'depends') {
+                                               $dependenciesToResolve = !(bool) $this->checkExtensionDependency($dependency);
+                                       }
+                               }
+                       } catch (ExtensionManagerException $e) {
+                               if (in_array($identifier, Dependency::$specialDependencies)) {
+                                       $this->dependencyErrors[] = $e->getMessage();
+                               } else {
+                                       $this->dependencyErrors[] = $identifier . ': ' . $e->getMessage();
                                }
                        }
                }
+
+               if (!empty($this->dependencyErrors)) {
+                       throw new ExtensionManagerException(implode(LF . LF, $this->dependencyErrors), 1396301826);
+               }
+
                return $dependenciesToResolve;
        }
 
@@ -263,17 +297,28 @@ class DependencyUtility implements \TYPO3\CMS\Core\SingletonInterface {
         * @return void
         */
        protected function getExtensionFromTer($extensionKey, Dependency $dependency) {
-               if (!$this->isExtensionDownloadableFromTer($extensionKey)) {
-                       throw new ExtensionManagerException('The extension ' . $extensionKey . ' is not available from TER.');
+               $isExtensionDownloadableFromTer = $this->isExtensionDownloadableFromTer($extensionKey);
+               if (!$isExtensionDownloadableFromTer) {
+                       if (!$this->skipSystemDependencyCheck) {
+                               throw new ExtensionManagerException('The extension ' . $extensionKey . ' is not available from TER.');
+                       }
+                       return;
                }
 
-               if (!$this->isDownloadableVersionCompatible($dependency)) {
-                       throw new ExtensionManagerException('No compatible version found for extension ' . $extensionKey);
+               $isDownloadableVersionCompatible = $this->isDownloadableVersionCompatible($dependency);
+               if (!$isDownloadableVersionCompatible) {
+                       if (!$this->skipSystemDependencyCheck) {
+                               throw new ExtensionManagerException('No compatible version found for extension ' . $extensionKey);
+                       }
+                       return;
                }
 
                $latestCompatibleExtensionByIntegerVersionDependency = $this->getLatestCompatibleExtensionByIntegerVersionDependency($dependency);
                if (!$latestCompatibleExtensionByIntegerVersionDependency instanceof \TYPO3\CMS\Extensionmanager\Domain\Model\Extension) {
-                       throw new ExtensionManagerException('Could not resolve dependency for "' . $dependency->getIdentifier() . '"');
+                       if (!$this->skipSystemDependencyCheck) {
+                               throw new ExtensionManagerException('Could not resolve dependency for "' . $dependency->getIdentifier() . '"');
+                       }
+                       return;
                }
 
                if ($this->isDependentExtensionLoaded($extensionKey)) {
index e0b816c..2d89de6 100644 (file)
                        <trans-unit id="uploadTemplate.extensionLabel" xml:space="preserve">
                                <source>Extension</source>
                        </trans-unit>
+                       <trans-unit id="extensionList.skipSystemDependencyCheckConfirmation.title" xml:space="preserve">
+                               <source>Install extension without system dependency check</source>
+                       </trans-unit>
+                       <trans-unit id="extensionList.skipSystemDependencyCheckConfirmation.message" xml:space="preserve">
+                               <source>PLEASE READ CAREFULLY! All depended extensions are fetched from TER if not existing yet. Version dependency check is skipped as well. Be aware that an installation without system dependency check may turn your installation unusable. In such a case manual intervention is required. We recommend to stop installation process.</source>
+                       </trans-unit>
                        <trans-unit id="extensionList.removalConfirmation.title" xml:space="preserve">
                                <source>Extension Removal</source>
                        </trans-unit>
index f775044..1f2b24a 100644 (file)
        }
 
        function bindActions() {
+               $('.skipSystemDependencyCheck').not('.transformed').each(function() {
+                       $(this).data('href', $(this).attr('href'));
+                       $(this).attr('href', '#');
+                       $(this).addClass('transformed');
+                       $(this).click(function (event) {
+                               event.preventDefault();
+                               TYPO3.Dialog.QuestionDialog({
+                                       title: TYPO3.l10n.localize('extensionList.skipSystemDependencyCheckConfirmation.title'),
+                                       msg: TYPO3.l10n.localize('extensionList.skipSystemDependencyCheckConfirmation.message'),
+                                       url: $(this).data('href'),
+                                       fn: function (button, dummy, dialog) {
+                                               if (button == 'no') {
+                                                       window.document.location = dialog.url;
+                                               }
+                                       }
+                               });
+                       })
+               });
+
                $('.removeExtension').not('.transformed').each(function() {
                        $(this).data('href', $(this).attr('href'));
                        $(this).attr('href', '#');
index 23806ab..09c5005 100644 (file)
                });
        }
 
+       function bindSkipSystemDependencyCheck() {
+               $('.skipSystemDependencyCheck').not('.transformed').each(function() {
+                       $(this).data('href', $(this).attr('href'));
+                       $(this).attr('href', '#');
+                       $(this).addClass('transformed');
+                       $(this).click(function () {
+                               TYPO3.Dialog.QuestionDialog({
+                                       title: TYPO3.l10n.localize('extensionList.skipSystemDependencyCheckConfirmation.title'),
+                                       msg: TYPO3.l10n.localize('extensionList.skipSystemDependencyCheckConfirmation.message'),
+                                       url: $(this).data('href'),
+                                       fn: function (button, dummy, dialog) {
+                                               if (button == 'no') {
+                                                       $('.typo3-extension-manager').mask();
+                                                       getResolveDependenciesAndInstallResult('yes', dummy, dialog);
+                                               }
+                                       }
+                               });
+                       })
+               });
+       }
+
        function getDependencies(data) {
                if (data.hasDependencies) {
                        TYPO3.Dialog.QuestionDialog({
                } else {
                        if(data.hasErrors) {
                                $('.typo3-extension-manager').unmask();
-                               TYPO3.Flashmessage.display(TYPO3.Severity.error, data.title, data.message, 10);
+                               TYPO3.Flashmessage.display(TYPO3.Severity.error, data.title, data.message, 15);
+                               bindSkipSystemDependencyCheck();
                        } else {
                                var button = 'yes';
                                var dialog = [];
                                success: function (data) {
                                        $('.typo3-extension-manager').unmask();
                                        if (data.errorMessage.length) {
-                                               TYPO3.Flashmessage.display(TYPO3.Severity.error, TYPO3.l10n.localize('extensionList.dependenciesResolveDownloadError.title'), data.errorMessage, 5);
+                                               TYPO3.Flashmessage.display(TYPO3.Severity.error, TYPO3.l10n.localize('extensionList.dependenciesResolveDownloadError.title'), data.errorMessage, 15);
                                        } else {
                                                var successMessage = TYPO3.l10n.localize('extensionList.dependenciesResolveDownloadSuccess.message').replace(/\{0\}/g, data.extension) + ' <br />';
                                                successMessage += '<br /><h3>' + TYPO3.l10n.localize('extensionList.dependenciesResolveDownloadSuccess.header') + ':</h3>';
index 3727ab8..4d1085d 100644 (file)
@@ -9,9 +9,9 @@ if (TYPO3_MODE === 'BE') {
                'tools',
                'extensionmanager', '', array(
                        'List' => 'index,ter,showAllVersions,distributions',
-                       'Action' => 'toggleExtensionInstallationState,removeExtension,downloadExtensionZip,downloadExtensionData',
+                       'Action' => 'toggleExtensionInstallationState,installExtensionWithoutSystemDependencyCheck,removeExtension,downloadExtensionZip,downloadExtensionData',
                        'Configuration' => 'showConfigurationForm,save',
-                       'Download' => 'checkDependencies,installFromTer,installDistribution,updateExtension,updateCommentForUpdatableVersions',
+                       'Download' => 'checkDependencies,installFromTer,installExtensionWithoutSystemDependencyCheck,installDistribution,updateExtension,updateCommentForUpdatableVersions',
                        'UpdateScript' => 'show',
                        'UpdateFromTer' => 'updateExtensionListFromTer',
                        'UploadExtensionFile' => 'form,extract',