[FEATURE] List module: Make table display order configurable 32/40932/10
authorMarkus Klein <markus.klein@typo3.org>
Mon, 6 Jul 2015 15:39:27 +0000 (17:39 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Thu, 9 Jul 2015 19:16:32 +0000 (21:16 +0200)
Resolves: #65550
Releases: master
Change-Id: I4f6da5f4d4e6a2834e619f683e77d055ac682566
Reviewed-on: http://review.typo3.org/40932
Reviewed-by: Daniel Maier <dani-maier@gmx.de>
Tested-by: Daniel Maier <dani-maier@gmx.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Documentation/Changelog/master/Feature-65550-MakeTableDisplayOrderConfigurableInListModule.rst [new file with mode: 0644]
typo3/sysext/recordlist/Classes/RecordList.php
typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php
typo3/sysext/recordlist/Tests/Unit/RecordList/AbstractDatabaseRecordListTest.php [new file with mode: 0644]

index 4f263cc..9f53065 100644 (file)
@@ -584,6 +584,20 @@ return array(
                'defaultPageTSconfig' => 'mod.web_list.enableDisplayBigControlPanel=selectable
                        mod.web_list.enableClipBoard=selectable
                        mod.web_list.enableLocalizationView=selectable
+                       mod.web_list.tableDisplayOrder {
+                               be_users.after = be_groups
+                               sys_filemounts.after = be_users
+                               sys_file_storage.after = sys_filemounts
+                               sys_language.after = sys_file_storage
+                               pages_language_overlay.before = pages
+                               fe_users.after = fe_groups
+                               fe_users.before = pages
+                               sys_template.after = pages
+                               backend_layout.after = pages
+                               sys_domain.after = sys_template
+                               tt_content.after = pages,backend_layout,sys_template
+                               sys_category.after = tt_content
+                       }
                        mod.wizards.newRecord.pages.show.pageInside=1
                        mod.wizards.newRecord.pages.show.pageAfter=1
                        mod.wizards.newRecord.pages.show.pageSelectPosition=1
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-65550-MakeTableDisplayOrderConfigurableInListModule.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-65550-MakeTableDisplayOrderConfigurableInListModule.rst
new file mode 100644 (file)
index 0000000..63ad7e4
--- /dev/null
@@ -0,0 +1,19 @@
+======================================================================
+Feature: #65550 - Make table display order configurable in List module
+======================================================================
+
+Description
+===========
+
+The new ``PageTSconfig`` configuration option ``mod.web_list.tableDisplayOrder`` has been added
+for the List module to allow flexible configuration of the order in which tables are displayed.
+The keywords ``before`` and ``after`` can be used to specify an order relative to other table names.
+
+Example:
+
+.. code-block:: typoscript
+
+       mod.web_list.tableDisplayOrder.<tableName> {
+         before = <tableA>, <tableB>, ...
+         after = <tableA>, <tableB>, ...
+       }
index 2f416f9..61beb5d 100644 (file)
@@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Extbase\Service\TypoScriptService;
 use TYPO3\CMS\Lang\LanguageService;
 
 /**
@@ -322,6 +323,10 @@ class RecordList {
                $dblist->modTSconfig = $this->modTSconfig;
                $clickTitleMode = trim($this->modTSconfig['properties']['clickTitleMode']);
                $dblist->clickTitleMode = $clickTitleMode === '' ? 'edit' : $clickTitleMode;
+               if (isset($this->modTSconfig['properties']['tableDisplayOrder.'])) {
+                       $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
+                       $dblist->setTableDisplayOrder($typoScriptService->convertTypoScriptArrayToPlainArray($this->modTSconfig['properties']['tableDisplayOrder.']));
+               }
                // Clipboard is initialized:
                // Start clipboard
                $dblist->clipObj = GeneralUtility::makeInstance(Clipboard::class);
index bbca124..2dce8dc 100644 (file)
@@ -19,6 +19,7 @@ use TYPO3\CMS\Backend\Tree\View\PageTreeView;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\DatabaseConnection;
+use TYPO3\CMS\Core\Service\DependencyOrderingService;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -320,6 +321,17 @@ class AbstractDatabaseRecordList extends AbstractRecordList {
        protected $overrideUrlParameters = array();
 
        /**
+        * Array with before/after setting for tables
+        * Structure:
+        * 'tableName' => [
+        *    'before' => ['A', ...]
+        *    'after' => []
+        *  ]
+        * @var array[]
+        */
+       protected $tableDisplayOrder = [];
+
+       /**
         * Initializes the list generation
         *
         * @param int $id Page id for which the list is rendered. Must be >= 0
@@ -417,64 +429,88 @@ class AbstractDatabaseRecordList extends AbstractRecordList {
                // Set page record in header
                $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
                $hideTablesArray = GeneralUtility::trimExplode(',', $this->hideTables);
-               // Traverse the TCA table array:
-               foreach ($GLOBALS['TCA'] as $tableName => $value) {
+
+               $backendUser = $this->getBackendUserAuthentication();
+
+               // pre-process tables and add sorting instructions
+               $tableNames = array_flip(array_keys($GLOBALS['TCA']));
+               foreach ($tableNames as $tableName => &$config) {
+                       $hideTable = FALSE;
+
                        // Checking if the table should be rendered:
                        // Checks that we see only permitted/requested tables:
-                       if ((!$this->table || $tableName == $this->table) && (!$this->tableList || GeneralUtility::inList($this->tableList, $tableName)) && $this->getBackendUserAuthentication()->check('tables_select', $tableName)) {
+                       if ($this->table && $tableName !== $this->table
+                               || $this->tableList && !GeneralUtility::inList($this->tableList, $tableName)
+                               || !$backendUser->check('tables_select', $tableName)
+                       ) {
+                               $hideTable = TRUE;
+                       }
+
+                       if (!$hideTable) {
                                // Don't show table if hidden by TCA ctrl section
-                               $hideTable = $GLOBALS['TCA'][$tableName]['ctrl']['hideTable'] ? TRUE : FALSE;
                                // Don't show table if hidden by pageTSconfig mod.web_list.hideTables
-                               if (in_array($tableName, $hideTablesArray)) {
-                                       $hideTable = TRUE;
-                               }
+                               $hideTable = $hideTable || !empty($GLOBALS['TCA'][$tableName]['ctrl']['hideTable']) || in_array($tableName, $hideTablesArray, TRUE);
                                // Override previous selection if table is enabled or hidden by TSconfig TCA override mod.web_list.table
                                if (isset($this->tableTSconfigOverTCA[$tableName . '.']['hideTable'])) {
-                                       $hideTable = $this->tableTSconfigOverTCA[$tableName . '.']['hideTable'] ? TRUE : FALSE;
+                                       $hideTable = (bool)$this->tableTSconfigOverTCA[$tableName . '.']['hideTable'];
                                }
-                               if ($hideTable) {
-                                       continue;
-                               }
-                               // check if we are in single- or multi-table mode
-                               if ($this->table) {
-                                       $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems']) ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems'] : $this->itemsLimitSingleTable;
+                       }
+                       if ($hideTable) {
+                               unset($tableNames[$tableName]);
+                       } else {
+                               if (isset($this->tableDisplayOrder[$tableName])) {
+                                       // Copy display order information
+                                       $tableNames[$tableName] = $this->tableDisplayOrder[$tableName];
                                } else {
-                                       // if there are no records in table continue current foreach
-                                       $firstRow = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
-                                               'uid',
-                                               $tableName,
-                                               $this->pidSelect . BackendUtility::deleteClause($tableName) . BackendUtility::versioningPlaceholderClause($tableName)
-                                       );
-                                       if ($firstRow === FALSE) {
-                                               continue;
-                                       }
-                                       $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxDBListItems']) ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxDBListItems'] : $this->itemsLimitPerTable;
+                                       $tableNames[$tableName] = [];
                                }
-                               if ($this->showLimit) {
-                                       $this->iLimit = $this->showLimit;
+                       }
+               }
+               unset($config);
+
+               $orderedTableNames = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($tableNames);
+
+               $db = $this->getDatabaseConnection();
+               foreach ($orderedTableNames as $tableName => $_) {
+                       // check if we are in single- or multi-table mode
+                       if ($this->table) {
+                               $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems']) ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems'] : $this->itemsLimitSingleTable;
+                       } else {
+                               // if there are no records in table continue current foreach
+                               $firstRow = $db->exec_SELECTgetSingleRow(
+                                       'uid',
+                                       $tableName,
+                                       $this->pidSelect . BackendUtility::deleteClause($tableName) . BackendUtility::versioningPlaceholderClause($tableName)
+                               );
+                               if ($firstRow === FALSE) {
+                                       continue;
                                }
-                               // Setting fields to select:
-                               if ($this->allFields) {
-                                       $fields = $this->makeFieldList($tableName);
-                                       $fields[] = 'tstamp';
-                                       $fields[] = 'crdate';
-                                       $fields[] = '_PATH_';
-                                       $fields[] = '_CONTROL_';
-                                       if (is_array($this->setFields[$tableName])) {
-                                               $fields = array_intersect($fields, $this->setFields[$tableName]);
-                                       } else {
-                                               $fields = array();
-                                       }
+                               $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxDBListItems']) ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxDBListItems'] : $this->itemsLimitPerTable;
+                       }
+                       if ($this->showLimit) {
+                               $this->iLimit = $this->showLimit;
+                       }
+                       // Setting fields to select:
+                       if ($this->allFields) {
+                               $fields = $this->makeFieldList($tableName);
+                               $fields[] = 'tstamp';
+                               $fields[] = 'crdate';
+                               $fields[] = '_PATH_';
+                               $fields[] = '_CONTROL_';
+                               if (is_array($this->setFields[$tableName])) {
+                                       $fields = array_intersect($fields, $this->setFields[$tableName]);
                                } else {
                                        $fields = array();
                                }
-                               // Find ID to use (might be different for "versioning_followPages" tables)
-                               if ($this->searchLevels === 0) {
-                                       $this->pidSelect = 'pid=' . (int)$this->id;
-                               }
-                               // Finally, render the list:
-                               $this->HTMLcode .= $this->getTable($tableName, $this->id, implode(',', $fields));
+                       } else {
+                               $fields = array();
                        }
+                       // Find ID to use (might be different for "versioning_followPages" tables)
+                       if ($this->searchLevels === 0) {
+                               $this->pidSelect = 'pid=' . (int)$this->id;
+                       }
+                       // Finally, render the list:
+                       $this->HTMLcode .= $this->getTable($tableName, $this->id, implode(',', $fields));
                }
        }
 
@@ -1032,6 +1068,38 @@ class AbstractDatabaseRecordList extends AbstractRecordList {
        }
 
        /**
+        * Set table display order information
+        *
+        * Structure of $orderInformation:
+        *   'tableName' => [
+        *      'before' => // comma-separated string list or array of table names
+        *      'after' => // comma-separated string list or array of table names
+        * ]
+        *
+        * @param array $orderInformation
+        * @throws \UnexpectedValueException
+        */
+       public function setTableDisplayOrder(array $orderInformation) {
+               foreach ($orderInformation as $tableName => &$configuration) {
+                       if (isset($configuration['before'])) {
+                               if (is_string($configuration['before'])) {
+                                       $configuration['before'] = GeneralUtility::trimExplode(',', $configuration['before'], TRUE);
+                               } elseif (!is_array($configuration['before'])) {
+                                       throw new \UnexpectedValueException('The specified "before" order configuration for table "' . $tableName . '" is invalid.', 1436195933);
+                               }
+                       }
+                       if (isset($configuration['after'])) {
+                               if (is_string($configuration['after'])) {
+                                       $configuration['after'] = GeneralUtility::trimExplode(',', $configuration['after'], TRUE);
+                               } elseif (!is_array($configuration['after'])) {
+                                       throw new \UnexpectedValueException('The specified "after" order configuration for table "' . $tableName . '" is invalid.', 1436195934);
+                               }
+                       }
+               }
+               $this->tableDisplayOrder = $orderInformation;
+       }
+
+       /**
         * @return BackendUserAuthentication
         */
        protected function getBackendUserAuthentication() {
diff --git a/typo3/sysext/recordlist/Tests/Unit/RecordList/AbstractDatabaseRecordListTest.php b/typo3/sysext/recordlist/Tests/Unit/RecordList/AbstractDatabaseRecordListTest.php
new file mode 100644 (file)
index 0000000..52f46a2
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+namespace TYPO3\CMS\Recordlist\Tests\Unit\RecordList;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+use TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList;
+
+/**
+ * Test case
+ */
+class AbstractDatabaseRecordListTest extends UnitTestCase {
+
+       /**
+        * @test
+        * @dataProvider setTableDisplayOrderConvertsStringsDataProvider
+        * @param array $input
+        * @param array $expected
+        */
+       public function setTableDisplayOrderConvertsStringInput(array $input, array $expected) {
+               /** @var AbstractDatabaseRecordList|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface $subject */
+               $subject = $this->getAccessibleMock(AbstractDatabaseRecordList::class, ['dummy']);
+               $subject->setTableDisplayOrder($input);
+               $this->assertSame($expected, $subject->_get('tableDisplayOrder'));
+       }
+
+       /**
+        * @return array
+        */
+       public function setTableDisplayOrderConvertsStringsDataProvider() {
+               return [
+                       'no information at all' => [
+                               [],
+                               []
+                       ],
+                       'string in before' => [
+                               [
+                                       'tableA' => [
+                                               'before' => 'tableB, tableC'
+                                       ]
+                               ],
+                               [
+                                       'tableA' => [
+                                               'before' => ['tableB', 'tableC']
+                                       ]
+                               ]
+                       ],
+                       'array is preserved in before' => [
+                               [
+                                       'tableA' => [
+                                               'before' => ['tableB', 'tableC']
+                                       ]
+                               ],
+                               [
+                                       'tableA' => [
+                                               'before' => ['tableB', 'tableC']
+                                       ]
+                               ]
+                       ],
+                       'array is preserved in before, after is modified' => [
+                               [
+                                       'tableA' => [
+                                               'before' => ['tableB', 'tableC'],
+                                               'after' => 'tableD'
+                                       ]
+                               ],
+                               [
+                                       'tableA' => [
+                                               'before' => ['tableB', 'tableC'],
+                                               'after' => ['tableD']
+                                       ]
+                               ]
+                       ],
+               ];
+       }
+
+       /**
+        * @test
+        * @expectedException \UnexpectedValueException
+        * @expectedExceptionCode 1436195934
+        */
+       public function setTableDisplayOrderThrowsExceptionOnInvalidAfter() {
+               $test = [
+                       'table' => [ 'after' => new \stdClass ]
+               ];
+               $subject = new AbstractDatabaseRecordList();
+               $subject->setTableDisplayOrder($test);
+       }
+
+
+       /**
+        * @test
+        * @expectedException \UnexpectedValueException
+        * @expectedExceptionCode 1436195933
+        */
+       public function setTableDisplayOrderThrowsExceptionOnInvalidBefore() {
+               $test = [
+                       'table' => [ 'before' => new \stdClass ]
+               ];
+               $subject = new AbstractDatabaseRecordList();
+               $subject->setTableDisplayOrder($test);
+       }
+
+}