From 0b8a0b272bf85fbaf0c1df38ab69e629300df99e Mon Sep 17 00:00:00 2001
From: Bernhard Kraft <kraftb@think-open.at>
Date: Fri, 15 Jan 2016 14:33:32 +0100
Subject: [PATCH] [BUGFIX] Import skips files with non-existent target
 directory

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>
---
 .../Controller/ImportExportController.php     |  37 +++---
 typo3/sysext/impexp/Classes/Import.php        | 120 +++++++++++++++---
 .../Templates/ImportExport/Import.html        |  23 +++-
 3 files changed, 134 insertions(+), 46 deletions(-)

diff --git a/typo3/sysext/impexp/Classes/Controller/ImportExportController.php b/typo3/sysext/impexp/Classes/Controller/ImportExportController.php
index d92b01af7d26..4cedcaec8596 100644
--- a/typo3/sysext/impexp/Classes/Controller/ImportExportController.php
+++ b/typo3/sysext/impexp/Classes/Controller/ImportExportController.php
@@ -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>'
-                );
-            }
         }
     }
 
diff --git a/typo3/sysext/impexp/Classes/Import.php b/typo3/sysext/impexp/Classes/Import.php
index b2f5d056cbfb..8472ea1e30b9 100644
--- a/typo3/sysext/impexp/Classes/Import.php
+++ b/typo3/sysext/impexp/Classes/Import.php
@@ -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 {
@@ -314,6 +303,95 @@ class Import extends ImportExport
         unset($this->dat['header']['records']['sys_file_storage']);
     }
 
+    /**
+     * 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.
      *
@@ -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 {
diff --git a/typo3/sysext/impexp/Resources/Private/Templates/ImportExport/Import.html b/typo3/sysext/impexp/Resources/Private/Templates/ImportExport/Import.html
index 9458b7ba790a..3c78e86c0154 100644
--- a/typo3/sysext/impexp/Resources/Private/Templates/ImportExport/Import.html
+++ b/typo3/sysext/impexp/Resources/Private/Templates/ImportExport/Import.html
@@ -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">
-- 
GitLab