Passed
Push — master ( c4c6dd...2cad69 )
by
unknown
20:24
created

PageLayoutView::isContentEditable()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 5
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 8
rs 9.6111
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\EventDispatcher\EventDispatcherInterface;
20
use Psr\Log\LoggerAwareInterface;
21
use Psr\Log\LoggerAwareTrait;
22
use TYPO3\CMS\Backend\Clipboard\Clipboard;
23
use TYPO3\CMS\Backend\Controller\Page\LocalizationController;
24
use TYPO3\CMS\Backend\Routing\UriBuilder;
25
use TYPO3\CMS\Backend\Utility\BackendUtility;
26
use TYPO3\CMS\Backend\View\Drawing\DrawingConfiguration;
27
use TYPO3\CMS\Backend\View\Event\AfterSectionMarkupGeneratedEvent;
28
use TYPO3\CMS\Backend\View\Event\BeforeSectionMarkupGeneratedEvent;
29
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
30
use TYPO3\CMS\Core\Core\Environment;
31
use TYPO3\CMS\Core\Database\Connection;
32
use TYPO3\CMS\Core\Database\ConnectionPool;
33
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
34
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
35
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
36
use TYPO3\CMS\Core\Database\ReferenceIndex;
37
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
38
use TYPO3\CMS\Core\Imaging\Icon;
39
use TYPO3\CMS\Core\Imaging\IconFactory;
40
use TYPO3\CMS\Core\Localization\LanguageService;
41
use TYPO3\CMS\Core\Messaging\FlashMessage;
42
use TYPO3\CMS\Core\Messaging\FlashMessageService;
43
use TYPO3\CMS\Core\Page\PageRenderer;
44
use TYPO3\CMS\Core\Service\FlexFormService;
45
use TYPO3\CMS\Core\Site\Entity\NullSite;
46
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
47
use TYPO3\CMS\Core\Site\SiteFinder;
48
use TYPO3\CMS\Core\Type\Bitmask\Permission;
49
use TYPO3\CMS\Core\Utility\GeneralUtility;
50
use TYPO3\CMS\Core\Utility\StringUtility;
51
use TYPO3\CMS\Core\Versioning\VersionState;
52
use TYPO3\CMS\Fluid\View\StandaloneView;
53
54
/**
55
 * Child class for the Web > Page module
56
 * @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
57
 * @deprecated Will be removed in TYPO3 11
58
 */
59
class PageLayoutView implements LoggerAwareInterface
60
{
61
    use LoggerAwareTrait;
62
63
    /**
64
     * If TRUE, new-wizards are linked to rather than the regular new-element list.
65
     *
66
     * @var bool
67
     */
68
    public $option_newWizard = true;
69
70
    /**
71
     * If TRUE, elements will have edit icons (probably this is whether the user has permission to edit the page content). Set externally.
72
     *
73
     * @var bool
74
     */
75
    public $doEdit = true;
76
77
    /**
78
     * If set TRUE, the language mode of tt_content elements will be rendered with hard binding between
79
     * default language content elements and their translations!
80
     *
81
     * @var bool
82
     */
83
    public $defLangBinding = false;
84
85
    /**
86
     * External, static: Configuration of tt_content element display:
87
     *
88
     * @var array
89
     */
90
    public $tt_contentConfig = [
91
        'languageCols' => 0,
92
        'languageMode' => 0,
93
        'languageColsPointer' => 0,
94
        // Displays hidden records as well
95
        'showHidden' => 1,
96
        // Which language
97
        'sys_language_uid' => 0,
98
        'cols' => '1,0,2,3',
99
        // Which columns can be accessed by current BE user
100
        'activeCols' => '1,0,2,3'
101
    ];
102
103
    /**
104
     * Used to move content up / down
105
     * @var array
106
     */
107
    public $tt_contentData = [
108
        'prev' => [],
109
        'next' => []
110
    ];
111
112
    /**
113
     * Used to store labels for CTypes for tt_content elements
114
     *
115
     * @var array
116
     */
117
    public $CType_labels = [];
118
119
    /**
120
     * Used to store labels for the various fields in tt_content elements
121
     *
122
     * @var array
123
     */
124
    public $itemLabels = [];
125
126
    /**
127
     * Page id
128
     *
129
     * @var int
130
     */
131
    public $id;
132
133
    /**
134
     * Loaded with page record with version overlay if any.
135
     *
136
     * @var string[]
137
     */
138
    public $pageRecord = [];
139
140
    /**
141
     * Contains site languages for this page ID
142
     *
143
     * @var SiteLanguage[]
144
     */
145
    protected $siteLanguages = [];
146
147
    /**
148
     * @var Clipboard
149
     */
150
    protected $clipboard;
151
152
    /**
153
     * Current ids page record
154
     *
155
     * @var array
156
     */
157
    protected $pageinfo;
158
159
    /**
160
     * Caches the amount of content elements as a matrix
161
     *
162
     * @var array
163
     * @internal
164
     */
165
    protected $contentElementCache = [];
166
167
    /**
168
     * @var IconFactory
169
     */
170
    protected $iconFactory;
171
172
    /**
173
     * Stores whether a certain language has translations in it
174
     *
175
     * @var array
176
     */
177
    protected $languageHasTranslationsCache = [];
178
179
    /**
180
     * @var LocalizationController
181
     */
182
    protected $localizationController;
183
184
    /**
185
     * Cache the number of references to a record
186
     *
187
     * @var array
188
     */
189
    protected $referenceCount = [];
190
191
    /**
192
     * @var EventDispatcherInterface
193
     */
194
    protected $eventDispatcher;
195
196
    /**
197
     * @var UriBuilder
198
     */
199
    protected $uriBuilder;
200
201
    public function __construct(EventDispatcherInterface $eventDispatcher)
202
    {
203
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
204
        $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
205
        $this->localizationController = GeneralUtility::makeInstance(LocalizationController::class);
206
        $this->eventDispatcher = $eventDispatcher;
207
    }
208
209
    /**
210
     * @param DrawingConfiguration $drawingConfiguration
211
     * @return PageLayoutView
212
     * @internal
213
     */
214
    public static function createFromDrawingConfiguration(DrawingConfiguration $drawingConfiguration): PageLayoutView
215
    {
216
        /** @var PageLayoutView $pageLayoutView */
217
        $pageLayoutView = GeneralUtility::makeInstance(self::class);
218
        $pageLayoutView->id = $drawingConfiguration->getPageId();
219
        $pageLayoutView->pageRecord = $drawingConfiguration->getPageRecord();
220
        $pageLayoutView->option_newWizard = $drawingConfiguration->getShowNewContentWizard();
221
        $pageLayoutView->defLangBinding = $drawingConfiguration->getDefaultLanguageBinding();
222
        $pageLayoutView->tt_contentConfig['cols'] = implode(',', $drawingConfiguration->getActiveColumns());
223
        $pageLayoutView->tt_contentConfig['activeCols'] = implode(',', $drawingConfiguration->getActiveColumns());
224
        $pageLayoutView->tt_contentConfig['showHidden'] = $drawingConfiguration->getShowHidden();
225
        $pageLayoutView->tt_contentConfig['sys_language_uid'] = $drawingConfiguration->getLanguageColumnsPointer();
226
        if ($drawingConfiguration->getLanguageMode()) {
227
            $pageLayoutView->tt_contentConfig['languageMode'] = 1;
228
            $pageLayoutView->tt_contentConfig['languageCols'] = $drawingConfiguration->getLanguageColumns();
229
            $pageLayoutView->tt_contentConfig['languageColsPointer'] = $drawingConfiguration->getLanguageColumnsPointer();
230
        }
231
        $pageLayoutView->doEdit = $pageLayoutView->isContentEditable($drawingConfiguration->getLanguageColumnsPointer());
232
        $pageLayoutView->CType_labels = $drawingConfiguration->getContentTypeLabels();
233
        $pageLayoutView->itemLabels = $drawingConfiguration->getItemLabels();
234
        return $pageLayoutView;
235
    }
236
237
    protected function initialize()
238
    {
239
        $this->resolveSiteLanguages($this->id);
240
        $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
241
        $this->initializeClipboard();
242
        $this->pageinfo = BackendUtility::readPageAccess($this->id, '');
243
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
244
        $pageActionsCallback = null;
245
        if ($this->isPageEditable()) {
246
            $languageOverlayId = 0;
247
            $pageLocalizationRecord = BackendUtility::getRecordLocalization('pages', $this->id, (int)$this->tt_contentConfig['sys_language_uid']);
248
            if (is_array($pageLocalizationRecord)) {
249
                $pageLocalizationRecord = reset($pageLocalizationRecord);
250
            }
251
            if (!empty($pageLocalizationRecord['uid'])) {
252
                $languageOverlayId = $pageLocalizationRecord['uid'];
253
            }
254
            $pageActionsCallback = 'function(PageActions) {
255
                PageActions.setPageId(' . (int)$this->id . ');
256
                PageActions.setLanguageOverlayId(' . $languageOverlayId . ');
257
            }';
258
        }
259
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/PageActions', $pageActionsCallback);
260
        // Get labels for CTypes and tt_content element fields in general:
261
        $this->CType_labels = [];
262
        foreach ($GLOBALS['TCA']['tt_content']['columns']['CType']['config']['items'] as $val) {
263
            $this->CType_labels[$val[1]] = $this->getLanguageService()->sL($val[0]);
264
        }
265
266
        $this->itemLabels = [];
267
        foreach ($GLOBALS['TCA']['tt_content']['columns'] as $name => $val) {
268
            $this->itemLabels[$name] = $this->getLanguageService()->sL($val['label']);
269
        }
270
    }
271
272
    /**
273
     * Build a list of language IDs that should be rendered in this view
274
     * @return int[]
275
     */
276
    protected function getSelectedLanguages(): array
277
    {
278
        $langList = $this->tt_contentConfig['sys_language_uid'];
279
        if ($this->tt_contentConfig['languageMode']) {
280
            if ($this->tt_contentConfig['languageColsPointer']) {
281
                $langList = '0,' . $this->tt_contentConfig['languageColsPointer'];
282
            } else {
283
                $langList = implode(',', array_keys($this->tt_contentConfig['languageCols']));
284
            }
285
        }
286
        return GeneralUtility::intExplode(',', $langList);
0 ignored issues
show
Bug Best Practice introduced by
The expression return TYPO3\CMS\Core\Ut...Explode(',', $langList) returns the type string[] which is incompatible with the documented return type integer[].
Loading history...
287
    }
288
289
    /**
290
     * Renders Content Elements from the tt_content table from page id
291
     *
292
     * @param int $id Page id
293
     * @return string HTML for the listing
294
     */
295
    public function getTable_tt_content($id)
0 ignored issues
show
Coding Style introduced by
Method name "PageLayoutView::getTable_tt_content" is not in camel caps format
Loading history...
296
    {
297
        $this->id = (int)$id;
298
        $this->initialize();
299
        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
300
            ->getConnectionForTable('tt_content')
301
            ->getExpressionBuilder();
302
303
        $languageColumn = [];
304
        $out = '';
305
        $tcaItems = GeneralUtility::makeInstance(BackendLayoutView::class)->getColPosListItemsParsed($this->id);
306
        $languageIds = $this->getSelectedLanguages();
307
        $defaultLanguageElementsByColumn = [];
308
        $defLangBinding = [];
309
        // For each languages...
310
        // If not languageMode, then we'll only be through this once.
311
        foreach ($languageIds as $lP) {
312
            if (!isset($this->contentElementCache[$lP])) {
313
                $this->contentElementCache[$lP] = [];
314
            }
315
316
            if (count($languageIds) === 1 || $lP === 0) {
317
                $showLanguage = $expressionBuilder->in('sys_language_uid', [$lP, -1]);
318
            } else {
319
                $showLanguage = $expressionBuilder->eq('sys_language_uid', $lP);
320
            }
321
            $content = [];
322
            $head = [];
323
324
            $backendLayout = $this->getBackendLayoutView()->getSelectedBackendLayout($this->id);
325
            $columns = $backendLayout['__colPosList'];
326
            // Select content records per column
327
            $contentRecordsPerColumn = $this->getContentRecordsPerColumn('tt_content', $id, $columns, $showLanguage);
328
            $cList = array_keys($contentRecordsPerColumn);
329
            // For each column, render the content into a variable:
330
            foreach ($cList as $columnId) {
331
                if (!isset($this->contentElementCache[$lP])) {
332
                    $this->contentElementCache[$lP] = [];
333
                }
334
335
                if (!$lP) {
336
                    $defaultLanguageElementsByColumn[$columnId] = [];
337
                }
338
339
                // Start wrapping div
340
                $content[$columnId] .= '<div data-colpos="' . $columnId . '" data-language-uid="' . $lP . '" class="t3js-sortable t3js-sortable-lang t3js-sortable-lang-' . $lP . ' t3-page-ce-wrapper';
341
                if (empty($contentRecordsPerColumn[$columnId])) {
342
                    $content[$columnId] .= ' t3-page-ce-empty';
343
                }
344
                $content[$columnId] .= '">';
345
                // Add new content at the top most position
346
                $link = '';
347
                if ($this->isContentEditable()
348
                    && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
0 ignored issues
show
Bug introduced by
$lP of type string is incompatible with the type integer expected by parameter $language of TYPO3\CMS\Backend\View\P...ationsExistInLanguage(). ( Ignorable by Annotation )

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

348
                    && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, /** @scrutinizer ignore-type */ $lP))
Loading history...
349
                ) {
350
                    if ($this->option_newWizard) {
351
                        $urlParameters = [
352
                            'id' => $id,
353
                            'sys_language_uid' => $lP,
354
                            'colPos' => $columnId,
355
                            'uid_pid' => $id,
356
                            'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
357
                        ];
358
                        $routeName = BackendUtility::getPagesTSconfig($id)['mod.']['newContentElementWizard.']['override']
359
                            ?? 'new_content_element_wizard';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "??"; newline found
Loading history...
360
                        $url = (string)$this->uriBuilder->buildUriFromRoute($routeName, $urlParameters);
361
                    } else {
362
                        $urlParameters = [
363
                            'edit' => [
364
                                'tt_content' => [
365
                                    $id => 'new'
366
                                ]
367
                            ],
368
                            'defVals' => [
369
                                'tt_content' => [
370
                                    'colPos' => $columnId,
371
                                    'sys_language_uid' => $lP
372
                                ]
373
                            ],
374
                            'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
375
                        ];
376
                        $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
377
                    }
378
                    $title = htmlspecialchars($this->getLanguageService()->getLL('newContentElement'));
379
                    $link = '<a href="' . htmlspecialchars($url) . '" '
380
                        . 'title="' . $title . '"'
381
                        . 'data-title="' . $title . '"'
382
                        . 'class="btn btn-default btn-sm ' . ($this->option_newWizard ? 't3js-toggle-new-content-element-wizard' : '') . '">'
383
                        . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render()
384
                        . ' '
385
                        . htmlspecialchars($this->getLanguageService()->getLL('content')) . '</a>';
386
                }
387
                if ($this->getBackendUser()->checkLanguageAccess($lP) && $columnId !== 'unused') {
0 ignored issues
show
Bug introduced by
$lP of type string is incompatible with the type integer expected by parameter $langValue of TYPO3\CMS\Core\Authentic...::checkLanguageAccess(). ( Ignorable by Annotation )

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

387
                if ($this->getBackendUser()->checkLanguageAccess(/** @scrutinizer ignore-type */ $lP) && $columnId !== 'unused') {
Loading history...
388
                    $content[$columnId] .= '
389
                    <div class="t3-page-ce t3js-page-ce" data-page="' . (int)$id . '" id="' . StringUtility::getUniqueId() . '">
390
                        <div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-page-' . $id . '-' . StringUtility::getUniqueId() . '">'
391
                            . $link
392
                            . '</div>
393
                        <div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div>
394
                    </div>
395
                    ';
396
                }
397
                $editUidList = '';
398
                if (!isset($contentRecordsPerColumn[$columnId]) || !is_array($contentRecordsPerColumn[$columnId])) {
399
                    $message = GeneralUtility::makeInstance(
400
                        FlashMessage::class,
401
                        $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:error.invalidBackendLayout'),
402
                        '',
403
                        FlashMessage::WARNING
404
                    );
405
                    $service = GeneralUtility::makeInstance(FlashMessageService::class);
406
                    $queue = $service->getMessageQueueByIdentifier();
407
                    $queue->addMessage($message);
408
                } else {
409
                    $rowArr = $contentRecordsPerColumn[$columnId];
410
                    $this->generateTtContentDataArray($rowArr);
411
412
                    foreach ((array)$rowArr as $rKey => $row) {
413
                        $this->contentElementCache[$lP][$columnId][$row['uid']] = $row;
414
                        if ($this->tt_contentConfig['languageMode']) {
415
                            $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
416
                        }
417
                        if (is_array($row) && !VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
418
                            $singleElementHTML = '<div class="t3-page-ce-dragitem" id="' . StringUtility::getUniqueId() . '">';
419
                            if (!$lP && ($this->defLangBinding || $row['sys_language_uid'] != -1)) {
420
                                $defaultLanguageElementsByColumn[$columnId][] = ($row['_ORIG_uid'] ?? $row['uid']);
421
                            }
422
                            $editUidList .= $row['uid'] . ',';
423
                            $disableMoveAndNewButtons = $this->defLangBinding && $lP > 0 && $this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP);
424
                            $singleElementHTML .= $this->tt_content_drawHeader(
425
                                $row,
426
                                0,
427
                                $disableMoveAndNewButtons,
428
                                true,
429
                                $this->hasContentModificationAndAccessPermissions()
430
                            );
431
                            $innerContent = '<div ' . ($row['_ORIG_uid'] ? ' class="ver-element"' : '') . '>'
432
                                . $this->tt_content_drawItem($row) . '</div>';
433
                            $singleElementHTML .= '<div class="t3-page-ce-body-inner">' . $innerContent . '</div></div>'
434
                                . $this->tt_content_drawFooter($row);
435
                            $isDisabled = $this->isDisabled('tt_content', $row);
436
                            $statusHidden = $isDisabled ? ' t3-page-ce-hidden t3js-hidden-record' : '';
437
                            $displayNone = !$this->tt_contentConfig['showHidden'] && $isDisabled ? ' style="display: none;"' : '';
438
                            $highlightHeader = '';
439
                            if ($this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid']) && (int)$row['l18n_parent'] === 0) {
440
                                $highlightHeader = ' t3-page-ce-danger';
441
                            } elseif ($columnId === 'unused') {
442
                                $highlightHeader = ' t3-page-ce-warning';
443
                            }
444
                            $singleElementHTML = '<div class="t3-page-ce' . $highlightHeader . ' t3js-page-ce t3js-page-ce-sortable ' . $statusHidden . '" id="element-tt_content-'
445
                                . $row['uid'] . '" data-table="tt_content" data-uid="' . $row['uid'] . '"' . $displayNone . '>' . $singleElementHTML . '</div>';
446
447
                            $singleElementHTML .= '<div class="t3-page-ce" data-colpos="' . $columnId . '">';
448
                            $singleElementHTML .= '<div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-page-' . $id .
449
                                '-' . StringUtility::getUniqueId() . '">';
450
                            // Add icon "new content element below"
451
                            if (!$disableMoveAndNewButtons
452
                                && $this->isContentEditable($lP)
0 ignored issues
show
Bug introduced by
$lP of type string is incompatible with the type integer|null expected by parameter $languageId of TYPO3\CMS\Backend\View\P...ew::isContentEditable(). ( Ignorable by Annotation )

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

452
                                && $this->isContentEditable(/** @scrutinizer ignore-type */ $lP)
Loading history...
453
                                && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
454
                                && $columnId !== 'unused'
455
                            ) {
456
                                // New content element:
457
                                if ($this->option_newWizard) {
458
                                    $urlParameters = [
459
                                        'id' => $row['pid'],
460
                                        'sys_language_uid' => $row['sys_language_uid'],
461
                                        'colPos' => $row['colPos'],
462
                                        'uid_pid' => -$row['uid'],
463
                                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
464
                                    ];
465
                                    $routeName = BackendUtility::getPagesTSconfig($row['pid'])['mod.']['newContentElementWizard.']['override']
466
                                        ?? 'new_content_element_wizard';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "??"; newline found
Loading history...
467
                                    $url = (string)$this->uriBuilder->buildUriFromRoute($routeName, $urlParameters);
468
                                } else {
469
                                    $urlParameters = [
470
                                        'edit' => [
471
                                            'tt_content' => [
472
                                                -$row['uid'] => 'new'
473
                                            ]
474
                                        ],
475
                                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
476
                                    ];
477
                                    $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
478
                                }
479
                                $title = htmlspecialchars($this->getLanguageService()->getLL('newContentElement'));
480
                                $singleElementHTML .= '<a href="' . htmlspecialchars($url) . '" '
481
                                    . 'title="' . $title . '"'
482
                                    . 'data-title="' . $title . '"'
483
                                    . 'class="btn btn-default btn-sm ' . ($this->option_newWizard ? 't3js-toggle-new-content-element-wizard' : '') . '">'
484
                                    . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render()
485
                                    . ' '
486
                                    . htmlspecialchars($this->getLanguageService()->getLL('content')) . '</a>';
487
                            }
488
                            $singleElementHTML .= '</div></div><div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div></div>';
489
                            if ($this->defLangBinding && $this->tt_contentConfig['languageMode']) {
490
                                $defLangBinding[$columnId][$lP][$row[$lP ? 'l18n_parent' : 'uid'] ?: $row['uid']] = $singleElementHTML;
491
                            } else {
492
                                $content[$columnId] .= $singleElementHTML;
493
                            }
494
                        } else {
495
                            unset($rowArr[$rKey]);
496
                        }
497
                    }
498
                    $content[$columnId] .= '</div>';
499
                    if ($columnId === 'unused') {
500
                        if (empty($unusedElementsMessage)) {
501
                            $unusedElementsMessage = GeneralUtility::makeInstance(
502
                                FlashMessage::class,
503
                                $this->getLanguageService()->getLL('staleUnusedElementsWarning'),
504
                                $this->getLanguageService()->getLL('staleUnusedElementsWarningTitle'),
505
                                FlashMessage::WARNING
506
                            );
507
                            $service = GeneralUtility::makeInstance(FlashMessageService::class);
508
                            $queue = $service->getMessageQueueByIdentifier();
509
                            $queue->addMessage($unusedElementsMessage);
510
                        }
511
                        $colTitle = $this->getLanguageService()->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:colPos.I.unused');
512
                        $editParam = '';
513
                    } else {
514
                        $colTitle = '';
515
                        foreach ($tcaItems as $item) {
516
                            if ($item[1] == $columnId) {
517
                                $colTitle = $this->getLanguageService()->sL($item[0]);
518
                            }
519
                        }
520
                        if (empty($colTitle)) {
521
                            $colTitle = BackendUtility::getProcessedValue('tt_content', 'colPos', $columnId);
522
                        }
523
                        $editParam = $this->doEdit && !empty($rowArr)
524
                            ? '&edit[tt_content][' . $editUidList . ']=edit&recTitle=' . rawurlencode(BackendUtility::getRecordTitle('pages', $this->pageRecord, true))
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
525
                            : '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
526
                    }
527
                    $head[$columnId] .= $this->tt_content_drawColHeader($colTitle, $editParam);
528
                }
529
            }
530
            // For each column, fit the rendered content into a table cell:
531
            $out = '';
532
            if ($this->tt_contentConfig['languageMode']) {
533
                // in language mode process the content elements, but only fill $languageColumn. output will be generated later
534
                $sortedLanguageColumn = [];
535
                foreach ($cList as $columnId) {
536
                    if (GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnId) || $columnId === 'unused') {
537
                        $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
538
539
                        // We sort $languageColumn again according to $cList as it may contain data already from above.
540
                        $sortedLanguageColumn[$columnId] = $languageColumn[$columnId];
541
                    }
542
                }
543
                if (!empty($languageColumn['unused'])) {
544
                    $sortedLanguageColumn['unused'] = $languageColumn['unused'];
545
                }
546
                $languageColumn = $sortedLanguageColumn;
547
            } else {
548
                // GRID VIEW:
549
                $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">';
550
                // Add colgroups
551
                $colCount = (int)$backendLayout['__config']['backend_layout.']['colCount'];
552
                $rowCount = (int)$backendLayout['__config']['backend_layout.']['rowCount'];
553
                $colSpan = 0;
554
                $rowSpan = 0;
555
                $grid .= '<colgroup>';
556
                for ($i = 0; $i < $colCount; $i++) {
557
                    $grid .= '<col />';
558
                }
559
                $grid .= '</colgroup>';
560
561
                // Check how to handle restricted columns
562
                $hideRestrictedCols = (bool)(BackendUtility::getPagesTSconfig($id)['mod.']['web_layout.']['hideRestrictedCols'] ?? false);
563
564
                // Cycle through rows
565
                for ($row = 1; $row <= $rowCount; $row++) {
566
                    $rowConfig = $backendLayout['__config']['backend_layout.']['rows.'][$row . '.'];
567
                    if (!isset($rowConfig)) {
568
                        continue;
569
                    }
570
                    $grid .= '<tr>';
571
                    for ($col = 1; $col <= $colCount; $col++) {
572
                        $columnConfig = $rowConfig['columns.'][$col . '.'];
573
                        if (!isset($columnConfig)) {
574
                            continue;
575
                        }
576
                        // Which tt_content colPos should be displayed inside this cell
577
                        $columnKey = (int)$columnConfig['colPos'];
578
                        // Render the grid cell
579
                        $colSpan = (int)$columnConfig['colspan'];
580
                        $rowSpan = (int)$columnConfig['rowspan'];
581
                        $grid .= '<td valign="top"' .
582
                            ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
583
                            ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
584
                            ' 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 .
585
                            ((!isset($columnConfig['colPos']) || $columnConfig['colPos'] === '') ? ' t3-grid-cell-unassigned' : '') .
586
                            ((isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && !$head[$columnKey]) || !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos']) ? ($hideRestrictedCols ? ' t3-grid-cell-restricted t3-grid-cell-hidden' : ' t3-grid-cell-restricted') : '') .
587
                            ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') .
588
                            ($rowSpan > 0 ? ' t3-gridCell-height' . $rowSpan : '') . '">';
589
590
                        // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
591
                        // If not, a new header without any buttons will be generated.
592
                        if (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && $head[$columnKey]
593
                            && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
594
                        ) {
595
                            $grid .= $head[$columnKey];
596
                            $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
0 ignored issues
show
Bug introduced by
$lP of type string is incompatible with the type integer expected by parameter $lP of TYPO3\CMS\Backend\View\P...nMarkupGeneratedEvent(). ( Ignorable by Annotation )

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

596
                            $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', /** @scrutinizer ignore-type */ $lP, $columnConfig);
Loading history...
597
                            $grid .= $content[$columnKey];
598
                        } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
599
                            && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
600
                        ) {
601
                            if (!$hideRestrictedCols) {
602
                                $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('noAccess'));
603
                                $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
604
                            }
605
                        } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
606
                            && !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
607
                        ) {
608
                            if (!$hideRestrictedCols) {
609
                                $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name']) .
610
                                  ' (' . $this->getLanguageService()->getLL('noAccess') . ')');
611
                                $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
612
                            }
613
                        } elseif (isset($columnConfig['name']) && $columnConfig['name'] !== '') {
614
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name'])
615
                                . ' (' . $this->getLanguageService()->getLL('notAssigned') . ')');
616
                            $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
617
                        } else {
618
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('notAssigned'));
619
                            $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
620
                        }
621
622
                        $grid .= $this->dispatchSectionMarkupGeneratedEvent('after', $lP, $columnConfig);
623
624
                        $grid .= '</td>';
625
                    }
626
                    $grid .= '</tr>';
627
                }
628
                if (!empty($content['unused'])) {
629
                    $grid .= '<tr>';
630
                    // Which tt_content colPos should be displayed inside this cell
631
                    $columnKey = 'unused';
632
                    // Render the grid cell
633
                    $colSpan = (int)$backendLayout['__config']['backend_layout.']['colCount'];
634
                    $grid .= '<td valign="top"' .
635
                        ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
636
                        ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
637
                        ' 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 .
638
                        ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') . '">';
639
640
                    // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
641
                    // If not, a new header without any buttons will be generated.
642
                    $grid .= $head[$columnKey] . $content[$columnKey];
643
                    $grid .= '</td></tr>';
644
                }
645
                $out .= $grid . '</table></div>';
646
            }
647
        }
648
        $elFromTable = $this->clipboard->elFromTable('tt_content');
649
        if (!empty($elFromTable) && $this->isPageEditable()) {
650
            $pasteItem = substr(key($elFromTable), 11);
651
            $pasteRecord = BackendUtility::getRecord('tt_content', (int)$pasteItem);
652
            $pasteTitle = $pasteRecord['header'] ?: $pasteItem;
653
            $copyMode = $this->clipboard->clipData['normal']['mode'] ? '-' . $this->clipboard->clipData['normal']['mode'] : '';
654
            $inlineJavaScript = '
655
                     top.pasteIntoLinkTemplate = '
656
                . $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

656
                . $this->tt_content_drawPasteIcon(/** @scrutinizer ignore-type */ $pasteItem, $pasteTitle, $copyMode, 't3js-paste-into', 'pasteIntoColumn')
Loading history...
657
                . ';
658
                    top.pasteAfterLinkTemplate = '
659
                . $this->tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, 't3js-paste-after', 'pasteAfterRecord')
660
                . ';';
661
        } else {
662
            $inlineJavaScript = '
663
                top.pasteIntoLinkTemplate = \'\';
664
                top.pasteAfterLinkTemplate = \'\';';
665
        }
666
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
667
        $pageRenderer->addJsInlineCode('pasteLinkTemplates', $inlineJavaScript);
668
        // If language mode, then make another presentation:
669
        // Notice that THIS presentation will override the value of $out!
670
        // But it needs the code above to execute since $languageColumn is filled with content we need!
671
        if ($this->tt_contentConfig['languageMode']) {
672
            return $this->generateLanguageView($languageIds, $defaultLanguageElementsByColumn, $languageColumn);
673
        }
674
        return $out;
675
    }
676
677
    /**
678
     * Shows the content elements of the selected languages in each column.
679
     * @param array $languageIds languages to render
680
     * @param array $defaultLanguageElementsByColumn
681
     * @param array $languageColumn
682
     * @return string the compiled content
683
     */
684
    protected function generateLanguageView(array $languageIds, array $defaultLanguageElementsByColumn, array $languageColumn): string
685
    {
686
        // Get language selector:
687
        $languageSelector = $this->languageSelector($this->id);
688
        // Reset out - we will make new content here:
689
        $out = '';
690
        // Traverse languages found on the page and build up the table displaying them side by side:
691
        $cCont = [];
692
        $sCont = [];
693
        foreach ($languageIds as $languageId) {
694
            $languageMode = '';
695
            $labelClass = 'info';
696
            // Header:
697
            $languageId = (int)$languageId;
698
            // Determine language mode
699
            if ($languageId > 0 && isset($this->languageHasTranslationsCache[$languageId]['mode'])) {
700
                switch ($this->languageHasTranslationsCache[$languageId]['mode']) {
701
                    case 'mixed':
702
                        $languageMode = $this->getLanguageService()->getLL('languageModeMixed');
703
                        $labelClass = 'danger';
704
                        break;
705
                    case 'connected':
706
                        $languageMode = $this->getLanguageService()->getLL('languageModeConnected');
707
                        break;
708
                    case 'free':
709
                        $languageMode = $this->getLanguageService()->getLL('languageModeFree');
710
                        break;
711
                    default:
712
                        // we'll let opcode optimize this intentionally empty case
713
                }
714
            }
715
            $columnAttributes = [
716
                'valign' => 'top',
717
                'class' => 't3-page-column t3-page-column-lang-name',
718
                'data-language-uid' => $languageId,
719
                'data-language-title' => $this->siteLanguages[$languageId]->getTitle(),
720
                'data-flag-identifier' => $this->siteLanguages[$languageId]->getFlagIdentifier()
721
            ];
722
723
            $cCont[$languageId] = '
724
					<td ' . GeneralUtility::implodeAttributes($columnAttributes, true) . '>
725
						<h2>' . htmlspecialchars($this->tt_contentConfig['languageCols'][$languageId]) . '</h2>
726
						' . ($languageMode !== '' ? '<span class="label label-' . $labelClass . '">' . $languageMode . '</span>' : '') . '
727
					</td>';
728
729
            $editLink = '';
730
            $recordIcon = '';
731
            $viewLink = '';
732
            // "View page" icon is added:
733
            if (!VersionState::cast($this->pageinfo['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
734
                $onClick = BackendUtility::viewOnClick(
735
                    $this->id,
736
                    '',
737
                    BackendUtility::BEgetRootLine($this->id),
738
                    '',
739
                    '',
740
                    '&L=' . $languageId
741
                );
742
                $viewLink = '<a href="#" class="btn btn-default btn-sm" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage')) . '">' . $this->iconFactory->getIcon('actions-view', Icon::SIZE_SMALL)->render() . '</a>';
743
            }
744
            // Language overlay page header:
745
            if ($languageId) {
746
                $pageLocalizationRecord = BackendUtility::getRecordLocalization('pages', $this->id, $languageId);
747
                if (is_array($pageLocalizationRecord)) {
748
                    $pageLocalizationRecord = reset($pageLocalizationRecord);
749
                }
750
                BackendUtility::workspaceOL('pages', $pageLocalizationRecord);
751
                $recordIcon = BackendUtility::wrapClickMenuOnIcon(
752
                    $this->iconFactory->getIconForRecord('pages', $pageLocalizationRecord, Icon::SIZE_SMALL)->render(),
753
                    'pages',
754
                    $pageLocalizationRecord['uid']
755
                );
756
                $urlParameters = [
757
                    'edit' => [
758
                        'pages' => [
759
                            $pageLocalizationRecord['uid'] => 'edit'
760
                        ]
761
                    ],
762
                    // Disallow manual adjustment of the language field for pages
763
                    'overrideVals' => [
764
                        'pages' => [
765
                            'sys_language_uid' => $languageId
766
                        ]
767
                    ],
768
                    'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
769
                ];
770
                $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
771
                if ($this->getBackendUser()->check('tables_modify', 'pages')) {
772
                    $editLink = '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
773
                        . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">'
774
                        . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
775
                }
776
777
                $defaultLanguageElements = [];
778
                array_walk($defaultLanguageElementsByColumn, function (array $columnContent) use (&$defaultLanguageElements) {
779
                    $defaultLanguageElements = array_merge($defaultLanguageElements, $columnContent);
780
                });
781
782
                $localizationButtons = [];
783
                $localizationButtons[] = $this->newLanguageButton(
784
                    $this->getNonTranslatedTTcontentUids($defaultLanguageElements, $this->id, $languageId),
785
                    $languageId
786
                );
787
788
                $languageLabel =
789
                    '<div class="btn-group">'
790
                    . $viewLink
791
                    . $editLink
792
                    . (!empty($localizationButtons) ? implode(LF, $localizationButtons) : '')
793
                    . '</div>'
794
                    . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($pageLocalizationRecord['title'], 20))
795
                ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
796
            } else {
797
                if ($this->getBackendUser()->checkLanguageAccess(0)) {
798
                    $recordIcon = BackendUtility::wrapClickMenuOnIcon(
799
                        $this->iconFactory->getIconForRecord('pages', $this->pageRecord, Icon::SIZE_SMALL)->render(),
800
                        'pages',
801
                        $this->id
802
                    );
803
                    $urlParameters = [
804
                        'edit' => [
805
                            'pages' => [
806
                                $this->id => 'edit'
807
                            ]
808
                        ],
809
                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
810
                    ];
811
                    $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
812
                    if ($this->getBackendUser()->check('tables_modify', 'pages')) {
813
                        $editLink = '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
814
                            . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">'
815
                            . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
816
                    }
817
                }
818
819
                $languageLabel =
820
                    '<div class="btn-group">'
821
                    . $viewLink
822
                    . $editLink
823
                    . '</div>'
824
                    . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($this->pageRecord['title'], 20));
825
            }
826
            $sCont[$languageId] = '
827
					<td class="t3-page-column t3-page-lang-label nowrap">' . $languageLabel . '</td>';
828
        }
829
        // Add headers:
830
        $out .= '<tr>' . implode('', $cCont) . '</tr>';
831
        $out .= '<tr>' . implode('', $sCont) . '</tr>';
832
        unset($cCont, $sCont);
833
834
        // Traverse previously built content for the columns:
835
        foreach ($languageColumn as $cKey => $cCont) {
836
            $out .= '<tr>';
837
            foreach ($cCont as $languageId => $columnContent) {
838
                $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>';
839
            }
840
            $out .= '</tr>';
841
            if ($this->defLangBinding && !empty($defLangBinding[$cKey])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $defLangBinding seems to never exist and therefore empty should always be true.
Loading history...
842
                $maxItemsCount = max(array_map('count', $defLangBinding[$cKey]));
843
                for ($i = 0; $i < $maxItemsCount; $i++) {
844
                    $defUid = $defaultLanguageElementsByColumn[$cKey][$i] ?? 0;
845
                    $cCont = [];
846
                    foreach ($languageIds as $languageId) {
847
                        if ($languageId > 0
848
                            && is_array($defLangBinding[$cKey][$languageId])
849
                            && !$this->checkIfTranslationsExistInLanguage($defaultLanguageElementsByColumn[$cKey], $languageId)
850
                            && count($defLangBinding[$cKey][$languageId]) > $i
851
                        ) {
852
                            $slice = array_slice($defLangBinding[$cKey][$languageId], $i, 1);
853
                            $element = $slice[0] ?? '';
854
                        } else {
855
                            $element = $defLangBinding[$cKey][$languageId][$defUid] ?? '';
856
                        }
857
                        $cCont[] = $element;
858
                    }
859
                    $out .= '
860
                        <tr>
861
							<td valign="top" class="t3-grid-cell">' . implode('</td>
862
							<td valign="top" class="t3-grid-cell">', $cCont) . '</td>
863
						</tr>';
864
                }
865
            }
866
        }
867
        // Finally, wrap it all in a table and add the language selector on top of it:
868
        return $languageSelector . '
869
                <div class="t3-grid-container">
870
                    <table cellpadding="0" cellspacing="0" class="t3-page-columns t3-grid-table t3js-page-columns">
871
						' . $out . '
872
                    </table>
873
				</div>';
874
    }
875
876
    /**
877
     * Gets content records per column.
878
     * This is required for correct workspace overlays.
879
     *
880
     * @param string $table Name of table storing content records, by default tt_content
881
     * @param int $id Page Id to be used (not used at all, but part of the API, see $this->pidSelect)
882
     * @param array $columns colPos values to be considered to be shown
883
     * @param string $additionalWhereClause Additional where clause for database select
884
     * @return array Associative array for each column (colPos)
885
     */
886
    protected function getContentRecordsPerColumn($table, $id, array $columns, $additionalWhereClause = '')
887
    {
888
        $contentRecordsPerColumn = array_fill_keys($columns, []);
889
        $columns = array_flip($columns);
890
        $queryBuilder = $this->getQueryBuilder(
891
            $table,
892
            $id,
893
            [
894
                $additionalWhereClause
895
            ]
896
        );
897
898
        // Traverse any selected elements and render their display code:
899
        $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

899
        $results = $this->getResult(/** @scrutinizer ignore-type */ $queryBuilder->execute());
Loading history...
900
        $unused = [];
901
        $hookArray = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['record_is_used'] ?? [];
902
        foreach ($results as $record) {
903
            $used = isset($columns[$record['colPos']]);
904
            foreach ($hookArray as $_funcRef) {
905
                $_params = ['columns' => $columns, 'record' => $record, 'used' => $used];
906
                $used = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
907
            }
908
            if ($used) {
909
                $columnValue = (string)$record['colPos'];
910
                $contentRecordsPerColumn[$columnValue][] = $record;
911
            } else {
912
                $unused[] = $record;
913
            }
914
        }
915
        if (!empty($unused)) {
916
            $contentRecordsPerColumn['unused'] = $unused;
917
        }
918
        return $contentRecordsPerColumn;
919
    }
920
921
    /**
922
     * Draw header for a content element column:
923
     *
924
     * @param string $colName Column name
925
     * @param string $editParams Edit params (Syntax: &edit[...] for FormEngine)
926
     * @return string HTML table
927
     */
928
    public function tt_content_drawColHeader($colName, $editParams = '')
0 ignored issues
show
Coding Style introduced by
Method name "PageLayoutView::tt_content_drawColHeader" is not in camel caps format
Loading history...
929
    {
930
        $icons = '';
931
        // Edit whole of column:
932
        if ($editParams && $this->hasContentModificationAndAccessPermissions() && $this->getBackendUser()->checkLanguageAccess(0)) {
933
            $link = $this->uriBuilder->buildUriFromRoute('record_edit') . $editParams . '&returnUrl=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'));
934
            $icons = '<a href="' . htmlspecialchars($link) . '"  title="'
935
                . htmlspecialchars($this->getLanguageService()->getLL('editColumn')) . '">'
936
                . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
937
            $icons = '<div class="t3-page-column-header-icons">' . $icons . '</div>';
938
        }
939
        return '<div class="t3-page-column-header">
940
					' . $icons . '
941
					<div class="t3-page-column-header-label">' . htmlspecialchars($colName) . '</div>
942
				</div>';
943
    }
944
945
    /**
946
     * Draw a paste icon either for pasting into a column or for pasting after a record
947
     *
948
     * @param int $pasteItem ID of the item in the clipboard
949
     * @param string $pasteTitle Title for the JS modal
950
     * @param string $copyMode copy or cut
951
     * @param string $cssClass CSS class to determine if pasting is done into column or after record
952
     * @param string $title title attribute of the generated link
953
     *
954
     * @return string Generated HTML code with link and icon
955
     */
956
    protected function tt_content_drawPasteIcon($pasteItem, $pasteTitle, $copyMode, $cssClass, $title)
0 ignored issues
show
Coding Style introduced by
Method name "PageLayoutView::tt_content_drawPasteIcon" is not in camel caps format
Loading history...
957
    {
958
        $pasteIcon = json_encode(
959
            ' <a data-content="' . htmlspecialchars($pasteItem) . '"'
960
            . ' data-title="' . htmlspecialchars($pasteTitle) . '"'
961
            . ' data-severity="warning"'
962
            . ' class="t3js-paste t3js-paste' . htmlspecialchars($copyMode) . ' ' . htmlspecialchars($cssClass) . ' btn btn-default btn-sm"'
963
            . ' title="' . htmlspecialchars($this->getLanguageService()->getLL($title)) . '">'
964
            . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
965
            . '</a>'
966
        );
967
        return $pasteIcon;
968
    }
969
970
    /**
971
     * Draw the footer for a single tt_content element
972
     *
973
     * @param array $row Record array
974
     * @return string HTML of the footer
975
     * @throws \UnexpectedValueException
976
     */
977
    protected function tt_content_drawFooter(array $row)
0 ignored issues
show
Coding Style introduced by
Method name "PageLayoutView::tt_content_drawFooter" is not in camel caps format
Loading history...
978
    {
979
        $content = '';
980
        // Get processed values:
981
        $info = [];
982
        $this->getProcessedValue('tt_content', 'starttime,endtime,fe_group,space_before_class,space_after_class', $row, $info);
983
984
        // Content element annotation
985
        if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']) && !empty($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']])) {
986
            $info[] = htmlspecialchars($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']]);
987
        }
988
989
        // Call drawFooter hooks
990
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawFooter'] ?? [] as $className) {
991
            $hookObject = GeneralUtility::makeInstance($className);
992
            if (!$hookObject instanceof PageLayoutViewDrawFooterHookInterface) {
993
                throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawFooterHookInterface::class, 1404378171);
994
            }
995
            $hookObject->preProcess($this, $info, $row);
996
        }
997
998
        // Display info from records fields:
999
        if (!empty($info)) {
1000
            $content = '<div class="t3-page-ce-info">
1001
				' . implode('<br>', $info) . '
1002
				</div>';
1003
        }
1004
        if (!empty($content)) {
1005
            $content = '<div class="t3-page-ce-footer">' . $content . '</div>';
1006
        }
1007
        return $content;
1008
    }
1009
1010
    /**
1011
     * Draw the header for a single tt_content element
1012
     *
1013
     * @param array $row Record array
1014
     * @param int $space Amount of pixel space above the header. UNUSED
1015
     * @param bool $disableMoveAndNewButtons If set the buttons for creating new elements and moving up and down are not shown.
1016
     * @param bool $langMode If set, we are in language mode and flags will be shown for languages
1017
     * @param bool $dragDropEnabled If set the move button must be hidden
1018
     * @return string HTML table with the record header.
1019
     */
1020
    public function tt_content_drawHeader($row, $space = 0, $disableMoveAndNewButtons = false, $langMode = false, $dragDropEnabled = false)
0 ignored issues
show
Unused Code introduced by
The parameter $space is not used and could be removed. ( Ignorable by Annotation )

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

1020
    public function tt_content_drawHeader($row, /** @scrutinizer ignore-unused */ $space = 0, $disableMoveAndNewButtons = false, $langMode = false, $dragDropEnabled = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Coding Style introduced by
Method name "PageLayoutView::tt_content_drawHeader" is not in camel caps format
Loading history...
1021
    {
1022
        $backendUser = $this->getBackendUser();
1023
        $out = '';
1024
        // Render control panel for the element
1025
        if ($backendUser->recordEditAccessInternals('tt_content', $row) && $this->isContentEditable($row['sys_language_uid'])) {
1026
            // Edit content element:
1027
            $urlParameters = [
1028
                'edit' => [
1029
                    'tt_content' => [
1030
                        $row['uid'] => 'edit'
1031
                    ]
1032
                ],
1033
                'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $row['uid'],
1034
            ];
1035
            $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters) . '#element-tt_content-' . $row['uid'];
1036
1037
            $out .= '<a class="btn btn-default" href="' . htmlspecialchars($url)
1038
                . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('edit'))
1039
                . '">' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
1040
            // Hide element:
1041
            $hiddenField = $GLOBALS['TCA']['tt_content']['ctrl']['enablecolumns']['disabled'];
1042
            if ($hiddenField && $GLOBALS['TCA']['tt_content']['columns'][$hiddenField]
1043
                && (!$GLOBALS['TCA']['tt_content']['columns'][$hiddenField]['exclude']
1044
                    || $backendUser->check('non_exclude_fields', 'tt_content:' . $hiddenField))
1045
            ) {
1046
                if ($row[$hiddenField]) {
1047
                    $value = 0;
1048
                    $label = 'unHide';
1049
                } else {
1050
                    $value = 1;
1051
                    $label = 'hide';
1052
                }
1053
                $params = '&data[tt_content][' . ($row['_ORIG_uid'] ?: $row['uid'])
1054
                    . '][' . $hiddenField . ']=' . $value;
1055
                $out .= '<a class="btn btn-default" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1056
                    . '#element-tt_content-' . $row['uid'] . '" title="' . htmlspecialchars($this->getLanguageService()->getLL($label)) . '">'
1057
                    . $this->iconFactory->getIcon('actions-edit-' . strtolower($label), Icon::SIZE_SMALL)->render() . '</a>';
1058
            }
1059
            // Delete
1060
            $disableDelete = (bool)\trim(
1061
                $backendUser->getTSConfig()['options.']['disableDelete.']['tt_content']
1062
                ?? $backendUser->getTSConfig()['options.']['disableDelete']
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "??"; newline found
Loading history...
1063
                ?? '0'
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "??"; newline found
Loading history...
1064
            );
1065
            if (!$disableDelete) {
1066
                $params = '&cmd[tt_content][' . $row['uid'] . '][delete]=1';
1067
                $refCountMsg = BackendUtility::referenceCount(
1068
                    'tt_content',
1069
                    $row['uid'],
1070
                    ' ' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord'),
1071
                    $this->getReferenceCount('tt_content', $row['uid'])
1072
                ) . BackendUtility::translationCount(
1073
                    'tt_content',
1074
                    $row['uid'],
1075
                    ' ' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord')
1076
                );
1077
                $confirm = $this->getLanguageService()->getLL('deleteWarning')
1078
                    . $refCountMsg;
1079
                $out .= '<a class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params)) . '"'
1080
                    . ' data-severity="warning"'
1081
                    . ' data-title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:label.confirm.delete_record.title')) . '"'
1082
                    . ' data-content="' . htmlspecialchars($confirm) . '" '
1083
                    . ' data-button-close-text="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel')) . '"'
1084
                    . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('deleteItem')) . '">'
1085
                    . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
1086
                if ($out && $this->hasContentModificationAndAccessPermissions()) {
1087
                    $out = '<div class="btn-group btn-group-sm" role="group">' . $out . '</div>';
1088
                } else {
1089
                    $out = '';
1090
                }
1091
            }
1092
            if (!$disableMoveAndNewButtons) {
1093
                $moveButtonContent = '';
1094
                $displayMoveButtons = false;
1095
                // Move element up:
1096
                if ($this->tt_contentData['prev'][$row['uid']]) {
1097
                    $params = '&cmd[tt_content][' . $row['uid'] . '][move]=' . $this->tt_contentData['prev'][$row['uid']];
1098
                    $moveButtonContent .= '<a class="btn btn-default" href="'
1099
                        . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1100
                        . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('moveUp')) . '">'
1101
                        . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . '</a>';
1102
                    if (!$dragDropEnabled) {
1103
                        $displayMoveButtons = true;
1104
                    }
1105
                } else {
1106
                    $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
1107
                }
1108
                // Move element down:
1109
                if ($this->tt_contentData['next'][$row['uid']]) {
1110
                    $params = '&cmd[tt_content][' . $row['uid'] . '][move]= ' . $this->tt_contentData['next'][$row['uid']];
1111
                    $moveButtonContent .= '<a class="btn btn-default" href="'
1112
                        . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1113
                        . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('moveDown')) . '">'
1114
                        . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . '</a>';
1115
                    if (!$dragDropEnabled) {
1116
                        $displayMoveButtons = true;
1117
                    }
1118
                } else {
1119
                    $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
1120
                }
1121
                if ($displayMoveButtons) {
1122
                    $out .= '<div class="btn-group btn-group-sm" role="group">' . $moveButtonContent . '</div>';
1123
                }
1124
            }
1125
        }
1126
        $allowDragAndDrop = $this->isDragAndDropAllowed($row);
1127
        $additionalIcons = [];
1128
        $additionalIcons[] = $this->getIcon('tt_content', $row) . ' ';
1129
        if ($langMode && isset($this->siteLanguages[(int)$row['sys_language_uid']])) {
1130
            $additionalIcons[] = $this->renderLanguageFlag($this->siteLanguages[(int)$row['sys_language_uid']]);
1131
        }
1132
        // Get record locking status:
1133
        if ($lockInfo = BackendUtility::isRecordLocked('tt_content', $row['uid'])) {
1134
            $additionalIcons[] = '<a href="#" data-toggle="tooltip" data-title="' . htmlspecialchars($lockInfo['msg']) . '">'
1135
                . $this->iconFactory->getIcon('warning-in-use', Icon::SIZE_SMALL)->render() . '</a>';
1136
        }
1137
        // Call stats information hook
1138
        $_params = ['tt_content', $row['uid'], &$row];
1139
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'] ?? [] as $_funcRef) {
1140
            $additionalIcons[] = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1141
        }
1142
1143
        // Wrap the whole header
1144
        // NOTE: end-tag for <div class="t3-page-ce-body"> is in getTable_tt_content()
1145
        return '<div class="t3-page-ce-header ' . ($allowDragAndDrop ? 't3-page-ce-header-draggable t3js-page-ce-draghandle' : '') . '">
1146
					<div class="t3-page-ce-header-icons-left">' . implode('', $additionalIcons) . '</div>
1147
					<div class="t3-page-ce-header-icons-right">' . ($out ? '<div class="btn-toolbar">' . $out . '</div>' : '') . '</div>
1148
				</div>
1149
				<div class="t3-page-ce-body">';
1150
    }
1151
1152
    /**
1153
     * Gets the number of records referencing the record with the UID $uid in
1154
     * the table $tableName.
1155
     *
1156
     * @param string $tableName
1157
     * @param int $uid
1158
     * @return int The number of references to record $uid in table
1159
     */
1160
    protected function getReferenceCount(string $tableName, int $uid): int
1161
    {
1162
        if (!isset($this->referenceCount[$tableName][$uid])) {
1163
            $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
1164
            $numberOfReferences = $referenceIndex->getNumberOfReferencedRecords($tableName, $uid);
1165
            $this->referenceCount[$tableName][$uid] = $numberOfReferences;
1166
        }
1167
        return $this->referenceCount[$tableName][$uid];
1168
    }
1169
1170
    /**
1171
     * Determine whether Drag & Drop should be allowed
1172
     *
1173
     * @param array $row
1174
     * @return bool
1175
     */
1176
    protected function isDragAndDropAllowed(array $row)
1177
    {
1178
        if ((int)$row['l18n_parent'] === 0 &&
1179
            (
1180
                $this->getBackendUser()->isAdmin()
1181
                || ((int)$row['editlock'] === 0 && (int)$this->pageinfo['editlock'] === 0)
1182
                && $this->hasContentModificationAndAccessPermissions()
1183
                && $this->getBackendUser()->checkAuthMode('tt_content', 'CType', $row['CType'], $GLOBALS['TYPO3_CONF_VARS']['BE']['explicitADmode'])
1184
            )
1185
        ) {
1186
            return true;
1187
        }
1188
        return false;
1189
    }
1190
1191
    /**
1192
     * Draws the preview content for a content element
1193
     *
1194
     * @param array $row Content element
1195
     * @return string HTML
1196
     * @throws \UnexpectedValueException
1197
     */
1198
    public function tt_content_drawItem($row)
0 ignored issues
show
Coding Style introduced by
Method name "PageLayoutView::tt_content_drawItem" is not in camel caps format
Loading history...
1199
    {
1200
        $out = '';
1201
        $outHeader = $this->renderContentElementHeader($row);
1202
        $drawItem = true;
1203
        // Hook: Render an own preview of a record
1204
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem'] ?? [] as $className) {
1205
            $hookObject = GeneralUtility::makeInstance($className);
1206
            if (!$hookObject instanceof PageLayoutViewDrawItemHookInterface) {
1207
                throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawItemHookInterface::class, 1218547409);
1208
            }
1209
            $hookObject->preProcess($this, $drawItem, $outHeader, $out, $row);
1210
        }
1211
1212
        // If the previous hook did not render something,
1213
        // then check if a Fluid-based preview template was defined for this CType
1214
        // and render it via Fluid. Possible option:
1215
        // mod.web_layout.tt_content.preview.media = EXT:site_mysite/Resources/Private/Templates/Preview/Media.html
1216
        if ($drawItem) {
1217
            $fluidPreview = $this->renderContentElementPreviewFromFluidTemplate($row);
1218
            if ($fluidPreview !== null) {
1219
                $out .= $fluidPreview;
1220
                $drawItem = false;
1221
            }
1222
        }
1223
1224
        // Draw preview of the item depending on its CType (if not disabled by previous hook)
1225
        if ($drawItem) {
1226
            $out .= $this->renderContentElementPreview($row);
1227
        }
1228
        $out = $outHeader . '<span class="exampleContent">' . $out . '</span>';
1229
        if ($this->isDisabled('tt_content', $row)) {
1230
            return '<span class="text-muted">' . $out . '</span>';
1231
        }
1232
        return $out;
1233
    }
1234
1235
    public function renderContentElementHeader(array $row): string
1236
    {
1237
        $outHeader = '';
1238
        // Make header:
1239
        if ($row['header']) {
1240
            $hiddenHeaderNote = '';
1241
            // If header layout is set to 'hidden', display an accordant note:
1242
            if ($row['header_layout'] == 100) {
1243
                $hiddenHeaderNote = ' <em>[' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.hidden')) . ']</em>';
1244
            }
1245
            $outHeader = $row['date']
1246
                ? htmlspecialchars($this->itemLabels['date'] . ' ' . BackendUtility::date($row['date'])) . '<br />'
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
1247
                : '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
1248
            $outHeader .= '<strong>' . $this->linkEditContent($this->renderText($row['header']), $row)
1249
                . $hiddenHeaderNote . '</strong><br />';
1250
        }
1251
        return $outHeader;
1252
    }
1253
1254
    public function renderContentElementPreviewFromFluidTemplate(array $row): ?string
1255
    {
1256
        $tsConfig = BackendUtility::getPagesTSconfig($row['pid'])['mod.']['web_layout.']['tt_content.']['preview.'] ?? [];
1257
        $fluidTemplateFile = '';
1258
1259
        if ($row['CType'] === 'list' && !empty($row['list_type'])
1260
            && !empty($tsConfig['list.'][$row['list_type']])
1261
        ) {
1262
            $fluidTemplateFile = $tsConfig['list.'][$row['list_type']];
1263
        } elseif (!empty($tsConfig[$row['CType']])) {
1264
            $fluidTemplateFile = $tsConfig[$row['CType']];
1265
        }
1266
1267
        if ($fluidTemplateFile) {
1268
            $fluidTemplateFile = GeneralUtility::getFileAbsFileName($fluidTemplateFile);
1269
            if ($fluidTemplateFile) {
1270
                try {
1271
                    $view = GeneralUtility::makeInstance(StandaloneView::class);
1272
                    $view->setTemplatePathAndFilename($fluidTemplateFile);
1273
                    $view->assignMultiple($row);
1274
                    if (!empty($row['pi_flexform'])) {
1275
                        $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
1276
                        $view->assign('pi_flexform_transformed', $flexFormService->convertFlexFormContentToArray($row['pi_flexform']));
1277
                    }
1278
                    return $view->render();
1279
                } catch (\Exception $e) {
1280
                    $this->logger->warning(sprintf(
1281
                        'The backend preview for content element %d can not be rendered using the Fluid template file "%s": %s',
1282
                        $row['uid'],
1283
                        $fluidTemplateFile,
1284
                        $e->getMessage()
1285
                    ));
1286
1287
                    if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] && $this->getBackendUser()->isAdmin()) {
1288
                        $view = GeneralUtility::makeInstance(StandaloneView::class);
1289
                        $view->assign('error', [
1290
                            'message' => str_replace(Environment::getProjectPath(), '', $e->getMessage()),
1291
                            'title' => 'Error while rendering FluidTemplate preview using ' . str_replace(Environment::getProjectPath(), '', $fluidTemplateFile),
1292
                        ]);
1293
                        $view->setTemplateSource('<f:be.infobox title="{error.title}" state="2">{error.message}</f:be.infobox>');
1294
                        return $view->render();
1295
                    }
1296
                }
1297
            }
1298
        }
1299
        return null;
1300
    }
1301
1302
    /**
1303
     * Renders the preview part of a content element
1304
     * @param array $row given tt_content database record
1305
     * @return string
1306
     */
1307
    public function renderContentElementPreview(array $row): string
1308
    {
1309
        $previewHtml = '';
1310
        switch ($row['CType']) {
1311
            case 'header':
1312
                if ($row['subheader']) {
1313
                    $previewHtml = $this->linkEditContent($this->renderText($row['subheader']), $row) . '<br />';
1314
                }
1315
                break;
1316
            case 'bullets':
1317
            case 'table':
1318
                if ($row['bodytext']) {
1319
                    $previewHtml = $this->linkEditContent($this->renderText($row['bodytext']), $row) . '<br />';
1320
                }
1321
                break;
1322
            case 'uploads':
1323
                if ($row['media']) {
1324
                    $previewHtml = $this->linkEditContent($this->getThumbCodeUnlinked($row, 'tt_content', 'media'), $row) . '<br />';
1325
                }
1326
                break;
1327
            case 'shortcut':
1328
                if (!empty($row['records'])) {
1329
                    $shortcutContent = [];
1330
                    $recordList = explode(',', $row['records']);
1331
                    foreach ($recordList as $recordIdentifier) {
1332
                        $split = BackendUtility::splitTable_Uid($recordIdentifier);
1333
                        $tableName = empty($split[0]) ? 'tt_content' : $split[0];
1334
                        $shortcutRecord = BackendUtility::getRecord($tableName, $split[1]);
0 ignored issues
show
Bug introduced by
$split[1] of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Backend\Utilit...endUtility::getRecord(). ( Ignorable by Annotation )

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

1334
                        $shortcutRecord = BackendUtility::getRecord($tableName, /** @scrutinizer ignore-type */ $split[1]);
Loading history...
1335
                        if (is_array($shortcutRecord)) {
1336
                            $icon = $this->iconFactory->getIconForRecord($tableName, $shortcutRecord, Icon::SIZE_SMALL)->render();
1337
                            $icon = BackendUtility::wrapClickMenuOnIcon(
1338
                                $icon,
1339
                                $tableName,
1340
                                $shortcutRecord['uid']
1341
                            );
1342
                            $shortcutContent[] = $icon
1343
                                . htmlspecialchars(BackendUtility::getRecordTitle($tableName, $shortcutRecord));
1344
                        }
1345
                    }
1346
                    $previewHtml = implode('<br />', $shortcutContent) . '<br />';
1347
                }
1348
                break;
1349
            case 'list':
1350
                $hookOut = '';
1351
                $_params = ['pObj' => &$this, 'row' => $row];
1352
                foreach (
0 ignored issues
show
Coding Style introduced by
Space found after opening bracket of FOREACH loop
Loading history...
Coding Style introduced by
Space found before closing bracket of FOREACH loop
Loading history...
1353
                    $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'][$row['list_type']] ??
1354
                    $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['_DEFAULT'] ??
1355
                    [] as $_funcRef
1356
                ) {
1357
                    $hookOut .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1358
                }
1359
                if ((string)$hookOut !== '') {
1360
                    $previewHtml = $hookOut;
1361
                } elseif (!empty($row['list_type'])) {
1362
                    $label = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'list_type', $row['list_type']);
1363
                    if (!empty($label)) {
1364
                        $previewHtml = $this->linkEditContent('<strong>' . htmlspecialchars($this->getLanguageService()->sL($label)) . '</strong>', $row) . '<br />';
1365
                    } else {
1366
                        $message = sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'), $row['list_type']);
1367
                        $previewHtml = '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
1368
                    }
1369
                } else {
1370
                    $previewHtml = '<strong>' . $this->getLanguageService()->getLL('noPluginSelected') . '</strong>';
1371
                }
1372
                $previewHtml .= htmlspecialchars($this->getLanguageService()->sL(
1373
                    BackendUtility::getLabelFromItemlist('tt_content', 'pages', $row['pages'])
1374
                )) . '<br />';
1375
                break;
1376
            default:
1377
                $contentType = $this->CType_labels[$row['CType']];
1378
                if (!isset($contentType)) {
1379
                    $contentType = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'CType', $row['CType']);
1380
                }
1381
1382
                if ($contentType) {
1383
                    $previewHtml = $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $row) . '<br />';
1384
                    if ($row['bodytext']) {
1385
                        $previewHtml .= $this->linkEditContent($this->renderText($row['bodytext']), $row) . '<br />';
1386
                    }
1387
                    if ($row['image']) {
1388
                        $previewHtml .= $this->linkEditContent($this->getThumbCodeUnlinked($row, 'tt_content', 'image'), $row) . '<br />';
1389
                    }
1390
                } else {
1391
                    $message = sprintf(
1392
                        $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'),
1393
                        $row['CType']
1394
                    );
1395
                    $previewHtml = '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
1396
                }
1397
        }
1398
        return $previewHtml;
1399
    }
1400
1401
    /**
1402
     * Filters out all tt_content uids which are already translated so only non-translated uids is left.
1403
     * Selects across columns, but within in the same PID. Columns are expect to be the same
1404
     * for translations and original but this may be a conceptual error (?)
1405
     *
1406
     * @param array $defaultLanguageUids Numeric array with uids of tt_content elements in the default language
1407
     * @param int $id The page UID from which to fetch untranslated records (unused, remains in place for compatibility)
1408
     * @param int $lP Sys language UID
1409
     * @return array Modified $defLanguageCount
1410
     */
1411
    public function getNonTranslatedTTcontentUids($defaultLanguageUids, $id, $lP)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed. ( Ignorable by Annotation )

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

1411
    public function getNonTranslatedTTcontentUids($defaultLanguageUids, /** @scrutinizer ignore-unused */ $id, $lP)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1412
    {
1413
        if ($lP && !empty($defaultLanguageUids)) {
1414
            // Select all translations here:
1415
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1416
                ->getQueryBuilderForTable('tt_content');
1417
            $queryBuilder->getRestrictions()
1418
                ->removeAll()
1419
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1420
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class, null, false));
1421
            $queryBuilder
1422
                ->select('*')
1423
                ->from('tt_content')
1424
                ->where(
1425
                    $queryBuilder->expr()->eq(
1426
                        'sys_language_uid',
1427
                        $queryBuilder->createNamedParameter($lP, \PDO::PARAM_INT)
1428
                    ),
1429
                    $queryBuilder->expr()->in(
1430
                        'l18n_parent',
1431
                        $queryBuilder->createNamedParameter($defaultLanguageUids, Connection::PARAM_INT_ARRAY)
1432
                    )
1433
                );
1434
1435
            $result = $queryBuilder->execute();
1436
1437
            // Flip uids:
1438
            $defaultLanguageUids = array_flip($defaultLanguageUids);
1439
            // Traverse any selected elements and unset original UID if any:
1440
            while ($row = $result->fetch()) {
1441
                BackendUtility::workspaceOL('tt_content', $row);
1442
                unset($defaultLanguageUids[$row['l18n_parent']]);
1443
            }
1444
            // Flip again:
1445
            $defaultLanguageUids = array_keys($defaultLanguageUids);
1446
        }
1447
        return $defaultLanguageUids;
1448
    }
1449
1450
    /**
1451
     * Creates button which is used to create copies of records..
1452
     *
1453
     * @param array $defaultLanguageUids Numeric array with uids of tt_content elements in the default language
1454
     * @param int $lP Sys language UID
1455
     * @return string "Copy languages" button, if available.
1456
     */
1457
    public function newLanguageButton($defaultLanguageUids, $lP)
1458
    {
1459
        $lP = (int)$lP;
1460
        if (!$this->doEdit || !$lP || !$this->hasContentModificationAndAccessPermissions()) {
1461
            return '';
1462
        }
1463
        $theNewButton = '';
1464
1465
        $localizationTsConfig = BackendUtility::getPagesTSconfig($this->id)['mod.']['web_layout.']['localization.'] ?? [];
1466
        $allowCopy = (bool)($localizationTsConfig['enableCopy'] ?? true);
1467
        $allowTranslate = (bool)($localizationTsConfig['enableTranslate'] ?? true);
1468
        if (!empty($this->languageHasTranslationsCache[$lP])) {
1469
            if (isset($this->languageHasTranslationsCache[$lP]['hasStandAloneContent'])) {
1470
                $allowTranslate = false;
1471
            }
1472
            if (isset($this->languageHasTranslationsCache[$lP]['hasTranslations'])) {
1473
                $allowCopy = $allowCopy && !$this->languageHasTranslationsCache[$lP]['hasTranslations'];
1474
            }
1475
        }
1476
1477
        if (isset($this->contentElementCache[$lP]) && is_array($this->contentElementCache[$lP])) {
1478
            foreach ($this->contentElementCache[$lP] as $column => $records) {
1479
                foreach ($records as $record) {
1480
                    $key = array_search($record['l10n_source'], $defaultLanguageUids);
1481
                    if ($key !== false) {
1482
                        unset($defaultLanguageUids[$key]);
1483
                    }
1484
                }
1485
            }
1486
        }
1487
1488
        if (!empty($defaultLanguageUids)) {
1489
            $theNewButton =
1490
                '<a'
1491
                    . ' href="#"'
1492
                    . ' class="btn btn-default btn-sm t3js-localize disabled"'
1493
                    . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('newPageContent_translate')) . '"'
1494
                    . ' data-page="' . htmlspecialchars($this->getLocalizedPageTitle()) . '"'
1495
                    . ' data-has-elements="' . (int)!empty($this->contentElementCache[$lP]) . '"'
1496
                    . ' data-allow-copy="' . (int)$allowCopy . '"'
1497
                    . ' data-allow-translate="' . (int)$allowTranslate . '"'
1498
                    . ' data-table="tt_content"'
1499
                    . ' data-page-id="' . (int)GeneralUtility::_GP('id') . '"'
1500
                    . ' data-language-id="' . $lP . '"'
1501
                    . ' data-language-name="' . htmlspecialchars($this->tt_contentConfig['languageCols'][$lP]) . '"'
1502
                . '>'
1503
                . $this->iconFactory->getIcon('actions-localize', Icon::SIZE_SMALL)->render()
1504
                . ' ' . htmlspecialchars($this->getLanguageService()->getLL('newPageContent_translate'))
1505
                . '</a>';
1506
        }
1507
1508
        return $theNewButton;
1509
    }
1510
1511
    /**
1512
     * Will create a link on the input string and possibly a big button after the string which links to editing in the RTE.
1513
     * Used for content element content displayed so the user can click the content / "Edit in Rich Text Editor" button
1514
     *
1515
     * @param string $str String to link. Must be prepared for HTML output.
1516
     * @param array $row The row.
1517
     * @return string If the whole thing was editable ($this->doEdit) $str is return with link around. Otherwise just $str.
1518
     * @see getTable_tt_content()
1519
     */
1520
    public function linkEditContent($str, $row)
1521
    {
1522
        if ($this->doEdit
1523
            && $this->hasContentModificationAndAccessPermissions()
1524
            && $this->getBackendUser()->recordEditAccessInternals('tt_content', $row)
1525
        ) {
1526
            $urlParameters = [
1527
                'edit' => [
1528
                    'tt_content' => [
1529
                        $row['uid'] => 'edit'
1530
                    ]
1531
                ],
1532
                'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $row['uid']
1533
            ];
1534
            $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1535
            return '<a href="' . htmlspecialchars($url) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">' . $str . '</a>';
1536
        }
1537
        return $str;
1538
    }
1539
1540
    /**
1541
     * Make selector box for creating new translation in a language
1542
     * Displays only languages which are not yet present for the current page and
1543
     * that are not disabled with page TS.
1544
     *
1545
     * @param int $id Page id for which to create a new translation record of pages
1546
     * @return string HTML <select> element (if there were items for the box anyways...)
1547
     * @see getTable_tt_content()
1548
     */
1549
    public function languageSelector($id)
1550
    {
1551
        if (!$this->getBackendUser()->check('tables_modify', 'pages')) {
1552
            return '';
1553
        }
1554
        $id = (int)$id;
1555
1556
        // First, select all languages that are available for the current user
1557
        $availableTranslations = [];
1558
        foreach ($this->siteLanguages as $language) {
1559
            if ($language->getLanguageId() === 0) {
1560
                continue;
1561
            }
1562
            $availableTranslations[$language->getLanguageId()] = $language->getTitle();
1563
        }
1564
1565
        // Then, subtract the languages which are already on the page:
1566
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
1567
        $queryBuilder->getRestrictions()->removeAll()
1568
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1569
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1570
        $queryBuilder->select('uid', $GLOBALS['TCA']['pages']['ctrl']['languageField'])
1571
            ->from('pages')
1572
            ->where(
1573
                $queryBuilder->expr()->eq(
1574
                    $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
1575
                    $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
1576
                )
1577
            );
1578
        $statement = $queryBuilder->execute();
1579
        while ($row = $statement->fetch()) {
1580
            unset($availableTranslations[(int)$row[$GLOBALS['TCA']['pages']['ctrl']['languageField']]]);
1581
        }
1582
        // If any languages are left, make selector:
1583
        if (!empty($availableTranslations)) {
1584
            $output = '<option value="">' . htmlspecialchars($this->getLanguageService()->getLL('new_language')) . '</option>';
1585
            foreach ($availableTranslations as $languageUid => $languageTitle) {
1586
                // Build localize command URL to DataHandler (tce_db)
1587
                // which redirects to FormEngine (record_edit)
1588
                // which, when finished editing should return back to the current page (returnUrl)
1589
                $parameters = [
1590
                    'justLocalized' => 'pages:' . $id . ':' . $languageUid,
1591
                    'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1592
                ];
1593
                $redirectUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $parameters);
1594
                $targetUrl = BackendUtility::getLinkToDataHandlerAction(
1595
                    '&cmd[pages][' . $id . '][localize]=' . $languageUid,
1596
                    $redirectUrl
1597
                );
1598
1599
                $output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
1600
            }
1601
1602
            return '<div class="form-inline form-inline-spaced">'
1603
                . '<div class="form-group">'
1604
                . '<select class="form-control input-sm" name="createNewLanguage" onchange="window.location.href=this.options[this.selectedIndex].value">'
1605
                . $output
1606
                . '</select></div></div>';
1607
        }
1608
        return '';
1609
    }
1610
1611
    /**
1612
     * Traverse the result pointer given, adding each record to array and setting some internal values at the same time.
1613
     *
1614
     * @param Statement $result DBAL Statement
1615
     * @return array The selected rows returned in this array.
1616
     */
1617
    public function getResult(Statement $result): array
1618
    {
1619
        $output = [];
1620
        // Traverse the result:
1621
        while ($row = $result->fetch()) {
1622
            BackendUtility::workspaceOL('tt_content', $row, -99, true);
1623
            if ($row) {
1624
                // Add the row to the array:
1625
                $output[] = $row;
1626
            }
1627
        }
1628
        $this->generateTtContentDataArray($output);
1629
        // Return selected records
1630
        return $output;
1631
    }
1632
1633
    /********************************
1634
     *
1635
     * Various helper functions
1636
     *
1637
     ********************************/
1638
1639
    /**
1640
     * Initializes the clipboard for generating paste links
1641
     *
1642
     *
1643
     * @see \TYPO3\CMS\Backend\Controller\ContextMenuController::clipboardAction()
1644
     * @see \TYPO3\CMS\Filelist\Controller\FileListController::indexAction()
1645
     */
1646
    protected function initializeClipboard()
1647
    {
1648
        // Start clipboard
1649
        $this->clipboard = GeneralUtility::makeInstance(Clipboard::class);
1650
1651
        // Initialize - reads the clipboard content from the user session
1652
        $this->clipboard->initializeClipboard();
1653
1654
        // This locks the clipboard to the Normal for this request.
1655
        $this->clipboard->lockToNormal();
1656
1657
        // Clean up pad
1658
        $this->clipboard->cleanCurrent();
1659
1660
        // Save the clipboard content
1661
        $this->clipboard->endClipboard();
1662
    }
1663
1664
    /**
1665
     * Generates the data for previous and next elements which is needed for movements.
1666
     *
1667
     * @param array $rowArray
1668
     */
1669
    protected function generateTtContentDataArray(array $rowArray)
1670
    {
1671
        if (empty($this->tt_contentData)) {
1672
            $this->tt_contentData = [
1673
                'next' => [],
1674
                'prev' => [],
1675
            ];
1676
        }
1677
        foreach ($rowArray as $key => $value) {
1678
            // Create information for next and previous content elements
1679
            if (isset($rowArray[$key - 1])) {
1680
                if (isset($rowArray[$key - 2])) {
1681
                    $this->tt_contentData['prev'][$value['uid']] = -$rowArray[$key - 2]['uid'];
1682
                } else {
1683
                    $this->tt_contentData['prev'][$value['uid']] = $value['pid'];
1684
                }
1685
                $this->tt_contentData['next'][$rowArray[$key - 1]['uid']] = -$value['uid'];
1686
            }
1687
        }
1688
    }
1689
1690
    /**
1691
     * Processing of larger amounts of text (usually from RTE/bodytext fields) with word wrapping etc.
1692
     *
1693
     * @param string $input Input string
1694
     * @return string Output string
1695
     */
1696
    public function renderText($input)
1697
    {
1698
        $input = strip_tags($input);
1699
        $input = GeneralUtility::fixed_lgd_cs($input, 1500);
1700
        return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
1701
    }
1702
1703
    /**
1704
     * Creates the icon image tag for record from table and wraps it in a link which will trigger the click menu.
1705
     *
1706
     * @param string $table Table name
1707
     * @param array $row Record array
1708
     * @return string HTML for the icon
1709
     */
1710
    public function getIcon($table, $row)
1711
    {
1712
        // Initialization
1713
        $toolTip = BackendUtility::getRecordToolTip($row, $table);
1714
        $icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
1715
        // The icon with link
1716
        if ($this->getBackendUser()->recordEditAccessInternals($table, $row)) {
1717
            $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
1718
        }
1719
        return $icon;
1720
    }
1721
1722
    /**
1723
     * Creates processed values for all field names in $fieldList based on values from $row array.
1724
     * The result is 'returned' through $info which is passed as a reference
1725
     *
1726
     * @param string $table Table name
1727
     * @param string $fieldList Comma separated list of fields.
1728
     * @param array $row Record from which to take values for processing.
1729
     * @param array $info Array to which the processed values are added.
1730
     */
1731
    public function getProcessedValue($table, $fieldList, array $row, array &$info)
1732
    {
1733
        // Splitting values from $fieldList
1734
        $fieldArr = explode(',', $fieldList);
1735
        // Traverse fields from $fieldList
1736
        foreach ($fieldArr as $field) {
1737
            if ($row[$field]) {
1738
                $info[] = '<strong>' . htmlspecialchars($this->itemLabels[$field]) . '</strong> '
1739
                    . htmlspecialchars(BackendUtility::getProcessedValue($table, $field, $row[$field]));
1740
            }
1741
        }
1742
    }
1743
1744
    /**
1745
     * Returns TRUE, if the record given as parameters is NOT visible based on hidden/starttime/endtime (if available)
1746
     *
1747
     * @param string $table Tablename of table to test
1748
     * @param array $row Record row.
1749
     * @return bool Returns TRUE, if disabled.
1750
     */
1751
    public function isDisabled($table, $row)
1752
    {
1753
        $enableCols = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
1754
        return $enableCols['disabled'] && $row[$enableCols['disabled']]
1755
            || $enableCols['starttime'] && $row[$enableCols['starttime']] > $GLOBALS['EXEC_TIME']
1756
            || $enableCols['endtime'] && $row[$enableCols['endtime']] && $row[$enableCols['endtime']] < $GLOBALS['EXEC_TIME'];
1757
    }
1758
1759
    /*****************************************
1760
     *
1761
     * External renderings
1762
     *
1763
     *****************************************/
1764
1765
    /**
1766
     * Create thumbnail code for record/field but not linked
1767
     *
1768
     * @param mixed[] $row Record array
1769
     * @param string $table Table (record is from)
1770
     * @param string $field Field name for which thumbnail are to be rendered.
1771
     * @return string HTML for thumbnails, if any.
1772
     */
1773
    public function getThumbCodeUnlinked($row, $table, $field)
1774
    {
1775
        return BackendUtility::thumbCode($row, $table, $field, '', '', null, 0, '', '', false);
1776
    }
1777
1778
    /**
1779
     * Checks whether translated Content Elements exist in the desired language
1780
     * If so, deny creating new ones via the UI
1781
     *
1782
     * @param array $contentElements
1783
     * @param int $language
1784
     * @return bool
1785
     */
1786
    protected function checkIfTranslationsExistInLanguage(array $contentElements, int $language)
1787
    {
1788
        // If in default language, you may always create new entries
1789
        // Also, you may override this strict behavior via user TS Config
1790
        // If you do so, you're on your own and cannot rely on any support by the TYPO3 core
1791
        // We jump out here since we don't need to do the expensive loop operations
1792
        $allowInconsistentLanguageHandling = (bool)(BackendUtility::getPagesTSconfig($this->id)['mod.']['web_layout.']['allowInconsistentLanguageHandling'] ?? false);
1793
        if ($language === 0 || $allowInconsistentLanguageHandling) {
1794
            return false;
1795
        }
1796
        /**
1797
         * Build up caches
1798
         */
1799
        if (!isset($this->languageHasTranslationsCache[$language])) {
1800
            foreach ($contentElements as $columns) {
1801
                foreach ($columns as $contentElement) {
1802
                    if ((int)$contentElement['l18n_parent'] === 0) {
1803
                        $this->languageHasTranslationsCache[$language]['hasStandAloneContent'] = true;
1804
                        $this->languageHasTranslationsCache[$language]['mode'] = 'free';
1805
                    }
1806
                    if ((int)$contentElement['l18n_parent'] > 0) {
1807
                        $this->languageHasTranslationsCache[$language]['hasTranslations'] = true;
1808
                        $this->languageHasTranslationsCache[$language]['mode'] = 'connected';
1809
                    }
1810
                }
1811
            }
1812
            if (!isset($this->languageHasTranslationsCache[$language])) {
1813
                $this->languageHasTranslationsCache[$language]['hasTranslations'] = false;
1814
            }
1815
            // Check whether we have a mix of both
1816
            if (isset($this->languageHasTranslationsCache[$language]['hasStandAloneContent'])
1817
                && $this->languageHasTranslationsCache[$language]['hasTranslations']
1818
            ) {
1819
                $this->languageHasTranslationsCache[$language]['mode'] = 'mixed';
1820
                $siteLanguage = $this->siteLanguages[$language];
1821
                $message = GeneralUtility::makeInstance(
1822
                    FlashMessage::class,
1823
                    sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $siteLanguage->getTitle()),
1824
                    sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $siteLanguage->getTitle()),
1825
                    FlashMessage::WARNING
1826
                );
1827
                $service = GeneralUtility::makeInstance(FlashMessageService::class);
1828
                $queue = $service->getMessageQueueByIdentifier();
1829
                $queue->addMessage($message);
1830
            }
1831
        }
1832
1833
        return $this->languageHasTranslationsCache[$language]['hasTranslations'];
1834
    }
1835
1836
    /**
1837
     * @return BackendLayoutView
1838
     */
1839
    protected function getBackendLayoutView()
1840
    {
1841
        return GeneralUtility::makeInstance(BackendLayoutView::class);
1842
    }
1843
1844
    /**
1845
     * @return BackendUserAuthentication
1846
     */
1847
    protected function getBackendUser()
1848
    {
1849
        return $GLOBALS['BE_USER'];
1850
    }
1851
1852
    /**
1853
     * Create thumbnail code for record/field
1854
     *
1855
     * @param mixed[] $row Record array
1856
     * @param string $table Table (record is from)
1857
     * @param string $field Field name for which thumbnail are to be rendered.
1858
     * @return string HTML for thumbnails, if any.
1859
     */
1860
    public function thumbCode($row, $table, $field)
1861
    {
1862
        return BackendUtility::thumbCode($row, $table, $field);
1863
    }
1864
1865
    /**
1866
     * Returns a QueryBuilder configured to select $fields from $table where the pid is restricted.
1867
     *
1868
     * @param string $table Table name
1869
     * @param int $pageId Page id Only used to build the search constraints, getPageIdConstraint() used for restrictions
1870
     * @param string[] $additionalConstraints Additional part for where clause
1871
     * @param string[] $fields Field list to select, * for all
1872
     * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
1873
     */
1874
    public function getQueryBuilder(
1875
        string $table,
1876
        int $pageId,
1877
        array $additionalConstraints = [],
1878
        array $fields = ['*']
1879
    ): QueryBuilder {
1880
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1881
            ->getQueryBuilderForTable($table);
1882
        $queryBuilder->getRestrictions()
1883
            ->removeAll()
1884
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1885
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1886
        $queryBuilder
1887
            ->select(...$fields)
1888
            ->from($table);
1889
1890
        if (!empty($additionalConstraints)) {
1891
            $queryBuilder->andWhere(...$additionalConstraints);
1892
        }
1893
1894
        $queryBuilder = $this->prepareQueryBuilder($table, $pageId, $fields, $additionalConstraints, $queryBuilder);
1895
1896
        return $queryBuilder;
1897
    }
1898
1899
    /**
1900
     * Return the modified QueryBuilder object ($queryBuilder) which will be
1901
     * used to select the records from a table $table with pid = $this->pidList
1902
     *
1903
     * @param string $table Table name
1904
     * @param int $pageId Page id Only used to build the search constraints, $this->pidList is used for restrictions
1905
     * @param string[] $fieldList List of fields to select from the table
1906
     * @param string[] $additionalConstraints Additional part for where clause
1907
     * @param QueryBuilder $queryBuilder
1908
     * @return QueryBuilder
1909
     */
1910
    protected function prepareQueryBuilder(
1911
        string $table,
1912
        int $pageId,
1913
        array $fieldList,
1914
        array $additionalConstraints,
1915
        QueryBuilder $queryBuilder
1916
    ): QueryBuilder {
1917
        $parameters = [
1918
            'table' => $table,
1919
            'fields' => $fieldList,
1920
            'groupBy' => null,
1921
            'orderBy' => null
1922
        ];
1923
1924
        // Build the query constraints
1925
        $queryBuilder->andWhere(
1926
            $queryBuilder->expr()->eq(
1927
                $table . '.pid',
1928
                $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
1929
            )
1930
        );
1931
1932
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][PageLayoutView::class]['modifyQuery'] ?? [] as $className) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1933
            $hookObject = GeneralUtility::makeInstance($className);
1934
            if (method_exists($hookObject, 'modifyQuery')) {
1935
                $hookObject->modifyQuery(
1936
                    $parameters,
1937
                    $table,
1938
                    $pageId,
1939
                    $additionalConstraints,
1940
                    $fieldList,
1941
                    $queryBuilder
1942
                );
1943
            }
1944
        }
1945
1946
        return $queryBuilder;
1947
    }
1948
1949
    /**
1950
     * Renders the language flag and language title, but only if an icon is given, otherwise just the language
1951
     *
1952
     * @param SiteLanguage $language
1953
     * @return string
1954
     */
1955
    protected function renderLanguageFlag(SiteLanguage $language)
1956
    {
1957
        $title = htmlspecialchars($language->getTitle());
1958
        if ($language->getFlagIdentifier()) {
1959
            $icon = $this->iconFactory->getIcon(
1960
                $language->getFlagIdentifier(),
1961
                Icon::SIZE_SMALL
1962
            )->render();
1963
            return '<span title="' . $title . '" class="t3js-flag">' . $icon . '</span>&nbsp;<span class="t3js-language-title">' . $title . '</span>';
1964
        }
1965
        return $title;
1966
    }
1967
1968
    /**
1969
     * Fetch the site language objects for the given $pageId and store it in $this->siteLanguages
1970
     *
1971
     * @param int $pageId
1972
     * @throws SiteNotFoundException
1973
     */
1974
    protected function resolveSiteLanguages(int $pageId)
1975
    {
1976
        try {
1977
            $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
1978
        } catch (SiteNotFoundException $e) {
1979
            $site = new NullSite();
1980
        }
1981
        $this->siteLanguages = $site->getAvailableLanguages($this->getBackendUser(), false, $pageId);
1982
    }
1983
1984
    /**
1985
     * @return string $title
1986
     */
1987
    protected function getLocalizedPageTitle(): string
1988
    {
1989
        if (($this->tt_contentConfig['sys_language_uid'] ?? 0) > 0) {
1990
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1991
                ->getQueryBuilderForTable('pages');
1992
            $queryBuilder->getRestrictions()
1993
                ->removeAll()
1994
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1995
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1996
            $localizedPage = $queryBuilder
1997
                ->select('*')
1998
                ->from('pages')
1999
                ->where(
2000
                    $queryBuilder->expr()->eq(
2001
                        $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
2002
                        $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
2003
                    ),
2004
                    $queryBuilder->expr()->eq(
2005
                        $GLOBALS['TCA']['pages']['ctrl']['languageField'],
2006
                        $queryBuilder->createNamedParameter($this->tt_contentConfig['sys_language_uid'], \PDO::PARAM_INT)
2007
                    )
2008
                )
2009
                ->setMaxResults(1)
2010
                ->execute()
2011
                ->fetch();
2012
            BackendUtility::workspaceOL('pages', $localizedPage);
2013
            return $localizedPage['title'];
2014
        }
2015
        return $this->pageinfo['title'];
2016
    }
2017
2018
    /**
2019
     * Check if page can be edited by current user
2020
     *
2021
     * @return bool
2022
     */
2023
    protected function isPageEditable()
2024
    {
2025
        if ($this->getBackendUser()->isAdmin()) {
2026
            return true;
2027
        }
2028
        return !$this->pageinfo['editlock'] && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::PAGE_EDIT);
2029
    }
2030
2031
    /**
2032
     * Check if content can be edited by current user
2033
     *
2034
     * @param int|null $languageId
2035
     * @return bool
2036
     */
2037
    protected function isContentEditable(?int $languageId = null)
2038
    {
2039
        if ($this->getBackendUser()->isAdmin()) {
2040
            return true;
2041
        }
2042
        return !$this->pageinfo['editlock']
2043
            && $this->hasContentModificationAndAccessPermissions()
2044
            && ($languageId === null || $this->getBackendUser()->checkLanguageAccess($languageId));
2045
    }
2046
2047
    /**
2048
     * Check if current user has modification and access permissons for content set
2049
     *
2050
     * @return bool
2051
     */
2052
    protected function hasContentModificationAndAccessPermissions(): bool
2053
    {
2054
        return $this->getBackendUser()->check('tables_modify', 'tt_content')
2055
            && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT);
2056
    }
2057
2058
    /**
2059
     * Returns the language service
2060
     * @return LanguageService
2061
     */
2062
    protected function getLanguageService()
2063
    {
2064
        return $GLOBALS['LANG'];
2065
    }
2066
2067
    /**
2068
     * @param string $position Which event should be triggered? Possible options: before or after
2069
     * @param int $lP The language id you want to show data for
2070
     * @param array $columnConfig Array with the configuration of the current column
2071
     * @return string
2072
     */
2073
    protected function dispatchSectionMarkupGeneratedEvent(string $position, int $lP, array $columnConfig): string
2074
    {
2075
        if ($position === 'before') {
2076
            $event = new BeforeSectionMarkupGeneratedEvent($this, $lP, $columnConfig);
2077
        } else {
2078
            $event = new AfterSectionMarkupGeneratedEvent($this, $lP, $columnConfig);
2079
        }
2080
2081
        $this->eventDispatcher->dispatch($event);
2082
        return $event->getContent();
2083
    }
2084
}
2085