PageLayoutView::getSelectedLanguages()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 11
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 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\ForwardCompatibility\Result;
19
use Doctrine\DBAL\Statement;
20
use Psr\Log\LoggerAwareInterface;
21
use Psr\Log\LoggerAwareTrait;
22
use TYPO3\CMS\Backend\Controller\Page\LocalizationController;
23
use TYPO3\CMS\Backend\Routing\PreviewUriBuilder;
24
use TYPO3\CMS\Backend\Routing\UriBuilder;
25
use TYPO3\CMS\Backend\Utility\BackendUtility;
26
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
27
use TYPO3\CMS\Core\Core\Environment;
28
use TYPO3\CMS\Core\Database\Connection;
29
use TYPO3\CMS\Core\Database\ConnectionPool;
30
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
31
use TYPO3\CMS\Core\Database\Query\QueryHelper;
32
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
33
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
34
use TYPO3\CMS\Core\Database\ReferenceIndex;
35
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
36
use TYPO3\CMS\Core\Imaging\Icon;
37
use TYPO3\CMS\Core\Imaging\IconFactory;
38
use TYPO3\CMS\Core\Localization\LanguageService;
39
use TYPO3\CMS\Core\Messaging\FlashMessage;
40
use TYPO3\CMS\Core\Messaging\FlashMessageService;
41
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
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
     * Current ids page record
148
     *
149
     * @var array
150
     */
151
    protected $pageinfo;
152
153
    /**
154
     * Caches the amount of content elements as a matrix
155
     *
156
     * @var array
157
     * @internal
158
     */
159
    protected $contentElementCache = [];
160
161
    /**
162
     * @var IconFactory
163
     */
164
    protected $iconFactory;
165
166
    /**
167
     * Stores whether a certain language has translations in it
168
     *
169
     * @var array
170
     */
171
    protected $languageHasTranslationsCache = [];
172
173
    /**
174
     * @var LocalizationController
175
     */
176
    protected $localizationController;
177
178
    /**
179
     * Cache the number of references to a record
180
     *
181
     * @var array
182
     */
183
    protected $referenceCount = [];
184
185
    /**
186
     * @var UriBuilder
187
     */
188
    protected $uriBuilder;
189
190
    public function __construct()
191
    {
192
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
193
        $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
194
        $this->localizationController = GeneralUtility::makeInstance(LocalizationController::class);
195
    }
196
197
    /**
198
     * @param PageLayoutContext $context
199
     * @return PageLayoutView
200
     * @internal
201
     */
202
    public static function createFromPageLayoutContext(PageLayoutContext $context): PageLayoutView
203
    {
204
        $drawingConfiguration = $context->getDrawingConfiguration();
205
        $languageId = $drawingConfiguration->getSelectedLanguageId();
206
        /** @var PageLayoutView $pageLayoutView */
207
        $pageLayoutView = GeneralUtility::makeInstance(self::class);
208
        $pageLayoutView->id = $context->getPageId();
209
        $pageLayoutView->pageinfo = BackendUtility::readPageAccess($pageLayoutView->id, '') ?: [];
210
        $pageLayoutView->pageRecord = $context->getPageRecord();
211
        $pageLayoutView->option_newWizard = $drawingConfiguration->getShowNewContentWizard();
212
        $pageLayoutView->defLangBinding = $drawingConfiguration->getDefaultLanguageBinding();
213
        $pageLayoutView->tt_contentConfig['cols'] = implode(',', $drawingConfiguration->getActiveColumns());
214
        $pageLayoutView->tt_contentConfig['activeCols'] = implode(',', $drawingConfiguration->getActiveColumns());
215
        $pageLayoutView->tt_contentConfig['showHidden'] = $drawingConfiguration->getShowHidden();
216
        $pageLayoutView->tt_contentConfig['sys_language_uid'] = $languageId;
217
        if ($drawingConfiguration->getLanguageMode()) {
218
            $pageLayoutView->tt_contentConfig['languageMode'] = 1;
219
            $pageLayoutView->tt_contentConfig['languageCols'] = $drawingConfiguration->getLanguageColumns();
220
            $pageLayoutView->tt_contentConfig['languageColsPointer'] = $languageId;
221
        }
222
        $pageLayoutView->doEdit = $pageLayoutView->isContentEditable($languageId);
223
        $pageLayoutView->CType_labels = $context->getContentTypeLabels();
224
        $pageLayoutView->itemLabels = $context->getItemLabels();
225
        return $pageLayoutView;
226
    }
227
228
    protected function initialize()
229
    {
230
        $this->resolveSiteLanguages($this->id);
231
        $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
232
        $this->pageinfo = BackendUtility::readPageAccess($this->id, '') ?: [];
233
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
234
235
        $pageActionsInstruction = JavaScriptModuleInstruction::forRequireJS('TYPO3/CMS/Backend/PageActions');
236
        if ($this->isPageEditable()) {
237
            $languageOverlayId = 0;
238
            $pageLocalizationRecord = BackendUtility::getRecordLocalization('pages', $this->id, (int)$this->tt_contentConfig['sys_language_uid']);
239
            if (is_array($pageLocalizationRecord)) {
240
                $pageLocalizationRecord = reset($pageLocalizationRecord);
241
            }
242
            if (!empty($pageLocalizationRecord['uid'])) {
243
                $languageOverlayId = $pageLocalizationRecord['uid'];
244
            }
245
            $pageActionsInstruction
246
                ->invoke('setPageId', (int)$this->id)
247
                ->invoke('setLanguageOverlayId', $languageOverlayId);
248
        }
249
        $pageRenderer->getJavaScriptRenderer()->addJavaScriptModuleInstruction($pageActionsInstruction);
250
        // Get labels for CTypes and tt_content element fields in general:
251
        $this->CType_labels = [];
252
        foreach ($GLOBALS['TCA']['tt_content']['columns']['CType']['config']['items'] as $val) {
253
            $this->CType_labels[$val[1]] = $this->getLanguageService()->sL($val[0]);
254
        }
255
256
        $this->itemLabels = [];
257
        foreach ($GLOBALS['TCA']['tt_content']['columns'] as $name => $val) {
258
            $this->itemLabels[$name] = $this->getLanguageService()->sL($val['label']);
259
        }
260
    }
261
262
    /**
263
     * Build a list of language IDs that should be rendered in this view
264
     * @return int[]
265
     */
266
    protected function getSelectedLanguages(): array
267
    {
268
        $langList = $this->tt_contentConfig['sys_language_uid'];
269
        if ($this->tt_contentConfig['languageMode']) {
270
            if ($this->tt_contentConfig['languageColsPointer']) {
271
                $langList = '0,' . $this->tt_contentConfig['languageColsPointer'];
272
            } else {
273
                $langList = implode(',', array_keys($this->tt_contentConfig['languageCols']));
274
            }
275
        }
276
        return GeneralUtility::intExplode(',', $langList);
277
    }
278
279
    /**
280
     * Renders Content Elements from the tt_content table from page id
281
     *
282
     * @param int $id Page id
283
     * @return string HTML for the listing
284
     */
285
    public function getTable_tt_content($id)
286
    {
287
        $this->id = (int)$id;
288
        $this->initialize();
289
        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
290
            ->getConnectionForTable('tt_content')
291
            ->getExpressionBuilder();
292
293
        $languageColumn = [];
294
        $out = '';
295
        $tcaItems = GeneralUtility::makeInstance(BackendLayoutView::class)->getColPosListItemsParsed($this->id);
296
        $languageIds = $this->getSelectedLanguages();
297
        $defaultLanguageElementsByColumn = [];
298
        $defLangBinding = [];
299
        // For each languages...
300
        // If not languageMode, then we'll only be through this once.
301
        foreach ($languageIds as $lP) {
302
            if (!isset($this->contentElementCache[$lP])) {
303
                $this->contentElementCache[$lP] = [];
304
            }
305
306
            if (count($languageIds) === 1 || $lP === 0) {
307
                $showLanguage = $expressionBuilder->in('sys_language_uid', [$lP, -1]);
308
            } else {
309
                $showLanguage = $expressionBuilder->eq('sys_language_uid', $lP);
310
            }
311
            $content = [];
312
            $head = [];
313
314
            $backendLayout = $this->getBackendLayoutView()->getSelectedBackendLayout($this->id);
315
            $columns = $backendLayout['__colPosList'];
316
            // Select content records per column
317
            $contentRecordsPerColumn = $this->getContentRecordsPerColumn('tt_content', $id, $columns, $showLanguage);
318
            $cList = array_keys($contentRecordsPerColumn);
319
            // For each column, render the content into a variable:
320
            foreach ($cList as $columnId) {
321
                if (!isset($this->contentElementCache[$lP])) {
322
                    $this->contentElementCache[$lP] = [];
323
                }
324
325
                if (!$lP) {
326
                    $defaultLanguageElementsByColumn[$columnId] = [];
327
                }
328
329
                // Start wrapping div
330
                $content[$columnId] .= '<div data-colpos="' . $columnId . '" data-language-uid="' . $lP . '" class="t3js-sortable t3js-sortable-lang t3js-sortable-lang-' . $lP . ' t3-page-ce-wrapper';
331
                if (empty($contentRecordsPerColumn[$columnId])) {
332
                    $content[$columnId] .= ' t3-page-ce-empty';
333
                }
334
                $content[$columnId] .= '">';
335
                // Add new content at the top most position
336
                $link = '';
337
                if ($this->isContentEditable()
338
                    && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
339
                ) {
340
                    if ($this->option_newWizard) {
341
                        $urlParameters = [
342
                            'id' => $id,
343
                            'sys_language_uid' => $lP,
344
                            'colPos' => $columnId,
345
                            'uid_pid' => $id,
346
                            'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
347
                        ];
348
                        $routeName = BackendUtility::getPagesTSconfig($id)['mod.']['newContentElementWizard.']['override']
349
                            ?? 'new_content_element_wizard';
350
                        $url = (string)$this->uriBuilder->buildUriFromRoute($routeName, $urlParameters);
351
                    } else {
352
                        $urlParameters = [
353
                            'edit' => [
354
                                'tt_content' => [
355
                                    $id => 'new',
356
                                ],
357
                            ],
358
                            'defVals' => [
359
                                'tt_content' => [
360
                                    'colPos' => $columnId,
361
                                    'sys_language_uid' => $lP,
362
                                ],
363
                            ],
364
                            'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
365
                        ];
366
                        $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
367
                    }
368
                    $title = htmlspecialchars($this->getLanguageService()->getLL('newContentElement'));
369
                    $link = '<a href="' . htmlspecialchars($url) . '" '
370
                        . 'title="' . $title . '"'
371
                        . 'data-title="' . $title . '"'
372
                        . 'class="btn btn-default btn-sm ' . ($this->option_newWizard ? 't3js-toggle-new-content-element-wizard disabled' : '') . '">'
373
                        . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render()
374
                        . ' '
375
                        . htmlspecialchars($this->getLanguageService()->getLL('content')) . '</a>';
376
                }
377
                if ($this->getBackendUser()->checkLanguageAccess($lP) && $columnId !== 'unused') {
378
                    $content[$columnId] .= '
379
                    <div class="t3-page-ce t3js-page-ce" data-page="' . (int)$id . '" id="' . StringUtility::getUniqueId() . '">
380
                        <div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-page-' . $id . '-' . StringUtility::getUniqueId() . '">'
381
                            . $link
382
                            . '</div>
383
                        <div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div>
384
                    </div>
385
                    ';
386
                }
387
                $editUidList = '';
388
                if (!isset($contentRecordsPerColumn[$columnId]) || !is_array($contentRecordsPerColumn[$columnId])) {
389
                    $message = GeneralUtility::makeInstance(
390
                        FlashMessage::class,
391
                        $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:error.invalidBackendLayout'),
392
                        '',
393
                        FlashMessage::WARNING
394
                    );
395
                    $service = GeneralUtility::makeInstance(FlashMessageService::class);
396
                    $queue = $service->getMessageQueueByIdentifier();
397
                    $queue->addMessage($message);
398
                } else {
399
                    $rowArr = $contentRecordsPerColumn[$columnId];
400
                    $this->generateTtContentDataArray($rowArr);
401
402
                    foreach ((array)$rowArr as $rKey => $row) {
403
                        $this->contentElementCache[$lP][$columnId][$row['uid']] = $row;
404
                        if ($this->tt_contentConfig['languageMode']) {
405
                            $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
406
                        }
407
                        if (is_array($row) && !VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
408
                            $singleElementHTML = '<div class="t3-page-ce-dragitem" id="' . StringUtility::getUniqueId() . '">';
409
                            if (!$lP && ($this->defLangBinding || $row['sys_language_uid'] != -1)) {
410
                                $defaultLanguageElementsByColumn[$columnId][] = ($row['_ORIG_uid'] ?? $row['uid']);
411
                            }
412
                            $editUidList .= $row['uid'] . ',';
413
                            $disableMoveAndNewButtons = $this->defLangBinding && $lP > 0 && $this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP);
414
                            $singleElementHTML .= $this->tt_content_drawHeader(
415
                                $row,
416
                                0,
417
                                $disableMoveAndNewButtons,
418
                                true,
419
                                $this->hasContentModificationAndAccessPermissions()
420
                            );
421
                            $innerContent = '<div ' . ($row['_ORIG_uid'] ? ' class="ver-element"' : '') . '>'
422
                                . $this->tt_content_drawItem($row) . '</div>';
423
                            $singleElementHTML .= '<div class="t3-page-ce-body-inner">' . $innerContent . '</div></div>'
424
                                . $this->tt_content_drawFooter($row);
425
                            $isDisabled = $this->isDisabled('tt_content', $row);
426
                            $statusHidden = $isDisabled ? ' t3-page-ce-hidden t3js-hidden-record' : '';
427
                            $displayNone = !$this->tt_contentConfig['showHidden'] && $isDisabled ? ' style="display: none;"' : '';
428
                            $highlightHeader = '';
429
                            if ($this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid']) && (int)$row['l18n_parent'] === 0) {
430
                                $highlightHeader = ' t3-page-ce-danger';
431
                            } elseif ($columnId === 'unused') {
432
                                $highlightHeader = ' t3-page-ce-warning';
433
                            }
434
                            $singleElementHTML = '<div class="t3-page-ce' . $highlightHeader . ' t3js-page-ce t3js-page-ce-sortable ' . $statusHidden . '" id="element-tt_content-'
435
                                . $row['uid'] . '" data-table="tt_content" data-uid="' . $row['uid'] . '" data-language-uid="'
436
                                . $row['sys_language_uid'] . '"' . $displayNone . '>' . $singleElementHTML . '</div>';
437
438
                            $singleElementHTML .= '<div class="t3-page-ce" data-colpos="' . $columnId . '">';
439
                            $singleElementHTML .= '<div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $columnId . '-page-' . $id .
440
                                '-' . StringUtility::getUniqueId() . '">';
441
                            // Add icon "new content element below"
442
                            if (!$disableMoveAndNewButtons
443
                                && $this->isContentEditable($lP)
444
                                && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
445
                                && $columnId !== 'unused'
446
                            ) {
447
                                // New content element:
448
                                if ($this->option_newWizard) {
449
                                    $urlParameters = [
450
                                        'id' => $row['pid'],
451
                                        'sys_language_uid' => $row['sys_language_uid'],
452
                                        'colPos' => $row['colPos'],
453
                                        'uid_pid' => -$row['uid'],
454
                                        'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
455
                                    ];
456
                                    $routeName = BackendUtility::getPagesTSconfig($row['pid'])['mod.']['newContentElementWizard.']['override']
457
                                        ?? 'new_content_element_wizard';
458
                                    $url = (string)$this->uriBuilder->buildUriFromRoute($routeName, $urlParameters);
459
                                } else {
460
                                    $urlParameters = [
461
                                        'edit' => [
462
                                            'tt_content' => [
463
                                                -$row['uid'] => 'new',
464
                                            ],
465
                                        ],
466
                                        'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
467
                                    ];
468
                                    $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
469
                                }
470
                                $title = htmlspecialchars($this->getLanguageService()->getLL('newContentElement'));
471
                                $singleElementHTML .= '<a href="' . htmlspecialchars($url) . '" '
472
                                    . 'title="' . $title . '"'
473
                                    . 'data-title="' . $title . '"'
474
                                    . 'class="btn btn-default btn-sm ' . ($this->option_newWizard ? 't3js-toggle-new-content-element-wizard disabled' : '') . '">'
475
                                    . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render()
476
                                    . ' '
477
                                    . htmlspecialchars($this->getLanguageService()->getLL('content')) . '</a>';
478
                            }
479
                            $singleElementHTML .= '</div></div><div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div></div>';
480
                            if ($this->defLangBinding && $this->tt_contentConfig['languageMode']) {
481
                                $defLangBinding[$columnId][$lP][$row[$lP ? 'l18n_parent' : 'uid'] ?: $row['uid']] = $singleElementHTML;
482
                            } else {
483
                                $content[$columnId] .= $singleElementHTML;
484
                            }
485
                        } else {
486
                            unset($rowArr[$rKey]);
487
                        }
488
                    }
489
                    $content[$columnId] .= '</div>';
490
                    if ($columnId === 'unused') {
491
                        if (empty($unusedElementsMessage)) {
492
                            $unusedElementsMessage = GeneralUtility::makeInstance(
493
                                FlashMessage::class,
494
                                $this->getLanguageService()->getLL('staleUnusedElementsWarning'),
495
                                $this->getLanguageService()->getLL('staleUnusedElementsWarningTitle'),
496
                                FlashMessage::WARNING
497
                            );
498
                            $service = GeneralUtility::makeInstance(FlashMessageService::class);
499
                            $queue = $service->getMessageQueueByIdentifier();
500
                            $queue->addMessage($unusedElementsMessage);
501
                        }
502
                        $colTitle = $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:unusedColPos');
503
                        $editParam = '';
504
                    } else {
505
                        $colTitle = '';
506
                        foreach ($tcaItems as $item) {
507
                            if ($item[1] == $columnId) {
508
                                $colTitle = $this->getLanguageService()->sL($item[0]);
509
                            }
510
                        }
511
                        if (empty($colTitle)) {
512
                            $colTitle = BackendUtility::getProcessedValue('tt_content', 'colPos', (string)$columnId) ?? '';
513
                        }
514
                        $editParam = $this->doEdit && !empty($rowArr)
515
                            ? '&edit[tt_content][' . $editUidList . ']=edit&recTitle=' . rawurlencode(BackendUtility::getRecordTitle('pages', $this->pageRecord, true))
516
                            : '';
517
                    }
518
                    $head[$columnId] .= $this->tt_content_drawColHeader($colTitle, $editParam);
519
                }
520
            }
521
            // For each column, fit the rendered content into a table cell:
522
            $out = '';
523
            if ($this->tt_contentConfig['languageMode']) {
524
                // in language mode process the content elements, but only fill $languageColumn. output will be generated later
525
                $sortedLanguageColumn = [];
526
                foreach ($cList as $columnId) {
527
                    if (GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnId) || $columnId === 'unused') {
528
                        $languageColumn[$columnId][$lP] = $head[$columnId] . $content[$columnId];
529
530
                        // We sort $languageColumn again according to $cList as it may contain data already from above.
531
                        $sortedLanguageColumn[$columnId] = $languageColumn[$columnId];
532
                    }
533
                }
534
                if (!empty($languageColumn['unused'])) {
535
                    $sortedLanguageColumn['unused'] = $languageColumn['unused'];
536
                }
537
                $languageColumn = $sortedLanguageColumn;
538
            } else {
539
                // GRID VIEW:
540
                $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">';
541
                // Add colgroups
542
                $colCount = (int)$backendLayout['__config']['backend_layout.']['colCount'];
543
                $rowCount = (int)$backendLayout['__config']['backend_layout.']['rowCount'];
544
                $colSpan = 0;
545
                $rowSpan = 0;
546
                $grid .= '<colgroup>';
547
                for ($i = 0; $i < $colCount; $i++) {
548
                    $grid .= '<col />';
549
                }
550
                $grid .= '</colgroup>';
551
552
                // Check how to handle restricted columns
553
                $hideRestrictedCols = (bool)(BackendUtility::getPagesTSconfig($id)['mod.']['web_layout.']['hideRestrictedCols'] ?? false);
554
555
                // Cycle through rows
556
                for ($row = 1; $row <= $rowCount; $row++) {
557
                    $rowConfig = $backendLayout['__config']['backend_layout.']['rows.'][$row . '.'];
558
                    if (!isset($rowConfig)) {
559
                        continue;
560
                    }
561
                    $grid .= '<tr>';
562
                    for ($col = 1; $col <= $colCount; $col++) {
563
                        $columnConfig = $rowConfig['columns.'][$col . '.'];
564
                        if (!isset($columnConfig)) {
565
                            continue;
566
                        }
567
                        // Which tt_content colPos should be displayed inside this cell
568
                        $columnKey = (int)$columnConfig['colPos'];
569
                        // Render the grid cell
570
                        $colSpan = (int)$columnConfig['colspan'];
571
                        $rowSpan = (int)$columnConfig['rowspan'];
572
                        $grid .= '<td valign="top"' .
573
                            ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
574
                            ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
575
                            ' 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 .
576
                            ((!isset($columnConfig['colPos']) || $columnConfig['colPos'] === '') ? ' t3-grid-cell-unassigned' : '') .
577
                            ((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') : '') .
578
                            ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') .
579
                            ($rowSpan > 0 ? ' t3-gridCell-height' . $rowSpan : '') . '">';
580
581
                        // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
582
                        // If not, a new header without any buttons will be generated.
583
                        if (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && $head[$columnKey]
584
                            && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
585
                        ) {
586
                            $grid .= $head[$columnKey];
587
                            $grid .= $content[$columnKey];
588
                        } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
589
                            && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
590
                        ) {
591
                            if (!$hideRestrictedCols) {
592
                                $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('noAccess'));
593
                            }
594
                        } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== ''
595
                            && !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])
596
                        ) {
597
                            if (!$hideRestrictedCols) {
598
                                $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name']) .
599
                                  ' (' . $this->getLanguageService()->getLL('noAccess') . ')');
600
                            }
601
                        } elseif (isset($columnConfig['name']) && $columnConfig['name'] !== '') {
602
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name'])
603
                                . ' (' . $this->getLanguageService()->getLL('notAssigned') . ')');
604
                        } else {
605
                            $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('notAssigned'));
606
                        }
607
608
                        $grid .= '</td>';
609
                    }
610
                    $grid .= '</tr>';
611
                }
612
                if (!empty($content['unused'])) {
613
                    $grid .= '<tr>';
614
                    // Which tt_content colPos should be displayed inside this cell
615
                    $columnKey = 'unused';
616
                    // Render the grid cell
617
                    $colSpan = (int)$backendLayout['__config']['backend_layout.']['colCount'];
618
                    $grid .= '<td valign="top"' .
619
                        ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') .
620
                        ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') .
621
                        ' 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 .
622
                        ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') . '">';
623
624
                    // Draw the pre-generated header with edit and new buttons if a colPos is assigned.
625
                    // If not, a new header without any buttons will be generated.
626
                    $grid .= $head[$columnKey] . $content[$columnKey];
627
                    $grid .= '</td></tr>';
628
                }
629
                $out .= $grid . '</table></div>';
630
            }
631
        }
632
        // If language mode, then make another presentation:
633
        // Notice that THIS presentation will override the value of $out!
634
        // But it needs the code above to execute since $languageColumn is filled with content we need!
635
        if ($this->tt_contentConfig['languageMode']) {
636
            return $this->generateLanguageView($languageIds, $defaultLanguageElementsByColumn, $languageColumn, $defLangBinding);
637
        }
638
        return $out;
639
    }
640
641
    /**
642
     * Shows the content elements of the selected languages in each column.
643
     * @param array $languageIds languages to render
644
     * @param array $defaultLanguageElementsByColumn
645
     * @param array $languageColumn
646
     * @param array $defLangBinding
647
     * @return string the compiled content
648
     */
649
    protected function generateLanguageView(
650
        array $languageIds,
651
        array $defaultLanguageElementsByColumn,
652
        array $languageColumn,
653
        array $defLangBinding
654
    ): string {
655
        // Get language selector:
656
        $languageSelector = $this->languageSelector($this->id);
657
        // Reset out - we will make new content here:
658
        $out = '';
659
        // Traverse languages found on the page and build up the table displaying them side by side:
660
        $cCont = [];
661
        $sCont = [];
662
        foreach ($languageIds as $languageId) {
663
            $languageMode = '';
664
            $labelClass = 'info';
665
            // Header:
666
            $languageId = (int)$languageId;
667
            // Determine language mode
668
            if ($languageId > 0 && isset($this->languageHasTranslationsCache[$languageId]['mode'])) {
669
                switch ($this->languageHasTranslationsCache[$languageId]['mode']) {
670
                    case 'mixed':
671
                        $languageMode = $this->getLanguageService()->getLL('languageModeMixed');
672
                        $labelClass = 'danger';
673
                        break;
674
                    case 'connected':
675
                        $languageMode = $this->getLanguageService()->getLL('languageModeConnected');
676
                        break;
677
                    case 'free':
678
                        $languageMode = $this->getLanguageService()->getLL('languageModeFree');
679
                        break;
680
                    default:
681
                        // we'll let opcode optimize this intentionally empty case
682
                }
683
            }
684
            $columnAttributes = [
685
                'valign' => 'top',
686
                'class' => 't3-page-column t3-page-column-lang-name',
687
                'data-language-uid' => (string)$languageId,
688
                'data-language-title' => $this->siteLanguages[$languageId]->getTitle(),
689
                'data-flag-identifier' => $this->siteLanguages[$languageId]->getFlagIdentifier(),
690
            ];
691
692
            $cCont[$languageId] = '
693
					<td ' . GeneralUtility::implodeAttributes($columnAttributes, true) . '>
694
						<h2>' . htmlspecialchars($this->tt_contentConfig['languageCols'][$languageId]) . '</h2>
695
						' . ($languageMode !== '' ? '<span class="label label-' . $labelClass . '">' . $languageMode . '</span>' : '') . '
696
					</td>';
697
698
            $editLink = '';
699
            $recordIcon = '';
700
            $viewLink = '';
701
            // "View page" icon is added:
702
            if (!VersionState::cast($this->pageinfo['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
703
                $attributes = PreviewUriBuilder::create($this->id)
704
                    ->withRootLine(BackendUtility::BEgetRootLine($this->id))
705
                    ->withAdditionalQueryParameters('&L=' . $languageId)
706
                    ->serializeDispatcherAttributes();
707
                $viewLink = '<a href="#" class="btn btn-default btn-sm" ' . $attributes . ' 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>';
708
            }
709
            // Language overlay page header:
710
            if ($languageId) {
711
                $pageLocalizationRecord = BackendUtility::getRecordLocalization('pages', $this->id, $languageId);
712
                if (is_array($pageLocalizationRecord)) {
713
                    $pageLocalizationRecord = reset($pageLocalizationRecord);
714
                }
715
                BackendUtility::workspaceOL('pages', $pageLocalizationRecord);
716
                $recordIcon = BackendUtility::wrapClickMenuOnIcon(
717
                    $this->iconFactory->getIconForRecord('pages', $pageLocalizationRecord, Icon::SIZE_SMALL)->render(),
718
                    'pages',
719
                    $pageLocalizationRecord['uid']
720
                );
721
                $urlParameters = [
722
                    'edit' => [
723
                        'pages' => [
724
                            $pageLocalizationRecord['uid'] => 'edit',
725
                        ],
726
                    ],
727
                    // Disallow manual adjustment of the language field for pages
728
                    'overrideVals' => [
729
                        'pages' => [
730
                            'sys_language_uid' => $languageId,
731
                        ],
732
                    ],
733
                    'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
734
                ];
735
                $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
736
                if ($this->getBackendUser()->check('tables_modify', 'pages')) {
737
                    $editLink = '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
738
                        . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">'
739
                        . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
740
                }
741
742
                $defaultLanguageElements = [];
743
                array_walk($defaultLanguageElementsByColumn, static function (array $columnContent) use (&$defaultLanguageElements) {
744
                    $defaultLanguageElements = array_merge($defaultLanguageElements, $columnContent);
745
                });
746
747
                $localizationButtons = [];
748
                $localizationButtons[] = $this->newLanguageButton(
749
                    $this->getNonTranslatedTTcontentUids($defaultLanguageElements, $this->id, $languageId),
750
                    $languageId
751
                );
752
753
                $languageLabel =
754
                    '<div class="btn-group">'
755
                    . $viewLink
756
                    . $editLink
757
                    . (!empty($localizationButtons) ? implode(LF, $localizationButtons) : '')
758
                    . '</div>'
759
                    . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($pageLocalizationRecord['title'], 20))
760
                ;
761
            } else {
762
                if ($this->getBackendUser()->checkLanguageAccess(0)) {
763
                    $recordIcon = BackendUtility::wrapClickMenuOnIcon(
764
                        $this->iconFactory->getIconForRecord('pages', $this->pageRecord, Icon::SIZE_SMALL)->render(),
765
                        'pages',
766
                        $this->id
767
                    );
768
                    $urlParameters = [
769
                        'edit' => [
770
                            'pages' => [
771
                                $this->id => 'edit',
772
                            ],
773
                        ],
774
                        'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
775
                    ];
776
                    $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
777
                    if ($this->getBackendUser()->check('tables_modify', 'pages')) {
778
                        $editLink = '<a href="' . htmlspecialchars($url) . '" class="btn btn-default btn-sm"'
779
                            . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">'
780
                            . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
781
                    }
782
                }
783
784
                $languageLabel =
785
                    '<div class="btn-group">'
786
                    . $viewLink
787
                    . $editLink
788
                    . '</div>'
789
                    . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($this->pageRecord['title'], 20));
790
            }
791
            $sCont[$languageId] = '
792
					<td class="t3-page-column t3-page-lang-label nowrap">' . $languageLabel . '</td>';
793
        }
794
        // Add headers:
795
        $out .= '<tr>' . implode('', $cCont) . '</tr>';
796
        $out .= '<tr>' . implode('', $sCont) . '</tr>';
797
        unset($cCont, $sCont);
798
799
        // Traverse previously built content for the columns:
800
        foreach ($languageColumn as $cKey => $cCont) {
801
            $out .= '<tr>';
802
            foreach ($cCont as $languageId => $columnContent) {
803
                $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>';
804
            }
805
            $out .= '</tr>';
806
            if ($this->defLangBinding && !empty($defLangBinding[$cKey])) {
807
                $maxItemsCount = max(array_map('count', $defLangBinding[$cKey]));
808
                for ($i = 0; $i < $maxItemsCount; $i++) {
809
                    $defUid = $defaultLanguageElementsByColumn[$cKey][$i] ?? 0;
810
                    $cCont = [];
811
                    foreach ($languageIds as $languageId) {
812
                        if ($languageId > 0
813
                            && is_array($defLangBinding[$cKey][$languageId])
814
                            && !$this->checkIfTranslationsExistInLanguage($defaultLanguageElementsByColumn[$cKey], $languageId)
815
                            && count($defLangBinding[$cKey][$languageId]) > $i
816
                        ) {
817
                            $slice = array_slice($defLangBinding[$cKey][$languageId], $i, 1);
818
                            $element = $slice[0] ?? '';
819
                        } else {
820
                            $element = $defLangBinding[$cKey][$languageId][$defUid] ?? '';
821
                        }
822
                        $cCont[] = $element;
823
                    }
824
                    $out .= '
825
                        <tr>
826
							<td valign="top" class="t3-grid-cell" data-colpos="' . $cKey . '">' . implode('</td>
827
                            <td valign="top" class="t3-grid-cell" data-colpos="' . $cKey . '">', $cCont) . '</td>
828
						</tr>';
829
                }
830
            }
831
        }
832
        // Finally, wrap it all in a table and add the language selector on top of it:
833
        return $languageSelector . '
834
                <div class="t3-grid-container">
835
                    <table cellpadding="0" cellspacing="0" class="t3-page-columns t3-grid-table t3js-page-columns">
836
						' . $out . '
837
                    </table>
838
				</div>';
839
    }
840
841
    /**
842
     * Gets content records per column.
843
     * This is required for correct workspace overlays.
844
     *
845
     * @param string $table Name of table storing content records, by default tt_content
846
     * @param int $id Page Id to be used (not used at all, but part of the API, see $this->pidSelect)
847
     * @param array $columns colPos values to be considered to be shown
848
     * @param string $additionalWhereClause Additional where clause for database select
849
     * @return array Associative array for each column (colPos)
850
     */
851
    protected function getContentRecordsPerColumn($table, $id, array $columns, $additionalWhereClause = '')
852
    {
853
        $contentRecordsPerColumn = array_fill_keys($columns, []);
854
        $columns = array_flip($columns);
855
        $queryBuilder = $this->getQueryBuilder(
856
            $table,
857
            $id,
858
            [
859
                $additionalWhereClause,
860
            ]
861
        );
862
863
        // Traverse any selected elements and render their display code:
864
        /** @var Statement $result */
865
        $result = $queryBuilder->execute();
866
        $results = $this->getResult($result);
867
        $unused = [];
868
        $hookArray = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['record_is_used'] ?? [];
869
        foreach ($results as $record) {
870
            $used = isset($columns[$record['colPos']]);
871
            foreach ($hookArray as $_funcRef) {
872
                $_params = ['columns' => $columns, 'record' => $record, 'used' => $used];
873
                $used = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
874
            }
875
            if ($used) {
876
                $columnValue = (string)$record['colPos'];
877
                $contentRecordsPerColumn[$columnValue][] = $record;
878
            } else {
879
                $unused[] = $record;
880
            }
881
        }
882
        if (!empty($unused)) {
883
            $contentRecordsPerColumn['unused'] = $unused;
884
        }
885
        return $contentRecordsPerColumn;
886
    }
887
888
    /**
889
     * Draw header for a content element column:
890
     *
891
     * @param string $colName Column name
892
     * @param string $editParams Edit params (Syntax: &edit[...] for FormEngine)
893
     * @return string HTML table
894
     */
895
    public function tt_content_drawColHeader($colName, $editParams = '')
896
    {
897
        $icons = '';
898
        // Edit whole of column:
899
        if ($editParams && $this->hasContentModificationAndAccessPermissions() && $this->getBackendUser()->checkLanguageAccess(0)) {
900
            $link = $this->uriBuilder->buildUriFromRoute('record_edit') . $editParams . '&returnUrl=' . rawurlencode($GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri());
901
            $icons = '<a href="' . htmlspecialchars($link) . '"  title="'
902
                . htmlspecialchars($this->getLanguageService()->getLL('editColumn')) . '">'
903
                . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
904
            $icons = '<div class="t3-page-column-header-icons">' . $icons . '</div>';
905
        }
906
        return '<div class="t3-page-column-header">
907
					' . $icons . '
908
					<div class="t3-page-column-header-label">' . htmlspecialchars($colName) . '</div>
909
				</div>';
910
    }
911
912
    /**
913
     * Draw the footer for a single tt_content element
914
     *
915
     * @param array $row Record array
916
     * @return string HTML of the footer
917
     * @throws \UnexpectedValueException
918
     */
919
    protected function tt_content_drawFooter(array $row)
920
    {
921
        $content = '';
922
        // Get processed values:
923
        $info = [];
924
        $this->getProcessedValue('tt_content', 'starttime,endtime,fe_group,space_before_class,space_after_class', $row, $info);
925
926
        // Content element annotation
927
        if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']) && !empty($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']])) {
928
            $info[] = htmlspecialchars($row[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']]);
929
        }
930
931
        // Call drawFooter hooks
932
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawFooter'] ?? [] as $className) {
933
            $hookObject = GeneralUtility::makeInstance($className);
934
            if (!$hookObject instanceof PageLayoutViewDrawFooterHookInterface) {
935
                throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawFooterHookInterface::class, 1404378171);
936
            }
937
            $hookObject->preProcess($this, $info, $row);
938
        }
939
940
        // Display info from records fields:
941
        if (!empty($info)) {
942
            $content = '<div class="t3-page-ce-info">
943
				' . implode('<br>', $info) . '
944
				</div>';
945
        }
946
        if (!empty($content)) {
947
            $content = '<div class="t3-page-ce-footer">' . $content . '</div>';
948
        }
949
        return $content;
950
    }
951
952
    /**
953
     * Draw the header for a single tt_content element
954
     *
955
     * @param array $row Record array
956
     * @param int $space Amount of pixel space above the header. UNUSED
957
     * @param bool $disableMoveAndNewButtons If set the buttons for creating new elements and moving up and down are not shown.
958
     * @param bool $langMode If set, we are in language mode and flags will be shown for languages
959
     * @param bool $dragDropEnabled If set the move button must be hidden
960
     * @return string HTML table with the record header.
961
     */
962
    public function tt_content_drawHeader($row, $space = 0, $disableMoveAndNewButtons = false, $langMode = false, $dragDropEnabled = false)
963
    {
964
        $backendUser = $this->getBackendUser();
965
        $out = '';
966
        // Render control panel for the element
967
        if ($backendUser->recordEditAccessInternals('tt_content', $row) && $this->isContentEditable($row['sys_language_uid'])) {
968
            // Edit content element:
969
            $urlParameters = [
970
                'edit' => [
971
                    'tt_content' => [
972
                        $row['uid'] => 'edit',
973
                    ],
974
                ],
975
                'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri() . '#element-tt_content-' . $row['uid'],
976
            ];
977
            $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters) . '#element-tt_content-' . $row['uid'];
978
979
            $out .= '<a class="btn btn-default" href="' . htmlspecialchars($url)
980
                . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('edit'))
981
                . '">' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
982
            // Hide element:
983
            $hiddenField = $GLOBALS['TCA']['tt_content']['ctrl']['enablecolumns']['disabled'];
984
            if ($hiddenField && $GLOBALS['TCA']['tt_content']['columns'][$hiddenField]
985
                && (!$GLOBALS['TCA']['tt_content']['columns'][$hiddenField]['exclude']
986
                    || $backendUser->check('non_exclude_fields', 'tt_content:' . $hiddenField))
987
            ) {
988
                if ($row[$hiddenField]) {
989
                    $value = 0;
990
                    $label = 'unHide';
991
                } else {
992
                    $value = 1;
993
                    $label = 'hide';
994
                }
995
                $params = '&data[tt_content][' . ($row['_ORIG_uid'] ?: $row['uid'])
996
                    . '][' . $hiddenField . ']=' . $value;
997
                $out .= '<a class="btn btn-default" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
998
                    . '#element-tt_content-' . $row['uid'] . '" title="' . htmlspecialchars($this->getLanguageService()->getLL($label)) . '">'
999
                    . $this->iconFactory->getIcon('actions-edit-' . strtolower($label), Icon::SIZE_SMALL)->render() . '</a>';
1000
            }
1001
            // Delete
1002
            $disableDelete = (bool)\trim(
1003
                $backendUser->getTSConfig()['options.']['disableDelete.']['tt_content']
1004
                ?? $backendUser->getTSConfig()['options.']['disableDelete']
1005
                ?? '0'
1006
            );
1007
            if (!$disableDelete) {
1008
                $params = '&cmd[tt_content][' . $row['uid'] . '][delete]=1';
1009
                $refCountMsg = BackendUtility::referenceCount(
1010
                    'tt_content',
1011
                    $row['uid'],
1012
                    ' ' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord'),
1013
                    (string)$this->getReferenceCount('tt_content', $row['uid'])
1014
                ) . BackendUtility::translationCount(
1015
                    'tt_content',
1016
                    $row['uid'],
1017
                    ' ' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord')
1018
                );
1019
                $confirm = $this->getLanguageService()->getLL('deleteWarning')
1020
                    . $refCountMsg;
1021
                $out .= '<a class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params)) . '"'
1022
                    . ' data-severity="warning"'
1023
                    . ' data-title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:label.confirm.delete_record.title')) . '"'
1024
                    . ' data-bs-content="' . htmlspecialchars($confirm) . '" '
1025
                    . ' data-button-close-text="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel')) . '"'
1026
                    . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('deleteItem')) . '">'
1027
                    . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
1028
                if ($out && $this->hasContentModificationAndAccessPermissions()) {
1029
                    $out = '<div class="btn-group btn-group-sm" role="group">' . $out . '</div>';
1030
                } else {
1031
                    $out = '';
1032
                }
1033
            }
1034
            if (!$disableMoveAndNewButtons) {
1035
                $moveButtonContent = '';
1036
                $displayMoveButtons = false;
1037
                // Move element up:
1038
                if ($this->tt_contentData['prev'][$row['uid']]) {
1039
                    $params = '&cmd[tt_content][' . $row['uid'] . '][move]=' . $this->tt_contentData['prev'][$row['uid']];
1040
                    $moveButtonContent .= '<a class="btn btn-default" href="'
1041
                        . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1042
                        . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('moveUp')) . '">'
1043
                        . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . '</a>';
1044
                    if (!$dragDropEnabled) {
1045
                        $displayMoveButtons = true;
1046
                    }
1047
                } else {
1048
                    $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
1049
                }
1050
                // Move element down:
1051
                if ($this->tt_contentData['next'][$row['uid']]) {
1052
                    $params = '&cmd[tt_content][' . $row['uid'] . '][move]= ' . $this->tt_contentData['next'][$row['uid']];
1053
                    $moveButtonContent .= '<a class="btn btn-default" href="'
1054
                        . htmlspecialchars(BackendUtility::getLinkToDataHandlerAction($params))
1055
                        . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('moveDown')) . '">'
1056
                        . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . '</a>';
1057
                    if (!$dragDropEnabled) {
1058
                        $displayMoveButtons = true;
1059
                    }
1060
                } else {
1061
                    $moveButtonContent .= '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
1062
                }
1063
                if ($displayMoveButtons) {
1064
                    $out .= '<div class="btn-group btn-group-sm" role="group">' . $moveButtonContent . '</div>';
1065
                }
1066
            }
1067
        }
1068
        $allowDragAndDrop = $this->isDragAndDropAllowed($row);
1069
        $additionalIcons = [];
1070
        $additionalIcons[] = $this->getIcon('tt_content', $row) . ' ';
1071
        if ($langMode && isset($this->siteLanguages[(int)$row['sys_language_uid']])) {
1072
            $additionalIcons[] = $this->renderLanguageFlag($this->siteLanguages[(int)$row['sys_language_uid']]);
1073
        }
1074
        // Get record locking status:
1075
        if ($lockInfo = BackendUtility::isRecordLocked('tt_content', $row['uid'])) {
1076
            $additionalIcons[] = '<a href="#" data-bs-toggle="tooltip" title="' . htmlspecialchars($lockInfo['msg']) . '">'
1077
                . $this->iconFactory->getIcon('warning-in-use', Icon::SIZE_SMALL)->render() . '</a>';
1078
        }
1079
        // Call stats information hook
1080
        $_params = ['tt_content', $row['uid'], &$row];
1081
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'] ?? [] as $_funcRef) {
1082
            $additionalIcons[] = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1083
        }
1084
1085
        // Wrap the whole header
1086
        // NOTE: end-tag for <div class="t3-page-ce-body"> is in getTable_tt_content()
1087
        return '<div class="t3-page-ce-header ' . ($allowDragAndDrop ? 't3-page-ce-header-draggable t3js-page-ce-draghandle' : '') . '">
1088
					<div class="t3-page-ce-header-icons-left">' . implode('', $additionalIcons) . '</div>
1089
					<div class="t3-page-ce-header-icons-right">' . ($out ? '<div class="btn-toolbar">' . $out . '</div>' : '') . '</div>
1090
				</div>
1091
				<div class="t3-page-ce-body">';
1092
    }
1093
1094
    /**
1095
     * Gets the number of records referencing the record with the UID $uid in
1096
     * the table $tableName.
1097
     *
1098
     * @param string $tableName
1099
     * @param int $uid
1100
     * @return int The number of references to record $uid in table
1101
     */
1102
    protected function getReferenceCount(string $tableName, int $uid): int
1103
    {
1104
        if (!isset($this->referenceCount[$tableName][$uid])) {
1105
            $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
1106
            $numberOfReferences = $referenceIndex->getNumberOfReferencedRecords($tableName, $uid);
1107
            $this->referenceCount[$tableName][$uid] = $numberOfReferences;
1108
        }
1109
        return $this->referenceCount[$tableName][$uid];
1110
    }
1111
1112
    /**
1113
     * Determine whether Drag & Drop should be allowed
1114
     *
1115
     * @param array $row
1116
     * @return bool
1117
     */
1118
    protected function isDragAndDropAllowed(array $row)
1119
    {
1120
        if ((int)$row['l18n_parent'] === 0 &&
1121
            (
1122
                $this->getBackendUser()->isAdmin()
1123
                || ((int)$row['editlock'] === 0 && (int)$this->pageinfo['editlock'] === 0)
1124
                && $this->hasContentModificationAndAccessPermissions()
1125
                && $this->getBackendUser()->checkAuthMode('tt_content', 'CType', $row['CType'], $GLOBALS['TYPO3_CONF_VARS']['BE']['explicitADmode'])
1126
            )
1127
        ) {
1128
            return true;
1129
        }
1130
        return false;
1131
    }
1132
1133
    /**
1134
     * Draws the preview content for a content element
1135
     *
1136
     * @param array $row Content element
1137
     * @return string HTML
1138
     * @throws \UnexpectedValueException
1139
     */
1140
    public function tt_content_drawItem($row)
1141
    {
1142
        $out = '';
1143
        $outHeader = $this->renderContentElementHeader($row);
1144
        $drawItem = true;
1145
        // Hook: Render an own preview of a record
1146
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem'] ?? [] as $className) {
1147
            $hookObject = GeneralUtility::makeInstance($className);
1148
            if (!$hookObject instanceof PageLayoutViewDrawItemHookInterface) {
1149
                throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawItemHookInterface::class, 1218547409);
1150
            }
1151
            $hookObject->preProcess($this, $drawItem, $outHeader, $out, $row);
1152
        }
1153
1154
        // If the previous hook did not render something,
1155
        // then check if a Fluid-based preview template was defined for this CType
1156
        // and render it via Fluid. Possible option:
1157
        // mod.web_layout.tt_content.preview.media = EXT:site_mysite/Resources/Private/Templates/Preview/Media.html
1158
        if ($drawItem) {
1159
            $fluidPreview = $this->renderContentElementPreviewFromFluidTemplate($row);
1160
            if ($fluidPreview !== null) {
1161
                $out .= $fluidPreview;
1162
                $drawItem = false;
1163
            }
1164
        }
1165
1166
        // Draw preview of the item depending on its CType (if not disabled by previous hook)
1167
        if ($drawItem) {
1168
            $out .= $this->renderContentElementPreview($row);
1169
        }
1170
        $out = $outHeader . '<span class="exampleContent">' . $out . '</span>';
1171
        if ($this->isDisabled('tt_content', $row)) {
1172
            return '<span class="text-muted">' . $out . '</span>';
1173
        }
1174
        return $out;
1175
    }
1176
1177
    public function renderContentElementHeader(array $row): string
1178
    {
1179
        $outHeader = '';
1180
        // Make header:
1181
        if ($row['header']) {
1182
            $hiddenHeaderNote = '';
1183
            // If header layout is set to 'hidden', display an accordant note:
1184
            if ($row['header_layout'] == 100) {
1185
                $hiddenHeaderNote = ' <em>[' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.hidden')) . ']</em>';
1186
            }
1187
            $outHeader = $row['date']
1188
                ? htmlspecialchars($this->itemLabels['date'] . ' ' . BackendUtility::date($row['date'])) . '<br />'
1189
                : '';
1190
            $outHeader .= '<strong>' . $this->linkEditContent($this->renderText($row['header']), $row)
1191
                . $hiddenHeaderNote . '</strong><br />';
1192
        }
1193
        return $outHeader;
1194
    }
1195
1196
    public function renderContentElementPreviewFromFluidTemplate(array $row): ?string
1197
    {
1198
        $tsConfig = BackendUtility::getPagesTSconfig($row['pid'])['mod.']['web_layout.']['tt_content.']['preview.'] ?? [];
1199
        $fluidTemplateFile = '';
1200
1201
        if ($row['CType'] === 'list' && !empty($row['list_type'])
1202
            && !empty($tsConfig['list.'][$row['list_type']])
1203
        ) {
1204
            $fluidTemplateFile = $tsConfig['list.'][$row['list_type']];
1205
        } elseif (!empty($tsConfig[$row['CType']])) {
1206
            $fluidTemplateFile = $tsConfig[$row['CType']];
1207
        }
1208
1209
        if ($fluidTemplateFile) {
1210
            $fluidTemplateFile = GeneralUtility::getFileAbsFileName($fluidTemplateFile);
1211
            if ($fluidTemplateFile) {
1212
                try {
1213
                    $view = GeneralUtility::makeInstance(StandaloneView::class);
1214
                    $view->setTemplatePathAndFilename($fluidTemplateFile);
1215
                    $view->assignMultiple($row);
1216
                    if (!empty($row['pi_flexform'])) {
1217
                        $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
1218
                        $view->assign('pi_flexform_transformed', $flexFormService->convertFlexFormContentToArray($row['pi_flexform']));
1219
                    }
1220
                    return $view->render();
1221
                } catch (\Exception $e) {
1222
                    $this->logger->warning('The backend preview for content element {uid} can not be rendered using the Fluid template file "{file}"', [
0 ignored issues
show
Bug introduced by
The method warning() does not exist on null. ( Ignorable by Annotation )

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

1222
                    $this->logger->/** @scrutinizer ignore-call */ 
1223
                                   warning('The backend preview for content element {uid} can not be rendered using the Fluid template file "{file}"', [

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1223
                        'uid' => $row['uid'],
1224
                        'file' => $fluidTemplateFile,
1225
                        $e->getMessage(),
1226
                    ]);
1227
1228
                    if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] && $this->getBackendUser()->isAdmin()) {
1229
                        $view = GeneralUtility::makeInstance(StandaloneView::class);
1230
                        $view->assign('error', [
1231
                            'message' => str_replace(Environment::getProjectPath(), '', $e->getMessage()),
1232
                            'title' => 'Error while rendering FluidTemplate preview using ' . str_replace(Environment::getProjectPath(), '', $fluidTemplateFile),
1233
                        ]);
1234
                        $view->setTemplateSource('<f:be.infobox title="{error.title}" state="2">{error.message}</f:be.infobox>');
1235
                        return $view->render();
1236
                    }
1237
                }
1238
            }
1239
        }
1240
        return null;
1241
    }
1242
1243
    /**
1244
     * Renders the preview part of a content element
1245
     * @param array $row given tt_content database record
1246
     * @return string
1247
     */
1248
    public function renderContentElementPreview(array $row): string
1249
    {
1250
        $previewHtml = '';
1251
        switch ($row['CType']) {
1252
            case 'header':
1253
                if ($row['subheader']) {
1254
                    $previewHtml = $this->linkEditContent($this->renderText($row['subheader']), $row) . '<br />';
1255
                }
1256
                break;
1257
            case 'bullets':
1258
            case 'table':
1259
                if ($row['bodytext']) {
1260
                    $previewHtml = $this->linkEditContent($this->renderText($row['bodytext']), $row) . '<br />';
1261
                }
1262
                break;
1263
            case 'uploads':
1264
                if ($row['media']) {
1265
                    $previewHtml = $this->linkEditContent($this->getThumbCodeUnlinked($row, 'tt_content', 'media'), $row) . '<br />';
1266
                }
1267
                break;
1268
            case 'shortcut':
1269
                if (!empty($row['records'])) {
1270
                    $shortcutContent = [];
1271
                    $recordList = explode(',', $row['records']);
1272
                    foreach ($recordList as $recordIdentifier) {
1273
                        $split = BackendUtility::splitTable_Uid($recordIdentifier);
1274
                        $tableName = empty($split[0]) ? 'tt_content' : $split[0];
1275
                        $shortcutRecord = BackendUtility::getRecord($tableName, $split[1]);
1276
                        if (is_array($shortcutRecord)) {
1277
                            $icon = $this->iconFactory->getIconForRecord($tableName, $shortcutRecord, Icon::SIZE_SMALL)->render();
1278
                            $icon = BackendUtility::wrapClickMenuOnIcon(
1279
                                $icon,
1280
                                $tableName,
1281
                                $shortcutRecord['uid']
1282
                            );
1283
                            $shortcutContent[] = $icon
1284
                                . htmlspecialchars(BackendUtility::getRecordTitle($tableName, $shortcutRecord));
1285
                        }
1286
                    }
1287
                    $previewHtml = implode('<br />', $shortcutContent) . '<br />';
1288
                }
1289
                break;
1290
            case 'list':
1291
                $hookOut = '';
1292
                $_params = ['pObj' => &$this, 'row' => $row];
1293
                foreach (
1294
                    $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'][$row['list_type']] ??
1295
                    $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['_DEFAULT'] ??
1296
                    [] as $_funcRef
1297
                ) {
1298
                    $hookOut .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1299
                }
1300
                if ((string)$hookOut !== '') {
1301
                    $previewHtml = $hookOut;
1302
                } elseif (!empty($row['list_type'])) {
1303
                    $label = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'list_type', $row['list_type']);
1304
                    if (!empty($label)) {
1305
                        $previewHtml = $this->linkEditContent('<strong>' . htmlspecialchars($this->getLanguageService()->sL($label)) . '</strong>', $row) . '<br />';
0 ignored issues
show
Bug introduced by
The method getLanguageService() does not exist on null. ( Ignorable by Annotation )

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

1305
                        $previewHtml = $this->linkEditContent('<strong>' . htmlspecialchars($this->/** @scrutinizer ignore-call */ getLanguageService()->sL($label)) . '</strong>', $row) . '<br />';

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1306
                    } else {
1307
                        $message = sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'), $row['list_type']);
1308
                        $previewHtml = '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
1309
                    }
1310
                } else {
1311
                    $previewHtml = '<strong>' . $this->getLanguageService()->getLL('noPluginSelected') . '</strong>';
1312
                }
1313
                $previewHtml .= htmlspecialchars($this->getLanguageService()->sL(
1314
                    BackendUtility::getLabelFromItemlist('tt_content', 'pages', $row['pages'])
1315
                )) . '<br />';
1316
                break;
1317
            default:
1318
                $contentType = $this->CType_labels[$row['CType']];
1319
                if (!isset($contentType)) {
1320
                    $contentType = BackendUtility::getLabelFromItemListMerged($row['pid'], 'tt_content', 'CType', $row['CType']);
1321
                }
1322
1323
                if ($contentType) {
1324
                    $previewHtml = $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $row) . '<br />';
1325
                    if ($row['bodytext']) {
1326
                        $previewHtml .= $this->linkEditContent($this->renderText($row['bodytext']), $row) . '<br />';
1327
                    }
1328
                    if ($row['image']) {
1329
                        $previewHtml .= $this->linkEditContent($this->getThumbCodeUnlinked($row, 'tt_content', 'image'), $row) . '<br />';
1330
                    }
1331
                } else {
1332
                    $message = sprintf(
1333
                        $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'),
1334
                        $row['CType']
1335
                    );
1336
                    $previewHtml = '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
1337
                }
1338
        }
1339
        return $previewHtml;
1340
    }
1341
1342
    /**
1343
     * Filters out all tt_content uids which are already translated so only non-translated uids is left.
1344
     * Selects across columns, but within in the same PID. Columns are expect to be the same
1345
     * for translations and original but this may be a conceptual error (?)
1346
     *
1347
     * @param array $defaultLanguageUids Numeric array with uids of tt_content elements in the default language
1348
     * @param int $id The page UID from which to fetch untranslated records (unused, remains in place for compatibility)
1349
     * @param int $lP Sys language UID
1350
     * @return array Modified $defLanguageCount
1351
     */
1352
    public function getNonTranslatedTTcontentUids($defaultLanguageUids, $id, $lP)
1353
    {
1354
        if ($lP && !empty($defaultLanguageUids)) {
1355
            // Select all translations here:
1356
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1357
                ->getQueryBuilderForTable('tt_content');
1358
            $queryBuilder->getRestrictions()
1359
                ->removeAll()
1360
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1361
                ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, 0));
1362
            $queryBuilder
1363
                ->select('*')
1364
                ->from('tt_content')
1365
                ->where(
1366
                    $queryBuilder->expr()->eq(
1367
                        'sys_language_uid',
1368
                        $queryBuilder->createNamedParameter($lP, \PDO::PARAM_INT)
1369
                    ),
1370
                    $queryBuilder->expr()->in(
1371
                        'l18n_parent',
1372
                        $queryBuilder->createNamedParameter($defaultLanguageUids, Connection::PARAM_INT_ARRAY)
1373
                    )
1374
                );
1375
1376
            $result = $queryBuilder->execute();
1377
1378
            // Flip uids:
1379
            $defaultLanguageUids = array_flip($defaultLanguageUids);
1380
            // Traverse any selected elements and unset original UID if any:
1381
            while ($row = $result->fetchAssociative()) {
1382
                BackendUtility::workspaceOL('tt_content', $row);
1383
                unset($defaultLanguageUids[$row['l18n_parent']]);
1384
            }
1385
            // Flip again:
1386
            $defaultLanguageUids = array_keys($defaultLanguageUids);
1387
        }
1388
        return $defaultLanguageUids;
1389
    }
1390
1391
    /**
1392
     * Creates button which is used to create copies of records..
1393
     *
1394
     * @param array $defaultLanguageUids Numeric array with uids of tt_content elements in the default language
1395
     * @param int $lP Sys language UID
1396
     * @return string "Copy languages" button, if available.
1397
     */
1398
    public function newLanguageButton($defaultLanguageUids, $lP)
1399
    {
1400
        $lP = (int)$lP;
1401
        if (!$this->doEdit || !$lP || !$this->hasContentModificationAndAccessPermissions()) {
1402
            return '';
1403
        }
1404
        $theNewButton = '';
1405
1406
        $localizationTsConfig = BackendUtility::getPagesTSconfig($this->id)['mod.']['web_layout.']['localization.'] ?? [];
1407
        $allowCopy = (bool)($localizationTsConfig['enableCopy'] ?? true);
1408
        $allowTranslate = (bool)($localizationTsConfig['enableTranslate'] ?? true);
1409
        if (!empty($this->languageHasTranslationsCache[$lP])) {
1410
            if (isset($this->languageHasTranslationsCache[$lP]['hasStandAloneContent'])) {
1411
                $allowTranslate = false;
1412
            }
1413
            if (isset($this->languageHasTranslationsCache[$lP]['hasTranslations'])) {
1414
                $allowCopy = $allowCopy && !$this->languageHasTranslationsCache[$lP]['hasTranslations'];
1415
            }
1416
        }
1417
1418
        if (isset($this->contentElementCache[$lP]) && is_array($this->contentElementCache[$lP])) {
1419
            foreach ($this->contentElementCache[$lP] as $column => $records) {
1420
                foreach ($records as $record) {
1421
                    $key = array_search($record['l10n_source'], $defaultLanguageUids);
1422
                    if ($key !== false) {
1423
                        unset($defaultLanguageUids[$key]);
1424
                    }
1425
                }
1426
            }
1427
        }
1428
1429
        if (!empty($defaultLanguageUids)) {
1430
            $theNewButton =
1431
                '<a'
1432
                    . ' href="#"'
1433
                    . ' class="btn btn-default btn-sm t3js-localize disabled"'
1434
                    . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('newPageContent_translate')) . '"'
1435
                    . ' data-page="' . htmlspecialchars($this->getLocalizedPageTitle()) . '"'
1436
                    . ' data-has-elements="' . (int)!empty($this->contentElementCache[$lP]) . '"'
1437
                    . ' data-allow-copy="' . (int)$allowCopy . '"'
1438
                    . ' data-allow-translate="' . (int)$allowTranslate . '"'
1439
                    . ' data-table="tt_content"'
1440
                    . ' data-page-id="' . (int)GeneralUtility::_GP('id') . '"'
1441
                    . ' data-language-id="' . $lP . '"'
1442
                    . ' data-language-name="' . htmlspecialchars($this->tt_contentConfig['languageCols'][$lP]) . '"'
1443
                . '>'
1444
                . $this->iconFactory->getIcon('actions-localize', Icon::SIZE_SMALL)->render()
1445
                . ' ' . htmlspecialchars($this->getLanguageService()->getLL('newPageContent_translate'))
1446
                . '</a>';
1447
        }
1448
1449
        return $theNewButton;
1450
    }
1451
1452
    /**
1453
     * Will create a link on the input string and possibly a big button after the string which links to editing in the RTE.
1454
     * Used for content element content displayed so the user can click the content / "Edit in Rich Text Editor" button
1455
     *
1456
     * @param string $str String to link. Must be prepared for HTML output.
1457
     * @param array $row The row.
1458
     * @return string If the whole thing was editable ($this->doEdit) $str is return with link around. Otherwise just $str.
1459
     * @see getTable_tt_content()
1460
     */
1461
    public function linkEditContent($str, $row)
1462
    {
1463
        if ($this->doEdit
1464
            && $this->hasContentModificationAndAccessPermissions()
1465
            && $this->getBackendUser()->recordEditAccessInternals('tt_content', $row)
1466
        ) {
1467
            $urlParameters = [
1468
                'edit' => [
1469
                    'tt_content' => [
1470
                        $row['uid'] => 'edit',
1471
                    ],
1472
                ],
1473
                'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri() . '#element-tt_content-' . $row['uid'],
1474
            ];
1475
            $url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1476
            return '<a href="' . htmlspecialchars($url) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">' . $str . '</a>';
1477
        }
1478
        return $str;
1479
    }
1480
1481
    /**
1482
     * Make selector box for creating new translation in a language
1483
     * Displays only languages which are not yet present for the current page and
1484
     * that are not disabled with page TS.
1485
     *
1486
     * @param int $id Page id for which to create a new translation record of pages
1487
     * @return string HTML <select> element (if there were items for the box anyways...)
1488
     * @see getTable_tt_content()
1489
     */
1490
    public function languageSelector($id)
1491
    {
1492
        if (!$this->getBackendUser()->check('tables_modify', 'pages')) {
1493
            return '';
1494
        }
1495
        $id = (int)$id;
1496
1497
        // First, select all languages that are available for the current user
1498
        $availableTranslations = [];
1499
        foreach ($this->siteLanguages as $language) {
1500
            if ($language->getLanguageId() <= 0) {
1501
                continue;
1502
            }
1503
            $availableTranslations[$language->getLanguageId()] = $language->getTitle();
1504
        }
1505
1506
        // Then, subtract the languages which are already on the page:
1507
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
1508
        $queryBuilder->getRestrictions()->removeAll()
1509
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1510
            ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->getBackendUser()->workspace));
1511
        $queryBuilder->select('uid', $GLOBALS['TCA']['pages']['ctrl']['languageField'])
1512
            ->from('pages')
1513
            ->where(
1514
                $queryBuilder->expr()->eq(
1515
                    $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
1516
                    $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
1517
                )
1518
            );
1519
        $statement = $queryBuilder->execute();
1520
        while ($row = $statement->fetchAssociative()) {
1521
            unset($availableTranslations[(int)$row[$GLOBALS['TCA']['pages']['ctrl']['languageField']]]);
1522
        }
1523
        // If any languages are left, make selector:
1524
        if (!empty($availableTranslations)) {
1525
            $output = '<option value="">' . htmlspecialchars($this->getLanguageService()->getLL('new_language')) . '</option>';
1526
            foreach ($availableTranslations as $languageUid => $languageTitle) {
1527
                // Build localize command URL to DataHandler (tce_db)
1528
                // which redirects to FormEngine (record_edit)
1529
                // which, when finished editing should return back to the current page (returnUrl)
1530
                $parameters = [
1531
                    'justLocalized' => 'pages:' . $id . ':' . $languageUid,
1532
                    'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
1533
                ];
1534
                $redirectUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $parameters);
1535
                $targetUrl = BackendUtility::getLinkToDataHandlerAction(
1536
                    '&cmd[pages][' . $id . '][localize]=' . $languageUid,
1537
                    $redirectUrl
1538
                );
1539
1540
                $output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
1541
            }
1542
1543
            return '<div class="row row-cols-auto align-items-end g-3 mb-3">'
1544
                . '<div class="col">'
1545
                . '<select class="form-select" name="createNewLanguage" data-global-event="change" data-action-navigate="$value">'
1546
                . $output
1547
                . '</select></div></div>';
1548
        }
1549
        return '';
1550
    }
1551
1552
    /**
1553
     * Traverse the result pointer given, adding each record to array and setting some internal values at the same time.
1554
     *
1555
     * @param Statement|Result $result DBAL Statement or Result
1556
     * @return array The selected rows returned in this array.
1557
     */
1558
    public function getResult($result): array
1559
    {
1560
        $output = [];
1561
        // Traverse the result:
1562
        while ($row = $result->fetchAssociative()) {
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Statement::fetchAssociative() has been deprecated: Use Result::fetchAssociative() instead ( Ignorable by Annotation )

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

1562
        while ($row = /** @scrutinizer ignore-deprecated */ $result->fetchAssociative()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1563
            BackendUtility::workspaceOL('tt_content', $row, -99, true);
1564
            if ($row) {
1565
                // Add the row to the array:
1566
                $output[] = $row;
1567
            }
1568
        }
1569
        $this->generateTtContentDataArray($output);
1570
        // Return selected records
1571
        return $output;
1572
    }
1573
1574
    /********************************
1575
     *
1576
     * Various helper functions
1577
     *
1578
     ********************************/
1579
1580
    /**
1581
     * Generates the data for previous and next elements which is needed for movements.
1582
     *
1583
     * @param array $rowArray
1584
     */
1585
    protected function generateTtContentDataArray(array $rowArray)
1586
    {
1587
        if (empty($this->tt_contentData)) {
1588
            $this->tt_contentData = [
1589
                'next' => [],
1590
                'prev' => [],
1591
            ];
1592
        }
1593
        foreach ($rowArray as $key => $value) {
1594
            // Create information for next and previous content elements
1595
            if (isset($rowArray[$key - 1])) {
1596
                if (isset($rowArray[$key - 2])) {
1597
                    $this->tt_contentData['prev'][$value['uid']] = -$rowArray[$key - 2]['uid'];
1598
                } else {
1599
                    $this->tt_contentData['prev'][$value['uid']] = $value['pid'];
1600
                }
1601
                $this->tt_contentData['next'][$rowArray[$key - 1]['uid']] = -$value['uid'];
1602
            }
1603
        }
1604
    }
1605
1606
    /**
1607
     * Processing of larger amounts of text (usually from RTE/bodytext fields) with word wrapping etc.
1608
     *
1609
     * @param string $input Input string
1610
     * @return string Output string
1611
     */
1612
    public function renderText($input)
1613
    {
1614
        $input = strip_tags($input);
1615
        $input = GeneralUtility::fixed_lgd_cs($input, 1500);
1616
        return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
1617
    }
1618
1619
    /**
1620
     * Creates the icon image tag for record from table and wraps it in a link which will trigger the click menu.
1621
     *
1622
     * @param string $table Table name
1623
     * @param array $row Record array
1624
     * @return string HTML for the icon
1625
     */
1626
    public function getIcon($table, $row)
1627
    {
1628
        // Initialization
1629
        $toolTip = BackendUtility::getRecordToolTip($row, $table);
1630
        $icon = '<span ' . $toolTip . '>' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
1631
        // The icon with link
1632
        if ($this->getBackendUser()->recordEditAccessInternals($table, $row)) {
1633
            $icon = BackendUtility::wrapClickMenuOnIcon($icon, $table, $row['uid']);
1634
        }
1635
        return $icon;
1636
    }
1637
1638
    /**
1639
     * Creates processed values for all field names in $fieldList based on values from $row array.
1640
     * The result is 'returned' through $info which is passed as a reference
1641
     *
1642
     * @param string $table Table name
1643
     * @param string $fieldList Comma separated list of fields.
1644
     * @param array $row Record from which to take values for processing.
1645
     * @param array $info Array to which the processed values are added.
1646
     */
1647
    public function getProcessedValue($table, $fieldList, array $row, array &$info)
1648
    {
1649
        // Splitting values from $fieldList
1650
        $fieldArr = explode(',', $fieldList);
1651
        // Traverse fields from $fieldList
1652
        foreach ($fieldArr as $field) {
1653
            if ($row[$field]) {
1654
                $info[] = '<strong>' . htmlspecialchars($this->itemLabels[$field]) . '</strong> '
1655
                    . htmlspecialchars(BackendUtility::getProcessedValue($table, $field, $row[$field]) ?? '');
1656
            }
1657
        }
1658
    }
1659
1660
    /**
1661
     * Returns TRUE, if the record given as parameters is NOT visible based on hidden/starttime/endtime (if available)
1662
     *
1663
     * @param string $table Tablename of table to test
1664
     * @param array $row Record row.
1665
     * @return bool Returns TRUE, if disabled.
1666
     */
1667
    public function isDisabled($table, $row)
1668
    {
1669
        $enableCols = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
1670
        return $enableCols['disabled'] && $row[$enableCols['disabled']]
1671
            || $enableCols['starttime'] && $row[$enableCols['starttime']] > $GLOBALS['EXEC_TIME']
1672
            || $enableCols['endtime'] && $row[$enableCols['endtime']] && $row[$enableCols['endtime']] < $GLOBALS['EXEC_TIME'];
1673
    }
1674
1675
    /*****************************************
1676
     *
1677
     * External renderings
1678
     *
1679
     *****************************************/
1680
1681
    /**
1682
     * Create thumbnail code for record/field but not linked
1683
     *
1684
     * @param mixed[] $row Record array
1685
     * @param string $table Table (record is from)
1686
     * @param string $field Field name for which thumbnail are to be rendered.
1687
     * @return string HTML for thumbnails, if any.
1688
     */
1689
    public function getThumbCodeUnlinked($row, $table, $field)
1690
    {
1691
        return BackendUtility::thumbCode($row, $table, $field, '', '', null, 0, '', '', false);
1692
    }
1693
1694
    /**
1695
     * Checks whether translated Content Elements exist in the desired language
1696
     * If so, deny creating new ones via the UI
1697
     *
1698
     * @param array $contentElements
1699
     * @param int $language
1700
     * @return bool
1701
     */
1702
    protected function checkIfTranslationsExistInLanguage(array $contentElements, int $language)
1703
    {
1704
        // If in default language, you may always create new entries
1705
        // Also, you may override this strict behavior via user TS Config
1706
        // If you do so, you're on your own and cannot rely on any support by the TYPO3 core
1707
        // We jump out here since we don't need to do the expensive loop operations
1708
        $allowInconsistentLanguageHandling = (bool)(BackendUtility::getPagesTSconfig($this->id)['mod.']['web_layout.']['allowInconsistentLanguageHandling'] ?? false);
1709
        if ($language === 0 || $allowInconsistentLanguageHandling) {
1710
            return false;
1711
        }
1712
        /**
1713
         * Build up caches
1714
         */
1715
        if (!isset($this->languageHasTranslationsCache[$language])) {
1716
            foreach ($contentElements as $columns) {
1717
                foreach ($columns as $contentElement) {
1718
                    if ((int)$contentElement['l18n_parent'] === 0) {
1719
                        $this->languageHasTranslationsCache[$language]['hasStandAloneContent'] = true;
1720
                        $this->languageHasTranslationsCache[$language]['mode'] = 'free';
1721
                    }
1722
                    if ((int)$contentElement['l18n_parent'] > 0) {
1723
                        $this->languageHasTranslationsCache[$language]['hasTranslations'] = true;
1724
                        $this->languageHasTranslationsCache[$language]['mode'] = 'connected';
1725
                    }
1726
                }
1727
            }
1728
            if (!isset($this->languageHasTranslationsCache[$language])) {
1729
                $this->languageHasTranslationsCache[$language]['hasTranslations'] = false;
1730
            }
1731
            // Check whether we have a mix of both
1732
            if (isset($this->languageHasTranslationsCache[$language]['hasStandAloneContent'])
1733
                && $this->languageHasTranslationsCache[$language]['hasTranslations']
1734
            ) {
1735
                $this->languageHasTranslationsCache[$language]['mode'] = 'mixed';
1736
                $siteLanguage = $this->siteLanguages[$language];
1737
                $message = GeneralUtility::makeInstance(
1738
                    FlashMessage::class,
1739
                    $this->getLanguageService()->getLL('staleTranslationWarning'),
1740
                    sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $siteLanguage->getTitle()),
1741
                    FlashMessage::WARNING
1742
                );
1743
                $service = GeneralUtility::makeInstance(FlashMessageService::class);
1744
                $queue = $service->getMessageQueueByIdentifier();
1745
                $queue->addMessage($message);
1746
            }
1747
        }
1748
1749
        return $this->languageHasTranslationsCache[$language]['hasTranslations'];
1750
    }
1751
1752
    /**
1753
     * @return BackendLayoutView
1754
     */
1755
    protected function getBackendLayoutView()
1756
    {
1757
        return GeneralUtility::makeInstance(BackendLayoutView::class);
1758
    }
1759
1760
    /**
1761
     * @return BackendUserAuthentication
1762
     */
1763
    protected function getBackendUser()
1764
    {
1765
        return $GLOBALS['BE_USER'];
1766
    }
1767
1768
    /**
1769
     * Create thumbnail code for record/field
1770
     *
1771
     * @param mixed[] $row Record array
1772
     * @param string $table Table (record is from)
1773
     * @param string $field Field name for which thumbnail are to be rendered.
1774
     * @return string HTML for thumbnails, if any.
1775
     */
1776
    public function thumbCode($row, $table, $field)
1777
    {
1778
        return BackendUtility::thumbCode($row, $table, $field);
1779
    }
1780
1781
    /**
1782
     * Returns a QueryBuilder configured to select $fields from $table where the pid is restricted.
1783
     *
1784
     * @param string $table Table name
1785
     * @param int $pageId Page id Only used to build the search constraints, getPageIdConstraint() used for restrictions
1786
     * @param string[] $additionalConstraints Additional part for where clause
1787
     * @param string[] $fields Field list to select, * for all
1788
     * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
1789
     */
1790
    public function getQueryBuilder(
1791
        string $table,
1792
        int $pageId,
1793
        array $additionalConstraints = [],
1794
        array $fields = ['*']
1795
    ): QueryBuilder {
1796
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1797
            ->getQueryBuilderForTable($table);
1798
        $queryBuilder->getRestrictions()
1799
            ->removeAll()
1800
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1801
            ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->getBackendUser()->workspace));
1802
        $queryBuilder
1803
            ->select(...$fields)
1804
            ->from($table);
1805
1806
        if (!empty($additionalConstraints)) {
1807
            $queryBuilder->andWhere(...$additionalConstraints);
1808
        }
1809
1810
        $queryBuilder = $this->prepareQueryBuilder($table, $pageId, $fields, $additionalConstraints, $queryBuilder);
1811
1812
        return $queryBuilder;
1813
    }
1814
1815
    /**
1816
     * Return the modified QueryBuilder object ($queryBuilder) which will be
1817
     * used to select the records from a table $table with pid = $this->pidList
1818
     *
1819
     * @param string $table Table name
1820
     * @param int $pageId Page id Only used to build the search constraints, $this->pidList is used for restrictions
1821
     * @param string[] $fieldList List of fields to select from the table
1822
     * @param string[] $additionalConstraints Additional part for where clause
1823
     * @param QueryBuilder $queryBuilder
1824
     * @return QueryBuilder
1825
     */
1826
    protected function prepareQueryBuilder(
1827
        string $table,
1828
        int $pageId,
1829
        array $fieldList,
1830
        array $additionalConstraints,
1831
        QueryBuilder $queryBuilder
1832
    ): QueryBuilder {
1833
        $parameters = [
1834
            'table' => $table,
1835
            'fields' => $fieldList,
1836
            'groupBy' => null,
1837
            'orderBy' => null,
1838
        ];
1839
1840
        $sortBy = (string)($GLOBALS['TCA'][$table]['ctrl']['sortby'] ?: $GLOBALS['TCA'][$table]['ctrl']['default_sortby']);
1841
        foreach (QueryHelper::parseOrderBy($sortBy) as $orderBy) {
1842
            $queryBuilder->addOrderBy($orderBy[0], $orderBy[1]);
1843
        }
1844
1845
        // Build the query constraints
1846
        $queryBuilder->andWhere(
1847
            $queryBuilder->expr()->eq(
1848
                $table . '.pid',
1849
                $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
1850
            )
1851
        );
1852
1853
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][PageLayoutView::class]['modifyQuery'] ?? [] as $className) {
1854
            $hookObject = GeneralUtility::makeInstance($className);
1855
            if (method_exists($hookObject, 'modifyQuery')) {
1856
                $hookObject->modifyQuery(
1857
                    $parameters,
1858
                    $table,
1859
                    $pageId,
1860
                    $additionalConstraints,
1861
                    $fieldList,
1862
                    $queryBuilder
1863
                );
1864
            }
1865
        }
1866
1867
        return $queryBuilder;
1868
    }
1869
1870
    /**
1871
     * Renders the language flag and language title, but only if an icon is given, otherwise just the language
1872
     *
1873
     * @param SiteLanguage $language
1874
     * @return string
1875
     */
1876
    protected function renderLanguageFlag(SiteLanguage $language)
1877
    {
1878
        $title = htmlspecialchars($language->getTitle());
1879
        if ($language->getFlagIdentifier()) {
1880
            $icon = $this->iconFactory->getIcon(
1881
                $language->getFlagIdentifier(),
1882
                Icon::SIZE_SMALL
1883
            )->render();
1884
            return '<span title="' . $title . '" class="t3js-flag">' . $icon . '</span>&nbsp;<span class="t3js-language-title">' . $title . '</span>';
1885
        }
1886
        return $title;
1887
    }
1888
1889
    /**
1890
     * Fetch the site language objects for the given $pageId and store it in $this->siteLanguages
1891
     *
1892
     * @param int $pageId
1893
     * @throws SiteNotFoundException
1894
     */
1895
    protected function resolveSiteLanguages(int $pageId)
1896
    {
1897
        try {
1898
            $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
1899
        } catch (SiteNotFoundException $e) {
1900
            $site = new NullSite();
1901
        }
1902
        $this->siteLanguages = $site->getAvailableLanguages($this->getBackendUser(), true, $pageId);
1903
    }
1904
1905
    /**
1906
     * @return string $title
1907
     */
1908
    protected function getLocalizedPageTitle(): string
1909
    {
1910
        if (($this->tt_contentConfig['sys_language_uid'] ?? 0) > 0) {
1911
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1912
                ->getQueryBuilderForTable('pages');
1913
            $queryBuilder->getRestrictions()
1914
                ->removeAll()
1915
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1916
                ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->getBackendUser()->workspace));
1917
            $localizedPage = $queryBuilder
1918
                ->select('*')
1919
                ->from('pages')
1920
                ->where(
1921
                    $queryBuilder->expr()->eq(
1922
                        $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
1923
                        $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
1924
                    ),
1925
                    $queryBuilder->expr()->eq(
1926
                        $GLOBALS['TCA']['pages']['ctrl']['languageField'],
1927
                        $queryBuilder->createNamedParameter($this->tt_contentConfig['sys_language_uid'], \PDO::PARAM_INT)
1928
                    )
1929
                )
1930
                ->setMaxResults(1)
1931
                ->execute()
1932
                ->fetchAssociative();
1933
            BackendUtility::workspaceOL('pages', $localizedPage);
1934
            return $localizedPage['title'];
1935
        }
1936
        return $this->pageinfo['title'];
1937
    }
1938
1939
    /**
1940
     * Check if page can be edited by current user
1941
     *
1942
     * @return bool
1943
     */
1944
    protected function isPageEditable()
1945
    {
1946
        if ($this->getBackendUser()->isAdmin()) {
1947
            return true;
1948
        }
1949
        return !$this->pageinfo['editlock'] && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::PAGE_EDIT);
1950
    }
1951
1952
    /**
1953
     * Check if content can be edited by current user
1954
     *
1955
     * @param int|null $languageId
1956
     * @return bool
1957
     */
1958
    protected function isContentEditable(?int $languageId = null)
1959
    {
1960
        if ($this->getBackendUser()->isAdmin()) {
1961
            return true;
1962
        }
1963
        return !$this->pageinfo['editlock']
1964
            && $this->hasContentModificationAndAccessPermissions()
1965
            && ($languageId === null || $this->getBackendUser()->checkLanguageAccess($languageId));
1966
    }
1967
1968
    /**
1969
     * Check if current user has modification and access permissions for content set
1970
     *
1971
     * @return bool
1972
     */
1973
    protected function hasContentModificationAndAccessPermissions(): bool
1974
    {
1975
        return $this->getBackendUser()->check('tables_modify', 'tt_content')
1976
            && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT);
1977
    }
1978
1979
    /**
1980
     * Returns the language service
1981
     * @return LanguageService
1982
     */
1983
    protected function getLanguageService()
1984
    {
1985
        return $GLOBALS['LANG'];
1986
    }
1987
}
1988