PageLayoutController.php 52.7 KB
Newer Older
1
2
3
<?php
namespace TYPO3\CMS\Backend\Controller;

4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
8
9
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
10
 *
11
12
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
15
 * The TYPO3 project - inspiring people to share!
 */
16

17
18
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
19
20
21
use TYPO3\CMS\Backend\Module\ModuleLoader;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
Nicole Cordes's avatar
Nicole Cordes committed
22
use TYPO3\CMS\Backend\Utility\BackendUtility;
23
24
use TYPO3\CMS\Backend\View\BackendLayoutView;
use TYPO3\CMS\Backend\View\PageLayoutView;
25
use TYPO3\CMS\Core\Database\ConnectionPool;
26
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
27
28
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
29
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
30
use TYPO3\CMS\Core\DataHandling\DataHandler;
31
32
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
33
use TYPO3\CMS\Core\Page\PageRenderer;
34
use TYPO3\CMS\Core\Type\Bitmask\Permission;
Nicole Cordes's avatar
Nicole Cordes committed
35
36
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
37
use TYPO3\CMS\Core\Versioning\VersionState;
38
use TYPO3\CMS\Fluid\View\StandaloneView;
39
use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper;
40
use TYPO3\CMS\Frontend\Page\PageRepository;
41
use TYPO3\CMS\Recordlist\RecordList;
Nicole Cordes's avatar
Nicole Cordes committed
42

43
44
45
/**
 * Script Class for Web > Layout module
 */
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
class PageLayoutController
{
    /**
     * Page Id for which to make the listing
     *
     * @var int
     */
    public $id;

    /**
     * Pointer - for browsing list of records.
     *
     * @var int
     */
    public $pointer;

    /**
     * Thumbnails or not
     *
     * @var string
     */
    public $imagemode;

    /**
     * Search-fields
     *
     * @var string
     */
    public $search_field;

    /**
     * Search-levels
     *
     * @var int
     */
    public $search_levels;

    /**
     * Show-limit
     *
     * @var int
     */
    public $showLimit;

    /**
     * Return URL
     *
     * @var string
     */
    public $returnUrl;

    /**
     * Clear-cache flag - if set, clears page cache for current id.
     *
     * @var bool
     */
    public $clear_cache;

    /**
     * PopView id - for opening a window with the page
     *
     * @var bool
     */
    public $popView;

    /**
     * QuickEdit: Variable, that tells quick edit what to show/edit etc.
     * Format is [tablename]:[uid] with some exceptional values for both parameters (with special meanings).
     *
     * @var string
116
     * @deprecated since TYPO3 CMS 8, will be removed in TYPO3 CMS 9.
117
118
119
120
121
122
123
124
     */
    public $edit_record;

    /**
     * QuickEdit: If set, this variable tells quick edit that the last edited record had
     * this value as UID and we should look up the new, real uid value in sys_log.
     *
     * @var string
125
     * @deprecated since TYPO3 CMS 8, will be removed in TYPO3 CMS 9.
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
     */
    public $new_unique_uid;

    /**
     * Page select perms clause
     *
     * @var string
     */
    public $perms_clause;

    /**
     * Module TSconfig
     *
     * @var array
     */
    public $modTSconfig;

    /**
     * Module shared TSconfig
     *
     * @var array
     */
    public $modSharedTSconfig;

    /**
     * Current ids page record
     *
     * @var array
     */
    public $pageinfo;

    /**

     * "Pseudo" Description -table name
     *
     * @var string
     */
    public $descrTable;

    /**
     * List of column-integers to edit. Is set from TSconfig, default is "1,0,2,3"
     *
     * @var string
     */
    public $colPosList;

    /**
     * Flag: If content can be edited or not.
     *
     * @var bool
     */
    public $EDIT_CONTENT;

    /**
     * Users permissions integer for this page.
     *
     * @var int
     */
    public $CALC_PERMS;

    /**
     * Currently selected language for editing content elements
     *
     * @var int
     */
    public $current_sys_language;

    /**
     * Module configuration
     *
     * @var array
     */
198
    public $MCONF = [];
199
200
201
202
203
204

    /**
     * Menu configuration
     *
     * @var array
     */
205
    public $MOD_MENU = [];
206
207
208
209
210
211

    /**
     * Module settings (session variable)
     *
     * @var array
     */
212
    public $MOD_SETTINGS = [];
213
214
215
216
217

    /**
     * Array of tables to be listed by the Web > Page module in addition to the default tables
     *
     * @var array
218
     * @deprecated since TYPO3 CMS 8, will be removed in TYPO3 CMS 9.
219
     */
220
    public $externalTables = [];
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241

    /**
     * Module output accumulation
     *
     * @var string
     */
    public $content;

    /**
     * List of column-integers accessible to the current BE user.
     * Is set from TSconfig, default is $colPosList
     *
     * @var string
     */
    public $activeColPosList;

    /**
     * @var string
     */
    protected $editSelect;

242
243
244
245
246
    /**
     * Caches the available languages in a colPos
     *
     * @var array
     */
247
    protected $languagesInColumnCache = [];
248

249
250
251
252
253
254
255
256
257
258
259
260
    /**
     * @var IconFactory
     */
    protected $iconFactory;

    /**
     * The name of the module
     *
     * @var string
     */
    protected $moduleName = 'web_layout';

261
262
263
264
265
266
267
268
269
270
    /**
     * @var ModuleTemplate
     */
    protected $moduleTemplate;

    /**
     * @var ButtonBar
     */
    protected $buttonBar;

271
272
273
274
275
    /**
     * @var string
     */
    protected $searchContent;

276
277
278
279
280
281
282
    /**
     * Initializing the module
     *
     * @return void
     */
    public function init()
    {
283
        $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
284
        $this->iconFactory = $this->moduleTemplate->getIconFactory();
285
        $this->buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
        $this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
        // Setting module configuration / page select clause
        $this->MCONF['name'] = $this->moduleName;
        $this->perms_clause = $this->getBackendUser()->getPagePermsClause(1);
        // Get session data
        $sessionData = $this->getBackendUser()->getSessionData(RecordList::class);
        $this->search_field = !empty($sessionData['search_field']) ? $sessionData['search_field'] : '';
        // GPvars:
        $this->id = (int)GeneralUtility::_GP('id');
        $this->pointer = GeneralUtility::_GP('pointer');
        $this->imagemode = GeneralUtility::_GP('imagemode');
        $this->clear_cache = GeneralUtility::_GP('clear_cache');
        $this->popView = GeneralUtility::_GP('popView');
        $this->edit_record = GeneralUtility::_GP('edit_record');
        $this->new_unique_uid = GeneralUtility::_GP('new_unique_uid');
        $this->search_field = GeneralUtility::_GP('search_field');
        $this->search_levels = GeneralUtility::_GP('search_levels');
        $this->showLimit = GeneralUtility::_GP('showLimit');
        $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
        $this->externalTables = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['cms']['db_layout']['addTables'];
        $sessionData['search_field'] = $this->search_field;
        // Store session data
        $this->getBackendUser()->setAndSaveSessionData(RecordList::class, $sessionData);
        // Load page info array:
        $this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause);
        // Initialize menu
        $this->menuConfig();
        // Setting sys language from session var:
        $this->current_sys_language = (int)$this->MOD_SETTINGS['language'];
        // CSH / Descriptions:
        $this->descrTable = '_MOD_' . $this->moduleName;
    }

    /**
     * Initialize menu array
     *
     * @return void
     */
    public function menuConfig()
    {
        $lang = $this->getLanguageService();
        // MENU-ITEMS:
328
        $this->MOD_MENU = [
329
            'tt_content_showHidden' => '',
330
            'function' => [
331
332
                1 => $lang->getLL('m_function_1'),
                2 => $lang->getLL('m_function_2')
333
334
            ],
            'language' => [
335
                0 => $lang->getLL('m_default')
336
337
            ]
        ];
338
339
340
        // initialize page/be_user TSconfig settings
        $this->modSharedTSconfig = BackendUtility::getModTSconfig($this->id, 'mod.SHARED');
        $this->modTSconfig = BackendUtility::getModTSconfig($this->id, 'mod.' . $this->moduleName);
341
        // example settings:
342
343
344
345
346
        //  $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['cms']['db_layout']['addTables']['tx_myext'] =
        //      array ('default' => array(
        //              'MENU' => 'LLL:EXT:tx_myext/locallang_db.xlf:menuDefault',
        //              'fList' =>  'title,description,image',
        //              'icon' => TRUE));
347
        if (is_array($this->externalTables)) {
348
349
350
351
352
353
            if (!empty($this->externalTables)) {
                GeneralUtility::deprecationLog('The rendering of records in the page module by using '
                    . '$GLOBALS[\'TYPO3_CONF_VARS\'][\'EXTCONF\'][\'cms\'][\'db_layout\'][\'addTables\']'
                    . ' has been deprecated since TYPO3 CMS 8 and will be removed in TYPO3 CMS 9.'
                );
            }
354
355
356
357
358
359
360
361
362
363
364
365
366
            foreach ($this->externalTables as $table => $tableSettings) {
                // delete the default settings from above
                if (is_array($this->MOD_MENU[$table])) {
                    unset($this->MOD_MENU[$table]);
                }
                if (is_array($tableSettings) && count($tableSettings) > 1) {
                    foreach ($tableSettings as $key => $settings) {
                        $this->MOD_MENU[$table][$key] = $lang->sL($settings['MENU']);
                    }
                }
            }
        }
        // First, select all pages_language_overlay records on the current page. Each represents a possibility for a language on the page. Add these to language selector.
367
368
369
370
371
372
373
374
375
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
        $queryBuilder->getRestrictions()->removeAll();
        if ($this->id) {
            $queryBuilder->select('sys_language.uid AS uid', 'sys_language.title AS title')
                ->from('sys_language')
                ->join(
                    'sys_language',
                    'pages_language_overlay',
                    'pages_language_overlay',
376
377
378
379
                    $queryBuilder->expr()->eq(
                        'sys_language.uid',
                        $queryBuilder->quoteIdentifier('pages_language_overlay.sys_language_uid')
                    )
380
381
                )
                ->where(
382
383
384
385
386
387
388
389
                    $queryBuilder->expr()->eq(
                        'pages_language_overlay.deleted',
                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
                    ),
                    $queryBuilder->expr()->eq(
                        'pages_language_overlay.pid',
                        $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
                    ),
390
                    $queryBuilder->expr()->orX(
391
392
393
394
395
396
397
398
399
400
401
                        $queryBuilder->expr()->gte(
                            'pages_language_overlay.t3ver_state',
                            $queryBuilder->createNamedParameter(
                                (string)new VersionState(VersionState::DEFAULT_STATE),
                                \PDO::PARAM_INT
                            )
                        ),
                        $queryBuilder->expr()->eq(
                            'pages_language_overlay.t3ver_wsid',
                            $queryBuilder->createNamedParameter($this->getBackendUser()->workspace, \PDO::PARAM_INT)
                        )
402
403
404
405
406
                    )
                )
                ->groupBy('pages_language_overlay.sys_language_uid', 'sys_language.uid', 'sys_language.pid',
                    'sys_language.tstamp', 'sys_language.hidden', 'sys_language.title',
                    'sys_language.language_isocode', 'sys_language.static_lang_isocode', 'sys_language.flag')
407
                ->orderBy('sys_language.sorting');
408
            if (!$this->getBackendUser()->isAdmin()) {
409
410
411
412
413
414
                $queryBuilder->andWhere(
                    $queryBuilder->expr()->eq(
                        'sys_language.hidden',
                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
                    )
                );
415
416
417
418
419
420
            }
            $statement = $queryBuilder->execute();
        } else {
            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
            $statement = $queryBuilder->select('uid', 'title')
                ->from('sys_language')
421
                ->orderBy('sorting')
422
423
424
                ->execute();
        }
        while ($lRow = $statement->fetch()) {
425
            if ($this->getBackendUser()->checkLanguageAccess($lRow['uid'])) {
426
                $this->MOD_MENU['language'][$lRow['uid']] = $lRow['title'];
427
428
            }
        }
429
430
        // Setting alternative default label:
        if (($this->modSharedTSconfig['properties']['defaultLanguageLabel'] || $this->modTSconfig['properties']['defaultLanguageLabel']) && isset($this->MOD_MENU['language'][0])) {
431
            $this->MOD_MENU['language'][0] = $this->modTSconfig['properties']['defaultLanguageLabel'] ? $this->modTSconfig['properties']['defaultLanguageLabel'] : $this->modSharedTSconfig['properties']['defaultLanguageLabel'];
432
        }
433
434
        // Initialize the avaiable actions
        $actions = $this->initActions();
435
436
437
438
439
440
        // Clean up settings
        $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->moduleName);
        // For all elements to be shown in draft workspaces & to also show hidden elements by default if user hasn't disabled the option
        if ($this->getBackendUser()->workspace != 0 || $this->MOD_SETTINGS['tt_content_showHidden'] !== '0') {
            $this->MOD_SETTINGS['tt_content_showHidden'] = 1;
        }
441
442
        // Make action menu from available actions
        $this->makeActionMenu($actions);
443
444
445
    }

    /**
446
     * Initializes the available actions this module provides
447
     *
448
     * @return array the available actions
449
     */
450
    protected function initActions()
451
    {
452
        $actions = [
453
454
            1 => $this->getLanguageService()->getLL('m_function_1'),
            2 => $this->getLanguageService()->getLL('m_function_2')
455
        ];
456
        // Find if there are ANY languages at all (and if not, remove the language option from function menu).
457
458
459
460
461
462
463
464
465
466
467
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
        if ($this->getBackendUser()->isAdmin()) {
            $queryBuilder->getRestrictions()->removeAll();
        }

        $count = $queryBuilder
            ->count('uid')
            ->from('sys_language')
            ->execute()
            ->fetchColumn(0);

468
        if (!$count) {
469
            unset($actions['2']);
470
        }
471
472
473
474
475
476
477
478
479
        // @internal: This is an internal hook for compatibility7 only, this hook will be removed without further notice
        $initActionHook = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::class]['initActionHook'];
        if (is_array($initActionHook)) {
            foreach ($initActionHook as $hook) {
                $params = [
                    'actions' => &$actions
                ];
                GeneralUtility::callUserFunction($hook, $params, $this);
            }
480
        }
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
        // page/be_user TSconfig blinding of menu-items
        $actions = BackendUtility::unsetMenuItems($this->modTSconfig['properties'], $actions, 'menu.function');

        return $actions;
    }

    /**
     * This creates the dropdown menu with the different actions this module is able to provide.
     * For now they are Columns, Quick Edit and Languages.
     *
     * @param array $actions array with the available actions
     * @return void
     */
    protected function makeActionMenu(array $actions)
    {
496
497
498
499
        $actionMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
        $actionMenu->setIdentifier('actionMenu');
        $actionMenu->setLabel('');

500
        $defaultKey = null;
501
        $foundDefaultKey = false;
502
        foreach ($actions as $key => $action) {
503
504
505
506
            $menuItem = $actionMenu
                ->makeMenuItem()
                ->setTitle($action)
                ->setHref(BackendUtility::getModuleUrl($this->moduleName) . '&id=' . $this->id . '&SET[function]=' . $key);
507

508
            if (!$foundDefaultKey) {
509
                $defaultKey = $key;
510
                $foundDefaultKey = true;
511
            }
512
513
            if ((int)$this->MOD_SETTINGS['function'] === $key) {
                $menuItem->setActive(true);
514
                $defaultKey = null;
515
516
            }
            $actionMenu->addMenuItem($menuItem);
517
        }
518
519
520
        if (isset($defaultKey)) {
            $this->MOD_SETTINGS['function'] = $defaultKey;
        }
521
        $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($actionMenu);
522
523
524
525
526
527
528
529
530
    }

    /**
     * Clears page cache for the current id, $this->id
     *
     * @return void
     */
    public function clearCache()
    {
531
532
        if ($this->clear_cache && !empty($this->pageinfo)) {
            $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
533
            $dataHandler->start([], []);
534
            $dataHandler->clear_cacheCmd($this->id);
535
536
537
538
539
540
541
542
543
544
545
546
        }
    }

    /**
     * Generate the flashmessages for current pid
     *
     * @return string HTML content with flashmessages
     */
    protected function getHeaderFlashMessagesForCurrentPid()
    {
        $content = '';
        $lang = $this->getLanguageService();
547

548
549
550
        $view = GeneralUtility::makeInstance(StandaloneView::class);
        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/InfoBox.html'));

551
552
553
554
555
556
557
558
559
        // If page is a folder
        if ($this->pageinfo['doktype'] == PageRepository::DOKTYPE_SYSFOLDER) {
            $moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
            $moduleLoader->load($GLOBALS['TBE_MODULES']);
            $modules = $moduleLoader->modules;
            if (is_array($modules['web']['sub']['list'])) {
                $title = $lang->getLL('goToListModule');
                $message = '<p>' . $lang->getLL('goToListModuleMessage') . '</p>';
                $message .= '<a class="btn btn-info" href="javascript:top.goToModule(\'web_list\',1);">' . $lang->getLL('goToListModule') . '</a>';
560
                $view->assignMultiple([
561
562
563
                    'title' => $title,
                    'message' => $message,
                    'state' => InfoboxViewHelper::STATE_INFO
564
                ]);
565
566
                $content .= $view->render();
            }
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
        } elseif ($this->pageinfo['doktype'] === PageRepository::DOKTYPE_SHORTCUT) {
            $shortcutMode = (int)$this->pageinfo['shortcut_mode'];
            $pageRepository = GeneralUtility::makeInstance(PageRepository::class);
            $targetPage = [];

            if ($this->pageinfo['shortcut'] || $shortcutMode) {
                switch ($shortcutMode) {
                    case PageRepository::SHORTCUT_MODE_NONE:
                        $targetPage = $pageRepository->getPage($this->pageinfo['shortcut']);
                        break;
                    case PageRepository::SHORTCUT_MODE_FIRST_SUBPAGE:
                        $targetPage = reset($pageRepository->getMenu($this->pageinfo['shortcut'] ?: $this->pageinfo['uid']));
                        break;
                    case PageRepository::SHORTCUT_MODE_PARENT_PAGE:
                        $targetPage = $pageRepository->getPage($this->pageinfo['pid']);
                        break;
                }

                $message = '';
                if ($shortcutMode === PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
                    $message .= sprintf($lang->getLL('pageIsRandomInternalLinkMessage'));
                } else {
                    $linkToPid = $this->local_linkThisScript(['id' => $targetPage['uid']]);
                    $path = BackendUtility::getRecordPath($targetPage['uid'], $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW), 1000);
                    $linkedPath = '<a href="' . $linkToPid . '">' . htmlspecialchars($path) . '</a>';
                    $message .= sprintf($lang->getLL('pageIsInternalLinkMessage'), $linkedPath);
                }

                $message .= ' (' . htmlspecialchars($lang->sL(BackendUtility::getLabelFromItemlist('pages', 'shortcut_mode', $shortcutMode))) . ')';

                $view->assignMultiple([
                    'title' => $this->pageinfo['title'],
                    'message' => $message,
                    'state' => InfoboxViewHelper::STATE_INFO
                ]);
                $content .= $view->render();
            } else {
                if (empty($targetPage) && $shortcutMode !== PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
                    $view->assignMultiple([
                        'title' => $this->pageinfo['title'],
                        'message' => $lang->getLL('pageIsMisconfiguredInternalLinkMessage'),
                        'state' => InfoboxViewHelper::STATE_ERROR
                    ]);
                    $content .= $view->render();
                }
            }
        } elseif ($this->pageinfo['doktype'] === PageRepository::DOKTYPE_LINK) {
            if (empty($this->pageinfo['url'])) {
                $view->assignMultiple([
                    'title' => $this->pageinfo['title'],
                    'message' => $lang->getLL('pageIsMisconfiguredExternalLinkMessage'),
                    'state' => InfoboxViewHelper::STATE_ERROR
                ]);
                $content .= $view->render();
            } else {
                $externalUrl = htmlspecialchars(GeneralUtility::makeInstance(PageRepository::class)->getExtURL($this->pageinfo));
                if ($externalUrl !== false) {
                    $externalUrlHtml = '<a href="' . $externalUrl . '" target="_blank" rel="noopener">' . $externalUrl . '</a>';
                    $view->assignMultiple([
                        'title' => $this->pageinfo['title'],
                        'message' => sprintf($lang->getLL('pageIsExternalLinkMessage'), $externalUrlHtml),
                        'state' => InfoboxViewHelper::STATE_INFO
                    ]);
                    $content .= $view->render();
                }
            }
633
634
635
636
        }
        // If content from different pid is displayed
        if ($this->pageinfo['content_from_pid']) {
            $contentPage = BackendUtility::getRecord('pages', (int)$this->pageinfo['content_from_pid']);
637
            $linkToPid = $this->local_linkThisScript(['id' => $this->pageinfo['content_from_pid']]);
638
            $title = BackendUtility::getRecordTitle('pages', $contentPage);
639
            $link = '<a href="' . $linkToPid . '">' . htmlspecialchars($title) . ' (PID ' . (int)$this->pageinfo['content_from_pid'] . ')</a>';
640
            $message = sprintf($lang->getLL('content_from_pid_title'), $link);
641
            $view->assignMultiple([
642
643
644
                'title' => $title,
                'message' => $message,
                'state' => InfoboxViewHelper::STATE_INFO
645
            ]);
646
            $content .= $view->render();
647
648
649
650
651
652
653
654
655
656
657
        }
        return $content;
    }

    /**
     *
     * @return string $title
     */
    protected function getLocalizedPageTitle()
    {
        if ($this->current_sys_language > 0) {
658
659
660
661
662
663
664
665
666
667
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
                ->getQueryBuilderForTable('pages_language_overlay');
            $queryBuilder->getRestrictions()
                ->removeAll()
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
            $overlayRecord = $queryBuilder
                ->select('title')
                ->from('pages_language_overlay')
                ->where(
668
669
670
671
672
                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)),
                    $queryBuilder->expr()->eq(
                        'sys_language_uid',
                        $queryBuilder->createNamedParameter($this->current_sys_language, \PDO::PARAM_INT)
                    )
673
674
675
676
                )
                ->setMaxResults(1)
                ->execute()
                ->fetch();
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
            return $overlayRecord['title'];
        } else {
            return $this->pageinfo['title'];
        }
    }

    /**
     * Injects the request object for the current request or subrequest
     * As this controller goes only through the main() method, it is rather simple for now
     *
     * @param ServerRequestInterface $request the current request
     * @param ResponseInterface $response
     * @return ResponseInterface the response with the content
     */
    public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
    {
        $GLOBALS['SOBE'] = $this;
        $this->init();
        $this->clearCache();
        $this->main();
697
        $response->getBody()->write($this->moduleTemplate->renderContent());
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
        return $response;
    }

    /**
     * Main function.
     * Creates some general objects and calls other functions for the main rendering of module content.
     *
     * @return void
     */
    public function main()
    {
        $lang = $this->getLanguageService();
        // Access check...
        // The page will show only if there is a valid page and if this page may be viewed by the user
        $access = is_array($this->pageinfo) ? 1 : 0;
713
714
        // Content
        $content = '';
715
716
717
        if ($this->id && $access) {
            // Initialize permission settings:
            $this->CALC_PERMS = $this->getBackendUser()->calcPerms($this->pageinfo);
718
            $this->EDIT_CONTENT = $this->contentIsNotLockedForEditors();
719

720
721
            $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);

722
            // override the default jumpToUrl
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
            $this->moduleTemplate->addJavaScriptCode('jumpToUrl', '
                function jumpToUrl(URL,formEl) {
                    if (document.editform && TBE_EDITOR.isFormChanged)  {   // Check if the function exists... (works in all browsers?)
                        if (!TBE_EDITOR.isFormChanged()) {
                            window.location.href = URL;
                        } else if (formEl) {
                            if (formEl.type=="checkbox") formEl.checked = formEl.checked ? 0 : 1;
                        }
                    } else {
                        window.location.href = URL;
                    }
                }
            ');
            $this->moduleTemplate->addJavaScriptCode('mainJsFunctions', '
                if (top.fsMod) {
                    top.fsMod.recentIds["web"] = ' . (int)$this->id . ';
                    top.fsMod.navFrameHighlightedID["web"] = "pages' . (int)$this->id . '_"+top.fsMod.currentBank; ' . (int)$this->id . ';
                }
                ' . ($this->popView ? BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id)) : '') . '
                function deleteRecord(table,id,url) {   //
743
                    window.location.href = ' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&cmd[')
744
                                             . ' + table + "][" + id + "][delete]=1&redirect=" + encodeURIComponent(url) + "&prErr=1&uPT=1";
745
746
747
748
                    return false;
                }
            ');

749
            // Find backend layout / columns
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
            $backendLayout = GeneralUtility::callUserFunction(BackendLayoutView::class . '->getSelectedBackendLayout', $this->id, $this);
            if (!empty($backendLayout['__colPosList'])) {
                $this->colPosList = implode(',', $backendLayout['__colPosList']);
            }
            // Removing duplicates, if any
            $this->colPosList = array_unique(GeneralUtility::intExplode(',', $this->colPosList));
            // Accessible columns
            if (isset($this->modSharedTSconfig['properties']['colPos_list']) && trim($this->modSharedTSconfig['properties']['colPos_list']) !== '') {
                $this->activeColPosList = array_unique(GeneralUtility::intExplode(',', trim($this->modSharedTSconfig['properties']['colPos_list'])));
                // Match with the list which is present in the colPosList for the current page
                if (!empty($this->colPosList) && !empty($this->activeColPosList)) {
                    $this->activeColPosList = array_unique(array_intersect(
                        $this->activeColPosList,
                        $this->colPosList
                    ));
                }
            } else {
                $this->activeColPosList = $this->colPosList;
            }
            $this->activeColPosList = implode(',', $this->activeColPosList);
            $this->colPosList = implode(',', $this->colPosList);

772
773
            $content .= $this->getHeaderFlashMessagesForCurrentPid();

774
            // Render the primary module content:
775
            if ($this->MOD_SETTINGS['function'] == 1 || $this->MOD_SETTINGS['function'] == 2) {
776
                $content .= '<form action="' . htmlspecialchars(BackendUtility::getModuleUrl($this->moduleName, ['id' => $this->id, 'imagemode' =>  $this->imagemode])) . '" id="PageLayoutController" method="post">';
777
                // Page title
778
                $content .= '<h1 class="t3js-title-inlineedit">' . htmlspecialchars($this->getLocalizedPageTitle()) . '</h1>';
779
                // All other listings
780
                $content .= $this->renderContent();
781
            }
782
783
            $content .= '</form>';
            $content .= $this->searchContent;
784
            // Setting up the buttons for the docheader
785
786
787
788
789
790
791
792
793
794
795
796
797
798
            $this->makeButtons();
            // @internal: This is an internal hook for compatibility7 only, this hook will be removed without further notice
            if ($this->MOD_SETTINGS['function'] != 1 && $this->MOD_SETTINGS['function'] != 2) {
                $renderActionHook = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::class]['renderActionHook'];
                if (is_array($renderActionHook)) {
                    foreach ($renderActionHook as $hook) {
                        $params = [
                            'deleteButton' => $this->deleteButton,
                            ''
                        ];
                        $content .= GeneralUtility::callUserFunction($hook, $params, $this);
                    }
                }
            }
799
800
801
802
803
804
805
806
            // Create LanguageMenu
            $this->makeLanguageMenu();
        } else {
            $this->moduleTemplate->addJavaScriptCode(
                'mainJsFunctions',
                'if (top.fsMod) top.fsMod.recentIds["web"] = ' . (int)$this->id . ';'
            );
            $content .= '<h1>' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '</h1>';
807
808
            $view = GeneralUtility::makeInstance(StandaloneView::class);
            $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/InfoBox.html'));
809
            $view->assignMultiple([
810
811
                'title' => $lang->getLL('clickAPage_header'),
                'message' => $lang->getLL('clickAPage_content'),
812
                'state' => InfoboxViewHelper::STATE_INFO
813
            ]);
814
            $content .= $view->render();
815
        }
816
817
        // Set content
        $this->moduleTemplate->setContent($content);
818
819
820
    }

    /**
821
     * Rendering content
822
823
824
     *
     * @return string
     */
825
    public function renderContent()
826
    {
827
828
        $this->moduleTemplate->getPageRenderer()->loadJquery();
        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
829
830
831
832
833
834
835
836
837
838
839
        /** @var $dbList \TYPO3\CMS\Backend\View\PageLayoutView */
        $dbList = GeneralUtility::makeInstance(PageLayoutView::class);
        $dbList->thumbs = $this->imagemode;
        $dbList->no_noWrap = 1;
        $dbList->descrTable = $this->descrTable;
        $this->pointer = MathUtility::forceIntegerInRange($this->pointer, 0, 100000);
        $dbList->script = BackendUtility::getModuleUrl($this->moduleName);
        $dbList->showIcon = 0;
        $dbList->setLMargin = 0;
        $dbList->doEdit = $this->EDIT_CONTENT;
        $dbList->ext_CALC_PERMS = $this->CALC_PERMS;
840
        $dbList->agePrefixes = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears');
841
842
843
844
845
846
847
848
849
850
851
852
        $dbList->id = $this->id;
        $dbList->nextThree = MathUtility::forceIntegerInRange($this->modTSconfig['properties']['editFieldsAtATime'], 0, 10);
        $dbList->option_newWizard = $this->modTSconfig['properties']['disableNewContentElementWizard'] ? 0 : 1;
        $dbList->defLangBinding = $this->modTSconfig['properties']['defLangBinding'] ? 1 : 0;
        if (!$dbList->nextThree) {
            $dbList->nextThree = 1;
        }
        $dbList->externalTables = $this->externalTables;
        // Create menu for selecting a table to jump to (this is, if more than just pages/tt_content elements are found on the page!)
        // also fills $dbList->activeTables
        $dbList->getTableMenu($this->id);
        // Initialize other variables:
853
854
        $tableOutput = [];
        $tableJSOutput = [];
855
856
857
858
        $CMcounter = 0;
        // Traverse the list of table names which has records on this page (that array is populated
        // by the $dblist object during the function getTableMenu()):
        foreach ($dbList->activeTables as $table => $value) {
859
            $h_func = '';
860
861
            $h_func_b = '';
            if (!isset($dbList->externalTables[$table])) {
862
863
864
865
866
867
868
                // Toggle hidden ContentElements
                $numberOfHiddenElements = $this->getNumberOfHiddenElements();
                if ($numberOfHiddenElements > 0) {
                    $h_func_b = '
                        <div class="checkbox">
                            <label for="checkTt_content_showHidden">
                                <input type="checkbox" id="checkTt_content_showHidden" class="checkbox" name="SET[tt_content_showHidden]" value="1" ' . ($this->MOD_SETTINGS['tt_content_showHidden'] ? 'checked="checked"' : '') . ' />
869
                                ' . htmlspecialchars($this->getLanguageService()->getLL('hiddenCE')) . ' (<span class="t3js-hidden-counter">' . $numberOfHiddenElements . '</span>)
870
871
                            </label>
                        </div>';
872
                }
873

874
875
876
877
878
879
                // Boolean: Display up/down arrows and edit icons for tt_content records
                $dbList->tt_contentConfig['showCommands'] = 1;
                // Boolean: Display info-marks or not
                $dbList->tt_contentConfig['showInfo'] = 1;
                // Setting up the tt_content columns to show:
                if (is_array($GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'])) {
880
                    $colList = [];
881
882
883
884
885
886
                    $tcaItems = GeneralUtility::callUserFunction(BackendLayoutView::class . '->getColPosListItemsParsed', $this->id, $this);
                    foreach ($tcaItems as $temp) {
                        $colList[] = $temp[1];
                    }
                } else {
                    // ... should be impossible that colPos has no array. But this is the fallback should it make any sense:
887
                    $colList = ['1', '0', '2', '3'];
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
                }
                if ($this->colPosList !== '') {
                    $colList = array_intersect(GeneralUtility::intExplode(',', $this->colPosList), $colList);
                }
                // The order of the rows: Default is left(1), Normal(0), right(2), margin(3)
                $dbList->tt_contentConfig['cols'] = implode(',', $colList);
                $dbList->tt_contentConfig['activeCols'] = $this->activeColPosList;
                $dbList->tt_contentConfig['showHidden'] = $this->MOD_SETTINGS['tt_content_showHidden'];
                $dbList->tt_contentConfig['sys_language_uid'] = (int)$this->current_sys_language;
                // If the function menu is set to "Language":
                if ($this->MOD_SETTINGS['function'] == 2) {
                    $dbList->tt_contentConfig['languageMode'] = 1;
                    $dbList->tt_contentConfig['languageCols'] = $this->MOD_MENU['language'];
                    $dbList->tt_contentConfig['languageColsPointer'] = $this->current_sys_language;
                }
            } else {
                if (isset($this->MOD_SETTINGS) && isset($this->MOD_MENU)) {
                    $h_func = BackendUtility::getFuncMenu($this->id, 'SET[' . $table . ']', $this->MOD_SETTINGS[$table], $this->MOD_MENU[$table], '', '');
                }
            }
            // Start the dblist object:
            $dbList->itemsLimitSingleTable = 1000;
            $dbList->start($this->id, $table, $this->pointer, $this->search_field, $this->search_levels, $this->showLimit);
            $dbList->counter = $CMcounter;
            $dbList->ext_function = $this->MOD_SETTINGS['function'];
            // Render versioning selector:
914
            $dbList->HTMLcode .= $this->moduleTemplate->getVersionSelector($this->id);
915
916
917
            // Generate the list of elements here:
            $dbList->generateList();
            // Adding the list content to the tableOutput variable:
918
            $tableOutput[$table] = $h_func . $dbList->HTMLcode . $h_func_b;
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
            // ... and any accumulated JavaScript goes the same way!
            $tableJSOutput[$table] = $dbList->JScode;
            // Increase global counter:
            $CMcounter += $dbList->counter;
            // Reset variables after operation:
            $dbList->HTMLcode = '';
            $dbList->JScode = '';
        }
        // END: traverse tables
        // For Context Sensitive Menus:
        // Init the content
        $content = '';
        // Additional header content
        $headerContentHook = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawHeaderHook'];
        if (is_array($headerContentHook)) {
            foreach ($headerContentHook as $hook) {
935
                $params = [];
936
937
938
939
940
                $content .= GeneralUtility::callUserFunction($hook, $params, $this);
            }
        }
        // Add the content for each table we have rendered (traversing $tableOutput variable)
        foreach ($tableOutput as $table => $output) {
941
            $content .= $output;
942
943
        }
        // Making search form:
944
        if (!$this->modTSconfig['properties']['disableSearchBox'] && ($dbList->counter > 0 || $this->currentPageHasSubPages())) {
945
            $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ToggleSearchToolbox');
946
947
            $toggleSearchFormButton = $this->buttonBar->makeLinkButton()
                ->setClasses('t3js-toggle-search-toolbox')
948
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.searchIcon'))
949
950
951
                ->setIcon($this->iconFactory->getIcon('actions-search', Icon::SIZE_SMALL))
                ->setHref('#');
            $this->buttonBar->addButton($toggleSearchFormButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
952
            $this->searchContent = $dbList->getSearchBox();
953
954
955
956
957
        }
        // Additional footer content
        $footerContentHook = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawFooterHook'];
        if (is_array($footerContentHook)) {
            foreach ($footerContentHook as $hook) {
958
                $params = [];
959
960
961
962
963
964
                $content .= GeneralUtility::callUserFunction($hook, $params, $this);
            }
        }
        return $content;
    }

965
966
967
968
969
970
971
972
    /**
     * @return ModuleTemplate
     */
    public function getModuleTemplate()
    {
        return $this->moduleTemplate;
    }

973
974
975
976
977
978
979
    /**
     * Print accumulated content of module
     *
     * @return void
     */
    public function printContent()
    {
980
        echo $this->moduleTemplate->renderContent();
981
982
983
984
985
986
987
988
    }

    /***************************
     *
     * Sub-content functions, rendering specific parts of the module content.
     *
     ***************************/
    /**
989
     * This creates the buttons for die modules
990
     *
991
     * @return void
992
     */
993
    protected function makeButtons()
994
    {
995
996
997
998
999
1000
        if ($this->MOD_SETTINGS['function'] == 1 || $this->MOD_SETTINGS['function'] == 2) {
            // Add CSH (Context Sensitive Help) icon to tool bar
            $contextSensitiveHelpButton = $this->buttonBar->makeHelpButton()
                ->setModuleName($this->descrTable)
                ->setFieldName('columns_' . $this->controller->MOD_SETTINGS['function']);
            $this->buttonBar->addButton($contextSensitiveHelpButton);