[!!!][TASK] Cleanup and remove old filefunc logic
[Packages/TYPO3.CMS.git] / typo3 / sysext / impexp / Classes / ImportExport.php
index 2ba411c..b6d3861 100644 (file)
@@ -16,9 +16,10 @@ namespace TYPO3\CMS\Impexp;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Database\DatabaseConnection;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Utility\DebugUtility;
 use TYPO3\CMS\Core\Utility\DiffUtility;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
@@ -253,6 +254,14 @@ abstract class ImportExport
     protected $iconFactory;
 
     /**
+     * Flag to control whether all disabled records and their children are excluded (true) or included (false). Defaults
+     * to the old behaviour of including everything.
+     *
+     * @var bool
+     */
+    protected $excludeDisabledRecords = false;
+
+    /**
      * The constructor
      */
     public function __construct()
@@ -286,56 +295,56 @@ abstract class ImportExport
      */
     public function displayContentOverview()
     {
+        if (!isset($this->dat['header'])) {
+            return [];
+        }
         // Check extension dependencies:
-        if (is_array($this->dat['header']['extensionDependencies'])) {
-            foreach ($this->dat['header']['extensionDependencies'] as $extKey) {
-                if (!ExtensionManagementUtility::isLoaded($extKey)) {
-                    $this->error('DEPENDENCY: The extension with key "' . $extKey . '" must be installed!');
-                }
+        foreach ($this->dat['header']['extensionDependencies'] as $extKey) {
+            if (!empty($extKey) && !ExtensionManagementUtility::isLoaded($extKey)) {
+                $this->error('DEPENDENCY: The extension with key "' . $extKey . '" must be installed!');
             }
         }
+
         // Probably this is done to save memory space?
         unset($this->dat['files']);
 
         $viewData = array();
         // Traverse header:
-        if (is_array($this->dat['header'])) {
-            $this->remainHeader = $this->dat['header'];
-            // If there is a page tree set, show that:
-            if (is_array($this->dat['header']['pagetree'])) {
-                reset($this->dat['header']['pagetree']);
-                $lines = array();
-                $this->traversePageTree($this->dat['header']['pagetree'], $lines);
-
-                $viewData['dat'] = $this->dat;
-                $viewData['update'] = $this->update;
-                $viewData['showDiff'] = $this->showDiff;
-                if (!empty($lines)) {
-                    foreach ($lines as &$r) {
-                        $r['controls'] = $this->renderControls($r);
-                        $r['fileSize'] = GeneralUtility::formatSize($r['size']);
-                        $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
-                    }
-                    $viewData['pagetreeLines'] = $lines;
-                } else {
-                    $viewData['pagetreeLines'] = array();
+        $this->remainHeader = $this->dat['header'];
+        // If there is a page tree set, show that:
+        if (is_array($this->dat['header']['pagetree'])) {
+            reset($this->dat['header']['pagetree']);
+            $lines = array();
+            $this->traversePageTree($this->dat['header']['pagetree'], $lines);
+
+            $viewData['dat'] = $this->dat;
+            $viewData['update'] = $this->update;
+            $viewData['showDiff'] = $this->showDiff;
+            if (!empty($lines)) {
+                foreach ($lines as &$r) {
+                    $r['controls'] = $this->renderControls($r);
+                    $r['fileSize'] = GeneralUtility::formatSize($r['size']);
+                    $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
                 }
+                $viewData['pagetreeLines'] = $lines;
+            } else {
+                $viewData['pagetreeLines'] = array();
             }
-            // Print remaining records that were not contained inside the page tree:
-            if (is_array($this->remainHeader['records'])) {
-                $lines = array();
-                if (is_array($this->remainHeader['records']['pages'])) {
-                    $this->traversePageRecords($this->remainHeader['records']['pages'], $lines);
-                }
-                $this->traverseAllRecords($this->remainHeader['records'], $lines);
-                if (!empty($lines)) {
-                    foreach ($lines as &$r) {
-                        $r['controls'] = $this->renderControls($r);
-                        $r['fileSize'] = GeneralUtility::formatSize($r['size']);
-                        $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
-                    }
-                    $viewData['remainingRecords'] = $lines;
+        }
+        // Print remaining records that were not contained inside the page tree:
+        if (is_array($this->remainHeader['records'])) {
+            $lines = array();
+            if (is_array($this->remainHeader['records']['pages'])) {
+                $this->traversePageRecords($this->remainHeader['records']['pages'], $lines);
+            }
+            $this->traverseAllRecords($this->remainHeader['records'], $lines);
+            if (!empty($lines)) {
+                foreach ($lines as &$r) {
+                    $r['controls'] = $this->renderControls($r);
+                    $r['fileSize'] = GeneralUtility::formatSize($r['size']);
+                    $r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
                 }
+                $viewData['remainingRecords'] = $lines;
             }
         }
 
@@ -353,6 +362,11 @@ abstract class ImportExport
     public function traversePageTree($pT, &$lines, $preCode = '')
     {
         foreach ($pT as $k => $v) {
+            if ($this->excludeDisabledRecords === true && !$this->isActive('pages', $k)) {
+                $this->excludePageAndRecords($k, $v);
+                continue;
+            }
+
             // Add this page:
             $this->singleRecordLines('pages', $k, $lines, $preCode);
             // Subrecords:
@@ -374,6 +388,53 @@ abstract class ImportExport
     }
 
     /**
+     * Test whether a record is active (i.e. not hidden)
+     *
+     * @param string $table Name of the records' database table
+     * @param int $uid Database uid of the record
+     * @return bool true if the record is active, false otherwise
+     */
+    protected function isActive($table, $uid)
+    {
+        return
+            !isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])
+            || !(bool)$this->dat['records'][$table . ':' . $uid]['data'][
+                $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']
+            ];
+    }
+
+    /**
+     * Exclude a page, its sub pages (recursively) and records placed in them from this import/export
+     *
+     * @param int $pageUid Uid of the page to exclude
+     * @param array $pageTree Page tree array with uid/subrow (from ->dat[header][pagetree]
+     * @return void
+     */
+    protected function excludePageAndRecords($pageUid, $pageTree)
+    {
+        // Prevent having this page appear in "remaining records" table
+        unset($this->remainHeader['records']['pages'][$pageUid]);
+
+        // Subrecords
+        if (is_array($this->dat['header']['pid_lookup'][$pageUid])) {
+            foreach ($this->dat['header']['pid_lookup'][$pageUid] as $table => $recordData) {
+                if ($table != 'pages') {
+                    foreach (array_keys($recordData) as $uid) {
+                        unset($this->remainHeader['records'][$table][$uid]);
+                    }
+                }
+            }
+            unset($this->remainHeader['pid_lookup'][$pageUid]);
+        }
+        // Subpages excluded recursively
+        if (is_array($pageTree['subrow'])) {
+            foreach ($pageTree['subrow'] as $subPageUid => $subPageTree) {
+                $this->excludePageAndRecords($subPageUid, $subPageTree);
+            }
+        }
+    }
+
+    /**
      * Go through remaining pages (not in tree)
      *
      * @param array $pT Page tree array with uid/subrow (from ->dat[header][pagetree]
@@ -459,13 +520,16 @@ abstract class ImportExport
         $lang = $this->getLanguageService();
         if ($table === '_SOFTREF_') {
             $pInfo['preCode'] = $preCode;
-            $pInfo['title'] = '<em>' . $lang->getLL('impexpcore_singlereco_softReferencesFiles', true) . '</em>';
+            $pInfo['title'] = '<em>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_softReferencesFiles')) . '</em>';
         } elseif (!isset($GLOBALS['TCA'][$table])) {
             // Unknown table name:
             $pInfo['preCode'] = $preCode;
             $pInfo['msg'] = 'UNKNOWN TABLE \'' . $pInfo['ref'] . '\'';
             $pInfo['title'] = '<em>' . htmlspecialchars($record['title']) . '</em>';
         } else {
+            // prepare data attribute telling whether the record is active or hidden, allowing frontend bulk selection
+            $pInfo['active'] = $this->isActive($table, $uid) ? 'active' : 'hidden';
+
             // Otherwise, set table icon and title.
             // Import Validation (triggered by $this->display_import_pid_record) will show messages if import is not possible of various items.
             if (is_array($this->display_import_pid_record) && !empty($this->display_import_pid_record)) {
@@ -547,7 +611,6 @@ abstract class ImportExport
                 }
             }
         }
-        $pInfo['class'] = $table == 'pages' ? 'bgColor4-20' : 'bgColor4';
         $pInfo['type'] = 'record';
         $pInfo['size'] = $record['size'];
         $lines[] = $pInfo;
@@ -569,16 +632,15 @@ abstract class ImportExport
                 $pInfo['title'] = '<em>' . $info['field'] . ', "' . $info['spKey'] . '" </em>: <span title="' . htmlspecialchars($info['matchString']) . '">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['matchString'], 60)) . '</span>';
                 if ($info['subst']['type']) {
                     if (strlen($info['subst']['title'])) {
-                        $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . $lang->getLL('impexpcore_singlereco_title', true) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['title'], 60));
+                        $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_title')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['title'], 60));
                     }
                     if (strlen($info['subst']['description'])) {
-                        $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . $lang->getLL('impexpcore_singlereco_descr', true) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['description'], 60));
+                        $pInfo['title'] .= '<br/>' . $preCode_B . '<strong>' . htmlspecialchars($lang->getLL('impexpcore_singlereco_descr')) . '</strong> ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($info['subst']['description'], 60));
                     }
-                    $pInfo['title'] .= '<br/>' . $preCode_B . ($info['subst']['type'] == 'file' ? $lang->getLL('impexpcore_singlereco_filename', true) . ' <strong>' . $info['subst']['relFileName'] . '</strong>' : '') . ($info['subst']['type'] == 'string' ? $lang->getLL('impexpcore_singlereco_value', true) . ' <strong>' . $info['subst']['tokenValue'] . '</strong>' : '') . ($info['subst']['type'] == 'db' ? $lang->getLL('impexpcore_softrefsel_record', true) . ' <strong>' . $info['subst']['recordRef'] . '</strong>' : '');
+                    $pInfo['title'] .= '<br/>' . $preCode_B . ($info['subst']['type'] == 'file' ? htmlspecialchars($lang->getLL('impexpcore_singlereco_filename')) . ' <strong>' . $info['subst']['relFileName'] . '</strong>' : '') . ($info['subst']['type'] == 'string' ? htmlspecialchars($lang->getLL('impexpcore_singlereco_value')) . ' <strong>' . $info['subst']['tokenValue'] . '</strong>' : '') . ($info['subst']['type'] == 'db' ? htmlspecialchars($lang->getLL('impexpcore_softrefsel_record')) . ' <strong>' . $info['subst']['recordRef'] . '</strong>' : '');
                 }
                 $pInfo['ref'] = 'SOFTREF';
                 $pInfo['size'] = '';
-                $pInfo['class'] = 'bgColor3';
                 $pInfo['type'] = 'softref';
                 $pInfo['_softRefInfo'] = $info;
                 $pInfo['type'] = 'softref';
@@ -655,12 +717,11 @@ abstract class ImportExport
             $icon = '<span class="' . $iconClass . '" title="' . htmlspecialchars($pInfo['ref']) . '">' . $this->iconFactory->getIcon($iconName, Icon::SIZE_SMALL)->render() . '</span>';
 
             $pInfo['preCode'] = $preCode . '&nbsp;&nbsp;&nbsp;&nbsp;' . $icon;
-            $pInfo['class'] = $htmlColorClass ?: 'bgColor3';
             $pInfo['type'] = 'rel';
             if (!$staticFixed || $this->showStaticRelations) {
                 $lines[] = $pInfo;
                 if (is_array($record) && is_array($record['rels'])) {
-                    $this->addRelations($record['rels'], $lines, $preCode . '&nbsp;&nbsp;', array_merge($recurCheck, array($pInfo['ref'])), $htmlColorClass);
+                    $this->addRelations($record['rels'], $lines, $preCode . '&nbsp;&nbsp;', array_merge($recurCheck, array($pInfo['ref'])));
                 }
             }
         }
@@ -696,7 +757,6 @@ abstract class ImportExport
             $pInfo['title'] = htmlspecialchars($fI['filename']);
             $pInfo['ref'] = 'FILE';
             $pInfo['size'] = $fI['filesize'];
-            $pInfo['class'] = $htmlColorClass ?: 'bgColor3';
             $pInfo['type'] = 'file';
             // If import mode and there is a non-RTE softreference, check the destination directory:
             if ($this->mode === 'import' && $tokenID && !$fI['RTE_ORIG_ID']) {
@@ -747,7 +807,6 @@ abstract class ImportExport
                 $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Original)</em>';
                 $pInfo['ref'] = 'FILE';
                 $pInfo['size'] = $fI['filesize'];
-                $pInfo['class'] = $htmlColorClass ?: 'bgColor3';
                 $pInfo['type'] = 'file';
                 $lines[] = $pInfo;
                 unset($this->remainHeader['files'][$ID]);
@@ -768,7 +827,6 @@ abstract class ImportExport
                     $pInfo['title'] = htmlspecialchars($fI['filename']) . ' <em>(Resource)</em>';
                     $pInfo['ref'] = 'FILE';
                     $pInfo['size'] = $fI['filesize'];
-                    $pInfo['class'] = $htmlColorClass ?: 'bgColor3';
                     $pInfo['type'] = 'file';
                     $lines[] = $pInfo;
                     unset($this->remainHeader['files'][$extID]);
@@ -805,7 +863,7 @@ abstract class ImportExport
     {
         if ($this->mode === 'export') {
             if ($r['type'] === 'record') {
-                return '<input type="checkbox" name="tx_impexp[exclude][' . $r['ref'] . ']" id="checkExclude' . $r['ref'] . '" value="1" /> <label for="checkExclude' . $r['ref'] . '">' . $this->getLanguageService()->getLL('impexpcore_singlereco_exclude', true) . '</label>';
+                return '<input type="checkbox" class="t3js-exclude-checkbox" name="tx_impexp[exclude][' . $r['ref'] . ']" id="checkExclude' . $r['ref'] . '" value="1" /> <label for="checkExclude' . $r['ref'] . '">' . htmlspecialchars($this->getLanguageService()->getLL('impexpcore_singlereco_exclude')) . '</label>';
             } else {
                 return  $r['type'] == 'softref' ? $this->softrefSelector($r['_softRefInfo']) : '';
             }
@@ -856,7 +914,7 @@ abstract class ImportExport
                 // Description:
                 if (!strlen($cfg['subst']['description'])) {
                     $descriptionField .= '
-                                       ' . $this->getLanguageService()->getLL('impexpcore_printerror_description', true) . '<br/>
+                                       ' . htmlspecialchars($this->getLanguageService()->getLL('impexpcore_printerror_description')) . '<br/>
                                        <input type="text" name="tx_impexp[softrefCfg][' . $cfg['subst']['tokenID'] . '][description]" value="' . htmlspecialchars($this->softrefCfg[$cfg['subst']['tokenID']]['description']) . '" />';
                 } else {
                     $descriptionField .= '
@@ -883,26 +941,22 @@ abstract class ImportExport
      */
     public function verifyFolderAccess($dirPrefix, $noAlternative = false)
     {
-        $fileProcObj = $this->getFileProcObj();
-        // Check, if dirPrefix is inside a valid Filemount for user:
-        $result = $fileProcObj->checkPathAgainstMounts(PATH_site . $dirPrefix);
-        // If not, try to find another relative filemount and use that instead:
-        if (!$result) {
-            if ($noAlternative) {
-                return false;
-            }
-            // Find first web folder:
-            $result = $fileProcObj->findFirstWebFolder();
-            // If that succeeded, return the path to it:
-            if ($result) {
-                // Remove the "fileadmin/" prefix of input path - and append the rest to the return value:
-                if (GeneralUtility::isFirstPartOfStr($dirPrefix, $this->fileadminFolderName . '/')) {
-                    $dirPrefix = substr($dirPrefix, strlen($this->fileadminFolderName . '/'));
+        // Check the absolute path for PATH_site, if the user has access - no problem
+        try {
+            ResourceFactory::getInstance()->getFolderObjectFromCombinedIdentifier($dirPrefix);
+            return $dirPrefix;
+        } catch (InsufficientFolderAccessPermissionsException $e) {
+            // Check all storages available for the user as alternative
+            if (!$noAlternative) {
+                $fileStorages = $this->getBackendUser()->getFileStorages();
+                foreach ($fileStorages as $fileStorage) {
+                    try {
+                        $folder = $fileStorage->getFolder(rtrim($dirPrefix, '/'));
+                        return $folder->getPublicUrl();
+                    } catch (InsufficientFolderAccessPermissionsException $e) {
+                    }
                 }
-                return PathUtility::stripPathSitePrefix($fileProcObj->mounts[$result]['path'] . $dirPrefix);
             }
-        } else {
-            return $dirPrefix;
         }
         return false;
     }
@@ -917,7 +971,7 @@ abstract class ImportExport
      */
     protected function getTemporaryFolderName()
     {
-        $temporaryPath = PATH_site . 'typo3temp/';
+        $temporaryPath = PATH_site . 'typo3temp/var/transient/';
         do {
             $temporaryFolderName = $temporaryPath . 'export_temp_files_' . mt_rand(1, PHP_INT_MAX);
         } while (is_dir($temporaryFolderName));
@@ -1118,12 +1172,12 @@ abstract class ImportExport
                 foreach ($output as $fN => $state) {
                     $tRows[] = '
                                                <tr>
-                                                       <td class="bgColor5">' . $this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fN]['label'], true) . ' (' . htmlspecialchars($fN) . ')</td>
-                                                       <td class="bgColor4">' . $state . '</td>
+                                                       <td>' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fN]['label'])) . ' (' . htmlspecialchars($fN) . ')</td>
+                                                       <td>' . $state . '</td>
                                                </tr>
                                        ';
                 }
-                $output = '<table border="0" cellpadding="0" cellspacing="1">' . implode('', $tRows) . '</table>';
+                $output = '<table class="table table-striped table-hover">' . implode('', $tRows) . '</table>';
             } else {
                 $output = 'Match';
             }
@@ -1160,7 +1214,6 @@ abstract class ImportExport
     {
         if ($this->fileProcObj === null) {
             $this->fileProcObj = GeneralUtility::makeInstance(ExtendedFileUtility::class);
-            $this->fileProcObj->init(array(), $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']);
             $this->fileProcObj->setActionPermissions();
         }
         return $this->fileProcObj;
@@ -1182,6 +1235,19 @@ abstract class ImportExport
         }
     }
 
+    /**
+     * Set flag to control whether disabled records and their children are excluded (true) or included (false). Defaults
+     * to the old behaviour of including everything.
+     *
+     * @param bool $excludeDisabledRecords Set to true if if all disabled records should be excluded, false otherwise
+     * @return \TYPO3\CMS\Impexp\ImportExport $this for fluent calls
+     */
+    public function setExcludeDisabledRecords($excludeDisabledRecords = false)
+    {
+        $this->excludeDisabledRecords = $excludeDisabledRecords;
+        return $this;
+    }
+
     /*****************************
      * Error handling
      *****************************/
@@ -1216,19 +1282,10 @@ abstract class ImportExport
     }
 
     /**
-     * @return DatabaseConnection
-     */
-    protected function getDatabaseConnection()
-    {
-        return $GLOBALS['TYPO3_DB'];
-    }
-
-    /**
      * @return LanguageService
      */
     protected function getLanguageService()
     {
         return $GLOBALS['LANG'];
     }
-
 }