[BUGFIX] Import skips files with non-existent target directory 40/45940/8
authorBernhard Kraft <kraftb@think-open.at>
Fri, 15 Jan 2016 13:33:32 +0000 (14:33 +0100)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Fri, 15 Jan 2016 17:29:03 +0000 (18:29 +0100)
When an import is taking place in which sys_file_storage records
get imported any files within this storage will not get imported
if the basePath of the sys_file_storage does not exist.

This patch displays an error message in such cases and prompts
the user to create the missing directory.

Resolves: #68791
Releases: master, 7.6
Change-Id: I7fb0f0cdf9b25b29b0a35781450020a59e0f03ab
Reviewed-on: https://review.typo3.org/45940
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
typo3/sysext/impexp/Classes/Controller/ImportExportController.php
typo3/sysext/impexp/Classes/Import.php
typo3/sysext/impexp/Resources/Private/Templates/ImportExport/Import.html

index d92b01a..4cedcae 100644 (file)
@@ -833,26 +833,14 @@ class ImportExportController extends BaseScriptClass
             }
 
             // Perform import or preview depending:
-            $extensionInstallationMessage = '';
             $inFile = $this->getFile($inData['file']);
             if ($inFile !== null && $inFile->exists()) {
                 $this->standaloneView->assign('metaDataInFileExists', true);
+                $importInhibitedMessages = array();
                 if ($import->loadFile($inFile->getForLocalProcessing(false), 1)) {
-                    // Check extension dependencies:
-                    $extKeysToInstall = array();
-                    if (is_array($import->dat['header']['extensionDependencies'])) {
-                        foreach ($import->dat['header']['extensionDependencies'] as $extKey) {
-                            if (!ExtensionManagementUtility::isLoaded($extKey)) {
-                                $extKeysToInstall[] = $extKey;
-                            }
-                        }
-                    }
-                    if (!empty($extKeysToInstall)) {
-                        $extensionInstallationMessage = 'Before you can install this T3D file you need to install the extensions "'
-                            . implode('", "', $extKeysToInstall) . '".';
-                    }
+                    $importInhibitedMessages = $import->checkImportPrerequisites();
                     if ($inData['import_file']) {
-                        if (empty($extKeysToInstall)) {
+                        if (empty($importInhibitedMessages)) {
                             $import->importData($this->id);
                             BackendUtility::setUpdateSignal('updatePageTree');
                         }
@@ -860,17 +848,22 @@ class ImportExportController extends BaseScriptClass
                     $import->display_import_pid_record = $this->pageinfo;
                     $this->standaloneView->assign('contentOverview',  $import->displayContentOverview());
                 }
+                // Compile messages which are inhibiting a proper import and add them to output.
+                if (!empty($importInhibitedMessages)) {
+                    $flashMessageQueue = GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier('impexp.errors');
+                    foreach ($importInhibitedMessages as $message) {
+                        $flashMessageQueue->addMessage(GeneralUtility::makeInstance(
+                            FlashMessage::class,
+                            $message,
+                            '',
+                            FlashMessage::ERROR
+                        ));
+                    }
+                }
             }
             // Print errors that might be:
             $errors = $import->printErrorLog();
             $this->standaloneView->assign('errors', trim($errors));
-            if ($extensionInstallationMessage) {
-                $this->standaloneView->assign(
-                    'extensionInstallationMessage',
-                    '<div style="border: 1px black solid; margin: 10px 10px 10px 10px; padding: 10px 10px 10px 10px;">'
-                    . $this->moduleTemplate->icons(1) . htmlspecialchars($extensionInstallationMessage) . '</div>'
-                );
-            }
         }
     }
 
index b2f5d05..8472ea1 100644 (file)
@@ -21,7 +21,9 @@ use TYPO3\CMS\Core\Resource\DuplicationBehavior;
 use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\FileInterface;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Resource\ResourceStorage;
 use TYPO3\CMS\Core\Resource\StorageRepository;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
@@ -74,7 +76,7 @@ class Import extends ImportExport
     /**
      * Array of current registered storage objects
      *
-     * @var array
+     * @var ResourceStorage[]
      */
     protected $storageObjects = array();
 
@@ -256,29 +258,16 @@ class Import extends ImportExport
             $storageRecord = $this->dat['records']['sys_file_storage:' . $sysFileStorageUid]['data'];
             // continue with Local, writable and online storage only
             if ($storageRecord['driver'] === 'Local' && $storageRecord['is_writable'] && $storageRecord['is_online']) {
-                $useThisStorageUidInsteadOfTheOneInImport = 0;
-                /** @var $localStorage \TYPO3\CMS\Core\Resource\ResourceStorage */
                 foreach ($this->storageObjects as $localStorage) {
-                    // check the available storage for Local, writable and online ones
-                    if ($localStorage->getDriverType() === 'Local' && $localStorage->isWritable() && $localStorage->isOnline()) {
-                        // check if there is already an identical storage present (same pathType and basePath)
-                        $storageRecordConfiguration = ResourceFactory::getInstance()->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
-                        $localStorageRecordConfiguration = $localStorage->getConfiguration();
-                        if (
-                            $storageRecordConfiguration['pathType'] === $localStorageRecordConfiguration['pathType']
-                            && $storageRecordConfiguration['basePath'] === $localStorageRecordConfiguration['basePath']
-                        ) {
-                            // same storage is already present
-                            $useThisStorageUidInsteadOfTheOneInImport = $localStorage->getUid();
-                            break;
-                        }
+                    if ($this->isEquivalentObjectStorage($localStorage, $storageRecord)) {
+                        $this->import_mapId['sys_file_storage'][$sysFileStorageUid] = $localStorage->getUid();
+                        break;
                     }
                 }
-                if ($useThisStorageUidInsteadOfTheOneInImport > 0) {
-                    // same storage is already present; map the to be imported one to the present one
-                    $this->import_mapId['sys_file_storage'][$sysFileStorageUid] = $useThisStorageUidInsteadOfTheOneInImport;
-                } else {
+
+                if (!isset($this->import_mapId['sys_file_storage'][$sysFileStorageUid])) {
                     // Local, writable and online storage. Is allowed to be used to later write files in.
+                    // Does currently not exist so add the record.
                     $this->addSingle('sys_file_storage', $sysFileStorageUid, 0);
                 }
             } else {
@@ -315,6 +304,95 @@ class Import extends ImportExport
     }
 
     /**
+     * Determines whether the passed storage object and record (sys_file_storage) can be
+     * seen as equivalent during import.
+     *
+     * @param ResourceStorage $storageObject The storage object which should get compared
+     * @param array $storageRecord The storage record which should get compared
+     * @return bool Returns TRUE when both object storages can be seen as equivalent
+     */
+    protected function isEquivalentObjectStorage(ResourceStorage $storageObject, array $storageRecord) {
+        // compare the properties: driver, writable and online
+        if (
+            $storageObject->getDriverType() === $storageRecord['driver']
+            && (bool)$storageObject->isWritable() === (bool)$storageRecord['is_writable']
+            && (bool)$storageObject->isOnline() === (bool)$storageRecord['is_online']
+        ) {
+            $storageRecordConfiguration = ResourceFactory::getInstance()->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
+            $storageObjectConfiguration = $storageObject->getConfiguration();
+            // compare the properties: pathType and basePath
+            if (
+                $storageRecordConfiguration['pathType'] === $storageObjectConfiguration['pathType']
+                && $storageRecordConfiguration['basePath'] === $storageObjectConfiguration['basePath']
+            ) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checks any prerequisites necessary to get fullfilled before import
+     *
+     * @return array Messages explaining issues which need to get resolved before import
+     */
+    public function checkImportPrerequisites() {
+        $messages = array();
+
+        // Check #1: Extension dependencies
+        $extKeysToInstall = array();
+        if (is_array($this->dat['header']['extensionDependencies'])) {
+            foreach ($this->dat['header']['extensionDependencies'] as $extKey) {
+                if (!ExtensionManagementUtility::isLoaded($extKey)) {
+                    $extKeysToInstall[] = $extKey;
+                }
+            }
+        }
+        if (!empty($extKeysToInstall)) {
+            $messages['missingExtensions'] = 'Before you can install this T3D file you need to install the extensions "' . implode('", "', $extKeysToInstall) . '".';
+        }
+
+        // Check #2: If the path for every local storage object exists.
+        // Else files can't get moved into a newly imported storage.
+        foreach ($this->dat['header']['records']['sys_file_storage'] as $sysFileStorageUid => $_) {
+            $storageRecord = $this->dat['records']['sys_file_storage:' . $sysFileStorageUid]['data'];
+            // continue with Local, writable and online storage only
+            if ($storageRecord['driver'] === 'Local' && $storageRecord['is_writable'] && $storageRecord['is_online']) {
+                $storageExists = false;
+                /** @var $localStorage \TYPO3\CMS\Core\Resource\ResourceStorage */
+                foreach ($this->storageObjects as $localStorage) {
+                    if ($this->isEquivalentObjectStorage($localStorage, $storageRecord)) {
+                        // There is already an existing storage
+                        $storageExists = true;
+                        break;
+                    }
+                }
+
+                if (!$storageExists) {
+                    // The storage from the import does not have an equivalent storage
+                    // in the current instance (same driver, same path, etc.). Before
+                    // the storage record can get inserted later on take care the path
+                    // it points to really exists and is accessible.
+                    $storageRecordUid = $storageRecord['uid'];
+                    // Unset the storage record UID when trying to create the storage object
+                    // as the record does not already exist in DB. The constructor of the
+                    // storage object will check whether the target folder exists and set the
+                    // isOnline flag depending on the outcome.
+                    $storageRecord['uid'] = 0;
+                    $resourceStorage = ResourceFactory::getInstance()->createStorageObject($storageRecord);
+                    if (!$resourceStorage->isOnline()) {
+                        $configuration = $resourceStorage->getConfiguration();
+                        $messages['resourceStorageFolderMissing_' . $storageRecordUid] = 'The resource storage "' . $resourceStorage->getName() . '" will get imported. The storage target directory "' . $configuration['basePath'] . '" does not exist. Please create the directory prior to starting the import!';
+                    }
+
+                }
+            }
+        }
+
+        return $messages;
+    }
+
+    /**
      * Imports the sys_file records and the binary files data from internal data array.
      *
      * @return void
@@ -408,7 +486,7 @@ class Import extends ImportExport
                             $sanitizedFolderMappings[$folderName] = $importFolder->getIdentifier();
                         }
                     } catch (Exception $e) {
-                        $this->error('Error: Folder could not be created for file "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
+                        $this->error('Error: Folder "' . $folderName . '" could not be created for file "' . $fileRecord['identifier'] . '" with storage uid "' . $fileRecord['storage'] . '"');
                         continue;
                     }
                 } else {
index 9458b7b..3c78e86 100644 (file)
@@ -2,9 +2,26 @@
 
 <f:section name="content">
        <div>
-               <f:if condition="{extensionInstallationMessage}">
-                       <f:format.raw>{extensionInstallationMessage}</f:format.raw>
-               </f:if>
+               <f:flashMessages as="flashMessages" queueIdentifier="impexp.errors">
+                       <f:for each="{flashMessages}" as="flashMessage">
+                               <div class="alert {flashMessage.class}">
+                                       <div class="media">
+                                               <div class="media-left">
+                                                       <span class="fa-stack fa-lg">
+                                                               <i class="fa fa-circle fa-stack-2x"></i>
+                                                               <i class="fa fa-{flashMessage.iconName} fa-stack-1x"></i>
+                                                       </span>
+                                               </div>
+                                               <div class="media-body">
+                                                       <f:if condition="{flashMessage.title}">
+                                                               <h4 class="alert-title">{flashMessage.title}</h4>
+                                                       </f:if>
+                                                       <div class="alert-message">{flashMessage.message}</div>
+                                               </div>
+                                       </div>
+                               </div>
+                       </f:for>
+               </f:flashMessages>
                <div role="tabpanel">
                        <ul class="nav nav-tabs" role="tablist">
                                <li role="presentation" class="active">