Passed
Push — master ( 844f02...dd001f )
by
unknown
23:08 queued 09:54
created

getLanguageColumnsWithDefLangBindingForPageLayoutContext()   B

Complexity

Conditions 11
Paths 11

Size

Total Lines 66
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 38
dl 0
loc 66
rs 7.3166
c 0
b 0
f 0
cc 11
nc 11
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Backend\View\Drawing;
19
20
use Psr\Log\LoggerAwareTrait;
21
use TYPO3\CMS\Backend\Clipboard\Clipboard;
22
use TYPO3\CMS\Backend\Utility\BackendUtility;
23
use TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher;
24
use TYPO3\CMS\Backend\View\BackendLayout\Grid\Grid;
25
use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumn;
26
use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem;
27
use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridRow;
28
use TYPO3\CMS\Backend\View\BackendLayout\Grid\LanguageColumn;
29
use TYPO3\CMS\Backend\View\BackendLayout\RecordRememberer;
30
use TYPO3\CMS\Backend\View\PageLayoutContext;
31
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
32
use TYPO3\CMS\Core\Imaging\Icon;
33
use TYPO3\CMS\Core\Imaging\IconFactory;
34
use TYPO3\CMS\Core\Localization\LanguageService;
35
use TYPO3\CMS\Core\Messaging\FlashMessage;
36
use TYPO3\CMS\Core\Messaging\FlashMessageService;
37
use TYPO3\CMS\Core\Page\PageRenderer;
38
use TYPO3\CMS\Core\Type\Bitmask\Permission;
39
use TYPO3\CMS\Core\Utility\GeneralUtility;
40
use TYPO3\CMS\Extbase\Mvc\Request;
41
use TYPO3\CMS\Extbase\Object\ObjectManager;
42
use TYPO3\CMS\Fluid\View\TemplateView;
43
44
/**
45
 * Backend Layout Renderer
46
 *
47
 * Draws a page layout - essentially, behaves as a wrapper for a view
48
 * which renders the Resources/Private/PageLayout/PageLayout template
49
 * with necessary assigned template variables.
50
 *
51
 * - Initializes the clipboard used in the page layout
52
 * - Inserts an encoded paste icon as JS which is made visible when clipboard elements are registered
53
 *
54
 * @internal this is experimental and subject to change in TYPO3 v10 / v11
55
 */
56
class BackendLayoutRenderer
57
{
58
    use LoggerAwareTrait;
59
60
    /**
61
     * @var IconFactory
62
     */
63
    protected $iconFactory;
64
65
    /**
66
     * @var PageLayoutContext
67
     */
68
    protected $context;
69
70
    /**
71
     * @var ContentFetcher
72
     */
73
    protected $contentFetcher;
74
75
    /**
76
     * @var Clipboard
77
     */
78
    protected $clipboard;
79
80
    /**
81
     * @var TemplateView
82
     */
83
    protected $view;
84
85
    public function __construct(PageLayoutContext $context)
86
    {
87
        $this->context = $context;
88
        $this->contentFetcher = GeneralUtility::makeInstance(ContentFetcher::class, $context);
89
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
90
        $this->initializeClipboard();
91
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
92
        $request = $objectManager->get(Request::class);
93
        $this->view = GeneralUtility::makeInstance(TemplateView::class);
94
        $this->view->getRenderingContext()->setRequest($request);
95
        $this->view->getRenderingContext()->getTemplatePaths()->fillDefaultsByPackageName('backend');
96
        $this->view->getRenderingContext()->setControllerName('PageLayout');
97
        $this->view->assign('context', $context);
98
    }
99
100
    public function getGridForPageLayoutContext(PageLayoutContext $context): Grid
101
    {
102
        $grid = GeneralUtility::makeInstance(Grid::class, $context);
103
        $recordRememberer = GeneralUtility::makeInstance(RecordRememberer::class);
104
        if ($context->getDrawingConfiguration()->getLanguageMode()) {
105
            $languageId = $context->getSiteLanguage()->getLanguageId();
106
        } else {
107
            $languageId = $context->getDrawingConfiguration()->getSelectedLanguageId();
108
        }
109
        foreach ($context->getBackendLayout()->getStructure()['__config']['backend_layout.']['rows.'] ?? [] as $row) {
110
            $rowObject = GeneralUtility::makeInstance(GridRow::class, $context);
111
            foreach ($row['columns.'] as $column) {
112
                $columnObject = GeneralUtility::makeInstance(GridColumn::class, $context, $column);
113
                $rowObject->addColumn($columnObject);
114
                if (isset($column['colPos'])) {
115
                    $records = $this->contentFetcher->getContentRecordsPerColumn((int)$column['colPos'], $languageId);
116
                    $recordRememberer->rememberRecords($records);
117
                    foreach ($records as $contentRecord) {
118
                        $columnItem = GeneralUtility::makeInstance(GridColumnItem::class, $context, $columnObject, $contentRecord);
119
                        $columnObject->addItem($columnItem);
120
                    }
121
                }
122
            }
123
            $grid->addRow($rowObject);
124
        }
125
        return $grid;
126
    }
127
128
    /**
129
     * @return LanguageColumn[]
130
     */
131
    public function getLanguageColumnsForPageLayoutContext(PageLayoutContext $context): iterable
132
    {
133
        $languageColumns = [];
134
        foreach ($context->getLanguagesToShow() as $siteLanguage) {
135
            $localizedLanguageId = $siteLanguage->getLanguageId();
136
            if ($localizedLanguageId === -1) {
137
                continue;
138
            }
139
            if ($localizedLanguageId > 0) {
140
                $localizedContext = $context->cloneForLanguage($siteLanguage);
141
                if (!$localizedContext->getLocalizedPageRecord()) {
142
                    continue;
143
                }
144
            } else {
145
                $localizedContext = $context;
146
            }
147
            $translationInfo = $this->contentFetcher->getTranslationData(
148
                $this->contentFetcher->getFlatContentRecords($localizedLanguageId),
149
                $localizedContext->getSiteLanguage()->getLanguageId()
150
            );
151
            $languageColumnObject = GeneralUtility::makeInstance(
152
                LanguageColumn::class,
153
                $localizedContext,
154
                $this->getGridForPageLayoutContext($localizedContext),
155
                $translationInfo
156
            );
157
            $languageColumns[] = $languageColumnObject;
158
        }
159
        return $languageColumns;
160
    }
161
162
    protected function getLanguageColumnsWithDefLangBindingForPageLayoutContext(PageLayoutContext $context): iterable
163
    {
164
        $languageColumns = [];
165
166
        // default language
167
        $translationInfo = $this->contentFetcher->getTranslationData(
168
            $this->contentFetcher->getFlatContentRecords(0),
169
            0
170
        );
171
172
        $defaultLanguageColumnObject = GeneralUtility::makeInstance(
173
            LanguageColumn::class,
174
            $context,
175
            $this->getGridForPageLayoutContext($context),
176
            $translationInfo
177
        );
178
        foreach ($context->getLanguagesToShow() as $siteLanguage) {
179
            $localizedLanguageId = $siteLanguage->getLanguageId();
180
            if ($localizedLanguageId  <= 0) {
181
                continue;
182
            }
183
184
            $localizedContext = $context->cloneForLanguage($siteLanguage);
185
            if (!$localizedContext->getLocalizedPageRecord()) {
186
                continue;
187
            }
188
189
            $translationInfo = $this->contentFetcher->getTranslationData(
190
                $this->contentFetcher->getFlatContentRecords($localizedLanguageId),
191
                $localizedContext->getSiteLanguage()->getLanguageId()
192
            );
193
194
            $translatedRows = $this->contentFetcher->getFlatContentRecords($localizedLanguageId);
195
196
            $grid = $defaultLanguageColumnObject->getGrid();
197
            if ($grid === null) {
198
                continue;
199
            }
200
201
            foreach ($grid->getRows() as $rows) {
202
                foreach ($rows->getColumns() as $column) {
203
                    if ($translationInfo['mode'] === 'connected') {
204
                        foreach ($column->getItems() as $item) {
205
                            // check if translation exists
206
                            foreach ($translatedRows as $translation) {
207
                                if ($translation['l18n_parent'] === $item->getRecord()['uid']) {
208
                                    $translatedItem = GeneralUtility::makeInstance(GridColumnItem::class, $this->context, $column, $translation);
209
                                    $item->addTranslation($localizedLanguageId, $translatedItem);
210
                                }
211
                            }
212
                        }
213
                    }
214
                }
215
            }
216
217
            $languageColumnObject = GeneralUtility::makeInstance(
218
                LanguageColumn::class,
219
                $localizedContext,
220
                $this->getGridForPageLayoutContext($localizedContext),
221
                $translationInfo
222
            );
223
            $languageColumns[$localizedLanguageId] = $languageColumnObject;
224
        }
225
        $languageColumns = [$defaultLanguageColumnObject] + $languageColumns;
226
227
        return $languageColumns;
228
    }
229
230
    /**
231
     * @param bool $renderUnused If true, renders the bottom column with unused records
232
     * @return string
233
     */
234
    public function drawContent(bool $renderUnused = true): string
235
    {
236
        $this->view->assign('hideRestrictedColumns', (bool)(BackendUtility::getPagesTSconfig($this->context->getPageId())['mod.']['web_layout.']['hideRestrictedCols'] ?? false));
237
        $this->view->assign('newContentTitle', $this->getLanguageService()->getLL('newContentElement'));
238
        $this->view->assign('newContentTitleShort', $this->getLanguageService()->getLL('content'));
239
        $this->view->assign('allowEditContent', $this->getBackendUser()->check('tables_modify', 'tt_content'));
240
241
        if ($this->context->getDrawingConfiguration()->getLanguageMode()) {
242
            if ($this->context->getDrawingConfiguration()->getDefaultLanguageBinding()) {
243
                $this->view->assign('languageColumns', $this->getLanguageColumnsWithDefLangBindingForPageLayoutContext($this->context));
244
            } else {
245
                $this->view->assign('languageColumns', $this->getLanguageColumnsForPageLayoutContext($this->context));
246
            }
247
        } else {
248
            $this->view->assign('grid', $this->getGridForPageLayoutContext($this->context));
249
        }
250
251
        $rendered = $this->view->render('PageLayout');
252
        if ($renderUnused) {
253
            $unusedRecords = $this->contentFetcher->getUnusedRecords();
254
255
            if (!empty($unusedRecords)) {
256
                $unusedElementsMessage = GeneralUtility::makeInstance(
257
                    FlashMessage::class,
258
                    $this->getLanguageService()->getLL('staleUnusedElementsWarning'),
259
                    $this->getLanguageService()->getLL('staleUnusedElementsWarningTitle'),
260
                    FlashMessage::WARNING
261
                );
262
                $service = GeneralUtility::makeInstance(FlashMessageService::class);
263
                $queue = $service->getMessageQueueByIdentifier();
264
                $queue->addMessage($unusedElementsMessage);
265
266
                $unusedGrid = GeneralUtility::makeInstance(Grid::class, $this->context);
267
                $unusedRow = GeneralUtility::makeInstance(GridRow::class, $this->context);
268
                $unusedColumn = GeneralUtility::makeInstance(GridColumn::class, $this->context, ['colPos' => false, 'name' => 'unused']);
269
270
                $unusedGrid->addRow($unusedRow);
271
                $unusedRow->addColumn($unusedColumn);
272
273
                foreach ($unusedRecords as $unusedRecord) {
274
                    $item = GeneralUtility::makeInstance(GridColumnItem::class, $this->context, $unusedColumn, $unusedRecord);
275
                    $unusedColumn->addItem($item);
276
                }
277
278
                $this->view->assign('grid', $unusedGrid);
279
                $rendered .= $this->view->render('UnusedRecords');
280
            }
281
        }
282
        return $rendered;
283
    }
284
285
    /**
286
     * Initializes the clipboard for generating paste links
287
     *
288
     * @see \TYPO3\CMS\Backend\Controller\ContextMenuController::clipboardAction()
289
     * @see \TYPO3\CMS\Filelist\Controller\FileListController::indexAction()
290
     */
291
    protected function initializeClipboard(): void
292
    {
293
        $this->clipboard = GeneralUtility::makeInstance(Clipboard::class);
294
        $this->clipboard->initializeClipboard();
295
        $this->clipboard->lockToNormal();
296
        $this->clipboard->cleanCurrent();
297
        $this->clipboard->endClipboard();
298
299
        $elFromTable = $this->clipboard->elFromTable('tt_content');
300
        if (!empty($elFromTable) && $this->isContentEditable()) {
301
            $pasteItem = (int)substr((string)key($elFromTable), 11);
302
            $pasteRecord = BackendUtility::getRecord('tt_content', (int)$pasteItem);
303
            $pasteTitle = (string)($pasteRecord['header'] ?: $pasteItem);
304
            $copyMode = $this->clipboard->clipData['normal']['mode'] ? '-' . $this->clipboard->clipData['normal']['mode'] : '';
305
            $addExtOnReadyCode = '
306
                     top.pasteIntoLinkTemplate = '
307
                . $this->drawPasteIcon($pasteItem, $pasteTitle, $copyMode, 't3js-paste-into', 'pasteIntoColumn')
308
                . ';
309
                    top.pasteAfterLinkTemplate = '
310
                . $this->drawPasteIcon($pasteItem, $pasteTitle, $copyMode, 't3js-paste-after', 'pasteAfterRecord')
311
                . ';';
312
        } else {
313
            $addExtOnReadyCode = '
314
                top.pasteIntoLinkTemplate = \'\';
315
                top.pasteAfterLinkTemplate = \'\';';
316
        }
317
        GeneralUtility::makeInstance(PageRenderer::class)->addJsInlineCode('pasteLinkTemplates', $addExtOnReadyCode);
318
    }
319
320
    /**
321
     * Draw a paste icon either for pasting into a column or for pasting after a record
322
     *
323
     * @param int $pasteItem ID of the item in the clipboard
324
     * @param string $pasteTitle Title for the JS modal
325
     * @param string $copyMode copy or cut
326
     * @param string $cssClass CSS class to determine if pasting is done into column or after record
327
     * @param string $title title attribute of the generated link
328
     *
329
     * @return string Generated HTML code with link and icon
330
     */
331
    private function drawPasteIcon(int $pasteItem, string $pasteTitle, string $copyMode, string $cssClass, string $title): string
332
    {
333
        $pasteIcon = json_encode(
334
            ' <button type="button"'
335
            . ' data-content="' . htmlspecialchars((string)$pasteItem) . '"'
336
            . ' data-title="' . htmlspecialchars($pasteTitle) . '"'
337
            . ' data-severity="warning"'
338
            . ' class="t3js-paste t3js-paste' . htmlspecialchars($copyMode) . ' ' . htmlspecialchars($cssClass) . ' btn btn-default btn-sm"'
339
            . ' title="' . htmlspecialchars($this->getLanguageService()->getLL($title)) . '">'
340
            . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
341
            . '</button>'
342
        );
343
        return $pasteIcon;
344
    }
345
346
    protected function isContentEditable(): bool
347
    {
348
        if ($this->getBackendUser()->isAdmin()) {
349
            return true;
350
        }
351
352
        $pageRecord = $this->context->getPageRecord();
353
        return !$pageRecord['editlock']
354
            && $this->getBackendUser()->check('tables_modify', 'tt_content')
355
            && $this->getBackendUser()->doesUserHaveAccess($pageRecord, Permission::CONTENT_EDIT);
356
    }
357
358
    protected function getBackendUser(): BackendUserAuthentication
359
    {
360
        return $GLOBALS['BE_USER'];
361
    }
362
363
    protected function getLanguageService(): LanguageService
364
    {
365
        return $GLOBALS['LANG'];
366
    }
367
}
368