Passed
Branch master (6c65a4)
by Christian
27:15 queued 11:09
created

PageLayoutView::prepareQueryBuilder()   F

Complexity

Conditions 26
Paths 9216

Size

Total Lines 105
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 26
eloc 60
nc 9216
nop 6
dl 0
loc 105
rs 2
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(Permission::PAGE_SHOW)
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() .
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
                            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][] = ($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
                        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
                        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
$pasteItem of type 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(Permission::PAGE_SHOW)
1668
            );
1669
1670
        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']);
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($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
                    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
                    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
        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
                    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
                        } 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
                        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
                    } 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
            if (!empty($availableTranslations)) {
2544
                $output = '<option value="">' . htmlspecialchars($this->getLanguageService()->getLL('new_language')) . '</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
                    . '<select class="form-control input-sm" name="createNewLanguage" onchange="window.location.href=this.options[this.selectedIndex].value">'
2566
                    . $output
2567
                    . '</select></div></div>';
2568
            }
2569
        }
2570
        return '';
2571
    }
2572
2573
    /**
2574
     * Traverse the result pointer given, adding each record to array and setting some internal values at the same time.
2575
     *
2576
     * @param Statement $result DBAL Statement
2577
     * @param string $table Table name defaulting to tt_content
2578
     * @return array The selected rows returned in this array.
2579
     */
2580
    public function getResult(Statement $result, string $table = 'tt_content'): array
2581
    {
2582
        $output = [];
2583
        // Traverse the result:
2584
        while ($row = $result->fetch()) {
2585
            BackendUtility::workspaceOL($table, $row, -99, true);
2586
            if ($row) {
2587
                // Add the row to the array:
2588
                $output[] = $row;
2589
            }
2590
        }
2591
        $this->generateTtContentDataArray($output);
2592
        // Return selected records
2593
        return $output;
2594
    }
2595
2596
    /********************************
2597
     *
2598
     * Various helper functions
2599
     *
2600
     ********************************/
2601
2602
    /**
2603
     * Initializes the clipboard for generating paste links
2604
     *
2605
     *
2606
     * @see \TYPO3\CMS\Recordlist\RecordList::main()
2607
     * @see \TYPO3\CMS\Backend\Controller\ContextMenuController::clipboardAction()
2608
     * @see \TYPO3\CMS\Filelist\Controller\FileListController::indexAction()
2609
     */
2610
    protected function initializeClipboard()
2611
    {
2612
        // Start clipboard
2613
        $this->clipboard = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Clipboard\Clipboard::class);
2614
2615
        // Initialize - reads the clipboard content from the user session
2616
        $this->clipboard->initializeClipboard();
2617
2618
        // This locks the clipboard to the Normal for this request.
2619
        $this->clipboard->lockToNormal();
2620
2621
        // Clean up pad
2622
        $this->clipboard->cleanCurrent();
2623
2624
        // Save the clipboard content
2625
        $this->clipboard->endClipboard();
2626
    }
2627
2628
    /**
2629
     * Generates the data for previous and next elements which is needed for movements.
2630
     *
2631
     * @param array $rowArray
2632
     */
2633
    protected function generateTtContentDataArray(array $rowArray)
2634
    {
2635
        if (empty($this->tt_contentData)) {
2636
            $this->tt_contentData = [
2637
                'nextThree' => [],
2638
                'next' => [],
2639
                'prev' => [],
2640
            ];
2641
        }
2642
        foreach ($rowArray as $key => $value) {
2643
            // Create the list of the next three ids (for editing links...)
2644
            for ($i = 0; $i < $this->nextThree; $i++) {
2645
                if (isset($rowArray[$key - $i])
2646
                    && !GeneralUtility::inList($this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']], $value['uid'])
2647
                ) {
2648
                    $this->tt_contentData['nextThree'][$rowArray[$key - $i]['uid']] .= $value['uid'] . ',';
2649
                }
2650
            }
2651
2652
            // Create information for next and previous content elements
2653
            if (isset($rowArray[$key - 1])) {
2654
                if (isset($rowArray[$key - 2])) {
2655
                    $this->tt_contentData['prev'][$value['uid']] = -$rowArray[$key - 2]['uid'];
2656
                } else {
2657
                    $this->tt_contentData['prev'][$value['uid']] = $value['pid'];
2658
                }
2659
                $this->tt_contentData['next'][$rowArray[$key - 1]['uid']] = -$value['uid'];
2660
            }
2661
        }
2662
    }
2663
2664
    /**
2665
     * Counts and returns the number of records on the page with $pid
2666
     *
2667
     * @param string $table Table name
2668
     * @param int $pid Page id
2669
     * @return int Number of records.
2670
     */
2671
    public function numberOfRecords($table, $pid)
2672
    {
2673
        $count = 0;
2674
        if ($GLOBALS['TCA'][$table]) {
2675
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2676
                ->getQueryBuilderForTable($table);
2677
            $queryBuilder->getRestrictions()
2678
                ->removeAll()
2679
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2680
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2681
            $count = (int)$queryBuilder->count('uid')
2682
                ->from($table)
2683
                ->where(
2684
                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT))
2685
                )
2686
                ->execute()
2687
                ->fetchColumn();
2688
        }
2689
2690
        return $count;
2691
    }
2692
2693
    /**
2694
     * Processing of larger amounts of text (usually from RTE/bodytext fields) with word wrapping etc.
2695
     *
2696
     * @param string $input Input string
2697
     * @return string Output string
2698
     */
2699
    public function renderText($input)
2700
    {
2701
        $input = strip_tags($input);
2702
        $input = GeneralUtility::fixed_lgd_cs($input, 1500);
2703
        return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
2704
    }
2705
2706
    /**
2707
     * Creates the icon image tag for record from table and wraps it in a link which will trigger the click menu.
2708
     *
2709
     * @param string $table Table name
2710
     * @param array $row Record array
2711
     * @param string $enabledClickMenuItems Passthrough to wrapClickMenuOnIcon
2712
     * @return string HTML for the icon
2713
     */
2714
    public function getIcon($table, $row, $enabledClickMenuItems = '')
2715
    {
2716
        // Initialization
2717
        $toolTip = BackendUtility::getRecordToolTip($row, 'tt_content');
2718
        $icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
2719
        $this->counter++;
2720
        // The icon with link
2721
        if ($this->getBackendUser()->recordEditAccessInternals($table, $row)) {
2722
            $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
2723
        }
2724
        return $icon;
2725
    }
2726
2727
    /**
2728
     * Creates processed values for all field names in $fieldList based on values from $row array.
2729
     * The result is 'returned' through $info which is passed as a reference
2730
     *
2731
     * @param string $table Table name
2732
     * @param string $fieldList Comma separated list of fields.
2733
     * @param array $row Record from which to take values for processing.
2734
     * @param array $info Array to which the processed values are added.
2735
     */
2736
    public function getProcessedValue($table, $fieldList, array $row, array &$info)
2737
    {
2738
        // Splitting values from $fieldList
2739
        $fieldArr = explode(',', $fieldList);
2740
        // Traverse fields from $fieldList
2741
        foreach ($fieldArr as $field) {
2742
            if ($row[$field]) {
2743
                $info[] = '<strong>' . htmlspecialchars($this->itemLabels[$field]) . '</strong> '
2744
                    . htmlspecialchars(BackendUtility::getProcessedValue($table, $field, $row[$field]));
2745
            }
2746
        }
2747
    }
2748
2749
    /**
2750
     * Returns TRUE, if the record given as parameters is NOT visible based on hidden/starttime/endtime (if available)
2751
     *
2752
     * @param string $table Tablename of table to test
2753
     * @param array $row Record row.
2754
     * @return bool Returns TRUE, if disabled.
2755
     */
2756
    public function isDisabled($table, $row)
2757
    {
2758
        $enableCols = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
2759
        return $enableCols['disabled'] && $row[$enableCols['disabled']]
2760
            || $enableCols['starttime'] && $row[$enableCols['starttime']] > $GLOBALS['EXEC_TIME']
2761
            || $enableCols['endtime'] && $row[$enableCols['endtime']] && $row[$enableCols['endtime']] < $GLOBALS['EXEC_TIME'];
2762
    }
2763
2764
    /**
2765
     * Returns icon for "no-edit" of a record.
2766
     * Basically, the point is to signal that this record could have had an edit link if
2767
     * the circumstances were right. A placeholder for the regular edit icon...
2768
     *
2769
     * @param string $label Label key from LOCAL_LANG
2770
     * @return string IMG tag for icon.
2771
     */
2772
    public function noEditIcon($label = 'noEditItems')
2773
    {
2774
        $title = htmlspecialchars($this->getLanguageService()->getLL($label));
2775
        return '<span title="' . $title . '">' . $this->iconFactory->getIcon('status-edit-read-only', Icon::SIZE_SMALL)->render() . '</span>';
2776
    }
2777
2778
    /*****************************************
2779
     *
2780
     * External renderings
2781
     *
2782
     *****************************************/
2783
2784
    /**
2785
     * Creates a menu of the tables that can be listed by this function
2786
     * Only tables which has records on the page will be included.
2787
     * Notice: The function also fills in the internal variable $this->activeTables with icon/titles.
2788
     *
2789
     * @param int $id Page id from which we are listing records (the function will look up if there are records on the page)
2790
     * @return string HTML output.
2791
     */
2792
    public function getTableMenu($id)
2793
    {
2794
        // Initialize:
2795
        $this->activeTables = [];
2796
        $theTables = ['tt_content'];
2797
        // External tables:
2798
        if (is_array($this->externalTables)) {
2799
            $theTables = array_unique(array_merge($theTables, array_keys($this->externalTables)));
2800
        }
2801
        $out = '';
2802
        // Traverse tables to check:
2803
        foreach ($theTables as $tName) {
2804
            // Check access and whether the proper extensions are loaded:
2805
            if ($this->getBackendUser()->check('tables_select', $tName)
2806
                && (
2807
                    isset($this->externalTables[$tName])
2808
                    || $tName === 'fe_users' || $tName === 'tt_content'
2809
                    || \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded($tName)
2810
                )
2811
            ) {
2812
                // Make query to count records from page:
2813
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2814
                    ->getQueryBuilderForTable($tName);
2815
                $queryBuilder->getRestrictions()
2816
                    ->removeAll()
2817
                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2818
                    ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2819
                $count = $queryBuilder->count('uid')
2820
                    ->from($tName)
2821
                    ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
2822
                    ->execute()
2823
                    ->fetchColumn();
2824
                // If records were found (or if "tt_content" is the table...):
2825
                if ($count || $tName === 'tt_content') {
2826
                    // Add row to menu:
2827
                    $out .= '
2828
					<td><a href="#' . $tName . '" title="' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title'])) . '"></a>'
2829
                        . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
2830
                        . '</td>';
2831
                    // ... and to the internal array, activeTables we also add table icon and title (for use elsewhere)
2832
                    $title = htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title']))
2833
                        . ': ' . $count . ' ' . htmlspecialchars($this->getLanguageService()->getLL('records'));
2834
                    $this->activeTables[$tName] = '<span title="' . $title . '">'
2835
                        . $this->iconFactory->getIconForRecord($tName, [], Icon::SIZE_SMALL)->render()
2836
                        . '</span>'
2837
                        . '&nbsp;' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tName]['ctrl']['title']));
2838
                }
2839
            }
2840
        }
2841
        // Wrap cells in table tags:
2842
        $out = '
2843
            <!--
2844
                Menu of tables on the page (table menu)
2845
            -->
2846
            <table border="0" cellpadding="0" cellspacing="0" id="typo3-page-tblMenu">
2847
				<tr>' . $out . '
2848
                </tr>
2849
			</table>';
2850
        // Return the content:
2851
        return $out;
2852
    }
2853
2854
    /**
2855
     * Create thumbnail code for record/field but not linked
2856
     *
2857
     * @param mixed[] $row Record array
2858
     * @param string $table Table (record is from)
2859
     * @param string $field Field name for which thumbnail are to be rendered.
2860
     * @return string HTML for thumbnails, if any.
2861
     */
2862
    public function getThumbCodeUnlinked($row, $table, $field)
2863
    {
2864
        return BackendUtility::thumbCode($row, $table, $field, '', '', null, 0, '', '', false);
2865
    }
2866
2867
    /**
2868
     * Checks whether translated Content Elements exist in the desired language
2869
     * If so, deny creating new ones via the UI
2870
     *
2871
     * @param array $contentElements
2872
     * @param int $language
2873
     * @return bool
2874
     */
2875
    protected function checkIfTranslationsExistInLanguage(array $contentElements, $language)
2876
    {
2877
        // If in default language, you may always create new entries
2878
        // Also, you may override this strict behavior via user TS Config
2879
        // If you do so, you're on your own and cannot rely on any support by the TYPO3 core
2880
        // We jump out here since we don't need to do the expensive loop operations
2881
        $allowInconsistentLanguageHandling = BackendUtility::getModTSconfig($this->id, 'mod.web_layout.allowInconsistentLanguageHandling');
2882
        if ($language === 0 || $allowInconsistentLanguageHandling['value'] === '1') {
2883
            return false;
2884
        }
2885
        /**
2886
         * Build up caches
2887
         */
2888
        if (!isset($this->languageHasTranslationsCache[$language])) {
2889
            foreach ($contentElements as $columns) {
2890
                foreach ($columns as $contentElement) {
2891
                    if ((int)$contentElement['l18n_parent'] === 0) {
2892
                        $this->languageHasTranslationsCache[$language]['hasStandAloneContent'] = true;
2893
                    }
2894
                    if ((int)$contentElement['l18n_parent'] > 0) {
2895
                        $this->languageHasTranslationsCache[$language]['hasTranslations'] = true;
2896
                    }
2897
                }
2898
            }
2899
            // Check whether we have a mix of both
2900
            if ($this->languageHasTranslationsCache[$language]['hasStandAloneContent']
2901
                && $this->languageHasTranslationsCache[$language]['hasTranslations']
2902
            ) {
2903
                $message = GeneralUtility::makeInstance(
2904
                    FlashMessage::class,
2905
                    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

2905
                    /** @scrutinizer ignore-type */ sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $this->languageIconTitles[$language]['title']),
Loading history...
2906
                    sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $this->languageIconTitles[$language]['title']),
2907
                    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

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

3397
            $parameters['where'] = array_unique(array_filter(array_values(/** @scrutinizer ignore-type */ $parameters['where'])));
Loading history...
3398
        }
3399
        if (!empty($parameters['where'])) {
3400
            $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

3400
            /** @scrutinizer ignore-deprecated */ $this->logDeprecation('where');
Loading history...
3401
            $queryBuilder->where(...$parameters['where']);
3402
        }
3403
        if (!empty($parameters['orderBy'])) {
3404
            $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

3404
            /** @scrutinizer ignore-deprecated */ $this->logDeprecation('orderBy');
Loading history...
3405
            foreach ($parameters['orderBy'] as $fieldNameAndSorting) {
3406
                list($fieldName, $sorting) = $fieldNameAndSorting;
3407
                $queryBuilder->addOrderBy($fieldName, $sorting);
3408
            }
3409
        }
3410
        if (!empty($parameters['firstResult'])) {
3411
            $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

3411
            /** @scrutinizer ignore-deprecated */ $this->logDeprecation('firstResult');
Loading history...
3412
            $queryBuilder->setFirstResult((int)$parameters['firstResult']);
3413
        }
3414
        if (!empty($parameters['maxResults']) && $parameters['maxResults'] !== $this->iLimit) {
3415
            $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

3415
            /** @scrutinizer ignore-deprecated */ $this->logDeprecation('maxResults');
Loading history...
3416
            $queryBuilder->setMaxResults((int)$parameters['maxResults']);
3417
        }
3418
        if (!empty($parameters['groupBy'])) {
3419
            $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

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

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