[BUGFIX] Remove failing extension installations 23/29123/12
authorNicole Cordes <typo3@cordes.co>
Wed, 2 Apr 2014 19:13:55 +0000 (21:13 +0200)
committerHelmut Hummel <helmut.hummel@typo3.org>
Tue, 13 May 2014 15:37:40 +0000 (17:37 +0200)
This patch takes care about removing extracted extension folders if an
installation fails. For new extensions the folder is simply removed, for
already existing ones a backup in typo3temp is done before the
installation process and restored if anything fails.

Resolves: #57606
Releases: 6.2
Change-Id: If6f251ebc5950aecfcdb97d722146d95cb7cfa74
Reviewed-on: https://review.typo3.org/29123
Reviewed-by: Markus Klein
Tested-by: Markus Klein
Reviewed-by: Helmut Hummel
Tested-by: Helmut Hummel
typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php
typo3/sysext/extensionmanager/Classes/Utility/FileHandlingUtility.php

index 7dd74e9..3eac91e 100644 (file)
@@ -26,6 +26,8 @@ namespace TYPO3\CMS\Extensionmanager\Controller;
  *
  *  This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
 
 /**
@@ -55,6 +57,16 @@ class UploadExtensionFileController extends AbstractController {
        protected $installUtility;
 
        /**
+        * @var string
+        */
+       protected $extensionBackupPath = '';
+
+       /**
+        * @var bool
+        */
+       protected $removeFromOriginalPath = FALSE;
+
+       /**
         * Render upload extension form
         *
         * @return void
@@ -82,7 +94,7 @@ class UploadExtensionFileController extends AbstractController {
                                throw new ExtensionManagerException('Wrong file format given.', 1342858853);
                        }
                        if (!empty($file['tmp_name']['extensionFile'])) {
-                               $tempFile = \TYPO3\CMS\Core\Utility\GeneralUtility::upload_to_tempfile($file['tmp_name']['extensionFile']);
+                               $tempFile = GeneralUtility::upload_to_tempfile($file['tmp_name']['extensionFile']);
                        } else {
                                throw new ExtensionManagerException(
                                        'Creating temporary file failed. Check your upload_max_filesize and post_max_size limits.',
@@ -96,6 +108,7 @@ class UploadExtensionFileController extends AbstractController {
                        }
                        $this->view->assign('extensionKey', $extensionData['extKey']);
                } catch (\Exception $exception) {
+                       $this->removeExtensionAndRestoreFromBackup($fileName);
                        $this->view->assign('error', $exception->getMessage());
                }
        }
@@ -109,7 +122,7 @@ class UploadExtensionFileController extends AbstractController {
         * @return array
         */
        protected function getExtensionFromT3xFile($file, $overwrite = FALSE) {
-               $fileContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($file);
+               $fileContent = GeneralUtility::getUrl($file);
                if (!$fileContent) {
                        throw new ExtensionManagerException('File had no or wrong content.', 1342859339);
                }
@@ -117,11 +130,17 @@ class UploadExtensionFileController extends AbstractController {
                if (empty($extensionData['extKey'])) {
                        throw new ExtensionManagerException('Decoding the file went wrong. No extension key found', 1342864309);
                }
-               if (!$overwrite && $this->installUtility->isAvailable($extensionData['extKey'])) {
+               $isExtensionAvailable = $this->installUtility->isAvailable($extensionData['extKey']);
+               if (!$overwrite && $isExtensionAvailable) {
                        throw new ExtensionManagerException($this->translate('extensionList.overwritingDisabled'), 1342864310);
                }
+               if ($isExtensionAvailable) {
+                       $this->copyExtensionFolderToTempFolder($extensionData['extKey']);
+               }
+               $this->removeFromOriginalPath = TRUE;
                $this->fileHandlingUtility->unpackExtensionFromExtensionDataArray($extensionData);
                $this->installUtility->install($extensionData['extKey']);
+               $this->removeBackupFolder();
                return $extensionData;
        }
 
@@ -138,14 +157,76 @@ class UploadExtensionFileController extends AbstractController {
         * @throws ExtensionManagerException
         */
        protected function getExtensionFromZipFile($file, $fileName, $overwrite = FALSE) {
-                       // Remove version and ending from filename to determine extension key
-               $extensionKey = preg_replace('/_(\d+)(\.|\-)(\d+)(\.|\-)(\d+).*/i', '', strtolower(substr($fileName, 0, -4)));
-               if (!$overwrite && $this->installUtility->isAvailable($extensionKey)) {
+                       // Remove version and extension from filename to determine the extension key
+               $extensionKey = $this->getExtensionKeyFromFileName($fileName);
+               $isExtensionAvailable = $this->installUtility->isAvailable($extensionKey);
+               if (!$overwrite && $isExtensionAvailable) {
                        throw new ExtensionManagerException('Extension is already available and overwriting is disabled.', 1342864311);
                }
+               if ($isExtensionAvailable) {
+                       $this->copyExtensionFolderToTempFolder($extensionKey);
+               }
+               $this->removeFromOriginalPath = TRUE;
                $this->fileHandlingUtility->unzipExtensionFromFile($file, $extensionKey);
                $this->installUtility->install($extensionKey);
+               $this->removeBackupFolder();
+
                return array('extKey' => $extensionKey);
        }
 
+       /**
+        * Removes version and file extension from filename to determine extension key
+        *
+        * @param string $fileName
+        * @return string
+        */
+       protected function getExtensionKeyFromFileName($fileName) {
+               return preg_replace('/_(\\d+)(\\.|\\-)(\\d+)(\\.|\\-)(\\d+).*/i', '', strtolower(substr($fileName, 0, -4)));
+       }
+
+       /**
+        * Copies current extension folder to typo3temp directory as backup
+        *
+        * @param string $extensionKey
+        * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
+        * @return void
+        */
+       protected function copyExtensionFolderToTempFolder($extensionKey) {
+               $this->extensionBackupPath = PATH_site . 'typo3temp/' . $extensionKey . substr(sha1($extensionKey . microtime()), 0, 7) . '/';
+               GeneralUtility::mkdir($this->extensionBackupPath);
+               GeneralUtility::copyDirectory(
+                       $this->fileHandlingUtility->getExtensionDir($extensionKey),
+                       $this->extensionBackupPath
+               );
+       }
+
+       /**
+        * Removes the extension directory and restores the extension from the backup directory
+        *
+        * @param string $fileName
+        * @see UploadExtensionFileController::extractAction
+        * @return void
+        */
+       protected function removeExtensionAndRestoreFromBackup($fileName) {
+               $extDirPath = $this->fileHandlingUtility->getExtensionDir($this->getExtensionKeyFromFileName($fileName));
+               if ($this->removeFromOriginalPath && is_dir($extDirPath)) {
+                       GeneralUtility::rmdir($extDirPath, TRUE);
+               }
+               if (!empty($this->extensionBackupPath)) {
+                       GeneralUtility::mkdir($extDirPath);
+                       GeneralUtility::copyDirectory($this->extensionBackupPath, $extDirPath);
+                       $this->removeBackupFolder();
+               }
+       }
+
+       /**
+        * Removes the backup folder in typo3temp
+        * @return void
+        */
+       protected function removeBackupFolder() {
+               if (!empty($this->extensionBackupPath)) {
+                       GeneralUtility::rmdir($this->extensionBackupPath, TRUE);
+                       $this->extensionBackupPath = '';
+               }
+       }
 }
index 31de059..cf38112 100644 (file)
@@ -1,6 +1,8 @@
 <?php
 namespace TYPO3\CMS\Extensionmanager\Utility;
-use \TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
 use TYPO3\CMS\Lang\LanguageService;
 
@@ -52,11 +54,11 @@ class FileHandlingUtility implements \TYPO3\CMS\Core\SingletonInterface {
         * Unpack an extension in t3x data format and write files
         *
         * @param array $extensionData
-        * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
+        * @param Extension $extension
         * @param string $pathType
         * @return void
         */
-       public function unpackExtensionFromExtensionDataArray(array $extensionData, \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension = NULL, $pathType = 'Local') {
+       public function unpackExtensionFromExtensionDataArray(array $extensionData, Extension $extension = NULL, $pathType = 'Local') {
                $extensionDir = $this->makeAndClearExtensionDir($extensionData['extKey'], $pathType);
                $files = $this->extractFilesArrayFromExtensionData($extensionData);
                $directories = $this->extractDirectoriesFromExtensionData($files);
@@ -142,30 +144,40 @@ class FileHandlingUtility implements \TYPO3\CMS\Core\SingletonInterface {
         * Removes the current extension of $type and creates the base folder for
         * the new one (which is going to be imported)
         *
-        * @param string $extensionkey
+        * @param string $extensionKey
         * @param string $pathType Extension installation scope (Local,Global,System)
         * @throws ExtensionManagerException
         * @return string
         */
-       protected function makeAndClearExtensionDir($extensionkey, $pathType = 'Local') {
-               $paths = \TYPO3\CMS\Extensionmanager\Domain\Model\Extension::returnInstallPaths();
-               $path = $paths[$pathType];
-               if (!$path || !is_dir($path) || !$extensionkey) {
-                       throw new ExtensionManagerException(
-                               sprintf('ERROR: The extension install path "%s" was no directory!', $this->getRelativePath($path)),
-                               1337280417
-                       );
-               } else {
-                       $extDirPath = $path . $extensionkey . '/';
-                       if (is_dir($extDirPath)) {
-                               $this->removeDirectory($extDirPath);
-                       }
-                       $this->addDirectory($extDirPath);
+       protected function makeAndClearExtensionDir($extensionKey, $pathType = 'Local') {
+               $extDirPath = $this->getExtensionDir($extensionKey, $pathType);
+               if (is_dir($extDirPath)) {
+                       $this->removeDirectory($extDirPath);
                }
+               $this->addDirectory($extDirPath);
+
                return $extDirPath;
        }
 
        /**
+        * Returns the installation directory for an extension depending on the installation scope
+        *
+        * @param string $extensionKey
+        * @param string $pathType Extension installation scope (Local,Global,System)
+        * @return string
+        * @throws ExtensionManagerException
+        */
+       public function getExtensionDir($extensionKey, $pathType = 'Local') {
+               $paths = Extension::returnInstallPaths();
+               $path = $paths[$pathType];
+               if (!$path || !is_dir($path) || !$extensionKey) {
+                       throw new ExtensionManagerException(sprintf('ERROR: The extension install path "%s" was no directory!', $this->getRelativePath($path)), 1337280417);
+               }
+
+               return $path . $extensionKey . '/';
+       }
+
+       /**
         * Add specified directory
         *
         * @param string $extDirPath
@@ -265,10 +277,10 @@ class FileHandlingUtility implements \TYPO3\CMS\Core\SingletonInterface {
         *
         * @param array $extensionData
         * @param string $rootPath
-        * @param \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension
+        * @param Extension $extension
         * @return void
         */
-       protected function writeEmConfToFile(array $extensionData, $rootPath, \TYPO3\CMS\Extensionmanager\Domain\Model\Extension $extension = NULL) {
+       protected function writeEmConfToFile(array $extensionData, $rootPath, Extension $extension = NULL) {
                $emConfContent = $this->emConfUtility->constructEmConf($extensionData, $extension);
                GeneralUtility::writeFile($rootPath . 'ext_emconf.php', $emConfContent);
        }
@@ -280,7 +292,7 @@ class FileHandlingUtility implements \TYPO3\CMS\Core\SingletonInterface {
         * @return boolean
         */
        public function isValidExtensionPath($path) {
-               $allowedPaths = \TYPO3\CMS\Extensionmanager\Domain\Model\Extension::returnAllowedInstallPaths();
+               $allowedPaths = Extension::returnAllowedInstallPaths();
                foreach ($allowedPaths as $allowedPath) {
                        if (GeneralUtility::isFirstPartOfStr($path, $allowedPath)) {
                                return TRUE;
@@ -311,7 +323,7 @@ class FileHandlingUtility implements \TYPO3\CMS\Core\SingletonInterface {
         * @return string
         */
        protected function getRelativePath($absolutePath) {
-               return \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($absolutePath);
+               return PathUtility::stripPathSitePrefix($absolutePath);
        }
 
        /**
@@ -349,7 +361,7 @@ class FileHandlingUtility implements \TYPO3\CMS\Core\SingletonInterface {
                $extensionPath = $this->getAbsoluteExtensionPath($extension);
 
                // Add trailing slash to the extension path, getAllFilesAndFoldersInPath explicitly requires that.
-               $extensionPath = \TYPO3\CMS\Core\Utility\PathUtility::sanitizeTrailingSeparator($extensionPath);
+               $extensionPath = PathUtility::sanitizeTrailingSeparator($extensionPath);
 
                $version = $this->getExtensionVersion($extension);
                if (empty($version)) {
@@ -364,7 +376,7 @@ class FileHandlingUtility implements \TYPO3\CMS\Core\SingletonInterface {
                $excludePattern = $GLOBALS['TYPO3_CONF_VARS']['EXT']['excludeForPackaging'];
 
                // Get all the files of the extension, but exclude the ones specified in the excludePattern
-               $files = \TYPO3\CMS\Core\Utility\GeneralUtility::getAllFilesAndFoldersInPath(
+               $files = GeneralUtility::getAllFilesAndFoldersInPath(
                        array(),                        // No files pre-added
                        $extensionPath,         // Start from here
                        '',                                     // Do not filter files by extension
@@ -374,7 +386,7 @@ class FileHandlingUtility implements \TYPO3\CMS\Core\SingletonInterface {
                );
 
                // Make paths relative to extension root directory.
-               $files = \TYPO3\CMS\Core\Utility\GeneralUtility::removePrefixPathFromList($files, $extensionPath);
+               $files = GeneralUtility::removePrefixPathFromList($files, $extensionPath);
 
                // Remove the one empty path that is the extension dir itself.
                $files = array_filter($files);