Commit 6e32ae86 authored by Markus Klein's avatar Markus Klein Committed by Christian Kuhn
Browse files

[FEATURE] List module: Make table display order configurable

Resolves: #65550
Releases: master
Change-Id: I4f6da5f4d4e6a2834e619f683e77d055ac682566
Reviewed-on: http://review.typo3.org/40932


Reviewed-by: Daniel Maier's avatarDaniel Maier <dani-maier@gmx.de>
Tested-by: Daniel Maier's avatarDaniel Maier <dani-maier@gmx.de>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent d2906855
......@@ -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
......
======================================================================
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>, ...
}
......@@ -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);
......
......@@ -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;
......@@ -319,6 +320,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
*
......@@ -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));
}
}
......@@ -1031,6 +1067,38 @@ class AbstractDatabaseRecordList extends AbstractRecordList {
$this->overrideUrlParameters = $urlParameters;
}
/**
* 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
*/
......
<?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);
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment