[!!!][TASK] Decouple classes extending AbstractRecordList 47/53947/15
authorMatthias Vogel <typo3@kanti.de>
Fri, 8 Sep 2017 10:23:25 +0000 (12:23 +0200)
committerAndreas Fernandez <typo3@scripting-base.de>
Fri, 8 Sep 2017 12:46:48 +0000 (14:46 +0200)
In order to refactor the page module, list module,
and the file module individually,
the classes should become independent from each other.

Resolves: #82334
Releases: master
Change-Id: I2ca6aad28cbe4a88c03ef71f47fc11eb4b7649f7
Reviewed-on: https://review.typo3.org/53947
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Andreas Fernandez <typo3@scripting-base.de>
Tested-by: Andreas Fernandez <typo3@scripting-base.de>
typo3/sysext/backend/Classes/RecordList/AbstractRecordList.php
typo3/sysext/backend/Classes/View/PageLayoutView.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-82334-AbstractRecordList.rst [new file with mode: 0644]
typo3/sysext/filelist/Classes/FileList.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php
typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php

index 0f6a912..21d9a61 100644 (file)
@@ -32,11 +32,13 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  *
  * Base for class listing of database records and files in the
  * modules Web>List and File>Filelist
+ * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
  */
 abstract class AbstractRecordList
 {
     /**
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected $id = 0;
 
@@ -44,6 +46,7 @@ abstract class AbstractRecordList
      * default Max items shown
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $iLimit = 10;
 
@@ -51,16 +54,19 @@ abstract class AbstractRecordList
      * OBSOLETE - NOT USED ANYMORE. leftMargin
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $leftMargin = 0;
 
     /**
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $showIcon = 1;
 
     /**
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $no_noWrap = 0;
 
@@ -68,6 +74,7 @@ abstract class AbstractRecordList
      * If set this is <td> CSS-classname for odd columns in addElement. Used with db_layout / pages section
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $oddColumnsCssClass = '';
 
@@ -75,6 +82,7 @@ abstract class AbstractRecordList
      * Decides the columns shown. Filled with values that refers to the keys of the data-array. $this->fieldArray[0] is the title column.
      *
      * @var array
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $fieldArray = [];
 
@@ -82,6 +90,7 @@ abstract class AbstractRecordList
      * Keys are fieldnames and values are td-parameters to add in addElement(), please use $addElement_tdCSSClass for CSS-classes;
      *
      * @var array
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $addElement_tdParams = [];
 
@@ -89,6 +98,7 @@ abstract class AbstractRecordList
      * Keys are fieldnames and values are td-css-classes to add in addElement();
      *
      * @var array
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $addElement_tdCssClass = [];
 
@@ -97,6 +107,7 @@ abstract class AbstractRecordList
      * Max length of strings
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $fixedL = 30;
 
@@ -104,6 +115,7 @@ abstract class AbstractRecordList
      * Script URL
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $thisScript = '';
 
@@ -111,6 +123,7 @@ abstract class AbstractRecordList
      * Set to zero, if you don't want a left-margin with addElement function
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $setLMargin = 1;
 
@@ -118,6 +131,7 @@ abstract class AbstractRecordList
      * Counter increased for each element. Used to index elements for the JavaScript-code that transfers to the clipboard
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $counter = 0;
 
@@ -125,6 +139,7 @@ abstract class AbstractRecordList
      * This could be set to the total number of items. Used by the fwd_rew_navigation...
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $totalItems = '';
 
@@ -132,11 +147,13 @@ abstract class AbstractRecordList
      * Internal (used in this class.)
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $firstElementNumber = 0;
 
     /**
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $eCounter = 0;
 
@@ -144,6 +161,7 @@ abstract class AbstractRecordList
      * String with accumulated HTML content
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $HTMLcode = '';
 
@@ -151,6 +169,7 @@ abstract class AbstractRecordList
      * Contains page translation languages
      *
      * @var array
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $pageOverlays = [];
 
@@ -158,24 +177,29 @@ abstract class AbstractRecordList
      * Contains sys language icons and titles
      *
      * @var array
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $languageIconTitles = [];
 
     /**
      * @var TranslationConfigurationProvider
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $translateTools;
 
     /**
      * @var IconFactory
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected $iconFactory;
 
     /**
      * Constructor
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function __construct()
     {
+        GeneralUtility::logDeprecatedFunction();
         if (isset($GLOBALS['BE_USER']->uc['titleLen']) && $GLOBALS['BE_USER']->uc['titleLen'] > 0) {
             $this->fixedL = $GLOBALS['BE_USER']->uc['titleLen'];
         }
@@ -186,6 +210,7 @@ abstract class AbstractRecordList
 
     /**
      * Sets the script url depending on being a module or script request
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected function determineScriptUrl()
     {
@@ -203,6 +228,7 @@ abstract class AbstractRecordList
 
     /**
      * @return string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected function getThisScript()
     {
@@ -221,6 +247,7 @@ abstract class AbstractRecordList
      * @param string $_2 OBSOLETE - NOT USED ANYMORE. Is the HTML <img>-tag for an alternative 'gfx/ol/line.gif'-icon (used in the top)
      * @param string $colType Defines the tag being used for the columns. Default is td.
      * @return string HTML content for the table row
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function addElement($h, $icon, $data, $rowParams = '', $_ = '', $_2 = '', $colType = 'td')
     {
@@ -303,6 +330,7 @@ abstract class AbstractRecordList
 
     /**
      * Dummy function, used to write the top of a table listing.
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function writeTop()
     {
@@ -313,6 +341,7 @@ abstract class AbstractRecordList
      *
      * @param string $table Table name
      * @return array array([boolean], [HTML]) where [boolean] is 1 for reverse element, [HTML] is the table-row code for the element
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function fwd_rwd_nav($table = '')
     {
@@ -345,6 +374,7 @@ abstract class AbstractRecordList
      * @param string $table Table name
      * @return string
      * @access private
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function fwd_rwd_HTML($type, $pointer, $table = '')
     {
@@ -370,6 +400,7 @@ abstract class AbstractRecordList
      * @param string $table Table name to display. Enter "-1" for the current table.
      * @param string $exclList Comma separated list of fields NOT to include ("sortField", "sortRev" or "firstElementNumber")
      * @return string URL
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function listURL($altId = '', $table = '-1', $exclList = '')
     {
@@ -380,6 +411,7 @@ abstract class AbstractRecordList
      * Returning JavaScript for ClipBoard functionality.
      *
      * @return string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function CBfunctions()
     {
@@ -417,6 +449,7 @@ abstract class AbstractRecordList
 
     /**
      * Initializes page languages and icons
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function initializeLanguages()
     {
@@ -452,6 +485,7 @@ abstract class AbstractRecordList
      * @param int $sys_language_uid Sys language uid
      * @param bool $addAsAdditionalText If set to true, only the flag is returned
      * @return string Language icon
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function languageFlag($sys_language_uid, $addAsAdditionalText = true)
     {
@@ -472,6 +506,7 @@ abstract class AbstractRecordList
      * Gets an instance of TranslationConfigurationProvider
      *
      * @return TranslationConfigurationProvider
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected function getTranslateTools()
     {
@@ -488,6 +523,7 @@ abstract class AbstractRecordList
      * @param int $references number of records from sys_refindex table
      * @param string $launchViewParameter JavaScript String, which will be passed as parameters to top.launchView
      * @return string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected function generateReferenceToolTip($references, $launchViewParameter = '')
     {
@@ -508,6 +544,7 @@ abstract class AbstractRecordList
     /**
      * Returns the language service
      * @return LanguageService
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected function getLanguageService()
     {
index a7067bb..97b0385 100644 (file)
@@ -16,32 +16,43 @@ namespace TYPO3\CMS\Backend\View;
  */
 
 use Doctrine\DBAL\Driver\Statement;
+use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
 use TYPO3\CMS\Backend\Controller\Page\LocalizationController;
 use TYPO3\CMS\Backend\Controller\PageLayoutController;
+use TYPO3\CMS\Backend\Routing\Router;
+use TYPO3\CMS\Backend\Routing\UriBuilder;
+use TYPO3\CMS\Backend\Tree\View\PageTreeView;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
+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;
+use TYPO3\CMS\Core\Utility\HttpUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
 use TYPO3\CMS\Core\Versioning\VersionState;
 use TYPO3\CMS\Extbase\Service\FlexFormService;
 use TYPO3\CMS\Fluid\View\StandaloneView;
+use TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList;
 
 /**
  * Child class for the Web > Page module
  */
-class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList
+class PageLayoutView
 {
     /**
      * If TRUE, users/groups are shown in the page info box.
@@ -166,6 +177,370 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
      */
     public $itemLabels = [];
 
+    /**
+     * Indicates if all available fields for a user should be selected or not.
+     *
+     * @var int
+     */
+    public $allFields = 0;
+
+    /**
+     * Number of records to show
+     *
+     * @var int
+     */
+    public $showLimit = 0;
+
+    /**
+     * Whether to show localization view or not
+     *
+     * @var bool
+     */
+    public $localizationView = false;
+
+    /**
+     * Shared module configuration, used by localization features
+     *
+     * @var array
+     */
+    public $modSharedTSconfig = [];
+
+    /**
+     * Tables which should not get listed
+     *
+     * @var string
+     */
+    public $hideTables = '';
+
+    /**
+     * Containing which fields to display in extended mode
+     *
+     * @var string[]
+     */
+    public $displayFields;
+
+    /**
+     * Tables which should not list their translations
+     *
+     * @var string
+     */
+    public $hideTranslations = '';
+
+    /**
+     * If set, csvList is outputted.
+     *
+     * @var bool
+     */
+    public $csvOutput = false;
+
+    /**
+     * Cache for record path
+     *
+     * @var mixed[]
+     */
+    public $recPath_cache = [];
+
+    /**
+     * Field, to sort list by
+     *
+     * @var string
+     */
+    public $sortField;
+
+    /**
+     * default Max items shown per table in "multi-table mode", may be overridden by tables.php
+     *
+     * @var int
+     */
+    public $itemsLimitPerTable = 20;
+
+    /**
+     * Page select permissions
+     *
+     * @var string
+     */
+    public $perms_clause = '';
+
+    /**
+     * Page id
+     *
+     * @var int
+     */
+    public $id;
+
+    /**
+     * Return URL
+     *
+     * @var string
+     */
+    public $returnUrl = '';
+
+    /**
+     * Tablename if single-table mode
+     *
+     * @var string
+     */
+    public $table = '';
+
+    /**
+     * Some permissions...
+     *
+     * @var int
+     */
+    public $calcPerms = 0;
+
+    /**
+     * Mode for what happens when a user clicks the title of a record.
+     *
+     * @var string
+     */
+    public $clickTitleMode = '';
+
+    /**
+     * Levels to search down.
+     *
+     * @var int
+     */
+    public $searchLevels = '';
+
+    /**
+     * "LIMIT " in SQL...
+     *
+     * @var int
+     */
+    public $iLimit = 0;
+
+    /**
+     * Set to the total number of items for a table when selecting.
+     *
+     * @var string
+     */
+    public $totalItems = '';
+
+    /**
+     * TSconfig which overwrites TCA-Settings
+     *
+     * @var mixed[][]
+     */
+    public $tableTSconfigOverTCA = [];
+
+    /**
+     * Loaded with page record with version overlay if any.
+     *
+     * @var string[]
+     */
+    public $pageRecord = [];
+
+    /**
+     * Used for tracking duplicate values of fields
+     *
+     * @var string[]
+     */
+    public $duplicateStack = [];
+
+    /**
+     * Fields to display for the current table
+     *
+     * @var string[]
+     */
+    public $setFields = [];
+
+    /**
+     * Current script name
+     *
+     * @var string
+     */
+    public $script = 'index.php';
+
+    /**
+     * If TRUE, records are listed only if a specific table is selected.
+     *
+     * @var bool
+     */
+    public $listOnlyInSingleTableMode = false;
+
+    /**
+     * JavaScript code accumulation
+     *
+     * @var string
+     */
+    public $JScode = '';
+
+    /**
+     * Pointer for browsing list
+     *
+     * @var int
+     */
+    public $firstElementNumber = 0;
+
+    /**
+     * Counting the elements no matter what...
+     *
+     * @var int
+     */
+    public $eCounter = 0;
+
+    /**
+     * Search string
+     *
+     * @var string
+     */
+    public $searchString = '';
+
+    /**
+     * default Max items shown per table in "single-table mode", may be overridden by tables.php
+     *
+     * @var int
+     */
+    public $itemsLimitSingleTable = 100;
+
+    /**
+     * Field, indicating to sort in reverse order.
+     *
+     * @var bool
+     */
+    public $sortRev;
+
+    /**
+     * String, can contain the field name from a table which must have duplicate values marked.
+     *
+     * @var string
+     */
+    public $duplicateField;
+
+    /**
+     * Specify a list of tables which are the only ones allowed to be displayed.
+     *
+     * @var string
+     */
+    public $tableList = '';
+
+    /**
+     * Array of collapsed / uncollapsed tables in multi table view
+     *
+     * @var int[][]
+     */
+    public $tablesCollapsed = [];
+
+    /**
+     * @var array[] Module configuration
+     */
+    public $modTSconfig;
+
+    /**
+     * HTML output
+     *
+     * @var string
+     */
+    public $HTMLcode = '';
+
+    /**
+     * Thumbnails on records containing files (pictures)
+     *
+     * @var bool
+     */
+    public $thumbs = 0;
+
+    /**
+     * Used for tracking next/prev uids
+     *
+     * @var int[][]
+     */
+    public $currentTable = [];
+
+    /**
+     * OBSOLETE - NOT USED ANYMORE. leftMargin
+     *
+     * @var int
+     */
+    public $leftMargin = 0;
+
+    /**
+     * Decides the columns shown. Filled with values that refers to the keys of the data-array. $this->fieldArray[0] is the title column.
+     *
+     * @var array
+     */
+    public $fieldArray = [];
+
+    /**
+     * Set to zero, if you don't want a left-margin with addElement function
+     *
+     * @var int
+     */
+    public $setLMargin = 1;
+
+    /**
+     * Contains page translation languages
+     *
+     * @var array
+     */
+    public $pageOverlays = [];
+
+    /**
+     * Counter increased for each element. Used to index elements for the JavaScript-code that transfers to the clipboard
+     *
+     * @var int
+     */
+    public $counter = 0;
+
+    /**
+     * Contains sys language icons and titles
+     *
+     * @var array
+     */
+    public $languageIconTitles = [];
+
+    /**
+     * Script URL
+     *
+     * @var string
+     */
+    public $thisScript = '';
+
+    /**
+     * If set this is <td> CSS-classname for odd columns in addElement. Used with db_layout / pages section
+     *
+     * @var string
+     */
+    public $oddColumnsCssClass = '';
+
+    /**
+     * Not used in this class - but maybe extension classes...
+     * Max length of strings
+     *
+     * @var int
+     */
+    public $fixedL = 30;
+
+    /**
+     * @var TranslationConfigurationProvider
+     */
+    public $translateTools;
+
+    /**
+     * Keys are fieldnames and values are td-parameters to add in addElement(), please use $addElement_tdCSSClass for CSS-classes;
+     *
+     * @var array
+     */
+    public $addElement_tdParams = [];
+
+    /**
+     * @var int
+     */
+    public $no_noWrap = 0;
+
+    /**
+     * @var int
+     */
+    public $showIcon = 1;
+
+    /**
+     * Keys are fieldnames and values are td-css-classes to add in addElement();
+     *
+     * @var array
+     */
+    public $addElement_tdCssClass = [];
+
     /**
      * @var \TYPO3\CMS\Backend\Clipboard\Clipboard
      */
@@ -218,11 +593,41 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
     protected $localizationController;
 
     /**
-     * Construct to initialize class variables.
+     * Override the page ids taken into account by getPageIdConstraint()
+     *
+     * @var array
      */
-    public function __construct()
-    {
-        parent::__construct();
+    protected $overridePageIdList = [];
+
+    /**
+     * Override/add urlparameters in listUrl() method
+     *
+     * @var string[]
+     */
+    protected $overrideUrlParameters = [];
+
+    /**
+     * Array with before/after setting for tables
+     * Structure:
+     * 'tableName' => [
+     *    'before' => ['A', ...]
+     *    'after' => []
+     *  ]
+     * @var array[]
+     */
+    protected $tableDisplayOrder = [];
+
+    /**
+     * Construct to initialize class variables.
+     */
+    public function __construct()
+    {
+        if (isset($GLOBALS['BE_USER']->uc['titleLen']) && $GLOBALS['BE_USER']->uc['titleLen'] > 0) {
+            $this->fixedL = $GLOBALS['BE_USER']->uc['titleLen'];
+        }
+        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+        $this->getTranslateTools();
+        $this->determineScriptUrl();
         $this->localizationController = GeneralUtility::makeInstance(LocalizationController::class);
         $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
         $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
@@ -2152,407 +2557,1879 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
                     }
                 }
             }
-            // Remove disabled languages
-            $modSharedTSconfig = BackendUtility::getModTSconfig($id, 'mod.SHARED');
-            $disableLanguages = isset($modSharedTSconfig['properties']['disableLanguages'])
-                ? GeneralUtility::trimExplode(',', $modSharedTSconfig['properties']['disableLanguages'], true)
-                : [];
-            if (!empty($langSelItems) && !empty($disableLanguages)) {
-                foreach ($disableLanguages as $language) {
-                    if ($language != 0 && isset($langSelItems[$language])) {
-                        unset($langSelItems[$language]);
+            // Remove disabled languages
+            $modSharedTSconfig = BackendUtility::getModTSconfig($id, 'mod.SHARED');
+            $disableLanguages = isset($modSharedTSconfig['properties']['disableLanguages'])
+                ? GeneralUtility::trimExplode(',', $modSharedTSconfig['properties']['disableLanguages'], true)
+                : [];
+            if (!empty($langSelItems) && !empty($disableLanguages)) {
+                foreach ($disableLanguages as $language) {
+                    if ($language != 0 && isset($langSelItems[$language])) {
+                        unset($langSelItems[$language]);
+                    }
+                }
+            }
+            // If any languages are left, make selector:
+            if (count($langSelItems) > 1) {
+                $url = BackendUtility::getModuleUrl('record_edit', [
+                    'edit[pages_language_overlay][' . $id . ']' => 'new',
+                    'overrideVals[pages_language_overlay][doktype]' => (int)$this->pageRecord['doktype'],
+                    'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
+                ]);
+                $onChangeContent = 'window.location.href=' . GeneralUtility::quoteJSvalue($url . '&overrideVals[pages_language_overlay][sys_language_uid]=') . '+this.options[this.selectedIndex].value';
+                return '<div class="form-inline form-inline-spaced">'
+                . '<div class="form-group">'
+                . '<label for="createNewLanguage">'
+                . htmlspecialchars($this->getLanguageService()->getLL('new_language'))
+                . '</label>'
+                . '<select class="form-control input-sm" name="createNewLanguage" onchange="' . htmlspecialchars($onChangeContent) . '">'
+                . implode('', $langSelItems)
+                . '</select></div></div>';
+            }
+        }
+        return '';
+    }
+
+    /**
+     * Traverse the result pointer given, adding each record to array and setting some internal values at the same time.
+     *
+     * @param Statement $result DBAL Statement
+     * @param string $table Table name defaulting to tt_content
+     * @return array The selected rows returned in this array.
+     */
+    public function getResult(Statement $result, string $table = 'tt_content'): array
+    {
+        $output = [];
+        // Traverse the result:
+        while ($row = $result->fetch()) {
+            BackendUtility::workspaceOL($table, $row, -99, true);
+            if ($row) {
+                // Add the row to the array:
+                $output[] = $row;
+            }
+        }
+        $this->generateTtContentDataArray($output);
+        // Return selected records
+        return $output;
+    }
+
+    /********************************
+     *
+     * Various helper functions
+     *
+     ********************************/
+
+    /**
+     * Initializes the clipboard for generating paste links
+     *
+     *
+     * @see \TYPO3\CMS\Recordlist\RecordList::main()
+     * @see \TYPO3\CMS\Backend\Controller\ContextMenuController::clipboardAction()
+     * @see \TYPO3\CMS\Filelist\Controller\FileListController::indexAction()
+     */
+    protected function initializeClipboard()
+    {
+        // Start clipboard
+        $this->clipboard = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Clipboard\Clipboard::class);
+
+        // Initialize - reads the clipboard content from the user session
+        $this->clipboard->initializeClipboard();
+
+        // This locks the clipboard to the Normal for this request.
+        $this->clipboard->lockToNormal();
+
+        // Clean up pad
+        $this->clipboard->cleanCurrent();
+
+        // Save the clipboard content
+        $this->clipboard->endClipboard();
+    }
+
+    /**
+     * Generates the data for previous and next elements which is needed for movements.
+     *
+     * @param array $rowArray
+     */
+    protected function generateTtContentDataArray(array $rowArray)
+    {
+        if (empty($this->tt_contentData)) {
+            $this->tt_contentData = [
+                'nextThree' => [],
+                'next' => [],
+                'prev' => [],
+            ];
+        }
+        foreach ($rowArray as $key => $value) {
+            // Create the list of the next three ids (for editing links...)
+            for ($i = 0; $i < $this->nextThree; $i++) {
+                if (isset($rowArray[$key - $i])
+                    && !GeneralUtility::inList($this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']], $value['uid'])
+                ) {
+                    $this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']] .= $value['uid'] . ',';
+                }
+            }
+
+            // Create information for next and previous content elements
+            if (isset($rowArray[$key - 1])) {
+                if (isset($rowArray[$key - 2])) {
+                    $this->tt_contentData['prev'][$value['uid']] = -$rowArray[$key - 2]['uid'];
+                } else {
+                    $this->tt_contentData['prev'][$value['uid']] = $value['pid'];
+                }
+                $this->tt_contentData['next'][$rowArray[$key - 1]['uid']] = -$value['uid'];
+            }
+        }
+    }
+
+    /**
+     * Counts and returns the number of records on the page with $pid
+     *
+     * @param string $table Table name
+     * @param int $pid Page id
+     * @return int Number of records.
+     */
+    public function numberOfRecords($table, $pid)
+    {
+        $count = 0;
+        if ($GLOBALS['TCA'][$table]) {
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getQueryBuilderForTable($table);
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+            $count = (int)$queryBuilder->count('uid')
+                ->from($table)
+                ->where(
+                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT))
+                )
+                ->execute()
+                ->fetchColumn();
+        }
+
+        return $count;
+    }
+
+    /**
+     * Processing of larger amounts of text (usually from RTE/bodytext fields) with word wrapping etc.
+     *
+     * @param string $input Input string
+     * @return string Output string
+     */
+    public function renderText($input)
+    {
+        $input = strip_tags($input);
+        $input = GeneralUtility::fixed_lgd_cs($input, 1500);
+        return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
+    }
+
+    /**
+     * Creates the icon image tag for record from table and wraps it in a link which will trigger the click menu.
+     *
+     * @param string $table Table name
+     * @param array $row Record array
+     * @param string $enabledClickMenuItems Passthrough to wrapClickMenuOnIcon
+     * @return string HTML for the icon
+     */
+    public function getIcon($table, $row, $enabledClickMenuItems = '')
+    {
+        // Initialization
+        $toolTip = BackendUtility::getRecordToolTip($row, 'tt_content');
+        $icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
+        $this->counter++;
+        // The icon with link
+        if ($this->getBackendUser()->recordEditAccessInternals($table, $row)) {
+            $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
+        }
+        return $icon;
+    }
+
+    /**
+     * Creates processed values for all field names in $fieldList based on values from $row array.
+     * The result is 'returned' through $info which is passed as a reference
+     *
+     * @param string $table Table name
+     * @param string $fieldList Comma separated list of fields.
+     * @param array $row Record from which to take values for processing.
+     * @param array $info Array to which the processed values are added.
+     */
+    public function getProcessedValue($table, $fieldList, array $row, array &$info)
+    {
+        // Splitting values from $fieldList
+        $fieldArr = explode(',', $fieldList);
+        // Traverse fields from $fieldList
+        foreach ($fieldArr as $field) {
+            if ($row[$field]) {
+                $info[] = '<strong>' . htmlspecialchars($this->itemLabels[$field]) . '</strong> '
+                    . htmlspecialchars(BackendUtility::getProcessedValue($table, $field, $row[$field]));
+            }
+        }
+    }
+
+    /**
+     * Returns TRUE, if the record given as parameters is NOT visible based on hidden/starttime/endtime (if available)
+     *
+     * @param string $table Tablename of table to test
+     * @param array $row Record row.
+     * @return bool Returns TRUE, if disabled.
+     */
+    public function isDisabled($table, $row)
+    {
+        $enableCols = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
+        return $enableCols['disabled'] && $row[$enableCols['disabled']]
+            || $enableCols['starttime'] && $row[$enableCols['starttime']] > $GLOBALS['EXEC_TIME']
+            || $enableCols['endtime'] && $row[$enableCols['endtime']] && $row[$enableCols['endtime']] < $GLOBALS['EXEC_TIME'];
+    }
+
+    /**
+     * Returns icon for "no-edit" of a record.
+     * Basically, the point is to signal that this record could have had an edit link if
+     * the circumstances were right. A placeholder for the regular edit icon...
+     *
+     * @param string $label Label key from LOCAL_LANG
+     * @return string IMG tag for icon.
+     */
+    public function noEditIcon($label = 'noEditItems')
+    {
+        $title = htmlspecialchars($this->getLanguageService()->getLL($label));
+        return '<span title="' . $title . '">' . $this->iconFactory->getIcon('status-status-edit-read-only', Icon::SIZE_SMALL)->render() . '</span>';
+    }
+
+    /*****************************************
+     *
+     * External renderings
+     *
+     *****************************************/
+
+    /**
+     * Creates a menu of the tables that can be listed by this function
+     * Only tables which has records on the page will be included.
+     * Notice: The function also fills in the internal variable $this->activeTables with icon/titles.
+     *
+     * @param int $id Page id from which we are listing records (the function will look up if there are records on the page)
+     * @return string HTML output.
+     */
+    public function getTableMenu($id)
+    {
+        // Initialize:
+        $this->activeTables = [];
+        $theTables = ['tt_content'];
+        // External tables:
+        if (is_array($this->externalTables)) {
+            $theTables = array_unique(array_merge($theTables, array_keys($this->externalTables)));
+        }
+        $out = '';
+        // Traverse tables to check:
+        foreach ($theTables as $tName) {
+            // Check access and whether the proper extensions are loaded:
+            if ($this->getBackendUser()->check('tables_select', $tName)
+                && (
+                    isset($this->externalTables[$tName])
+                    || $tName === 'fe_users' || $tName === 'tt_content'
+                    || \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded($tName)
+                )
+            ) {
+                // Make query to count records from page:
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getQueryBuilderForTable($tName);
+                $queryBuilder->getRestrictions()
+                    ->removeAll()
+                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                    ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+                $count = $queryBuilder->count('uid')
+                    ->from($tName)
+                    ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
+                    ->execute()
+                    ->fetchColumn();
+                // If records were found (or if "tt_content" is the table...):
+                if ($count || $tName === 'tt_content') {
+                    // Add row to menu:
+                    $out .= '
+                                       <td><a href="#' . $tName . '" title="' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title'])) . '"></a>'
+                        . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
+                        . '</td>';
+                    // ... and to the internal array, activeTables we also add table icon and title (for use elsewhere)
+                    $title = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title']))
+                        . ': ' . $count . ' ' . htmlspecialchars($this->getLanguageService()->getLL('records'));
+                    $this->activeTables[$tName] = '<span title="' . $title . '">'
+                        . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
+                        . '</span>'
+                        . '&nbsp;' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title']));
+                }
+            }
+        }
+        // Wrap cells in table tags:
+        $out = '
+            <!--
+                Menu of tables on the page (table menu)
+            -->
+            <table border="0" cellpadding="0" cellspacing="0" id="typo3-page-tblMenu">
+                               <tr>' . $out . '
+                </tr>
+                       </table>';
+        // Return the content:
+        return $out;
+    }
+
+    /**
+     * Create thumbnail code for record/field but not linked
+     *
+     * @param mixed[] $row Record array
+     * @param string $table Table (record is from)
+     * @param string $field Field name for which thumbnail are to be rendered.
+     * @return string HTML for thumbnails, if any.
+     */
+    public function getThumbCodeUnlinked($row, $table, $field)
+    {
+        return BackendUtility::thumbCode($row, $table, $field, '', '', null, 0, '', '', false);
+    }
+
+    /**
+     * Checks whether translated Content Elements exist in the desired language
+     * If so, deny creating new ones via the UI
+     *
+     * @param array $contentElements
+     * @param int $language
+     * @return bool
+     */
+    protected function checkIfTranslationsExistInLanguage(array $contentElements, $language)
+    {
+        // If in default language, you may always create new entries
+        // Also, you may override this strict behavior via user TS Config
+        // If you do so, you're on your own and cannot rely on any support by the TYPO3 core
+        // We jump out here since we don't need to do the expensive loop operations
+        $allowInconsistentLanguageHandling = BackendUtility::getModTSconfig($this->id, 'mod.web_layout.allowInconsistentLanguageHandling');
+        if ($language === 0 || $allowInconsistentLanguageHandling['value'] === '1') {
+            return false;
+        }
+        /**
+         * Build up caches
+         */
+        if (!isset($this->languageHasTranslationsCache[$language])) {
+            foreach ($contentElements as $columns) {
+                foreach ($columns as $contentElement) {
+                    if ((int)$contentElement['l18n_parent'] === 0) {
+                        $this->languageHasTranslationsCache[$language]['hasStandAloneContent'] = true;
+                    }
+                    if ((int)$contentElement['l18n_parent'] > 0) {
+                        $this->languageHasTranslationsCache[$language]['hasTranslations'] = true;
+                    }
+                }
+            }
+            // Check whether we have a mix of both
+            if ($this->languageHasTranslationsCache[$language]['hasStandAloneContent']
+                && $this->languageHasTranslationsCache[$language]['hasTranslations']
+            ) {
+                $message = GeneralUtility::makeInstance(
+                    FlashMessage::class,
+                    sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $this->languageIconTitles[$language]['title']),
+                    sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $this->languageIconTitles[$language]['title']),
+                    FlashMessage::WARNING
+                );
+                $service = GeneralUtility::makeInstance(FlashMessageService::class);
+                $queue = $service->getMessageQueueByIdentifier();
+                $queue->addMessage($message);
+            }
+        }
+        if ($this->languageHasTranslationsCache[$language]['hasTranslations']) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return BackendLayoutView
+     */
+    protected function getBackendLayoutView()
+    {
+        return GeneralUtility::makeInstance(BackendLayoutView::class);
+    }
+
+    /**
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser()
+    {
+        return $GLOBALS['BE_USER'];
+    }
+
+    /**
+     * @return PageLayoutController
+     */
+    protected function getPageLayoutController()
+    {
+        return $GLOBALS['SOBE'];
+    }
+
+    /**
+     * Initializes the list generation
+     *
+     * @param int $id Page id for which the list is rendered. Must be >= 0
+     * @param string $table Tablename - if extended mode where only one table is listed at a time.
+     * @param int $pointer Browsing pointer.
+     * @param string $search Search word, if any
+     * @param int $levels Number of levels to search down the page tree
+     * @param int $showLimit Limit of records to be listed.
+     */
+    public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
+    {
+        $backendUser = $this->getBackendUserAuthentication();
+        // Setting internal variables:
+        // sets the parent id
+        $this->id = (int)$id;
+        if ($GLOBALS['TCA'][$table]) {
+            // Setting single table mode, if table exists:
+            $this->table = $table;
+        }
+        $this->firstElementNumber = $pointer;
+        $this->searchString = trim($search);
+        $this->searchLevels = (int)$levels;
+        $this->showLimit = MathUtility::forceIntegerInRange($showLimit, 0, 10000);
+        // Setting GPvars:
+        $this->csvOutput = (bool)GeneralUtility::_GP('csv');
+        $this->sortField = GeneralUtility::_GP('sortField');
+        $this->sortRev = GeneralUtility::_GP('sortRev');
+        $this->displayFields = GeneralUtility::_GP('displayFields');
+        $this->duplicateField = GeneralUtility::_GP('duplicateField');
+        if (GeneralUtility::_GP('justLocalized')) {
+            $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
+        }
+        // Init dynamic vars:
+        $this->counter = 0;
+        $this->JScode = '';
+        $this->HTMLcode = '';
+        // Limits
+        if (isset($this->modTSconfig['properties']['itemsLimitPerTable'])) {
+            $this->itemsLimitPerTable = MathUtility::forceIntegerInRange(
+                (int)$this->modTSconfig['properties']['itemsLimitPerTable'],
+                1,
+                10000
+            );
+        }
+        if (isset($this->modTSconfig['properties']['itemsLimitSingleTable'])) {
+            $this->itemsLimitSingleTable = MathUtility::forceIntegerInRange(
+                (int)$this->modTSconfig['properties']['itemsLimitSingleTable'],
+                1,
+                10000
+            );
+        }
+
+        // $table might be NULL at this point in the code. As the expressionBuilder
+        // is used to limit returned records based on the page permissions and the
+        // uid field of the pages it can hardcoded to work on the pages table.
+        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('pages')
+            ->expr();
+        $permsClause = $expressionBuilder->andX($backendUser->getPagePermsClause(1));
+        // This will hide records from display - it has nothing to do with user rights!!
+        if ($pidList = $backendUser->getTSConfigVal('options.hideRecords.pages')) {
+            $pidList = GeneralUtility::intExplode(',', $pidList, true);
+            if (!empty($pidList)) {
+                $permsClause->add($expressionBuilder->notIn('pages.uid', $pidList));
+            }
+        }
+        $this->perms_clause = (string)$permsClause;
+
+        // Get configuration of collapsed tables from user uc and merge with sanitized GP vars
+        $this->tablesCollapsed = is_array($backendUser->uc['moduleData']['list'])
+            ? $backendUser->uc['moduleData']['list']
+            : [];
+        $collapseOverride = GeneralUtility::_GP('collapse');
+        if (is_array($collapseOverride)) {
+            foreach ($collapseOverride as $collapseTable => $collapseValue) {
+                if (is_array($GLOBALS['TCA'][$collapseTable]) && ($collapseValue == 0 || $collapseValue == 1)) {
+                    $this->tablesCollapsed[$collapseTable] = $collapseValue;
+                }
+            }
+            // Save modified user uc
+            $backendUser->uc['moduleData']['list'] = $this->tablesCollapsed;
+            $backendUser->writeUC($backendUser->uc);
+            $returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
+            if ($returnUrl !== '') {
+                HttpUtility::redirect($returnUrl);
+            }
+        }
+
+        // Initialize languages:
+        if ($this->localizationView) {
+            $this->initializeLanguages();
+        }
+    }
+
+    /**
+     * Traverses the table(s) to be listed and renders the output code for each:
+     * The HTML is accumulated in $this->HTMLcode
+     * Finishes off with a stopper-gif
+     */
+    public function generateList()
+    {
+        // Set page record in header
+        $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
+        $hideTablesArray = GeneralUtility::trimExplode(',', $this->hideTables);
+
+        $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)
+                || !$backendUser->check('tables_select', $tableName)
+            ) {
+                $hideTable = true;
+            }
+
+            if (!$hideTable) {
+                // Don't show table if hidden by TCA ctrl section
+                // Don't show table if hidden by pageTSconfig mod.web_list.hideTables
+                $hideTable = $hideTable
+                    || !empty($GLOBALS['TCA'][$tableName]['ctrl']['hideTable'])
+                    || in_array($tableName, $hideTablesArray, true)
+                    || in_array('*', $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 = (bool)$this->tableTSconfigOverTCA[$tableName . '.']['hideTable'];
+                }
+            }
+            if ($hideTable) {
+                unset($tableNames[$tableName]);
+            } else {
+                if (isset($this->tableDisplayOrder[$tableName])) {
+                    // Copy display order information
+                    $tableNames[$tableName] = $this->tableDisplayOrder[$tableName];
+                } else {
+                    $tableNames[$tableName] = [];
+                }
+            }
+        }
+        unset($config);
+
+        $orderedTableNames = GeneralUtility::makeInstance(DependencyOrderingService::class)
+            ->orderByDependencies($tableNames);
+
+        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
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getQueryBuilderForTable($tableName);
+                $queryBuilder->getRestrictions()
+                    ->removeAll()
+                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                    ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+                $queryBuilder = $this->addPageIdConstraint($tableName, $queryBuilder);
+                $firstRow = $queryBuilder->select('uid')
+                    ->from($tableName)
+                    ->execute()
+                    ->fetch();
+                if (!is_array($firstRow)) {
+                    continue;
+                }
+                $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 = [];
+                }
+            } else {
+                $fields = [];
+            }
+
+            // Finally, render the list:
+            $this->HTMLcode .= $this->getTable($tableName, $this->id, implode(',', $fields));
+        }
+    }
+
+    /**
+     * Creates the search box
+     *
+     * @param bool $formFields If TRUE, the search box is wrapped in its own form-tags
+     * @return string HTML for the search box
+     */
+    public function getSearchBox($formFields = true)
+    {
+        /** @var $iconFactory IconFactory */
+        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+        $lang = $this->getLanguageService();
+        // Setting form-elements, if applicable:
+        $formElements = ['', ''];
+        if ($formFields) {
+            $formElements = [
+                '<form action="' . htmlspecialchars(
+                    $this->listURL('', '-1', 'firstElementNumber,search_field')
+                ) . '" method="post">',
+                '</form>'
+            ];
+        }
+        // Make level selector:
+        $opt = [];
+
+        // "New" generation of search levels ... based on TS config
+        $config = BackendUtility::getPagesTSconfig($this->id);
+        $searchLevelsFromTSconfig = $config['mod.']['web_list.']['searchLevel.']['items.'];
+        $searchLevelItems = [];
+
+        // get translated labels for search levels from pagets
+        foreach ($searchLevelsFromTSconfig as $keySearchLevel => $labelConfigured) {
+            $label = $lang->sL('LLL:' . $labelConfigured);
+            if ($label === '') {
+                $label = $labelConfigured;
+            }
+            $searchLevelItems[$keySearchLevel] = $label;
+        }
+
+        foreach ($searchLevelItems as $kv => $label) {
+            $opt[] = '<option value="' . $kv . '"' . ($kv === $this->searchLevels ? ' selected="selected"' : '') . '>' . htmlspecialchars(
+                    $label
+                ) . '</option>';
+        }
+        $lMenu = '<select class="form-control" name="search_levels" title="' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.search_levels')
+            ) . '" id="search_levels">' . implode('', $opt) . '</select>';
+        // Table with the search box:
+        $content = '<div class="db_list-searchbox-form db_list-searchbox-toolbar module-docheader-bar module-docheader-bar-search t3js-module-docheader-bar t3js-module-docheader-bar-search" id="db_list-searchbox-toolbar" style="display: ' . ($this->searchString == '' ? 'none' : 'block') . ';">
+                       ' . $formElements[0] . '
+                <div id="typo3-dblist-search">
+                    <div class="panel panel-default">
+                        <div class="panel-body">
+                            <div class="row">
+                                <div class="form-group col-xs-12">
+                                    <label for="search_field">' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.searchString')
+            ) . ': </label>
+                                                                       <input class="form-control" type="search" placeholder="' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.enterSearchString')
+            ) . '" title="' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.searchString')
+            ) . '" name="search_field" id="search_field" value="' . htmlspecialchars($this->searchString) . '" />
+                                </div>
+                                <div class="form-group col-xs-12 col-sm-6">
+                                                                       <label for="search_levels">' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.search_levels')
+            ) . ': </label>
+                                                                       ' . $lMenu . '
+                                </div>
+                                <div class="form-group col-xs-12 col-sm-6">
+                                                                       <label for="showLimit">' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.limit')
+            ) . ': </label>
+                                                                       <input class="form-control" type="number" min="0" max="10000" placeholder="10" title="' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.limit')
+            ) . '" name="showLimit" id="showLimit" value="' . htmlspecialchars(
+                ($this->showLimit ? $this->showLimit : '')
+            ) . '" />
+                                </div>
+                                <div class="form-group col-xs-12">
+                                    <div class="form-control-wrap">
+                                        <button type="submit" class="btn btn-default" name="search" title="' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.search')
+            ) . '">
+                                            ' . $iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render(
+            ) . ' ' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.search')
+            ) . '
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                       ' . $formElements[1] . '</div>';
+        return $content;
+    }
+
+    /**
+     * Setting the field names to display in extended list.
+     * Sets the internal variable $this->setFields
+     */
+    public function setDispFields()
+    {
+        $backendUser = $this->getBackendUserAuthentication();
+        // Getting from session:
+        $dispFields = $backendUser->getModuleData('list/displayFields');
+        // If fields has been inputted, then set those as the value and push it to session variable:
+        if (is_array($this->displayFields)) {
+            reset($this->displayFields);
+            $tKey = key($this->displayFields);
+            $dispFields[$tKey] = $this->displayFields[$tKey];
+            $backendUser->pushModuleData('list/displayFields', $dispFields);
+        }
+        // Setting result:
+        $this->setFields = $dispFields;
+    }
+
+    /**
+     * Create thumbnail code for record/field
+     *
+     * @param mixed[] $row Record array
+     * @param string $table Table (record is from)
+     * @param string $field Field name for which thumbnail are to be rendered.
+     * @return string HTML for thumbnails, if any.
+     */
+    public function thumbCode($row, $table, $field)
+    {
+        return BackendUtility::thumbCode($row, $table, $field);
+    }
+
+    /**
+     * Returns a QueryBuilder configured to select $fields from $table where the pid is restricted
+     * depending on the current searchlevel setting.
+     *
+     * @param string $table Table name
+     * @param int $pageId Page id Only used to build the search constraints, getPageIdConstraint() used for restrictions
+     * @param string[] $additionalConstraints Additional part for where clause
+     * @param string[] $fields Field list to select, * for all
+     * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
+     */
+    public function getQueryBuilder(
+        string $table,
+        int $pageId,
+        array $additionalConstraints = [],
+        array $fields = ['*']
+    ): QueryBuilder {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($table);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+        $queryBuilder
+            ->select(...$fields)
+            ->from($table);
+
+        if (!empty($additionalConstraints)) {
+            $queryBuilder->andWhere(...$additionalConstraints);
+        }
+
+        $queryBuilder = $this->prepareQueryBuilder($table, $pageId, $fields, $additionalConstraints, $queryBuilder);
+
+        return $queryBuilder;
+    }
+
+    /**
+     * Return the modified QueryBuilder object ($queryBuilder) which will be
+     * used to select the records from a table $table with pid = $this->pidList
+     *
+     * @param string $table Table name
+     * @param int $pageId Page id Only used to build the search constraints, $this->pidList is used for restrictions
+     * @param string[] $fieldList List of fields to select from the table
+     * @param string[] $additionalConstraints Additional part for where clause
+     * @param QueryBuilder $queryBuilder
+     * @paran bool $addSorting
+     * @return QueryBuilder
+     */
+    protected function prepareQueryBuilder(
+        string $table,
+        int $pageId,
+        array $fieldList = ['*'],
+        array $additionalConstraints = [],
+        QueryBuilder $queryBuilder,
+        bool $addSorting = true
+    ): QueryBuilder {
+        $parameters = [
+            'table' => $table,
+            'fields' => $fieldList,
+            'groupBy' => null,
+            'orderBy' => null,
+            'firstResult' => $this->firstElementNumber ?: null,
+            'maxResults' => $this->iLimit ?: null
+        ];
+
+        if ($this->iLimit !== null) {
+            $queryBuilder->setMaxResults($this->iLimit);
+        }
+
+        if ($addSorting) {
+            if ($this->sortField && in_array($this->sortField, $this->makeFieldList($table, 1))) {
+                $queryBuilder->orderBy($this->sortField, $this->sortRev ? 'DESC' : 'ASC');
+            } else {
+                $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?: $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
+                $orderBys = QueryHelper::parseOrderBy((string)$orderBy);
+                foreach ($orderBys as $orderBy) {
+                    $queryBuilder->orderBy($orderBy[0], $orderBy[1]);
+                }
+            }
+        }
+
+        // Build the query constraints
+        $queryBuilder = $this->addPageIdConstraint($table, $queryBuilder);
+        $searchWhere = $this->makeSearchString($table, $pageId);
+        if (!empty($searchWhere)) {
+            $queryBuilder->andWhere($searchWhere);
+        }
+
+        // Filtering on displayable pages (permissions):
+        if ($table === 'pages' && $this->perms_clause) {
+            $queryBuilder->andWhere($this->perms_clause);
+        }
+
+        // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
+        if (
+            $table !== 'pages_language_overlay'
+            && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
+            && (GeneralUtility::inList($this->hideTranslations, $table) || $this->hideTranslations === '*')
+        ) {
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->eq(
+                    $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
+                    0
+                )
+            );
+        }
+
+        $hookName = DatabaseRecordList::class;
+        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'])) {
+            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'] as $className) {
+                $hookObject = GeneralUtility::makeInstance($className);
+                if (method_exists($hookObject, 'buildQueryParametersPostProcess')) {
+                    $hookObject->buildQueryParametersPostProcess(
+                        $parameters,
+                        $table,
+                        $pageId,
+                        $additionalConstraints,
+                        $fieldList,
+                        $this,
+                        $queryBuilder
+                    );
+                }
+            }
+        }
+
+        // array_unique / array_filter used to eliminate empty and duplicate constraints
+        // the array keys are eliminated by this as well to facilitate argument unpacking
+        // when used with the querybuilder.
+        // @deprecated since TYPO3 v9, will be removed in TYPO3 v10
+        if (!empty($parameters['where'])) {
+            $parameters['where'] = array_unique(array_filter(array_values($parameters['where'])));
+        }
+        if (!empty($parameters['where'])) {
+            $this->logDeprecation('where');
+            $queryBuilder->where(...$parameters['where']);
+        }
+        if (!empty($parameters['orderBy'])) {
+            $this->logDeprecation('orderBy');
+            foreach ($parameters['orderBy'] as $fieldNameAndSorting) {
+                list($fieldName, $sorting) = $fieldNameAndSorting;
+                $queryBuilder->addOrderBy($fieldName, $sorting);
+            }
+        }
+        if (!empty($parameters['firstResult'])) {
+            $this->logDeprecation('firstResult');
+            $queryBuilder->setFirstResult((int)$parameters['firstResult']);
+        }
+        if (!empty($parameters['maxResults']) && $parameters['maxResults'] !== $this->iLimit) {
+            $this->logDeprecation('maxResults');
+            $queryBuilder->setMaxResults((int)$parameters['maxResults']);
+        }
+        if (!empty($parameters['groupBy'])) {
+            $this->logDeprecation('groupBy');
+            $queryBuilder->groupBy($parameters['groupBy']);
+        }
+
+        return $queryBuilder;
+    }
+
+    /**
+     * Executed a query to set $this->totalItems to the number of total
+     * items, eg. for pagination
+     *
+     * @param string $table Table name
+     * @param int $pageId Only used to build the search constraints, $this->pidList is used for restrictions
+     * @param array $constraints Additional constraints for where clause
+     */
+    public function setTotalItems(string $table, int $pageId, array $constraints)
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($table);
+
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+        $queryBuilder
+            ->from($table);
+
+        if (!empty($constraints)) {
+            $queryBuilder->andWhere(...$constraints);
+        }
+
+        $queryBuilder = $this->prepareQueryBuilder($table, $pageId, ['*'], $constraints, $queryBuilder, false);
+        // Reset limit and offset for full count query
+        $queryBuilder->setFirstResult(0);
+        $queryBuilder->setMaxResults(1);
+
+        $this->totalItems = (int)$queryBuilder->count('*')
+            ->execute()
+            ->fetchColumn();
+    }
+
+    /**
+     * Creates part of query for searching after a word ($this->searchString)
+     * fields in input table.
+     *
+     * @param string $table Table, in which the fields are being searched.
+     * @param int $currentPid Page id for the possible search limit. -1 only if called from an old XCLASS.
+     * @return string Returns part of WHERE-clause for searching, if applicable.
+     */
+    public function makeSearchString($table, $currentPid = -1)
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+        $expressionBuilder = $queryBuilder->expr();
+        $constraints = [];
+        $currentPid = (int)$currentPid;
+        $tablePidField = $table === 'pages' ? 'uid' : 'pid';
+        // Make query, only if table is valid and a search string is actually defined:
+        if (empty($this->searchString)) {
+            return '';
+        }
+
+        $searchableFields = $this->getSearchFields($table);
+        if (empty($searchableFields)) {
+            return '';
+        }
+        if (MathUtility::canBeInterpretedAsInteger($this->searchString)) {
+            $constraints[] = $expressionBuilder->eq('uid', (int)$this->searchString);
+            foreach ($searchableFields as $fieldName) {
+                if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
+                    continue;
+                }
+                $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
+                $fieldType = $fieldConfig['type'];
+                $evalRules = $fieldConfig['eval'] ?: '';
+                if ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int')) {
+                    if (is_array($fieldConfig['search'])
+                        && in_array('pidonly', $fieldConfig['search'], true)
+                        && $currentPid > 0
+                    ) {
+                        $constraints[] = $expressionBuilder->andX(
+                            $expressionBuilder->eq($fieldName, (int)$this->searchString),
+                            $expressionBuilder->eq($tablePidField, (int)$currentPid)
+                        );
+                    }
+                } elseif ($fieldType === 'text'
+                    || $fieldType === 'flex'
+                    || ($fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules)))
+                ) {
+                    $constraints[] = $expressionBuilder->like(
+                        $fieldName,
+                        $queryBuilder->quote('%' . (int)$this->searchString . '%')
+                    );
+                }
+            }
+        } else {
+            $like = $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($this->searchString) . '%');
+            foreach ($searchableFields as $fieldName) {
+                if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
+                    continue;
+                }
+                $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
+                $fieldType = $fieldConfig['type'];
+                $evalRules = $fieldConfig['eval'] ?: '';
+                $searchConstraint = $expressionBuilder->andX(
+                    $expressionBuilder->comparison(
+                        'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
+                        'LIKE',
+                        'LOWER(' . $like . ')'
+                    )
+                );
+                if (is_array($fieldConfig['search'])) {
+                    $searchConfig = $fieldConfig['search'];
+                    if (in_array('case', $searchConfig)) {
+                        // Replace case insensitive default constraint
+                        $searchConstraint = $expressionBuilder->andX($expressionBuilder->like($fieldName, $like));
+                    }
+                    if (in_array('pidonly', $searchConfig) && $currentPid > 0) {
+                        $searchConstraint->add($expressionBuilder->eq($tablePidField, (int)$currentPid));
+                    }
+                    if ($searchConfig['andWhere']) {
+                        $searchConstraint->add(
+                            QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
+                        );
+                    }
+                }
+                if ($fieldType === 'text'
+                    || $fieldType === 'flex'
+                    || $fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules))
+                ) {
+                    if ($searchConstraint->count() !== 0) {
+                        $constraints[] = $searchConstraint;
                     }
                 }
-            }
-            // If any languages are left, make selector:
-            if (count($langSelItems) > 1) {
-                $url = BackendUtility::getModuleUrl('record_edit', [
-                    'edit[pages_language_overlay][' . $id . ']' => 'new',
-                    'overrideVals[pages_language_overlay][doktype]' => (int)$this->pageRecord['doktype'],
-                    'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
-                ]);
-                $onChangeContent = 'window.location.href=' . GeneralUtility::quoteJSvalue($url . '&overrideVals[pages_language_overlay][sys_language_uid]=') . '+this.options[this.selectedIndex].value';
-                return '<div class="form-inline form-inline-spaced">'
-                . '<div class="form-group">'
-                . '<label for="createNewLanguage">'
-                . htmlspecialchars($this->getLanguageService()->getLL('new_language'))
-                . '</label>'
-                . '<select class="form-control input-sm" name="createNewLanguage" onchange="' . htmlspecialchars($onChangeContent) . '">'
-                . implode('', $langSelItems)
-                . '</select></div></div>';
             }
         }
-        return '';
+        // If no search field conditions have been build ensure no results are returned
+        if (empty($constraints)) {
+            return '0=1';
+        }
+
+        return $expressionBuilder->orX(...$constraints);
     }
 
     /**
-     * Traverse the result pointer given, adding each record to array and setting some internal values at the same time.
+     * Fetches a list of fields to use in the Backend search for the given table.
      *
-     * @param Statement $result DBAL Statement
-     * @param string $table Table name defaulting to tt_content
-     * @return array The selected rows returned in this array.
+     * @param string $tableName
+     * @return string[]
      */
-    public function getResult(Statement $result, string $table = 'tt_content'): array
+    protected function getSearchFields($tableName)
     {
-        $output = [];
-        // Traverse the result:
-        while ($row = $result->fetch()) {
-            BackendUtility::workspaceOL($table, $row, -99, true);
-            if ($row) {
-                // Add the row to the array:
-                $output[] = $row;
+        $fieldArray = [];
+        $fieldListWasSet = false;
+        // Get fields from ctrl section of TCA first
+        if (isset($GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
+            $fieldArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
+            $fieldListWasSet = true;
+        }
+        // Call hook to add or change the list
+        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'])) {
+            $hookParameters = [
+                'tableHasSearchConfiguration' => $fieldListWasSet,
+                'tableName' => $tableName,
+                'searchFields' => &$fieldArray,
+                'searchString' => $this->searchString
+            ];
+            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'] as $hookFunction) {
+                GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
             }
         }
-        $this->generateTtContentDataArray($output);
-        // Return selected records
-        return $output;
+        return $fieldArray;
     }
 
-    /********************************
-     *
-     * Various helper functions
+    /**
+     * Returns the title (based on $code) of a table ($table) with the proper link around. For headers over tables.
+     * The link will cause the display of all extended mode or not for the table.
      *
-     ********************************/
+     * @param string $table Table name
+     * @param string $code Table label
+     * @return string The linked table label
+     */
+    public function linkWrapTable($table, $code)
+    {
+        if ($this->table !== $table) {
+            return '<a href="' . htmlspecialchars(
+                    $this->listURL('', $table, 'firstElementNumber')
+                ) . '">' . $code . '</a>';
+        }
+        return '<a href="' . htmlspecialchars(
+                $this->listURL('', '', 'sortField,sortRev,table,firstElementNumber')
+            ) . '">' . $code . '</a>';
+    }
 
     /**
-     * Initializes the clipboard for generating paste links
+     * Returns the title (based on $code) of a record (from table $table) with the proper link around (that is for 'pages'-records a link to the level of that record...)
      *
+     * @param string $table Table name
+     * @param int $uid Item uid
+     * @param string $code Item title (not htmlspecialchars()'ed yet)
+     * @param mixed[] $row Item row
+     * @return string The item title. Ready for HTML output (is htmlspecialchars()'ed)
+     */
+    public function linkWrapItems($table, $uid, $code, $row)
+    {
+        $lang = $this->getLanguageService();
+        $origCode = $code;
+        // If the title is blank, make a "no title" label:
+        if ((string)$code === '') {
+            $code = '<i>[' . htmlspecialchars(
+                    $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.no_title')
+                ) . ']</i> - '
+                . htmlspecialchars(BackendUtility::getRecordTitle($table, $row));
+        } else {
+            $code = htmlspecialchars($code, ENT_QUOTES, 'UTF-8', false);
+            if ($code != htmlspecialchars($origCode)) {
+                $code = '<span title="' . htmlspecialchars(
+                        $origCode,
+                        ENT_QUOTES,
+                        'UTF-8',
+                        false
+                    ) . '">' . $code . '</span>';
+            }
+        }
+        switch ((string)$this->clickTitleMode) {
+            case 'edit':
+                // If the listed table is 'pages' we have to request the permission settings for each page:
+                if ($table === 'pages') {
+                    $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(
+                        BackendUtility::getRecord('pages', $row['uid'])
+                    );
+                    $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
+                } else {
+                    $permsEdit = $this->calcPerms & Permission::CONTENT_EDIT;
+                }
+                // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
+                if ($permsEdit) {
+                    $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
+                    $code = '<a href="#" onclick="' . htmlspecialchars(
+                            BackendUtility::editOnClick($params, '', -1)
+                        ) . '" title="' . htmlspecialchars($lang->getLL('edit')) . '">' . $code . '</a>';
+                }
+                break;
+            case 'show':
+                // "Show" link (only pages and tt_content elements)
+                if ($table === 'pages' || $table === 'tt_content') {
+                    $code = '<a href="#" onclick="' . htmlspecialchars(
+                            BackendUtility::viewOnClick(
+                                ($table === 'tt_content' ? $this->id . '#' . $row['uid'] : $row['uid'])
+                            )
+                        ) . '" title="' . htmlspecialchars(
+                            $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage')
+                        ) . '">' . $code . '</a>';
+                }
+                break;
+            case 'info':
+                // "Info": (All records)
+                $code = '<a href="#" onclick="' . htmlspecialchars(
+                        ('top.launchView(\'' . $table . '\', \'' . $row['uid'] . '\'); return false;')
+                    ) . '" title="' . htmlspecialchars($lang->getLL('showInfo')) . '">' . $code . '</a>';
+                break;
+            default:
+                // Output the label now:
+                if ($table === 'pages') {
+                    $code = '<a href="' . htmlspecialchars(
+                            $this->listURL($uid, '', 'firstElementNumber')
+                        ) . '" onclick="setHighlight(' . $uid . ')">' . $code . '</a>';
+                } else {
+                    $code = $this->linkUrlMail($code, $origCode);
+                }
+        }
+        return $code;
+    }
+
+    /**
+     * Wrapping input code in link to URL or email if $testString is either.
      *
-     * @see \TYPO3\CMS\Recordlist\RecordList::main()
-     * @see \TYPO3\CMS\Backend\Controller\ContextMenuController::clipboardAction()
-     * @see \TYPO3\CMS\Filelist\Controller\FileListController::indexAction()
+     * @param string $code code to wrap
+     * @param string $testString String which is tested for being a URL or email and which will be used for the link if so.
+     * @return string Link-Wrapped $code value, if $testString was URL or email.
      */
-    protected function initializeClipboard()
+    public function linkUrlMail($code, $testString)
     {
-        // Start clipboard
-        $this->clipboard = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Clipboard\Clipboard::class);
+        // Check for URL:
+        $scheme = parse_url($testString, PHP_URL_SCHEME);
+        if ($scheme === 'http' || $scheme === 'https' || $scheme === 'ftp') {
+            return '<a href="' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
+        }
+        // Check for email:
+        if (GeneralUtility::validEmail($testString)) {
+            return '<a href="mailto:' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
+        }
+        // Return if nothing else...
+        return $code;
+    }
 
-        // Initialize - reads the clipboard content from the user session
-        $this->clipboard->initializeClipboard();
+    /**
+     * Creates the URL to this script, including all relevant GPvars
+     * Fixed GPvars are id, table, imagemode, returnUrl, search_field, search_levels and showLimit
+     * The GPvars "sortField" and "sortRev" are also included UNLESS they are found in the $exclList variable.
+     *
+     * @param string $altId Alternative id value. Enter blank string for the current id ($this->id)
+     * @param string $table Table name to display. Enter "-1" for the current table.
+     * @param string $exclList Comma separated list of fields NOT to include ("sortField", "sortRev" or "firstElementNumber")
+     * @return string URL
+     */
+    public function listURL($altId = '', $table = '-1', $exclList = '')
+    {
+        $urlParameters = [];
+        if ((string)$altId !== '') {
+            $urlParameters['id'] = $altId;
+        } else {
+            $urlParameters['id'] = $this->id;
+        }
+        if ($table === '-1') {
+            $urlParameters['table'] = $this->table;
+        } else {
+            $urlParameters['table'] = $table;
+        }
+        if ($this->thumbs) {
+            $urlParameters['imagemode'] = $this->thumbs;
+        }
+        if ($this->returnUrl) {
+            $urlParameters['returnUrl'] = $this->returnUrl;
+        }
+        if ((!$exclList || !GeneralUtility::inList($exclList, 'search_field')) && $this->searchString) {
+            $urlParameters['search_field'] = $this->searchString;
+        }
+        if ($this->searchLevels) {
+            $urlParameters['search_levels'] = $this->searchLevels;
+        }
+        if ($this->showLimit) {
+            $urlParameters['showLimit'] = $this->showLimit;
+        }
+        if ((!$exclList || !GeneralUtility::inList($exclList, 'firstElementNumber')) && $this->firstElementNumber) {
+            $urlParameters['pointer'] = $this->firstElementNumber;
+        }
+        if ((!$exclList || !GeneralUtility::inList($exclList, 'sortField')) && $this->sortField) {
+            $urlParameters['sortField'] = $this->sortField;
+        }
+        if ((!$exclList || !GeneralUtility::inList($exclList, 'sortRev')) && $this->sortRev) {
+            $urlParameters['sortRev'] = $this->sortRev;
+        }
 
-        // This locks the clipboard to the Normal for this request.
-        $this->clipboard->lockToNormal();
+        $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters);
 
-        // Clean up pad
-        $this->clipboard->cleanCurrent();
+        if ($routePath = GeneralUtility::_GP('route')) {
+            $router = GeneralUtility::makeInstance(Router::class);
+            $route = $router->match($routePath);
+            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+            $url = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'), $urlParameters);
+        } elseif ($moduleName = GeneralUtility::_GP('M')) {
+            $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
+        } else {
+            $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim(
+                    GeneralUtility::implodeArrayForUrl('', $urlParameters),
+                    '&'
+                );
+        }
+        return $url;
+    }
 
-        // Save the clipboard content
-        $this->clipboard->endClipboard();
+    /**
+     * Returns "requestUri" - which is basically listURL
+     * @return string Content of ->listURL()
+     */
+    public function requestUri()
+    {
+        return $this->listURL();
     }
 
     /**
-     * Generates the data for previous and next elements which is needed for movements.
+     * Makes the list of fields to select for a table
      *
-     * @param array $rowArray
+     * @param string $table Table name
+     * @param bool $dontCheckUser If set, users access to the field (non-exclude-fields) is NOT checked.
+     * @param bool $addDateFields If set, also adds crdate and tstamp fields (note: they will also be added if user is admin or dontCheckUser is set)
+     * @return string[] Array, where values are fieldnames to include in query
      */
-    protected function generateTtContentDataArray(array $rowArray)
+    public function makeFieldList($table, $dontCheckUser = false, $addDateFields = false)
     {
-        if (empty($this->tt_contentData)) {
-            $this->tt_contentData = [
-                'nextThree' => [],
-                'next' => [],
-                'prev' => [],
-            ];
-        }
-        foreach ($rowArray as $key => $value) {
-            // Create the list of the next three ids (for editing links...)
-            for ($i = 0; $i < $this->nextThree; $i++) {
-                if (isset($rowArray[$key - $i])
-                    && !GeneralUtility::inList($this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']], $value['uid'])
-                ) {
-                    $this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']] .= $value['uid'] . ',';
+        $backendUser = $this->getBackendUserAuthentication();
+        // Init fieldlist array:
+        $fieldListArr = [];
+        // Check table:
+        if (is_array($GLOBALS['TCA'][$table]) && isset($GLOBALS['TCA'][$table]['columns']) && is_array(
+                $GLOBALS['TCA'][$table]['columns']
+            )) {
+            if (isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
+                // Traverse configured columns and add them to field array, if available for user.
+                foreach ($GLOBALS['TCA'][$table]['columns'] as $fN => $fieldValue) {
+                    if ($dontCheckUser || (!$fieldValue['exclude'] || $backendUser->check(
+                                'non_exclude_fields',
+                                $table . ':' . $fN
+                            )) && $fieldValue['config']['type'] !== 'passthrough') {
+                        $fieldListArr[] = $fN;
+                    }
                 }
-            }
 
-            // Create information for next and previous content elements
-            if (isset($rowArray[$key - 1])) {
-                if (isset($rowArray[$key - 2])) {
-                    $this->tt_contentData['prev'][$value['uid']] = -$rowArray[$key - 2]['uid'];
-                } else {
-                    $this->tt_contentData['prev'][$value['uid']] = $value['pid'];
+                $fieldListArr[] = 'uid';
+                $fieldListArr[] = 'pid';
+
+                // Add date fields
+                if ($dontCheckUser || $backendUser->isAdmin() || $addDateFields) {
+                    if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
+                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['tstamp'];
+                    }
+                    if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
+                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['crdate'];
+                    }
                 }
-                $this->tt_contentData['next'][$rowArray[$key - 1]['uid']] = -$value['uid'];
+                // Add more special fields:
+                if ($dontCheckUser || $backendUser->isAdmin()) {
+                    if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
+                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['cruser_id'];
+                    }
+                    if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
+                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
+                    }
+                    if (ExtensionManagementUtility::isLoaded(
+                            'version'
+                        ) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+                        $fieldListArr[] = 't3ver_id';
+                        $fieldListArr[] = 't3ver_state';
+                        $fieldListArr[] = 't3ver_wsid';
+                    }
+                }
+            } else {
+                GeneralUtility::sysLog(
+                    sprintf('$TCA is broken for the table "%s": no required "columns" entry in $TCA.', $table),
+                    'core',
+                    GeneralUtility::SYSLOG_SEVERITY_ERROR
+                );
             }
         }
+        return $fieldListArr;
     }
 
     /**
-     * Counts and returns the number of records on the page with $pid
+     * Redirects to FormEngine if a record is just localized.
      *
-     * @param string $table Table name
-     * @param int $pid Page id
-     * @return int Number of records.
+     * @param string $justLocalized String with table, orig uid and language separated by ":
      */
-    public function numberOfRecords($table, $pid)
+    public function localizationRedirect($justLocalized)
     {
-        $count = 0;
-        if ($GLOBALS['TCA'][$table]) {
-            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-                ->getQueryBuilderForTable($table);
+        list($table, $orig_uid, $language) = explode(':', $justLocalized);
+        if ($GLOBALS['TCA'][$table]
+            && $GLOBALS['TCA'][$table]['ctrl']['languageField']
+            && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
+        ) {
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
             $queryBuilder->getRestrictions()
                 ->removeAll()
                 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
                 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
-            $count = (int)$queryBuilder->count('uid')
+
+            $localizedRecordUid = $queryBuilder->select('uid')
                 ->from($table)
                 ->where(
-                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT))
+                    $queryBuilder->expr()->eq(
+                        $GLOBALS['TCA'][$table]['ctrl']['languageField'],
+                        $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
+                    ),
+                    $queryBuilder->expr()->eq(
+                        $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
+                        $queryBuilder->createNamedParameter($orig_uid, \PDO::PARAM_INT)
+                    )
                 )
+                ->setMaxResults(1)
                 ->execute()
                 ->fetchColumn();
+
+            if ($localizedRecordUid !== false) {
+                // Create parameters and finally run the classic page module for creating a new page translation
+                $url = $this->listURL();
+                $editUserAccountUrl = BackendUtility::getModuleUrl(
+                    'record_edit',
+                    [
+                        'edit[' . $table . '][' . $localizedRecordUid . ']' => 'edit',
+                        'returnUrl' => $url
+                    ]
+                );
+                HttpUtility::redirect($editUserAccountUrl);
+            }
+        }
+    }
+
+    /**
+     * Set URL parameters to override or add in the listUrl() method.
+     *
+     * @param string[] $urlParameters
+     */
+    public function setOverrideUrlParameters(array $urlParameters)
+    {
+        $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.',
+                        1504870805
+                    );
+                }
+            }
+            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.',
+                        1504870806
+                    );
+                }
+            }
+        }
+        $this->tableDisplayOrder = $orderInformation;
+    }
+
+    /**
+     * @return array
+     */
+    public function getOverridePageIdList(): array
+    {
+        return $this->overridePageIdList;
+    }
+
+    /**
+     * @param int[]|array $overridePageIdList
+     */
+    public function setOverridePageIdList(array $overridePageIdList)
+    {
+        $this->overridePageIdList = array_map('intval', $overridePageIdList);
+    }
+
+    /**
+     * Get all allowed mount pages to be searched in.
+     *
+     * @param int $id Page id
+     * @param int $depth Depth to go down
+     * @param string $perms_clause select clause
+     * @return int[]
+     */
+    protected function getSearchableWebmounts($id, $depth, $perms_clause)
+    {
+        $backendUser = $this->getBackendUserAuthentication();
+        /** @var PageTreeView $tree */
+        $tree = GeneralUtility::makeInstance(PageTreeView::class);
+        $tree->init('AND ' . $perms_clause);
+        $tree->makeHTML = 0;
+        $tree->fieldArray = ['uid', 'php_tree_stop'];
+        $idList = [];
+
+        $allowedMounts = !$backendUser->isAdmin() && $id === 0
+            ? $backendUser->returnWebmounts()
+            : [$id];
+
+        foreach ($allowedMounts as $allowedMount) {
+            $idList[] = $allowedMount;
+            if ($depth) {
+                $tree->getTree($allowedMount, $depth, '');
+            }
+            $idList = array_merge($idList, $tree->ids);
+        }
+
+        return $idList;
+    }
+
+    /**
+     * Add conditions to the QueryBuilder object ($queryBuilder) to limit a
+     * query to a list of page IDs based on the current search level setting.
+     *
+     * @param string $tableName
+     * @param QueryBuilder $queryBuilder
+     * @return QueryBuilder Modified QueryBuilder object
+     */
+    protected function addPageIdConstraint(string $tableName, QueryBuilder $queryBuilder): QueryBuilder
+    {
+        // Set search levels:
+        $searchLevels = $this->searchLevels;
+
+        // Set search levels to 999 instead of -1 as the following methods
+        // do not support -1 as valid value for infinite search.
+        if ($searchLevels === -1) {
+            $searchLevels = 999;
+        }
+
+        if ($searchLevels === 0) {
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->eq(
+                    $tableName . '.pid',
+                    $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
+                )
+            );
+        } elseif ($searchLevels > 0) {
+            $allowedMounts = $this->getSearchableWebmounts($this->id, $searchLevels, $this->perms_clause);
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->in(
+                    $tableName . '.pid',
+                    $queryBuilder->createNamedParameter($allowedMounts, Connection::PARAM_INT_ARRAY)
+                )
+            );
         }
 
-        return $count;
+        if (!empty($this->getOverridePageIdList())) {
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->in(
+                    $tableName . '.pid',
+                    $queryBuilder->createNamedParameter($this->getOverridePageIdList(), Connection::PARAM_INT_ARRAY)
+                )
+            );
+        }
+
+        return $queryBuilder;
+    }
+
+    /**
+     * Method used to log deprecated usage of old buildQueryParametersPostProcess hook arguments
+     *
+     * @param string $index
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10 - see method usages
+     */
+    protected function logDeprecation(string $index)
+    {
+        GeneralUtility::deprecationLog(
+            '[index: ' . $index . '] $parameters in "buildQueryParameters"-Hook has been deprecated in v9 and will be remove in v10, use $queryBuilder instead'
+        );
+    }
+
+    /**
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUserAuthentication()
+    {
+        return $GLOBALS['BE_USER'];
     }
 
     /**
-     * Processing of larger amounts of text (usually from RTE/bodytext fields) with word wrapping etc.
-     *
-     * @param string $input Input string
-     * @return string Output string
+     * Sets the script url depending on being a module or script request
      */
-    public function renderText($input)
+    protected function determineScriptUrl()
     {
-        $input = strip_tags($input);
-        $input = GeneralUtility::fixed_lgd_cs($input, 1500);
-        return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
+        if ($routePath = GeneralUtility::_GP('route')) {
+            $router = GeneralUtility::makeInstance(Router::class);
+            $route = $router->match($routePath);
+            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+            $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
+        } elseif ($moduleName = GeneralUtility::_GP('M')) {
+            $this->thisScript = BackendUtility::getModuleUrl($moduleName);
+        } else {
+            $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
+        }
     }
 
     /**
-     * Creates the icon image tag for record from table and wraps it in a link which will trigger the click menu.
+     * Returns a table-row with the content from the fields in the input data array.
+     * OBS: $this->fieldArray MUST be set! (represents the list of fields to display)
      *
-     * @param string $table Table name
-     * @param array $row Record array
-     * @param string $enabledClickMenuItems Passthrough to wrapClickMenuOnIcon
-     * @return string HTML for the icon
+     * @param int $h Is an integer >=0 and denotes how tall an element is. Set to '0' makes a halv line, -1 = full line, set to 1 makes a 'join' and above makes 'line'
+     * @param string $icon Is the <img>+<a> of the record. If not supplied the first 'join'-icon will be a 'line' instead
+     * @param array $data Is the dataarray, record with the fields. Notice: These fields are (currently) NOT htmlspecialchar'ed before being wrapped in <td>-tags
+     * @param string $rowParams Is insert in the <tr>-tags. Must carry a ' ' as first character
+     * @param string $_ OBSOLETE - NOT USED ANYMORE. $lMargin is the leftMargin (int)
+     * @param string $_2 OBSOLETE - NOT USED ANYMORE. Is the HTML <img>-tag for an alternative 'gfx/ol/line.gif'-icon (used in the top)
+     * @param string $colType Defines the tag being used for the columns. Default is td.
+     *
+     * @return string HTML content for the table row
      */
-    public function getIcon($table, $row, $enabledClickMenuItems = '')
+    public function addElement($h, $icon, $data, $rowParams = '', $_ = '', $_2 = '', $colType = 'td')
     {
-        // Initialization
-        $toolTip = BackendUtility::getRecordToolTip($row, 'tt_content');
-        $icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
-        $this->counter++;
-        // The icon with link
-        if ($this->getBackendUser()->recordEditAccessInternals($table, $row)) {
-            $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
+        $colType = ($colType === 'th') ? 'th' : 'td';
+        $noWrap = $this->no_noWrap ? '' : ' nowrap';
+        // Start up:
+        $l10nParent = isset($data['_l10nparent_']) ? (int)$data['_l10nparent_'] : 0;
+        $out = '
+               <!-- Element, begin: -->
+               <tr ' . $rowParams . ' data-uid="' . (int)$data['uid'] . '" data-l10nparent="' . $l10nParent . '">';
+        // Show icon and lines
+        if ($this->showIcon) {
+            $out .= '
+                       <' . $colType . ' class="col-icon nowrap">';
+            if (!$h) {
+                $out .= '&nbsp;';
+            } else {
+                for ($a = 0; $a < $h; $a++) {
+                    if (!$a) {
+                        if ($icon) {
+                            $out .= $icon;
+                        }
+                    }
+                }
+            }
+            $out .= '</' . $colType . '>
+                       ';
         }
-        return $icon;
+        // Init rendering.
+        $colsp = '';
+        $lastKey = '';
+        $c = 0;
+        $ccount = 0;
+        // __label is used as the label key to circumvent problems with uid used as label (see #67756)
+        // as it was introduced later on, check if it really exists before using it
+        $fields = $this->fieldArray;
+        if ($colType === 'td' && array_key_exists('__label', $data)) {
+            $fields[0] = '__label';
+        }
+        // Traverse field array which contains the data to present:
+        foreach ($fields as $vKey) {
+            if (isset($data[$vKey])) {
+                if ($lastKey) {
+                    $cssClass = $this->addElement_tdCssClass[$lastKey];
+                    if ($this->oddColumnsCssClass && $ccount % 2 == 0) {
+                        $cssClass = implode(' ', [$this->addElement_tdCssClass[$lastKey], $this->oddColumnsCssClass]);
+                    }
+                    $out .= '
+                                               <' . $colType . ' class="' . $cssClass . $noWrap . '"' . $colsp . $this->addElement_tdParams[$lastKey] . '>' . $data[$lastKey] . '</' . $colType . '>';
+                }
+                $lastKey = $vKey;
+                $c = 1;
+                $ccount++;
+            } else {
+                if (!$lastKey) {
+                    $lastKey = $vKey;
+                }
+                $c++;
+            }
+            if ($c > 1) {
+                $colsp = ' colspan="' . $c . '"';
+            } else {
+                $colsp = '';
+            }
+        }
+        if ($lastKey) {
+            $cssClass = $this->addElement_tdCssClass[$lastKey];
+            if ($this->oddColumnsCssClass) {
+                $cssClass = implode(' ', [$this->addElement_tdCssClass[$lastKey], $this->oddColumnsCssClass]);
+            }
+            $out .= '
+                               <' . $colType . ' class="' . $cssClass . $noWrap . '"' . $colsp . $this->addElement_tdParams[$lastKey] . '>' . $data[$lastKey] . '</' . $colType . '>';
+        }
+        // End row
+        $out .= '
+               </tr>';
+        // Return row.
+        return $out;
     }
 
     /**
-     * Creates processed values for all field names in $fieldList based on values from $row array.
-     * The result is 'returned' through $info which is passed as a reference
+     * Dummy function, used to write the top of a table listing.
+     */
+    public function writeTop()
+    {
+    }
+
+    /**
+     * Creates a forward/reverse button based on the status of ->eCounter, ->firstElementNumber, ->iLimit
      *
      * @param string $table Table name
-     * @param string $fieldList Comma separated list of fields.
-     * @param array $row Record from which to take values for processing.
-     * @param array $info Array to which the processed values are added.
+     * @return array array([boolean], [HTML]) where [boolean] is 1 for reverse element, [HTML] is the table-row code for the element
      */
-    public function getProcessedValue($table, $fieldList, array $row, array &$info)
+    public function fwd_rwd_nav($table = '')
     {
-        // Splitting values from $fieldList
-        $fieldArr = explode(',', $fieldList);
-        // Traverse fields from $fieldList
-        foreach ($fieldArr as $field) {
-            if ($row[$field]) {
-                $info[] = '<strong>' . htmlspecialchars($this->itemLabels[$field]) . '</strong> '
-                    . htmlspecialchars(BackendUtility::getProcessedValue($table, $field, $row[$field]));
+        $code = '';
+        if ($this->eCounter >= $this->firstElementNumber && $this->eCounter < $this->firstElementNumber + $this->iLimit) {
+            if ($this->firstElementNumber && $this->eCounter == $this->firstElementNumber) {
+                //     Reverse
+                $theData = [];
+                $titleCol = $this->fieldArray[0];
+                $theData[$titleCol] = $this->fwd_rwd_HTML('fwd', $this->eCounter, $table);
+                $code = $this->addElement(1, '', $theData, 'class="fwd_rwd_nav"');
             }
+            return [1, $code];
+        }
+        if ($this->eCounter == $this->firstElementNumber + $this->iLimit) {
+            //         Forward
+            $theData = [];
+            $titleCol = $this->fieldArray[0];
+            $theData[$titleCol] = $this->fwd_rwd_HTML('rwd', $this->eCounter, $table);
+            $code = $this->addElement(1, '', $theData, 'class="fwd_rwd_nav"');
         }
+        return [0, $code];
     }
 
     /**
-     * Returns TRUE, if the record given as parameters is NOT visible based on hidden/starttime/endtime (if available)
+     * Creates the button with link to either forward or reverse
      *
-     * @param string $table Tablename of table to test
-     * @param array $row Record row.
-     * @return bool Returns TRUE, if disabled.
+     * @param string $type Type: "fwd" or "rwd
+     * @param int $pointer Pointer
+     * @param string $table Table name
+     * @return string
+     * @access private
      */
-    public function isDisabled($table, $row)
+    public function fwd_rwd_HTML($type, $pointer, $table = '')
     {
-        $enableCols = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
-        return $enableCols['disabled'] && $row[$enableCols['disabled']]
-            || $enableCols['starttime'] && $row[$enableCols['starttime']] > $GLOBALS['EXEC_TIME']
-            || $enableCols['endtime'] && $row[$enableCols['endtime']] && $row[$enableCols['endtime']] < $GLOBALS['EXEC_TIME'];
+        $content = '';
+        $tParam = $table ? '&table=' . rawurlencode($table) : '';
+        switch ($type) {
+            case 'fwd':
+                $href = $this->listURL() . '&pointer=' . ($pointer - $this->iLimit) . $tParam;
+                $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
+                        'actions-move-up',
+                        Icon::SIZE_SMALL
+                    )->render() . '</a> <i>[1 - ' . $pointer . ']</i>';
+                break;
+            case 'rwd':
+                $href = $this->listURL() . '&pointer=' . $pointer . $tParam;
+                $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
+                        'actions-move-down',
+                        Icon::SIZE_SMALL
+                    )->render() . '</a> <i>[' . ($pointer + 1) . ' - ' . $this->totalItems . ']</i>';
+                break;
+        }
+        return $content;
     }
 
     /**
-     * Returns icon for "no-edit" of a record.
-     * Basically, the point is to signal that this record could have had an edit link if
-     * the circumstances were right. A placeholder for the regular edit icon...
-     *
-     * @param string $label Label key from LOCAL_LANG
-     * @return string IMG tag for icon.
+     * @return string
      */
-    public function noEditIcon($label = 'noEditItems')
+    protected function getThisScript()
     {
-        $title = htmlspecialchars($this->getLanguageService()->getLL($label));
-        return '<span title="' . $title . '">' . $this->iconFactory->getIcon('status-status-edit-read-only', Icon::SIZE_SMALL)->render() . '</span>';
+        return strpos($this->thisScript, '?') === false ? $this->thisScript . '?' : $this->thisScript . '&';
     }
 
-    /*****************************************
-     *
-     * External renderings
-     *
-     *****************************************/
-
     /**
-     * Creates a menu of the tables that can be listed by this function
-     * Only tables which has records on the page will be included.
-     * Notice: The function also fills in the internal variable $this->activeTables with icon/titles.
+     * Returning JavaScript for ClipBoard functionality.
      *
-     * @param int $id Page id from which we are listing records (the function will look up if there are records on the page)
-     * @return string HTML output.
+     * @return string
      */
-    public function getTableMenu($id)
+    public function CBfunctions()
     {
-        // Initialize:
-        $this->activeTables = [];
-        $theTables = ['tt_content'];
-        // External tables:
-        if (is_array($this->externalTables)) {
-            $theTables = array_unique(array_merge($theTables, array_keys($this->externalTables)));
-        }
-        $out = '';
-        // Traverse tables to check:
-        foreach ($theTables as $tName) {
-            // Check access and whether the proper extensions are loaded:
-            if ($this->getBackendUser()->check('tables_select', $tName)
-                && (
-                    isset($this->externalTables[$tName])
-                    || $tName === 'fe_users' || $tName === 'tt_content'
-                    || \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded($tName)
-                )
-            ) {
-                // Make query to count records from page:
-                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-                    ->getQueryBuilderForTable($tName);
-                $queryBuilder->getRestrictions()
-                    ->removeAll()
-                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
-                    ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
-                $count = $queryBuilder->count('uid')
-                    ->from($tName)
-                    ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
-                    ->execute()
-                    ->fetchColumn();
-                // If records were found (or if "tt_content" is the table...):
-                if ($count || $tName === 'tt_content') {
-                    // Add row to menu:
-                    $out .= '
-                                       <td><a href="#' . $tName . '" title="' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title'])) . '"></a>'
-                        . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
-                        . '</td>';
-                    // ... and to the internal array, activeTables we also add table icon and title (for use elsewhere)
-                    $title = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title']))
-                        . ': ' . $count . ' ' . htmlspecialchars($this->getLanguageService()->getLL('records'));
-                    $this->activeTables[$tName] = '<span title="' . $title . '">'
-                        . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
-                        . '</span>'
-                        . '&nbsp;' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title']));
-                }
-            }
-        }
-        // Wrap cells in table tags:
-        $out = '
-            <!--
-                Menu of tables on the page (table menu)
-            -->
-            <table border="0" cellpadding="0" cellspacing="0" id="typo3-page-tblMenu">
-                               <tr>' . $out . '
-                </tr>
-                       </table>';
-        // Return the content:
-        return $out;
+        return '
+               // checkOffCB()
+       function checkOffCB(listOfCBnames, link) {      //
+               var checkBoxes, flag, i;
+               var checkBoxes = listOfCBnames.split(",");
+               if (link.rel === "") {
+                       link.rel = "allChecked";
+                       flag = true;
+               } else {
+                       link.rel = "";
+                       flag = false;
+               }
+               for (i = 0; i < checkBoxes.length; i++) {
+                       setcbValue(checkBoxes[i], flag);
+               }
+       }
+               // cbValue()
+       function cbValue(CBname) {      //
+               var CBfullName = "CBC["+CBname+"]";
+               return (document.dblistForm[CBfullName] && document.dblistForm[CBfullName].checked ? 1 : 0);
+       }
+               // setcbValue()
+       function setcbValue(CBname,flag) {      //
+               CBfullName = "CBC["+CBname+"]";
+               if(document.dblistForm[CBfullName]) {
+                       document.dblistForm[CBfullName].checked = flag ? "on" : 0;
+               }
+       }
+
+               ';
     }
 
     /**
-     * Create thumbnail code for record/field but not linked
-     *
-     * @param mixed[] $row Record array
-     * @param string $table Table (record is from)
-     * @param string $field Field name for which thumbnail are to be rendered.
-     * @return string HTML for thumbnails, if any.
+     * Initializes page languages and icons
      */
-    public function getThumbCodeUnlinked($row, $table, $field)
+    public function initializeLanguages()
     {
-        return BackendUtility::thumbCode($row, $table, $field, '', '', null, 0, '', '', false);
+        // Look up page overlays:
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('pages_language_overlay');
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+        $result = $queryBuilder
+            ->select('*')
+            ->from('pages_language_overlay')
+            ->where(
+                $queryBuilder->expr()->andX(
+                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)),
+                    $queryBuilder->expr()->gt(
+                        'sys_language_uid',
+                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    )
+                )
+            )
+            ->execute();
+
+        $this->pageOverlays = [];
+        while ($row = $result->fetch()) {
+            $this->pageOverlays[$row['sys_language_uid']] = $row;
+        }
+
+        $this->languageIconTitles = $this->getTranslateTools()->getSystemLanguages($this->id);
     }
 
     /**
-     * Checks whether translated Content Elements exist in the desired language
-     * If so, deny creating new ones via the UI
+     * Return the icon for the language
      *
-     * @param array $contentElements
-     * @param int $language
-     * @return bool
+     * @param int $sys_language_uid Sys language uid
+     * @param bool $addAsAdditionalText If set to true, only the flag is returned
+     * @return string Language icon
      */
-    protected function checkIfTranslationsExistInLanguage(array $contentElements, $language)
+    public function languageFlag($sys_language_uid, $addAsAdditionalText = true)
     {
-        // If in default language, you may always create new entries
-        // Also, you may override this strict behavior via user TS Config
-        // If you do so, you're on your own and cannot rely on any support by the TYPO3 core
-        // We jump out here since we don't need to do the expensive loop operations
-        $allowInconsistentLanguageHandling = BackendUtility::getModTSconfig($this->id, 'mod.web_layout.allowInconsistentLanguageHandling');
-        if ($language === 0 || $allowInconsistentLanguageHandling['value'] === '1') {
-            return false;
-        }
-        /**
-         * Build up caches
-         */
-        if (!isset($this->languageHasTranslationsCache[$language])) {
-            foreach ($contentElements as $columns) {
-                foreach ($columns as $contentElement) {
-                    if ((int)$contentElement['l18n_parent'] === 0) {
-                        $this->languageHasTranslationsCache[$language]['hasStandAloneContent'] = true;
-                    }
-                    if ((int)$contentElement['l18n_parent'] > 0) {
-                        $this->languageHasTranslationsCache[$language]['hasTranslations'] = true;
-                    }
-                }
-            }
-            // Check whether we have a mix of both
-            if ($this->languageHasTranslationsCache[$language]['hasStandAloneContent']
-                && $this->languageHasTranslationsCache[$language]['hasTranslations']
-            ) {
-                $message = GeneralUtility::makeInstance(
-                    FlashMessage::class,
-                    sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $this->languageIconTitles[$language]['title']),
-                    sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $this->languageIconTitles[$language]['title']),
-                    FlashMessage::WARNING
-                );
-                $service = GeneralUtility::makeInstance(FlashMessageService::class);
-                $queue = $service->getMessageQueueByIdentifier();
-                $queue->addMessage($message);
+        $out = '';
+        $title = htmlspecialchars($this->languageIconTitles[$sys_language_uid]['title']);
+        if ($this->languageIconTitles[$sys_language_uid]['flagIcon']) {
+            $out .= '<span title="' . $title . '">' . $this->iconFactory->getIcon(
+                    $this->languageIconTitles[$sys_language_uid]['flagIcon'],
+                    Icon::SIZE_SMALL
+                )->render() . '</span>';
+            if (!$addAsAdditionalText) {
+                return $out;
             }
+            $out .= '&nbsp;';
         }
-        if ($this->languageHasTranslationsCache[$language]['hasTranslations']) {
-            return true;
-        }
-        return false;
+        $out .= $title;
+        return $out;
     }
 
     /**
-     * @return BackendLayoutView
+     * Gets an instance of TranslationConfigurationProvider
+     *
+     * @return TranslationConfigurationProvider
      */
-    protected function getBackendLayoutView()
+    protected function getTranslateTools()
     {
-        return GeneralUtility::makeInstance(BackendLayoutView::class);
+        if (!isset($this->translateTools)) {
+            $this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
+        }
+        return $this->translateTools;
     }
 
     /**
-     * @return BackendUserAuthentication
+     * Generates HTML code for a Reference tooltip out of
+     * sys_refindex records you hand over
+     *
+     * @param int $references number of records from sys_refindex table
+     * @param string $launchViewParameter JavaScript String, which will be passed as parameters to top.launchView
+     * @return string
      */
-    protected function getBackendUser()
+    protected function generateReferenceToolTip($references, $launchViewParameter = '')
     {
-        return $GLOBALS['BE_USER'];
+        if (!$references) {
+            $htmlCode = '-';
+        } else {
+            $htmlCode = '<a href="#"';
+            if ($launchViewParameter !== '') {
+                $htmlCode .= ' onclick="' . htmlspecialchars(
+                        ('top.launchView(' . $launchViewParameter . '); return false;')
+                    ) . '"';
+            }
+            $htmlCode .= ' title="' . htmlspecialchars(
+                    $this->getLanguageService()->sL(
+                        'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:show_references'
+                    ) . ' (' . $references . ')'
+                ) . '">';
+            $htmlCode .= $references;
+            $htmlCode .= '</a>';
+        }
+        return $htmlCode;
     }
 
     /**
-     * @return PageLayoutController
+     * Returns the language service
+     * @return LanguageService
      */
-    protected function getPageLayoutController()
+    protected function getLanguageService()
     {
-        return $GLOBALS['SOBE'];
+        return $GLOBALS['LANG'];
     }
 }
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-82334-AbstractRecordList.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-82334-AbstractRecordList.rst
new file mode 100644 (file)
index 0000000..be68d70
--- /dev/null
@@ -0,0 +1,41 @@
+.. include:: ../../Includes.txt
+
+=====================================
+Breaking: #82334 - AbstractRecordList
+=====================================
+
+See :issue:`82334`
+
+Description
+===========
+
+The PHP classes :php:`AbstractRecordList` and :php:`AbstractDatabaseRecordList` have been marked as deprecated.
+
+Some classes changed inheritances, which can be breaking for instance in hooks if they type hint or otherwise
+check instance types of these classes:
+
+* :php:`PageLayoutView` no longer extends :php:`AbstractDatabaseRecordList`
+* :php:`FileList` no longer extends :php:`AbstractRecordList`
+* :php:`DatabaseRecordList` no longer extends :php:`AbstractDatabaseRecordList`
+
+
+Impact
+======
+
+Calling the constructor in these classes triggers a deprecation log entry.
+
+
+Affected Installations
+======================
+
+Any TYPO3 installation with an extension accessing or extending the deprecated classes.
+
+
+Migration
+=========
+
+The extension scanner checks if the classes are used.
+
+Any extension authors are encouraged to copy the content of these Classes into there child Classes.
+
+.. index:: Backend, PHP-API, FullyScanned
index 98d0ca5..fe821d9 100644 (file)
@@ -16,9 +16,12 @@ namespace TYPO3\CMS\Filelist;
 
 use TYPO3\CMS\Backend\Clipboard\Clipboard;
 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
-use TYPO3\CMS\Backend\RecordList\AbstractRecordList;
+use TYPO3\CMS\Backend\Routing\Router;
+use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
@@ -39,7 +42,7 @@ use TYPO3\CMS\Filelist\Controller\FileListController;
 /**
  * Class for rendering of File>Filelist
  */
-class FileList extends AbstractRecordList
+class FileList
 {
     /**
      * Default Max items shown
@@ -104,6 +107,8 @@ class FileList extends AbstractRecordList
     public $JScode = '';
 
     /**
+     * String with accumulated HTML content
+     *
      * @var string
      */
     public $HTMLcode = '';
@@ -129,9 +134,67 @@ class FileList extends AbstractRecordList
     public $path = '';
 
     /**
-     * @var Folder
+     * OBSOLETE - NOT USED ANYMORE. leftMargin
+     *
+     * @var int
      */
-    protected $folderObject;
+    public $leftMargin = 0;
+
+    /**
+     * This could be set to the total number of items. Used by the fwd_rew_navigation...
+     *
+     * @var string
+     */
+    public $totalItems = '';
+
+    /**
+     * Decides the columns shown. Filled with values that refers to the keys of the data-array. $this->fieldArray[0] is the title column.
+     *
+     * @var array
+     */
+    public $fieldArray = [];
+
+    /**
+     * Set to zero, if you don't want a left-margin with addElement function
+     *
+     * @var int
+     */
+    public $setLMargin = 1;
+
+    /**
+     * Contains page translation languages
+     *
+     * @var array
+     */
+    public $pageOverlays = [];
+
+    /**
+     * Counter increased for each element. Used to index elements for the JavaScript-code that transfers to the clipboard
+     *
+     * @var int
+     */
+    public $counter = 0;
+
+    /**
+     * Contains sys language icons and titles
+     *
+     * @var array
+     */
+    public $languageIconTitles = [];
+
+    /**
+     * Script URL
+     *
+     * @var string
+     */
+    public $thisScript = '';
+
+    /**
+     * If set this is <td> CSS-classname for odd columns in addElement. Used with db_layout / pages section
+     *
+     * @var string
+     */
+    public $oddColumnsCssClass = '';
 
     /**
      * Counting the elements no matter what
@@ -141,9 +204,38 @@ class FileList extends AbstractRecordList
     public $eCounter = 0;
 
     /**
-     * @var string
+     * @var TranslationConfigurationProvider
      */
-    public $totalItems = '';
+    public $translateTools;
+
+    /**
+     * Keys are fieldnames and values are td-parameters to add in addElement(), please use $addElement_tdCSSClass for CSS-classes;
+     *
+     * @var array
+     */
+    public $addElement_tdParams = [];
+
+    /**
+     * @var int
+     */
+    public $no_noWrap = 0;
+
+    /**
+     * @var int
+     */
+    public $showIcon = 1;
+
+    /**
+     * Keys are fieldnames and values are td-css-classes to add in addElement();
+     *
+     * @var array
+     */
+    public $addElement_tdCssClass = [];
+
+    /**
+     * @var Folder
+     */
+    protected $folderObject;
 
     /**
      * @var array
@@ -161,17 +253,14 @@ class FileList extends AbstractRecordList
     protected $resourceFactory;
 
     /**
-     * @param ResourceFactory $resourceFactory
+     * @var IconFactory
      */
-    public function injectResourceFactory(ResourceFactory $resourceFactory)
-    {
-        $this->resourceFactory = $resourceFactory;
-    }
+    protected $iconFactory;
 
     /**
-     * @var IconFactory
+     * @var int
      */
-    protected $iconFactory;
+    protected $id = 0;
 
     /**
      * @var FileListController
@@ -185,7 +274,12 @@ class FileList extends AbstractRecordList
      */
     public function __construct(FileListController $fileListController)
     {
-        parent::__construct();
+        if (isset($GLOBALS['BE_USER']->uc['titleLen']) && $GLOBALS['BE_USER']->uc['titleLen'] > 0) {
+            $this->fixedL = $GLOBALS['BE_USER']->uc['titleLen'];
+        }
+        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+        $this->getTranslateTools();
+        $this->determineScriptUrl();
         $this->fileListController = $fileListController;
         $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
 
@@ -195,6 +289,14 @@ class FileList extends AbstractRecordList
         }
     }
 
+    /**
+     * @param ResourceFactory $resourceFactory
+     */
+    public function injectResourceFactory(ResourceFactory $resourceFactory)
+    {
+        $this->resourceFactory = $resourceFactory;
+    }
+
     /**
      * Initialization of class
      *
@@ -437,6 +539,265 @@ class FileList extends AbstractRecordList
         return $out;
     }
 
+    /**
+     * Returns a table-row with the content from the fields in the input data array.
+     * OBS: $this->fieldArray MUST be set! (represents the list of fields to display)
+     *
+     * @param int $h Is an integer >=0 and denotes how tall an element is. Set to '0' makes a halv line, -1 = full line, set to 1 makes a 'join' and above makes 'line'
+     * @param string $icon Is the <img>+<a> of the record. If not supplied the first 'join'-icon will be a 'line' instead
+     * @param array $data Is the dataarray, record with the fields. Notice: These fields are (currently) NOT htmlspecialchar'ed before being wrapped in <td>-tags
+     * @param string $rowParams Is insert in the <tr>-tags. Must carry a ' ' as first character
+     * @param string $_ OBSOLETE - NOT USED ANYMORE. $lMargin is the leftMargin (int)
+     * @param string $_2 OBSOLETE - NOT USED ANYMORE. Is the HTML <img>-tag for an alternative 'gfx/ol/line.gif'-icon (used in the top)
+     * @param string $colType Defines the tag being used for the columns. Default is td.
+     *
+     * @return string HTML content for the table row
+     */
+    public function addElement($h, $icon, $data, $rowParams = '', $_ = '', $_2 = '', $colType = 'td')
+    {
+        $colType = ($colType === 'th') ? 'th' : 'td';
+        $noWrap = $this->no_noWrap ? '' : ' nowrap';
+        // Start up:
+        $l10nParent = isset($data['_l10nparent_']) ? (int)$data['_l10nparent_'] : 0;
+        $out = '
+               <!-- Element, begin: -->
+               <tr ' . $rowParams . ' data-uid="' . (int)$data['uid'] . '" data-l10nparent="' . $l10nParent . '">';
+        // Show icon and lines
+        if ($this->showIcon) {
+            $out .= '
+                       <' . $colType . ' class="col-icon nowrap">';
+            if (!$h) {
+                $out .= '&nbsp;';
+            } else {
+                for ($a = 0; $a < $h; $a++) {
+                    if (!$a) {
+                        if ($icon) {
+                            $out .= $icon;
+                        }
+                    }
+                }
+            }
+            $out .= '</' . $colType . '>
+                       ';
+        }
+        // Init rendering.
+        $colsp = '';
+        $lastKey = '';
+        $c = 0;
+        $ccount = 0;
+        // __label is used as the label key to circumvent problems with uid used as label (see #67756)
+        // as it was introduced later on, check if it really exists before using it
+        $fields = $this->fieldArray;
+        if ($colType === 'td' && array_key_exists('__label', $data)) {
+            $fields[0] = '__label';
+        }
+        // Traverse field array which contains the data to present:
+        foreach ($fields as $vKey) {
+            if (isset($data[$vKey])) {
+                if ($lastKey) {
+                    $cssClass = $this->addElement_tdCssClass[$lastKey];
+                    if ($this->oddColumnsCssClass && $ccount % 2 == 0) {
+                        $cssClass = implode(' ', [$this->addElement_tdCssClass[$lastKey], $this->oddColumnsCssClass]);
+                    }
+                    $out .= '
+                                               <' . $colType . ' class="' . $cssClass . $noWrap . '"' . $colsp . $this->addElement_tdParams[$lastKey] . '>' . $data[$lastKey] . '</' . $colType . '>';
+                }
+                $lastKey = $vKey;
+                $c = 1;
+                $ccount++;
+            } else {
+                if (!$lastKey) {
+                    $lastKey = $vKey;
+                }
+                $c++;
+            }
+            if ($c > 1) {
+                $colsp = ' colspan="' . $c . '"';
+            } else {
+                $colsp = '';
+            }
+        }
+        if ($lastKey) {
+            $cssClass = $this->addElement_tdCssClass[$lastKey];
+            if ($this->oddColumnsCssClass) {
+                $cssClass = implode(' ', [$this->addElement_tdCssClass[$lastKey], $this->oddColumnsCssClass]);
+            }
+            $out .= '
+                               <' . $colType . ' class="' . $cssClass . $noWrap . '"' . $colsp . $this->addElement_tdParams[$lastKey] . '>' . $data[$lastKey] . '</' . $colType . '>';
+        }
+        // End row
+        $out .= '
+               </tr>';
+        // Return row.
+        return $out;
+    }
+
+    /**
+     * Dummy function, used to write the top of a table listing.
+     */
+    public function writeTop()
+    {
+    }
+
+    /**
+     * Creates a forward/reverse button based on the status of ->eCounter, ->firstElementNumber, ->iLimit
+     *
+     * @param string $table Table name
+     * @return array array([boolean], [HTML]) where [boolean] is 1 for reverse element, [HTML] is the table-row code for the element
+     */
+    public function fwd_rwd_nav($table = '')
+    {
+        $code = '';
+        if ($this->eCounter >= $this->firstElementNumber && $this->eCounter < $this->firstElementNumber + $this->iLimit) {
+            if ($this->firstElementNumber && $this->eCounter == $this->firstElementNumber) {
+                //     Reverse
+                $theData = [];
+                $titleCol = $this->fieldArray[0];
+                $theData[$titleCol] = $this->fwd_rwd_HTML('fwd', $this->eCounter, $table);
+                $code = $this->addElement(1, '', $theData, 'class="fwd_rwd_nav"');
+            }
+            return [1, $code];
+        }
+        if ($this->eCounter == $this->firstElementNumber + $this->iLimit) {
+            //         Forward
+            $theData = [];
+            $titleCol = $this->fieldArray[0];
+            $theData[$titleCol] = $this->fwd_rwd_HTML('rwd', $this->eCounter, $table);
+            $code = $this->addElement(1, '', $theData, 'class="fwd_rwd_nav"');
+        }
+        return [0, $code];
+    }
+
+    /**
+     * Creates the button with link to either forward or reverse
+     *
+     * @param string $type Type: "fwd" or "rwd
+     * @param int $pointer Pointer
+     * @param string $table Table name
+     * @return string
+     * @access private
+     */
+    public function fwd_rwd_HTML($type, $pointer, $table = '')
+    {
+        $content = '';
+        $tParam = $table ? '&table=' . rawurlencode($table) : '';
+        switch ($type) {
+            case 'fwd':
+                $href = $this->listURL() . '&pointer=' . ($pointer - $this->iLimit) . $tParam;
+                $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
+                        'actions-move-up',
+                        Icon::SIZE_SMALL
+                    )->render() . '</a> <i>[1 - ' . $pointer . ']</i>';
+                break;
+            case 'rwd':
+                $href = $this->listURL() . '&pointer=' . $pointer . $tParam;
+                $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
+                        'actions-move-down',
+                        Icon::SIZE_SMALL
+                    )->render() . '</a> <i>[' . ($pointer + 1) . ' - ' . $this->totalItems . ']</i>';
+                break;
+        }
+        return $content;
+    }
+
+    /**
+     * Returning JavaScript for ClipBoard functionality.
+     *
+     * @return string
+     */
+    public function CBfunctions()
+    {
+        return '
+               // checkOffCB()
+       function checkOffCB(listOfCBnames, link) {      //
+               var checkBoxes, flag, i;
+               var checkBoxes = listOfCBnames.split(",");
+               if (link.rel === "") {
+                       link.rel = "allChecked";
+                       flag = true;
+               } else {
+                       link.rel = "";
+                       flag = false;
+               }
+               for (i = 0; i < checkBoxes.length; i++) {
+                       setcbValue(checkBoxes[i], flag);
+               }
+       }
+               // cbValue()
+       function cbValue(CBname) {      //
+               var CBfullName = "CBC["+CBname+"]";
+               return (document.dblistForm[CBfullName] && document.dblistForm[CBfullName].checked ? 1 : 0);
+       }
+               // setcbValue()
+       function setcbValue(CBname,flag) {      //
+               CBfullName = "CBC["+CBname+"]";
+               if(document.dblistForm[CBfullName]) {
+                       document.dblistForm[CBfullName].checked = flag ? "on" : 0;
+               }
+       }
+
+               ';
+    }
+
+    /**
+     * Initializes page languages and icons
+     */
+    public function initializeLanguages()
+    {
+        // Look up page overlays:
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('pages_language_overlay');
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+        $result = $queryBuilder
+            ->select('*')
+            ->from('pages_language_overlay')
+            ->where(
+                $queryBuilder->expr()->andX(
+                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)),
+                    $queryBuilder->expr()->gt(
+                        'sys_language_uid',
+                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    )
+                )
+            )
+            ->execute();
+
+        $this->pageOverlays = [];
+        while ($row = $result->fetch()) {
+            $this->pageOverlays[$row['sys_language_uid']] = $row;
+        }
+
+        $this->languageIconTitles = $this->getTranslateTools()->getSystemLanguages($this->id);
+    }
+
+    /**
+     * Return the icon for the language
+     *
+     * @param int $sys_language_uid Sys language uid
+     * @param bool $addAsAdditionalText If set to true, only the flag is returned
+     * @return string Language icon
+     */
+    public function languageFlag($sys_language_uid, $addAsAdditionalText = true)
+    {
+        $out = '';
+        $title = htmlspecialchars($this->languageIconTitles[$sys_language_uid]['title']);
+        if ($this->languageIconTitles[$sys_language_uid]['flagIcon']) {
+            $out .= '<span title="' . $title . '">' . $this->iconFactory->getIcon(
+                    $this->languageIconTitles[$sys_language_uid]['flagIcon'],
+                    Icon::SIZE_SMALL
+                )->render() . '</span>';
+            if (!$addAsAdditionalText) {
+                return $out;
+            }
+            $out .= '&nbsp;';
+        }
+        $out .= $title;
+        return $out;
+    }
+
     /**
      * If there is a parent folder and user has access to it, return an icon
      * which is linked to the filelist of the parent folder.
@@ -1121,4 +1482,72 @@ class FileList extends AbstractRecordList
     {
         return $GLOBALS['BE_USER'];
     }
+
+    /**
+     * Sets the script url depending on being a module or script request
+     */
+    protected function determineScriptUrl()
+    {
+        if ($routePath = GeneralUtility::_GP('route')) {
+            $router = GeneralUtility::makeInstance(Router::class);
+            $route = $router->match($routePath);
+            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+            $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
+        } elseif ($moduleName = GeneralUtility::_GP('M')) {
+            $this->thisScript = BackendUtility::getModuleUrl($moduleName);
+        } else {
+            $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
+        }
+    }
+
+    /**
+     * @return string
+     */
+    protected function getThisScript()
+    {
+        return strpos($this->thisScript, '?') === false ? $this->thisScript . '?' : $this->thisScript . '&';
+    }
+
+    /**
+     * Gets an instance of TranslationConfigurationProvider
+     *
+     * @return TranslationConfigurationProvider
+     */
+    protected function getTranslateTools()
+    {
+        if (!isset($this->translateTools)) {
+            $this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
+        }
+        return $this->translateTools;
+    }
+
+    /**
+     * Generates HTML code for a Reference tooltip out of
+     * sys_refindex records you hand over
+     *
+     * @param int $references number of records from sys_refindex table
+     * @param string $launchViewParameter JavaScript String, which will be passed as parameters to top.launchView
+     * @return string
+     */
+    protected function generateReferenceToolTip($references, $launchViewParameter = '')
+    {
+        if (!$references) {
+            $htmlCode = '-';
+        } else {
+            $htmlCode = '<a href="#"';
+            if ($launchViewParameter !== '') {
+                $htmlCode .= ' onclick="' . htmlspecialchars(
+                        ('top.launchView(' . $launchViewParameter . '); return false;')
+                    ) . '"';
+            }
+            $htmlCode .= ' title="' . htmlspecialchars(
+                    $this->getLanguageService()->sL(
+                        'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:show_references'
+                    ) . ' (' . $references . ')'
+                ) . '">';
+            $htmlCode .= $references;
+            $htmlCode .= '</a>';
+        }
+        return $htmlCode;
+    }
 }
index afd4a0e..b9d8c44 100644 (file)
@@ -354,6 +354,16 @@ return [
             'Breaking-37180-RemovedExtDirectDebugAndGLOBALSerror.rst',
         ],
     ],
+    'TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList' => [
+        'restFiles' => [
+            'Breaking-82334-AbstractRecordList.rst',
+        ],
+    ],
+    'TYPO3\CMS\Backend\RecordList\AbstractRecordList' => [
+        'restFiles' => [
+            'Breaking-82334-AbstractRecordList.rst',
+        ],
+    ],
 
     // Removed interfaces
     'TYPO3\CMS\Backend\Form\DatabaseFileIconsHookInterface' => [
index 53c2971..1b3b1dd 100644 (file)
@@ -39,6 +39,7 @@ use TYPO3\CMS\Core\Utility\MathUtility;
  * Child class for rendering of Web > List (not the final class)
  * Shared between Web>List and Web>Page
  * @see \TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList
+ * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
  */
 class AbstractDatabaseRecordList extends AbstractRecordList
 {
@@ -46,6 +47,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Specify a list of tables which are the only ones allowed to be displayed.
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $tableList = '';
 
@@ -53,6 +55,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Return URL
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $returnUrl = '';
 
@@ -60,6 +63,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Thumbnails on records containing files (pictures)
      *
      * @var bool
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $thumbs = 0;
 
@@ -67,6 +71,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * default Max items shown per table in "multi-table mode", may be overridden by tables.php
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $itemsLimitPerTable = 20;
 
@@ -74,6 +79,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * default Max items shown per table in "single-table mode", may be overridden by tables.php
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $itemsLimitSingleTable = 100;
 
@@ -81,6 +87,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Current script name
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $script = 'index.php';
 
@@ -88,6 +95,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Indicates if all available fields for a user should be selected or not.
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $allFields = 0;
 
@@ -95,6 +103,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Whether to show localization view or not
      *
      * @var bool
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $localizationView = false;
 
@@ -102,6 +111,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * If set, csvList is outputted.
      *
      * @var bool
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $csvOutput = false;
 
@@ -109,6 +119,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Field, to sort list by
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $sortField;
 
@@ -116,6 +127,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Field, indicating to sort in reverse order.
      *
      * @var bool
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $sortRev;
 
@@ -123,6 +135,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Containing which fields to display in extended mode
      *
      * @var string[]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $displayFields;
 
@@ -130,6 +143,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * String, can contain the field name from a table which must have duplicate values marked.
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $duplicateField;
 
@@ -137,6 +151,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Page id
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $id;
 
@@ -144,6 +159,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Tablename if single-table mode
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $table = '';
 
@@ -151,6 +167,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * If TRUE, records are listed only if a specific table is selected.
      *
      * @var bool
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $listOnlyInSingleTableMode = false;
 
@@ -158,6 +175,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Pointer for browsing list
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $firstElementNumber = 0;
 
@@ -165,6 +183,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Search string
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $searchString = '';
 
@@ -172,6 +191,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Levels to search down.
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $searchLevels = '';
 
@@ -179,6 +199,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Number of records to show
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $showLimit = 0;
 
@@ -186,6 +207,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Page select permissions
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $perms_clause = '';
 
@@ -193,6 +215,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Some permissions...
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $calcPerms = 0;
 
@@ -200,6 +223,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Mode for what happens when a user clicks the title of a record.
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $clickTitleMode = '';
 
@@ -207,6 +231,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Shared module configuration, used by localization features
      *
      * @var array
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $modSharedTSconfig = [];
 
@@ -214,6 +239,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Loaded with page record with version overlay if any.
      *
      * @var string[]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $pageRecord = [];
 
@@ -221,6 +247,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Tables which should not get listed
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $hideTables = '';
 
@@ -228,6 +255,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Tables which should not list their translations
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $hideTranslations = '';
 
@@ -235,6 +263,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * TSconfig which overwrites TCA-Settings
      *
      * @var mixed[][]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $tableTSconfigOverTCA = [];
 
@@ -242,6 +271,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Array of collapsed / uncollapsed tables in multi table view
      *
      * @var int[][]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $tablesCollapsed = [];
 
@@ -249,6 +279,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * JavaScript code accumulation
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $JScode = '';
 
@@ -256,6 +287,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * HTML output
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $HTMLcode = '';
 
@@ -263,6 +295,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * "LIMIT " in SQL...
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $iLimit = 0;
 
@@ -270,6 +303,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Counting the elements no matter what...
      *
      * @var int
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $eCounter = 0;
 
@@ -277,6 +311,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Set to the total number of items for a table when selecting.
      *
      * @var string
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $totalItems = '';
 
@@ -284,6 +319,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Cache for record path
      *
      * @var mixed[]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $recPath_cache = [];
 
@@ -291,6 +327,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Fields to display for the current table
      *
      * @var string[]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $setFields = [];
 
@@ -298,6 +335,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Used for tracking next/prev uids
      *
      * @var int[][]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $currentTable = [];
 
@@ -305,17 +343,20 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Used for tracking duplicate values of fields
      *
      * @var string[]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $duplicateStack = [];
 
     /**
      * @var array[] Module configuration
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public $modTSconfig;
 
     /**
      * Override/add urlparameters in listUrl() method
      * @var string[]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected $overrideUrlParameters = [];
 
@@ -323,6 +364,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Override the page ids taken into account by getPageIdConstraint()
      *
      * @var array
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected $overridePageIdList = [];
 
@@ -334,6 +376,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      *    'after' => []
      *  ]
      * @var array[]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected $tableDisplayOrder = [];
 
@@ -346,6 +389,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param string $search Search word, if any
      * @param int $levels Number of levels to search down the page tree
      * @param int $showLimit Limit of records to be listed.
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
     {
@@ -436,6 +480,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Traverses the table(s) to be listed and renders the output code for each:
      * The HTML is accumulated in $this->HTMLcode
      * Finishes off with a stopper-gif
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function generateList()
     {
@@ -544,6 +589,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param int $id
      * @param string $fields List of fields to show in the listing. Pseudo fields will be added including the record header.
      * @return string HTML code
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function getTable($tableName, $id, $fields = '')
     {
@@ -555,6 +601,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      *
      * @param bool $formFields If TRUE, the search box is wrapped in its own form-tags
      * @return string HTML for the search box
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function getSearchBox($formFields = true)
     {
@@ -629,6 +676,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
     /**
      * Setting the field names to display in extended list.
      * Sets the internal variable $this->setFields
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function setDispFields()
     {
@@ -653,6 +701,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param string $table Table (record is from)
      * @param string $field Field name for which thumbnail are to be rendered.
      * @return string HTML for thumbnails, if any.
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function thumbCode($row, $table, $field)
     {
@@ -668,6 +717,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param string[] $additionalConstraints Additional part for where clause
      * @param string[] $fields Field list to select, * for all
      * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function getQueryBuilder(
         string $table,
@@ -705,6 +755,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param QueryBuilder $queryBuilder
      * @paran bool $addSorting
      * @return QueryBuilder
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected function prepareQueryBuilder(
         string $table,
@@ -822,6 +873,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param string $table Table name
      * @param int $pageId Only used to build the search constraints, $this->pidList is used for restrictions
      * @param array $constraints Additional constraints for where clause
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function setTotalItems(string $table, int $pageId, array $constraints)
     {
@@ -856,6 +908,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param string $table Table, in which the fields are being searched.
      * @param int $currentPid Page id for the possible search limit. -1 only if called from an old XCLASS.
      * @return string Returns part of WHERE-clause for searching, if applicable.
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function makeSearchString($table, $currentPid = -1)
     {
@@ -956,6 +1009,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      *
      * @param string $tableName
      * @return string[]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected function getSearchFields($tableName)
     {
@@ -988,6 +1042,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param string $table Table name
      * @param string $code Table label
      * @return string The linked table label
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function linkWrapTable($table, $code)
     {
@@ -1005,6 +1060,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param string $code Item title (not htmlspecialchars()'ed yet)
      * @param mixed[] $row Item row
      * @return string The item title. Ready for HTML output (is htmlspecialchars()'ed)
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function linkWrapItems($table, $uid, $code, $row)
     {
@@ -1064,6 +1120,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param string $code code to wrap
      * @param string $testString String which is tested for being a URL or email and which will be used for the link if so.
      * @return string Link-Wrapped $code value, if $testString was URL or email.
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function linkUrlMail($code, $testString)
     {
@@ -1089,6 +1146,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param string $table Table name to display. Enter "-1" for the current table.
      * @param string $exclList Comma separated list of fields NOT to include ("sortField", "sortRev" or "firstElementNumber")
      * @return string URL
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function listURL($altId = '', $table = '-1', $exclList = '')
     {
@@ -1146,6 +1204,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
     /**
      * Returns "requestUri" - which is basically listURL
      * @return string Content of ->listURL()
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function requestUri()
     {
@@ -1159,6 +1218,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param bool $dontCheckUser If set, users access to the field (non-exclude-fields) is NOT checked.
      * @param bool $addDateFields If set, also adds crdate and tstamp fields (note: they will also be added if user is admin or dontCheckUser is set)
      * @return string[] Array, where values are fieldnames to include in query
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function makeFieldList($table, $dontCheckUser = false, $addDateFields = false)
     {
@@ -1215,6 +1275,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param int $depth Depth to go down
      * @param string $perms_clause select clause
      * @return int[]
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected function getSearchableWebmounts($id, $depth, $perms_clause)
     {
@@ -1245,6 +1306,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Redirects to FormEngine if a record is just localized.
      *
      * @param string $justLocalized String with table, orig uid and language separated by ":
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function localizationRedirect($justLocalized)
     {
@@ -1294,6 +1356,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Set URL parameters to override or add in the listUrl() method.
      *
      * @param string[] $urlParameters
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function setOverrideUrlParameters(array $urlParameters)
     {
@@ -1311,6 +1374,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      *
      * @param array $orderInformation
      * @throws \UnexpectedValueException
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function setTableDisplayOrder(array $orderInformation)
     {
@@ -1335,6 +1399,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
 
     /**
      * @return array
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function getOverridePageIdList(): array
     {
@@ -1343,6 +1408,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
 
     /**
      * @param int[]|array $overridePageIdList
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function setOverridePageIdList(array $overridePageIdList)
     {
@@ -1356,6 +1422,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param string $tableName
      * @param QueryBuilder $queryBuilder
      * @return QueryBuilder Modified QueryBuilder object
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected function addPageIdConstraint(string $tableName, QueryBuilder $queryBuilder): QueryBuilder
     {
@@ -1401,7 +1468,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * Method used to log deprecated usage of old buildQueryParametersPostProcess hook arguments
      *
      * @param string $index
-     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10 - see method usages
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected function logDeprecation(string $index)
     {
@@ -1410,6 +1477,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
 
     /**
      * @return BackendUserAuthentication
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     protected function getBackendUserAuthentication()
     {
index feb4773..51bd69b 100644 (file)
@@ -14,29 +14,41 @@ namespace TYPO3\CMS\Recordlist\RecordList;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
 use TYPO3\CMS\Backend\Module\BaseScriptClass;
 use TYPO3\CMS\Backend\RecordList\RecordListGetTableHookInterface;
+use TYPO3\CMS\Backend\Routing\Router;
+use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
 use TYPO3\CMS\Backend\Template\DocumentTemplate;
 use TYPO3\CMS\Backend\Template\ModuleTemplate;
+use TYPO3\CMS\Backend\Tree\View\PageTreeView;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
+use TYPO3\CMS\Core\Service\DependencyOrderingService;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\CsvUtility;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\HttpUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Frontend\Page\PageRepository;
 
 /**
  * Class for rendering of Web>List module
  */
-class DatabaseRecordList extends AbstractDatabaseRecordList
+class DatabaseRecordList
 {
     // *********
     // External:
@@ -128,11 +140,39 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
     public $pageRow = [];
 
     /**
-     * Used to accumulate CSV lines for CSV export.
+     * Whether to show localization view or not
      *
-     * @var string[]
+     * @var bool
      */
-    protected $csvLines = [];
+    public $localizationView = false;
+
+    /**
+     * Shared module configuration, used by localization features
+     *
+     * @var array
+     */
+    public $modSharedTSconfig = [];
+
+    /**
+     * Contains page translation languages
+     *
+     * @var array
+     */
+    public $pageOverlays = [];
+
+    /**
+     * Contains sys language icons and titles
+     *
+     * @var array
+     */
+    public $languageIconTitles = [];
+
+    /**
+     * Tables which should not list their translations
+     *
+     * @var string
+     */
+    public $hideTranslations = '';
 
     /**
      * If set, the listing is returned as CSV instead.
@@ -141,6 +181,335 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
      */
     public $csvOutput = false;
 
+    /**
+     * Cache for record path
+     *
+     * @var mixed[]
+     */
+    public $recPath_cache = [];
+
+    /**
+     * Field, to sort list by
+     *
+     * @var string
+     */
+    public $sortField;
+
+    /**
+     * default Max items shown per table in "multi-table mode", may be overridden by tables.php
+     *
+     * @var int
+     */
+    public $itemsLimitPerTable = 20;
+
+    /**
+     * Keys are fieldnames and values are td-parameters to add in addElement(), please use $addElement_tdCSSClass for CSS-classes;
+     *
+     * @var array
+     */
+    public $addElement_tdParams = [];
+
+    /**
+     * Page id
+     *
+     * @var int
+     */
+    public $id;
+
+    /**
+     * @var int
+     */
+    public $no_noWrap = 0;
+
+    /**
+     * Set to zero, if you don't want a left-margin with addElement function
+     *
+     * @var int
+     */
+    public $setLMargin = 1;
+
+    /**
+     * Used for tracking duplicate values of fields
+     *
+     * @var string[]
+     */
+    public $duplicateStack = [];
+
+    /**
+     * Current script name
+     *
+     * @var string
+     */
+    public $script = 'index.php';
+
+    /**
+     * If TRUE, records are listed only if a specific table is selected.
+     *
+     * @var bool
+     */
+    public $listOnlyInSingleTableMode = false;
+
+    /**
+     * Script URL
+     *
+     * @var string
+     */
+    public $thisScript = '';
+
+    /**
+     * JavaScript code accumulation
+     *
+     * @var string
+     */
+    public $JScode = '';
+
+    /**
+     * @var TranslationConfigurationProvider
+     */
+    public $translateTools;
+
+    /**
+     * default Max items shown per table in "single-table mode", may be overridden by tables.php
+     *
+     * @var int
+     */
+    public $itemsLimitSingleTable = 100;
+
+    /**
+     * Array of collapsed / uncollapsed tables in multi table view
+     *
+     * @var int[][]
+     */
+    public $tablesCollapsed = [];
+
+    /**
+     * @var array[] Module configuration
+     */
+    public $modTSconfig;
+
+    /**
+     * String with accumulated HTML content
+     *
+     * @var string
+     */
+    public $HTMLcode = '';
+
+    /**
+     * Keys are fieldnames and values are td-css-classes to add in addElement();
+     *
+     * @var array
+     */
+    public $addElement_tdCssClass = [];
+
+    /**
+     * Thumbnails on records containing files (pictures)
+     *
+     * @var bool
+     */
+    public $thumbs = 0;
+
+    /**
+     * Used for tracking next/prev uids
+     *
+     * @var int[][]
+     */
+    public $currentTable = [];
+
+    /**
+     * Indicates if all available fields for a user should be selected or not.
+     *
+     * @var int
+     */
+    public $allFields = 0;
+
+    /**
+     * Number of records to show
+     *
+     * @var int
+     */
+    public $showLimit = 0;
+
+    /**
+     * Decides the columns shown. Filled with values that refers to the keys of the data-array. $this->fieldArray[0] is the title column.
+     *
+     * @var array
+     */
+    public $fieldArray = [];
+
+    /**
+     * Tables which should not get listed
+     *
+     * @var string
+     */
+    public $hideTables = '';
+
+    /**
+     * Containing which fields to display in extended mode
+     *
+     * @var string[]
+     */
+    public $displayFields;
+
+    /**
+     * If set this is <td> CSS-classname for odd columns in addElement. Used with db_layout / pages section
+     *
+     * @var string
+     */
+    public $oddColumnsCssClass = '';
+
+    /**
+     * Not used in this class - but maybe extension classes...
+     * Max length of strings
+     *
+     * @var int
+     */
+    public $fixedL = 30;
+
+    /**
+     * Page select permissions
+     *
+     * @var string
+     */
+    public $perms_clause = '';
+
+    /**
+     * Return URL
+     *
+     * @var string
+     */
+    public $returnUrl = '';
+
+    /**
+     * Tablename if single-table mode
+     *
+     * @var string
+     */
+    public $table = '';
+
+    /**
+     * Some permissions...
+     *
+     * @var int
+     */
+    public $calcPerms = 0;
+
+    /**
+     * Mode for what happens when a user clicks the title of a record.
+     *
+     * @var string
+     */
+    public $clickTitleMode = '';
+
+    /**
+     * @var int
+     */
+    public $showIcon = 1;
+
+    /**
+     * Levels to search down.
+     *
+     * @var int
+     */
+    public $searchLevels = '';
+
+    /**
+     * "LIMIT " in SQL...
+     *
+     * @var int
+     */
+    public $iLimit = 0;
+
+    /**
+     * Set to the total number of items for a table when selecting.
+     *
+     * @var string
+     */
+    public $totalItems = '';
+
+    /**
+     * OBSOLETE - NOT USED ANYMORE. leftMargin
+     *
+     * @var int
+     */
+    public $leftMargin = 0;
+
+    /**
+     * TSconfig which overwrites TCA-Settings
+     *
+     * @var mixed[][]
+     */
+    public $tableTSconfigOverTCA = [];
+
+    /**
+     * Loaded with page record with version overlay if any.
+     *
+     * @var string[]
+     */
+    public $pageRecord = [];
+
+    /**
+     * Fields to display for the current table
+     *
+     * @var string[]
+     */
+    public $setFields = [];
+
+    /**
+     * Counter increased for each element. Used to index elements for the JavaScript-code that transfers to the clipboard
+     *
+     * @var int
+     */
+    public $counter = 0;
+
+    /**
+     * Pointer for browsing list
+     *
+     * @var int
+     */
+    public $firstElementNumber = 0;
+
+    /**
+     * Counting the elements no matter what...
+     *
+     * @var int
+     */
+    public $eCounter = 0;
+
+    /**
+     * Search string
+     *
+     * @var string
+     */
+    public $searchString = '';
+
+    /**
+     * Field, indicating to sort in reverse order.
+     *
+     * @var bool
+     */
+    public $sortRev;
+
+    /**
+     * String, can contain the field name from a table which must have duplicate values marked.
+     *
+     * @var string
+     */
+    public $duplicateField;
+
+    /**
+     * Specify a list of tables which are the only ones allowed to be displayed.
+     *
+     * @var string
+     */
+    public $tableList = '';
+
+    /**
+     * Used to accumulate CSV lines for CSV export.
+     *
+     * @var string[]
+     */
+    protected $csvLines = [];
+
     /**
      * Clipboard object
      *
@@ -202,11 +571,42 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
     protected $iconFactory;
 
     /**
-     * Constructor
+     * Array with before/after setting for tables
+     * Structure:
+     * 'tableName' => [
+     *    'before' => ['A', ...]
+     *    'after' => []
+     *  ]
+     *
+     * @var array[]
      */
-    public function __construct()
+    protected $tableDisplayOrder = [];
+
+    /**
+     * Override the page ids taken into account by getPageIdConstraint()
+     *
+     * @var array
+     */
+    protected $overridePageIdList = [];
+
+    /**
+     * Override/add urlparameters in listUrl() method
+     * @var string[]
+     */
+    protected $overrideUrlParameters = [];
+
+    /**
+     * Constructor
+     */
+    public function __construct()
     {
-        parent::__construct();
+        if (isset($GLOBALS['BE_USER']->uc['titleLen']) && $GLOBALS['BE_USER']->uc['titleLen'] > 0) {
+            $this->fixedL = $GLOBALS['BE_USER']->uc['titleLen'];
+        }
+        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+        $this->getTranslateTools();
+        $this->determineScriptUrl();
+
         $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
     }
 
@@ -2086,285 +2486,1754 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
         foreach ($attributes as $key => $value) {
             $attributesString .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
         }
-        return '<a href="#" ' . $attributesString . '>' . $string . '</a>';
+        return '<a href="#" ' . $attributesString . '>' . $string . '</a>';
+    }
+
+    /**
+     * Returns TRUE if a numeric clipboard pad is selected/active
+     *
+     * @return bool
+     */
+    public function clipNumPane()
+    {
+        return in_array('_CLIPBOARD_', $this->fieldArray) && $this->clipObj->current !== 'normal';
+    }
+
+    /**
+     * Creates a sort-by link on the input string ($code).
+     * It will automatically detect if sorting should be ascending or descending depending on $this->sortRev.
+     * Also some fields will not be possible to sort (including if single-table-view is disabled).
+     *
+     * @param string $code The string to link (text)
+     * @param string $field The fieldname represented by the title ($code)
+     * @param string $table Table name
+     * @return string Linked $code variable
+     */
+    public function addSortLink($code, $field, $table)
+    {
+        // Certain circumstances just return string right away (no links):
+        if ($field === '_CONTROL_' || $field === '_LOCALIZATION_' || $field === '_CLIPBOARD_' || $field === '_REF_' || $this->disableSingleTableView) {
+            return $code;
+        }
+        // If "_PATH_" (showing record path) is selected, force sorting by pid field (will at least group the records!)
+        if ($field === '_PATH_') {
+            $field = 'pid';
+        }
+        //      Create the sort link:
+        $sortUrl = $this->listURL('', '-1', 'sortField,sortRev,table,firstElementNumber') . '&table=' . $table
+            . '&sortField=' . $field . '&sortRev=' . ($this->sortRev || $this->sortField != $field ? 0 : 1);
+        $sortArrow = $this->sortField === $field
+            ? $this->iconFactory->getIcon('status-status-sorting-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render()
+            : '';
+        // Return linked field:
+        return '<a href="' . htmlspecialchars($sortUrl) . '">' . $code . $sortArrow . '</a>';
+    }
+
+    /**
+     * Returns the path for a certain pid
+     * The result is cached internally for the session, thus you can call
+     * this function as much as you like without performance problems.
+     *
+     * @param int $pid The page id for which to get the path
+     * @return mixed[] The path.
+     */
+    public function recPath($pid)
+    {
+        if (!isset($this->recPath_cache[$pid])) {
+            $this->recPath_cache[$pid] = BackendUtility::getRecordPath($pid, $this->perms_clause, 20);
+        }
+        return $this->recPath_cache[$pid];
+    }
+
+    /**
+     * Returns TRUE if a link for creating new records should be displayed for $table
+     *
+     * @param string $table Table name
+     * @return bool Returns TRUE if a link for creating new records should be displayed for $table
+     * @see \TYPO3\CMS\Backend\Controller\NewRecordController::showNewRecLink
+     */
+    public function showNewRecLink($table)
+    {
+        // No deny/allow tables are set:
+        if (empty($this->allowedNewTables) && empty($this->deniedNewTables)) {
+            return true;
+        }
+        return !in_array($table, $this->deniedNewTables)
+            && (empty($this->allowedNewTables) || in_array($table, $this->allowedNewTables));
+    }
+
+    /**
+     * Creates the "&returnUrl" parameter for links - this is used when the script links
+     * to other scripts and passes its own URL with the link so other scripts can return to the listing again.
+     * Uses REQUEST_URI as value.
+     *
+     * @return string
+     */
+    public function makeReturnUrl()
+    {
+        return '&returnUrl=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'));
+    }
+
+    /************************************
+     *
+     * CSV related functions
+     *
+     ************************************/
+    /**
+     * Initializes internal csvLines array with the header of field names
+     */
+    protected function initCSV()
+    {
+        $this->addHeaderRowToCSV();
+    }
+
+    /**
+     * Add header line with field names as CSV line
+     */
+    protected function addHeaderRowToCSV()
+    {
+        // Add header row, control fields will be reduced inside addToCSV()
+        $this->addToCSV(array_combine($this->fieldArray, $this->fieldArray));
+    }
+
+    /**
+     * Adds selected columns of one table row as CSV line.
+     *
+     * @param mixed[] $row Record array, from which the values of fields found in $this->fieldArray will be listed in the CSV output.
+     */
+    protected function addToCSV(array $row = [])
+    {
+        $rowReducedByControlFields = self::removeControlFieldsFromFieldRow($row);
+        // Get an field array without control fields but in the expected order
+        $fieldArray = array_intersect_key(array_flip($this->fieldArray), $rowReducedByControlFields);
+        // Overwrite fieldArray to keep the order with an array of needed fields
+        $rowReducedToSelectedColumns = array_replace($fieldArray, array_intersect_key($rowReducedByControlFields, $fieldArray));
+        $this->setCsvRow($rowReducedToSelectedColumns);
+    }
+
+    /**
+     * Remove control fields from row for CSV export
+     *
+     * @param mixed[] $row fieldNames => fieldValues
+     * @return mixed[] Input array reduces by control fields
+     */
+    protected static function removeControlFieldsFromFieldRow(array $row = [])
+    {
+        // Possible control fields in a list row
+        $controlFields = [
+            '_PATH_',
+            '_REF_',
+            '_CONTROL_',
+            '_CLIPBOARD_',
+            '_LOCALIZATION_',
+            '_LOCALIZATION_b'
+        ];
+        return array_diff_key($row, array_flip($controlFields));
+    }
+
+    /**
+     * Adds input row of values to the internal csvLines array as a CSV formatted line
+     *
+     * @param mixed[] $csvRow Array with values to be listed.
+     */
+    public function setCsvRow($csvRow)
+    {
+        $this->csvLines[] = CsvUtility::csvValues($csvRow);
+    }
+
+    /**
+     * Compiles the internal csvLines array to a csv-string and outputs it to the browser.
+     * This function exits!
+     *
+     * @param string $prefix Filename prefix:
+     */
+    public function outputCSV($prefix)
+    {
+        // Setting filename:
+        $filename = $prefix . '_' . date('dmy-Hi') . '.csv';
+        // Creating output header:
+        header('Content-Type: application/octet-stream');
+        header('Content-Disposition: attachment; filename=' . $filename);
+        // Cache-Control header is needed here to solve an issue with browser IE and
+        // versions lower than 9. See for more information: http://support.microsoft.com/kb/323308
+        header("Cache-Control: ''");
+        // Printing the content of the CSV lines:
+        echo implode(CRLF, $this->csvLines);
+        // Exits:
+        die;
+    }
+
+    /**
+     * add action into correct section
+     *
+     * @param array $cells
+     * @param string $action
+     * @param string $actionKey
+     */
+    public function addActionToCellGroup(&$cells, $action, $actionKey)
+    {
+        $cellsMap = [
+            'primary' => [
+                'view', 'edit', 'hide', 'delete', 'stat'
+            ],
+            'secondary' => [
+                'viewBig', 'history', 'perms', 'new', 'move', 'moveUp', 'moveDown', 'moveLeft', 'moveRight', 'version'
+            ]
+        ];
+        $classification = in_array($actionKey, $cellsMap['primary']) ? 'primary' : 'secondary';
+        $cells[$classification][$actionKey] = $action;
+        unset($cells[$actionKey]);
+    }
+
+    /**
+     * Check if the record represents the current backend user
+     *
+     * @param string $table
+     * @param array $row
+     * @return bool
+     */
+    protected function isRecordCurrentBackendUser($table, $row)
+    {
+        return $table === 'be_users' && (int)$row['uid'] === $this->getBackendUserAuthentication()->user['uid'];
+    }
+
+    /**
+     * @param bool $isEditable
+     */
+    public function setIsEditable($isEditable)
+    {
+        $this->editable = $isEditable;
+    }
+
+    /**
+     * Check if the table is readonly or editable
+     * @param string $table
+     * @return bool
+     */
+    public function isEditable($table)
+    {
+        return $GLOBALS['TCA'][$table]['ctrl']['readOnly'] || $this->editable;
+    }
+
+    /**
+     * Check if the current record is locked by editlock. Pages are locked if their editlock flag is set,
+     * records are if they are locked themselves or if the page they are on is locked (a page’s editlock
+     * is transitive for its content elements).
+     *
+     * @param string $table
+     * @param array $row
+     * @param bool $editPermission
+     * @return bool
+     */
+    protected function overlayEditLockPermissions($table, $row = [], $editPermission = true)
+    {
+        if ($editPermission && !$this->getBackendUserAuthentication()->isAdmin()) {
+            // If no $row is submitted we only check for general edit lock of current page (except for table "pages")
+            if (empty($row)) {
+                return $table === 'pages' ? true : !$this->pageRow['editlock'];
+            }
+            if (($table === 'pages' && $row['editlock']) || ($table !== 'pages' && $this->pageRow['editlock'])) {
+                $editPermission = false;
+            } elseif (isset($GLOBALS['TCA'][$table]['ctrl']['editlock']) && $row[$GLOBALS['TCA'][$table]['ctrl']['editlock']]) {
+                $editPermission = false;
+            }
+        }
+        return $editPermission;
+    }
+
+    /**
+     * Check whether or not the current backend user is an admin or the current page is
+     * locked by editlock.
+     *
+     * @return bool
+     */
+    protected function editLockPermissions()
+    {
+        return $this->getBackendUserAuthentication()->isAdmin() || !$this->pageRow['editlock'];
+    }
+
+    /**
+     * @return BaseScriptClass
+     */
+    protected function getModule()
+    {
+        return $GLOBALS['SOBE'];
+    }
+
+    /**
+     * @return DocumentTemplate
+     */
+    protected function getDocumentTemplate()
+    {
+        return $GLOBALS['TBE_TEMPLATE'];
+    }
+
+    /**
+     * Initializes the list generation
+     *
+     * @param int $id Page id for which the list is rendered. Must be >= 0
+     * @param string $table Tablename - if extended mode where only one table is listed at a time.
+     * @param int $pointer Browsing pointer.
+     * @param string $search Search word, if any
+     * @param int $levels Number of levels to search down the page tree
+     * @param int $showLimit Limit of records to be listed.
+     */
+    public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
+    {
+        $backendUser = $this->getBackendUserAuthentication();
+        // Setting internal variables:
+        // sets the parent id
+        $this->id = (int)$id;
+        if ($GLOBALS['TCA'][$table]) {
+            // Setting single table mode, if table exists:
+            $this->table = $table;
+        }
+        $this->firstElementNumber = $pointer;
+        $this->searchString = trim($search);
+        $this->searchLevels = (int)$levels;
+        $this->showLimit = MathUtility::forceIntegerInRange($showLimit, 0, 10000);
+        // Setting GPvars:
+        $this->csvOutput = (bool)GeneralUtility::_GP('csv');
+        $this->sortField = GeneralUtility::_GP('sortField');
+        $this->sortRev = GeneralUtility::_GP('sortRev');
+        $this->displayFields = GeneralUtility::_GP('displayFields');
+        $this->duplicateField = GeneralUtility::_GP('duplicateField');
+        if (GeneralUtility::_GP('justLocalized')) {
+            $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
+        }
+        // Init dynamic vars:
+        $this->counter = 0;
+        $this->JScode = '';
+        $this->HTMLcode = '';
+        // Limits
+        if (isset($this->modTSconfig['properties']['itemsLimitPerTable'])) {
+            $this->itemsLimitPerTable = MathUtility::forceIntegerInRange(
+                (int)$this->modTSconfig['properties']['itemsLimitPerTable'],
+                1,
+                10000
+            );
+        }
+        if (isset($this->modTSconfig['properties']['itemsLimitSingleTable'])) {
+            $this->itemsLimitSingleTable = MathUtility::forceIntegerInRange(
+                (int)$this->modTSconfig['properties']['itemsLimitSingleTable'],
+                1,
+                10000
+            );
+        }
+
+        // $table might be NULL at this point in the code. As the expressionBuilder
+        // is used to limit returned records based on the page permissions and the
+        // uid field of the pages it can hardcoded to work on the pages table.
+        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('pages')
+            ->expr();
+        $permsClause = $expressionBuilder->andX($backendUser->getPagePermsClause(1));
+        // This will hide records from display - it has nothing to do with user rights!!
+        if ($pidList = $backendUser->getTSConfigVal('options.hideRecords.pages')) {
+            $pidList = GeneralUtility::intExplode(',', $pidList, true);
+            if (!empty($pidList)) {
+                $permsClause->add($expressionBuilder->notIn('pages.uid', $pidList));
+            }
+        }
+        $this->perms_clause = (string)$permsClause;
+
+        // Get configuration of collapsed tables from user uc and merge with sanitized GP vars
+        $this->tablesCollapsed = is_array($backendUser->uc['moduleData']['list'])
+            ? $backendUser->uc['moduleData']['list']
+            : [];
+        $collapseOverride = GeneralUtility::_GP('collapse');
+        if (is_array($collapseOverride)) {
+            foreach ($collapseOverride as $collapseTable => $collapseValue) {
+                if (is_array($GLOBALS['TCA'][$collapseTable]) && ($collapseValue == 0 || $collapseValue == 1)) {
+                    $this->tablesCollapsed[$collapseTable] = $collapseValue;
+                }
+            }
+            // Save modified user uc
+            $backendUser->uc['moduleData']['list'] = $this->tablesCollapsed;
+            $backendUser->writeUC($backendUser->uc);
+            $returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
+            if ($returnUrl !== '') {
+                HttpUtility::redirect($returnUrl);
+            }
+        }
+
+        // Initialize languages:
+        if ($this->localizationView) {
+            $this->initializeLanguages();
+        }
+    }
+
+    /**
+     * Traverses the table(s) to be listed and renders the output code for each:
+     * The HTML is accumulated in $this->HTMLcode
+     * Finishes off with a stopper-gif
+     */
+    public function generateList()
+    {
+        // Set page record in header
+        $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
+        $hideTablesArray = GeneralUtility::trimExplode(',', $this->hideTables);
+
+        $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)
+                || !$backendUser->check('tables_select', $tableName)
+            ) {
+                $hideTable = true;
+            }
+
+            if (!$hideTable) {
+                // Don't show table if hidden by TCA ctrl section
+                // Don't show table if hidden by pageTSconfig mod.web_list.hideTables
+                $hideTable = $hideTable
+                    || !empty($GLOBALS['TCA'][$tableName]['ctrl']['hideTable'])
+                    || in_array($tableName, $hideTablesArray, true)
+                    || in_array('*', $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 = (bool)$this->tableTSconfigOverTCA[$tableName . '.']['hideTable'];
+                }
+            }
+            if ($hideTable) {
+                unset($tableNames[$tableName]);
+            } else {
+                if (isset($this->tableDisplayOrder[$tableName])) {
+                    // Copy display order information
+                    $tableNames[$tableName] = $this->tableDisplayOrder[$tableName];
+                } else {
+                    $tableNames[$tableName] = [];
+                }
+            }
+        }
+        unset($config);
+
+        $orderedTableNames = GeneralUtility::makeInstance(DependencyOrderingService::class)
+            ->orderByDependencies($tableNames);
+
+        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
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getQueryBuilderForTable($tableName);
+                $queryBuilder->getRestrictions()
+                    ->removeAll()
+                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                    ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+                $queryBuilder = $this->addPageIdConstraint($tableName, $queryBuilder);
+                $firstRow = $queryBuilder->select('uid')
+                    ->from($tableName)
+                    ->execute()
+                    ->fetch();
+                if (!is_array($firstRow)) {
+                    continue;
+                }
+                $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 = [];
+                }
+            } else {
+                $fields = [];
+            }
+
+            // Finally, render the list:
+            $this->HTMLcode .= $this->getTable($tableName, $this->id, implode(',', $fields));
+        }
+    }
+
+    /**
+     * Creates the search box
+     *
+     * @param bool $formFields If TRUE, the search box is wrapped in its own form-tags
+     * @return string HTML for the search box
+     */
+    public function getSearchBox($formFields = true)
+    {
+        /** @var $iconFactory IconFactory */
+        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+        $lang = $this->getLanguageService();
+        // Setting form-elements, if applicable:
+        $formElements = ['', ''];
+        if ($formFields) {
+            $formElements = [
+                '<form action="' . htmlspecialchars(
+                    $this->listURL('', '-1', 'firstElementNumber,search_field')
+                ) . '" method="post">',
+                '</form>'
+            ];
+        }
+        // Make level selector:
+        $opt = [];
+
+        // "New" generation of search levels ... based on TS config
+        $config = BackendUtility::getPagesTSconfig($this->id);
+        $searchLevelsFromTSconfig = $config['mod.']['web_list.']['searchLevel.']['items.'];
+        $searchLevelItems = [];
+
+        // get translated labels for search levels from pagets
+        foreach ($searchLevelsFromTSconfig as $keySearchLevel => $labelConfigured) {
+            $label = $lang->sL('LLL:' . $labelConfigured);
+            if ($label === '') {
+                $label = $labelConfigured;
+            }
+            $searchLevelItems[$keySearchLevel] = $label;
+        }
+
+        foreach ($searchLevelItems as $kv => $label) {
+            $opt[] = '<option value="' . $kv . '"' . ($kv === $this->searchLevels ? ' selected="selected"' : '') . '>' . htmlspecialchars(
+                    $label
+                ) . '</option>';
+        }
+        $lMenu = '<select class="form-control" name="search_levels" title="' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.search_levels')
+            ) . '" id="search_levels">' . implode('', $opt) . '</select>';
+        // Table with the search box:
+        $content = '<div class="db_list-searchbox-form db_list-searchbox-toolbar module-docheader-bar module-docheader-bar-search t3js-module-docheader-bar t3js-module-docheader-bar-search" id="db_list-searchbox-toolbar" style="display: ' . ($this->searchString == '' ? 'none' : 'block') . ';">
+                       ' . $formElements[0] . '
+                <div id="typo3-dblist-search">
+                    <div class="panel panel-default">
+                        <div class="panel-body">
+                            <div class="row">
+                                <div class="form-group col-xs-12">
+                                    <label for="search_field">' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.searchString')
+            ) . ': </label>
+                                                                       <input class="form-control" type="search" placeholder="' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.enterSearchString')
+            ) . '" title="' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.searchString')
+            ) . '" name="search_field" id="search_field" value="' . htmlspecialchars($this->searchString) . '" />
+                                </div>
+                                <div class="form-group col-xs-12 col-sm-6">
+                                                                       <label for="search_levels">' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.search_levels')
+            ) . ': </label>
+                                                                       ' . $lMenu . '
+                                </div>
+                                <div class="form-group col-xs-12 col-sm-6">
+                                                                       <label for="showLimit">' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.limit')
+            ) . ': </label>
+                                                                       <input class="form-control" type="number" min="0" max="10000" placeholder="10" title="' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.limit')
+            ) . '" name="showLimit" id="showLimit" value="' . htmlspecialchars(
+                ($this->showLimit ? $this->showLimit : '')
+            ) . '" />
+                                </div>
+                                <div class="form-group col-xs-12">
+                                    <div class="form-control-wrap">
+                                        <button type="submit" class="btn btn-default" name="search" title="' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.search')
+            ) . '">
+                                            ' . $iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render(
+            ) . ' ' . htmlspecialchars(
+                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.search')
+            ) . '
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                       ' . $formElements[1] . '</div>';
+        return $content;
+    }
+
+    /**
+     * Setting the field names to display in extended list.
+     * Sets the internal variable $this->setFields
+     */
+    public function setDispFields()
+    {
+        $backendUser = $this->getBackendUserAuthentication();
+        // Getting from session:
+        $dispFields = $backendUser->getModuleData('list/displayFields');
+        // If fields has been inputted, then set those as the value and push it to session variable:
+        if (is_array($this->displayFields)) {
+            reset($this->displayFields);
+            $tKey = key($this->displayFields);
+            $dispFields[$tKey] = $this->displayFields[$tKey];
+            $backendUser->pushModuleData('list/displayFields', $dispFields);
+        }
+        // Setting result:
+        $this->setFields = $dispFields;
+    }
+
+    /**
+     * Create thumbnail code for record/field
+     *
+     * @param mixed[] $row Record array
+     * @param string $table Table (record is from)
+     * @param string $field Field name for which thumbnail are to be rendered.
+     * @return string HTML for thumbnails, if any.
+     */
+    public function thumbCode($row, $table, $field)
+    {
+        return BackendUtility::thumbCode($row, $table, $field);
+    }
+
+    /**
+     * Returns a QueryBuilder configured to select $fields from $table where the pid is restricted
+     * depending on the current searchlevel setting.
+     *
+     * @param string $table Table name
+     * @param int $pageId Page id Only used to build the search constraints, getPageIdConstraint() used for restrictions
+     * @param string[] $additionalConstraints Additional part for where clause
+     * @param string[] $fields Field list to select, * for all
+     * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
+     */
+    public function getQueryBuilder(
+        string $table,
+        int $pageId,
+        array $additionalConstraints = [],
+        array $fields = ['*']
+    ): QueryBuilder {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($table);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+        $queryBuilder
+            ->select(...$fields)
+            ->from($table);
+
+        if (!empty($additionalConstraints)) {
+            $queryBuilder->andWhere(...$additionalConstraints);
+        }
+
+        $queryBuilder = $this->prepareQueryBuilder($table, $pageId, $fields, $additionalConstraints, $queryBuilder);
+
+        return $queryBuilder;
+    }
+
+    /**
+     * Return the modified QueryBuilder object ($queryBuilder) which will be
+     * used to select the records from a table $table with pid = $this->pidList
+     *
+     * @param string $table Table name
+     * @param int $pageId Page id Only used to build the search constraints, $this->pidList is used for restrictions
+     * @param string[] $fieldList List of fields to select from the table
+     * @param string[] $additionalConstraints Additional part for where clause
+     * @param QueryBuilder $queryBuilder
+     * @paran bool $addSorting
+     * @return QueryBuilder
+     */
+    protected function prepareQueryBuilder(
+        string $table,
+        int $pageId,
+        array $fieldList = ['*'],
+        array $additionalConstraints = [],
+        QueryBuilder $queryBuilder,
+        bool $addSorting = true
+    ): QueryBuilder {
+        $parameters = [
+            'table' => $table,
+            'fields' => $fieldList,
+            'groupBy' => null,
+            'orderBy' => null,
+            'firstResult' => $this->firstElementNumber ?: null,
+            'maxResults' => $this->iLimit ?: null
+        ];
+
+        if ($this->iLimit !== null) {
+            $queryBuilder->setMaxResults($this->iLimit);
+        }
+
+        if ($addSorting) {
+            if ($this->sortField && in_array($this->sortField, $this->makeFieldList($table, 1))) {
+                $queryBuilder->orderBy($this->sortField, $this->sortRev ? 'DESC' : 'ASC');
+            } else {
+                $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?: $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
+                $orderBys = QueryHelper::parseOrderBy((string)$orderBy);
+                foreach ($orderBys as $orderBy) {
+                    $queryBuilder->orderBy($orderBy[0], $orderBy[1]);
+                }
+            }
+        }
+
+        // Build the query constraints
+        $queryBuilder = $this->addPageIdConstraint($table, $queryBuilder);
+        $searchWhere = $this->makeSearchString($table, $pageId);
+        if (!empty($searchWhere)) {
+            $queryBuilder->andWhere($searchWhere);
+        }
+
+        // Filtering on displayable pages (permissions):
+        if ($table === 'pages' && $this->perms_clause) {
+            $queryBuilder->andWhere($this->perms_clause);
+        }
+
+        // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
+        if (
+            $table !== 'pages_language_overlay'
+            && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
+            && (GeneralUtility::inList($this->hideTranslations, $table) || $this->hideTranslations === '*')
+        ) {
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->eq(
+                    $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
+                    0
+                )
+            );
+        }
+
+        $hookName = static::class;
+        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'])) {
+            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'] as $className) {
+                $hookObject = GeneralUtility::makeInstance($className);
+                if (method_exists($hookObject, 'buildQueryParametersPostProcess')) {
+                    $hookObject->buildQueryParametersPostProcess(
+                        $parameters,
+                        $table,
+                        $pageId,
+                        $additionalConstraints,
+                        $fieldList,
+                        $this,
+                        $queryBuilder
+                    );
+                }
+            }
+        }
+
+        // array_unique / array_filter used to eliminate empty and duplicate constraints
+        // the array keys are eliminated by this as well to facilitate argument unpacking
+        // when used with the querybuilder.
+        // @deprecated since TYPO3 v9, will be removed in TYPO3 v10
+        if (!empty($parameters['where'])) {
+            $parameters['where'] = array_unique(array_filter(array_values($parameters['where'])));
+        }
+        if (!empty($parameters['where'])) {
+            $this->logDeprecation('where');
+            $queryBuilder->where(...$parameters['where']);
+        }
+        if (!empty($parameters['orderBy'])) {
+            $this->logDeprecation('orderBy');
+            foreach ($parameters['orderBy'] as $fieldNameAndSorting) {
+                list($fieldName, $sorting) = $fieldNameAndSorting;
+                $queryBuilder->addOrderBy($fieldName, $sorting);
+            }
+        }
+        if (!empty($parameters['firstResult'])) {
+            $this->logDeprecation('firstResult');
+            $queryBuilder->setFirstResult((int)$parameters['firstResult']);
+        }
+        if (!empty($parameters['maxResults']) && $parameters['maxResults'] !== $this->iLimit) {
+            $this->logDeprecation('maxResults');
+            $queryBuilder->setMaxResults((int)$parameters['maxResults']);
+        }
+        if (!empty($parameters['groupBy'])) {
+            $this->logDeprecation('groupBy');
+            $queryBuilder->groupBy($parameters['groupBy']);
+        }
+
+        return $queryBuilder;
+    }
+
+    /**
+     * Executed a query to set $this->totalItems to the number of total
+     * items, eg. for pagination
+     *
+     * @param string $table Table name
+     * @param int $pageId Only used to build the search constraints, $this->pidList is used for restrictions
+     * @param array $constraints Additional constraints for where clause
+     */
+    public function setTotalItems(string $table, int $pageId, array $constraints)
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($table);
+
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+        $queryBuilder
+            ->from($table);
+
+        if (!empty($constraints)) {
+            $queryBuilder->andWhere(...$constraints);
+        }
+
+        $queryBuilder = $this->prepareQueryBuilder($table, $pageId, ['*'], $constraints, $queryBuilder, false);
+
+        $this->totalItems = (int)$queryBuilder->count('*')
+            ->execute()
+            ->fetchColumn();
+    }
+
+    /**
+     * Creates part of query for searching after a word ($this->searchString)
+     * fields in input table.
+     *
+     * @param string $table Table, in which the fields are being searched.
+     * @param int $currentPid Page id for the possible search limit. -1 only if called from an old XCLASS.
+     * @return string Returns part of WHERE-clause for searching, if applicable.
+     */
+    public function makeSearchString($table, $currentPid = -1)
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+        $expressionBuilder = $queryBuilder->expr();
+        $constraints = [];
+        $currentPid = (int)$currentPid;
+        $tablePidField = $table === 'pages' ? 'uid' : 'pid';
+        // Make query, only if table is valid and a search string is actually defined:
+        if (empty($this->searchString)) {
+            return '';
+        }
+
+        $searchableFields = $this->getSearchFields($table);
+        if (empty($searchableFields)) {
+            return '';
+        }
+        if (MathUtility::canBeInterpretedAsInteger($this->searchString)) {
+            $constraints[] = $expressionBuilder->eq('uid', (int)$this->searchString);
+            foreach ($searchableFields as $fieldName) {
+                if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
+                    continue;
+                }
+                $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
+                $fieldType = $fieldConfig['type'];
+                $evalRules = $fieldConfig['eval'] ?: '';
+                if ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int')) {
+                    if (is_array($fieldConfig['search'])
+                        && in_array('pidonly', $fieldConfig['search'], true)
+                        && $currentPid > 0
+                    ) {
+                        $constraints[] = $expressionBuilder->andX(
+                            $expressionBuilder->eq($fieldName, (int)$this->searchString),
+                            $expressionBuilder->eq($tablePidField, (int)$currentPid)
+                        );
+                    }
+                } elseif ($fieldType === 'text'
+                    || $fieldType === 'flex'
+                    || ($fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules)))
+                ) {
+                    $constraints[] = $expressionBuilder->like(
+                        $fieldName,
+                        $queryBuilder->quote('%' . (int)$this->searchString . '%')
+                    );
+                }
+            }
+        } else {
+            $like = $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($this->searchString) . '%');
+            foreach ($searchableFields as $fieldName) {
+                if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
+                    continue;
+                }
+                $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
+                $fieldType = $fieldConfig['type'];
+                $evalRules = $fieldConfig['eval'] ?: '';
+                $searchConstraint = $expressionBuilder->andX(
+                    $expressionBuilder->comparison(
+                        'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
+                        'LIKE',
+                        'LOWER(' . $like . ')'
+                    )
+                );
+                if (is_array($fieldConfig['search'])) {
+                    $searchConfig = $fieldConfig['search'];
+                    if (in_array('case', $searchConfig)) {
+                        // Replace case insensitive default constraint
+                        $searchConstraint = $expressionBuilder->andX($expressionBuilder->like($fieldName, $like));
+                    }
+                    if (in_array('pidonly', $searchConfig) && $currentPid > 0) {
+                        $searchConstraint->add($expressionBuilder->eq($tablePidField, (int)$currentPid));
+                    }
+                    if ($searchConfig['andWhere']) {
+                        $searchConstraint->add(
+                            QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
+                        );
+                    }
+                }
+                if ($fieldType === 'text'
+                    || $fieldType === 'flex'
+                    || $fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules))
+                ) {
+                    if ($searchConstraint->count() !== 0) {
+                        $constraints[] = $searchConstraint;
+                    }
+                }
+            }
+        }
+        // If no search field conditions have been build ensure no results are returned
+        if (empty($constraints)) {
+            return '0=1';
+        }
+
+        return $expressionBuilder->orX(...$constraints);
+    }
+
+    /**
+     * Fetches a list of fields to use in the Backend search for the given table.
+     *
+     * @param string $tableName
+     * @return string[]
+     */
+    protected function getSearchFields($tableName)
+    {
+        $fieldArray = [];
+        $fieldListWasSet = false;
+        // Get fields from ctrl section of TCA first
+        if (isset($GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
+            $fieldArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
+            $fieldListWasSet = true;
+        }
+        // Call hook to add or change the list
+        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'])) {
+            $hookParameters = [
+                'tableHasSearchConfiguration' => $fieldListWasSet,
+                'tableName' => $tableName,
+                'searchFields' => &$fieldArray,
+                'searchString' => $this->searchString
+            ];
+            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'] as $hookFunction) {
+                GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
+            }
+        }
+        return $fieldArray;
+    }
+
+    /**
+     * Returns the title (based on $code) of a table ($table) with the proper link around. For headers over tables.
+     * The link will cause the display of all extended mode or not for the table.
+     *
+     * @param string $table Table name
+     * @param string $code Table label
+     * @return string The linked table label
+     */
+    public function linkWrapTable($table, $code)
+    {
+        if ($this->table !== $table) {
+            return '<a href="' . htmlspecialchars(
+                    $this->listURL('', $table, 'firstElementNumber')
+                ) . '">' . $code . '</a>';
+        }
+        return '<a href="' . htmlspecialchars(
+                $this->listURL('', '', 'sortField,sortRev,table,firstElementNumber')
+            ) . '">' . $code . '</a>';
+    }
+
+    /**
+     * Returns the title (based on $code) of a record (from table $table) with the proper link around (that is for 'pages'-records a link to the level of that record...)
+     *
+     * @param string $table Table name
+     * @param int $uid Item uid
+     * @param string $code Item title (not htmlspecialchars()'ed yet)
+     * @param mixed[] $row Item row
+     * @return string The item title. Ready for HTML output (is htmlspecialchars()'ed)
+     */
+    public function linkWrapItems($table, $uid, $code, $row)
+    {
+        $lang = $this->getLanguageService();
+        $origCode = $code;
+        // If the title is blank, make a "no title" label:
+        if ((string)$code === '') {
+            $code = '<i>[' . htmlspecialchars(
+                    $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.no_title')
+                ) . ']</i> - '
+                . htmlspecialchars(BackendUtility::getRecordTitle($table, $row));
+        } else {
+            $code = htmlspecialchars($code, ENT_QUOTES, 'UTF-8', false);
+            if ($code != htmlspecialchars($origCode)) {
+                $code = '<span title="' . htmlspecialchars(
+                        $origCode,
+                        ENT_QUOTES,
+                        'UTF-8',
+                        false
+                    ) . '">' . $code . '</span>';
+            }
+        }
+        switch ((string)$this->clickTitleMode) {
+            case 'edit':
+                // If the listed table is 'pages' we have to request the permission settings for each page:
+                if ($table === 'pages') {
+                    $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(
+                        BackendUtility::getRecord('pages', $row['uid'])
+                    );
+                    $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
+                } else {
+                    $permsEdit = $this->calcPerms & Permission::CONTENT_EDIT;
+                }
+                // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
+                if ($permsEdit) {
+                    $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
+                    $code = '<a href="#" onclick="' . htmlspecialchars(
+                            BackendUtility::editOnClick($params, '', -1)
+                        ) . '" title="' . htmlspecialchars($lang->getLL('edit')) . '">' . $code . '</a>';
+                }
+                break;
+            case 'show':
+                // "Show" link (only pages and tt_content elements)
+                if ($table === 'pages' || $table === 'tt_content') {
+                    $code = '<a href="#" onclick="' . htmlspecialchars(
+                            BackendUtility::viewOnClick(
+                                ($table === 'tt_content' ? $this->id . '#' . $row['uid'] : $row['uid'])
+                            )
+                        ) . '" title="' . htmlspecialchars(
+                            $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage')
+                        ) . '">' . $code . '</a>';
+                }
+                break;
+            case 'info':
+                // "Info": (All records)
+                $code = '<a href="#" onclick="' . htmlspecialchars(
+                        ('top.launchView(\'' . $table . '\', \'' . $row['uid'] . '\'); return false;')
+                    ) . '" title="' . htmlspecialchars($lang->getLL('showInfo')) . '">' . $code . '</a>';
+                break;
+            default:
+                // Output the label now:
+                if ($table === 'pages') {
+                    $code = '<a href="' . htmlspecialchars(
+                            $this->listURL($uid, '', 'firstElementNumber')
+                        ) . '" onclick="setHighlight(' . $uid . ')">' . $code . '</a>';
+                } else {
+                    $code = $this->linkUrlMail($code, $origCode);
+                }
+        }
+        return $code;
+    }
+
+    /**
+     * Wrapping input code in link to URL or email if $testString is either.
+     *
+     * @param string $code code to wrap
+     * @param string $testString String which is tested for being a URL or email and which will be used for the link if so.
+     * @return string Link-Wrapped $code value, if $testString was URL or email.
+     */
+    public function linkUrlMail($code, $testString)
+    {
+        // Check for URL:
+        $scheme = parse_url($testString, PHP_URL_SCHEME);
+        if ($scheme === 'http' || $scheme === 'https' || $scheme === 'ftp') {
+            return '<a href="' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
+        }
+        // Check for email:
+        if (GeneralUtility::validEmail($testString)) {
+            return '<a href="mailto:' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
+        }
+        // Return if nothing else...
+        return $code;
+    }
+
+    /**
+     * Creates the URL to this script, including all relevant GPvars
+     * Fixed GPvars are id, table, imagemode, returnUrl, search_field, search_levels and showLimit
+     * The GPvars "sortField" and "sortRev" are also included UNLESS they are found in the $exclList variable.
+     *
+     * @param string $altId Alternative id value. Enter blank string for the current id ($this->id)
+     * @param string $table Table name to display. Enter "-1" for the current table.
+     * @param string $exclList Comma separated list of fields NOT to include ("sortField", "sortRev" or "firstElementNumber")
+     * @return string URL
+     */
+    public function listURL($altId = '', $table = '-1', $exclList = '')
+    {
+        $urlParameters = [];
+        if ((string)$altId !== '') {
+            $urlParameters['id'] = $altId;
+        } else {
+            $urlParameters['id'] = $this->id;
+        }
+        if ($table === '-1') {
+            $urlParameters['table'] = $this->table;
+        } else {
+            $urlParameters['table'] = $table;
+        }
+        if ($this->thumbs) {
+            $urlParameters['imagemode'] = $this->thumbs;
+        }
+        if ($this->returnUrl) {
+            $urlParameters['returnUrl'] = $this->returnUrl;
+        }
+        if ((!$exclList || !GeneralUtility::inList($exclList, 'search_field')) && $this->searchString) {
+            $urlParameters['search_field'] = $this->searchString;
+        }
+        if ($this->searchLevels) {
+            $urlParameters['search_levels'] = $this->searchLevels;
+        }
+        if ($this->showLimit) {
+            $urlParameters['showLimit'] = $this->showLimit;
+        }
+        if ((!$exclList || !GeneralUtility::inList($exclList, 'firstElementNumber')) && $this->firstElementNumber) {
+            $urlParameters['pointer'] = $this->firstElementNumber;
+        }
+        if ((!$exclList || !GeneralUtility::inList($exclList, 'sortField')) && $this->sortField) {
+            $urlParameters['sortField'] = $this->sortField;
+        }
+        if ((!$exclList || !GeneralUtility::inList($exclList, 'sortRev')) && $this->sortRev) {
+            $urlParameters['sortRev'] = $this->sortRev;
+        }
+
+        $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters);
+
+        if ($routePath = GeneralUtility::_GP('route')) {
+            $router = GeneralUtility::makeInstance(Router::class);
+            $route = $router->match($routePath);
+            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+            $url = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'), $urlParameters);
+        } elseif ($moduleName = GeneralUtility::_GP('M')) {
+            $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
+        } else {
+            $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim(
+                    GeneralUtility::implodeArrayForUrl('', $urlParameters),
+                    '&'
+                );
+        }
+        return $url;
+    }
+
+    /**
+     * Returns "requestUri" - which is basically listURL
+     * @return string Content of ->listURL()
+     */
+    public function requestUri()
+    {
+        return $this->listURL();
+    }
+
+    /**
+     * Makes the list of fields to select for a table
+     *
+     * @param string $table Table name
+     * @param bool $dontCheckUser If set, users access to the field (non-exclude-fields) is NOT checked.
+     * @param bool $addDateFields If set, also adds crdate and tstamp fields (note: they will also be added if user is admin or dontCheckUser is set)
+     * @return string[] Array, where values are fieldnames to include in query
+     */
+    public function makeFieldList($table, $dontCheckUser = false, $addDateFields = false)
+    {
+        $backendUser = $this->getBackendUserAuthentication();
+        // Init fieldlist array:
+        $fieldListArr = [];
+        // Check table:
+        if (is_array($GLOBALS['TCA'][$table]) && isset($GLOBALS['TCA'][$table]['columns']) && is_array(
+                $GLOBALS['TCA'][$table]['columns']
+            )) {
+            if (isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
+                // Traverse configured columns and add them to field array, if available for user.
+                foreach ($GLOBALS['TCA'][$table]['columns'] as $fN => $fieldValue) {
+                    if ($dontCheckUser || (!$fieldValue['exclude'] || $backendUser->check(
+                                'non_exclude_fields',
+                                $table . ':' . $fN
+                            )) && $fieldValue['config']['type'] !== 'passthrough') {
+                        $fieldListArr[] = $fN;
+                    }
+                }
+
+                $fieldListArr[] = 'uid';
+                $fieldListArr[] = 'pid';
+
+                // Add date fields
+                if ($dontCheckUser || $backendUser->isAdmin() || $addDateFields) {
+                    if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
+                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['tstamp'];
+                    }
+                    if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
+                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['crdate'];
+                    }
+                }
+                // Add more special fields:
+                if ($dontCheckUser || $backendUser->isAdmin()) {
+                    if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
+                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['cruser_id'];
+                    }
+                    if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
+                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
+                    }
+                    if (ExtensionManagementUtility::isLoaded(
+                            'version'
+                        ) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+                        $fieldListArr[] = 't3ver_id';
+                        $fieldListArr[] = 't3ver_state';
+                        $fieldListArr[] = 't3ver_wsid';
+                    }
+                }
+            } else {
+                GeneralUtility::sysLog(
+                    sprintf('$TCA is broken for the table "%s": no required "columns" entry in $TCA.', $table),
+                    'core',
+                    GeneralUtility::SYSLOG_SEVERITY_ERROR
+                );
+            }
+        }
+        return $fieldListArr;
+    }
+
+    /**
+     * Redirects to FormEngine if a record is just localized.
+     *
+     * @param string $justLocalized String with table, orig uid and language separated by ":
+     */
+    public function localizationRedirect($justLocalized)
+    {
+        list($table, $orig_uid, $language) = explode(':', $justLocalized);
+        if ($GLOBALS['TCA'][$table]
+            && $GLOBALS['TCA'][$table]['ctrl']['languageField']
+            && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
+        ) {
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+            $localizedRecordUid = $queryBuilder->select('uid')
+                ->from($table)
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        $GLOBALS['TCA'][$table]['ctrl']['languageField'],
+                        $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
+                    ),
+                    $queryBuilder->expr()->eq(
+                        $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
+                        $queryBuilder->createNamedParameter($orig_uid, \PDO::PARAM_INT)
+                    )
+                )
+                ->setMaxResults(1)
+                ->execute()
+                ->fetchColumn();
+
+            if ($localizedRecordUid !== false) {
+                // Create parameters and finally run the classic page module for creating a new page translation
+                $url = $this->listURL();
+                $editUserAccountUrl = BackendUtility::getModuleUrl(
+                    'record_edit',
+                    [
+                        'edit[' . $table . '][' . $localizedRecordUid . ']' => 'edit',
+                        'returnUrl' => $url
+                    ]
+                );
+                HttpUtility::redirect($editUserAccountUrl);
+            }
+        }
+    }
+
+    /**
+     * Set URL parameters to override or add in the listUrl() method.
+     *
+     * @param string[] $urlParameters
+     */
+    public function setOverrideUrlParameters(array $urlParameters)
+    {
+        $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.',
+                        1504793406
+                    );
+                }
+            }
+            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.',
+                        1504793407
+                    );
+                }
+            }
+        }
+        $this->tableDisplayOrder = $orderInformation;
     }
 
     /**
-     * Returns TRUE if a numeric clipboard pad is selected/active
-     *
-     * @return bool
+     * @return array
      */
-    public function clipNumPane()
+    public function getOverridePageIdList(): array
     {
-        return in_array('_CLIPBOARD_', $this->fieldArray) && $this->clipObj->current !== 'normal';
+        return $this->overridePageIdList;
     }
 
     /**
-     * Creates a sort-by link on the input string ($code).
-     * It will automatically detect if sorting should be ascending or descending depending on $this->sortRev.
-     * Also some fields will not be possible to sort (including if single-table-view is disabled).
-     *
-     * @param string $code The string to link (text)
-     * @param string $field The fieldname represented by the title ($code)
-     * @param string $table Table name
-     * @return string Linked $code variable
+     * @param int[]|array $overridePageIdList
      */
-    public function addSortLink($code, $field, $table)
+    public function setOverridePageIdList(array $overridePageIdList)
     {
-        // Certain circumstances just return string right away (no links):
-        if ($field === '_CONTROL_' || $field === '_LOCALIZATION_' || $field === '_CLIPBOARD_' || $field === '_REF_' || $this->disableSingleTableView) {
-            return $code;
-        }
-        // If "_PATH_" (showing record path) is selected, force sorting by pid field (will at least group the records!)
-        if ($field === '_PATH_') {
-            $field = 'pid';
-        }
-        //      Create the sort link:
-        $sortUrl = $this->listURL('', '-1', 'sortField,sortRev,table,firstElementNumber') . '&table=' . $table
-            . '&sortField=' . $field . '&sortRev=' . ($this->sortRev || $this->sortField != $field ? 0 : 1);
-        $sortArrow = $this->sortField === $field
-            ? $this->iconFactory->getIcon('status-status-sorting-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render()
-            : '';
-        // Return linked field:
-        return '<a href="' . htmlspecialchars($sortUrl) . '">' . $code . $sortArrow . '</a>';
+        $this->overridePageIdList = array_map('intval', $overridePageIdList);
     }
 
     /**
-     * Returns the path for a certain pid
-     * The result is cached internally for the session, thus you can call
-     * this function as much as you like without performance problems.
+     * Get all allowed mount pages to be searched in.
      *
-     * @param int $pid The page id for which to get the path
-     * @return mixed[] The path.
+     * @param int $id Page id
+     * @param int $depth Depth to go down
+     * @param string $perms_clause select clause
+     * @return int[]
      */
-    public function recPath($pid)
+    protected function getSearchableWebmounts($id, $depth, $perms_clause)
     {
-        if (!isset($this->recPath_cache[$pid])) {
-            $this->recPath_cache[$pid] = BackendUtility::getRecordPath($pid, $this->perms_clause, 20);
+        $backendUser = $this->getBackendUserAuthentication();
+        /** @var PageTreeView $tree */
+        $tree = GeneralUtility::makeInstance(PageTreeView::class);
+        $tree->init('AND ' . $perms_clause);
+        $tree->makeHTML = 0;
+        $tree->fieldArray = ['uid', 'php_tree_stop'];
+        $idList = [];
+
+        $allowedMounts = !$backendUser->isAdmin() && $id === 0
+            ? $backendUser->returnWebmounts()
+            : [$id];
+
+        foreach ($allowedMounts as $allowedMount) {
+            $idList[] = $allowedMount;
+            if ($depth) {
+                $tree->getTree($allowedMount, $depth, '');
+            }
+            $idList = array_merge($idList, $tree->ids);
         }
-        return $this->recPath_cache[$pid];
+
+        return $idList;
     }
 
     /**
-     * Returns TRUE if a link for creating new records should be displayed for $table
+     * Add conditions to the QueryBuilder object ($queryBuilder) to limit a
+     * query to a list of page IDs based on the current search level setting.
      *
-     * @param string $table Table name
-     * @return bool Returns TRUE if a link for creating new records should be displayed for $table
-     * @see \TYPO3\CMS\Backend\Controller\NewRecordController::showNewRecLink
+     * @param string $tableName
+     * @param QueryBuilder $queryBuilder
+     * @return QueryBuilder Modified QueryBuilder object
      */
-    public function showNewRecLink($table)
+    protected function addPageIdConstraint(string $tableName, QueryBuilder $queryBuilder): QueryBuilder
     {
-        // No deny/allow tables are set:
-        if (empty($this->allowedNewTables) && empty($this->deniedNewTables)) {
-            return true;
+        // Set search levels:
+        $searchLevels = $this->searchLevels;
+
+        // Set search levels to 999 instead of -1 as the following methods
+        // do not support -1 as valid value for infinite search.
+        if ($searchLevels === -1) {
+            $searchLevels = 999;
         }
-        return !in_array($table, $this->deniedNewTables)
-            && (empty($this->allowedNewTables) || in_array($table, $this->allowedNewTables));
+
+        if ($searchLevels === 0) {
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->eq(
+                    $tableName . '.pid',
+                    $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
+                )
+            );
+        } elseif ($searchLevels > 0) {
+            $allowedMounts = $this->getSearchableWebmounts($this->id, $searchLevels, $this->perms_clause);
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->in(
+                    $tableName . '.pid',
+                    $queryBuilder->createNamedParameter($allowedMounts, Connection::PARAM_INT_ARRAY)
+                )
+            );
+        }
+
+        if (!empty($this->getOverridePageIdList())) {
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->in(
+                    $tableName . '.pid',
+                    $queryBuilder->createNamedParameter($this->getOverridePageIdList(), Connection::PARAM_INT_ARRAY)
+                )
+            );
+        }
+
+        return $queryBuilder;
     }
 
     /**
-     * Creates the "&returnUrl" parameter for links - this is used when the script links
-     * to other scripts and passes its own URL with the link so other scripts can return to the listing again.
-     * Uses REQUEST_URI as value.
+     * Method used to log deprecated usage of old buildQueryParametersPostProcess hook arguments
      *
-     * @return string
+     * @param string $index
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10 - see method usages
      */
-    public function makeReturnUrl()
+    protected function logDeprecation(string $index)
     {
-        return '&returnUrl=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'));
+        GeneralUtility::deprecationLog(
+            '[index: ' . $index . '] $parameters in "buildQueryParameters"-Hook has been deprecated in v9 and will be remove in v10, use $queryBuilder instead'
+        );
     }
 
-    /************************************
-     *
-     * CSV related functions
-     *
-     ************************************/
     /**
-     * Initializes internal csvLines array with the header of field names
+     * @return BackendUserAuthentication
      */
-    protected function initCSV()
+    protected function getBackendUserAuthentication()
     {
-        $this->addHeaderRowToCSV();
+        return $GLOBALS['BE_USER'];
     }
 
     /**
-     * Add header line with field names as CSV line
+     * Returns a table-row with the content from the fields in the input data array.
+     * OBS: $this->fieldArray MUST be set! (represents the list of fields to display)
+     *
+     * @param int $h Is an integer >=0 and denotes how tall an element is. Set to '0' makes a halv line, -1 = full line, set to 1 makes a 'join' and above makes 'line'
+     * @param string $icon Is the <img>+<a> of the record. If not supplied the first 'join'-icon will be a 'line' instead
+     * @param array $data Is the dataarray, record with the fields. Notice: These fields are (currently) NOT htmlspecialchar'ed before being wrapped in <td>-tags
+     * @param string $rowParams Is insert in the <tr>-tags. Must carry a ' ' as first character
+     * @param string $_ OBSOLETE - NOT USED ANYMORE. $lMargin is the leftMargin (int)
+     * @param string $_2 OBSOLETE - NOT USED ANYMORE. Is the HTML <img>-tag for an alternative 'gfx/ol/line.gif'-icon (used in the top)
+     * @param string $colType Defines the tag being used for the columns. Default is td.
+     *
+     * @return string HTML content for the table row
      */
-    protected function addHeaderRowToCSV()
+    public function addElement($h, $icon, $data, $rowParams = '', $_ = '', $_2 = '', $colType = 'td')
     {
-        // Add header row, control fields will be reduced inside addToCSV()
-        $this->addToCSV(array_combine($this->fieldArray, $this->fieldArray));
+        $colType = ($colType === 'th') ? 'th' : 'td';
+        $noWrap = $this->no_noWrap ? '' : ' nowrap';
+        // Start up:
+        $l10nParent = isset($data['_l10nparent_']) ? (int)$data['_l10nparent_'] : 0;
+        $out = '
+               <!-- Element, begin: -->
+               <tr ' . $rowParams . ' data-uid="' . (int)$data['uid'] . '" data-l10nparent="' . $l10nParent . '">';
+        // Show icon and lines
+        if ($this->showIcon) {
+            $out .= '
+                       <' . $colType . ' class="col-icon nowrap">';
+            if (!$h) {
+                $out .= '&nbsp;';
+            } else {
+                for ($a = 0; $a < $h; $a++) {
+                    if (!$a) {
+                        if ($icon) {
+                            $out .= $icon;
+                        }
+                    }
+                }
+            }
+            $out .= '</' . $colType . '>
+                       ';
+        }
+        // Init rendering.
+        $colsp = '';
+        $lastKey = '';
+        $c = 0;
+        $ccount = 0;
+        // __label is used as the label key to circumvent problems with uid used as label (see #67756)
+        // as it was introduced later on, check if it really exists before using it
+        $fields = $this->fieldArray;
+        if ($colType === 'td' && array_key_exists('__label', $data)) {
+            $fields[0] = '__label';
+        }
+        // Traverse field array which contains the data to present:
+        foreach ($fields as $vKey) {
+            if (isset($data[$vKey])) {
+                if ($lastKey) {
+                    $cssClass = $this->addElement_tdCssClass[$lastKey];
+                    if ($this->oddColumnsCssClass && $ccount % 2 == 0) {
+                        $cssClass = implode(' ', [$this->addElement_tdCssClass[$lastKey], $this->oddColumnsCssClass]);
+                    }
+                    $out .= '
+                                               <' . $colType . ' class="' . $cssClass . $noWrap . '"' . $colsp . $this->addElement_tdParams[$lastKey] . '>' . $data[$lastKey] . '</' . $colType . '>';
+                }
+                $lastKey = $vKey;
+                $c = 1;
+                $ccount++;
+            } else {
+                if (!$lastKey) {
+                    $lastKey = $vKey;
+                }
+                $c++;
+            }
+            if ($c > 1) {
+                $colsp = ' colspan="' . $c . '"';
+            } else {
+                $colsp = '';
+            }
+        }
+        if ($lastKey) {
+            $cssClass = $this->addElement_tdCssClass[$lastKey];
+            if ($this->oddColumnsCssClass) {
+                $cssClass = implode(' ', [$this->addElement_tdCssClass[$lastKey], $this->oddColumnsCssClass]);
+            }
+            $out .= '
+                               <' . $colType . ' class="' . $cssClass . $noWrap . '"' . $colsp . $this->addElement_tdParams[$lastKey] . '>' . $data[$lastKey] . '</' . $colType . '>';
+        }
+        // End row
+        $out .= '
+               </tr>';
+        // Return row.
+        return $out;
     }
 
     /**
-     * Adds selected columns of one table row as CSV line.
-     *
-     * @param mixed[] $row Record array, from which the values of fields found in $this->fieldArray will be listed in the CSV output.
+     * Dummy function, used to write the top of a table listing.
      */
-    protected function addToCSV(array $row = [])
+    public function writeTop()
     {
-        $rowReducedByControlFields = self::removeControlFieldsFromFieldRow($row);
-        // Get an field array without control fields but in the expected order
-        $fieldArray = array_intersect_key(array_flip($this->fieldArray), $rowReducedByControlFields);
-        // Overwrite fieldArray to keep the order with an array of needed fields
-        $rowReducedToSelectedColumns = array_replace($fieldArray, array_intersect_key($rowReducedByControlFields, $fieldArray));
-        $this->setCsvRow($rowReducedToSelectedColumns);
     }
 
     /**
-     * Remove control fields from row for CSV export
+     * Creates a forward/reverse button based on the status of ->eCounter, ->firstElementNumber, ->iLimit
      *
-     * @param mixed[] $row fieldNames => fieldValues
-     * @return mixed[] Input array reduces by control fields
+     * @param string $table Table name
+     * @return array array([boolean], [HTML]) where [boolean] is 1 for reverse element, [HTML] is the table-row code for the element
      */
-    protected static function removeControlFieldsFromFieldRow(array $row = [])
+    public function fwd_rwd_nav($table = '')
     {
-        // Possible control fields in a list row
-        $controlFields = [
-            '_PATH_',
-            '_REF_',
-            '_CONTROL_',
-            '_CLIPBOARD_',
-            '_LOCALIZATION_',
-            '_LOCALIZATION_b'
-        ];
-        return array_diff_key($row, array_flip($controlFields));
+        $code = '';
+        if ($this->eCounter >= $this->firstElementNumber && $this->eCounter < $this->firstElementNumber + $this->iLimit) {
+            if ($this->firstElementNumber && $this->eCounter == $this->firstElementNumber) {
+                //     Reverse
+                $theData = [];
+                $titleCol = $this->fieldArray[0];
+                $theData[$titleCol] = $this->fwd_rwd_HTML('fwd', $this->eCounter, $table);
+                $code = $this->addElement(1, '', $theData, 'class="fwd_rwd_nav"');
+            }
+            return [1, $code];
+        }
+        if ($this->eCounter == $this->firstElementNumber + $this->iLimit) {
+            //         Forward
+            $theData = [];
+            $titleCol = $this->fieldArray[0];
+            $theData[$titleCol] = $this->fwd_rwd_HTML('rwd', $this->eCounter, $table);
+            $code = $this->addElement(1, '', $theData, 'class="fwd_rwd_nav"');
+        }
+        return [0, $code];
     }
 
     /**
-     * Adds input row of values to the internal csvLines array as a CSV formatted line
+     * Creates the button with link to either forward or reverse
      *
-     * @param mixed[] $csvRow Array with values to be listed.
+     * @param string $type Type: "fwd" or "rwd
+     * @param int $pointer Pointer
+     * @param string $table Table name
+     * @return string
+     * @access private
      */
-    public function setCsvRow($csvRow)
+    public function fwd_rwd_HTML($type, $pointer, $table = '')
     {
-        $this->csvLines[] = CsvUtility::csvValues($csvRow);
+        $content = '';
+        $tParam = $table ? '&table=' . rawurlencode($table) : '';
+        switch ($type) {
+            case 'fwd':
+                $href = $this->listURL() . '&pointer=' . ($pointer - $this->iLimit) . $tParam;
+                $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
+                        'actions-move-up',
+                        Icon::SIZE_SMALL
+                    )->render() . '</a> <i>[1 - ' . $pointer . ']</i>';
+                break;
+            case 'rwd':
+                $href = $this->listURL() . '&pointer=' . $pointer . $tParam;
+                $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
+                        'actions-move-down',
+                        Icon::SIZE_SMALL
+                    )->render() . '</a> <i>[' . ($pointer + 1) . ' - ' . $this->totalItems . ']</i>';
+                break;
+        }
+        return $content;
     }
 
     /**
-     * Compiles the internal csvLines array to a csv-string and outputs it to the browser.
-     * This function exits!
+     * Returning JavaScript for ClipBoard functionality.
      *
-     * @param string $prefix Filename prefix:
+     * @return string
      */
-    public function outputCSV($prefix)
+    public function CBfunctions()
     {
-        // Setting filename:
-        $filename = $prefix . '_' . date('dmy-Hi') . '.csv';
-        // Creating output header:
-        header('Content-Type: application/octet-stream');
-        header('Content-Disposition: attachment; filename=' . $filename);
-        // Cache-Control header is needed here to solve an issue with browser IE and
-        // versions lower than 9. See for more information: http://support.microsoft.com/kb/323308
-        header("Cache-Control: ''");
-        // Printing the content of the CSV lines:
-        echo implode(CRLF, $this->csvLines);
-        // Exits:
-        die;
+        return '
+               // checkOffCB()
+       function checkOffCB(listOfCBnames, link) {      //
+               var checkBoxes, flag, i;
+               var checkBoxes = listOfCBnames.split(",");
+               if (link.rel === "") {
+                       link.rel = "allChecked";
+                       flag = true;
+               } else {
+                       link.rel = "";
+                       flag = false;
+               }
+               for (i = 0; i < checkBoxes.length; i++) {
+                       setcbValue(checkBoxes[i], flag);
+               }
+       }
+               // cbValue()
+       function cbValue(CBname) {      //
+               var CBfullName = "CBC["+CBname+"]";
+               return (document.dblistForm[CBfullName] && document.dblistForm[CBfullName].checked ? 1 : 0);
+       }
+               // setcbValue()
+       function setcbValue(CBname,flag) {      //
+               CBfullName = "CBC["+CBname+"]";
+               if(document.dblistForm[CBfullName]) {
+                       document.dblistForm[CBfullName].checked = flag ? "on" : 0;
+               }
+       }
+
+               ';
     }
 
     /**
-     * add action into correct section
-     *
-     * @param array $cells
-     * @param string $action
-     * @param string $actionKey
+     * Initializes page languages and icons
      */
-    public function addActionToCellGroup(&$cells, $action, $actionKey)
+    public function initializeLanguages()
     {
-        $cellsMap = [
-            'primary' => [
-                'view', 'edit', 'hide', 'delete', 'stat'
-            ],
-            'secondary' => [
-                'viewBig', 'history', 'perms', 'new', 'move', 'moveUp', 'moveDown', 'moveLeft', 'moveRight', 'version'
-            ]
-        ];
-        $classification = in_array($actionKey, $cellsMap['primary']) ? 'primary' : 'secondary';
-        $cells[$classification][$actionKey] = $action;
-        unset($cells[$actionKey]);
+        // Look up page overlays:
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('pages_language_overlay');
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+        $result = $queryBuilder
+            ->select('*')
+            ->from('pages_language_overlay')
+            ->where(
+                $queryBuilder->expr()->andX(
+                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)),
+                    $queryBuilder->expr()->gt(
+                        'sys_language_uid',
+                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    )
+                )
+            )
+            ->execute();
+
+        $this->pageOverlays = [];
+        while ($row = $result->fetch()) {
+            $this->pageOverlays[$row['sys_language_uid']] = $row;
+        }
+
+        $this->languageIconTitles = $this->getTranslateTools()->getSystemLanguages($this->id);
     }
 
     /**
-     * Check if the record represents the current backend user
+     * Return the icon for the language
      *
-     * @param string $table
-     * @param array $row
-     * @return bool
+     * @param int $sys_language_uid Sys language uid
+     * @param bool $addAsAdditionalText If set to true, only the flag is returned
+     * @return string Language icon
      */
-    protected function isRecordCurrentBackendUser($table, $row)
+    public function languageFlag($sys_language_uid, $addAsAdditionalText = true)
     {
-        return $table === 'be_users' && (int)$row['uid'] === $this->getBackendUserAuthentication()->user['uid'];
+        $out = '';
+        $title = htmlspecialchars($this->languageIconTitles[$sys_language_uid]['title']);
+        if ($this->languageIconTitles[$sys_language_uid]['flagIcon']) {
+            $out .= '<span title="' . $title . '">' . $this->iconFactory->getIcon(
+                    $this->languageIconTitles[$sys_language_uid]['flagIcon'],
+                    Icon::SIZE_SMALL
+                )->render() . '</span>';
+            if (!$addAsAdditionalText) {
+                return $out;
+            }
+            $out .= '&nbsp;';
+        }
+        $out .= $title;
+        return $out;
     }
 
     /**
-     * @param bool $isEditable
+     * Sets the script url depending on being a module or script request
      */
-    public function setIsEditable($isEditable)
+    protected function determineScriptUrl()
     {
-        $this->editable = $isEditable;
+        if ($routePath = GeneralUtility::_GP('route')) {
+            $router = GeneralUtility::makeInstance(Router::class);
+            $route = $router->match($routePath);
+            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+            $this->thisScript = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'));
+        } elseif ($moduleName = GeneralUtility::_GP('M')) {
+            $this->thisScript = BackendUtility::getModuleUrl($moduleName);
+        } else {
+            $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
+        }
     }
 
     /**
-     * Check if the table is readonly or editable
-     * @param string $table
-     * @return bool
+     * @return string
      */
-    public function isEditable($table)
+    protected function getThisScript()
     {
-        return $GLOBALS['TCA'][$table]['ctrl']['readOnly'] || $this->editable;
+        return strpos($this->thisScript, '?') === false ? $this->thisScript . '?' : $this->thisScript . '&';
     }
 
     /**
-     * Check if the current record is locked by editlock. Pages are locked if their editlock flag is set,
-     * records are if they are locked themselves or if the page they are on is locked (a page’s editlock
-     * is transitive for its content elements).
+     * Gets an instance of TranslationConfigurationProvider
      *
-     * @param string $table
-     * @param array $row
-     * @param bool $editPermission
-     * @return bool
+     * @return TranslationConfigurationProvider
      */
-    protected function overlayEditLockPermissions($table, $row = [], $editPermission = true)
+    protected function getTranslateTools()
     {
-        if ($editPermission && !$this->getBackendUserAuthentication()->isAdmin()) {
-            // If no $row is submitted we only check for general edit lock of current page (except for table "pages")
-            if (empty($row)) {
-                return $table === 'pages' ? true : !$this->pageRow['editlock'];
-            }
-            if (($table === 'pages' && $row['editlock']) || ($table !== 'pages' && $this->pageRow['editlock'])) {
-                $editPermission = false;
-            } elseif (isset($GLOBALS['TCA'][$table]['ctrl']['editlock']) && $row[$GLOBALS['TCA'][$table]['ctrl']['editlock']]) {
-                $editPermission = false;
-            }
+        if (!isset($this->translateTools)) {
+            $this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
         }
-        return $editPermission;
+        return $this->translateTools;
     }
 
     /**
-     * Check whether or not the current backend user is an admin or the current page is
-     * locked by editlock.
+     * Generates HTML code for a Reference tooltip out of
+     * sys_refindex records you hand over
      *
-     * @return bool
-     */
-    protected function editLockPermissions()
-    {
-        return $this->getBackendUserAuthentication()->isAdmin() || !$this->pageRow['editlock'];
-    }
-
-    /**
-     * @return BaseScriptClass
+     * @param int $references number of records from sys_refindex table
+     * @param string $launchViewParameter JavaScript String, which will be passed as parameters to top.launchView
+     * @return string
      */
-    protected function getModule()
+    protected function generateReferenceToolTip($references, $launchViewParameter = '')
     {
-        return $GLOBALS['SOBE'];
+        if (!$references) {
+            $htmlCode = '-';
+        } else {
+            $htmlCode = '<a href="#"';
+            if ($launchViewParameter !== '') {
+                $htmlCode .= ' onclick="' . htmlspecialchars(
+                        ('top.launchView(' . $launchViewParameter . '); return false;')
+                    ) . '"';
+            }
+            $htmlCode .= ' title="' . htmlspecialchars(
+                    $this->getLanguageService()->sL(
+                        'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:show_references'
+                    ) . ' (' . $references . ')'
+                ) . '">';
+            $htmlCode .= $references;
+            $htmlCode .= '</a>';
+        }
+        return $htmlCode;
     }
 
     /**
-     * @return DocumentTemplate
+     * Returns the language service
+     * @return LanguageService
      */
-    protected function getDocumentTemplate()
+    protected function getLanguageService()
     {
-        return $GLOBALS['TBE_TEMPLATE'];
+        return $GLOBALS['LANG'];
     }
 }