Completed
Push — master ( 0573cc...b86ccc )
by
unknown
17:00
created

PageLayoutView::getNonTranslatedTTcontentUids()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 37
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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

319
                    && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, /** @scrutinizer ignore-type */ $lP))
Loading history...
320
                ) {
321
                    if ($this->option_newWizard) {
322
                        $urlParameters = [
323
                            'id' => $id,
324
                            'sys_language_uid' => $lP,
325
                            'colPos' => $columnId,
326
                            'uid_pid' => $id,
327
                            'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
328
                        ];
329
                        $routeName = BackendUtility::getPagesTSconfig($id)['mod.']['newContentElementWizard.']['override']
330
                            ?? 'new_content_element_wizard';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "??"; newline found
Loading history...
331
                        $url = (string)$this->uriBuilder->buildUriFromRoute($routeName, $urlParameters);
332
                    } else {
333
                        $urlParameters = [
334
                            'edit' => [
335
                                'tt_content' => [
336
                                    $id => 'new'
337
                                ]
338
                            ],
339
                            'defVals' => [
340
                                'tt_content' => [
341
                                    'colPos' => $columnId,
342
                                    'sys_language_uid' => $lP
343
                                ]
344
                            ],
345
                            'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
346
                        ];
347
                        $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
348
                    }
349
                    $title = htmlspecialchars($this->getLanguageService()->getLL('newContentElement'));
350
                    $link = '<a href="' . htmlspecialchars($url) . '" '
351
                        . 'title="' . $title . '"'
352
                        . 'data-title="' . $title . '"'
353
                        . 'class="btn btn-default btn-sm ' . ($this->option_newWizard ? 't3js-toggle-new-content-element-wizard' : '') . '">'
354
                        . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render()
355
                        . ' '
356
                        . htmlspecialchars($this->getLanguageService()->getLL('content')) . '</a>';
357
                }
358
                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

358
                if ($this->getBackendUser()->checkLanguageAccess(/** @scrutinizer ignore-type */ $lP) && $columnId !== 'unused') {
Loading history...
359
                    $content[$columnId] .= '
360
                    <div class="t3-page-ce t3js-page-ce" data-page="' . (int)$id . '" id="' . StringUtility::getUniqueId() . '">
361
                        <div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-page-' . $id . '-' . StringUtility::getUniqueId() . '">'
362
                            . $link
363
                            . '</div>
364
                        <div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div>
365
                    </div>
366
                    ';
367
                }
368
                $editUidList = '';
369
                if (!isset($contentRecordsPerColumn[$columnId]) || !is_array($contentRecordsPerColumn[$columnId])) {
370
                    $message = GeneralUtility::makeInstance(
371
                        FlashMessage::class,
372
                        $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:error.invalidBackendLayout'),
0 ignored issues
show
Bug introduced by
$this->getLanguageServic....invalidBackendLayout') of type string is incompatible with the type array|array<mixed,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

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

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

374
                        /** @scrutinizer ignore-type */ FlashMessage::WARNING
Loading history...
375
                    );
376
                    $service = GeneralUtility::makeInstance(FlashMessageService::class);
377
                    $queue = $service->getMessageQueueByIdentifier();
378
                    $queue->addMessage($message);
379
                } else {
380
                    $rowArr = $contentRecordsPerColumn[$columnId];
381
                    $this->generateTtContentDataArray($rowArr);
382
383
                    foreach ((array)$rowArr as $rKey => $row) {
384
                        $this->contentElementCache[$lP][$columnId][$row['uid']] = $row;
385
                        if ($this->tt_contentConfig['languageMode']) {
386
                            $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
387
                        }
388
                        if (is_array($row) && !VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
389
                            $singleElementHTML = '<div class="t3-page-ce-dragitem" id="' . StringUtility::getUniqueId() . '">';
390
                            if (!$lP && ($this->defLangBinding || $row['sys_language_uid'] != -1)) {
391
                                $defaultLanguageElementsByColumn[$columnId][] = ($row['_ORIG_uid'] ?? $row['uid']);
392
                            }
393
                            $editUidList .= $row['uid'] . ',';
394
                            $disableMoveAndNewButtons = $this->defLangBinding && $lP > 0 && $this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP);
395
                            $singleElementHTML .= $this->tt_content_drawHeader(
396
                                $row,
397
                                0,
398
                                $disableMoveAndNewButtons,
399
                                true,
400
                                $this->hasContentModificationAndAccessPermissions()
401
                            );
402
                            $innerContent = '<div ' . ($row['_ORIG_uid'] ? ' class="ver-element"' : '') . '>'
403
                                . $this->tt_content_drawItem($row) . '</div>';
404
                            $singleElementHTML .= '<div class="t3-page-ce-body-inner">' . $innerContent . '</div></div>'
405
                                . $this->tt_content_drawFooter($row);
406
                            $isDisabled = $this->isDisabled('tt_content', $row);
407
                            $statusHidden = $isDisabled ? ' t3-page-ce-hidden t3js-hidden-record' : '';
408
                            $displayNone = !$this->tt_contentConfig['showHidden'] && $isDisabled ? ' style="display: none;"' : '';
409
                            $highlightHeader = '';
410
                            if ($this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid']) && (int)$row['l18n_parent'] === 0) {
411
                                $highlightHeader = ' t3-page-ce-danger';
412
                            } elseif ($columnId === 'unused') {
413
                                $highlightHeader = ' t3-page-ce-warning';
414
                            }
415
                            $singleElementHTML = '<div class="t3-page-ce' . $highlightHeader . ' t3js-page-ce t3js-page-ce-sortable ' . $statusHidden . '" id="element-tt_content-'
416
                                . $row['uid'] . '" data-table="tt_content" data-uid="' . $row['uid'] . '"' . $displayNone . '>' . $singleElementHTML . '</div>';
417
418
                            $singleElementHTML .= '<div class="t3-page-ce" data-colpos="' . $columnId . '">';
419
                            $singleElementHTML .= '<div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-page-' . $id .
420
                                '-' . StringUtility::getUniqueId() . '">';
421
                            // Add icon "new content element below"
422
                            if (!$disableMoveAndNewButtons
423
                                && $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

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

565
                            $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', /** @scrutinizer ignore-type */ $lP, $columnConfig);
Loading history...
566
                            $grid .= $content[$columnKey];
567
                        } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
568
                            && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
569
                        ) {
570
                            if (!$hideRestrictedCols) {
571
                                $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('noAccess'));
572
                                $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
573
                            }
574
                        } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
575
                            && !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
576
                        ) {
577
                            if (!$hideRestrictedCols) {
578
                                $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name']) .
579
                                  ' (' . $this->getLanguageService()->getLL('noAccess') . ')');
580
                                $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
581
                            }
582
                        } elseif (isset($columnConfig['name']) && $columnConfig['name'] !== '') {
583
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name'])
584
                                . ' (' . $this->getLanguageService()->getLL('notAssigned') . ')');
585
                            $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
586
                        } else {
587
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('notAssigned'));
588
                            $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
589
                        }
590
591
                        $grid .= $this->dispatchSectionMarkupGeneratedEvent('after', $lP, $columnConfig);
592
593
                        $grid .= '</td>';
594
                    }
595
                    $grid .= '</tr>';
596
                }
597
                if (!empty($content['unused'])) {
598
                    $grid .= '<tr>';
599
                    // Which tt_content colPos should be displayed inside this cell
600
                    $columnKey = 'unused';
601
                    // Render the grid cell
602
                    $colSpan = (int)$backendLayout['__config']['backend_layout.']['colCount'];
603
                    $grid .= '<td valign="top"' .
604
                        ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
605
                        ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $rowSpan does not seem to be defined for all execution paths leading up to this point.
Loading history...
606
                        ' 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 .
607
                        ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') . '">';
608
609
                    // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
610
                    // If not, a new header without any buttons will be generated.
611
                    $grid .= $head[$columnKey] . $content[$columnKey];
612
                    $grid .= '</td></tr>';
613
                }
614
                $out .= $grid . '</table></div>';
615
            }
616
        }
617
        $elFromTable = $this->clipboard->elFromTable('tt_content');
618
        if (!empty($elFromTable) && $this->isPageEditable()) {
619
            $pasteItem = substr(key($elFromTable), 11);
620
            $pasteRecord = BackendUtility::getRecord('tt_content', (int)$pasteItem);
621
            $pasteTitle = $pasteRecord['header'] ?: $pasteItem;
622
            $copyMode = $this->clipboard->clipData['normal']['mode'] ? '-' . $this->clipboard->clipData['normal']['mode'] : '';
623
            $inlineJavaScript = '
624
                     top.pasteIntoLinkTemplate = '
625
                . $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

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

860
        $results = $this->getResult(/** @scrutinizer ignore-type */ $queryBuilder->execute());
Loading history...
861
        $unused = [];
862
        $hookArray = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['record_is_used'] ?? [];
863
        foreach ($results as $record) {
864
            $used = isset($columns[$record['colPos']]);
865
            foreach ($hookArray as $_funcRef) {
866
                $_params = ['columns' => $columns, 'record' => $record, 'used' => $used];
867
                $used = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
868
            }
869
            if ($used) {
870
                $columnValue = (string)$record['colPos'];
871
                $contentRecordsPerColumn[$columnValue][] = $record;
872
            } else {
873
                $unused[] = $record;
874
            }
875
        }
876
        if (!empty($unused)) {
877
            $contentRecordsPerColumn['unused'] = $unused;
878
        }
879
        return $contentRecordsPerColumn;
880
    }
881
882
    /**
883
     * Draw header for a content element column:
884
     *
885
     * @param string $colName Column name
886
     * @param string $editParams Edit params (Syntax: &edit[...] for FormEngine)
887
     * @return string HTML table
888
     */
889
    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...
890
    {
891
        $icons = '';
892
        // Edit whole of column:
893
        if ($editParams && $this->hasContentModificationAndAccessPermissions() && $this->getBackendUser()->checkLanguageAccess(0)) {
894
            $link = $this->uriBuilder->buildUriFromRoute('record_edit') . $editParams . '&returnUrl=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'));
895
            $icons = '<a href="' . htmlspecialchars($link) . '"  title="'
896
                . htmlspecialchars($this->getLanguageService()->getLL('editColumn')) . '">'
897
                . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
898
            $icons = '<div class="t3-page-column-header-icons">' . $icons . '</div>';
899
        }
900
        return '<div class="t3-page-column-header">
901
					' . $icons . '
902
					<div class="t3-page-column-header-label">' . htmlspecialchars($colName) . '</div>
903
				</div>';
904
    }
905
906
    /**
907
     * Draw a paste icon either for pasting into a column or for pasting after a record
908
     *
909
     * @param int $pasteItem ID of the item in the clipboard
910
     * @param string $pasteTitle Title for the JS modal
911
     * @param string $copyMode copy or cut
912
     * @param string $cssClass CSS class to determine if pasting is done into column or after record
913
     * @param string $title title attribute of the generated link
914
     *
915
     * @return string Generated HTML code with link and icon
916
     */
917
    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...
918
    {
919
        $pasteIcon = json_encode(
920
            ' <a data-content="' . htmlspecialchars($pasteItem) . '"'
921
            . ' data-title="' . htmlspecialchars($pasteTitle) . '"'
922
            . ' data-severity="warning"'
923
            . ' class="t3js-paste t3js-paste' . htmlspecialchars($copyMode) . ' ' . htmlspecialchars($cssClass) . ' btn btn-default btn-sm"'
924
            . ' title="' . htmlspecialchars($this->getLanguageService()->getLL($title)) . '">'
925
            . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
926
            . '</a>'
927
        );
928
        return $pasteIcon;
929
    }
930
931
    /**
932
     * Draw the footer for a single tt_content element
933
     *
934
     * @param array $row Record array
935
     * @return string HTML of the footer
936
     * @throws \UnexpectedValueException
937
     */
938
    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...
939
    {
940
        $content = '';
941
        // Get processed values:
942
        $info = [];
943
        $this->getProcessedValue('tt_content', 'starttime,endtime,fe_group,space_before_class,space_after_class', $row, $info);
944
945
        // Content element annotation
946
        if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']) && !empty($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']])) {
947
            $info[] = htmlspecialchars($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']]);
948
        }
949
950
        // Call drawFooter hooks
951
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawFooter'] ?? [] as $className) {
952
            $hookObject = GeneralUtility::makeInstance($className);
953
            if (!$hookObject instanceof PageLayoutViewDrawFooterHookInterface) {
954
                throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawFooterHookInterface::class, 1404378171);
955
            }
956
            $hookObject->preProcess($this, $info, $row);
957
        }
958
959
        // Display info from records fields:
960
        if (!empty($info)) {
961
            $content = '<div class="t3-page-ce-info">
962
				' . implode('<br>', $info) . '
963
				</div>';
964
        }
965
        if (!empty($content)) {
966
            $content = '<div class="t3-page-ce-footer">' . $content . '</div>';
967
        }
968
        return $content;
969
    }
970
971
    /**
972
     * Draw the header for a single tt_content element
973
     *
974
     * @param array $row Record array
975
     * @param int $space Amount of pixel space above the header. UNUSED
976
     * @param bool $disableMoveAndNewButtons If set the buttons for creating new elements and moving up and down are not shown.
977
     * @param bool $langMode If set, we are in language mode and flags will be shown for languages
978
     * @param bool $dragDropEnabled If set the move button must be hidden
979
     * @return string HTML table with the record header.
980
     */
981
    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

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

1295
                        $shortcutRecord = BackendUtility::getRecord($tableName, /** @scrutinizer ignore-type */ $split[1]);
Loading history...
1296
                        if (is_array($shortcutRecord)) {
1297
                            $icon = $this->iconFactory->getIconForRecord($tableName, $shortcutRecord, Icon::SIZE_SMALL)->render();
1298
                            $icon = BackendUtility::wrapClickMenuOnIcon(
1299
                                $icon,
1300
                                $tableName,
1301
                                $shortcutRecord['uid']
1302
                            );
1303
                            $shortcutContent[] = $icon
1304
                                . htmlspecialchars(BackendUtility::getRecordTitle($tableName, $shortcutRecord));
1305
                        }
1306
                    }
1307
                    $previewHtml = implode('<br />', $shortcutContent) . '<br />';
1308
                }
1309
                break;
1310
            case 'list':
1311
                $hookOut = '';
1312
                $_params = ['pObj' => &$this, 'row' => $row];
1313
                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...
1314
                    $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'][$row['list_type']] ??
1315
                    $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['_DEFAULT'] ??
1316
                    [] as $_funcRef
1317
                ) {
1318
                    $hookOut .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1319
                }
1320
                if ((string)$hookOut !== '') {
1321
                    $previewHtml = $hookOut;
1322
                } elseif (!empty($row['list_type'])) {
1323
                    $label = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'list_type', $row['list_type']);
1324
                    if (!empty($label)) {
1325
                        $previewHtml = $this->linkEditContent('<strong>' . htmlspecialchars($this->getLanguageService()->sL($label)) . '</strong>', $row) . '<br />';
1326
                    } else {
1327
                        $message = sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'), $row['list_type']);
1328
                        $previewHtml = '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
1329
                    }
1330
                } else {
1331
                    $previewHtml = '<strong>' . $this->getLanguageService()->getLL('noPluginSelected') . '</strong>';
1332
                }
1333
                $previewHtml .= htmlspecialchars($this->getLanguageService()->sL(
1334
                    BackendUtility::getLabelFromItemlist('tt_content', 'pages', $row['pages'])
1335
                )) . '<br />';
1336
                break;
1337
            default:
1338
                $contentType = $this->CType_labels[$row['CType']];
1339
                if (!isset($contentType)) {
1340
                    $contentType = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'CType', $row['CType']);
1341
                }
1342
1343
                if ($contentType) {
1344
                    $previewHtml = $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $row) . '<br />';
1345
                    if ($row['bodytext']) {
1346
                        $previewHtml .= $this->linkEditContent($this->renderText($row['bodytext']), $row) . '<br />';
1347
                    }
1348
                    if ($row['image']) {
1349
                        $previewHtml .= $this->linkEditContent($this->getThumbCodeUnlinked($row, 'tt_content', 'image'), $row) . '<br />';
1350
                    }
1351
                } else {
1352
                    $message = sprintf(
1353
                        $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'),
1354
                        $row['CType']
1355
                    );
1356
                    $previewHtml = '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
1357
                }
1358
        }
1359
        return $previewHtml;
1360
    }
1361
1362
    /**
1363
     * Filters out all tt_content uids which are already translated so only non-translated uids is left.
1364
     * Selects across columns, but within in the same PID. Columns are expect to be the same
1365
     * for translations and original but this may be a conceptual error (?)
1366
     *
1367
     * @param array $defaultLanguageUids Numeric array with uids of tt_content elements in the default language
1368
     * @param int $id The page UID from which to fetch untranslated records (unused, remains in place for compatibility)
1369
     * @param int $lP Sys language UID
1370
     * @return array Modified $defLanguageCount
1371
     */
1372
    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

1372
    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...
1373
    {
1374
        if ($lP && !empty($defaultLanguageUids)) {
1375
            // Select all translations here:
1376
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1377
                ->getQueryBuilderForTable('tt_content');
1378
            $queryBuilder->getRestrictions()
1379
                ->removeAll()
1380
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1381
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class, null, false));
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type array|array<mixed,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

1381
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class, null, /** @scrutinizer ignore-type */ false));
Loading history...
1382
            $queryBuilder
1383
                ->select('*')
1384
                ->from('tt_content')
1385
                ->where(
1386
                    $queryBuilder->expr()->eq(
1387
                        'sys_language_uid',
1388
                        $queryBuilder->createNamedParameter($lP, \PDO::PARAM_INT)
1389
                    ),
1390
                    $queryBuilder->expr()->in(
1391
                        'l18n_parent',
1392
                        $queryBuilder->createNamedParameter($defaultLanguageUids, Connection::PARAM_INT_ARRAY)
1393
                    )
1394
                );
1395
1396
            $result = $queryBuilder->execute();
1397
1398
            // Flip uids:
1399
            $defaultLanguageUids = array_flip($defaultLanguageUids);
1400
            // Traverse any selected elements and unset original UID if any:
1401
            while ($row = $result->fetch()) {
1402
                BackendUtility::workspaceOL('tt_content', $row);
1403
                unset($defaultLanguageUids[$row['l18n_parent']]);
1404
            }
1405
            // Flip again:
1406
            $defaultLanguageUids = array_keys($defaultLanguageUids);
1407
        }
1408
        return $defaultLanguageUids;
1409
    }
1410
1411
    /**
1412
     * Creates button which is used to create copies of records..
1413
     *
1414
     * @param array $defaultLanguageUids Numeric array with uids of tt_content elements in the default language
1415
     * @param int $lP Sys language UID
1416
     * @return string "Copy languages" button, if available.
1417
     */
1418
    public function newLanguageButton($defaultLanguageUids, $lP)
1419
    {
1420
        $lP = (int)$lP;
1421
        if (!$this->doEdit || !$lP) {
1422
            return '';
1423
        }
1424
        $theNewButton = '';
1425
1426
        $localizationTsConfig = BackendUtility::getPagesTSconfig($this->id)['mod.']['web_layout.']['localization.'] ?? [];
1427
        $allowCopy = (bool)($localizationTsConfig['enableCopy'] ?? true);
1428
        $allowTranslate = (bool)($localizationTsConfig['enableTranslate'] ?? true);
1429
        if (!empty($this->languageHasTranslationsCache[$lP])) {
1430
            if (isset($this->languageHasTranslationsCache[$lP]['hasStandAloneContent'])) {
1431
                $allowTranslate = false;
1432
            }
1433
            if (isset($this->languageHasTranslationsCache[$lP]['hasTranslations'])) {
1434
                $allowCopy = $allowCopy && !$this->languageHasTranslationsCache[$lP]['hasTranslations'];
1435
            }
1436
        }
1437
1438
        if (isset($this->contentElementCache[$lP]) && is_array($this->contentElementCache[$lP])) {
1439
            foreach ($this->contentElementCache[$lP] as $column => $records) {
1440
                foreach ($records as $record) {
1441
                    $key = array_search($record['l10n_source'], $defaultLanguageUids);
1442
                    if ($key !== false) {
1443
                        unset($defaultLanguageUids[$key]);
1444
                    }
1445
                }
1446
            }
1447
        }
1448
1449
        if (!empty($defaultLanguageUids)) {
1450
            $theNewButton =
1451
                '<a'
1452
                    . ' href="#"'
1453
                    . ' class="btn btn-default btn-sm t3js-localize disabled"'
1454
                    . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('newPageContent_translate')) . '"'
1455
                    . ' data-page="' . htmlspecialchars($this->getLocalizedPageTitle()) . '"'
1456
                    . ' data-has-elements="' . (int)!empty($this->contentElementCache[$lP]) . '"'
1457
                    . ' data-allow-copy="' . (int)$allowCopy . '"'
1458
                    . ' data-allow-translate="' . (int)$allowTranslate . '"'
1459
                    . ' data-table="tt_content"'
1460
                    . ' data-page-id="' . (int)GeneralUtility::_GP('id') . '"'
1461
                    . ' data-language-id="' . $lP . '"'
1462
                    . ' data-language-name="' . htmlspecialchars($this->tt_contentConfig['languageCols'][$lP]) . '"'
1463
                . '>'
1464
                . $this->iconFactory->getIcon('actions-localize', Icon::SIZE_SMALL)->render()
1465
                . ' ' . htmlspecialchars($this->getLanguageService()->getLL('newPageContent_translate'))
1466
                . '</a>';
1467
        }
1468
1469
        return $theNewButton;
1470
    }
1471
1472
    /**
1473
     * Will create a link on the input string and possibly a big button after the string which links to editing in the RTE.
1474
     * Used for content element content displayed so the user can click the content / "Edit in Rich Text Editor" button
1475
     *
1476
     * @param string $str String to link. Must be prepared for HTML output.
1477
     * @param array $row The row.
1478
     * @return string If the whole thing was editable ($this->doEdit) $str is return with link around. Otherwise just $str.
1479
     * @see getTable_tt_content()
1480
     */
1481
    public function linkEditContent($str, $row)
1482
    {
1483
        if ($this->doEdit
1484
            && $this->hasContentModificationAndAccessPermissions()
1485
            && $this->getBackendUser()->recordEditAccessInternals('tt_content', $row)
1486
        ) {
1487
            $urlParameters = [
1488
                'edit' => [
1489
                    'tt_content' => [
1490
                        $row['uid'] => 'edit'
1491
                    ]
1492
                ],
1493
                'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $row['uid']
1494
            ];
1495
            $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1496
            return '<a href="' . htmlspecialchars($url) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">' . $str . '</a>';
1497
        }
1498
        return $str;
1499
    }
1500
1501
    /**
1502
     * Make selector box for creating new translation in a language
1503
     * Displays only languages which are not yet present for the current page and
1504
     * that are not disabled with page TS.
1505
     *
1506
     * @param int $id Page id for which to create a new translation record of pages
1507
     * @return string HTML <select> element (if there were items for the box anyways...)
1508
     * @see getTable_tt_content()
1509
     */
1510
    public function languageSelector($id)
1511
    {
1512
        if (!$this->getBackendUser()->check('tables_modify', 'pages')) {
1513
            return '';
1514
        }
1515
        $id = (int)$id;
1516
1517
        // First, select all languages that are available for the current user
1518
        $availableTranslations = [];
1519
        foreach ($this->siteLanguages as $language) {
1520
            if ($language->getLanguageId() === 0) {
1521
                continue;
1522
            }
1523
            $availableTranslations[$language->getLanguageId()] = $language->getTitle();
1524
        }
1525
1526
        // Then, subtract the languages which are already on the page:
1527
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
1528
        $queryBuilder->getRestrictions()->removeAll()
1529
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1530
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1531
        $queryBuilder->select('uid', $GLOBALS['TCA']['pages']['ctrl']['languageField'])
1532
            ->from('pages')
1533
            ->where(
1534
                $queryBuilder->expr()->eq(
1535
                    $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
1536
                    $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
1537
                )
1538
            );
1539
        $statement = $queryBuilder->execute();
1540
        while ($row = $statement->fetch()) {
1541
            unset($availableTranslations[(int)$row[$GLOBALS['TCA']['pages']['ctrl']['languageField']]]);
1542
        }
1543
        // If any languages are left, make selector:
1544
        if (!empty($availableTranslations)) {
1545
            $output = '<option value="">' . htmlspecialchars($this->getLanguageService()->getLL('new_language')) . '</option>';
1546
            foreach ($availableTranslations as $languageUid => $languageTitle) {
1547
                // Build localize command URL to DataHandler (tce_db)
1548
                // which redirects to FormEngine (record_edit)
1549
                // which, when finished editing should return back to the current page (returnUrl)
1550
                $parameters = [
1551
                    'justLocalized' => 'pages:' . $id . ':' . $languageUid,
1552
                    'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1553
                ];
1554
                $redirectUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $parameters);
1555
                $targetUrl = BackendUtility::getLinkToDataHandlerAction(
1556
                    '&cmd[pages][' . $id . '][localize]=' . $languageUid,
1557
                    $redirectUrl
1558
                );
1559
1560
                $output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
1561
            }
1562
1563
            return '<div class="form-inline form-inline-spaced">'
1564
                . '<div class="form-group">'
1565
                . '<select class="form-control input-sm" name="createNewLanguage" onchange="window.location.href=this.options[this.selectedIndex].value">'
1566
                . $output
1567
                . '</select></div></div>';
1568
        }
1569
        return '';
1570
    }
1571
1572
    /**
1573
     * Traverse the result pointer given, adding each record to array and setting some internal values at the same time.
1574
     *
1575
     * @param Statement $result DBAL Statement
1576
     * @return array The selected rows returned in this array.
1577
     */
1578
    public function getResult(Statement $result): array
1579
    {
1580
        $output = [];
1581
        // Traverse the result:
1582
        while ($row = $result->fetch()) {
1583
            BackendUtility::workspaceOL('tt_content', $row, -99, true);
1584
            if ($row) {
1585
                // Add the row to the array:
1586
                $output[] = $row;
1587
            }
1588
        }
1589
        $this->generateTtContentDataArray($output);
1590
        // Return selected records
1591
        return $output;
1592
    }
1593
1594
    /********************************
1595
     *
1596
     * Various helper functions
1597
     *
1598
     ********************************/
1599
1600
    /**
1601
     * Initializes the clipboard for generating paste links
1602
     *
1603
     *
1604
     * @see \TYPO3\CMS\Backend\Controller\ContextMenuController::clipboardAction()
1605
     * @see \TYPO3\CMS\Filelist\Controller\FileListController::indexAction()
1606
     */
1607
    protected function initializeClipboard()
1608
    {
1609
        // Start clipboard
1610
        $this->clipboard = GeneralUtility::makeInstance(Clipboard::class);
1611
1612
        // Initialize - reads the clipboard content from the user session
1613
        $this->clipboard->initializeClipboard();
1614
1615
        // This locks the clipboard to the Normal for this request.
1616
        $this->clipboard->lockToNormal();
1617
1618
        // Clean up pad
1619
        $this->clipboard->cleanCurrent();
1620
1621
        // Save the clipboard content
1622
        $this->clipboard->endClipboard();
1623
    }
1624
1625
    /**
1626
     * Generates the data for previous and next elements which is needed for movements.
1627
     *
1628
     * @param array $rowArray
1629
     */
1630
    protected function generateTtContentDataArray(array $rowArray)
1631
    {
1632
        if (empty($this->tt_contentData)) {
1633
            $this->tt_contentData = [
1634
                'next' => [],
1635
                'prev' => [],
1636
            ];
1637
        }
1638
        foreach ($rowArray as $key => $value) {
1639
            // Create information for next and previous content elements
1640
            if (isset($rowArray[$key - 1])) {
1641
                if (isset($rowArray[$key - 2])) {
1642
                    $this->tt_contentData['prev'][$value['uid']] = -$rowArray[$key - 2]['uid'];
1643
                } else {
1644
                    $this->tt_contentData['prev'][$value['uid']] = $value['pid'];
1645
                }
1646
                $this->tt_contentData['next'][$rowArray[$key - 1]['uid']] = -$value['uid'];
1647
            }
1648
        }
1649
    }
1650
1651
    /**
1652
     * Processing of larger amounts of text (usually from RTE/bodytext fields) with word wrapping etc.
1653
     *
1654
     * @param string $input Input string
1655
     * @return string Output string
1656
     */
1657
    public function renderText($input)
1658
    {
1659
        $input = strip_tags($input);
1660
        $input = GeneralUtility::fixed_lgd_cs($input, 1500);
1661
        return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
1662
    }
1663
1664
    /**
1665
     * Creates the icon image tag for record from table and wraps it in a link which will trigger the click menu.
1666
     *
1667
     * @param string $table Table name
1668
     * @param array $row Record array
1669
     * @return string HTML for the icon
1670
     */
1671
    public function getIcon($table, $row)
1672
    {
1673
        // Initialization
1674
        $toolTip = BackendUtility::getRecordToolTip($row, $table);
1675
        $icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
1676
        // The icon with link
1677
        if ($this->getBackendUser()->recordEditAccessInternals($table, $row)) {
1678
            $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
1679
        }
1680
        return $icon;
1681
    }
1682
1683
    /**
1684
     * Creates processed values for all field names in $fieldList based on values from $row array.
1685
     * The result is 'returned' through $info which is passed as a reference
1686
     *
1687
     * @param string $table Table name
1688
     * @param string $fieldList Comma separated list of fields.
1689
     * @param array $row Record from which to take values for processing.
1690
     * @param array $info Array to which the processed values are added.
1691
     */
1692
    public function getProcessedValue($table, $fieldList, array $row, array &$info)
1693
    {
1694
        // Splitting values from $fieldList
1695
        $fieldArr = explode(',', $fieldList);
1696
        // Traverse fields from $fieldList
1697
        foreach ($fieldArr as $field) {
1698
            if ($row[$field]) {
1699
                $info[] = '<strong>' . htmlspecialchars($this->itemLabels[$field]) . '</strong> '
1700
                    . htmlspecialchars(BackendUtility::getProcessedValue($table, $field, $row[$field]));
1701
            }
1702
        }
1703
    }
1704
1705
    /**
1706
     * Returns TRUE, if the record given as parameters is NOT visible based on hidden/starttime/endtime (if available)
1707
     *
1708
     * @param string $table Tablename of table to test
1709
     * @param array $row Record row.
1710
     * @return bool Returns TRUE, if disabled.
1711
     */
1712
    public function isDisabled($table, $row)
1713
    {
1714
        $enableCols = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
1715
        return $enableCols['disabled'] && $row[$enableCols['disabled']]
1716
            || $enableCols['starttime'] && $row[$enableCols['starttime']] > $GLOBALS['EXEC_TIME']
1717
            || $enableCols['endtime'] && $row[$enableCols['endtime']] && $row[$enableCols['endtime']] < $GLOBALS['EXEC_TIME'];
1718
    }
1719
1720
    /*****************************************
1721
     *
1722
     * External renderings
1723
     *
1724
     *****************************************/
1725
1726
    /**
1727
     * Create thumbnail code for record/field but not linked
1728
     *
1729
     * @param mixed[] $row Record array
1730
     * @param string $table Table (record is from)
1731
     * @param string $field Field name for which thumbnail are to be rendered.
1732
     * @return string HTML for thumbnails, if any.
1733
     */
1734
    public function getThumbCodeUnlinked($row, $table, $field)
1735
    {
1736
        return BackendUtility::thumbCode($row, $table, $field, '', '', null, 0, '', '', false);
1737
    }
1738
1739
    /**
1740
     * Checks whether translated Content Elements exist in the desired language
1741
     * If so, deny creating new ones via the UI
1742
     *
1743
     * @param array $contentElements
1744
     * @param int $language
1745
     * @return bool
1746
     */
1747
    protected function checkIfTranslationsExistInLanguage(array $contentElements, int $language)
1748
    {
1749
        // If in default language, you may always create new entries
1750
        // Also, you may override this strict behavior via user TS Config
1751
        // If you do so, you're on your own and cannot rely on any support by the TYPO3 core
1752
        // We jump out here since we don't need to do the expensive loop operations
1753
        $allowInconsistentLanguageHandling = (bool)(BackendUtility::getPagesTSconfig($this->id)['mod.']['web_layout.']['allowInconsistentLanguageHandling'] ?? false);
1754
        if ($language === 0 || $allowInconsistentLanguageHandling) {
1755
            return false;
1756
        }
1757
        /**
1758
         * Build up caches
1759
         */
1760
        if (!isset($this->languageHasTranslationsCache[$language])) {
1761
            foreach ($contentElements as $columns) {
1762
                foreach ($columns as $contentElement) {
1763
                    if ((int)$contentElement['l18n_parent'] === 0) {
1764
                        $this->languageHasTranslationsCache[$language]['hasStandAloneContent'] = true;
1765
                        $this->languageHasTranslationsCache[$language]['mode'] = 'free';
1766
                    }
1767
                    if ((int)$contentElement['l18n_parent'] > 0) {
1768
                        $this->languageHasTranslationsCache[$language]['hasTranslations'] = true;
1769
                        $this->languageHasTranslationsCache[$language]['mode'] = 'connected';
1770
                    }
1771
                }
1772
            }
1773
            if (!isset($this->languageHasTranslationsCache[$language])) {
1774
                $this->languageHasTranslationsCache[$language]['hasTranslations'] = false;
1775
            }
1776
            // Check whether we have a mix of both
1777
            if (isset($this->languageHasTranslationsCache[$language]['hasStandAloneContent'])
1778
                && $this->languageHasTranslationsCache[$language]['hasTranslations']
1779
            ) {
1780
                $this->languageHasTranslationsCache[$language]['mode'] = 'mixed';
1781
                $siteLanguage = $this->siteLanguages[$language];
1782
                $message = GeneralUtility::makeInstance(
1783
                    FlashMessage::class,
1784
                    sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $siteLanguage->getTitle()),
0 ignored issues
show
Bug introduced by
sprintf($this->getLangua...teLanguage->getTitle()) of type string is incompatible with the type array|array<mixed,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

1784
                    /** @scrutinizer ignore-type */ sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $siteLanguage->getTitle()),
Loading history...
1785
                    sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $siteLanguage->getTitle()),
1786
                    FlashMessage::WARNING
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Core\Messaging\FlashMessage::WARNING of type integer is incompatible with the type array|array<mixed,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

1786
                    /** @scrutinizer ignore-type */ FlashMessage::WARNING
Loading history...
1787
                );
1788
                $service = GeneralUtility::makeInstance(FlashMessageService::class);
1789
                $queue = $service->getMessageQueueByIdentifier();
1790
                $queue->addMessage($message);
1791
            }
1792
        }
1793
1794
        return $this->languageHasTranslationsCache[$language]['hasTranslations'];
1795
    }
1796
1797
    /**
1798
     * @return BackendLayoutView
1799
     */
1800
    protected function getBackendLayoutView()
1801
    {
1802
        return GeneralUtility::makeInstance(BackendLayoutView::class);
1803
    }
1804
1805
    /**
1806
     * @return BackendUserAuthentication
1807
     */
1808
    protected function getBackendUser()
1809
    {
1810
        return $GLOBALS['BE_USER'];
1811
    }
1812
1813
    /**
1814
     * Create thumbnail code for record/field
1815
     *
1816
     * @param mixed[] $row Record array
1817
     * @param string $table Table (record is from)
1818
     * @param string $field Field name for which thumbnail are to be rendered.
1819
     * @return string HTML for thumbnails, if any.
1820
     */
1821
    public function thumbCode($row, $table, $field)
1822
    {
1823
        return BackendUtility::thumbCode($row, $table, $field);
1824
    }
1825
1826
    /**
1827
     * Returns a QueryBuilder configured to select $fields from $table where the pid is restricted.
1828
     *
1829
     * @param string $table Table name
1830
     * @param int $pageId Page id Only used to build the search constraints, getPageIdConstraint() used for restrictions
1831
     * @param string[] $additionalConstraints Additional part for where clause
1832
     * @param string[] $fields Field list to select, * for all
1833
     * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
1834
     */
1835
    public function getQueryBuilder(
1836
        string $table,
1837
        int $pageId,
1838
        array $additionalConstraints = [],
1839
        array $fields = ['*']
1840
    ): QueryBuilder {
1841
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1842
            ->getQueryBuilderForTable($table);
1843
        $queryBuilder->getRestrictions()
1844
            ->removeAll()
1845
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1846
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1847
        $queryBuilder
1848
            ->select(...$fields)
1849
            ->from($table);
1850
1851
        if (!empty($additionalConstraints)) {
1852
            $queryBuilder->andWhere(...$additionalConstraints);
1853
        }
1854
1855
        $queryBuilder = $this->prepareQueryBuilder($table, $pageId, $fields, $additionalConstraints, $queryBuilder);
1856
1857
        return $queryBuilder;
1858
    }
1859
1860
    /**
1861
     * Return the modified QueryBuilder object ($queryBuilder) which will be
1862
     * used to select the records from a table $table with pid = $this->pidList
1863
     *
1864
     * @param string $table Table name
1865
     * @param int $pageId Page id Only used to build the search constraints, $this->pidList is used for restrictions
1866
     * @param string[] $fieldList List of fields to select from the table
1867
     * @param string[] $additionalConstraints Additional part for where clause
1868
     * @param QueryBuilder $queryBuilder
1869
     * @return QueryBuilder
1870
     */
1871
    protected function prepareQueryBuilder(
1872
        string $table,
1873
        int $pageId,
1874
        array $fieldList,
1875
        array $additionalConstraints,
1876
        QueryBuilder $queryBuilder
1877
    ): QueryBuilder {
1878
        $parameters = [
1879
            'table' => $table,
1880
            'fields' => $fieldList,
1881
            'groupBy' => null,
1882
            'orderBy' => null
1883
        ];
1884
1885
        // Build the query constraints
1886
        $queryBuilder->andWhere(
1887
            $queryBuilder->expr()->eq(
1888
                $table . '.pid',
1889
                $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
1890
            )
1891
        );
1892
1893
        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...
1894
            $hookObject = GeneralUtility::makeInstance($className);
1895
            if (method_exists($hookObject, 'modifyQuery')) {
1896
                $hookObject->modifyQuery(
1897
                    $parameters,
1898
                    $table,
1899
                    $pageId,
1900
                    $additionalConstraints,
1901
                    $fieldList,
1902
                    $queryBuilder
1903
                );
1904
            }
1905
        }
1906
1907
        return $queryBuilder;
1908
    }
1909
1910
    /**
1911
     * Renders the language flag and language title, but only if an icon is given, otherwise just the language
1912
     *
1913
     * @param SiteLanguage $language
1914
     * @return string
1915
     */
1916
    protected function renderLanguageFlag(SiteLanguage $language)
1917
    {
1918
        $title = htmlspecialchars($language->getTitle());
1919
        if ($language->getFlagIdentifier()) {
1920
            $icon = $this->iconFactory->getIcon(
1921
                $language->getFlagIdentifier(),
1922
                Icon::SIZE_SMALL
1923
            )->render();
1924
            return '<span title="' . $title . '">' . $icon . '</span>&nbsp;' . $title;
1925
        }
1926
        return $title;
1927
    }
1928
1929
    /**
1930
     * Fetch the site language objects for the given $pageId and store it in $this->siteLanguages
1931
     *
1932
     * @param int $pageId
1933
     * @throws SiteNotFoundException
1934
     */
1935
    protected function resolveSiteLanguages(int $pageId)
1936
    {
1937
        try {
1938
            $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
1939
        } catch (SiteNotFoundException $e) {
1940
            $site = new NullSite();
1941
        }
1942
        $this->siteLanguages = $site->getAvailableLanguages($this->getBackendUser(), false, $pageId);
1943
    }
1944
1945
    /**
1946
     * @return string $title
1947
     */
1948
    protected function getLocalizedPageTitle(): string
1949
    {
1950
        if (($this->tt_contentConfig['sys_language_uid'] ?? 0) > 0) {
1951
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1952
                ->getQueryBuilderForTable('pages');
1953
            $queryBuilder->getRestrictions()
1954
                ->removeAll()
1955
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1956
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1957
            $localizedPage = $queryBuilder
1958
                ->select('*')
1959
                ->from('pages')
1960
                ->where(
1961
                    $queryBuilder->expr()->eq(
1962
                        $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
1963
                        $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
1964
                    ),
1965
                    $queryBuilder->expr()->eq(
1966
                        $GLOBALS['TCA']['pages']['ctrl']['languageField'],
1967
                        $queryBuilder->createNamedParameter($this->tt_contentConfig['sys_language_uid'], \PDO::PARAM_INT)
1968
                    )
1969
                )
1970
                ->setMaxResults(1)
1971
                ->execute()
1972
                ->fetch();
1973
            BackendUtility::workspaceOL('pages', $localizedPage);
1974
            return $localizedPage['title'];
1975
        }
1976
        return $this->pageinfo['title'];
1977
    }
1978
1979
    /**
1980
     * Check if page can be edited by current user
1981
     *
1982
     * @return bool
1983
     */
1984
    protected function isPageEditable()
1985
    {
1986
        if ($this->getBackendUser()->isAdmin()) {
1987
            return true;
1988
        }
1989
        return !$this->pageinfo['editlock'] && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::PAGE_EDIT);
1990
    }
1991
1992
    /**
1993
     * Check if content can be edited by current user
1994
     *
1995
     * @param int|null $languageId
1996
     * @return bool
1997
     */
1998
    protected function isContentEditable(?int $languageId = null)
1999
    {
2000
        if ($this->getBackendUser()->isAdmin()) {
2001
            return true;
2002
        }
2003
        return !$this->pageinfo['editlock']
2004
            && $this->hasContentModificationAndAccessPermissions()
2005
            && ($languageId === null || $this->getBackendUser()->checkLanguageAccess($languageId));
2006
    }
2007
2008
    /**
2009
     * Check if current user has modification and access permissons for content set
2010
     *
2011
     * @return bool
2012
     */
2013
    protected function hasContentModificationAndAccessPermissions(): bool
2014
    {
2015
        return $this->getBackendUser()->check('tables_modify', 'tt_content')
2016
            && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT);
2017
    }
2018
2019
    /**
2020
     * Returns the language service
2021
     * @return LanguageService
2022
     */
2023
    protected function getLanguageService()
2024
    {
2025
        return $GLOBALS['LANG'];
2026
    }
2027
2028
    /**
2029
     * @param string $position Which event should be triggered? Possible options: before or after
2030
     * @param int $lP The language id you want to show data for
2031
     * @param array $columnConfig Array with the configuration of the current column
2032
     * @return string
2033
     */
2034
    protected function dispatchSectionMarkupGeneratedEvent(string $position, int $lP, array $columnConfig): string
2035
    {
2036
        if ($position === 'before') {
2037
            $event = new BeforeSectionMarkupGeneratedEvent($this, $lP, $columnConfig);
2038
        } else {
2039
            $event = new AfterSectionMarkupGeneratedEvent($this, $lP, $columnConfig);
2040
        }
2041
2042
        $this->eventDispatcher->dispatch($event);
2043
        return $event->getContent();
2044
    }
2045
}
2046