From 22695e3b2c5940c0be31541b16ff589390429826 Mon Sep 17 00:00:00 2001 From: Nicole Cordes <typo3@cordes.co> Date: Thu, 27 Mar 2014 22:30:58 +0100 Subject: [PATCH] [TASK] EM: Add possibility to bypass system dependency checks 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 --- .../Classes/Controller/AbstractController.php | 23 +++++++ .../Classes/Controller/ActionController.php | 13 +++- .../Classes/Controller/DownloadController.php | 13 +++- .../UploadExtensionFileController.php | 44 +++++++++++- .../Service/ExtensionManagementService.php | 17 +++++ .../Classes/Utility/DependencyUtility.php | 67 ++++++++++++++++--- .../Resources/Private/Language/locallang.xlf | 6 ++ .../Resources/Public/JavaScript/main.js | 19 ++++++ .../Resources/Public/JavaScript/ter.js | 26 ++++++- typo3/sysext/extensionmanager/ext_tables.php | 4 +- 10 files changed, 213 insertions(+), 19 deletions(-) diff --git a/typo3/sysext/extensionmanager/Classes/Controller/AbstractController.php b/typo3/sysext/extensionmanager/Classes/Controller/AbstractController.php index 9e18683aa932..1acc59bcb8ed 100644 --- a/typo3/sysext/extensionmanager/Classes/Controller/AbstractController.php +++ b/typo3/sysext/extensionmanager/Classes/Controller/AbstractController.php @@ -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>'; + } } diff --git a/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php b/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php index 396fa62be9c3..047bde035865 100644 --- a/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php +++ b/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php @@ -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); @@ -86,6 +86,17 @@ class ActionController extends AbstractController { $this->redirect('index', 'List', NULL, array(self::TRIGGER_RefreshModuleMenu => TRUE)); } + /** + * 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) * diff --git a/typo3/sysext/extensionmanager/Classes/Controller/DownloadController.php b/typo3/sysext/extensionmanager/Classes/Controller/DownloadController.php index 7575be013953..af532b6cf7d9 100644 --- a/typo3/sysext/extensionmanager/Classes/Controller/DownloadController.php +++ b/typo3/sysext/extensionmanager/Classes/Controller/DownloadController.php @@ -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) @@ -108,6 +108,17 @@ class DownloadController extends AbstractController { ->assign('title', $title); } + /** + * 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 * diff --git a/typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php b/typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php index 7dd74e918600..c4f4963f6f63 100644 --- a/typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php +++ b/typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php @@ -54,6 +54,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 * @@ -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; + } } diff --git a/typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php b/typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php index 32d5d3f26445..fe602dbd8c0a 100644 --- a/typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php +++ b/typo3/sysext/extensionmanager/Classes/Service/ExtensionManagementService.php @@ -71,6 +71,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) { @@ -277,6 +285,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 */ diff --git a/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php b/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php index 5a0da13f5779..32e6df11d16a 100644 --- a/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php +++ b/typo3/sysext/extensionmanager/Classes/Utility/DependencyUtility.php @@ -76,8 +76,19 @@ 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; @@ -102,6 +113,14 @@ class DependencyUtility implements \TYPO3\CMS\Core\SingletonInterface { $this->checkDependencies($dependencies); } + /** + * @param bool $skipSpecialDependencyCheck + * @return void + */ + public function setSkipSystemDependencyCheck($skipSpecialDependencyCheck) { + $this->skipSystemDependencyCheck = $skipSpecialDependencyCheck; + } + /** * Checks dependencies for special cases (currently typo3 and php) * @@ -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)) { diff --git a/typo3/sysext/extensionmanager/Resources/Private/Language/locallang.xlf b/typo3/sysext/extensionmanager/Resources/Private/Language/locallang.xlf index e0b816c8c6b9..2d89de6985da 100644 --- a/typo3/sysext/extensionmanager/Resources/Private/Language/locallang.xlf +++ b/typo3/sysext/extensionmanager/Resources/Private/Language/locallang.xlf @@ -177,6 +177,12 @@ <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> diff --git a/typo3/sysext/extensionmanager/Resources/Public/JavaScript/main.js b/typo3/sysext/extensionmanager/Resources/Public/JavaScript/main.js index f775044c192c..1f2b24a8c2e2 100644 --- a/typo3/sysext/extensionmanager/Resources/Public/JavaScript/main.js +++ b/typo3/sysext/extensionmanager/Resources/Public/JavaScript/main.js @@ -122,6 +122,25 @@ } 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', '#'); diff --git a/typo3/sysext/extensionmanager/Resources/Public/JavaScript/ter.js b/typo3/sysext/extensionmanager/Resources/Public/JavaScript/ter.js index 23806abf69e1..09c5005d6bc3 100644 --- a/typo3/sysext/extensionmanager/Resources/Public/JavaScript/ter.js +++ b/typo3/sysext/extensionmanager/Resources/Public/JavaScript/ter.js @@ -82,6 +82,27 @@ }); } + 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({ @@ -93,7 +114,8 @@ } 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 = []; @@ -114,7 +136,7 @@ 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>'; diff --git a/typo3/sysext/extensionmanager/ext_tables.php b/typo3/sysext/extensionmanager/ext_tables.php index 3727ab8c6ad6..4d1085d2ade8 100644 --- a/typo3/sysext/extensionmanager/ext_tables.php +++ b/typo3/sysext/extensionmanager/ext_tables.php @@ -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', -- GitLab