From a62280c0bef6280e6e02bd8cdc4a75db7fda2c47 Mon Sep 17 00:00:00 2001
From: Ludwig Rafelsberger <ludwig.rafelsberger@gmx.at>
Date: Fri, 15 Jan 2016 18:37:31 +0100
Subject: [PATCH] [FEATURE] Allow exclusion of hidden records on export
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Allow editors to exclude all disabled records during export
preparation (EXT:impexp).

When preparing to export a page tree, users can now:
- choose to exclude all disabled records recursively. This is the new
  default behaviour
- toggle all disabled records which are scheduled to be exported
  (convenience method, this was already possible by manually checking
  all these records.

Resolves: #19157
Releases: master
Change-Id: Ibb3534151a9d08cf4a60b54430678563d6feb5be
Reviewed-on: https://review.typo3.org/45960
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Reinhard Führicht <rf@typoheads.at>
Tested-by: Reinhard Führicht <rf@typoheads.at>
Reviewed-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de>
---
 ...dHaveAnOptionToExcludeAllHiddenRecords.rst | 14 ++++
 .../Controller/ImportExportController.php     | 34 +++++++-
 typo3/sysext/impexp/Classes/Export.php        |  3 +
 typo3/sysext/impexp/Classes/ImportExport.php  | 81 ++++++++++++++++++-
 .../Resources/Private/Language/locallang.xlf  | 14 +++-
 .../Private/Partials/ContentOverview.html     | 17 +++-
 .../Partials/Export/Configuration.html        |  8 +-
 .../Public/JavaScript/ImportExport.js         |  5 ++
 8 files changed, 160 insertions(+), 16 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-19157-impexpCouldHaveAnOptionToExcludeAllHiddenRecords.rst

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-19157-impexpCouldHaveAnOptionToExcludeAllHiddenRecords.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-19157-impexpCouldHaveAnOptionToExcludeAllHiddenRecords.rst
new file mode 100644
index 000000000000..7cb091612ea4
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-19157-impexpCouldHaveAnOptionToExcludeAllHiddenRecords.rst
@@ -0,0 +1,14 @@
+========================================================================
+Feature: #19157 - Add option to exclude all hidden records in EXT:impexp
+========================================================================
+
+Description
+===========
+
+The export configuration of EXT:impexp has been extended to allow to
+completely deactivate exporting of hidden/deactivated records. This
+behaviour can be controlled via a new option which is checked by default.
+
+Furthermore, if the inclusion of hidden records is activated (which is
+now an explicit choice), then an additional button is shown, allowing
+users to preselect all hidden records for manual exclusion.
diff --git a/typo3/sysext/impexp/Classes/Controller/ImportExportController.php b/typo3/sysext/impexp/Classes/Controller/ImportExportController.php
index df846cac03c0..1a9f3808d936 100644
--- a/typo3/sysext/impexp/Classes/Controller/ImportExportController.php
+++ b/typo3/sysext/impexp/Classes/Controller/ImportExportController.php
@@ -34,12 +34,12 @@ use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Impexp\Domain\Repository\PresetRepository;
 use TYPO3\CMS\Impexp\Export;
 use TYPO3\CMS\Impexp\Import;
 use TYPO3\CMS\Impexp\View\ExportPageTreeView;
 use TYPO3\CMS\Lang\LanguageService;
-use TYPO3\CMS\Fluid\View\StandaloneView;
 
 /**
  * Main script class for the Import / Export facility
@@ -126,6 +126,11 @@ class ImportExportController extends BaseScriptClass
      */
     protected $standaloneView = null;
 
+    /**
+     * @var bool
+     */
+    protected $excludeDisabledRecords = false;
+
     /**
      * Constructor
      */
@@ -185,6 +190,10 @@ class ImportExportController extends BaseScriptClass
 
         // Input data grabbed:
         $inData = GeneralUtility::_GP('tx_impexp');
+        if (!array_key_exists('excludeDisabled', $inData)) {
+            // flag doesn't exist initially; state is on by default
+            $inData['excludeDisabled'] = 1;
+        }
         $this->standaloneView->assign('moduleUrl', BackendUtility::getModuleUrl('xMOD_tximpexp'));
         $this->standaloneView->assign('id', $this->id);
         $this->standaloneView->assign('inData', $inData);
@@ -343,6 +352,8 @@ class ImportExportController extends BaseScriptClass
         $this->export->extensionDependencies = (array)$inData['extension_dep'];
         $this->export->showStaticRelations = $inData['showStaticRelations'];
         $this->export->includeExtFileResources = !$inData['excludeHTMLfileResources'];
+        $this->excludeDisabledRecords = (bool)$inData['excludeDisabled'];
+        $this->export->setExcludeDisabledRecords($this->excludeDisabledRecords);
 
         // Static tables:
         if (is_array($inData['external_static']['tables'])) {
@@ -396,6 +407,9 @@ class ImportExportController extends BaseScriptClass
             $idH = null;
             if ($inData['pagetree']['levels'] == -1) {
                 $pagetree = GeneralUtility::makeInstance(ExportPageTreeView::class);
+                if ($this->excludeDisabledRecords) {
+                    $pagetree->init(BackendUtility::BEenableFields('pages'));
+                }
                 $tree = $pagetree->ext_tree($inData['pagetree']['id'], $this->filterPageIds($this->export->excludeMap));
                 $this->treeHTML = $pagetree->printTree($tree);
                 $idH = $pagetree->buffer_idH;
@@ -416,7 +430,11 @@ class ImportExportController extends BaseScriptClass
                 if (is_array($sPage)) {
                     $pid = $inData['pagetree']['id'];
                     $tree = GeneralUtility::makeInstance(PageTreeView::class);
-                    $tree->init('AND ' . $this->perms_clause . $this->filterPageIds($this->export->excludeMap));
+                    $initClause = 'AND ' . $this->perms_clause . $this->filterPageIds($this->export->excludeMap);
+                    if ($this->excludeDisabledRecords) {
+                        $initClause .= BackendUtility::BEenableFields('pages');
+                    }
+                    $tree->init($initClause);
                     $HTML = $this->iconFactory->getIconForRecord('pages', $sPage, Icon::SIZE_SMALL)->render();
                     $tree->tree[] = array('row' => $sPage, 'HTML' => $HTML);
                     $tree->buffer_idH = array();
@@ -539,7 +557,10 @@ class ImportExportController extends BaseScriptClass
         $this->standaloneView->assign('errors', $this->export->errorLog);
 
         // Generate overview:
-        $this->standaloneView->assign('contentOverview', $this->export->displayContentOverview());
+        $this->standaloneView->assign(
+            'contentOverview',
+            $this->export->displayContentOverview()
+        );
     }
 
     /**
@@ -583,10 +604,15 @@ class ImportExportController extends BaseScriptClass
         $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby']
             ? 'ORDER BY ' . $GLOBALS['TCA'][$table]['ctrl']['sortby']
             : $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
+
+        $whereClause = 'pid=' . (int)$pid . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table);
+        if ($this->excludeDisabledRecords) {
+            $whereClause .= BackendUtility::BEenableFields($table);
+        }
         $res = $db->exec_SELECTquery(
             '*',
             $table,
-            'pid=' . (int)$pid . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table),
+            $whereClause,
             '',
             $db->stripOrderBy($orderBy),
             $limit
diff --git a/typo3/sysext/impexp/Classes/Export.php b/typo3/sysext/impexp/Classes/Export.php
index 66ad7c6f75a4..1ebfaee7ce15 100644
--- a/typo3/sysext/impexp/Classes/Export.php
+++ b/typo3/sysext/impexp/Classes/Export.php
@@ -307,6 +307,9 @@ class Export extends ImportExport
     public function export_addRecord($table, $row, $relationLevel = 0)
     {
         BackendUtility::workspaceOL($table, $row);
+        if ($this->excludeDisabledRecords && !$this->isActive($table, $row['uid'])) {
+            return;
+        }
         if ((string)$table !== '' && is_array($row) && $row['uid'] > 0 && !$this->excludeMap[$table . ':' . $row['uid']]) {
             if ($this->checkPID($table === 'pages' ? $row['uid'] : $row['pid'])) {
                 if (!isset($this->dat['records'][$table . ':' . $row['uid']])) {
diff --git a/typo3/sysext/impexp/Classes/ImportExport.php b/typo3/sysext/impexp/Classes/ImportExport.php
index 2ba411cb2678..4f75bcbf57a4 100644
--- a/typo3/sysext/impexp/Classes/ImportExport.php
+++ b/typo3/sysext/impexp/Classes/ImportExport.php
@@ -252,6 +252,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
      */
@@ -305,7 +313,7 @@ abstract class ImportExport
             if (is_array($this->dat['header']['pagetree'])) {
                 reset($this->dat['header']['pagetree']);
                 $lines = array();
-                $this->traversePageTree($this->dat['header']['pagetree'], $lines);
+                $this->traversePageTree($this->dat['header']['pagetree'], $lines, '');
 
                 $viewData['dat'] = $this->dat;
                 $viewData['update'] = $this->update;
@@ -353,6 +361,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:
@@ -373,6 +386,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)
      *
@@ -466,6 +526,9 @@ abstract class ImportExport
             $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)) {
@@ -805,7 +868,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'] . '">' . $this->getLanguageService()->getLL('impexpcore_singlereco_exclude', true) . '</label>';
             } else {
                 return  $r['type'] == 'softref' ? $this->softrefSelector($r['_softRefInfo']) : '';
             }
@@ -1182,6 +1245,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
      *****************************/
@@ -1230,5 +1306,4 @@ abstract class ImportExport
     {
         return $GLOBALS['LANG'];
     }
-
 }
diff --git a/typo3/sysext/impexp/Resources/Private/Language/locallang.xlf b/typo3/sysext/impexp/Resources/Private/Language/locallang.xlf
index 4cbd983aad20..818ae8dce449 100644
--- a/typo3/sysext/impexp/Resources/Private/Language/locallang.xlf
+++ b/typo3/sysext/impexp/Resources/Private/Language/locallang.xlf
@@ -45,6 +45,9 @@
 			<trans-unit id="execlistqu_structureToBeExported">
 				<source>Structure to be exported:</source>
 			</trans-unit>
+			<trans-unit id="impexpcore_toggle_all_disabled_records">
+				<source>Toggle disabled records</source>
+			</trans-unit>
 			<trans-unit id="execlistqu_maxNumberLimit">
 				<source>Max number limit!</source>
 			</trans-unit>
@@ -108,11 +111,14 @@
 			<trans-unit id="makeconfig_excludeElements">
 				<source>Exclude elements:</source>
 			</trans-unit>
-			<trans-unit id="makeconfig_clearAllExclusions">
-				<source>Clear all exclusions:</source>
+			<trans-unit id="makeconfig_clearAllManualExclusions">
+				<source>Clear all manual exclusions:</source>
+			</trans-unit>
+			<trans-unit id="makeconfig_noManuallyExcludedElementsYet">
+				<source>No manually excluded elements yet. Exclude by setting checkboxes below in the element display.</source>
 			</trans-unit>
-			<trans-unit id="makeconfig_noExcludedElementsYet">
-				<source>No excluded elements yet. Exclude by setting checkboxes below in the element display.</source>
+			<trans-unit id="makeconfig_excludeDisabledElements">
+				<source>Exclude disabled elements</source>
 			</trans-unit>
 			<trans-unit id="makeadvanc_update">
 				<source>Update</source>
diff --git a/typo3/sysext/impexp/Resources/Private/Partials/ContentOverview.html b/typo3/sysext/impexp/Resources/Private/Partials/ContentOverview.html
index 0026384097e6..d877488fa3de 100644
--- a/typo3/sysext/impexp/Resources/Private/Partials/ContentOverview.html
+++ b/typo3/sysext/impexp/Resources/Private/Partials/ContentOverview.html
@@ -19,9 +19,18 @@
 	</f:else>
 </f:if>
 <div>
+	<f:if condition="{inData.excludeDisabled}">
+		<f:else>
+			<f:if condition="{inData.action} == 'export'">
+				<f:form.button class="btn btn-default t3js-impexp-toggledisabled" type="button">
+					<f:translate key="impexpcore_toggle_all_disabled_records" />
+				</f:form.button>
+			</f:if>
+		</f:else>
+	</f:if>
 	<f:if condition="{contentOverview.dat.header.pagetree -> f:count()} > 0">
 		<h3><f:translate key="impexpcore_displaycon_insidePagetree" /></h3>
-		<table class="table table-striped table-hover">
+		<table class="table table-striped table-hover t3js-impexp-preview">
 			<tbody>
 				<tr>
 					<th><f:translate key="impexpcore_displaycon_controls" /></th>
@@ -37,7 +46,7 @@
 					</f:if>
 				</tr>
 				<f:for each="{contentOverview.pagetreeLines}" as="line">
-					<tr>
+					<tr data-active="{line.active}">
 						<td><f:format.raw>{line.controls}</f:format.raw></td>
 						<td class="col-nowrap"><f:format.raw>{line.preCode}{line.title}</f:format.raw></td>
 						<td class="col-nowrap"><f:format.raw>{line.fileSize}</f:format.raw></td>
@@ -56,9 +65,9 @@
 	</f:if>
 	<f:if condition="{contentOverview.remainingRecords -> f:count()} > 0">
 		<h3><f:translate key="impexpcore_singlereco_outsidePagetree" /></h3>
-		<table class="table table-striped table-hover">
+		<table class="table table-striped table-hover t3js-impexp-preview">
 			<tbody>
-				<tr>
+				<tr data-active="{line.active}">
 					<th><f:translate key="impexpcore_displaycon_controls" /></th>
 					<th><f:translate key="impexpcore_displaycon_title" /></th>
 					<th><f:translate key="impexpcore_displaycon_size" /></th>
diff --git a/typo3/sysext/impexp/Resources/Private/Partials/Export/Configuration.html b/typo3/sysext/impexp/Resources/Private/Partials/Export/Configuration.html
index b8deaff5b21f..db45732e6c6f 100644
--- a/typo3/sysext/impexp/Resources/Private/Partials/Export/Configuration.html
+++ b/typo3/sysext/impexp/Resources/Private/Partials/Export/Configuration.html
@@ -143,10 +143,16 @@
 				</label>
 			</f:then>
 			<f:else>
-					<f:translate key="makeconfig_noExcludedElementsYet" />
+					<f:translate key="makeconfig_noManuallyExcludedElementsYet" />
 			</f:else>
 		</f:if>
 	</p>
+	<p class="form-control-static">
+		<label for="checkExcludeDisabled">
+			<f:form.checkbox name="tx_impexp[excludeDisabled]" id="checkExcludeDisabled" value="1" checked="{inData.excludeDisabled}" />
+			<f:translate key="makeconfig_excludeDisabledElements" />
+		</label>
+	</p>
 </div>
 
 <div class="form-group">
diff --git a/typo3/sysext/impexp/Resources/Public/JavaScript/ImportExport.js b/typo3/sysext/impexp/Resources/Public/JavaScript/ImportExport.js
index 8ab3de89a449..cc5f843feda2 100644
--- a/typo3/sysext/impexp/Resources/Public/JavaScript/ImportExport.js
+++ b/typo3/sysext/impexp/Resources/Public/JavaScript/ImportExport.js
@@ -33,5 +33,10 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal'], function ($, Modal) {
 					Modal.currentModal.trigger('modal-dismiss');
 				});
 		});
+
+		$('.t3js-impexp-toggledisabled').on('click', function() {
+			var $checkboxes = $('table.t3js-impexp-preview tr[data-active="hidden"] input.t3js-exclude-checkbox');
+			$checkboxes.prop('checked', !$checkboxes.get(0).checked);
+		});
 	});
 });
-- 
GitLab