Test Failed
Branch master (7b1793)
by Tymoteusz
15:35
created

PageLayoutView::newLanguageButton()   F

Complexity

Conditions 13

Size

Total Lines 54
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 39
nop 3
dl 0
loc 54
rs 3.5512
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace TYPO3\CMS\Backend\View;
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
use Doctrine\DBAL\Driver\Statement;
19
use Psr\Log\LoggerAwareInterface;
20
use Psr\Log\LoggerAwareTrait;
21
use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
22
use TYPO3\CMS\Backend\Controller\Page\LocalizationController;
23
use TYPO3\CMS\Backend\Controller\PageLayoutController;
24
use TYPO3\CMS\Backend\Routing\UriBuilder;
25
use TYPO3\CMS\Backend\Tree\View\PageTreeView;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, TYPO3\CMS\Backend\View\PageTreeView. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
26
use TYPO3\CMS\Backend\Utility\BackendUtility;
27
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
28
use TYPO3\CMS\Core\Database\Connection;
29
use TYPO3\CMS\Core\Database\ConnectionPool;
30
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
31
use TYPO3\CMS\Core\Database\Query\QueryHelper;
32
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
33
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
34
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
35
use TYPO3\CMS\Core\Imaging\Icon;
36
use TYPO3\CMS\Core\Imaging\IconFactory;
37
use TYPO3\CMS\Core\Localization\LanguageService;
38
use TYPO3\CMS\Core\Messaging\FlashMessage;
39
use TYPO3\CMS\Core\Messaging\FlashMessageService;
40
use TYPO3\CMS\Core\Page\PageRenderer;
41
use TYPO3\CMS\Core\Service\DependencyOrderingService;
42
use TYPO3\CMS\Core\Type\Bitmask\Permission;
43
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
44
use TYPO3\CMS\Core\Utility\GeneralUtility;
45
use TYPO3\CMS\Core\Utility\HttpUtility;
46
use TYPO3\CMS\Core\Utility\MathUtility;
47
use TYPO3\CMS\Core\Utility\StringUtility;
48
use TYPO3\CMS\Core\Versioning\VersionState;
49
use TYPO3\CMS\Extbase\Service\FlexFormService;
50
use TYPO3\CMS\Fluid\View\StandaloneView;
51
use TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList;
52
53
/**
54
 * Child class for the Web > Page module
55
 */
56
class PageLayoutView implements LoggerAwareInterface
57
{
58
    use LoggerAwareTrait;
59
60
    /**
61
     * If TRUE, users/groups are shown in the page info box.
62
     *
63
     * @var bool
64
     */
65
    public $pI_showUser = false;
66
67
    /**
68
     * The number of successive records to edit when showing content elements.
69
     *
70
     * @var int
71
     */
72
    public $nextThree = 3;
73
74
    /**
75
     * If TRUE, disables the edit-column icon for tt_content elements
76
     *
77
     * @var bool
78
     */
79
    public $pages_noEditColumns = false;
80
81
    /**
82
     * If TRUE, new-wizards are linked to rather than the regular new-element list.
83
     *
84
     * @var bool
85
     */
86
    public $option_newWizard = true;
87
88
    /**
89
     * If set to "1", will link a big button to content element wizard.
90
     *
91
     * @var int
92
     */
93
    public $ext_function = 0;
94
95
    /**
96
     * If TRUE, elements will have edit icons (probably this is whether the user has permission to edit the page content). Set externally.
97
     *
98
     * @var bool
99
     */
100
    public $doEdit = true;
101
102
    /**
103
     * Age prefixes for displaying times. May be set externally to localized values.
104
     *
105
     * @var string
106
     */
107
    public $agePrefixes = ' min| hrs| days| yrs| min| hour| day| year';
108
109
    /**
110
     * Array of tables to be listed by the Web > Page module in addition to the default tables.
111
     *
112
     * @var array
113
     */
114
    public $externalTables = [];
115
116
    /**
117
     * "Pseudo" Description -table name
118
     *
119
     * @var string
120
     */
121
    public $descrTable;
122
123
    /**
124
     * If set TRUE, the language mode of tt_content elements will be rendered with hard binding between
125
     * default language content elements and their translations!
126
     *
127
     * @var bool
128
     */
129
    public $defLangBinding = false;
130
131
    /**
132
     * External, static: Configuration of tt_content element display:
133
     *
134
     * @var array
135
     */
136
    public $tt_contentConfig = [
137
        // Boolean: Display info-marks or not
138
        'showInfo' => 1,
139
        // Boolean: Display up/down arrows and edit icons for tt_content records
140
        'showCommands' => 1,
141
        'languageCols' => 0,
142
        'languageMode' => 0,
143
        'languageColsPointer' => 0,
144
        'showHidden' => 1,
145
        // Displays hidden records as well
146
        'sys_language_uid' => 0,
147
        // Which language
148
        'cols' => '1,0,2,3',
149
        'activeCols' => '1,0,2,3'
150
        // Which columns can be accessed by current BE user
151
    ];
152
153
    /**
154
     * Contains icon/title of pages which are listed in the tables menu (see getTableMenu() function )
155
     *
156
     * @var array
157
     */
158
    public $activeTables = [];
159
160
    /**
161
     * @var array
162
     */
163
    public $tt_contentData = [
164
        'nextThree' => [],
165
        'prev' => [],
166
        'next' => []
167
    ];
168
169
    /**
170
     * Used to store labels for CTypes for tt_content elements
171
     *
172
     * @var array
173
     */
174
    public $CType_labels = [];
175
176
    /**
177
     * Used to store labels for the various fields in tt_content elements
178
     *
179
     * @var array
180
     */
181
    public $itemLabels = [];
182
183
    /**
184
     * Indicates if all available fields for a user should be selected or not.
185
     *
186
     * @var int
187
     */
188
    public $allFields = 0;
189
190
    /**
191
     * Number of records to show
192
     *
193
     * @var int
194
     */
195
    public $showLimit = 0;
196
197
    /**
198
     * Shared module configuration, used by localization features
199
     *
200
     * @var array
201
     */
202
    public $modSharedTSconfig = [];
203
204
    /**
205
     * Tables which should not get listed
206
     *
207
     * @var string
208
     */
209
    public $hideTables = '';
210
211
    /**
212
     * Containing which fields to display in extended mode
213
     *
214
     * @var string[]
215
     */
216
    public $displayFields;
217
218
    /**
219
     * Tables which should not list their translations
220
     *
221
     * @var string
222
     */
223
    public $hideTranslations = '';
224
225
    /**
226
     * If set, csvList is outputted.
227
     *
228
     * @var bool
229
     */
230
    public $csvOutput = false;
231
232
    /**
233
     * Cache for record path
234
     *
235
     * @var mixed[]
236
     */
237
    public $recPath_cache = [];
238
239
    /**
240
     * Field, to sort list by
241
     *
242
     * @var string
243
     */
244
    public $sortField;
245
246
    /**
247
     * default Max items shown per table in "multi-table mode", may be overridden by tables.php
248
     *
249
     * @var int
250
     */
251
    public $itemsLimitPerTable = 20;
252
253
    /**
254
     * Page select permissions
255
     *
256
     * @var string
257
     */
258
    public $perms_clause = '';
259
260
    /**
261
     * Page id
262
     *
263
     * @var int
264
     */
265
    public $id;
266
267
    /**
268
     * Return URL
269
     *
270
     * @var string
271
     */
272
    public $returnUrl = '';
273
274
    /**
275
     * Tablename if single-table mode
276
     *
277
     * @var string
278
     */
279
    public $table = '';
280
281
    /**
282
     * Some permissions...
283
     *
284
     * @var int
285
     */
286
    public $calcPerms = 0;
287
288
    /**
289
     * Mode for what happens when a user clicks the title of a record.
290
     *
291
     * @var string
292
     */
293
    public $clickTitleMode = '';
294
295
    /**
296
     * Levels to search down.
297
     *
298
     * @var int
299
     */
300
    public $searchLevels = '';
301
302
    /**
303
     * "LIMIT " in SQL...
304
     *
305
     * @var int
306
     */
307
    public $iLimit = 0;
308
309
    /**
310
     * Set to the total number of items for a table when selecting.
311
     *
312
     * @var string
313
     */
314
    public $totalItems = '';
315
316
    /**
317
     * TSconfig which overwrites TCA-Settings
318
     *
319
     * @var mixed[][]
320
     */
321
    public $tableTSconfigOverTCA = [];
322
323
    /**
324
     * Loaded with page record with version overlay if any.
325
     *
326
     * @var string[]
327
     */
328
    public $pageRecord = [];
329
330
    /**
331
     * Used for tracking duplicate values of fields
332
     *
333
     * @var string[]
334
     */
335
    public $duplicateStack = [];
336
337
    /**
338
     * Fields to display for the current table
339
     *
340
     * @var string[]
341
     */
342
    public $setFields = [];
343
344
    /**
345
     * Current script name
346
     *
347
     * @var string
348
     */
349
    public $script = 'index.php';
350
351
    /**
352
     * If TRUE, records are listed only if a specific table is selected.
353
     *
354
     * @var bool
355
     */
356
    public $listOnlyInSingleTableMode = false;
357
358
    /**
359
     * JavaScript code accumulation
360
     *
361
     * @var string
362
     */
363
    public $JScode = '';
364
365
    /**
366
     * Pointer for browsing list
367
     *
368
     * @var int
369
     */
370
    public $firstElementNumber = 0;
371
372
    /**
373
     * Counting the elements no matter what...
374
     *
375
     * @var int
376
     */
377
    public $eCounter = 0;
378
379
    /**
380
     * Search string
381
     *
382
     * @var string
383
     */
384
    public $searchString = '';
385
386
    /**
387
     * default Max items shown per table in "single-table mode", may be overridden by tables.php
388
     *
389
     * @var int
390
     */
391
    public $itemsLimitSingleTable = 100;
392
393
    /**
394
     * Field, indicating to sort in reverse order.
395
     *
396
     * @var bool
397
     */
398
    public $sortRev;
399
400
    /**
401
     * String, can contain the field name from a table which must have duplicate values marked.
402
     *
403
     * @var string
404
     */
405
    public $duplicateField;
406
407
    /**
408
     * Specify a list of tables which are the only ones allowed to be displayed.
409
     *
410
     * @var string
411
     */
412
    public $tableList = '';
413
414
    /**
415
     * Array of collapsed / uncollapsed tables in multi table view
416
     *
417
     * @var int[][]
418
     */
419
    public $tablesCollapsed = [];
420
421
    /**
422
     * @var array[] Module configuration
423
     */
424
    public $modTSconfig;
425
426
    /**
427
     * HTML output
428
     *
429
     * @var string
430
     */
431
    public $HTMLcode = '';
432
433
    /**
434
     * Thumbnails on records containing files (pictures)
435
     *
436
     * @var bool
437
     */
438
    public $thumbs = 0;
439
440
    /**
441
     * Used for tracking next/prev uids
442
     *
443
     * @var int[][]
444
     */
445
    public $currentTable = [];
446
447
    /**
448
     * OBSOLETE - NOT USED ANYMORE. leftMargin
449
     *
450
     * @var int
451
     */
452
    public $leftMargin = 0;
453
454
    /**
455
     * Decides the columns shown. Filled with values that refers to the keys of the data-array. $this->fieldArray[0] is the title column.
456
     *
457
     * @var array
458
     */
459
    public $fieldArray = [];
460
461
    /**
462
     * Set to zero, if you don't want a left-margin with addElement function
463
     *
464
     * @var int
465
     */
466
    public $setLMargin = 1;
467
468
    /**
469
     * Contains page translation languages
470
     *
471
     * @var array
472
     */
473
    public $pageOverlays = [];
474
475
    /**
476
     * Counter increased for each element. Used to index elements for the JavaScript-code that transfers to the clipboard
477
     *
478
     * @var int
479
     */
480
    public $counter = 0;
481
482
    /**
483
     * Contains sys language icons and titles
484
     *
485
     * @var array
486
     */
487
    public $languageIconTitles = [];
488
489
    /**
490
     * Script URL
491
     *
492
     * @var string
493
     */
494
    public $thisScript = '';
495
496
    /**
497
     * If set this is <td> CSS-classname for odd columns in addElement. Used with db_layout / pages section
498
     *
499
     * @var string
500
     */
501
    public $oddColumnsCssClass = '';
502
503
    /**
504
     * Not used in this class - but maybe extension classes...
505
     * Max length of strings
506
     *
507
     * @var int
508
     */
509
    public $fixedL = 30;
510
511
    /**
512
     * @var TranslationConfigurationProvider
513
     */
514
    public $translateTools;
515
516
    /**
517
     * Keys are fieldnames and values are td-parameters to add in addElement(), please use $addElement_tdCSSClass for CSS-classes;
518
     *
519
     * @var array
520
     */
521
    public $addElement_tdParams = [];
522
523
    /**
524
     * @var int
525
     */
526
    public $no_noWrap = 0;
527
528
    /**
529
     * @var int
530
     */
531
    public $showIcon = 1;
532
533
    /**
534
     * Keys are fieldnames and values are td-css-classes to add in addElement();
535
     *
536
     * @var array
537
     */
538
    public $addElement_tdCssClass = [];
539
540
    /**
541
     * @var \TYPO3\CMS\Backend\Clipboard\Clipboard
542
     */
543
    protected $clipboard;
544
545
    /**
546
     * User permissions
547
     *
548
     * @var int
549
     */
550
    public $ext_CALC_PERMS;
551
552
    /**
553
     * Current ids page record
554
     *
555
     * @var array
556
     */
557
    protected $pageinfo;
558
559
    /**
560
     * Caches the available languages in a colPos
561
     *
562
     * @var array
563
     */
564
    protected $languagesInColumnCache = [];
565
566
    /**
567
     * Caches the amount of content elements as a matrix
568
     *
569
     * @var array
570
     * @internal
571
     */
572
    protected $contentElementCache = [];
573
574
    /**
575
     * @var IconFactory
576
     */
577
    protected $iconFactory;
578
579
    /**
580
     * Stores whether a certain language has translations in it
581
     *
582
     * @var array
583
     */
584
    protected $languageHasTranslationsCache = [];
585
586
    /**
587
     * @var LocalizationController
588
     */
589
    protected $localizationController;
590
591
    /**
592
     * Override the page ids taken into account by getPageIdConstraint()
593
     *
594
     * @var array
595
     */
596
    protected $overridePageIdList = [];
597
598
    /**
599
     * Override/add urlparameters in listUrl() method
600
     *
601
     * @var string[]
602
     */
603
    protected $overrideUrlParameters = [];
604
605
    /**
606
     * Array with before/after setting for tables
607
     * Structure:
608
     * 'tableName' => [
609
     *    'before' => ['A', ...]
610
     *    'after' => []
611
     *  ]
612
     * @var array[]
613
     */
614
    protected $tableDisplayOrder = [];
615
616
    /**
617
     * Construct to initialize class variables.
618
     */
619
    public function __construct()
620
    {
621
        if (isset($GLOBALS['BE_USER']->uc['titleLen']) && $GLOBALS['BE_USER']->uc['titleLen'] > 0) {
622
            $this->fixedL = $GLOBALS['BE_USER']->uc['titleLen'];
623
        }
624
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
625
        $this->getTranslateTools();
626
        $this->determineScriptUrl();
627
        $this->localizationController = GeneralUtility::makeInstance(LocalizationController::class);
628
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
629
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
630
        $pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
631
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
632
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Localization');
633
    }
634
635
    /*****************************************
636
     *
637
     * Renderings
638
     *
639
     *****************************************/
640
    /**
641
     * Adds the code of a single table
642
     *
643
     * @param string $table Table name
644
     * @param int $id Current page id
645
     * @param string $fields
646
     * @return string HTML for listing.
647
     */
648
    public function getTable($table, $id, $fields = '')
649
    {
650
        if (isset($this->externalTables[$table])) {
651
            return $this->getExternalTables($id, $table);
652
        }
653
        // Branch out based on table name:
654
        switch ($table) {
655
                case 'pages':
656
                    return $this->getTable_pages($id);
657
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
658
                case 'tt_content':
659
                    return $this->getTable_tt_content($id);
660
                    break;
661
                default:
662
                    return '';
663
            }
664
    }
665
666
    /**
667
     * Renders an external table from page id
668
     *
669
     * @param int $id Page id
670
     * @param string $table Name of the table
671
     * @return string HTML for the listing
672
     */
673
    public function getExternalTables($id, $table)
674
    {
675
        $this->pageinfo = BackendUtility::readPageAccess($id, '');
676
        $type = $this->getPageLayoutController()->MOD_SETTINGS[$table];
677
        if (!isset($type)) {
678
            $type = 0;
679
        }
680
        // eg. "name;title;email;company,image"
681
        $fList = $this->externalTables[$table][$type]['fList'];
682
        // The columns are separeted by comma ','.
683
        // Values separated by semicolon ';' are shown in the same column.
684
        $icon = $this->externalTables[$table][$type]['icon'];
685
        $addWhere = $this->externalTables[$table][$type]['addWhere'];
686
        // Create listing
687
        $out = $this->makeOrdinaryList($table, $id, $fList, $icon, $addWhere);
688
        return $out;
689
    }
690
691
    /**
692
     * Renders records from the pages table from page id
693
     * (Used to get information about the page tree content by "Web>Info"!)
694
     *
695
     * @param int $id Page id
696
     * @return string HTML for the listing
697
     */
698
    public function getTable_pages($id)
699
    {
700
        // Initializing:
701
        $out = '';
702
        $lang = $this->getLanguageService();
703
        // Select current page:
704
        if (!$id) {
705
            // The root has a pseudo record in pageinfo...
706
            $row = $this->getPageLayoutController()->pageinfo;
707
        } else {
708
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
709
                ->getQueryBuilderForTable('pages');
710
            $queryBuilder->getRestrictions()
711
                ->removeAll()
712
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
713
            $row = $queryBuilder
714
                ->select('*')
715
                ->from('pages')
716
                ->where(
717
                    $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)),
718
                    $this->getBackendUser()->getPagePermsClause(1)
719
                )
720
                ->execute()
721
                ->fetch();
722
            BackendUtility::workspaceOL('pages', $row);
723
        }
724
        // If there was found a page:
725
        if (is_array($row)) {
726
            // Getting select-depth:
727
            $depth = (int)$this->getPageLayoutController()->MOD_SETTINGS['pages_levels'];
728
            // Overriding a few things:
729
            $this->no_noWrap = 0;
730
            // Items
731
            $this->eCounter = $this->firstElementNumber;
732
            // Creating elements:
733
            list($flag, $code) = $this->fwd_rwd_nav();
734
            $out .= $code;
735
            $editUids = [];
736
            if ($flag) {
737
                // Getting children:
738
                $theRows = $this->getPageRecordsRecursive($row['uid'], $depth);
739
                if ($this->getBackendUser()->doesUserHaveAccess($row, 2) && $row['uid'] > 0) {
740
                    $editUids[] = $row['uid'];
741
                }
742
                $out .= $this->pages_drawItem($row, $this->fieldArray);
743
                // Traverse all pages selected:
744
                foreach ($theRows as $sRow) {
745
                    if ($this->getBackendUser()->doesUserHaveAccess($sRow, 2)) {
746
                        $editUids[] = $sRow['uid'];
747
                    }
748
                    $out .= $this->pages_drawItem($sRow, $this->fieldArray);
749
                }
750
                $this->eCounter++;
751
            }
752
            // Header line is drawn
753
            $theData = [];
754
            $editIdList = implode(',', $editUids);
755
            // Traverse fields (as set above) in order to create header values:
756
            foreach ($this->fieldArray as $field) {
757
                if ($editIdList
758
                    && isset($GLOBALS['TCA']['pages']['columns'][$field])
759
                    && $field !== 'uid'
760
                    && !$this->pages_noEditColumns
761
                ) {
762
                    $iTitle = sprintf(
763
                        $lang->getLL('editThisColumn'),
764
                        rtrim(trim($lang->sL(BackendUtility::getItemLabel('pages', $field))), ':')
765
                    );
766
                    $urlParameters = [
767
                        'edit' => [
768
                            'pages' => [
769
                                $editIdList => 'edit'
770
                            ]
771
                        ],
772
                        'columnsOnly' => $field,
773
                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
774
                    ];
775
                    $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
776
                    $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
777
                    $eI = '<a class="btn btn-default" href="' . htmlspecialchars($url)
778
                        . '" title="' . htmlspecialchars($iTitle) . '">'
779
                        . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
780
                } else {
781
                    $eI = '';
782
                }
783
                switch ($field) {
784
                    case 'title':
785
                        $theData[$field] = '&nbsp;' . $eI . '<strong>'
786
                            . $lang->sL($GLOBALS['TCA']['pages']['columns'][$field]['label'])
787
                            . '</strong>';
788
                        break;
789
                    case 'uid':
790
                        $theData[$field] = '&nbsp;<strong>ID:</strong>';
791
                        break;
792
                    default:
793
                        if (substr($field, 0, 6) === 'table_') {
794
                            $f2 = substr($field, 6);
795
                            if ($GLOBALS['TCA'][$f2]) {
796
                                $theData[$field] = '&nbsp;' .
797
                                    '<span title="' .
798
                                    htmlspecialchars($lang->sL($GLOBALS['TCA'][$f2]['ctrl']['title'])) .
799
                                    '">' .
800
                                    $this->iconFactory->getIconForRecord($f2, [], Icon::SIZE_SMALL)->render() .
0 ignored issues
show
Bug introduced by
It seems like $f2 can also be of type false; however, parameter $table of TYPO3\CMS\Core\Imaging\I...ory::getIconForRecord() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

800
                                    $this->iconFactory->getIconForRecord(/** @scrutinizer ignore-type */ $f2, [], Icon::SIZE_SMALL)->render() .
Loading history...
801
                                    '</span>';
802
                            }
803
                        } else {
804
                            $theData[$field] = '&nbsp;&nbsp;' . $eI . '<strong>'
805
                                . htmlspecialchars($lang->sL($GLOBALS['TCA']['pages']['columns'][$field]['label']))
806
                                . '</strong>';
807
                        }
808
                }
809
            }
810
            $out = '<div class="table-fit">'
811
                . '<table class="table table-striped table-hover typo3-page-pages">'
812
                    . '<thead>'
813
                            . $this->addElement(1, '', $theData)
814
                    . '</thead>'
815
                    . '<tbody>'
816
                        . $out
817
                    . '</tbody>'
818
                . '</table>'
819
                . '</div>';
820
        }
821
        return $out;
822
    }
823
824
    /**
825
     * Renders Content Elements from the tt_content table from page id
826
     *
827
     * @param int $id Page id
828
     * @return string HTML for the listing
829
     */
830
    public function getTable_tt_content($id)
831
    {
832
        $backendUser = $this->getBackendUser();
833
        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
834
            ->getConnectionForTable('tt_content')
835
            ->getExpressionBuilder();
836
        $this->pageinfo = BackendUtility::readPageAccess($this->id, '');
837
        $this->initializeLanguages();
838
        $this->initializeClipboard();
839
        $pageTitleParamForAltDoc = '&recTitle=' . rawurlencode(BackendUtility::getRecordTitle('pages', BackendUtility::getRecordWSOL('pages', $id), true));
840
        /** @var $pageRenderer PageRenderer */
841
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
842
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LayoutModule/DragDrop');
843
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
844
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LayoutModule/Paste');
845
        $userCanEditPage = $this->ext_CALC_PERMS & Permission::PAGE_EDIT && !empty($this->id) && ($backendUser->isAdmin() || (int)$this->pageinfo['editlock'] === 0);
846
        if ($userCanEditPage) {
847
            $languageOverlayId = 0;
848
            $pageLocalizationRecord = BackendUtility::getRecordLocalization('pages', $this->id, (int)$this->tt_contentConfig['sys_language_uid']);
849
            if (is_array($pageLocalizationRecord)) {
850
                $pageLocalizationRecord = reset($pageLocalizationRecord);
851
            }
852
            if (!empty($pageLocalizationRecord['uid'])) {
853
                $languageOverlayId = $pageLocalizationRecord['uid'];
854
            }
855
            $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/PageActions', 'function(PageActions) {
856
                PageActions.setPageId(' . (int)$this->id . ');
857
                PageActions.setLanguageOverlayId(' . $languageOverlayId . ');
858
                PageActions.initializePageTitleRenaming();
859
            }');
860
        }
861
        // Get labels for CTypes and tt_content element fields in general:
862
        $this->CType_labels = [];
863
        foreach ($GLOBALS['TCA']['tt_content']['columns']['CType']['config']['items'] as $val) {
864
            $this->CType_labels[$val[1]] = $this->getLanguageService()->sL($val[0]);
865
        }
866
        $this->itemLabels = [];
867
        foreach ($GLOBALS['TCA']['tt_content']['columns'] as $name => $val) {
868
            $this->itemLabels[$name] = $this->getLanguageService()->sL($val['label']);
869
        }
870
        $languageColumn = [];
871
        $out = '';
872
873
        // Setting language list:
874
        $langList = $this->tt_contentConfig['sys_language_uid'];
875
        if ($this->tt_contentConfig['languageMode']) {
876
            if ($this->tt_contentConfig['languageColsPointer']) {
877
                $langList = '0,' . $this->tt_contentConfig['languageColsPointer'];
878
            } else {
879
                $langList = implode(',', array_keys($this->tt_contentConfig['languageCols']));
880
            }
881
            $languageColumn = [];
882
        }
883
        $langListArr = GeneralUtility::intExplode(',', $langList);
884
        $defaultLanguageElementsByColumn = [];
885
        $defLangBinding = [];
886
        // For each languages... :
887
        // If not languageMode, then we'll only be through this once.
888
        foreach ($langListArr as $lP) {
889
            $lP = (int)$lP;
890
891
            if (!isset($this->contentElementCache[$lP])) {
892
                $this->contentElementCache[$lP] = [];
893
            }
894
895
            if (count($langListArr) === 1 || $lP === 0) {
896
                $showLanguage = $expressionBuilder->in('sys_language_uid', [$lP, -1]);
897
            } else {
898
                $showLanguage = $expressionBuilder->eq('sys_language_uid', $lP);
899
            }
900
            $content = [];
901
            $head = [];
902
903
            $backendLayout = $this->getBackendLayoutView()->getSelectedBackendLayout($this->id);
904
            $columns = $backendLayout['__colPosList'];
905
            // Select content records per column
906
            $contentRecordsPerColumn = $this->getContentRecordsPerColumn('table', $id, $columns, $showLanguage);
907
            $cList = array_keys($contentRecordsPerColumn);
908
            // For each column, render the content into a variable:
909
            foreach ($cList as $columnId) {
910
                if (!isset($this->contentElementCache[$lP][$columnId])) {
911
                    $this->contentElementCache[$lP][$columnId] = [];
912
                }
913
914
                if (!$lP) {
915
                    $defaultLanguageElementsByColumn[$columnId] = [];
916
                }
917
                // Start wrapping div
918
                $content[$columnId] .= '<div data-colpos="' . $columnId . '" data-language-uid="' . $lP . '" class="t3js-sortable t3js-sortable-lang t3js-sortable-lang-' . $lP . ' t3-page-ce-wrapper';
919
                if (empty($contentRecordsPerColumn[$columnId])) {
920
                    $content[$columnId] .= ' t3-page-ce-empty';
921
                }
922
                $content[$columnId] .= '">';
923
                // Add new content at the top most position
924
                $link = '';
925
                if ($this->getPageLayoutController()->contentIsNotLockedForEditors()
926
                    && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
927
                ) {
928
                    if ($this->option_newWizard) {
929
                        $urlParameters = [
930
                            'id' => $id,
931
                            'sys_language_uid' => $lP,
932
                            'colPos' => $columnId,
933
                            'uid_pid' => $id,
934
                            'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
935
                        ];
936
                        $tsConfig = BackendUtility::getModTSconfig($id, 'mod');
937
                        $routeName = $tsConfig['properties']['newContentElementWizard.']['override']
938
                            ?? 'new_content_element_wizard';
939
                        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
940
                        $url = (string)$uriBuilder->buildUriFromRoute($routeName, $urlParameters);
941
                    } else {
942
                        $urlParameters = [
943
                            'edit' => [
944
                                'tt_content' => [
945
                                    $id => 'new'
946
                                ]
947
                            ],
948
                            'defVals' => [
949
                                'tt_content' => [
950
                                    'colPos' => $columnId,
951
                                    'sys_language_uid' => $lP
952
                                ]
953
                            ],
954
                            'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
955
                        ];
956
                        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
957
                        $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
958
                    }
959
                    $title = htmlspecialchars($this->getLanguageService()->getLL('newContentElement'));
960
                    $link = '<a href="#" data-url="' . htmlspecialchars($url) . '" '
961
                        . 'title="' . $title . '"'
962
                        . 'data-title="' . $title . '"'
963
                        . 'class="btn btn-default btn-sm t3js-toggle-new-content-element-wizard">'
964
                        . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render()
965
                        . ' '
966
                        . htmlspecialchars($this->getLanguageService()->getLL('content')) . '</a>';
967
                }
968
                if ($this->getBackendUser()->checkLanguageAccess($lP) && $columnId !== 'unused') {
969
                    $content[$columnId] .= '
970
                    <div class="t3-page-ce t3js-page-ce" data-page="' . (int)$id . '" id="' . StringUtility::getUniqueId() . '">
971
                        <div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-' . 'page-' . $id . '-' . StringUtility::getUniqueId() . '">'
972
                            . $link
973
                            . '</div>
974
                        <div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div>
975
                    </div>
976
                    ';
977
                }
978
                $editUidList = '';
979
                if (!isset($contentRecordsPerColumn[$columnId]) || !is_array($contentRecordsPerColumn[$columnId])) {
980
                    $message = GeneralUtility::makeInstance(
981
                        FlashMessage::class,
982
                        $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:error.invalidBackendLayout'),
0 ignored issues
show
Bug introduced by
$this->getLanguageServic....invalidBackendLayout') of type string is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

982
                        /** @scrutinizer ignore-type */ $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:error.invalidBackendLayout'),
Loading history...
983
                        '',
984
                        FlashMessage::WARNING
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Core\Messaging\FlashMessage::WARNING of type integer is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

984
                        /** @scrutinizer ignore-type */ FlashMessage::WARNING
Loading history...
985
                    );
986
                    $service = GeneralUtility::makeInstance(FlashMessageService::class);
987
                    $queue = $service->getMessageQueueByIdentifier();
988
                    $queue->addMessage($message);
989
                } else {
990
                    $rowArr = $contentRecordsPerColumn[$columnId];
991
                    $this->generateTtContentDataArray($rowArr);
992
993
                    foreach ((array)$rowArr as $rKey => $row) {
994
                        $this->contentElementCache[$lP][$columnId][$row['uid']] = $row;
995
                        if ($this->tt_contentConfig['languageMode']) {
996
                            $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
997 View Code Duplication
                            if (!$this->defLangBinding && $columnId !== 'unused') {
998
                                $languageColumn[$columnId][$lP] .= $this->newLanguageButton(
999
                                    $this->getNonTranslatedTTcontentUids($defaultLanguageElementsByColumn[$columnId], $id, $lP),
1000
                                    $lP,
1001
                                    $columnId
1002
                                );
1003
                            }
1004
                        }
1005
                        if (is_array($row) && !VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
1006
                            $singleElementHTML = '';
1007
                            if (!$lP && ($this->defLangBinding || $row['sys_language_uid'] != -1)) {
1008
                                $defaultLanguageElementsByColumn[$columnId][] = (isset($row['_ORIG_uid']) ? $row['_ORIG_uid'] : $row['uid']);
1009
                            }
1010
                            $editUidList .= $row['uid'] . ',';
1011
                            $disableMoveAndNewButtons = $this->defLangBinding && $lP > 0;
1012
                            if (!$this->tt_contentConfig['languageMode']) {
1013
                                $singleElementHTML .= '<div class="t3-page-ce-dragitem" id="' . StringUtility::getUniqueId() . '">';
1014
                            }
1015
                            $singleElementHTML .= $this->tt_content_drawHeader(
1016
                                $row,
1017
                                $this->tt_contentConfig['showInfo'] ? 15 : 5,
1018
                                $disableMoveAndNewButtons,
1019
                                true,
1020
                                $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)
1021
                            );
1022
                            $innerContent = '<div ' . ($row['_ORIG_uid'] ? ' class="ver-element"' : '') . '>'
1023
                                . $this->tt_content_drawItem($row) . '</div>';
1024
                            $singleElementHTML .= '<div class="t3-page-ce-body-inner">' . $innerContent . '</div>'
1025
                                . $this->tt_content_drawFooter($row);
1026
                            $isDisabled = $this->isDisabled('tt_content', $row);
1027
                            $statusHidden = $isDisabled ? ' t3-page-ce-hidden t3js-hidden-record' : '';
1028
                            $displayNone = !$this->tt_contentConfig['showHidden'] && $isDisabled ? ' style="display: none;"' : '';
1029
                            $highlightHeader = '';
1030
                            if ($this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid']) && (int)$row['l18n_parent'] === 0) {
1031
                                $highlightHeader = ' t3-page-ce-danger';
1032
                            } elseif ($columnId === 'unused') {
1033
                                $highlightHeader = ' t3-page-ce-warning';
1034
                            }
1035
                            $singleElementHTML = '<div class="t3-page-ce' . $highlightHeader . ' t3js-page-ce t3js-page-ce-sortable ' . $statusHidden . '" id="element-tt_content-'
1036
                                . $row['uid'] . '" data-table="tt_content" data-uid="' . $row['uid'] . '"' . $displayNone . '>' . $singleElementHTML . '</div>';
1037
1038
                            if ($this->tt_contentConfig['languageMode']) {
1039
                                $singleElementHTML .= '<div class="t3-page-ce t3js-page-ce">';
1040
                            }
1041
                            $singleElementHTML .= '<div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-' . 'page-' . $id .
1042
                                '-' . StringUtility::getUniqueId() . '">';
1043
                            // Add icon "new content element below"
1044
                            if (!$disableMoveAndNewButtons
1045
                                && $this->getPageLayoutController()->contentIsNotLockedForEditors()
1046
                                && $this->getBackendUser()->checkLanguageAccess($lP)
1047
                                && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
1048
                                && $columnId !== 'unused'
1049
                            ) {
1050
                                // New content element:
1051
                                if ($this->option_newWizard) {
1052
                                    $urlParameters = [
1053
                                        'id' => $row['pid'],
1054
                                        'sys_language_uid' => $row['sys_language_uid'],
1055
                                        'colPos' => $row['colPos'],
1056
                                        'uid_pid' => -$row['uid'],
1057
                                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1058
                                    ];
1059
                                    $tsConfig = BackendUtility::getModTSconfig($row['pid'], 'mod');
1060
                                    $routeName = $tsConfig['properties']['newContentElementWizard.']['override']
1061
                                        ?? 'new_content_element_wizard';
1062
                                    $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1063
                                    $url = (string)$uriBuilder->buildUriFromRoute($routeName, $urlParameters);
1064
                                } else {
1065
                                    $urlParameters = [
1066
                                        'edit' => [
1067
                                            'tt_content' => [
1068
                                                -$row['uid'] => 'new'
1069
                                            ]
1070
                                        ],
1071
                                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1072
                                    ];
1073
                                    $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1074
                                    $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1075
                                }
1076
                                $title = htmlspecialchars($this->getLanguageService()->getLL('newContentElement'));
1077
                                $singleElementHTML .= '<a href="#" data-url="' . htmlspecialchars($url) . '" '
1078
                                    . 'title="' . $title . '"'
1079
                                    . 'data-title="' . $title . '"'
1080
                                    . 'class="btn btn-default btn-sm t3js-toggle-new-content-element-wizard">'
1081
                                    . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render()
1082
                                    . ' '
1083
                                    . htmlspecialchars($this->getLanguageService()->getLL('content')) . '</a>';
1084
                            }
1085
                            $singleElementHTML .= '</div></div><div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div></div>';
1086
                            if ($this->defLangBinding && $this->tt_contentConfig['languageMode']) {
1087
                                $defLangBinding[$columnId][$lP][$row[$lP ? 'l18n_parent' : 'uid']] = $singleElementHTML;
1088
                            } else {
1089
                                $content[$columnId] .= $singleElementHTML;
1090
                            }
1091
                        } else {
1092
                            unset($rowArr[$rKey]);
1093
                        }
1094
                    }
1095
                    $content[$columnId] .= '</div>';
1096
                    $colTitle = BackendUtility::getProcessedValue('tt_content', 'colPos', $columnId);
1097
                    $tcaItems = GeneralUtility::callUserFunction(\TYPO3\CMS\Backend\View\BackendLayoutView::class . '->getColPosListItemsParsed', $id, $this);
1098
                    foreach ($tcaItems as $item) {
1099
                        if ($item[1] == $columnId) {
1100
                            $colTitle = $this->getLanguageService()->sL($item[0]);
1101
                        }
1102
                    }
1103
                    if ($columnId === 'unused') {
1104 View Code Duplication
                        if (empty($unusedElementsMessage)) {
1105
                            $unusedElementsMessage = GeneralUtility::makeInstance(
1106
                                FlashMessage::class,
1107
                                $this->getLanguageService()->getLL('staleUnusedElementsWarning'),
1108
                                $this->getLanguageService()->getLL('staleUnusedElementsWarningTitle'),
1109
                                FlashMessage::WARNING
1110
                            );
1111
                            $service = GeneralUtility::makeInstance(FlashMessageService::class);
1112
                            $queue = $service->getMessageQueueByIdentifier();
1113
                            $queue->addMessage($unusedElementsMessage);
1114
                        }
1115
                        $colTitle = $this->getLanguageService()->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:colPos.I.unused');
1116
                        $editParam = '';
1117
                    } else {
1118
                        $editParam = $this->doEdit && !empty($rowArr)
1119
                            ? '&edit[tt_content][' . $editUidList . ']=edit' . $pageTitleParamForAltDoc
1120
                            : '';
1121
                    }
1122
                    $head[$columnId] .= $this->tt_content_drawColHeader($colTitle, $editParam);
1123
                }
1124
            }
1125
            // For each column, fit the rendered content into a table cell:
1126
            $out = '';
1127
            if ($this->tt_contentConfig['languageMode']) {
1128
                // in language mode process the content elements, but only fill $languageColumn. output will be generated later
1129
                $sortedLanguageColumn = [];
1130
                foreach ($cList as $columnId) {
1131
                    if (GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnId) || $columnId === 'unused') {
1132
                        $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
1133 View Code Duplication
                        if (!$this->defLangBinding && $columnId !== 'unused') {
1134
                            $languageColumn[$columnId][$lP] .= $this->newLanguageButton(
1135
                                $this->getNonTranslatedTTcontentUids($defaultLanguageElementsByColumn[$columnId], $id, $lP),
1136
                                $lP,
1137
                                $columnId
1138
                            );
1139
                        }
1140
                        // We sort $languageColumn again according to $cList as it may contain data already from above.
1141
                        $sortedLanguageColumn[$columnId] = $languageColumn[$columnId];
1142
                    }
1143
                }
1144
                if (!empty($languageColumn['unused'])) {
1145
                    $sortedLanguageColumn['unused'] = $languageColumn['unused'];
1146
                }
1147
                $languageColumn = $sortedLanguageColumn;
1148
            } else {
1149
                // GRID VIEW:
1150
                $grid = '<div class="t3-grid-container"><table border="0" cellspacing="0" cellpadding="0" width="100%" class="t3-page-columns t3-grid-table t3js-page-columns">';
1151
                // Add colgroups
1152
                $colCount = (int)$backendLayout['__config']['backend_layout.']['colCount'];
1153
                $rowCount = (int)$backendLayout['__config']['backend_layout.']['rowCount'];
1154
                $grid .= '<colgroup>';
1155
                for ($i = 0; $i < $colCount; $i++) {
1156
                    $grid .= '<col />';
1157
                }
1158
                $grid .= '</colgroup>';
1159
                // Cycle through rows
1160
                for ($row = 1; $row <= $rowCount; $row++) {
1161
                    $rowConfig = $backendLayout['__config']['backend_layout.']['rows.'][$row . '.'];
1162
                    if (!isset($rowConfig)) {
1163
                        continue;
1164
                    }
1165
                    $grid .= '<tr>';
1166
                    for ($col = 1; $col <= $colCount; $col++) {
1167
                        $columnConfig = $rowConfig['columns.'][$col . '.'];
1168
                        if (!isset($columnConfig)) {
1169
                            continue;
1170
                        }
1171
                        // Which tt_content colPos should be displayed inside this cell
1172
                        $columnKey = (int)$columnConfig['colPos'];
1173
                        // Render the grid cell
1174
                        $colSpan = (int)$columnConfig['colspan'];
1175
                        $rowSpan = (int)$columnConfig['rowspan'];
1176
                        $grid .= '<td valign="top"' .
1177
                            ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
1178
                            ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
1179
                            ' data-colpos="' . (int)$columnConfig['colPos'] . '" data-language-uid="' . $lP . '" class="t3js-page-lang-column-' . $lP . ' t3js-page-column t3-grid-cell t3-page-column t3-page-column-' . $columnKey .
1180
                            ((!isset($columnConfig['colPos']) || $columnConfig['colPos'] === '') ? ' t3-grid-cell-unassigned' : '') .
1181
                            ((isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && !$head[$columnKey]) || !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos']) ? ' t3-grid-cell-restricted' : '') .
1182
                            ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') .
1183
                            ($rowSpan > 0 ? ' t3-gridCell-height' . $rowSpan : '') . '">';
1184
1185
                        // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
1186
                        // If not, a new header without any buttons will be generated.
1187
                        if (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && $head[$columnKey]
1188
                            && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
1189
                        ) {
1190
                            $grid .= $head[$columnKey] . $content[$columnKey];
1191
                        } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
1192
                            && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
1193
                        ) {
1194
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('noAccess'));
1195
                        } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
1196
                            && !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
1197
                        ) {
1198
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name']) .
1199
                                ' (' . $this->getLanguageService()->getLL('noAccess') . ')');
1200
                        } elseif (isset($columnConfig['name']) && $columnConfig['name'] !== '') {
1201
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name'])
1202
                                . ' (' . $this->getLanguageService()->getLL('notAssigned') . ')');
1203
                        } else {
1204
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('notAssigned'));
1205
                        }
1206
1207
                        $grid .= '</td>';
1208
                    }
1209
                    $grid .= '</tr>';
1210
                }
1211
                if (!empty($content['unused'])) {
1212
                    $grid .= '<tr>';
1213
                    // Which tt_content colPos should be displayed inside this cell
1214
                    $columnKey = 'unused';
1215
                    // Render the grid cell
1216
                    $colSpan = (int)$backendLayout['__config']['backend_layout.']['colCount'];
1217
                    $grid .= '<td valign="top"' .
1218
                        ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
1219
                        ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $rowSpan does not seem to be defined for all execution paths leading up to this point.
Loading history...
1220
                        ' data-colpos="unused" data-language-uid="' . $lP . '" class="t3js-page-lang-column-' . $lP . ' t3js-page-column t3-grid-cell t3-page-column t3-page-column-' . $columnKey .
1221
                        ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') . '">';
1222
1223
                    // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
1224
                    // If not, a new header without any buttons will be generated.
1225
                    $grid .= $head[$columnKey] . $content[$columnKey];
1226
                    $grid .= '</td></tr>';
1227
                }
1228
                $out .= $grid . '</table></div>';
1229
            }
1230
        }
1231
        $elFromTable = $this->clipboard->elFromTable('tt_content');
1232
        if (!empty($elFromTable) && $this->getPageLayoutController()->pageIsNotLockedForEditors()) {
1233
            $pasteItem = substr(key($elFromTable), 11);
1234
            $pasteRecord = BackendUtility::getRecord('tt_content', (int)$pasteItem);
1235
            $pasteTitle = $pasteRecord['header'] ? $pasteRecord['header'] : $pasteItem;
1236
            $copyMode = $this->clipboard->clipData['normal']['mode'] ? '-' . $this->clipboard->clipData['normal']['mode'] : '';
1237
            $addExtOnReadyCode = '
1238
                     top.pasteIntoLinkTemplate = '
1239
                . $this->tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, 't3js-paste-into', 'pasteIntoColumn')
0 ignored issues
show
Bug introduced by
It seems like $pasteTitle can also be of type false; however, parameter $pasteTitle of TYPO3\CMS\Backend\View\P...content_drawPasteIcon() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1239
                . $this->tt_content_drawPasteIcon($pasteItem, /** @scrutinizer ignore-type */ $pasteTitle, $copyMode, 't3js-paste-into', 'pasteIntoColumn')
Loading history...
Bug introduced by
$pasteItem of type false|string is incompatible with the type integer expected by parameter $pasteItem of TYPO3\CMS\Backend\View\P...content_drawPasteIcon(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1239
                . $this->tt_content_drawPasteIcon(/** @scrutinizer ignore-type */ $pasteItem, $pasteTitle, $copyMode, 't3js-paste-into', 'pasteIntoColumn')
Loading history...
1240
                . ';
1241
                    top.pasteAfterLinkTemplate = '
1242
                . $this->tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, 't3js-paste-after', 'pasteAfterRecord')
1243
                . ';';
1244
        } else {
1245
            $addExtOnReadyCode = '
1246
                top.pasteIntoLinkTemplate = \'\';
1247
                top.pasteAfterLinkTemplate = \'\';';
1248
        }
1249
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
1250
        $pageRenderer->addJsInlineCode('pasteLinkTemplates', $addExtOnReadyCode);
1251
        // If language mode, then make another presentation:
1252
        // Notice that THIS presentation will override the value of $out!
1253
        // But it needs the code above to execute since $languageColumn is filled with content we need!
1254
        if ($this->tt_contentConfig['languageMode']) {
1255
            // Get language selector:
1256
            $languageSelector = $this->languageSelector($id);
1257
            // Reset out - we will make new content here:
1258
            $out = '';
1259
            // Traverse languages found on the page and build up the table displaying them side by side:
1260
            $cCont = [];
1261
            $sCont = [];
1262
            foreach ($langListArr as $lP) {
1263
                // Header:
1264
                $lP = (int)$lP;
1265
                $cCont[$lP] = '
1266
					<td valign="top" class="t3-page-column" data-language-uid="' . $lP . '">
1267
						<h2>' . htmlspecialchars($this->tt_contentConfig['languageCols'][$lP]) . '</h2>
1268
					</td>';
1269
1270
                // "View page" icon is added:
1271
                $viewLink = '';
1272
                if (!VersionState::cast($this->getPageLayoutController()->pageinfo['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
1273
                    $onClick = BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id), '', '', ('&L=' . $lP));
1274
                    $viewLink = '<a href="#" class="btn btn-default btn-sm" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage')) . '">' . $this->iconFactory->getIcon('actions-view', Icon::SIZE_SMALL)->render() . '</a>';
1275
                }
1276
                // Language overlay page header:
1277
                if ($lP) {
1278
                    $pageLocalizationRecord = BackendUtility::getRecordLocalization('pages', $id, $lP);
1279
                    if (is_array($pageLocalizationRecord)) {
1280
                        $pageLocalizationRecord = reset($pageLocalizationRecord);
1281
                    }
1282
                    BackendUtility::workspaceOL('pages', $pageLocalizationRecord);
0 ignored issues
show
Bug introduced by
It seems like $pageLocalizationRecord can also be of type false; however, parameter $row of TYPO3\CMS\Backend\Utilit...dUtility::workspaceOL() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1282
                    BackendUtility::workspaceOL('pages', /** @scrutinizer ignore-type */ $pageLocalizationRecord);
Loading history...
1283
                    $recordIcon = BackendUtility::wrapClickMenuOnIcon(
1284
                        $this->iconFactory->getIconForRecord('pages', $pageLocalizationRecord, Icon::SIZE_SMALL)->render(),
0 ignored issues
show
Bug introduced by
It seems like $pageLocalizationRecord can also be of type false; however, parameter $row of TYPO3\CMS\Core\Imaging\I...ory::getIconForRecord() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1284
                        $this->iconFactory->getIconForRecord('pages', /** @scrutinizer ignore-type */ $pageLocalizationRecord, Icon::SIZE_SMALL)->render(),
Loading history...
1285
                        'pages',
1286
                        $pageLocalizationRecord['uid']
1287
                    );
1288
                    $urlParameters = [
1289
                        'edit' => [
1290
                            'pages' => [
1291
                                $pageLocalizationRecord['uid'] => 'edit'
1292
                            ]
1293
                        ],
1294
                        'overrideVals' => [
1295
                            'pages' => [
1296
                                'sys_language_uid' => $lP
1297
                            ]
1298
                        ],
1299
                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1300
                    ];
1301
                    $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1302
                    $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1303
                    $editLink = (
1304
                        $this->getBackendUser()->check('tables_modify', 'pages')
1305
                        ? '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
1306
                        . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">'
1307
                        . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>'
1308
                        : ''
1309
                    );
1310
1311
                    $lPLabel =
1312
                        '<div class="btn-group">'
1313
                            . $viewLink
1314
                            . $editLink
1315
                        . '</div>'
1316
                        . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($pageLocalizationRecord['title'], 20));
1317
                } else {
1318
                    $editLink = '';
1319
                    $recordIcon = '';
1320
                    if ($this->getBackendUser()->checkLanguageAccess(0)) {
1321
                        $recordIcon = BackendUtility::wrapClickMenuOnIcon(
1322
                            $this->iconFactory->getIconForRecord('pages', $this->pageRecord, Icon::SIZE_SMALL)->render(),
1323
                            'pages',
1324
                            $this->id
1325
                        );
1326
                        $urlParameters = [
1327
                            'edit' => [
1328
                                'pages' => [
1329
                                    $this->id => 'edit'
1330
                                ]
1331
                            ],
1332
                            'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1333
                        ];
1334
                        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1335
                        $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1336
                        $editLink = (
1337
                            $this->getBackendUser()->check('tables_modify', 'pages')
1338
                            ? '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
1339
                            . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">'
1340
                            . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>'
1341
                            : ''
1342
                        );
1343
                    }
1344
1345
                    $lPLabel =
1346
                        '<div class="btn-group">'
1347
                            . $viewLink
1348
                            . $editLink
1349
                        . '</div>'
1350
                        . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($this->pageRecord['title'], 20));
1351
                }
1352
                $sCont[$lP] = '
1353
					<td class="t3-page-column t3-page-lang-label nowrap">' . $lPLabel . '</td>';
1354
            }
1355
            // Add headers:
1356
            $out .= '<tr>' . implode($cCont) . '</tr>';
0 ignored issues
show
Bug introduced by
The call to implode() has too few arguments starting with pieces. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1356
            $out .= '<tr>' . /** @scrutinizer ignore-call */ implode($cCont) . '</tr>';

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
$cCont of type array|string[] is incompatible with the type string expected by parameter $glue of implode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1356
            $out .= '<tr>' . implode(/** @scrutinizer ignore-type */ $cCont) . '</tr>';
Loading history...
1357
            $out .= '<tr>' . implode($sCont) . '</tr>';
1358
            unset($cCont, $sCont);
1359
1360
            // Traverse previously built content for the columns:
1361
            foreach ($languageColumn as $cKey => $cCont) {
1362
                $out .= '<tr>';
1363
                foreach ($cCont as $languageId => $columnContent) {
1364
                    $out .= '<td valign="top" data-colpos="' . $cKey . '" class="t3-grid-cell t3-page-column t3js-page-column t3js-page-lang-column t3js-page-lang-column-' . $languageId . '">' . $columnContent . '</td>';
1365
                }
1366
                $out .= '</tr>';
1367
                if ($this->defLangBinding) {
1368
                    // "defLangBinding" mode
1369
                    foreach ($defaultLanguageElementsByColumn[$cKey] as $defUid) {
1370
                        $cCont = [];
1371
                        foreach ($langListArr as $lP) {
1372
                            $cCont[] = $defLangBinding[$cKey][$lP][$defUid] . $this->newLanguageButton(
1373
                                $this->getNonTranslatedTTcontentUids([$defUid], $id, $lP),
1374
                                $lP,
1375
                                $cKey
1376
                            );
1377
                        }
1378
                        $out .= '
1379
                        <tr>
1380
							<td valign="top" class="t3-grid-cell">' . implode(('</td>' . '
1381
							<td valign="top" class="t3-grid-cell">'), $cCont) . '</td>
1382
						</tr>';
1383
                    }
1384
                }
1385
            }
1386
            // Finally, wrap it all in a table and add the language selector on top of it:
1387
            $out = $languageSelector . '
1388
                <div class="t3-grid-container">
1389
                    <table cellpadding="0" cellspacing="0" class="t3-page-columns t3-grid-table t3js-page-columns">
1390
						' . $out . '
1391
                    </table>
1392
				</div>';
1393
        }
1394
1395
        return $out;
1396
    }
1397
1398
    /**********************************
1399
     *
1400
     * Generic listing of items
1401
     *
1402
     **********************************/
1403
    /**
1404
     * Creates a standard list of elements from a table.
1405
     *
1406
     * @param string $table Table name
1407
     * @param int $id Page id.
1408
     * @param string $fList Comma list of fields to display
1409
     * @param bool $icon If TRUE, icon is shown
1410
     * @param string $addWhere Additional WHERE-clauses.
1411
     * @return string HTML table
1412
     */
1413
    public function makeOrdinaryList($table, $id, $fList, $icon = false, $addWhere = '')
1414
    {
1415
        // Initialize
1416
        $addWhere = empty($addWhere) ? [] : [QueryHelper::stripLogicalOperatorPrefix($addWhere)];
1417
        $queryBuilder = $this->getQueryBuilder($table, $id, $addWhere);
1418
        $this->setTotalItems($table, $id, $addWhere);
1419
        $dbCount = 0;
1420
        $result = false;
1421
        // Make query for records if there were any records found in the count operation
1422
        if ($this->totalItems) {
1423
            $result = $queryBuilder->execute();
1424
            // Will return FALSE, if $result is invalid
1425
            $dbCount = $result->rowCount();
1426
        }
1427
        // If records were found, render the list
1428
        if (!$dbCount) {
1429
            return '';
1430
        }
1431
        // Set fields
1432
        $out = '';
1433
        $this->fieldArray = GeneralUtility::trimExplode(',', '__cmds__,' . $fList . ',__editIconLink__', true);
1434
        $theData = [];
1435
        $theData = $this->headerFields($this->fieldArray, $table, $theData);
1436
        // Title row
1437
        $localizedTableTitle = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['ctrl']['title']));
1438
        $out .= '<tr><th class="col-icon"></th>'
1439
            . '<th colspan="' . (count($theData) - 2) . '"><span class="c-table">'
1440
            . $localizedTableTitle . '</span> (' . $dbCount . ')</td>' . '<td class="col-icon"></td>'
1441
            . '</tr>';
1442
        // Column's titles
1443
        if ($this->doEdit) {
1444
            $urlParameters = [
1445
                'edit' => [
1446
                    $table => [
1447
                        $this->id => 'new'
1448
                    ]
1449
                ],
1450
                'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1451
            ];
1452
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1453
            $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1454
            $title = htmlspecialchars($this->getLanguageService()->getLL('new'));
1455
            $theData['__cmds__'] = '<a href="#" data-url="' . htmlspecialchars($url) . '" class="t3js-toggle-new-content-element-wizard" '
1456
                . 'title="' . $title . '"'
1457
                . 'data-title="' . $title . '">'
1458
                . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render() . '</a>';
1459
        }
1460
        $out .= $this->addElement(1, '', $theData, ' class="c-headLine"', 15, '', 'th');
1461
        // Render Items
1462
        $this->eCounter = $this->firstElementNumber;
1463
        while ($row = $result->fetch()) {
1464
            BackendUtility::workspaceOL($table, $row);
1465
            if (is_array($row)) {
1466
                list($flag, $code) = $this->fwd_rwd_nav();
1467
                $out .= $code;
1468
                if ($flag) {
1469
                    $Nrow = [];
1470
                    // Setting icons links
1471
                    if ($icon) {
1472
                        $Nrow['__cmds__'] = $this->getIcon($table, $row);
1473
                    }
1474
                    // Get values:
1475
                    $Nrow = $this->dataFields($this->fieldArray, $table, $row, $Nrow);
1476
                    // Attach edit icon
1477
                    if ($this->doEdit && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)) {
1478
                        $urlParameters = [
1479
                            'edit' => [
1480
                                $table => [
1481
                                    $row['uid'] => 'edit'
1482
                                ]
1483
                            ],
1484
                            'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1485
                        ];
1486
                        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1487
                        $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1488
                        $Nrow['__editIconLink__'] = '<a class="btn btn-default" href="' . htmlspecialchars($url)
1489
                            . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">'
1490
                            . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
1491
                    } else {
1492
                        $Nrow['__editIconLink__'] = $this->noEditIcon();
1493
                    }
1494
                    $out .= $this->addElement(1, '', $Nrow);
1495
                }
1496
                $this->eCounter++;
1497
            }
1498
        }
1499
        // Wrap it all in a table:
1500
        $out = '
1501
			<!--
1502
				Standard list of table "' . $table . '"
1503
			-->
1504
			<div class="table-fit"><table class="table table-hover table-striped">
1505
				' . $out . '
1506
			</table></div>';
1507
        return $out;
1508
    }
1509
1510
    /**
1511
     * Adds content to all data fields in $out array
1512
     *
1513
     * Each field name in $fieldArr has a special feature which is that the field name can be specified as more field names.
1514
     * Eg. "field1,field2;field3".
1515
     * Field 2 and 3 will be shown in the same cell of the table separated by <br /> while field1 will have its own cell.
1516
     *
1517
     * @param array $fieldArr Array of fields to display
1518
     * @param string $table Table name
1519
     * @param array $row Record array
1520
     * @param array $out Array to which the data is added
1521
     * @return array $out array returned after processing.
1522
     * @see makeOrdinaryList()
1523
     */
1524
    public function dataFields($fieldArr, $table, $row, $out = [])
1525
    {
1526
        // Check table validity
1527
        if (!isset($GLOBALS['TCA'][$table])) {
1528
            return $out;
1529
        }
1530
1531
        $thumbsCol = $GLOBALS['TCA'][$table]['ctrl']['thumbnail'];
1532
        // Traverse fields
1533
        foreach ($fieldArr as $fieldName) {
1534
            if ($GLOBALS['TCA'][$table]['columns'][$fieldName]) {
1535
                // Each field has its own cell (if configured in TCA)
1536
                // If the column is a thumbnail column:
1537
                if ($fieldName == $thumbsCol) {
1538
                    $out[$fieldName] = $this->thumbCode($row, $table, $fieldName);
1539
                } else {
1540
                    // ... otherwise just render the output:
1541
                    $out[$fieldName] = nl2br(htmlspecialchars(trim(GeneralUtility::fixed_lgd_cs(
1542
                        BackendUtility::getProcessedValue($table, $fieldName, $row[$fieldName], 0, 0, 0, $row['uid']),
1543
                        250
1544
                    ))));
1545
                }
1546
            } else {
1547
                // Each field is separated by <br /> and shown in the same cell (If not a TCA field, then explode
1548
                // the field name with ";" and check each value there as a TCA configured field)
1549
                $theFields = explode(';', $fieldName);
1550
                // Traverse fields, separated by ";" (displayed in a single cell).
1551
                foreach ($theFields as $fName2) {
1552
                    if ($GLOBALS['TCA'][$table]['columns'][$fName2]) {
1553
                        $out[$fieldName] .= '<strong>' . htmlspecialchars($this->getLanguageService()->sL(
1554
                            $GLOBALS['TCA'][$table]['columns'][$fName2]['label']
1555
                        )) . '</strong>' . '&nbsp;&nbsp;' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(
1556
                            BackendUtility::getProcessedValue($table, $fName2, $row[$fName2], 0, 0, 0, $row['uid']),
1557
                            25
1558
                        )) . '<br />';
1559
                    }
1560
                }
1561
            }
1562
            // If no value, add a nbsp.
1563
            if (!$out[$fieldName]) {
1564
                $out[$fieldName] = '&nbsp;';
1565
            }
1566
            // Wrap in dimmed-span tags if record is "disabled"
1567
            if ($this->isDisabled($table, $row)) {
1568
                $out[$fieldName] = '<span class="text-muted">' . $out[$fieldName] . '</span>';
1569
            }
1570
        }
1571
        return $out;
1572
    }
1573
1574
    /**
1575
     * Header fields made for the listing of records
1576
     *
1577
     * @param array $fieldArr Field names
1578
     * @param string $table The table name
1579
     * @param array $out Array to which the headers are added.
1580
     * @return array $out returned after addition of the header fields.
1581
     * @see makeOrdinaryList()
1582
     */
1583
    public function headerFields($fieldArr, $table, $out = [])
1584
    {
1585
        foreach ($fieldArr as $fieldName) {
1586
            $ll = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$table]['columns'][$fieldName]['label']));
1587
            $out[$fieldName] = $ll ? $ll : '&nbsp;';
1588
        }
1589
        return $out;
1590
    }
1591
1592
    /**
1593
     * Gets content records per column.
1594
     * This is required for correct workspace overlays.
1595
     *
1596
     * @param string $table UNUSED (will always be queried from tt_content)
1597
     * @param int $id Page Id to be used (not used at all, but part of the API, see $this->pidSelect)
1598
     * @param array $columns colPos values to be considered to be shown
1599
     * @param string $additionalWhereClause Additional where clause for database select
1600
     * @return array Associative array for each column (colPos)
1601
     */
1602
    protected function getContentRecordsPerColumn($table, $id, array $columns, $additionalWhereClause = '')
1603
    {
1604
        $contentRecordsPerColumn = array_fill_keys($columns, []);
1605
        $columns = array_flip($columns);
1606
        $queryBuilder = $this->getQueryBuilder(
1607
            'tt_content',
1608
            $id,
1609
            [
1610
                $additionalWhereClause
1611
            ]
1612
        );
1613
1614
        // Traverse any selected elements and render their display code:
1615
        $results = $this->getResult($queryBuilder->execute());
0 ignored issues
show
Bug introduced by
It seems like $queryBuilder->execute() can also be of type integer; however, parameter $result of TYPO3\CMS\Backend\View\PageLayoutView::getResult() does only seem to accept Doctrine\DBAL\Driver\Statement, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1615
        $results = $this->getResult(/** @scrutinizer ignore-type */ $queryBuilder->execute());
Loading history...
1616
        $unused = [];
1617
        $hookArray = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['record_is_used'] ?? [];
1618
        foreach ($results as $record) {
1619
            $used = isset($columns[$record['colPos']]);
1620
            foreach ($hookArray as $_funcRef) {
1621
                $_params = ['columns' => $columns, 'record' => $record, 'used' => $used];
1622
                $used = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1623
            }
1624
            if ($used) {
1625
                $columnValue = (string)$record['colPos'];
1626
                $contentRecordsPerColumn[$columnValue][] = $record;
1627
            } else {
1628
                $unused[] = $record;
1629
            }
1630
        }
1631
        if (!empty($unused)) {
1632
            $contentRecordsPerColumn['unused'] = $unused;
1633
        }
1634
        return $contentRecordsPerColumn;
1635
    }
1636
1637
    /**********************************
1638
     *
1639
     * Additional functions; Pages
1640
     *
1641
     **********************************/
1642
1643
    /**
1644
     * Adds pages-rows to an array, selecting recursively in the page tree.
1645
     *
1646
     * @param int $pid Starting page id to select from
1647
     * @param string $iconPrefix Prefix for icon code.
1648
     * @param int $depth Depth (decreasing)
1649
     * @param array $rows Array which will accumulate page rows
1650
     * @return array $rows with added rows.
1651
     */
1652
    protected function getPageRecordsRecursive(int $pid, int $depth, string $iconPrefix = '', array $rows = []): array
1653
    {
1654
        $depth--;
1655
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
1656
        $queryBuilder->getRestrictions()
1657
            ->removeAll()
1658
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1659
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1660
1661
        $queryBuilder
1662
            ->select('*')
1663
            ->from('pages')
1664
            ->where(
1665
                $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)),
1666
                $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
1667
                $this->getBackendUser()->getPagePermsClause(1)
1668
            );
1669
1670 View Code Duplication
        if (!empty($GLOBALS['TCA']['pages']['ctrl']['sortby'])) {
1671
            $queryBuilder->orderBy($GLOBALS['TCA']['pages']['ctrl']['sortby']);
1672
        }
1673
1674
        if ($depth >= 0) {
1675
            $result = $queryBuilder->execute();
1676
            $rowCount = $result->rowCount();
1677
            $count = 0;
1678
            while ($row = $result->fetch()) {
1679
                BackendUtility::workspaceOL('pages', $row);
1680
                if (is_array($row)) {
1681
                    $count++;
1682
                    $row['treeIcons'] = $iconPrefix
1683
                        . '<span class="treeline-icon treeline-icon-join'
1684
                        . ($rowCount === $count ? 'bottom' : '')
1685
                        . '"></span>';
1686
                    $rows[] = $row;
1687
                    // Get the branch
1688
                    $spaceOutIcons = '<span class="treeline-icon treeline-icon-'
1689
                        . ($rowCount === $count ? 'clear' : 'line')
1690
                        . '"></span>';
1691
                    $rows = $this->getPageRecordsRecursive(
1692
                        $row['uid'],
0 ignored issues
show
Bug introduced by
It seems like $row['uid'] can also be of type string; however, parameter $pid of TYPO3\CMS\Backend\View\P...tPageRecordsRecursive() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1692
                        /** @scrutinizer ignore-type */ $row['uid'],
Loading history...
1693
                        $row['php_tree_stop'] ? 0 : $depth,
1694
                        $iconPrefix . $spaceOutIcons,
1695
                        $rows
1696
                    );
1697
                }
1698
            }
1699
        }
1700
1701
        return $rows;
1702
    }
1703
1704
    /**
1705
     * Adds a list item for the pages-rendering
1706
     *
1707
     * @param array $row Record array
1708
     * @param array $fieldArr Field list
1709
     * @return string HTML for the item
1710
     */
1711
    public function pages_drawItem($row, $fieldArr)
1712
    {
1713
        // Initialization
1714
        $theIcon = $this->getIcon('pages', $row);
1715
        // Preparing and getting the data-array
1716
        $theData = [];
1717
        foreach ($fieldArr as $field) {
1718
            switch ($field) {
1719
                case 'title':
1720
                    $pTitle = htmlspecialchars(BackendUtility::getProcessedValue('pages', $field, $row[$field], 20));
1721
                    $theData[$field] = $row['treeIcons'] . $theIcon . $pTitle . '&nbsp;&nbsp;';
1722
                    break;
1723
                case 'php_tree_stop':
1724
                    // Intended fall through
1725
                case 'TSconfig':
1726
                    $theData[$field] = $row[$field] ? '&nbsp;<strong>x</strong>' : '&nbsp;';
1727
                    break;
1728
                case 'uid':
1729
                    if ($this->getBackendUser()->doesUserHaveAccess($row, 2) && $row['uid'] > 0) {
1730
                        $urlParameters = [
1731
                            'edit' => [
1732
                                'pages' => [
1733
                                    $row['uid'] => 'edit'
1734
                                ]
1735
                            ],
1736
                            'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1737
                        ];
1738
                        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1739
                        $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1740
                        $eI = '<a class="btn btn-default" href="' . htmlspecialchars($url)
1741
                            . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('editThisPage')) . '">'
1742
                            . $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render() . '</a>';
1743
                    } else {
1744
                        $eI = '';
1745
                    }
1746
                    $theData[$field] = $eI . '<span align="right">' . $row['uid'] . '</span>';
1747
                    break;
1748
                case 'shortcut':
1749
                case 'shortcut_mode':
1750
                    if ((int)$row['doktype'] === \TYPO3\CMS\Frontend\Page\PageRepository::DOKTYPE_SHORTCUT) {
1751
                        $theData[$field] = $this->getPagesTableFieldValue($field, $row);
1752
                    }
1753
                    break;
1754
                default:
1755
                    if (substr($field, 0, 6) === 'table_') {
1756
                        $f2 = substr($field, 6);
1757
                        if ($GLOBALS['TCA'][$f2]) {
1758
                            $c = $this->numberOfRecords($f2, $row['uid']);
0 ignored issues
show
Bug introduced by
It seems like $f2 can also be of type false; however, parameter $table of TYPO3\CMS\Backend\View\P...View::numberOfRecords() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1758
                            $c = $this->numberOfRecords(/** @scrutinizer ignore-type */ $f2, $row['uid']);
Loading history...
1759
                            $theData[$field] = '&nbsp;&nbsp;' . ($c ? $c : '');
1760
                        }
1761
                    } else {
1762
                        $theData[$field] = $this->getPagesTableFieldValue($field, $row);
1763
                    }
1764
            }
1765
        }
1766
        $this->addElement_tdParams['title'] = $row['_CSSCLASS'] ? ' class="' . $row['_CSSCLASS'] . '"' : '';
1767
        return $this->addElement(1, '', $theData);
1768
    }
1769
1770
    /**
1771
     * Returns the HTML code for rendering a field in the pages table.
1772
     * The row value is processed to a human readable form and the result is parsed through htmlspecialchars().
1773
     *
1774
     * @param string $field The name of the field of which the value should be rendered.
1775
     * @param array $row The pages table row as an associative array.
1776
     * @return string The rendered table field value.
1777
     */
1778
    protected function getPagesTableFieldValue($field, array $row)
1779
    {
1780
        return '&nbsp;&nbsp;' . htmlspecialchars(BackendUtility::getProcessedValue('pages', $field, $row[$field]));
1781
    }
1782
1783
    /**********************************
1784
     *
1785
     * Additional functions; Content Elements
1786
     *
1787
     **********************************/
1788
    /**
1789
     * Draw header for a content element column:
1790
     *
1791
     * @param string $colName Column name
1792
     * @param string $editParams Edit params (Syntax: &edit[...] for FormEngine)
1793
     * @return string HTML table
1794
     */
1795
    public function tt_content_drawColHeader($colName, $editParams = '')
1796
    {
1797
        $iconsArr = [];
1798
        // Create command links:
1799
        if ($this->tt_contentConfig['showCommands']) {
1800
            // Edit whole of column:
1801
            if ($editParams && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT) && $this->getBackendUser()->checkLanguageAccess(0)) {
1802
                $iconsArr['edit'] = '<a href="#" onclick="'
1803
                    . htmlspecialchars(BackendUtility::editOnClick($editParams)) . '" title="'
1804
                    . htmlspecialchars($this->getLanguageService()->getLL('editColumn')) . '">'
1805
                    . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1806
            }
1807
        }
1808
        $icons = '';
1809
        if (!empty($iconsArr)) {
1810
            $icons = '<div class="t3-page-column-header-icons">' . implode('', $iconsArr) . '</div>';
1811
        }
1812
        // Create header row:
1813
        $out = '<div class="t3-page-column-header">
1814
					' . $icons . '
1815
					<div class="t3-page-column-header-label">' . htmlspecialchars($colName) . '</div>
1816
				</div>';
1817
        return $out;
1818
    }
1819
1820
    /**
1821
     * Draw a paste icon either for pasting into a column or for pasting after a record
1822
     *
1823
     * @param int $pasteItem ID of the item in the clipboard
1824
     * @param string $pasteTitle Title for the JS modal
1825
     * @param string $copyMode copy or cut
1826
     * @param string $cssClass CSS class to determine if pasting is done into column or after record
1827
     * @param string $title title attribute of the generated link
1828
     *
1829
     * @return string Generated HTML code with link and icon
1830
     */
1831
    protected function tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, $cssClass, $title)
1832
    {
1833
        $pasteIcon = json_encode(
1834
            ' <a data-content="' . htmlspecialchars($pasteItem) . '"'
1835
            . ' data-title="' . htmlspecialchars($pasteTitle) . '"'
1836
            . ' class="t3js-paste t3js-paste' . htmlspecialchars($copyMode) . ' ' . htmlspecialchars($cssClass) . ' btn btn-default btn-sm"'
1837
            . ' title="' . htmlspecialchars($this->getLanguageService()->getLL($title)) . '">'
1838
            . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
1839
            . '</a>'
1840
        );
1841
        return $pasteIcon;
1842
    }
1843
1844
    /**
1845
     * Draw the footer for a single tt_content element
1846
     *
1847
     * @param array $row Record array
1848
     * @return string HTML of the footer
1849
     * @throws \UnexpectedValueException
1850
     */
1851
    protected function tt_content_drawFooter(array $row)
1852
    {
1853
        $content = '';
1854
        // Get processed values:
1855
        $info = [];
1856
        $this->getProcessedValue('tt_content', 'starttime,endtime,fe_group,space_before_class,space_after_class', $row, $info);
1857
1858
        // Content element annotation
1859
        if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn'])) {
1860
            $info[] = htmlspecialchars($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']]);
1861
        }
1862
1863
        // Call drawFooter hooks
1864
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawFooter'] ?? [] as $className) {
1865
            $hookObject = GeneralUtility::makeInstance($className);
1866
            if (!$hookObject instanceof PageLayoutViewDrawFooterHookInterface) {
1867
                throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawFooterHookInterface::class, 1404378171);
1868
            }
1869
            $hookObject->preProcess($this, $info, $row);
0 ignored issues
show
Bug introduced by
$info of type array|string[] is incompatible with the type string expected by parameter $info of TYPO3\CMS\Backend\View\P...Interface::preProcess(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1869
            $hookObject->preProcess($this, /** @scrutinizer ignore-type */ $info, $row);
Loading history...
1870
        }
1871
1872
        // Display info from records fields:
1873
        if (!empty($info)) {
1874
            $content = '<div class="t3-page-ce-info">
1875
				' . implode('<br>', $info) . '
1876
				</div>';
1877
        }
1878
        // Wrap it
1879
        if (!empty($content)) {
1880
            $content = '<div class="t3-page-ce-footer">' . $content . '</div>';
1881
        }
1882
        return $content;
1883
    }
1884
1885
    /**
1886
     * Draw the header for a single tt_content element
1887
     *
1888
     * @param array $row Record array
1889
     * @param int $space Amount of pixel space above the header. UNUSED
1890
     * @param bool $disableMoveAndNewButtons If set the buttons for creating new elements and moving up and down are not shown.
1891
     * @param bool $langMode If set, we are in language mode and flags will be shown for languages
1892
     * @param bool $dragDropEnabled If set the move button must be hidden
1893
     * @return string HTML table with the record header.
1894
     */
1895
    public function tt_content_drawHeader($row, $space = 0, $disableMoveAndNewButtons = false, $langMode = false, $dragDropEnabled = false)
1896
    {
1897
        $out = '';
1898
        // If show info is set...;
1899
        if ($this->tt_contentConfig['showInfo'] && $this->getBackendUser()->recordEditAccessInternals('tt_content', $row)) {
1900
            // Render control panel for the element:
1901
            if ($this->tt_contentConfig['showCommands'] && $this->doEdit) {
1902
                // Edit content element:
1903
                $urlParameters = [
1904
                    'edit' => [
1905
                        'tt_content' => [
1906
                            $this->tt_contentData['nextThree'][$row['uid']] => 'edit'
1907
                        ]
1908
                    ],
1909
                    'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $row['uid'],
1910
                ];
1911
                $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1912
                $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters) . '#element-tt_content-' . $row['uid'];
1913
1914
                $out .= '<a class="btn btn-default" href="' . htmlspecialchars($url)
1915
                    . '" title="' . htmlspecialchars($this->nextThree > 1
1916
                        ? sprintf($this->getLanguageService()->getLL('nextThree'), $this->nextThree)
1917
                        : $this->getLanguageService()->getLL('edit'))
1918
                    . '">' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
1919
                // Hide element:
1920
                $hiddenField = $GLOBALS['TCA']['tt_content']['ctrl']['enablecolumns']['disabled'];
1921
                if ($hiddenField && $GLOBALS['TCA']['tt_content']['columns'][$hiddenField]
1922
                    && (!$GLOBALS['TCA']['tt_content']['columns'][$hiddenField]['exclude']
1923
                        || $this->getBackendUser()->check('non_exclude_fields', 'tt_content:' . $hiddenField))
1924
                ) {
1925
                    if ($row[$hiddenField]) {
1926
                        $value = 0;
1927
                        $label = 'unHide';
1928
                    } else {
1929
                        $value = 1;
1930
                        $label = 'hide';
1931
                    }
1932
                    $params = '&data[tt_content][' . ($row['_ORIG_uid'] ? $row['_ORIG_uid'] : $row['uid'])
1933
                        . '][' . $hiddenField . ']=' . $value;
1934
                    $out .= '<a class="btn btn-default" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1935
                        . '#element-tt_content-' . $row['uid'] . '" title="' . htmlspecialchars($this->getLanguageService()->getLL($label)) . '">'
1936
                        . $this->iconFactory->getIcon('actions-edit-' . strtolower($label), Icon::SIZE_SMALL)->render() . '</a>';
1937
                }
1938
                // Delete
1939
                $disableDeleteTS = $this->getBackendUser()->getTSConfig('options.disableDelete');
1940
                $disableDelete = (bool) trim(isset($disableDeleteTS['properties']['tt_content']) ? $disableDeleteTS['properties']['tt_content'] : $disableDeleteTS['value']);
1941
                if (!$disableDelete) {
1942
                    $params = '&cmd[tt_content][' . $row['uid'] . '][delete]=1';
1943
                    $confirm = $this->getLanguageService()->getLL('deleteWarning')
1944
                        . BackendUtility::translationCount('tt_content', $row['uid'], (' '
1945
                                                                                       . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord')));
1946
                    $out .= '<a class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params)) . '"'
1947
                        . ' data-severity="warning"'
1948
                        . ' data-title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_alt_doc.xlf:label.confirm.delete_record.title')) . '"'
1949
                        . ' data-content="' . htmlspecialchars($confirm) . '" '
1950
                        . ' data-button-close-text="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel')) . '"'
1951
                        . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('deleteItem')) . '">'
1952
                        . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
1953
                    if ($out && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)) {
1954
                        $out = '<div class="btn-group btn-group-sm" role="group">' . $out . '</div>';
1955
                    } else {
1956
                        $out = '';
1957
                    }
1958
                }
1959
                if (!$disableMoveAndNewButtons) {
1960
                    $moveButtonContent = '';
1961
                    $displayMoveButtons = false;
1962
                    // Move element up:
1963 View Code Duplication
                    if ($this->tt_contentData['prev'][$row['uid']]) {
1964
                        $params = '&cmd[tt_content][' . $row['uid'] . '][move]=' . $this->tt_contentData['prev'][$row['uid']];
1965
                        $moveButtonContent .= '<a class="btn btn-default" href="'
1966
                            . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1967
                            . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('moveUp')) . '">'
1968
                            . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . '</a>';
1969
                        if (!$dragDropEnabled) {
1970
                            $displayMoveButtons = true;
1971
                        }
1972
                    } else {
1973
                        $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
1974
                    }
1975
                    // Move element down:
1976 View Code Duplication
                    if ($this->tt_contentData['next'][$row['uid']]) {
1977
                        $params = '&cmd[tt_content][' . $row['uid'] . '][move]= ' . $this->tt_contentData['next'][$row['uid']];
1978
                        $moveButtonContent .= '<a class="btn btn-default" href="'
1979
                            . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1980
                            . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('moveDown')) . '">'
1981
                            . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . '</a>';
1982
                        if (!$dragDropEnabled) {
1983
                            $displayMoveButtons = true;
1984
                        }
1985
                    } else {
1986
                        $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
1987
                    }
1988
                    if ($displayMoveButtons) {
1989
                        $out .= '<div class="btn-group btn-group-sm" role="group">' . $moveButtonContent . '</div>';
1990
                    }
1991
                }
1992
            }
1993
        }
1994
        $allowDragAndDrop = $this->isDragAndDropAllowed($row);
1995
        $additionalIcons = [];
1996
        if ($row['sys_language_uid'] > 0 && $this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid'])) {
1997
            $allowDragAndDrop = false;
1998
        }
1999
        $additionalIcons[] = $this->getIcon('tt_content', $row) . ' ';
2000
        $additionalIcons[] = $langMode ? $this->languageFlag($row['sys_language_uid'], false) : '';
2001
        // Get record locking status:
2002
        if ($lockInfo = BackendUtility::isRecordLocked('tt_content', $row['uid'])) {
2003
            $additionalIcons[] = '<a href="#" data-toggle="tooltip" data-title="' . htmlspecialchars($lockInfo['msg']) . '">'
2004
                . $this->iconFactory->getIcon('warning-in-use', Icon::SIZE_SMALL)->render() . '</a>';
2005
        }
2006
        // Call stats information hook
2007
        $_params = ['tt_content', $row['uid'], &$row];
2008 View Code Duplication
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'] ?? [] as $_funcRef) {
2009
            $additionalIcons[] = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2010
        }
2011
2012
        // Wrap the whole header
2013
        // NOTE: end-tag for <div class="t3-page-ce-body"> is in getTable_tt_content()
2014
        return '<div class="t3-page-ce-header ' . ($allowDragAndDrop ? 't3-page-ce-header-draggable t3js-page-ce-draghandle' : '') . '">
2015
					<div class="t3-page-ce-header-icons-left">' . implode('', $additionalIcons) . '</div>
2016
					<div class="t3-page-ce-header-icons-right">' . ($out ? '<div class="btn-toolbar">' . $out . '</div>' : '') . '</div>
2017
				</div>
2018
				<div class="t3-page-ce-body">';
2019
    }
2020
2021
    /**
2022
     * Determine whether Drag & Drop should be allowed
2023
     *
2024
     * @param array $row
2025
     * @return bool
2026
     */
2027
    protected function isDragAndDropAllowed(array $row)
2028
    {
2029
        if ($this->getBackendUser()->isAdmin()
2030
            || ((int)$row['editlock'] === 0 && (int)$this->pageinfo['editlock'] === 0)
2031
            && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)
2032
            && $this->getBackendUser()->checkAuthMode('tt_content', 'CType', $row['CType'], $GLOBALS['TYPO3_CONF_VARS']['BE']['explicitADmode'])
2033
        ) {
2034
            return true;
2035
        }
2036
        return false;
2037
    }
2038
2039
    /**
2040
     * Draws the preview content for a content element
2041
     *
2042
     * @param array $row Content element
2043
     * @return string HTML
2044
     * @throws \UnexpectedValueException
2045
     */
2046
    public function tt_content_drawItem($row)
2047
    {
2048
        $out = '';
2049
        $outHeader = '';
2050
        // Make header:
2051
2052
        if ($row['header']) {
2053
            $infoArr = [];
2054
            $this->getProcessedValue('tt_content', 'header_position,header_layout,header_link', $row, $infoArr);
2055
            $hiddenHeaderNote = '';
2056
            // If header layout is set to 'hidden', display an accordant note:
2057
            if ($row['header_layout'] == 100) {
2058
                $hiddenHeaderNote = ' <em>[' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.hidden')) . ']</em>';
2059
            }
2060
            $outHeader = $row['date']
2061
                ? htmlspecialchars($this->itemLabels['date'] . ' ' . BackendUtility::date($row['date'])) . '<br />'
2062
                : '';
2063
            $outHeader .= '<strong>' . $this->linkEditContent($this->renderText($row['header']), $row)
2064
                . $hiddenHeaderNote . '</strong><br />';
2065
        }
2066
        // Make content:
2067
        $infoArr = [];
2068
        $drawItem = true;
2069
        // Hook: Render an own preview of a record
2070
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem'] ?? [] as $className) {
2071
            $hookObject = GeneralUtility::makeInstance($className);
2072
            if (!$hookObject instanceof PageLayoutViewDrawItemHookInterface) {
2073
                throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawItemHookInterface::class, 1218547409);
2074
            }
2075
            $hookObject->preProcess($this, $drawItem, $outHeader, $out, $row);
2076
        }
2077
2078
        // If the previous hook did not render something,
2079
        // then check if a Fluid-based preview template was defined for this CType
2080
        // and render it via Fluid. Possible option:
2081
        // mod.web_layout.tt_content.preview.media = EXT:site_mysite/Resources/Private/Templates/Preview/Media.html
2082
        if ($drawItem) {
2083
            $tsConfig = BackendUtility::getModTSconfig($row['pid'], 'mod.web_layout.tt_content.preview');
2084
            $fluidTemplateFile = '';
2085
2086
            if (
2087
                $row['CType'] === 'list' && !empty($row['list_type'])
2088
                && !empty($tsConfig['properties']['list.'][$row['list_type']])
2089
            ) {
2090
                $fluidTemplateFile = $tsConfig['properties']['list.'][$row['list_type']];
2091
            } elseif (!empty($tsConfig['properties'][$row['CType']])) {
2092
                $fluidTemplateFile = $tsConfig['properties'][$row['CType']];
2093
            }
2094
2095
            if ($fluidTemplateFile) {
2096
                $fluidTemplateFile = GeneralUtility::getFileAbsFileName($fluidTemplateFile);
2097
                if ($fluidTemplateFile) {
2098
                    try {
2099
                        /** @var StandaloneView $view */
2100
                        $view = GeneralUtility::makeInstance(StandaloneView::class);
2101
                        $view->setTemplatePathAndFilename($fluidTemplateFile);
2102
                        $view->assignMultiple($row);
2103
                        if (!empty($row['pi_flexform'])) {
2104
                            /** @var FlexFormService $flexFormService */
2105
                            $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
2106
                            $view->assign('pi_flexform_transformed', $flexFormService->convertFlexFormContentToArray($row['pi_flexform']));
2107
                        }
2108
                        $out = $view->render();
2109
                        $drawItem = false;
2110
                    } catch (\Exception $e) {
2111
                        // Catch any exception to avoid breaking the view
2112
                    }
2113
                }
2114
            }
2115
        }
2116
2117
        // Draw preview of the item depending on its CType (if not disabled by previous hook):
2118
        if ($drawItem) {
2119
            switch ($row['CType']) {
2120
                case 'header':
2121
                    if ($row['subheader']) {
2122
                        $out .= $this->linkEditContent($this->renderText($row['subheader']), $row) . '<br />';
2123
                    }
2124
                    break;
2125
                case 'bullets':
2126
                case 'table':
2127 View Code Duplication
                    if ($row['bodytext']) {
2128
                        $out .= $this->linkEditContent($this->renderText($row['bodytext']), $row) . '<br />';
2129
                    }
2130
                    break;
2131
                case 'uploads':
2132
                    if ($row['media']) {
2133
                        $out .= $this->linkEditContent($this->getThumbCodeUnlinked($row, 'tt_content', 'media'), $row) . '<br />';
2134
                    }
2135
                    break;
2136
                case 'menu':
2137
                    $contentType = $this->CType_labels[$row['CType']];
2138
                    $out .= $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $row) . '<br />';
2139
                    // Add Menu Type
2140
                    $menuTypeLabel = $this->getLanguageService()->sL(
2141
                        BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'menu_type', $row['menu_type'])
2142
                    );
2143
                    $menuTypeLabel = $menuTypeLabel ?: 'invalid menu type';
2144
                    $out .= $this->linkEditContent($menuTypeLabel, $row);
2145
                    if ($row['menu_type'] !== '2' && ($row['pages'] || $row['selected_categories'])) {
2146
                        // Show pages if menu type is not "Sitemap"
2147
                        $out .= ':' . $this->linkEditContent($this->generateListForCTypeMenu($row), $row) . '<br />';
2148
                    }
2149
                    break;
2150
                case 'shortcut':
2151
                    if (!empty($row['records'])) {
2152
                        $shortcutContent = [];
2153
                        $recordList = explode(',', $row['records']);
2154
                        foreach ($recordList as $recordIdentifier) {
2155
                            $split = BackendUtility::splitTable_Uid($recordIdentifier);
2156
                            $tableName = empty($split[0]) ? 'tt_content' : $split[0];
2157
                            $shortcutRecord = BackendUtility::getRecord($tableName, $split[1]);
2158
                            if (is_array($shortcutRecord)) {
2159
                                $icon = $this->iconFactory->getIconForRecord($tableName, $shortcutRecord, Icon::SIZE_SMALL)->render();
2160
                                $icon = BackendUtility::wrapClickMenuOnIcon(
2161
                                    $icon,
2162
                                    $tableName,
2163
                                    $shortcutRecord['uid']
2164
                                );
2165
                                $shortcutContent[] = $icon
2166
                                    . htmlspecialchars(BackendUtility::getRecordTitle($tableName, $shortcutRecord));
2167
                            }
2168
                        }
2169
                        $out .= implode('<br />', $shortcutContent) . '<br />';
2170
                    }
2171
                    break;
2172
                case 'list':
2173
                    $hookOut = '';
2174
                    $_params = ['pObj' => &$this, 'row' => $row, 'infoArr' => $infoArr];
2175
                    foreach (
2176
                        $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'][$row['list_type']] ??
2177
                        $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['_DEFAULT'] ??
2178
                        [] as $_funcRef
2179
                    ) {
2180
                        $hookOut .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
2181
                    }
2182
                    if ((string)$hookOut !== '') {
2183
                        $out .= $hookOut;
2184
                    } elseif (!empty($row['list_type'])) {
2185
                        $label = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'list_type', $row['list_type']);
2186
                        if (!empty($label)) {
2187
                            $out .= $this->linkEditContent('<strong>' . htmlspecialchars($this->getLanguageService()->sL($label)) . '</strong>', $row) . '<br />';
2188 View Code Duplication
                        } else {
2189
                            $message = sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'), $row['list_type']);
2190
                            $out .= '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
2191
                        }
2192
                    } else {
2193
                        $out .= '<strong>' . $this->getLanguageService()->getLL('noPluginSelected') . '</strong>';
2194
                    }
2195
                    $out .= htmlspecialchars($this->getLanguageService()->sL(
2196
                            BackendUtility::getLabelFromItemlist('tt_content', 'pages', $row['pages'])
2197
                        )) . '<br />';
2198
                    break;
2199
                default:
2200
                    $contentType = $this->CType_labels[$row['CType']];
2201
2202
                    if (isset($contentType)) {
2203
                        $out .= $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $row) . '<br />';
2204 View Code Duplication
                        if ($row['bodytext']) {
2205
                            $out .= $this->linkEditContent($this->renderText($row['bodytext']), $row) . '<br />';
2206
                        }
2207
                        if ($row['image']) {
2208
                            $out .= $this->linkEditContent($this->getThumbCodeUnlinked($row, 'tt_content', 'image'), $row) . '<br />';
2209
                        }
2210 View Code Duplication
                    } else {
2211
                        $message = sprintf(
2212
                            $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'),
2213
                            $row['CType']
2214
                        );
2215
                        $out .= '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
2216
                    }
2217
            }
2218
        }
2219
        // Wrap span-tags:
2220
        $out = '
2221
			<span class="exampleContent">' . $out . '</span>';
2222
        // Add header:
2223
        $out = $outHeader . $out;
2224
        // Return values:
2225
        if ($this->isDisabled('tt_content', $row)) {
2226
            return '<span class="text-muted">' . $out . '</span>';
2227
        }
2228
        return $out;
2229
    }
2230
2231
    /**
2232
     * Generates a list of selected pages or categories for the CType menu
2233
     *
2234
     * @param array $row row from pages
2235
     * @return string
2236
     */
2237
    protected function generateListForCTypeMenu(array $row)
2238
    {
2239
        $table = 'pages';
2240
        $field = 'pages';
2241
        // get categories instead of pages
2242
        if (strpos($row['menu_type'], 'categorized_') !== false) {
2243
            $table = 'sys_category';
2244
            $field = 'selected_categories';
2245
        }
2246
        if (trim($row[$field]) === '') {
2247
            return '';
2248
        }
2249
        $content = '';
2250
        $uidList = explode(',', $row[$field]);
2251
        foreach ($uidList as $uid) {
2252
            $uid = (int)$uid;
2253
            $record = BackendUtility::getRecord($table, $uid, 'title');
2254
            $content .= '<br>' . $record['title'] . ' (' . $uid . ')';
2255
        }
2256
        return $content;
2257
    }
2258
2259
    /**
2260
     * Filters out all tt_content uids which are already translated so only non-translated uids is left.
2261
     * Selects across columns, but within in the same PID. Columns are expect to be the same
2262
     * for translations and original but this may be a conceptual error (?)
2263
     *
2264
     * @param array $defaultLanguageUids Numeric array with uids of tt_content elements in the default language
2265
     * @param int $id Page pid
2266
     * @param int $lP Sys language UID
2267
     * @return array Modified $defLanguageCount
2268
     */
2269
    public function getNonTranslatedTTcontentUids($defaultLanguageUids, $id, $lP)
2270
    {
2271
        if ($lP && !empty($defaultLanguageUids)) {
2272
            // Select all translations here:
2273
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2274
                ->getQueryBuilderForTable('tt_content');
2275
            $queryBuilder->getRestrictions()
2276
                ->removeAll()
2277
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2278
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class, null, false));
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2278
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class, null, /** @scrutinizer ignore-type */ false));
Loading history...
2279
            $queryBuilder
2280
                ->select('*')
2281
                ->from('tt_content')
2282
                ->where(
2283
                    $queryBuilder->expr()->eq(
2284
                        'sys_language_uid',
2285
                        $queryBuilder->createNamedParameter($lP, \PDO::PARAM_INT)
2286
                    ),
2287
                    $queryBuilder->expr()->in(
2288
                        'l18n_parent',
2289
                        $queryBuilder->createNamedParameter($defaultLanguageUids, Connection::PARAM_INT_ARRAY)
2290
                    )
2291
                );
2292
2293
            $result = $queryBuilder->execute();
2294
2295
            // Flip uids:
2296
            $defaultLanguageUids = array_flip($defaultLanguageUids);
2297
            // Traverse any selected elements and unset original UID if any:
2298
            while ($row = $result->fetch()) {
2299
                BackendUtility::workspaceOL('tt_content', $row);
2300
                unset($defaultLanguageUids[$row['l18n_parent']]);
2301
            }
2302
            // Flip again:
2303
            $defaultLanguageUids = array_keys($defaultLanguageUids);
2304
        }
2305
        return $defaultLanguageUids;
2306
    }
2307
2308
    /**
2309
     * Creates button which is used to create copies of records..
2310
     *
2311
     * @param array $defaultLanguageUids Numeric array with uids of tt_content elements in the default language
2312
     * @param int $lP Sys language UID
2313
     * @param int $colPos Column position
2314
     * @return string "Copy languages" button, if available.
2315
     */
2316
    public function newLanguageButton($defaultLanguageUids, $lP, $colPos = 0)
2317
    {
2318
        $lP = (int)$lP;
2319
        if (!$this->doEdit || !$lP) {
2320
            return '';
2321
        }
2322
        $theNewButton = '';
2323
2324
        $localizationTsConfig = BackendUtility::getModTSconfig($this->id, 'mod.web_layout.localization');
2325
        $allowCopy = isset($localizationTsConfig['properties']['enableCopy'])
2326
            ? (int)$localizationTsConfig['properties']['enableCopy'] === 1
2327
            : true;
2328
        $allowTranslate = isset($localizationTsConfig['properties']['enableTranslate'])
2329
            ? (int)$localizationTsConfig['properties']['enableTranslate'] === 1
2330
            : true;
2331
2332
        if (!empty($this->languageHasTranslationsCache[$lP])) {
2333
            if (isset($this->languageHasTranslationsCache[$lP]['hasStandAloneContent'])) {
2334
                $allowTranslate = false;
2335
            }
2336
            if (isset($this->languageHasTranslationsCache[$lP]['hasTranslations'])) {
2337
                $allowCopy = false;
2338
            }
2339
        }
2340
2341
        if (isset($this->contentElementCache[$lP][$colPos]) && is_array($this->contentElementCache[$lP][$colPos])) {
2342
            foreach ($this->contentElementCache[$lP][$colPos] as $record) {
2343
                $key = array_search($record['l10n_source'], $defaultLanguageUids);
2344
                if ($key !== false) {
2345
                    unset($defaultLanguageUids[$key]);
2346
                }
2347
            }
2348
        }
2349
2350
        if (!empty($defaultLanguageUids)) {
2351
            $theNewButton =
2352
                '<input'
2353
                    . ' class="btn btn-default t3js-localize"'
2354
                    . ' type="button"'
2355
                    . ' disabled'
2356
                    . ' value="' . htmlspecialchars($this->getLanguageService()->getLL('newPageContent_translate')) . '"'
2357
                    . ' data-has-elements="' . (int)!empty($this->contentElementCache[$lP][$colPos]) . '"'
2358
                    . ' data-allow-copy="' . (int)$allowCopy . '"'
2359
                    . ' data-allow-translate="' . (int)$allowTranslate . '"'
2360
                    . ' data-table="tt_content"'
2361
                    . ' data-page-id="' . (int)GeneralUtility::_GP('id') . '"'
2362
                    . ' data-language-id="' . $lP . '"'
2363
                    . ' data-language-name="' . htmlspecialchars($this->tt_contentConfig['languageCols'][$lP]) . '"'
2364
                    . ' data-colpos-id="' . $colPos . '"'
2365
                    . ' data-colpos-name="' . BackendUtility::getProcessedValue('tt_content', 'colPos', $colPos) . '"'
2366
                . '/>';
2367
        }
2368
2369
        return '<div class="t3-page-lang-copyce">' . $theNewButton . '</div>';
2370
    }
2371
2372
    /**
2373
     * Creates onclick-attribute content for a new content element
2374
     *
2375
     * @param int $id Page id where to create the element.
2376
     * @param int $colPos Preset: Column position value
2377
     * @param int $sys_language Preset: Sys langauge value
2378
     * @return string String for onclick attribute.
2379
     * @see getTable_tt_content()
2380
     */
2381
    public function newContentElementOnClick($id, $colPos, $sys_language)
2382
    {
2383
        if ($this->option_newWizard) {
2384
            $tsConfig = BackendUtility::getModTSconfig($id, 'mod');
2385
            $routeName = $tsConfig['properties']['newContentElementWizard.']['override']
2386
                ?? 'new_content_element_wizard';
2387
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
2388
            $url = $uriBuilder->buildUriFromRoute($routeName, [
2389
                'id' => $id,
2390
                'colPos' => $colPos,
2391
                'sys_language_uid' => $sys_language,
2392
                'uid_pid' => $id,
2393
                'returnUrl' => rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'))
2394
            ]);
2395
            $onClick = 'window.location.href=' . GeneralUtility::quoteJSvalue((string)$url) . ';';
2396
        } else {
2397
            $onClick = BackendUtility::editOnClick('&edit[tt_content][' . $id . ']=new&defVals[tt_content][colPos]='
2398
                . $colPos . '&defVals[tt_content][sys_language_uid]=' . $sys_language);
2399
        }
2400
        return $onClick;
2401
    }
2402
2403
    /**
2404
     * Will create a link on the input string and possibly a big button after the string which links to editing in the RTE.
2405
     * Used for content element content displayed so the user can click the content / "Edit in Rich Text Editor" button
2406
     *
2407
     * @param string $str String to link. Must be prepared for HTML output.
2408
     * @param array $row The row.
2409
     * @return string If the whole thing was editable ($this->doEdit) $str is return with link around. Otherwise just $str.
2410
     * @see getTable_tt_content()
2411
     */
2412
    public function linkEditContent($str, $row)
2413
    {
2414
        if ($this->doEdit && $this->getBackendUser()->recordEditAccessInternals('tt_content', $row)) {
2415
            $urlParameters = [
2416
                'edit' => [
2417
                    'tt_content' => [
2418
                        $row['uid'] => 'edit'
2419
                    ]
2420
                ],
2421
                'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $row['uid']
2422
            ];
2423
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
2424
            $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
2425
            // Return link
2426
            return '<a href="' . htmlspecialchars($url) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">' . $str . '</a>';
2427
        }
2428
        return $str;
2429
    }
2430
2431
    /**
2432
     * Make selector box for creating new translation in a language
2433
     * Displays only languages which are not yet present for the current page and
2434
     * that are not disabled with page TS.
2435
     *
2436
     * @param int $id Page id for which to create a new translation record of pages
2437
     * @return string <select> HTML element (if there were items for the box anyways...)
2438
     * @see getTable_tt_content()
2439
     */
2440
    public function languageSelector($id)
2441
    {
2442
        if ($this->getBackendUser()->check('tables_modify', 'pages')) {
2443
            // First, select all
2444
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
2445
            $queryBuilder->getRestrictions()->removeAll();
2446
            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
2447
            $statement = $queryBuilder->select('uid', 'title')
2448
                ->from('sys_language')
2449
                ->orderBy('sorting')
2450
                ->execute();
2451
            $availableTranslations = [];
2452
            while ($row = $statement->fetch()) {
2453
                if ($this->getBackendUser()->checkLanguageAccess($row['uid'])) {
2454
                    $availableTranslations[(int)$row['uid']] = $row['title'];
2455
                }
2456
            }
2457
            // Then, subtract the languages which are already on the page:
2458
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
2459
            $queryBuilder->getRestrictions()->removeAll();
2460
            $queryBuilder->select('sys_language.uid AS uid', 'sys_language.title AS title')
2461
                ->from('sys_language')
2462
                ->join(
2463
                    'sys_language',
2464
                    'pages',
2465
                    'pages',
2466
                    $queryBuilder->expr()->eq('sys_language.uid', $queryBuilder->quoteIdentifier('pages.' . $GLOBALS['TCA']['pages']['ctrl']['languageField']))
2467
                )
2468
                ->where(
2469
                    $queryBuilder->expr()->eq(
2470
                        'pages.deleted',
2471
                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
2472
                    ),
2473
                    $queryBuilder->expr()->eq(
2474
                        'pages.' . $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
2475
                        $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
2476
                    ),
2477
                    $queryBuilder->expr()->orX(
2478
                        $queryBuilder->expr()->gte(
2479
                            'pages.t3ver_state',
2480
                            $queryBuilder->createNamedParameter(
2481
                                (string)new VersionState(VersionState::DEFAULT_STATE),
2482
                                \PDO::PARAM_INT
2483
                            )
2484
                        ),
2485
                        $queryBuilder->expr()->eq(
2486
                            'pages.t3ver_wsid',
2487
                            $queryBuilder->createNamedParameter($this->getBackendUser()->workspace, \PDO::PARAM_INT)
2488
                        )
2489
                    )
2490
                )
2491
                ->groupBy(
2492
                    'pages.' . $GLOBALS['TCA']['pages']['ctrl']['languageField'],
2493
                    'sys_language.uid',
2494
                    'sys_language.pid',
2495
                    'sys_language.tstamp',
2496
                    'sys_language.hidden',
2497
                    'sys_language.title',
2498
                    'sys_language.language_isocode',
2499
                    'sys_language.static_lang_isocode',
2500
                    'sys_language.flag',
2501
                    'sys_language.sorting'
2502
                )
2503
                ->orderBy('sys_language.sorting');
2504
            if (!$this->getBackendUser()->isAdmin()) {
2505
                $queryBuilder->andWhere(
2506
                    $queryBuilder->expr()->eq(
2507
                        'sys_language.hidden',
2508
                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
2509
                    )
2510
                );
2511
            }
2512
            $statement = $queryBuilder->execute();
2513
            while ($row = $statement->fetch()) {
2514
                unset($availableTranslations[(int)$row['uid']]);
2515
            }
2516
            // Remove disallowed languages
2517
            if (!empty($availableTranslations)
2518
                && !$this->getBackendUser()->isAdmin()
2519
                && $this->getBackendUser()->groupData['allowed_languages'] !== ''
2520
            ) {
2521
                $allowed_languages = array_flip(explode(',', $this->getBackendUser()->groupData['allowed_languages']));
2522
                if (!empty($allowed_languages)) {
2523
                    foreach ($availableTranslations as $key => $value) {
2524
                        if (!isset($allowed_languages[$key]) && $key != 0) {
2525
                            unset($availableTranslations[$key]);
2526
                        }
2527
                    }
2528
                }
2529
            }
2530
            // Remove disabled languages
2531
            $modSharedTSconfig = BackendUtility::getModTSconfig($id, 'mod.SHARED');
2532
            $disableLanguages = isset($modSharedTSconfig['properties']['disableLanguages'])
2533
                ? GeneralUtility::trimExplode(',', $modSharedTSconfig['properties']['disableLanguages'], true)
2534
                : [];
2535
            if (!empty($availableTranslations) && !empty($disableLanguages)) {
2536
                foreach ($disableLanguages as $language) {
2537
                    if ($language != 0 && isset($availableTranslations[$language])) {
2538
                        unset($availableTranslations[$language]);
2539
                    }
2540
                }
2541
            }
2542
            // If any languages are left, make selector:
2543 View Code Duplication
            if (!empty($availableTranslations)) {
2544
                $output = '<option value=""></option>';
2545
                foreach ($availableTranslations as $languageUid => $languageTitle) {
2546
                    // Build localize command URL to DataHandler (tce_db)
2547
                    // which redirects to FormEngine (record_edit)
2548
                    // which, when finished editing should return back to the current page (returnUrl)
2549
                    $parameters = [
2550
                        'justLocalized' => 'pages:' . $id . ':' . $languageUid,
2551
                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
2552
                    ];
2553
                    $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
2554
                    $redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', $parameters);
2555
                    $targetUrl = BackendUtility::getLinkToDataHandlerAction(
2556
                        '&cmd[pages][' . $id . '][localize]=' . $languageUid,
2557
                        $redirectUrl
2558
                    );
2559
2560
                    $output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
2561
                }
2562
2563
                return '<div class="form-inline form-inline-spaced">'
2564
                    . '<div class="form-group">'
2565
                    . '<label for="createNewLanguage">'
2566
                    . htmlspecialchars($this->getLanguageService()->getLL('new_language'))
2567
                    . '</label>'
2568
                    . '<select class="form-control input-sm" name="createNewLanguage" onchange="window.location.href=this.options[this.selectedIndex].value">'
2569
                    . $output
2570
                    . '</select></div></div>';
2571
            }
2572
        }
2573
        return '';
2574
    }
2575
2576
    /**
2577
     * Traverse the result pointer given, adding each record to array and setting some internal values at the same time.
2578
     *
2579
     * @param Statement $result DBAL Statement
2580
     * @param string $table Table name defaulting to tt_content
2581
     * @return array The selected rows returned in this array.
2582
     */
2583
    public function getResult(Statement $result, string $table = 'tt_content'): array
2584
    {
2585
        $output = [];
2586
        // Traverse the result:
2587
        while ($row = $result->fetch()) {
2588
            BackendUtility::workspaceOL($table, $row, -99, true);
2589
            if ($row) {
2590
                // Add the row to the array:
2591
                $output[] = $row;
2592
            }
2593
        }
2594
        $this->generateTtContentDataArray($output);
2595
        // Return selected records
2596
        return $output;
2597
    }
2598
2599
    /********************************
2600
     *
2601
     * Various helper functions
2602
     *
2603
     ********************************/
2604
2605
    /**
2606
     * Initializes the clipboard for generating paste links
2607
     *
2608
     *
2609
     * @see \TYPO3\CMS\Recordlist\RecordList::main()
2610
     * @see \TYPO3\CMS\Backend\Controller\ContextMenuController::clipboardAction()
2611
     * @see \TYPO3\CMS\Filelist\Controller\FileListController::indexAction()
2612
     */
2613
    protected function initializeClipboard()
2614
    {
2615
        // Start clipboard
2616
        $this->clipboard = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Clipboard\Clipboard::class);
2617
2618
        // Initialize - reads the clipboard content from the user session
2619
        $this->clipboard->initializeClipboard();
2620
2621
        // This locks the clipboard to the Normal for this request.
2622
        $this->clipboard->lockToNormal();
2623
2624
        // Clean up pad
2625
        $this->clipboard->cleanCurrent();
2626
2627
        // Save the clipboard content
2628
        $this->clipboard->endClipboard();
2629
    }
2630
2631
    /**
2632
     * Generates the data for previous and next elements which is needed for movements.
2633
     *
2634
     * @param array $rowArray
2635
     */
2636
    protected function generateTtContentDataArray(array $rowArray)
2637
    {
2638
        if (empty($this->tt_contentData)) {
2639
            $this->tt_contentData = [
2640
                'nextThree' => [],
2641
                'next' => [],
2642
                'prev' => [],
2643
            ];
2644
        }
2645
        foreach ($rowArray as $key => $value) {
2646
            // Create the list of the next three ids (for editing links...)
2647
            for ($i = 0; $i < $this->nextThree; $i++) {
2648
                if (isset($rowArray[$key - $i])
2649
                    && !GeneralUtility::inList($this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']], $value['uid'])
2650
                ) {
2651
                    $this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']] .= $value['uid'] . ',';
2652
                }
2653
            }
2654
2655
            // Create information for next and previous content elements
2656
            if (isset($rowArray[$key - 1])) {
2657
                if (isset($rowArray[$key - 2])) {
2658
                    $this->tt_contentData['prev'][$value['uid']] = -$rowArray[$key - 2]['uid'];
2659
                } else {
2660
                    $this->tt_contentData['prev'][$value['uid']] = $value['pid'];
2661
                }
2662
                $this->tt_contentData['next'][$rowArray[$key - 1]['uid']] = -$value['uid'];
2663
            }
2664
        }
2665
    }
2666
2667
    /**
2668
     * Counts and returns the number of records on the page with $pid
2669
     *
2670
     * @param string $table Table name
2671
     * @param int $pid Page id
2672
     * @return int Number of records.
2673
     */
2674
    public function numberOfRecords($table, $pid)
2675
    {
2676
        $count = 0;
2677
        if ($GLOBALS['TCA'][$table]) {
2678
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2679
                ->getQueryBuilderForTable($table);
2680
            $queryBuilder->getRestrictions()
2681
                ->removeAll()
2682
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2683
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2684
            $count = (int)$queryBuilder->count('uid')
2685
                ->from($table)
2686
                ->where(
2687
                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT))
2688
                )
2689
                ->execute()
2690
                ->fetchColumn();
2691
        }
2692
2693
        return $count;
2694
    }
2695
2696
    /**
2697
     * Processing of larger amounts of text (usually from RTE/bodytext fields) with word wrapping etc.
2698
     *
2699
     * @param string $input Input string
2700
     * @return string Output string
2701
     */
2702
    public function renderText($input)
2703
    {
2704
        $input = strip_tags($input);
2705
        $input = GeneralUtility::fixed_lgd_cs($input, 1500);
2706
        return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
2707
    }
2708
2709
    /**
2710
     * Creates the icon image tag for record from table and wraps it in a link which will trigger the click menu.
2711
     *
2712
     * @param string $table Table name
2713
     * @param array $row Record array
2714
     * @param string $enabledClickMenuItems Passthrough to wrapClickMenuOnIcon
2715
     * @return string HTML for the icon
2716
     */
2717
    public function getIcon($table, $row, $enabledClickMenuItems = '')
2718
    {
2719
        // Initialization
2720
        $toolTip = BackendUtility::getRecordToolTip($row, 'tt_content');
2721
        $icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
2722
        $this->counter++;
2723
        // The icon with link
2724
        if ($this->getBackendUser()->recordEditAccessInternals($table, $row)) {
2725
            $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
2726
        }
2727
        return $icon;
2728
    }
2729
2730
    /**
2731
     * Creates processed values for all field names in $fieldList based on values from $row array.
2732
     * The result is 'returned' through $info which is passed as a reference
2733
     *
2734
     * @param string $table Table name
2735
     * @param string $fieldList Comma separated list of fields.
2736
     * @param array $row Record from which to take values for processing.
2737
     * @param array $info Array to which the processed values are added.
2738
     */
2739
    public function getProcessedValue($table, $fieldList, array $row, array &$info)
2740
    {
2741
        // Splitting values from $fieldList
2742
        $fieldArr = explode(',', $fieldList);
2743
        // Traverse fields from $fieldList
2744
        foreach ($fieldArr as $field) {
2745
            if ($row[$field]) {
2746
                $info[] = '<strong>' . htmlspecialchars($this->itemLabels[$field]) . '</strong> '
2747
                    . htmlspecialchars(BackendUtility::getProcessedValue($table, $field, $row[$field]));
2748
            }
2749
        }
2750
    }
2751
2752
    /**
2753
     * Returns TRUE, if the record given as parameters is NOT visible based on hidden/starttime/endtime (if available)
2754
     *
2755
     * @param string $table Tablename of table to test
2756
     * @param array $row Record row.
2757
     * @return bool Returns TRUE, if disabled.
2758
     */
2759
    public function isDisabled($table, $row)
2760
    {
2761
        $enableCols = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
2762
        return $enableCols['disabled'] && $row[$enableCols['disabled']]
2763
            || $enableCols['starttime'] && $row[$enableCols['starttime']] > $GLOBALS['EXEC_TIME']
2764
            || $enableCols['endtime'] && $row[$enableCols['endtime']] && $row[$enableCols['endtime']] < $GLOBALS['EXEC_TIME'];
2765
    }
2766
2767
    /**
2768
     * Returns icon for "no-edit" of a record.
2769
     * Basically, the point is to signal that this record could have had an edit link if
2770
     * the circumstances were right. A placeholder for the regular edit icon...
2771
     *
2772
     * @param string $label Label key from LOCAL_LANG
2773
     * @return string IMG tag for icon.
2774
     */
2775
    public function noEditIcon($label = 'noEditItems')
2776
    {
2777
        $title = htmlspecialchars($this->getLanguageService()->getLL($label));
2778
        return '<span title="' . $title . '">' . $this->iconFactory->getIcon('status-edit-read-only', Icon::SIZE_SMALL)->render() . '</span>';
2779
    }
2780
2781
    /*****************************************
2782
     *
2783
     * External renderings
2784
     *
2785
     *****************************************/
2786
2787
    /**
2788
     * Creates a menu of the tables that can be listed by this function
2789
     * Only tables which has records on the page will be included.
2790
     * Notice: The function also fills in the internal variable $this->activeTables with icon/titles.
2791
     *
2792
     * @param int $id Page id from which we are listing records (the function will look up if there are records on the page)
2793
     * @return string HTML output.
2794
     */
2795
    public function getTableMenu($id)
2796
    {
2797
        // Initialize:
2798
        $this->activeTables = [];
2799
        $theTables = ['tt_content'];
2800
        // External tables:
2801
        if (is_array($this->externalTables)) {
2802
            $theTables = array_unique(array_merge($theTables, array_keys($this->externalTables)));
2803
        }
2804
        $out = '';
2805
        // Traverse tables to check:
2806
        foreach ($theTables as $tName) {
2807
            // Check access and whether the proper extensions are loaded:
2808
            if ($this->getBackendUser()->check('tables_select', $tName)
2809
                && (
2810
                    isset($this->externalTables[$tName])
2811
                    || $tName === 'fe_users' || $tName === 'tt_content'
2812
                    || \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded($tName)
2813
                )
2814
            ) {
2815
                // Make query to count records from page:
2816
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2817
                    ->getQueryBuilderForTable($tName);
2818
                $queryBuilder->getRestrictions()
2819
                    ->removeAll()
2820
                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2821
                    ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2822
                $count = $queryBuilder->count('uid')
2823
                    ->from($tName)
2824
                    ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
2825
                    ->execute()
2826
                    ->fetchColumn();
2827
                // If records were found (or if "tt_content" is the table...):
2828
                if ($count || $tName === 'tt_content') {
2829
                    // Add row to menu:
2830
                    $out .= '
2831
					<td><a href="#' . $tName . '" title="' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title'])) . '"></a>'
2832
                        . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
2833
                        . '</td>';
2834
                    // ... and to the internal array, activeTables we also add table icon and title (for use elsewhere)
2835
                    $title = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title']))
2836
                        . ': ' . $count . ' ' . htmlspecialchars($this->getLanguageService()->getLL('records'));
2837
                    $this->activeTables[$tName] = '<span title="' . $title . '">'
2838
                        . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
2839
                        . '</span>'
2840
                        . '&nbsp;' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title']));
2841
                }
2842
            }
2843
        }
2844
        // Wrap cells in table tags:
2845
        $out = '
2846
            <!--
2847
                Menu of tables on the page (table menu)
2848
            -->
2849
            <table border="0" cellpadding="0" cellspacing="0" id="typo3-page-tblMenu">
2850
				<tr>' . $out . '
2851
                </tr>
2852
			</table>';
2853
        // Return the content:
2854
        return $out;
2855
    }
2856
2857
    /**
2858
     * Create thumbnail code for record/field but not linked
2859
     *
2860
     * @param mixed[] $row Record array
2861
     * @param string $table Table (record is from)
2862
     * @param string $field Field name for which thumbnail are to be rendered.
2863
     * @return string HTML for thumbnails, if any.
2864
     */
2865
    public function getThumbCodeUnlinked($row, $table, $field)
2866
    {
2867
        return BackendUtility::thumbCode($row, $table, $field, '', '', null, 0, '', '', false);
2868
    }
2869
2870
    /**
2871
     * Checks whether translated Content Elements exist in the desired language
2872
     * If so, deny creating new ones via the UI
2873
     *
2874
     * @param array $contentElements
2875
     * @param int $language
2876
     * @return bool
2877
     */
2878
    protected function checkIfTranslationsExistInLanguage(array $contentElements, $language)
2879
    {
2880
        // If in default language, you may always create new entries
2881
        // Also, you may override this strict behavior via user TS Config
2882
        // If you do so, you're on your own and cannot rely on any support by the TYPO3 core
2883
        // We jump out here since we don't need to do the expensive loop operations
2884
        $allowInconsistentLanguageHandling = BackendUtility::getModTSconfig($this->id, 'mod.web_layout.allowInconsistentLanguageHandling');
2885
        if ($language === 0 || $allowInconsistentLanguageHandling['value'] === '1') {
2886
            return false;
2887
        }
2888
        /**
2889
         * Build up caches
2890
         */
2891
        if (!isset($this->languageHasTranslationsCache[$language])) {
2892
            foreach ($contentElements as $columns) {
2893
                foreach ($columns as $contentElement) {
2894 View Code Duplication
                    if ((int)$contentElement['l18n_parent'] === 0) {
2895
                        $this->languageHasTranslationsCache[$language]['hasStandAloneContent'] = true;
2896
                    }
2897 View Code Duplication
                    if ((int)$contentElement['l18n_parent'] > 0) {
2898
                        $this->languageHasTranslationsCache[$language]['hasTranslations'] = true;
2899
                    }
2900
                }
2901
            }
2902
            // Check whether we have a mix of both
2903
            if ($this->languageHasTranslationsCache[$language]['hasStandAloneContent']
2904
                && $this->languageHasTranslationsCache[$language]['hasTranslations']
2905
            ) {
2906
                $message = GeneralUtility::makeInstance(
2907
                    FlashMessage::class,
2908
                    sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $this->languageIconTitles[$language]['title']),
0 ignored issues
show
Bug introduced by
sprintf($this->getLangua...es[$language]['title']) of type string is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2908
                    /** @scrutinizer ignore-type */ sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $this->languageIconTitles[$language]['title']),
Loading history...
2909
                    sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $this->languageIconTitles[$language]['title']),
2910
                    FlashMessage::WARNING
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Core\Messaging\FlashMessage::WARNING of type integer is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2910
                    /** @scrutinizer ignore-type */ FlashMessage::WARNING
Loading history...
2911
                );
2912
                $service = GeneralUtility::makeInstance(FlashMessageService::class);
2913
                $queue = $service->getMessageQueueByIdentifier();
2914
                $queue->addMessage($message);
2915
            }
2916
        }
2917
        if ($this->languageHasTranslationsCache[$language]['hasTranslations']) {
2918
            return true;
2919
        }
2920
        return false;
2921
    }
2922
2923
    /**
2924
     * @return BackendLayoutView
2925
     */
2926
    protected function getBackendLayoutView()
2927
    {
2928
        return GeneralUtility::makeInstance(BackendLayoutView::class);
2929
    }
2930
2931
    /**
2932
     * @return BackendUserAuthentication
2933
     */
2934
    protected function getBackendUser()
2935
    {
2936
        return $GLOBALS['BE_USER'];
2937
    }
2938
2939
    /**
2940
     * @return PageLayoutController
2941
     */
2942
    protected function getPageLayoutController()
2943
    {
2944
        return $GLOBALS['SOBE'];
2945
    }
2946
2947
    /**
2948
     * Initializes the list generation
2949
     *
2950
     * @param int $id Page id for which the list is rendered. Must be >= 0
2951
     * @param string $table Tablename - if extended mode where only one table is listed at a time.
2952
     * @param int $pointer Browsing pointer.
2953
     * @param string $search Search word, if any
2954
     * @param int $levels Number of levels to search down the page tree
2955
     * @param int $showLimit Limit of records to be listed.
2956
     */
2957 View Code Duplication
    public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
2958
    {
2959
        $backendUser = $this->getBackendUserAuthentication();
2960
        // Setting internal variables:
2961
        // sets the parent id
2962
        $this->id = (int)$id;
2963
        if ($GLOBALS['TCA'][$table]) {
2964
            // Setting single table mode, if table exists:
2965
            $this->table = $table;
2966
        }
2967
        $this->firstElementNumber = $pointer;
2968
        $this->searchString = trim($search);
2969
        $this->searchLevels = (int)$levels;
2970
        $this->showLimit = MathUtility::forceIntegerInRange($showLimit, 0, 10000);
2971
        // Setting GPvars:
2972
        $this->csvOutput = (bool)GeneralUtility::_GP('csv');
2973
        $this->sortField = GeneralUtility::_GP('sortField');
2974
        $this->sortRev = GeneralUtility::_GP('sortRev');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...Utility::_GP('sortRev') can also be of type string. However, the property $sortRev is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2975
        $this->displayFields = GeneralUtility::_GP('displayFields');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...y::_GP('displayFields') can also be of type string. However, the property $displayFields is declared as type string[]. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2976
        $this->duplicateField = GeneralUtility::_GP('duplicateField');
2977
        if (GeneralUtility::_GP('justLocalized')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression TYPO3\CMS\Core\Utility\G...y::_GP('justLocalized') of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2978
            $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
2979
        }
2980
        // Init dynamic vars:
2981
        $this->counter = 0;
2982
        $this->JScode = '';
2983
        $this->HTMLcode = '';
2984
        // Limits
2985
        if (isset($this->modTSconfig['properties']['itemsLimitPerTable'])) {
2986
            $this->itemsLimitPerTable = MathUtility::forceIntegerInRange(
2987
                (int)$this->modTSconfig['properties']['itemsLimitPerTable'],
2988
                1,
2989
                10000
2990
            );
2991
        }
2992
        if (isset($this->modTSconfig['properties']['itemsLimitSingleTable'])) {
2993
            $this->itemsLimitSingleTable = MathUtility::forceIntegerInRange(
2994
                (int)$this->modTSconfig['properties']['itemsLimitSingleTable'],
2995
                1,
2996
                10000
2997
            );
2998
        }
2999
3000
        // $table might be NULL at this point in the code. As the expressionBuilder
3001
        // is used to limit returned records based on the page permissions and the
3002
        // uid field of the pages it can hardcoded to work on the pages table.
3003
        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
3004
            ->getQueryBuilderForTable('pages')
3005
            ->expr();
3006
        $permsClause = $expressionBuilder->andX($backendUser->getPagePermsClause(1));
3007
        // This will hide records from display - it has nothing to do with user rights!!
3008
        if ($pidList = $backendUser->getTSConfigVal('options.hideRecords.pages')) {
3009
            $pidList = GeneralUtility::intExplode(',', $pidList, true);
3010
            if (!empty($pidList)) {
3011
                $permsClause->add($expressionBuilder->notIn('pages.uid', $pidList));
3012
            }
3013
        }
3014
        $this->perms_clause = (string)$permsClause;
3015
3016
        // Get configuration of collapsed tables from user uc and merge with sanitized GP vars
3017
        $this->tablesCollapsed = is_array($backendUser->uc['moduleData']['list'])
3018
            ? $backendUser->uc['moduleData']['list']
3019
            : [];
3020
        $collapseOverride = GeneralUtility::_GP('collapse');
3021
        if (is_array($collapseOverride)) {
3022
            foreach ($collapseOverride as $collapseTable => $collapseValue) {
3023
                if (is_array($GLOBALS['TCA'][$collapseTable]) && ($collapseValue == 0 || $collapseValue == 1)) {
3024
                    $this->tablesCollapsed[$collapseTable] = $collapseValue;
3025
                }
3026
            }
3027
            // Save modified user uc
3028
            $backendUser->uc['moduleData']['list'] = $this->tablesCollapsed;
3029
            $backendUser->writeUC($backendUser->uc);
3030
            $returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
3031
            if ($returnUrl !== '') {
3032
                HttpUtility::redirect($returnUrl);
3033
            }
3034
        }
3035
        $this->initializeLanguages();
3036
    }
3037
3038
    /**
3039
     * Traverses the table(s) to be listed and renders the output code for each:
3040
     * The HTML is accumulated in $this->HTMLcode
3041
     * Finishes off with a stopper-gif
3042
     */
3043 View Code Duplication
    public function generateList()
3044
    {
3045
        // Set page record in header
3046
        $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
3047
        $hideTablesArray = GeneralUtility::trimExplode(',', $this->hideTables);
3048
3049
        $backendUser = $this->getBackendUserAuthentication();
3050
3051
        // pre-process tables and add sorting instructions
3052
        $tableNames = array_flip(array_keys($GLOBALS['TCA']));
3053
        foreach ($tableNames as $tableName => &$config) {
3054
            $hideTable = false;
3055
3056
            // Checking if the table should be rendered:
3057
            // Checks that we see only permitted/requested tables:
3058
            if ($this->table && $tableName !== $this->table
3059
                || $this->tableList && !GeneralUtility::inList($this->tableList, $tableName)
3060
                || !$backendUser->check('tables_select', $tableName)
3061
            ) {
3062
                $hideTable = true;
3063
            }
3064
3065
            if (!$hideTable) {
3066
                // Don't show table if hidden by TCA ctrl section
3067
                // Don't show table if hidden by pageTSconfig mod.web_list.hideTables
3068
                $hideTable = $hideTable
3069
                    || !empty($GLOBALS['TCA'][$tableName]['ctrl']['hideTable'])
3070
                    || in_array($tableName, $hideTablesArray, true)
3071
                    || in_array('*', $hideTablesArray, true);
3072
                // Override previous selection if table is enabled or hidden by TSconfig TCA override mod.web_list.table
3073
                if (isset($this->tableTSconfigOverTCA[$tableName . '.']['hideTable'])) {
3074
                    $hideTable = (bool)$this->tableTSconfigOverTCA[$tableName . '.']['hideTable'];
3075
                }
3076
            }
3077
            if ($hideTable) {
3078
                unset($tableNames[$tableName]);
3079
            } else {
3080
                if (isset($this->tableDisplayOrder[$tableName])) {
3081
                    // Copy display order information
3082
                    $tableNames[$tableName] = $this->tableDisplayOrder[$tableName];
3083
                } else {
3084
                    $tableNames[$tableName] = [];
3085
                }
3086
            }
3087
        }
3088
        unset($config);
3089
3090
        $orderedTableNames = GeneralUtility::makeInstance(DependencyOrderingService::class)
3091
            ->orderByDependencies($tableNames);
3092
3093
        foreach ($orderedTableNames as $tableName => $_) {
3094
            // check if we are in single- or multi-table mode
3095
            if ($this->table) {
3096
                $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems'])
3097
                    ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems']
3098
                    : $this->itemsLimitSingleTable;
3099
            } else {
3100
                // if there are no records in table continue current foreach
3101
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
3102
                    ->getQueryBuilderForTable($tableName);
3103
                $queryBuilder->getRestrictions()
3104
                    ->removeAll()
3105
                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
3106
                    ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
3107
                $queryBuilder = $this->addPageIdConstraint($tableName, $queryBuilder);
3108
                $firstRow = $queryBuilder->select('uid')
3109
                    ->from($tableName)
3110
                    ->execute()
3111
                    ->fetch();
3112
                if (!is_array($firstRow)) {
3113
                    continue;
3114
                }
3115
                $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxDBListItems'])
3116
                    ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxDBListItems']
3117
                    : $this->itemsLimitPerTable;
3118
            }
3119
            if ($this->showLimit) {
3120
                $this->iLimit = $this->showLimit;
3121
            }
3122
            // Setting fields to select:
3123
            if ($this->allFields) {
3124
                $fields = $this->makeFieldList($tableName);
3125
                $fields[] = 'tstamp';
3126
                $fields[] = 'crdate';
3127
                $fields[] = '_PATH_';
3128
                $fields[] = '_CONTROL_';
3129
                if (is_array($this->setFields[$tableName])) {
3130
                    $fields = array_intersect($fields, $this->setFields[$tableName]);
3131
                } else {
3132
                    $fields = [];
3133
                }
3134
            } else {
3135
                $fields = [];
3136
            }
3137
3138
            // Finally, render the list:
3139
            $this->HTMLcode .= $this->getTable($tableName, $this->id, implode(',', $fields));
3140
        }
3141
    }
3142
3143
    /**
3144
     * Creates the search box
3145
     *
3146
     * @param bool $formFields If TRUE, the search box is wrapped in its own form-tags
3147
     * @return string HTML for the search box
3148
     */
3149 View Code Duplication
    public function getSearchBox($formFields = true)
3150
    {
3151
        /** @var $iconFactory IconFactory */
3152
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
3153
        $lang = $this->getLanguageService();
3154
        // Setting form-elements, if applicable:
3155
        $formElements = ['', ''];
3156
        if ($formFields) {
3157
            $formElements = [
3158
                '<form action="' . htmlspecialchars(
3159
                    $this->listURL('', '-1', 'firstElementNumber,search_field')
3160
                ) . '" method="post">',
3161
                '</form>'
3162
            ];
3163
        }
3164
        // Make level selector:
3165
        $opt = [];
3166
3167
        // "New" generation of search levels ... based on TS config
3168
        $config = BackendUtility::getPagesTSconfig($this->id);
3169
        $searchLevelsFromTSconfig = $config['mod.']['web_list.']['searchLevel.']['items.'];
3170
        $searchLevelItems = [];
3171
3172
        // get translated labels for search levels from pagets
3173
        foreach ($searchLevelsFromTSconfig as $keySearchLevel => $labelConfigured) {
3174
            $label = $lang->sL('LLL:' . $labelConfigured);
3175
            if ($label === '') {
3176
                $label = $labelConfigured;
3177
            }
3178
            $searchLevelItems[$keySearchLevel] = $label;
3179
        }
3180
3181
        foreach ($searchLevelItems as $kv => $label) {
3182
            $opt[] = '<option value="' . $kv . '"' . ($kv === $this->searchLevels ? ' selected="selected"' : '') . '>' . htmlspecialchars(
3183
                    $label
3184
                ) . '</option>';
3185
        }
3186
        $lMenu = '<select class="form-control" name="search_levels" title="' . htmlspecialchars(
3187
                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.search_levels')
3188
            ) . '" id="search_levels">' . implode('', $opt) . '</select>';
3189
        // Table with the search box:
3190
        $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') . ';">
3191
			' . $formElements[0] . '
3192
                <div id="typo3-dblist-search">
3193
                    <div class="panel panel-default">
3194
                        <div class="panel-body">
3195
                            <div class="row">
3196
                                <div class="form-group col-xs-12">
3197
                                    <label for="search_field">' . htmlspecialchars(
3198
                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.searchString')
3199
            ) . ': </label>
3200
									<input class="form-control" type="search" placeholder="' . htmlspecialchars(
3201
                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.enterSearchString')
3202
            ) . '" title="' . htmlspecialchars(
3203
                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.searchString')
3204
            ) . '" name="search_field" id="search_field" value="' . htmlspecialchars($this->searchString) . '" />
3205
                                </div>
3206
                                <div class="form-group col-xs-12 col-sm-6">
3207
									<label for="search_levels">' . htmlspecialchars(
3208
                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.search_levels')
3209
            ) . ': </label>
3210
									' . $lMenu . '
3211
                                </div>
3212
                                <div class="form-group col-xs-12 col-sm-6">
3213
									<label for="showLimit">' . htmlspecialchars(
3214
                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.limit')
3215
            ) . ': </label>
3216
									<input class="form-control" type="number" min="0" max="10000" placeholder="10" title="' . htmlspecialchars(
3217
                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.limit')
3218
            ) . '" name="showLimit" id="showLimit" value="' . htmlspecialchars(
3219
                ($this->showLimit ? $this->showLimit : '')
3220
            ) . '" />
3221
                                </div>
3222
                                <div class="form-group col-xs-12">
3223
                                    <div class="form-control-wrap">
3224
                                        <button type="submit" class="btn btn-default" name="search" title="' . htmlspecialchars(
3225
                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.search')
3226
            ) . '">
3227
                                            ' . $iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render(
3228
            ) . ' ' . htmlspecialchars(
3229
                $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.search')
3230
            ) . '
3231
                                        </button>
3232
                                    </div>
3233
                                </div>
3234
                            </div>
3235
                        </div>
3236
                    </div>
3237
                </div>
3238
			' . $formElements[1] . '</div>';
3239
        return $content;
3240
    }
3241
3242
    /**
3243
     * Setting the field names to display in extended list.
3244
     * Sets the internal variable $this->setFields
3245
     */
3246 View Code Duplication
    public function setDispFields()
3247
    {
3248
        $backendUser = $this->getBackendUserAuthentication();
3249
        // Getting from session:
3250
        $dispFields = $backendUser->getModuleData('list/displayFields');
3251
        // If fields has been inputted, then set those as the value and push it to session variable:
3252
        if (is_array($this->displayFields)) {
3253
            reset($this->displayFields);
3254
            $tKey = key($this->displayFields);
3255
            $dispFields[$tKey] = $this->displayFields[$tKey];
3256
            $backendUser->pushModuleData('list/displayFields', $dispFields);
3257
        }
3258
        // Setting result:
3259
        $this->setFields = $dispFields;
3260
    }
3261
3262
    /**
3263
     * Create thumbnail code for record/field
3264
     *
3265
     * @param mixed[] $row Record array
3266
     * @param string $table Table (record is from)
3267
     * @param string $field Field name for which thumbnail are to be rendered.
3268
     * @return string HTML for thumbnails, if any.
3269
     */
3270
    public function thumbCode($row, $table, $field)
3271
    {
3272
        return BackendUtility::thumbCode($row, $table, $field);
3273
    }
3274
3275
    /**
3276
     * Returns a QueryBuilder configured to select $fields from $table where the pid is restricted
3277
     * depending on the current searchlevel setting.
3278
     *
3279
     * @param string $table Table name
3280
     * @param int $pageId Page id Only used to build the search constraints, getPageIdConstraint() used for restrictions
3281
     * @param string[] $additionalConstraints Additional part for where clause
3282
     * @param string[] $fields Field list to select, * for all
3283
     * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
3284
     */
3285 View Code Duplication
    public function getQueryBuilder(
3286
        string $table,
3287
        int $pageId,
3288
        array $additionalConstraints = [],
3289
        array $fields = ['*']
3290
    ): QueryBuilder {
3291
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
3292
            ->getQueryBuilderForTable($table);
3293
        $queryBuilder->getRestrictions()
3294
            ->removeAll()
3295
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
3296
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
3297
        $queryBuilder
3298
            ->select(...$fields)
3299
            ->from($table);
3300
3301
        if (!empty($additionalConstraints)) {
3302
            $queryBuilder->andWhere(...$additionalConstraints);
3303
        }
3304
3305
        $queryBuilder = $this->prepareQueryBuilder($table, $pageId, $fields, $additionalConstraints, $queryBuilder);
3306
3307
        return $queryBuilder;
3308
    }
3309
3310
    /**
3311
     * Return the modified QueryBuilder object ($queryBuilder) which will be
3312
     * used to select the records from a table $table with pid = $this->pidList
3313
     *
3314
     * @param string $table Table name
3315
     * @param int $pageId Page id Only used to build the search constraints, $this->pidList is used for restrictions
3316
     * @param string[] $fieldList List of fields to select from the table
3317
     * @param string[] $additionalConstraints Additional part for where clause
3318
     * @param QueryBuilder $queryBuilder
3319
     * @param bool $addSorting
3320
     * @return QueryBuilder
3321
     */
3322
    protected function prepareQueryBuilder(
3323
        string $table,
3324
        int $pageId,
3325
        array $fieldList = ['*'],
3326
        array $additionalConstraints = [],
3327
        QueryBuilder $queryBuilder,
3328
        bool $addSorting = true
3329
    ): QueryBuilder {
3330
        $parameters = [
3331
            'table' => $table,
3332
            'fields' => $fieldList,
3333
            'groupBy' => null,
3334
            'orderBy' => null,
3335
            'firstResult' => $this->firstElementNumber ?: null,
3336
            'maxResults' => $this->iLimit ?: null
3337
        ];
3338
3339
        if ($this->iLimit > 0) {
3340
            $queryBuilder->setMaxResults($this->iLimit);
3341
        }
3342
3343
        if ($addSorting) {
3344
            if ($this->sortField && in_array($this->sortField, $this->makeFieldList($table, 1))) {
3345
                $queryBuilder->orderBy($this->sortField, $this->sortRev ? 'DESC' : 'ASC');
3346
            } else {
3347
                $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?: $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
3348
                $orderBys = QueryHelper::parseOrderBy((string)$orderBy);
3349
                foreach ($orderBys as $orderBy) {
3350
                    $queryBuilder->orderBy($orderBy[0], $orderBy[1]);
3351
                }
3352
            }
3353
        }
3354
3355
        // Build the query constraints
3356
        $queryBuilder = $this->addPageIdConstraint($table, $queryBuilder);
3357
        $searchWhere = $this->makeSearchString($table, $pageId);
3358
        if (!empty($searchWhere)) {
3359
            $queryBuilder->andWhere($searchWhere);
3360
        }
3361
3362
        // Filtering on displayable pages (permissions):
3363
        if ($table === 'pages' && $this->perms_clause) {
3364
            $queryBuilder->andWhere($this->perms_clause);
3365
        }
3366
3367
        // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
3368
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
3369
            && (GeneralUtility::inList($this->hideTranslations, $table) || $this->hideTranslations === '*')
3370
        ) {
3371
            $queryBuilder->andWhere(
3372
                $queryBuilder->expr()->eq(
3373
                    $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
3374
                    0
3375
                )
3376
            );
3377
        }
3378
3379
        $hookName = DatabaseRecordList::class;
3380
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'] ?? [] as $className) {
3381
            $hookObject = GeneralUtility::makeInstance($className);
3382
            if (method_exists($hookObject, 'buildQueryParametersPostProcess')) {
3383
                $hookObject->buildQueryParametersPostProcess(
3384
                    $parameters,
3385
                    $table,
3386
                    $pageId,
3387
                    $additionalConstraints,
3388
                    $fieldList,
3389
                    $this,
3390
                    $queryBuilder
3391
                );
3392
            }
3393
        }
3394
3395
        // array_unique / array_filter used to eliminate empty and duplicate constraints
3396
        // the array keys are eliminated by this as well to facilitate argument unpacking
3397
        // when used with the querybuilder.
3398
        // @deprecated since TYPO3 v9, will be removed in TYPO3 v10
3399
        if (!empty($parameters['where'])) {
3400
            $parameters['where'] = array_unique(array_filter(array_values($parameters['where'])));
0 ignored issues
show
Bug introduced by
It seems like $parameters['where'] can also be of type null and integer and string; however, parameter $input of array_values() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3400
            $parameters['where'] = array_unique(array_filter(array_values(/** @scrutinizer ignore-type */ $parameters['where'])));
Loading history...
3401
        }
3402
        if (!empty($parameters['where'])) {
3403
            $this->logDeprecation('where');
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Backend\View\P...tView::logDeprecation() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3403
            /** @scrutinizer ignore-deprecated */ $this->logDeprecation('where');
Loading history...
3404
            $queryBuilder->where(...$parameters['where']);
3405
        }
3406
        if (!empty($parameters['orderBy'])) {
3407
            $this->logDeprecation('orderBy');
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Backend\View\P...tView::logDeprecation() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3407
            /** @scrutinizer ignore-deprecated */ $this->logDeprecation('orderBy');
Loading history...
3408
            foreach ($parameters['orderBy'] as $fieldNameAndSorting) {
3409
                list($fieldName, $sorting) = $fieldNameAndSorting;
3410
                $queryBuilder->addOrderBy($fieldName, $sorting);
3411
            }
3412
        }
3413
        if (!empty($parameters['firstResult'])) {
3414
            $this->logDeprecation('firstResult');
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Backend\View\P...tView::logDeprecation() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3414
            /** @scrutinizer ignore-deprecated */ $this->logDeprecation('firstResult');
Loading history...
3415
            $queryBuilder->setFirstResult((int)$parameters['firstResult']);
3416
        }
3417
        if (!empty($parameters['maxResults']) && $parameters['maxResults'] !== $this->iLimit) {
3418
            $this->logDeprecation('maxResults');
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Backend\View\P...tView::logDeprecation() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3418
            /** @scrutinizer ignore-deprecated */ $this->logDeprecation('maxResults');
Loading history...
3419
            $queryBuilder->setMaxResults((int)$parameters['maxResults']);
3420
        }
3421
        if (!empty($parameters['groupBy'])) {
3422
            $this->logDeprecation('groupBy');
0 ignored issues
show
Deprecated Code introduced by
The function TYPO3\CMS\Backend\View\P...tView::logDeprecation() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3422
            /** @scrutinizer ignore-deprecated */ $this->logDeprecation('groupBy');
Loading history...
3423
            $queryBuilder->groupBy($parameters['groupBy']);
3424
        }
3425
3426
        return $queryBuilder;
3427
    }
3428
3429
    /**
3430
     * Executed a query to set $this->totalItems to the number of total
3431
     * items, eg. for pagination
3432
     *
3433
     * @param string $table Table name
3434
     * @param int $pageId Only used to build the search constraints, $this->pidList is used for restrictions
3435
     * @param array $constraints Additional constraints for where clause
3436
     */
3437 View Code Duplication
    public function setTotalItems(string $table, int $pageId, array $constraints)
3438
    {
3439
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
3440
            ->getQueryBuilderForTable($table);
3441
3442
        $queryBuilder->getRestrictions()
3443
            ->removeAll()
3444
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
3445
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
3446
        $queryBuilder
3447
            ->from($table);
3448
3449
        if (!empty($constraints)) {
3450
            $queryBuilder->andWhere(...$constraints);
3451
        }
3452
3453
        $queryBuilder = $this->prepareQueryBuilder($table, $pageId, ['*'], $constraints, $queryBuilder, false);
3454
        // Reset limit and offset for full count query
3455
        $queryBuilder->setFirstResult(0);
3456
        $queryBuilder->setMaxResults(1);
3457
3458
        $this->totalItems = (int)$queryBuilder->count('*')
3459
            ->execute()
3460
            ->fetchColumn();
3461
    }
3462
3463
    /**
3464
     * Creates part of query for searching after a word ($this->searchString)
3465
     * fields in input table.
3466
     *
3467
     * @param string $table Table, in which the fields are being searched.
3468
     * @param int $currentPid Page id for the possible search limit. -1 only if called from an old XCLASS.
3469
     * @return string Returns part of WHERE-clause for searching, if applicable.
3470
     */
3471 View Code Duplication
    public function makeSearchString($table, $currentPid = -1)
3472
    {
3473
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
3474
        $expressionBuilder = $queryBuilder->expr();
3475
        $constraints = [];
3476
        $currentPid = (int)$currentPid;
3477
        $tablePidField = $table === 'pages' ? 'uid' : 'pid';
3478
        // Make query, only if table is valid and a search string is actually defined:
3479
        if (empty($this->searchString)) {
3480
            return '';
3481
        }
3482
3483
        $searchableFields = $this->getSearchFields($table);
3484
        if (empty($searchableFields)) {
3485
            return '';
3486
        }
3487
        if (MathUtility::canBeInterpretedAsInteger($this->searchString)) {
3488
            $constraints[] = $expressionBuilder->eq('uid', (int)$this->searchString);
3489
            foreach ($searchableFields as $fieldName) {
3490
                if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
3491
                    continue;
3492
                }
3493
                $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
3494
                $fieldType = $fieldConfig['type'];
3495
                $evalRules = $fieldConfig['eval'] ?: '';
3496
                if ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int')) {
3497
                    if (is_array($fieldConfig['search'])
3498
                        && in_array('pidonly', $fieldConfig['search'], true)
3499
                        && $currentPid > 0
3500
                    ) {
3501
                        $constraints[] = $expressionBuilder->andX(
3502
                            $expressionBuilder->eq($fieldName, (int)$this->searchString),
3503
                            $expressionBuilder->eq($tablePidField, (int)$currentPid)
3504
                        );
3505
                    }
3506
                } elseif ($fieldType === 'text'
3507
                    || $fieldType === 'flex'
3508
                    || ($fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules)))
3509
                ) {
3510
                    $constraints[] = $expressionBuilder->like(
3511
                        $fieldName,
3512
                        $queryBuilder->quote('%' . (int)$this->searchString . '%')
3513
                    );
3514
                }
3515
            }
3516
        } else {
3517
            $like = $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($this->searchString) . '%');
3518
            foreach ($searchableFields as $fieldName) {
3519
                if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
3520
                    continue;
3521
                }
3522
                $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
3523
                $fieldType = $fieldConfig['type'];
3524
                $evalRules = $fieldConfig['eval'] ?: '';
3525
                $searchConstraint = $expressionBuilder->andX(
3526
                    $expressionBuilder->comparison(
3527
                        'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
3528
                        'LIKE',
3529
                        'LOWER(' . $like . ')'
3530
                    )
3531
                );
3532
                if (is_array($fieldConfig['search'])) {
3533
                    $searchConfig = $fieldConfig['search'];
3534
                    if (in_array('case', $searchConfig)) {
3535
                        // Replace case insensitive default constraint
3536
                        $searchConstraint = $expressionBuilder->andX($expressionBuilder->like($fieldName, $like));
3537
                    }
3538
                    if (in_array('pidonly', $searchConfig) && $currentPid > 0) {
3539
                        $searchConstraint->add($expressionBuilder->eq($tablePidField, (int)$currentPid));
3540
                    }
3541
                    if ($searchConfig['andWhere']) {
3542
                        $searchConstraint->add(
3543
                            QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
3544
                        );
3545
                    }
3546
                }
3547
                if ($fieldType === 'text'
3548
                    || $fieldType === 'flex'
3549
                    || $fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules))
3550
                ) {
3551
                    if ($searchConstraint->count() !== 0) {
3552
                        $constraints[] = $searchConstraint;
3553
                    }
3554
                }
3555
            }
3556
        }
3557
        // If no search field conditions have been build ensure no results are returned
3558
        if (empty($constraints)) {
3559
            return '0=1';
3560
        }
3561
3562
        return $expressionBuilder->orX(...$constraints);
3563
    }
3564
3565
    /**
3566
     * Fetches a list of fields to use in the Backend search for the given table.
3567
     *
3568
     * @param string $tableName
3569
     * @return string[]
3570
     */
3571 View Code Duplication
    protected function getSearchFields($tableName)
3572
    {
3573
        $fieldArray = [];
3574
        $fieldListWasSet = false;
3575
        // Get fields from ctrl section of TCA first
3576
        if (isset($GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
3577
            $fieldArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
3578
            $fieldListWasSet = true;
3579
        }
3580
        // Call hook to add or change the list
3581
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'] ?? [] as $hookFunction) {
3582
            $hookParameters = [
3583
                'tableHasSearchConfiguration' => $fieldListWasSet,
3584
                'tableName' => $tableName,
3585
                'searchFields' => &$fieldArray,
3586
                'searchString' => $this->searchString
3587
            ];
3588
            GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
3589
        }
3590
        return $fieldArray;
3591
    }
3592
3593
    /**
3594
     * Returns the title (based on $code) of a table ($table) with the proper link around. For headers over tables.
3595
     * The link will cause the display of all extended mode or not for the table.
3596
     *
3597
     * @param string $table Table name
3598
     * @param string $code Table label
3599
     * @return string The linked table label
3600
     */
3601 View Code Duplication
    public function linkWrapTable($table, $code)
3602
    {
3603
        if ($this->table !== $table) {
3604
            return '<a href="' . htmlspecialchars(
3605
                    $this->listURL('', $table, 'firstElementNumber')
3606
                ) . '">' . $code . '</a>';
3607
        }
3608
        return '<a href="' . htmlspecialchars(
3609
                $this->listURL('', '', 'sortField,sortRev,table,firstElementNumber')
3610
            ) . '">' . $code . '</a>';
3611
    }
3612
3613
    /**
3614
     * 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...)
3615
     *
3616
     * @param string $table Table name
3617
     * @param int $uid Item uid
3618
     * @param string $code Item title (not htmlspecialchars()'ed yet)
3619
     * @param mixed[] $row Item row
3620
     * @return string The item title. Ready for HTML output (is htmlspecialchars()'ed)
3621
     */
3622 View Code Duplication
    public function linkWrapItems($table, $uid, $code, $row)
3623
    {
3624
        $lang = $this->getLanguageService();
3625
        $origCode = $code;
3626
        // If the title is blank, make a "no title" label:
3627
        if ((string)$code === '') {
3628
            $code = '<i>[' . htmlspecialchars(
3629
                    $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.no_title')
3630
                ) . ']</i> - '
3631
                . htmlspecialchars(BackendUtility::getRecordTitle($table, $row));
3632
        } else {
3633
            $code = htmlspecialchars($code, ENT_QUOTES, 'UTF-8', false);
3634
            if ($code != htmlspecialchars($origCode)) {
3635
                $code = '<span title="' . htmlspecialchars(
3636
                        $origCode,
3637
                        ENT_QUOTES,
3638
                        'UTF-8',
3639
                        false
3640
                    ) . '">' . $code . '</span>';
3641
            }
3642
        }
3643
        switch ((string)$this->clickTitleMode) {
3644
            case 'edit':
3645
                // If the listed table is 'pages' we have to request the permission settings for each page:
3646
                if ($table === 'pages') {
3647
                    $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(
3648
                        BackendUtility::getRecord('pages', $row['uid'])
3649
                    );
3650
                    $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
3651
                } else {
3652
                    $permsEdit = $this->calcPerms & Permission::CONTENT_EDIT;
3653
                }
3654
                // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
3655
                if ($permsEdit) {
3656
                    $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
3657
                    $code = '<a href="#" onclick="' . htmlspecialchars(
3658
                            BackendUtility::editOnClick($params, '', -1)
3659
                        ) . '" title="' . htmlspecialchars($lang->getLL('edit')) . '">' . $code . '</a>';
3660
                }
3661
                break;
3662
            case 'show':
3663
                // "Show" link (only pages and tt_content elements)
3664
                if ($table === 'pages' || $table === 'tt_content') {
3665
                    $code = '<a href="#" onclick="' . htmlspecialchars(
3666
                            BackendUtility::viewOnClick(
3667
                                ($table === 'tt_content' ? $this->id . '#' . $row['uid'] : $row['uid'])
0 ignored issues
show
Bug introduced by
It seems like $table === 'tt_content' ...ow['uid'] : $row['uid'] can also be of type string; however, parameter $pageUid of TYPO3\CMS\Backend\Utilit...dUtility::viewOnClick() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3667
                                /** @scrutinizer ignore-type */ ($table === 'tt_content' ? $this->id . '#' . $row['uid'] : $row['uid'])
Loading history...
3668
                            )
3669
                        ) . '" title="' . htmlspecialchars(
3670
                            $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage')
3671
                        ) . '">' . $code . '</a>';
3672
                }
3673
                break;
3674
            case 'info':
3675
                // "Info": (All records)
3676
                $code = '<a href="#" onclick="' . htmlspecialchars(
3677
                        ('top.launchView(\'' . $table . '\', \'' . $row['uid'] . '\'); return false;')
3678
                    ) . '" title="' . htmlspecialchars($lang->getLL('showInfo')) . '">' . $code . '</a>';
3679
                break;
3680
            default:
3681
                // Output the label now:
3682
                if ($table === 'pages') {
3683
                    $code = '<a href="' . htmlspecialchars(
3684
                            $this->listURL($uid, '', 'firstElementNumber')
3685
                        ) . '" onclick="setHighlight(' . $uid . ')">' . $code . '</a>';
3686
                } else {
3687
                    $code = $this->linkUrlMail($code, $origCode);
3688
                }
3689
        }
3690
        return $code;
3691
    }
3692
3693
    /**
3694
     * Wrapping input code in link to URL or email if $testString is either.
3695
     *
3696
     * @param string $code code to wrap
3697
     * @param string $testString String which is tested for being a URL or email and which will be used for the link if so.
3698
     * @return string Link-Wrapped $code value, if $testString was URL or email.
3699
     */
3700 View Code Duplication
    public function linkUrlMail($code, $testString)
3701
    {
3702
        // Check for URL:
3703
        $scheme = parse_url($testString, PHP_URL_SCHEME);
3704
        if ($scheme === 'http' || $scheme === 'https' || $scheme === 'ftp') {
3705
            return '<a href="' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
3706
        }
3707
        // Check for email:
3708
        if (GeneralUtility::validEmail($testString)) {
3709
            return '<a href="mailto:' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
3710
        }
3711
        // Return if nothing else...
3712
        return $code;
3713
    }
3714
3715
    /**
3716
     * Creates the URL to this script, including all relevant GPvars
3717
     * Fixed GPvars are id, table, imagemode, returnUrl, search_field, search_levels and showLimit
3718
     * The GPvars "sortField" and "sortRev" are also included UNLESS they are found in the $exclList variable.
3719
     *
3720
     * @param string $altId Alternative id value. Enter blank string for the current id ($this->id)
3721
     * @param string $table Table name to display. Enter "-1" for the current table.
3722
     * @param string $exclList Comma separated list of fields NOT to include ("sortField", "sortRev" or "firstElementNumber")
3723
     * @return string URL
3724
     */
3725 View Code Duplication
    public function listURL($altId = '', $table = '-1', $exclList = '')
3726
    {
3727
        $urlParameters = [];
3728
        if ((string)$altId !== '') {
3729
            $urlParameters['id'] = $altId;
3730
        } else {
3731
            $urlParameters['id'] = $this->id;
3732
        }
3733
        if ($table === '-1') {
3734
            $urlParameters['table'] = $this->table;
3735
        } else {
3736
            $urlParameters['table'] = $table;
3737
        }
3738
        if ($this->thumbs) {
3739
            $urlParameters['imagemode'] = $this->thumbs;
3740
        }
3741
        if ($this->returnUrl) {
3742
            $urlParameters['returnUrl'] = $this->returnUrl;
3743
        }
3744
        if ((!$exclList || !GeneralUtility::inList($exclList, 'search_field')) && $this->searchString) {
3745
            $urlParameters['search_field'] = $this->searchString;
3746
        }
3747
        if ($this->searchLevels) {
3748
            $urlParameters['search_levels'] = $this->searchLevels;
3749
        }
3750
        if ($this->showLimit) {
3751
            $urlParameters['showLimit'] = $this->showLimit;
3752
        }
3753
        if ((!$exclList || !GeneralUtility::inList($exclList, 'firstElementNumber')) && $this->firstElementNumber) {
3754
            $urlParameters['pointer'] = $this->firstElementNumber;
3755
        }
3756
        if ((!$exclList || !GeneralUtility::inList($exclList, 'sortField')) && $this->sortField) {
3757
            $urlParameters['sortField'] = $this->sortField;
3758
        }
3759
        if ((!$exclList || !GeneralUtility::inList($exclList, 'sortRev')) && $this->sortRev) {
3760
            $urlParameters['sortRev'] = $this->sortRev;
3761
        }
3762
3763
        $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters);
3764
3765
        if ($routePath = GeneralUtility::_GP('route')) {
3766
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
3767
            $url = (string)$uriBuilder->buildUriFromRoutePath($routePath, $urlParameters);
3768
        } else {
3769
            $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim(
3770
                    GeneralUtility::implodeArrayForUrl('', $urlParameters),
3771
                    '&'
3772
                );
3773
        }
3774
        return $url;
3775
    }
3776
3777
    /**
3778
     * Returns "requestUri" - which is basically listURL
3779
     * @return string Content of ->listURL()
3780
     */
3781
    public function requestUri()
3782
    {
3783
        return $this->listURL();
3784
    }
3785
3786
    /**
3787
     * Makes the list of fields to select for a table
3788
     *
3789
     * @param string $table Table name
3790
     * @param bool $dontCheckUser If set, users access to the field (non-exclude-fields) is NOT checked.
3791
     * @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)
3792
     * @return string[] Array, where values are fieldnames to include in query
3793
     */
3794
    public function makeFieldList($table, $dontCheckUser = false, $addDateFields = false)
3795
    {
3796
        $backendUser = $this->getBackendUserAuthentication();
3797
        // Init fieldlist array:
3798
        $fieldListArr = [];
3799
        // Check table:
3800
        if (is_array($GLOBALS['TCA'][$table]) && isset($GLOBALS['TCA'][$table]['columns']) && is_array(
3801
                $GLOBALS['TCA'][$table]['columns']
3802
            )) {
3803
            if (isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
3804
                // Traverse configured columns and add them to field array, if available for user.
3805
                foreach ($GLOBALS['TCA'][$table]['columns'] as $fN => $fieldValue) {
3806
                    if ($dontCheckUser || (!$fieldValue['exclude'] || $backendUser->check(
3807
                                'non_exclude_fields',
3808
                                $table . ':' . $fN
3809
                            )) && $fieldValue['config']['type'] !== 'passthrough') {
3810
                        $fieldListArr[] = $fN;
3811
                    }
3812
                }
3813
3814
                $fieldListArr[] = 'uid';
3815
                $fieldListArr[] = 'pid';
3816
3817
                // Add date fields
3818
                if ($dontCheckUser || $backendUser->isAdmin() || $addDateFields) {
3819
                    if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
3820
                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['tstamp'];
3821
                    }
3822
                    if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
3823
                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['crdate'];
3824
                    }
3825
                }
3826
                // Add more special fields:
3827
                if ($dontCheckUser || $backendUser->isAdmin()) {
3828
                    if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
3829
                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['cruser_id'];
3830
                    }
3831
                    if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
3832
                        $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
3833
                    }
3834
                    if (ExtensionManagementUtility::isLoaded('workspaces')
3835
                        && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
3836
                        $fieldListArr[] = 't3ver_id';
3837
                        $fieldListArr[] = 't3ver_state';
3838
                        $fieldListArr[] = 't3ver_wsid';
3839
                    }
3840
                }
3841
            } else {
3842
                $this->logger->error('TCA is broken for the table "' . $table . '": no required "columns" entry in TCA.');
3843
            }
3844
        }
3845
        return $fieldListArr;
3846
    }
3847
3848
    /**
3849
     * Redirects to FormEngine if a record is just localized.
3850
     *
3851
     * @param string $justLocalized String with table, orig uid and language separated by ":
3852
     */
3853 View Code Duplication
    public function localizationRedirect($justLocalized)
3854
    {
3855
        list($table, $orig_uid, $language) = explode(':', $justLocalized);
3856
        if ($GLOBALS['TCA'][$table]
3857
            && $GLOBALS['TCA'][$table]['ctrl']['languageField']
3858
            && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
3859
        ) {
3860
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
3861
            $queryBuilder->getRestrictions()
3862
                ->removeAll()
3863
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
3864
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
3865
3866
            $localizedRecordUid = $queryBuilder->select('uid')
3867
                ->from($table)
3868
                ->where(
3869
                    $queryBuilder->expr()->eq(
3870
                        $GLOBALS['TCA'][$table]['ctrl']['languageField'],
3871
                        $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
3872
                    ),
3873
                    $queryBuilder->expr()->eq(
3874
                        $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
3875
                        $queryBuilder->createNamedParameter($orig_uid, \PDO::PARAM_INT)
3876
                    )
3877
                )
3878
                ->setMaxResults(1)
3879
                ->execute()
3880
                ->fetchColumn();
3881
3882
            if ($localizedRecordUid !== false) {
3883
                // Create parameters and finally run the classic page module for creating a new page translation
3884
                $url = $this->listURL();
3885
                $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
3886
                $editUserAccountUrl = (string)$uriBuilder->buildUriFromRoute(
3887
                    'record_edit',
3888
                    [
3889
                        'edit[' . $table . '][' . $localizedRecordUid . ']' => 'edit',
3890
                        'returnUrl' => $url
3891
                    ]
3892
                );
3893
                HttpUtility::redirect($editUserAccountUrl);
3894
            }
3895
        }
3896
    }
3897
3898
    /**
3899
     * Set URL parameters to override or add in the listUrl() method.
3900
     *
3901
     * @param string[] $urlParameters
3902
     */
3903
    public function setOverrideUrlParameters(array $urlParameters)
3904
    {
3905
        $this->overrideUrlParameters = $urlParameters;
3906
    }
3907
3908
    /**
3909
     * Set table display order information
3910
     *
3911
     * Structure of $orderInformation:
3912
     *   'tableName' => [
3913
     *      'before' => // comma-separated string list or array of table names
3914
     *      'after' => // comma-separated string list or array of table names
3915
     * ]
3916
     *
3917
     * @param array $orderInformation
3918
     * @throws \UnexpectedValueException
3919
     */
3920 View Code Duplication
    public function setTableDisplayOrder(array $orderInformation)
3921
    {
3922
        foreach ($orderInformation as $tableName => &$configuration) {
3923
            if (isset($configuration['before'])) {
3924
                if (is_string($configuration['before'])) {
3925
                    $configuration['before'] = GeneralUtility::trimExplode(',', $configuration['before'], true);
3926
                } elseif (!is_array($configuration['before'])) {
3927
                    throw new \UnexpectedValueException(
3928
                        'The specified "before" order configuration for table "' . $tableName . '" is invalid.',
3929
                        1504870805
3930
                    );
3931
                }
3932
            }
3933
            if (isset($configuration['after'])) {
3934
                if (is_string($configuration['after'])) {
3935
                    $configuration['after'] = GeneralUtility::trimExplode(',', $configuration['after'], true);
3936
                } elseif (!is_array($configuration['after'])) {
3937
                    throw new \UnexpectedValueException(
3938
                        'The specified "after" order configuration for table "' . $tableName . '" is invalid.',
3939
                        1504870806
3940
                    );
3941
                }
3942
            }
3943
        }
3944
        $this->tableDisplayOrder = $orderInformation;
3945
    }
3946
3947
    /**
3948
     * @return array
3949
     */
3950
    public function getOverridePageIdList(): array
3951
    {
3952
        return $this->overridePageIdList;
3953
    }
3954
3955
    /**
3956
     * @param int[]|array $overridePageIdList
3957
     */
3958
    public function setOverridePageIdList(array $overridePageIdList)
3959
    {
3960
        $this->overridePageIdList = array_map('intval', $overridePageIdList);
3961
    }
3962
3963
    /**
3964
     * Get all allowed mount pages to be searched in.
3965
     *
3966
     * @param int $id Page id
3967
     * @param int $depth Depth to go down
3968
     * @param string $perms_clause select clause
3969
     * @return int[]
3970
     */
3971 View Code Duplication
    protected function getSearchableWebmounts($id, $depth, $perms_clause)
3972
    {
3973
        $backendUser = $this->getBackendUserAuthentication();
3974
        /** @var PageTreeView $tree */
3975
        $tree = GeneralUtility::makeInstance(PageTreeView::class);
3976
        $tree->init('AND ' . $perms_clause);
3977
        $tree->makeHTML = 0;
3978
        $tree->fieldArray = ['uid', 'php_tree_stop'];
3979
        $idList = [];
3980
3981
        $allowedMounts = !$backendUser->isAdmin() && $id === 0
3982
            ? $backendUser->returnWebmounts()
3983
            : [$id];
3984
3985
        foreach ($allowedMounts as $allowedMount) {
3986
            $idList[] = $allowedMount;
3987
            if ($depth) {
3988
                $tree->getTree($allowedMount, $depth, '');
3989
            }
3990
            $idList = array_merge($idList, $tree->ids);
3991
        }
3992
3993
        return $idList;
3994
    }
3995
3996
    /**
3997
     * Add conditions to the QueryBuilder object ($queryBuilder) to limit a
3998
     * query to a list of page IDs based on the current search level setting.
3999
     *
4000
     * @param string $tableName
4001
     * @param QueryBuilder $queryBuilder
4002
     * @return QueryBuilder Modified QueryBuilder object
4003
     */
4004 View Code Duplication
    protected function addPageIdConstraint(string $tableName, QueryBuilder $queryBuilder): QueryBuilder
4005
    {
4006
        // Set search levels:
4007
        $searchLevels = $this->searchLevels;
4008
4009
        // Set search levels to 999 instead of -1 as the following methods
4010
        // do not support -1 as valid value for infinite search.
4011
        if ($searchLevels === -1) {
4012
            $searchLevels = 999;
4013
        }
4014
4015
        if ($searchLevels === 0) {
4016
            $queryBuilder->andWhere(
4017
                $queryBuilder->expr()->eq(
4018
                    $tableName . '.pid',
4019
                    $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
4020
                )
4021
            );
4022
        } elseif ($searchLevels > 0) {
4023
            $allowedMounts = $this->getSearchableWebmounts($this->id, $searchLevels, $this->perms_clause);
4024
            $queryBuilder->andWhere(
4025
                $queryBuilder->expr()->in(
4026
                    $tableName . '.pid',
4027
                    $queryBuilder->createNamedParameter($allowedMounts, Connection::PARAM_INT_ARRAY)
4028
                )
4029
            );
4030
        }
4031
4032
        if (!empty($this->getOverridePageIdList())) {
4033
            $queryBuilder->andWhere(
4034
                $queryBuilder->expr()->in(
4035
                    $tableName . '.pid',
4036
                    $queryBuilder->createNamedParameter($this->getOverridePageIdList(), Connection::PARAM_INT_ARRAY)
4037
                )
4038
            );
4039
        }
4040
4041
        return $queryBuilder;
4042
    }
4043
4044
    /**
4045
     * Method used to log deprecated usage of old buildQueryParametersPostProcess hook arguments
4046
     *
4047
     * @param string $index
4048
     * @deprecated
4049
     */
4050
    protected function logDeprecation(string $index)
4051
    {
4052
        trigger_error(
4053
            '[index: ' . $index . '] $parameters in "buildQueryParameters"-Hook has been deprecated in v9 and will be remove in v10, use $queryBuilder instead',
4054
            E_USER_DEPRECATED
4055
        );
4056
    }
4057
4058
    /**
4059
     * @return BackendUserAuthentication
4060
     */
4061
    protected function getBackendUserAuthentication()
4062
    {
4063
        return $GLOBALS['BE_USER'];
4064
    }
4065
4066
    /**
4067
     * Sets the script url depending on being a module or script request
4068
     */
4069 View Code Duplication
    protected function determineScriptUrl()
4070
    {
4071
        if ($routePath = GeneralUtility::_GP('route')) {
4072
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
4073
            $this->thisScript = (string)$uriBuilder->buildUriFromRoutePath($routePath);
4074
        } else {
4075
            $this->thisScript = GeneralUtility::getIndpEnv('SCRIPT_NAME');
4076
        }
4077
    }
4078
4079
    /**
4080
     * Returns a table-row with the content from the fields in the input data array.
4081
     * OBS: $this->fieldArray MUST be set! (represents the list of fields to display)
4082
     *
4083
     * @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'
4084
     * @param string $icon Is the <img>+<a> of the record. If not supplied the first 'join'-icon will be a 'line' instead
4085
     * @param array $data Is the dataarray, record with the fields. Notice: These fields are (currently) NOT htmlspecialchar'ed before being wrapped in <td>-tags
4086
     * @param string $rowParams Is insert in the <tr>-tags. Must carry a ' ' as first character
4087
     * @param string $_ OBSOLETE - NOT USED ANYMORE. $lMargin is the leftMargin (int)
4088
     * @param string $_2 OBSOLETE - NOT USED ANYMORE. Is the HTML <img>-tag for an alternative 'gfx/ol/line.gif'-icon (used in the top)
4089
     * @param string $colType Defines the tag being used for the columns. Default is td.
4090
     *
4091
     * @return string HTML content for the table row
4092
     */
4093 View Code Duplication
    public function addElement($h, $icon, $data, $rowParams = '', $_ = '', $_2 = '', $colType = 'td')
4094
    {
4095
        $colType = ($colType === 'th') ? 'th' : 'td';
4096
        $noWrap = $this->no_noWrap ? '' : ' nowrap';
4097
        // Start up:
4098
        $l10nParent = isset($data['_l10nparent_']) ? (int)$data['_l10nparent_'] : 0;
4099
        $out = '
4100
		<!-- Element, begin: -->
4101
		<tr ' . $rowParams . ' data-uid="' . (int)$data['uid'] . '" data-l10nparent="' . $l10nParent . '">';
4102
        // Show icon and lines
4103
        if ($this->showIcon) {
4104
            $out .= '
4105
			<' . $colType . ' class="col-icon nowrap">';
4106
            if (!$h) {
4107
                $out .= '&nbsp;';
4108
            } else {
4109
                for ($a = 0; $a < $h; $a++) {
4110
                    if (!$a) {
4111
                        if ($icon) {
4112
                            $out .= $icon;
4113
                        }
4114
                    }
4115
                }
4116
            }
4117
            $out .= '</' . $colType . '>
4118
			';
4119
        }
4120
        // Init rendering.
4121
        $colsp = '';
4122
        $lastKey = '';
4123
        $c = 0;
4124
        $ccount = 0;
4125
        // __label is used as the label key to circumvent problems with uid used as label (see #67756)
4126
        // as it was introduced later on, check if it really exists before using it
4127
        $fields = $this->fieldArray;
4128
        if ($colType === 'td' && array_key_exists('__label', $data)) {
4129
            $fields[0] = '__label';
4130
        }
4131
        // Traverse field array which contains the data to present:
4132
        foreach ($fields as $vKey) {
4133
            if (isset($data[$vKey])) {
4134
                if ($lastKey) {
4135
                    $cssClass = $this->addElement_tdCssClass[$lastKey];
4136
                    if ($this->oddColumnsCssClass && $ccount % 2 == 0) {
4137
                        $cssClass = implode(' ', [$this->addElement_tdCssClass[$lastKey], $this->oddColumnsCssClass]);
4138
                    }
4139
                    $out .= '
4140
						<' . $colType . ' class="' . $cssClass . $noWrap . '"' . $colsp . $this->addElement_tdParams[$lastKey] . '>' . $data[$lastKey] . '</' . $colType . '>';
4141
                }
4142
                $lastKey = $vKey;
4143
                $c = 1;
4144
                $ccount++;
4145
            } else {
4146
                if (!$lastKey) {
4147
                    $lastKey = $vKey;
4148
                }
4149
                $c++;
4150
            }
4151
            if ($c > 1) {
4152
                $colsp = ' colspan="' . $c . '"';
4153
            } else {
4154
                $colsp = '';
4155
            }
4156
        }
4157
        if ($lastKey) {
4158
            $cssClass = $this->addElement_tdCssClass[$lastKey];
4159
            if ($this->oddColumnsCssClass) {
4160
                $cssClass = implode(' ', [$this->addElement_tdCssClass[$lastKey], $this->oddColumnsCssClass]);
4161
            }
4162
            $out .= '
4163
				<' . $colType . ' class="' . $cssClass . $noWrap . '"' . $colsp . $this->addElement_tdParams[$lastKey] . '>' . $data[$lastKey] . '</' . $colType . '>';
4164
        }
4165
        // End row
4166
        $out .= '
4167
		</tr>';
4168
        // Return row.
4169
        return $out;
4170
    }
4171
4172
    /**
4173
     * Dummy function, used to write the top of a table listing.
4174
     */
4175
    public function writeTop()
4176
    {
4177
    }
4178
4179
    /**
4180
     * Creates a forward/reverse button based on the status of ->eCounter, ->firstElementNumber, ->iLimit
4181
     *
4182
     * @param string $table Table name
4183
     * @return array array([boolean], [HTML]) where [boolean] is 1 for reverse element, [HTML] is the table-row code for the element
4184
     */
4185 View Code Duplication
    public function fwd_rwd_nav($table = '')
4186
    {
4187
        $code = '';
4188
        if ($this->eCounter >= $this->firstElementNumber && $this->eCounter < $this->firstElementNumber + $this->iLimit) {
4189
            if ($this->firstElementNumber && $this->eCounter == $this->firstElementNumber) {
4190
                // 	Reverse
4191
                $theData = [];
4192
                $titleCol = $this->fieldArray[0];
4193
                $theData[$titleCol] = $this->fwd_rwd_HTML('fwd', $this->eCounter, $table);
4194
                $code = $this->addElement(1, '', $theData, 'class="fwd_rwd_nav"');
4195
            }
4196
            return [1, $code];
4197
        }
4198
        if ($this->eCounter == $this->firstElementNumber + $this->iLimit) {
4199
            // 	Forward
4200
            $theData = [];
4201
            $titleCol = $this->fieldArray[0];
4202
            $theData[$titleCol] = $this->fwd_rwd_HTML('rwd', $this->eCounter, $table);
4203
            $code = $this->addElement(1, '', $theData, 'class="fwd_rwd_nav"');
4204
        }
4205
        return [0, $code];
4206
    }
4207
4208
    /**
4209
     * Creates the button with link to either forward or reverse
4210
     *
4211
     * @param string $type Type: "fwd" or "rwd
4212
     * @param int $pointer Pointer
4213
     * @param string $table Table name
4214
     * @return string
4215
     * @access private
4216
     */
4217 View Code Duplication
    public function fwd_rwd_HTML($type, $pointer, $table = '')
4218
    {
4219
        $content = '';
4220
        $tParam = $table ? '&table=' . rawurlencode($table) : '';
4221
        switch ($type) {
4222
            case 'fwd':
4223
                $href = $this->listURL() . '&pointer=' . ($pointer - $this->iLimit) . $tParam;
4224
                $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
4225
                        'actions-move-up',
4226
                        Icon::SIZE_SMALL
4227
                    )->render() . '</a> <i>[1 - ' . $pointer . ']</i>';
4228
                break;
4229
            case 'rwd':
4230
                $href = $this->listURL() . '&pointer=' . $pointer . $tParam;
4231
                $content = '<a href="' . htmlspecialchars($href) . '">' . $this->iconFactory->getIcon(
4232
                        'actions-move-down',
4233
                        Icon::SIZE_SMALL
4234
                    )->render() . '</a> <i>[' . ($pointer + 1) . ' - ' . $this->totalItems . ']</i>';
4235
                break;
4236
        }
4237
        return $content;
4238
    }
4239
4240
    /**
4241
     * @return string
4242
     */
4243
    protected function getThisScript()
4244
    {
4245
        return strpos($this->thisScript, '?') === false ? $this->thisScript . '?' : $this->thisScript . '&';
4246
    }
4247
4248
    /**
4249
     * Returning JavaScript for ClipBoard functionality.
4250
     *
4251
     * @return string
4252
     */
4253
    public function CBfunctions()
4254
    {
4255
        return '
4256
		// checkOffCB()
4257
	function checkOffCB(listOfCBnames, link) {	//
4258
		var checkBoxes, flag, i;
4259
		var checkBoxes = listOfCBnames.split(",");
4260
		if (link.rel === "") {
4261
			link.rel = "allChecked";
4262
			flag = true;
4263
		} else {
4264
			link.rel = "";
4265
			flag = false;
4266
		}
4267
		for (i = 0; i < checkBoxes.length; i++) {
4268
			setcbValue(checkBoxes[i], flag);
4269
		}
4270
	}
4271
		// cbValue()
4272
	function cbValue(CBname) {	//
4273
		var CBfullName = "CBC["+CBname+"]";
4274
		return (document.dblistForm[CBfullName] && document.dblistForm[CBfullName].checked ? 1 : 0);
4275
	}
4276
		// setcbValue()
4277
	function setcbValue(CBname,flag) {	//
4278
		CBfullName = "CBC["+CBname+"]";
4279
		if(document.dblistForm[CBfullName]) {
4280
			document.dblistForm[CBfullName].checked = flag ? "on" : 0;
4281
		}
4282
	}
4283
4284
		';
4285
    }
4286
4287
    /**
4288
     * Initializes page languages and icons
4289
     */
4290
    public function initializeLanguages()
4291
    {
4292
        // Look up page overlays:
4293
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
4294
            ->getQueryBuilderForTable('pages');
4295
        $queryBuilder->getRestrictions()
4296
            ->removeAll()
4297
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
4298
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
4299
        $result = $queryBuilder
4300
            ->select('*')
4301
            ->from('pages')
4302
            ->where(
4303
                $queryBuilder->expr()->andX(
4304
                    $queryBuilder->expr()->eq(
4305
                        $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
4306
                        $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
4307
                    ),
4308
                    $queryBuilder->expr()->gt(
4309
                        $GLOBALS['TCA']['pages']['ctrl']['languageField'],
4310
                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
4311
                    )
4312
                )
4313
            )
4314
            ->execute();
4315
4316
        $this->pageOverlays = [];
4317
        while ($row = $result->fetch()) {
4318
            $this->pageOverlays[$row[$GLOBALS['TCA']['pages']['ctrl']['languageField']]] = $row;
4319
        }
4320
4321
        $this->languageIconTitles = $this->getTranslateTools()->getSystemLanguages($this->id);
4322
    }
4323
4324
    /**
4325
     * Return the icon for the language
4326
     *
4327
     * @param int $sys_language_uid Sys language uid
4328
     * @param bool $addAsAdditionalText If set to true, only the flag is returned
4329
     * @return string Language icon
4330
     */
4331 View Code Duplication
    public function languageFlag($sys_language_uid, $addAsAdditionalText = true)
4332
    {
4333
        $out = '';
4334
        $title = htmlspecialchars($this->languageIconTitles[$sys_language_uid]['title']);
4335
        if ($this->languageIconTitles[$sys_language_uid]['flagIcon']) {
4336
            $out .= '<span title="' . $title . '">' . $this->iconFactory->getIcon(
4337
                    $this->languageIconTitles[$sys_language_uid]['flagIcon'],
4338
                    Icon::SIZE_SMALL
4339
                )->render() . '</span>';
4340
            if (!$addAsAdditionalText) {
4341
                return $out;
4342
            }
4343
            $out .= '&nbsp;';
4344
        }
4345
        $out .= $title;
4346
        return $out;
4347
    }
4348
4349
    /**
4350
     * Gets an instance of TranslationConfigurationProvider
4351
     *
4352
     * @return TranslationConfigurationProvider
4353
     */
4354
    protected function getTranslateTools()
4355
    {
4356
        if (!isset($this->translateTools)) {
4357
            $this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
4358
        }
4359
        return $this->translateTools;
4360
    }
4361
4362
    /**
4363
     * Generates HTML code for a Reference tooltip out of
4364
     * sys_refindex records you hand over
4365
     *
4366
     * @param int $references number of records from sys_refindex table
4367
     * @param string $launchViewParameter JavaScript String, which will be passed as parameters to top.launchView
4368
     * @return string
4369
     */
4370 View Code Duplication
    protected function generateReferenceToolTip($references, $launchViewParameter = '')
4371
    {
4372
        if (!$references) {
4373
            $htmlCode = '-';
4374
        } else {
4375
            $htmlCode = '<a href="#"';
4376
            if ($launchViewParameter !== '') {
4377
                $htmlCode .= ' onclick="' . htmlspecialchars(
4378
                        ('top.launchView(' . $launchViewParameter . '); return false;')
4379
                    ) . '"';
4380
            }
4381
            $htmlCode .= ' title="' . htmlspecialchars(
4382
                    $this->getLanguageService()->sL(
4383
                        'LLL:EXT:backend/Resources/Private/Language/locallang.xlf:show_references'
4384
                    ) . ' (' . $references . ')'
4385
                ) . '">';
4386
            $htmlCode .= $references;
4387
            $htmlCode .= '</a>';
4388
        }
4389
        return $htmlCode;
4390
    }
4391
4392
    /**
4393
     * Returns the language service
4394
     * @return LanguageService
4395
     */
4396
    protected function getLanguageService()
4397
    {
4398
        return $GLOBALS['LANG'];
4399
    }
4400
}
4401