Completed
Push — master ( cb9863...be434f )
by
unknown
40:07 queued 25:06
created

PageLayoutView::createFromPageLayoutContext()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 20
nc 2
nop 1
dl 0
loc 24
rs 9.6
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Backend\View;
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\DeletedRestriction;
34
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
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
    /**
209
     * @param PageLayoutContext $context
210
     * @return PageLayoutView
211
     * @internal
212
     */
213
    public static function createFromPageLayoutContext(PageLayoutContext $context): PageLayoutView
214
    {
215
        $drawingConfiguration = $context->getDrawingConfiguration();
216
        $languageId = $drawingConfiguration->getSelectedLanguageId();
217
        /** @var PageLayoutView $pageLayoutView */
218
        $pageLayoutView = GeneralUtility::makeInstance(self::class);
219
        $pageLayoutView->id = $context->getPageId();
220
        $pageLayoutView->pageinfo = BackendUtility::readPageAccess($pageLayoutView->id, '');
221
        $pageLayoutView->pageRecord = $context->getPageRecord();
222
        $pageLayoutView->option_newWizard = $drawingConfiguration->getShowNewContentWizard();
223
        $pageLayoutView->defLangBinding = $drawingConfiguration->getDefaultLanguageBinding();
224
        $pageLayoutView->tt_contentConfig['cols'] = implode(',', $drawingConfiguration->getActiveColumns());
225
        $pageLayoutView->tt_contentConfig['activeCols'] = implode(',', $drawingConfiguration->getActiveColumns());
226
        $pageLayoutView->tt_contentConfig['showHidden'] = $drawingConfiguration->getShowHidden();
227
        $pageLayoutView->tt_contentConfig['sys_language_uid'] = $languageId;
228
        if ($drawingConfiguration->getLanguageMode()) {
229
            $pageLayoutView->tt_contentConfig['languageMode'] = 1;
230
            $pageLayoutView->tt_contentConfig['languageCols'] = $drawingConfiguration->getLanguageColumns();
231
            $pageLayoutView->tt_contentConfig['languageColsPointer'] = $languageId;
232
        }
233
        $pageLayoutView->doEdit = $pageLayoutView->isContentEditable($languageId);
234
        $pageLayoutView->CType_labels = $context->getContentTypeLabels();
235
        $pageLayoutView->itemLabels = $context->getItemLabels();
236
        return $pageLayoutView;
237
    }
238
239
    protected function initialize()
240
    {
241
        $this->resolveSiteLanguages($this->id);
242
        $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
243
        $this->initializeClipboard();
244
        $this->pageinfo = BackendUtility::readPageAccess($this->id, '');
245
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
246
        $pageActionsCallback = null;
247
        if ($this->isPageEditable()) {
248
            $languageOverlayId = 0;
249
            $pageLocalizationRecord = BackendUtility::getRecordLocalization('pages', $this->id, (int)$this->tt_contentConfig['sys_language_uid']);
250
            if (is_array($pageLocalizationRecord)) {
251
                $pageLocalizationRecord = reset($pageLocalizationRecord);
252
            }
253
            if (!empty($pageLocalizationRecord['uid'])) {
254
                $languageOverlayId = $pageLocalizationRecord['uid'];
255
            }
256
            $pageActionsCallback = 'function(PageActions) {
257
                PageActions.setPageId(' . (int)$this->id . ');
258
                PageActions.setLanguageOverlayId(' . $languageOverlayId . ');
259
            }';
260
        }
261
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/PageActions', $pageActionsCallback);
262
        // Get labels for CTypes and tt_content element fields in general:
263
        $this->CType_labels = [];
264
        foreach ($GLOBALS['TCA']['tt_content']['columns']['CType']['config']['items'] as $val) {
265
            $this->CType_labels[$val[1]] = $this->getLanguageService()->sL($val[0]);
266
        }
267
268
        $this->itemLabels = [];
269
        foreach ($GLOBALS['TCA']['tt_content']['columns'] as $name => $val) {
270
            $this->itemLabels[$name] = $this->getLanguageService()->sL($val['label']);
271
        }
272
    }
273
274
    /**
275
     * Build a list of language IDs that should be rendered in this view
276
     * @return int[]
277
     */
278
    protected function getSelectedLanguages(): array
279
    {
280
        $langList = $this->tt_contentConfig['sys_language_uid'];
281
        if ($this->tt_contentConfig['languageMode']) {
282
            if ($this->tt_contentConfig['languageColsPointer']) {
283
                $langList = '0,' . $this->tt_contentConfig['languageColsPointer'];
284
            } else {
285
                $langList = implode(',', array_keys($this->tt_contentConfig['languageCols']));
286
            }
287
        }
288
        return GeneralUtility::intExplode(',', $langList);
289
    }
290
291
    /**
292
     * Renders Content Elements from the tt_content table from page id
293
     *
294
     * @param int $id Page id
295
     * @return string HTML for the listing
296
     */
297
    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...
298
    {
299
        $this->id = (int)$id;
300
        $this->initialize();
301
        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
302
            ->getConnectionForTable('tt_content')
303
            ->getExpressionBuilder();
304
305
        $languageColumn = [];
306
        $out = '';
307
        $tcaItems = GeneralUtility::makeInstance(BackendLayoutView::class)->getColPosListItemsParsed($this->id);
308
        $languageIds = $this->getSelectedLanguages();
309
        $defaultLanguageElementsByColumn = [];
310
        $defLangBinding = [];
311
        // For each languages...
312
        // If not languageMode, then we'll only be through this once.
313
        foreach ($languageIds as $lP) {
314
            if (!isset($this->contentElementCache[$lP])) {
315
                $this->contentElementCache[$lP] = [];
316
            }
317
318
            if (count($languageIds) === 1 || $lP === 0) {
319
                $showLanguage = $expressionBuilder->in('sys_language_uid', [$lP, -1]);
320
            } else {
321
                $showLanguage = $expressionBuilder->eq('sys_language_uid', $lP);
322
            }
323
            $content = [];
324
            $head = [];
325
326
            $backendLayout = $this->getBackendLayoutView()->getSelectedBackendLayout($this->id);
327
            $columns = $backendLayout['__colPosList'];
328
            // Select content records per column
329
            $contentRecordsPerColumn = $this->getContentRecordsPerColumn('tt_content', $id, $columns, $showLanguage);
330
            $cList = array_keys($contentRecordsPerColumn);
331
            // For each column, render the content into a variable:
332
            foreach ($cList as $columnId) {
333
                if (!isset($this->contentElementCache[$lP])) {
334
                    $this->contentElementCache[$lP] = [];
335
                }
336
337
                if (!$lP) {
338
                    $defaultLanguageElementsByColumn[$columnId] = [];
339
                }
340
341
                // Start wrapping div
342
                $content[$columnId] .= '<div data-colpos="' . $columnId . '" data-language-uid="' . $lP . '" class="t3js-sortable t3js-sortable-lang t3js-sortable-lang-' . $lP . ' t3-page-ce-wrapper';
343
                if (empty($contentRecordsPerColumn[$columnId])) {
344
                    $content[$columnId] .= ' t3-page-ce-empty';
345
                }
346
                $content[$columnId] .= '">';
347
                // Add new content at the top most position
348
                $link = '';
349
                if ($this->isContentEditable()
350
                    && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
351
                ) {
352
                    if ($this->option_newWizard) {
353
                        $urlParameters = [
354
                            'id' => $id,
355
                            'sys_language_uid' => $lP,
356
                            'colPos' => $columnId,
357
                            'uid_pid' => $id,
358
                            'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
359
                        ];
360
                        $routeName = BackendUtility::getPagesTSconfig($id)['mod.']['newContentElementWizard.']['override']
361
                            ?? 'new_content_element_wizard';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "??"; newline found
Loading history...
362
                        $url = (string)$this->uriBuilder->buildUriFromRoute($routeName, $urlParameters);
363
                    } else {
364
                        $urlParameters = [
365
                            'edit' => [
366
                                'tt_content' => [
367
                                    $id => 'new'
368
                                ]
369
                            ],
370
                            'defVals' => [
371
                                'tt_content' => [
372
                                    'colPos' => $columnId,
373
                                    'sys_language_uid' => $lP
374
                                ]
375
                            ],
376
                            'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
377
                        ];
378
                        $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
379
                    }
380
                    $title = htmlspecialchars($this->getLanguageService()->getLL('newContentElement'));
381
                    $link = '<a href="' . htmlspecialchars($url) . '" '
382
                        . 'title="' . $title . '"'
383
                        . 'data-title="' . $title . '"'
384
                        . 'class="btn btn-default btn-sm ' . ($this->option_newWizard ? 't3js-toggle-new-content-element-wizard' : '') . '">'
385
                        . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render()
386
                        . ' '
387
                        . htmlspecialchars($this->getLanguageService()->getLL('content')) . '</a>';
388
                }
389
                if ($this->getBackendUser()->checkLanguageAccess($lP) && $columnId !== 'unused') {
390
                    $content[$columnId] .= '
391
                    <div class="t3-page-ce t3js-page-ce" data-page="' . (int)$id . '" id="' . StringUtility::getUniqueId() . '">
392
                        <div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-page-' . $id . '-' . StringUtility::getUniqueId() . '">'
393
                            . $link
394
                            . '</div>
395
                        <div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div>
396
                    </div>
397
                    ';
398
                }
399
                $editUidList = '';
400
                if (!isset($contentRecordsPerColumn[$columnId]) || !is_array($contentRecordsPerColumn[$columnId])) {
401
                    $message = GeneralUtility::makeInstance(
402
                        FlashMessage::class,
403
                        $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:error.invalidBackendLayout'),
404
                        '',
405
                        FlashMessage::WARNING
406
                    );
407
                    $service = GeneralUtility::makeInstance(FlashMessageService::class);
408
                    $queue = $service->getMessageQueueByIdentifier();
409
                    $queue->addMessage($message);
410
                } else {
411
                    $rowArr = $contentRecordsPerColumn[$columnId];
412
                    $this->generateTtContentDataArray($rowArr);
413
414
                    foreach ((array)$rowArr as $rKey => $row) {
415
                        $this->contentElementCache[$lP][$columnId][$row['uid']] = $row;
416
                        if ($this->tt_contentConfig['languageMode']) {
417
                            $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
418
                        }
419
                        if (is_array($row) && !VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
420
                            $singleElementHTML = '<div class="t3-page-ce-dragitem" id="' . StringUtility::getUniqueId() . '">';
421
                            if (!$lP && ($this->defLangBinding || $row['sys_language_uid'] != -1)) {
422
                                $defaultLanguageElementsByColumn[$columnId][] = ($row['_ORIG_uid'] ?? $row['uid']);
423
                            }
424
                            $editUidList .= $row['uid'] . ',';
425
                            $disableMoveAndNewButtons = $this->defLangBinding && $lP > 0 && $this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP);
426
                            $singleElementHTML .= $this->tt_content_drawHeader(
427
                                $row,
428
                                0,
429
                                $disableMoveAndNewButtons,
430
                                true,
431
                                $this->hasContentModificationAndAccessPermissions()
432
                            );
433
                            $innerContent = '<div ' . ($row['_ORIG_uid'] ? ' class="ver-element"' : '') . '>'
434
                                . $this->tt_content_drawItem($row) . '</div>';
435
                            $singleElementHTML .= '<div class="t3-page-ce-body-inner">' . $innerContent . '</div></div>'
436
                                . $this->tt_content_drawFooter($row);
437
                            $isDisabled = $this->isDisabled('tt_content', $row);
438
                            $statusHidden = $isDisabled ? ' t3-page-ce-hidden t3js-hidden-record' : '';
439
                            $displayNone = !$this->tt_contentConfig['showHidden'] && $isDisabled ? ' style="display: none;"' : '';
440
                            $highlightHeader = '';
441
                            if ($this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid']) && (int)$row['l18n_parent'] === 0) {
442
                                $highlightHeader = ' t3-page-ce-danger';
443
                            } elseif ($columnId === 'unused') {
444
                                $highlightHeader = ' t3-page-ce-warning';
445
                            }
446
                            $singleElementHTML = '<div class="t3-page-ce' . $highlightHeader . ' t3js-page-ce t3js-page-ce-sortable ' . $statusHidden . '" id="element-tt_content-'
447
                                . $row['uid'] . '" data-table="tt_content" data-uid="' . $row['uid'] . '"' . $displayNone . '>' . $singleElementHTML . '</div>';
448
449
                            $singleElementHTML .= '<div class="t3-page-ce" data-colpos="' . $columnId . '">';
450
                            $singleElementHTML .= '<div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-page-' . $id .
451
                                '-' . StringUtility::getUniqueId() . '">';
452
                            // Add icon "new content element below"
453
                            if (!$disableMoveAndNewButtons
454
                                && $this->isContentEditable($lP)
455
                                && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
456
                                && $columnId !== 'unused'
457
                            ) {
458
                                // New content element:
459
                                if ($this->option_newWizard) {
460
                                    $urlParameters = [
461
                                        'id' => $row['pid'],
462
                                        'sys_language_uid' => $row['sys_language_uid'],
463
                                        'colPos' => $row['colPos'],
464
                                        'uid_pid' => -$row['uid'],
465
                                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
466
                                    ];
467
                                    $routeName = BackendUtility::getPagesTSconfig($row['pid'])['mod.']['newContentElementWizard.']['override']
468
                                        ?? 'new_content_element_wizard';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "??"; newline found
Loading history...
469
                                    $url = (string)$this->uriBuilder->buildUriFromRoute($routeName, $urlParameters);
470
                                } else {
471
                                    $urlParameters = [
472
                                        'edit' => [
473
                                            'tt_content' => [
474
                                                -$row['uid'] => 'new'
475
                                            ]
476
                                        ],
477
                                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
478
                                    ];
479
                                    $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
480
                                }
481
                                $title = htmlspecialchars($this->getLanguageService()->getLL('newContentElement'));
482
                                $singleElementHTML .= '<a href="' . htmlspecialchars($url) . '" '
483
                                    . 'title="' . $title . '"'
484
                                    . 'data-title="' . $title . '"'
485
                                    . 'class="btn btn-default btn-sm ' . ($this->option_newWizard ? 't3js-toggle-new-content-element-wizard' : '') . '">'
486
                                    . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render()
487
                                    . ' '
488
                                    . htmlspecialchars($this->getLanguageService()->getLL('content')) . '</a>';
489
                            }
490
                            $singleElementHTML .= '</div></div><div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div></div>';
491
                            if ($this->defLangBinding && $this->tt_contentConfig['languageMode']) {
492
                                $defLangBinding[$columnId][$lP][$row[$lP ? 'l18n_parent' : 'uid'] ?: $row['uid']] = $singleElementHTML;
493
                            } else {
494
                                $content[$columnId] .= $singleElementHTML;
495
                            }
496
                        } else {
497
                            unset($rowArr[$rKey]);
498
                        }
499
                    }
500
                    $content[$columnId] .= '</div>';
501
                    if ($columnId === 'unused') {
502
                        if (empty($unusedElementsMessage)) {
503
                            $unusedElementsMessage = GeneralUtility::makeInstance(
504
                                FlashMessage::class,
505
                                $this->getLanguageService()->getLL('staleUnusedElementsWarning'),
506
                                $this->getLanguageService()->getLL('staleUnusedElementsWarningTitle'),
507
                                FlashMessage::WARNING
508
                            );
509
                            $service = GeneralUtility::makeInstance(FlashMessageService::class);
510
                            $queue = $service->getMessageQueueByIdentifier();
511
                            $queue->addMessage($unusedElementsMessage);
512
                        }
513
                        $colTitle = $this->getLanguageService()->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:colPos.I.unused');
514
                        $editParam = '';
515
                    } else {
516
                        $colTitle = '';
517
                        foreach ($tcaItems as $item) {
518
                            if ($item[1] == $columnId) {
519
                                $colTitle = $this->getLanguageService()->sL($item[0]);
520
                            }
521
                        }
522
                        if (empty($colTitle)) {
523
                            $colTitle = BackendUtility::getProcessedValue('tt_content', 'colPos', $columnId);
524
                        }
525
                        $editParam = $this->doEdit && !empty($rowArr)
526
                            ? '&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...
527
                            : '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
528
                    }
529
                    $head[$columnId] .= $this->tt_content_drawColHeader($colTitle, $editParam);
530
                }
531
            }
532
            // For each column, fit the rendered content into a table cell:
533
            $out = '';
534
            if ($this->tt_contentConfig['languageMode']) {
535
                // in language mode process the content elements, but only fill $languageColumn. output will be generated later
536
                $sortedLanguageColumn = [];
537
                foreach ($cList as $columnId) {
538
                    if (GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnId) || $columnId === 'unused') {
539
                        $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
540
541
                        // We sort $languageColumn again according to $cList as it may contain data already from above.
542
                        $sortedLanguageColumn[$columnId] = $languageColumn[$columnId];
543
                    }
544
                }
545
                if (!empty($languageColumn['unused'])) {
546
                    $sortedLanguageColumn['unused'] = $languageColumn['unused'];
547
                }
548
                $languageColumn = $sortedLanguageColumn;
549
            } else {
550
                // GRID VIEW:
551
                $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">';
552
                // Add colgroups
553
                $colCount = (int)$backendLayout['__config']['backend_layout.']['colCount'];
554
                $rowCount = (int)$backendLayout['__config']['backend_layout.']['rowCount'];
555
                $colSpan = 0;
556
                $rowSpan = 0;
557
                $grid .= '<colgroup>';
558
                for ($i = 0; $i < $colCount; $i++) {
559
                    $grid .= '<col />';
560
                }
561
                $grid .= '</colgroup>';
562
563
                // Check how to handle restricted columns
564
                $hideRestrictedCols = (bool)(BackendUtility::getPagesTSconfig($id)['mod.']['web_layout.']['hideRestrictedCols'] ?? false);
565
566
                // Cycle through rows
567
                for ($row = 1; $row <= $rowCount; $row++) {
568
                    $rowConfig = $backendLayout['__config']['backend_layout.']['rows.'][$row . '.'];
569
                    if (!isset($rowConfig)) {
570
                        continue;
571
                    }
572
                    $grid .= '<tr>';
573
                    for ($col = 1; $col <= $colCount; $col++) {
574
                        $columnConfig = $rowConfig['columns.'][$col . '.'];
575
                        if (!isset($columnConfig)) {
576
                            continue;
577
                        }
578
                        // Which tt_content colPos should be displayed inside this cell
579
                        $columnKey = (int)$columnConfig['colPos'];
580
                        // Render the grid cell
581
                        $colSpan = (int)$columnConfig['colspan'];
582
                        $rowSpan = (int)$columnConfig['rowspan'];
583
                        $grid .= '<td valign="top"' .
584
                            ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
585
                            ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
586
                            ' 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 .
587
                            ((!isset($columnConfig['colPos']) || $columnConfig['colPos'] === '') ? ' t3-grid-cell-unassigned' : '') .
588
                            ((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') : '') .
589
                            ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') .
590
                            ($rowSpan > 0 ? ' t3-gridCell-height' . $rowSpan : '') . '">';
591
592
                        // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
593
                        // If not, a new header without any buttons will be generated.
594
                        if (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && $head[$columnKey]
595
                            && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
596
                        ) {
597
                            $grid .= $head[$columnKey];
598
                            $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
599
                            $grid .= $content[$columnKey];
600
                        } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
601
                            && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
602
                        ) {
603
                            if (!$hideRestrictedCols) {
604
                                $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('noAccess'));
605
                                $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
606
                            }
607
                        } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
608
                            && !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
609
                        ) {
610
                            if (!$hideRestrictedCols) {
611
                                $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name']) .
612
                                  ' (' . $this->getLanguageService()->getLL('noAccess') . ')');
613
                                $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
614
                            }
615
                        } elseif (isset($columnConfig['name']) && $columnConfig['name'] !== '') {
616
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name'])
617
                                . ' (' . $this->getLanguageService()->getLL('notAssigned') . ')');
618
                            $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
619
                        } else {
620
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('notAssigned'));
621
                            $grid .= $this->dispatchSectionMarkupGeneratedEvent('before', $lP, $columnConfig);
622
                        }
623
624
                        $grid .= $this->dispatchSectionMarkupGeneratedEvent('after', $lP, $columnConfig);
625
626
                        $grid .= '</td>';
627
                    }
628
                    $grid .= '</tr>';
629
                }
630
                if (!empty($content['unused'])) {
631
                    $grid .= '<tr>';
632
                    // Which tt_content colPos should be displayed inside this cell
633
                    $columnKey = 'unused';
634
                    // Render the grid cell
635
                    $colSpan = (int)$backendLayout['__config']['backend_layout.']['colCount'];
636
                    $grid .= '<td valign="top"' .
637
                        ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
638
                        ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
639
                        ' 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 .
640
                        ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') . '">';
641
642
                    // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
643
                    // If not, a new header without any buttons will be generated.
644
                    $grid .= $head[$columnKey] . $content[$columnKey];
645
                    $grid .= '</td></tr>';
646
                }
647
                $out .= $grid . '</table></div>';
648
            }
649
        }
650
        $elFromTable = $this->clipboard->elFromTable('tt_content');
651
        if (!empty($elFromTable) && $this->isPageEditable()) {
652
            $pasteItem = substr(key($elFromTable), 11);
653
            $pasteRecord = BackendUtility::getRecord('tt_content', (int)$pasteItem);
654
            $pasteTitle = $pasteRecord['header'] ?: $pasteItem;
655
            $copyMode = $this->clipboard->clipData['normal']['mode'] ? '-' . $this->clipboard->clipData['normal']['mode'] : '';
656
            $inlineJavaScript = '
657
                     top.pasteIntoLinkTemplate = '
658
                . $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

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

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

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

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

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