1<?php
2namespace TYPO3\CMS\Recordlist\Controller;
3
4/*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17use Psr\Http\Message\ResponseInterface;
18use Psr\Http\Message\ServerRequestInterface;
19use TYPO3\CMS\Backend\Clipboard\Clipboard;
20use TYPO3\CMS\Backend\Routing\UriBuilder;
21use TYPO3\CMS\Backend\Template\Components\ButtonBar;
22use TYPO3\CMS\Backend\Template\DocumentTemplate;
23use TYPO3\CMS\Backend\Template\ModuleTemplate;
24use TYPO3\CMS\Backend\Utility\BackendUtility;
25use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
26use TYPO3\CMS\Core\Compatibility\PublicMethodDeprecationTrait;
27use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
28use TYPO3\CMS\Core\Database\ConnectionPool;
29use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
30use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
31use TYPO3\CMS\Core\DataHandling\DataHandler;
32use TYPO3\CMS\Core\Http\HtmlResponse;
33use TYPO3\CMS\Core\Imaging\Icon;
34use TYPO3\CMS\Core\Imaging\IconFactory;
35use TYPO3\CMS\Core\Localization\LanguageService;
36use TYPO3\CMS\Core\Messaging\FlashMessage;
37use TYPO3\CMS\Core\Messaging\FlashMessageService;
38use TYPO3\CMS\Core\Page\PageRenderer;
39use TYPO3\CMS\Core\Site\Entity\SiteInterface;
40use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
41use TYPO3\CMS\Core\Type\Bitmask\Permission;
42use TYPO3\CMS\Core\TypoScript\TypoScriptService;
43use TYPO3\CMS\Core\Utility\GeneralUtility;
44use TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList;
45
46/**
47 * Script Class for the Web > List module; rendering the listing of records on a page
48 * @internal This class is a specific Backend controller implementation and is not part of the TYPO3's Core API.
49 */
50class RecordListController
51{
52    use PublicPropertyDeprecationTrait;
53    use PublicMethodDeprecationTrait;
54
55    /**
56     * @var array
57     */
58    private $deprecatedPublicProperties = [
59        'id' => 'Using RecordListController::$id is deprecated and will not be possible anymore in TYPO3 v10.0.',
60        'pointer' => 'Using RecordListController::$pointer is deprecated and will not be possible anymore in TYPO3 v10.0.',
61        'table' => 'Using RecordListController::$table is deprecated and will not be possible anymore in TYPO3 v10.0.',
62        'search_field' => 'Using RecordListController::$search_field is deprecated and will not be possible anymore in TYPO3 v10.0.',
63        'search_levels' => 'Using RecordListController::$search_levels is deprecated and will not be possible anymore in TYPO3 v10.0.',
64        'showLimit' => 'Using RecordListController::$showLimit is deprecated and will not be possible anymore in TYPO3 v10.0.',
65        'returnUrl' => 'Using RecordListController::$returnUrl is deprecated and will not be possible anymore in TYPO3 v10.0.',
66        'clear_cache' => 'Using RecordListController::$clear_cache is deprecated and will not be possible anymore in TYPO3 v10.0.',
67        'cmd' => 'Using RecordListController::$cmd is deprecated and will not be possible anymore in TYPO3 v10.0.',
68        'cmd_table' => 'Using RecordListController::$cmd_table is deprecated and will not be possible anymore in TYPO3 v10.0.',
69        'perms_clause' => 'Using RecordListController::$perms_clause is deprecated and will not be possible anymore in TYPO3 v10.0.',
70        'pageinfo' => 'Using RecordListController::$pageinfo is deprecated and will not be possible anymore in TYPO3 v10.0.',
71        'MOD_MENU' => 'Using RecordListController::$MOD_MENU is deprecated and will not be possible anymore in TYPO3 v10.0.',
72        'content' => 'Using RecordListController::$content is deprecated and will not be possible anymore in TYPO3 v10.0.',
73        'body' => 'Using RecordListController::$body is deprecated and will not be possible anymore in TYPO3 v10.0.',
74        'imagemode' => 'Using RecordListController::$imagemode is deprecated, property will be removed in TYPO3 v10.0.',
75        'doc' => 'Using RecordListController::$doc is deprecated, property will be removed in TYPO3 v10.0.',
76    ];
77
78    /**
79     * @var array
80     */
81    private $deprecatedPublicMethods = [
82        'init' => 'Using RecordListController::init() is deprecated and will not be possible anymore in TYPO3 v10.0.',
83        'menuConfig' => 'Using RecordListController::menuConfig() is deprecated and will not be possible anymore in TYPO3 v10.0.',
84        'clearCache' => 'Using RecordListController::clearCache() is deprecated and will not be possible anymore in TYPO3 v10.0.',
85        'main' => 'Using RecordListController::main() is deprecated and will not be possible anymore in TYPO3 v10.0.',
86        'getModuleTemplate' => 'Using RecordListController::getModuleTemplate() is deprecated and will not be possible anymore in TYPO3 v10.0.',
87    ];
88
89    /**
90     * Page Id for which to make the listing
91     *
92     * @var int
93     */
94    protected $id;
95
96    /**
97     * Pointer - for browsing list of records.
98     *
99     * @var int
100     */
101    protected $pointer;
102
103    /**
104     * Thumbnails or not
105     *
106     * @var string
107     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
108     */
109    protected $imagemode;
110
111    /**
112     * Which table to make extended listing for
113     *
114     * @var string
115     */
116    protected $table;
117
118    /**
119     * Search-fields
120     *
121     * @var string
122     */
123    protected $search_field;
124
125    /**
126     * Search-levels
127     *
128     * @var int
129     */
130    protected $search_levels;
131
132    /**
133     * Show-limit
134     *
135     * @var int
136     */
137    protected $showLimit;
138
139    /**
140     * Return URL
141     *
142     * @var string
143     */
144    protected $returnUrl;
145
146    /**
147     * Clear-cache flag - if set, clears page cache for current id.
148     *
149     * @var bool
150     */
151    protected $clear_cache;
152
153    /**
154     * Command: Eg. "delete" or "setCB" (for DataHandler / clipboard operations)
155     *
156     * @var string
157     */
158    protected $cmd;
159
160    /**
161     * Table on which the cmd-action is performed.
162     *
163     * @var string
164     */
165    protected $cmd_table;
166
167    /**
168     * Page select perms clause
169     *
170     * @var int
171     */
172    protected $perms_clause;
173
174    /**
175     * Module TSconfig
176     *
177     * @var array
178     * @internal Still used by DatabaseRecordList via $GLOBALS['SOBE']
179     */
180    public $modTSconfig;
181
182    /**
183     * Current ids page record
184     *
185     * @var mixed[]|bool
186     */
187    protected $pageinfo;
188
189    /**
190     * Document template object
191     *
192     * @var DocumentTemplate
193     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
194     */
195    protected $doc;
196
197    /**
198     * Menu configuration
199     *
200     * @var string[]
201     */
202    protected $MOD_MENU = [];
203
204    /**
205     * Module settings (session variable)
206     *
207     * @var string[]
208     * @internal Still used by DatabaseRecordList via $GLOBALS['SOBE']
209     */
210    public $MOD_SETTINGS = [];
211
212    /**
213     * Module output accumulation
214     *
215     * @var string
216     */
217    protected $content;
218
219    /**
220     * @var string
221     */
222    protected $body = '';
223
224    /**
225     * @var PageRenderer
226     */
227    protected $pageRenderer;
228
229    /**
230     * @var IconFactory
231     */
232    protected $iconFactory;
233
234    /**
235     * ModuleTemplate object
236     *
237     * @var ModuleTemplate
238     */
239    protected $moduleTemplate;
240
241    /**
242     * @var SiteInterface
243     */
244    protected $site;
245
246    /**
247     * @var SiteLanguage[]
248     */
249    protected $siteLanguages = [];
250
251    /**
252     * Constructor
253     */
254    public function __construct()
255    {
256        $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
257        $this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf');
258        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Recordlist/FieldSelectBox');
259        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Recordlist/Recordlist');
260    }
261
262    /**
263     * Initializing the module
264     */
265    protected function init()
266    {
267        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
268        $backendUser = $this->getBackendUserAuthentication();
269        $this->perms_clause = $backendUser->getPagePermsClause(Permission::PAGE_SHOW);
270        // Get session data
271        $sessionData = $backendUser->getSessionData(__CLASS__);
272        $this->search_field = !empty($sessionData['search_field']) ? $sessionData['search_field'] : '';
273        // GPvars:
274        $this->id = (int)GeneralUtility::_GP('id');
275        $this->pointer = GeneralUtility::_GP('pointer');
276        // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
277        $this->imagemode = GeneralUtility::_GP('imagemode');
278        $this->table = GeneralUtility::_GP('table');
279        $this->search_field = GeneralUtility::_GP('search_field');
280        $this->search_levels = (int)GeneralUtility::_GP('search_levels');
281        $this->showLimit = GeneralUtility::_GP('showLimit');
282        $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
283        $this->clear_cache = GeneralUtility::_GP('clear_cache');
284        $this->cmd = GeneralUtility::_GP('cmd');
285        $this->cmd_table = GeneralUtility::_GP('cmd_table');
286        $sessionData['search_field'] = $this->search_field;
287        // Initialize menu
288        $this->menuConfig();
289        // Store session data
290        $backendUser->setAndSaveSessionData(self::class, $sessionData);
291        $this->getPageRenderer()->addInlineLanguageLabelFile('EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf');
292    }
293
294    /**
295     * Initialize function menu array
296     */
297    protected function menuConfig()
298    {
299        // MENU-ITEMS:
300        $this->MOD_MENU = [
301            'bigControlPanel' => '',
302            'clipBoard' => '',
303        ];
304        // Loading module configuration:
305        $this->modTSconfig['properties'] = BackendUtility::getPagesTSconfig($this->id)['mod.']['web_list.'] ?? [];
306        // Clean up settings:
307        $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), 'web_list');
308    }
309
310    /**
311     * Clears page cache for the current id, $this->id
312     */
313    protected function clearCache()
314    {
315        if ($this->clear_cache) {
316            $tce = GeneralUtility::makeInstance(DataHandler::class);
317            $tce->start([], []);
318            $tce->clear_cacheCmd($this->id);
319        }
320    }
321
322    /**
323     * Main function, starting the rendering of the list.
324     *
325     * @param ServerRequestInterface $request
326     */
327    protected function main(ServerRequestInterface $request = null)
328    {
329        if ($request === null) {
330            // Missing argument? This method must have been called from outside.
331            // Method will be protected and $request mandatory in TYPO3 v10.0, giving core freedom to move stuff around
332            // New v10 signature: "protected function main(ServerRequestInterface $request)"
333            // @deprecated since TYPO3 v9, method argument $request will be set to mandatory
334            $request = $GLOBALS['TYPO3_REQUEST'];
335        }
336
337        $backendUser = $this->getBackendUserAuthentication();
338        $lang = $this->getLanguageService();
339        // Loading current page record and checking access:
340        $this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause);
341        $access = is_array($this->pageinfo);
342
343        // Start document template object
344        // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Instantiation will be removed.
345        $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
346
347        $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/AjaxDataHandler');
348        $calcPerms = $backendUser->calcPerms($this->pageinfo);
349        $userCanEditPage = $calcPerms & Permission::PAGE_EDIT && !empty($this->id) && ($backendUser->isAdmin() || (int)$this->pageinfo['editlock'] === 0);
350        $pageActionsCallback = null;
351        if ($userCanEditPage) {
352            $pageActionsCallback = 'function(PageActions) {
353                PageActions.setPageId(' . (int)$this->id . ');
354            }';
355        }
356        $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/PageActions', $pageActionsCallback);
357        $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Recordlist/Tooltip');
358        // Apply predefined values for hidden checkboxes
359        // Set predefined value for DisplayBigControlPanel:
360        if ($this->modTSconfig['properties']['enableDisplayBigControlPanel'] === 'activated') {
361            $this->MOD_SETTINGS['bigControlPanel'] = true;
362        } elseif ($this->modTSconfig['properties']['enableDisplayBigControlPanel'] === 'deactivated') {
363            $this->MOD_SETTINGS['bigControlPanel'] = false;
364        }
365        // Set predefined value for Clipboard:
366        if ($this->modTSconfig['properties']['enableClipBoard'] === 'activated') {
367            $this->MOD_SETTINGS['clipBoard'] = true;
368        } elseif ($this->modTSconfig['properties']['enableClipBoard'] === 'deactivated') {
369            $this->MOD_SETTINGS['clipBoard'] = false;
370        } else {
371            if ($this->MOD_SETTINGS['clipBoard'] === null) {
372                $this->MOD_SETTINGS['clipBoard'] = true;
373            }
374        }
375
376        // Initialize the dblist object:
377        $dblist = GeneralUtility::makeInstance(DatabaseRecordList::class);
378        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
379        $dblist->script = (string)$uriBuilder->buildUriFromRoute('web_list');
380        $dblist->calcPerms = $calcPerms;
381        $dblist->thumbs = $backendUser->uc['thumbnailsByDefault'];
382        $dblist->returnUrl = $this->returnUrl;
383        $dblist->allFields = $this->MOD_SETTINGS['bigControlPanel'] || $this->table ? 1 : 0;
384        $dblist->showClipboard = 1;
385        $dblist->disableSingleTableView = $this->modTSconfig['properties']['disableSingleTableView'];
386        $dblist->listOnlyInSingleTableMode = $this->modTSconfig['properties']['listOnlyInSingleTableView'];
387        $dblist->hideTables = $this->modTSconfig['properties']['hideTables'];
388        $dblist->hideTranslations = $this->modTSconfig['properties']['hideTranslations'];
389        $dblist->tableTSconfigOverTCA = $this->modTSconfig['properties']['table.'];
390        $dblist->allowedNewTables = GeneralUtility::trimExplode(',', $this->modTSconfig['properties']['allowedNewTables'], true);
391        $dblist->deniedNewTables = GeneralUtility::trimExplode(',', $this->modTSconfig['properties']['deniedNewTables'], true);
392        $dblist->pageRow = $this->pageinfo;
393        $dblist->counter++;
394        $dblist->MOD_MENU = ['bigControlPanel' => '', 'clipBoard' => ''];
395        $dblist->modTSconfig = $this->modTSconfig;
396        $clickTitleMode = trim($this->modTSconfig['properties']['clickTitleMode']);
397        $dblist->clickTitleMode = $clickTitleMode === '' ? 'edit' : $clickTitleMode;
398        if (isset($this->modTSconfig['properties']['tableDisplayOrder.'])) {
399            $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
400            $dblist->setTableDisplayOrder($typoScriptService->convertTypoScriptArrayToPlainArray($this->modTSconfig['properties']['tableDisplayOrder.']));
401        }
402        // Clipboard is initialized:
403        // Start clipboard
404        $dblist->clipObj = GeneralUtility::makeInstance(Clipboard::class);
405        // Initialize - reads the clipboard content from the user session
406        $dblist->clipObj->initializeClipboard();
407        // Clipboard actions are handled:
408        // CB is the clipboard command array
409        $CB = GeneralUtility::_GET('CB');
410        if ($this->cmd === 'setCB') {
411            // CBH is all the fields selected for the clipboard, CBC is the checkbox fields which were checked.
412            // By merging we get a full array of checked/unchecked elements
413            // This is set to the 'el' array of the CB after being parsed so only the table in question is registered.
414            $CB['el'] = $dblist->clipObj->cleanUpCBC(array_merge(GeneralUtility::_POST('CBH'), (array)GeneralUtility::_POST('CBC')), $this->cmd_table);
415        }
416        if (!$this->MOD_SETTINGS['clipBoard']) {
417            // If the clipboard is NOT shown, set the pad to 'normal'.
418            $CB['setP'] = 'normal';
419        }
420        // Execute commands.
421        $dblist->clipObj->setCmd($CB);
422        // Clean up pad
423        $dblist->clipObj->cleanCurrent();
424        // Save the clipboard content
425        $dblist->clipObj->endClipboard();
426        // This flag will prevent the clipboard panel in being shown.
427        // It is set, if the clickmenu-layer is active AND the extended view is not enabled.
428        $dblist->dontShowClipControlPanels = ($dblist->clipObj->current === 'normal' && !$this->modTSconfig['properties']['showClipControlPanelsDespiteOfCMlayers']);
429        // If there is access to the page or root page is used for searching, then render the list contents and set up the document template object:
430        if ($access || ($this->id === 0 && $this->search_levels !== 0 && $this->search_field !== '')) {
431            // Deleting records...:
432            // Has not to do with the clipboard but is simply the delete action. The clipboard object is used to clean up the submitted entries to only the selected table.
433            if ($this->cmd === 'delete') {
434                $items = $dblist->clipObj->cleanUpCBC(GeneralUtility::_POST('CBC'), $this->cmd_table, 1);
435                if (!empty($items)) {
436                    $cmd = [];
437                    foreach ($items as $iK => $value) {
438                        $iKParts = explode('|', $iK);
439                        $cmd[$iKParts[0]][$iKParts[1]]['delete'] = 1;
440                    }
441                    $tce = GeneralUtility::makeInstance(DataHandler::class);
442                    $tce->start([], $cmd);
443                    $tce->process_cmdmap();
444                    if (isset($cmd['pages'])) {
445                        BackendUtility::setUpdateSignal('updatePageTree');
446                    }
447                    $tce->printLogErrorMessages();
448                }
449            }
450            // Initialize the listing object, dblist, for rendering the list:
451            $this->pointer = max(0, (int)$this->pointer);
452            $dblist->start($this->id, $this->table, $this->pointer, $this->search_field, $this->search_levels, $this->showLimit);
453            $dblist->setDispFields();
454            // Render the list of tables:
455            $dblist->generateList();
456            $listUrl = $dblist->listURL();
457            // Add JavaScript functions to the page:
458
459            $this->moduleTemplate->addJavaScriptCode(
460                'RecordListInlineJS',
461                '
462				function jumpExt(URL,anchor) {
463					var anc = anchor?anchor:"";
464					window.location.href = URL+(T3_THIS_LOCATION?"&returnUrl="+T3_THIS_LOCATION:"")+anc;
465					return false;
466				}
467				function jumpSelf(URL) {
468					window.location.href = URL+(T3_RETURN_URL?"&returnUrl="+T3_RETURN_URL:"");
469					return false;
470				}
471				function jumpToUrl(URL) {
472					window.location.href = URL;
473					return false;
474				}
475
476				function setHighlight(id) {
477					top.fsMod.recentIds["web"] = id;
478					top.fsMod.navFrameHighlightedID["web"] = top.fsMod.currentBank + "_" + id; // For highlighting
479
480					if (top.nav_frame && top.nav_frame.refresh_nav) {
481						top.nav_frame.refresh_nav();
482					}
483				}
484				' . $this->moduleTemplate->redirectUrls($listUrl) . '
485				' . $dblist->CBfunctions() . '
486				function editRecords(table,idList,addParams,CBflag) {
487					window.location.href="' . (string)$uriBuilder->buildUriFromRoute('record_edit', ['returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')]) . '&edit["+table+"]["+idList+"]=edit"+addParams;
488				}
489				function editList(table,idList) {
490					var list="";
491
492						// Checking how many is checked, how many is not
493					var pointer=0;
494					var pos = idList.indexOf(",");
495					while (pos!=-1) {
496						if (cbValue(table+"|"+idList.substr(pointer,pos-pointer))) {
497							list+=idList.substr(pointer,pos-pointer)+",";
498						}
499						pointer=pos+1;
500						pos = idList.indexOf(",",pointer);
501					}
502					if (cbValue(table+"|"+idList.substr(pointer))) {
503						list+=idList.substr(pointer)+",";
504					}
505
506					return list ? list : idList;
507				}
508
509				if (top.fsMod) top.fsMod.recentIds["web"] = ' . (int)$this->id . ';
510			'
511            );
512
513            // Setting up the context sensitive menu:
514            $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
515        }
516        // access
517        // Begin to compile the whole page, starting out with page header:
518        if (!$this->id) {
519            $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
520        } else {
521            $title = $this->pageinfo['title'];
522        }
523        $this->body = $this->moduleTemplate->header($title);
524
525        // Additional header content
526        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['recordlist/Modules/Recordlist/index.php']['drawHeaderHook'] ?? [] as $hook) {
527            $params = [
528                'request' => $request,
529            ];
530            // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0: Handing over $this as second constructor argument will be changed to $null = null;
531            $this->body .= GeneralUtility::callUserFunction($hook, $params, $this);
532        }
533
534        $this->moduleTemplate->setTitle($title);
535
536        $output = '';
537        // Show the selector to add page translations and the list of translations of the current page
538        // but only when in "default" mode
539        if ($this->id && !$dblist->csvOutput && !$this->search_field && !$this->cmd && !$this->table) {
540            $output .= $this->languageSelector($this->id);
541            $pageTranslationsDatabaseRecordList = clone $dblist;
542            $pageTranslationsDatabaseRecordList->listOnlyInSingleTableMode = false;
543            $pageTranslationsDatabaseRecordList->disableSingleTableView = true;
544            $pageTranslationsDatabaseRecordList->deniedNewTables = ['pages'];
545            $pageTranslationsDatabaseRecordList->hideTranslations = '';
546            $pageTranslationsDatabaseRecordList->iLimit = $pageTranslationsDatabaseRecordList->itemsLimitPerTable;
547            $pageTranslationsDatabaseRecordList->setLanguagesAllowedForUser($this->siteLanguages);
548            $pageTranslationsDatabaseRecordList->showOnlyTranslatedRecords(true);
549            $output .= $pageTranslationsDatabaseRecordList->getTable('pages', $this->id);
550        }
551
552        if (!empty($dblist->HTMLcode)) {
553            $output .= $dblist->HTMLcode;
554        } else {
555            if (isset($this->table, $GLOBALS['TCA'][$this->table]['ctrl']['title'])) {
556                if (strpos($GLOBALS['TCA'][$this->table]['ctrl']['title'], 'LLL:') === 0) {
557                    $ll = sprintf($lang->getLL('noRecordsOfTypeOnThisPage'), $lang->sL($GLOBALS['TCA'][$this->table]['ctrl']['title']));
558                } else {
559                    $ll = sprintf($lang->getLL('noRecordsOfTypeOnThisPage'), $GLOBALS['TCA'][$this->table]['ctrl']['title']);
560                }
561            } else {
562                $ll = $lang->getLL('noRecordsOnThisPage');
563            }
564            $flashMessage = GeneralUtility::makeInstance(
565                FlashMessage::class,
566                $ll,
567                '',
568                FlashMessage::INFO
569            );
570            unset($ll);
571            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
572            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
573            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
574            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
575            $defaultFlashMessageQueue->enqueue($flashMessage);
576        }
577
578        $this->body .= '<form action="' . htmlspecialchars($dblist->listURL()) . '" method="post" name="dblistForm">';
579        $this->body .= $output;
580        $this->body .= '<input type="hidden" name="cmd_table" /><input type="hidden" name="cmd" /></form>';
581        // If a listing was produced, create the page footer with search form etc:
582        if ($dblist->HTMLcode) {
583            // Making field select box (when extended view for a single table is enabled):
584            if ($dblist->table) {
585                $this->body .= $dblist->fieldSelectBox($dblist->table);
586            }
587            // Adding checkbox options for extended listing and clipboard display:
588            $this->body .= '
589
590					<!--
591						Listing options for extended view and clipboard view
592					-->
593					<div class="typo3-listOptions">
594						<form action="" method="post">';
595
596            // Add "display bigControlPanel" checkbox:
597            if ($this->modTSconfig['properties']['enableDisplayBigControlPanel'] === 'selectable') {
598                $this->body .= '<div class="checkbox">' .
599                    '<label for="checkLargeControl">' .
600                    BackendUtility::getFuncCheck($this->id, 'SET[bigControlPanel]', $this->MOD_SETTINGS['bigControlPanel'], '', $this->table ? '&table=' . $this->table : '', 'id="checkLargeControl"') .
601                    BackendUtility::wrapInHelp('xMOD_csh_corebe', 'list_options', htmlspecialchars($lang->getLL('largeControl'))) .
602                    '</label>' .
603                    '</div>';
604            }
605
606            // Add "clipboard" checkbox:
607            if ($this->modTSconfig['properties']['enableClipBoard'] === 'selectable') {
608                if ($dblist->showClipboard) {
609                    $this->body .= '<div class="checkbox">' .
610                        '<label for="checkShowClipBoard">' .
611                        BackendUtility::getFuncCheck($this->id, 'SET[clipBoard]', $this->MOD_SETTINGS['clipBoard'], '', $this->table ? '&table=' . $this->table : '', 'id="checkShowClipBoard"') .
612                        BackendUtility::wrapInHelp('xMOD_csh_corebe', 'list_options', htmlspecialchars($lang->getLL('showClipBoard'))) .
613                        '</label>' .
614                        '</div>';
615                }
616            }
617
618            $this->body .= '
619						</form>
620					</div>';
621        }
622        // Printing clipboard if enabled
623        if ($this->MOD_SETTINGS['clipBoard'] && $dblist->showClipboard && ($dblist->HTMLcode || $dblist->clipObj->hasElements())) {
624            $this->body .= '<div class="db_list-dashboard">' . $dblist->clipObj->printClipboard() . '</div>';
625        }
626        // Additional footer content
627        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['recordlist/Modules/Recordlist/index.php']['drawFooterHook'] ?? [] as $hook) {
628            $params = [
629                'request' => $request,
630            ];
631            // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0: Handing over $this as second constructor argument will be changed to $null = null;
632            $this->body .= GeneralUtility::callUserFunction($hook, $params, $this);
633        }
634        // Setting up the buttons for docheader
635        $dblist->getDocHeaderButtons($this->moduleTemplate);
636        // search box toolbar
637        if (!$this->modTSconfig['properties']['disableSearchBox'] && ($dblist->HTMLcode || !empty($dblist->searchString))) {
638            $this->content = $dblist->getSearchBox();
639            $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ToggleSearchToolbox');
640
641            $searchButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeLinkButton();
642            $searchButton
643                ->setHref('#')
644                ->setClasses('t3js-toggle-search-toolbox')
645                ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.searchIcon'))
646                ->setIcon($this->iconFactory->getIcon('actions-search', Icon::SIZE_SMALL));
647            $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton(
648                $searchButton,
649                ButtonBar::BUTTON_POSITION_LEFT,
650                90
651            );
652        }
653
654        if ($this->pageinfo) {
655            $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
656        }
657
658        // Build the <body> for the module
659        $this->content .= $this->body;
660    }
661
662    /**
663     * Injects the request object for the current request or subrequest
664     * Simply calls main() and init() and outputs the content
665     *
666     * @param ServerRequestInterface $request the current request
667     * @return ResponseInterface the response with the content
668     */
669    public function mainAction(ServerRequestInterface $request): ResponseInterface
670    {
671        $this->site = $request->getAttribute('site');
672        $this->siteLanguages = $this->site->getAvailableLanguages($this->getBackendUserAuthentication(), false, (int)$this->id);
673        BackendUtility::lockRecords();
674        // @deprecated  since TYPO3 v9, will be removed in TYPO3 v10.0. Can be removed along with $this->doc.
675        $GLOBALS['SOBE'] = $this;
676        $this->init();
677        $this->clearCache();
678        $this->main($request);
679        $this->moduleTemplate->setContent($this->content);
680        return new HtmlResponse($this->moduleTemplate->renderContent());
681    }
682
683    /**
684     * Make selector box for creating new translation in a language
685     * Displays only languages which are not yet present for the current page and
686     * that are not disabled with page TS.
687     *
688     * @param int $id Page id for which to create a new translation record of pages
689     * @return string <select> HTML element (if there were items for the box anyways...)
690     */
691    protected function languageSelector(int $id): string
692    {
693        if (!$this->getBackendUserAuthentication()->check('tables_modify', 'pages')) {
694            return '';
695        }
696        $availableTranslations = [];
697        foreach ($this->siteLanguages as $siteLanguage) {
698            if ($siteLanguage->getLanguageId() === 0) {
699                continue;
700            }
701            $availableTranslations[$siteLanguage->getLanguageId()] = $siteLanguage->getTitle();
702        }
703        // Then, subtract the languages which are already on the page:
704        $localizationParentField = $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'];
705        $languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField'];
706        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
707        $queryBuilder->getRestrictions()->removeAll()
708            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
709            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
710        $statement = $queryBuilder->select('uid', $languageField)
711            ->from('pages')
712            ->where(
713                $queryBuilder->expr()->eq(
714                    $localizationParentField,
715                    $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
716                )
717            )
718            ->execute();
719        while ($pageTranslation = $statement->fetch()) {
720            unset($availableTranslations[(int)$pageTranslation[$languageField]]);
721        }
722        // If any languages are left, make selector:
723        if (!empty($availableTranslations)) {
724            $output = '<option value="">' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:new_language')) . '</option>';
725            foreach ($availableTranslations as $languageUid => $languageTitle) {
726                // Build localize command URL to DataHandler (tce_db)
727                // which redirects to FormEngine (record_edit)
728                // which, when finished editing should return back to the current page (returnUrl)
729                $parameters = [
730                    'justLocalized' => 'pages:' . $id . ':' . $languageUid,
731                    'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
732                ];
733                $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
734                $redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', $parameters);
735                $targetUrl = BackendUtility::getLinkToDataHandlerAction(
736                    '&cmd[pages][' . $id . '][localize]=' . $languageUid,
737                    $redirectUrl
738                );
739
740                $output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
741            }
742
743            return '<div class="form-inline form-inline-spaced">'
744                . '<div class="form-group">'
745                . '<select class="form-control input-sm" name="createNewLanguage" onchange="window.location.href=this.options[this.selectedIndex].value">'
746                . $output
747                . '</select></div></div>';
748        }
749        return '';
750    }
751
752    /**
753     * @return ModuleTemplate
754     */
755    protected function getModuleTemplate(): ModuleTemplate
756    {
757        return $this->moduleTemplate;
758    }
759
760    /**
761     * @return BackendUserAuthentication
762     */
763    protected function getBackendUserAuthentication(): BackendUserAuthentication
764    {
765        return $GLOBALS['BE_USER'];
766    }
767
768    /**
769     * @return LanguageService
770     */
771    protected function getLanguageService(): LanguageService
772    {
773        return $GLOBALS['LANG'];
774    }
775
776    /**
777     * @return PageRenderer
778     */
779    protected function getPageRenderer(): PageRenderer
780    {
781        if ($this->pageRenderer === null) {
782            $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
783        }
784        return $this->pageRenderer;
785    }
786}
787