Completed
Push — master ( f9a904...7077be )
by
unknown
13:18
created

BackendLayoutRenderer::isContentEditable()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 4
nop 0
dl 0
loc 10
rs 10
c 0
b 0
f 0
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\Controller\ControllerContext;
41
use TYPO3\CMS\Extbase\Mvc\Request;
42
use TYPO3\CMS\Extbase\Object\ObjectManager;
43
use TYPO3\CMS\Fluid\View\TemplateView;
44
45
/**
46
 * Backend Layout Renderer
47
 *
48
 * Draws a page layout - essentially, behaves as a wrapper for a view
49
 * which renders the Resources/Private/PageLayout/PageLayout template
50
 * with necessary assigned template variables.
51
 *
52
 * - Initializes the clipboard used in the page layout
53
 * - Inserts an encoded paste icon as JS which is made visible when clipboard elements are registered
54
 */
55
class BackendLayoutRenderer
56
{
57
    use LoggerAwareTrait;
58
59
    /**
60
     * @var IconFactory
61
     */
62
    protected $iconFactory;
63
64
    /**
65
     * @var PageLayoutContext
66
     */
67
    protected $context;
68
69
    /**
70
     * @var ContentFetcher
71
     */
72
    protected $contentFetcher;
73
74
    /**
75
     * @var Clipboard
76
     */
77
    protected $clipboard;
78
79
    /**
80
     * @var TemplateView
81
     */
82
    protected $view;
83
84
    public function __construct(PageLayoutContext $context)
85
    {
86
        $this->context = $context;
87
        $this->contentFetcher = GeneralUtility::makeInstance(ContentFetcher::class, $context);
88
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
89
        $this->initializeClipboard();
90
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
91
        $controllerContext = $objectManager->get(ControllerContext::class);
92
        $request = $objectManager->get(Request::class);
93
        $controllerContext->setRequest($request);
94
        $this->view = GeneralUtility::makeInstance(TemplateView::class);
95
        $this->view->getRenderingContext()->setControllerContext($controllerContext);
96
        $this->view->getRenderingContext()->getTemplatePaths()->fillDefaultsByPackageName('backend');
97
        $this->view->getRenderingContext()->setControllerName('PageLayout');
98
        $this->view->assign('context', $context);
99
    }
100
101
    public function getGridForPageLayoutContext(PageLayoutContext $context): Grid
102
    {
103
        $grid = GeneralUtility::makeInstance(Grid::class, $context);
104
        $recordRememberer = GeneralUtility::makeInstance(RecordRememberer::class);
105
        if ($context->getDrawingConfiguration()->getLanguageMode()) {
106
            $languageId = $context->getSiteLanguage()->getLanguageId();
107
        } else {
108
            $languageId = $context->getDrawingConfiguration()->getSelectedLanguageId();
109
        }
110
        foreach ($context->getBackendLayout()->getStructure()['__config']['backend_layout.']['rows.'] ?? [] as $row) {
111
            $rowObject = GeneralUtility::makeInstance(GridRow::class, $context);
112
            foreach ($row['columns.'] as $column) {
113
                $columnObject = GeneralUtility::makeInstance(GridColumn::class, $context, $column);
114
                $rowObject->addColumn($columnObject);
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
            $grid->addRow($rowObject);
123
        }
124
        return $grid;
125
    }
126
127
    /**
128
     * @return LanguageColumn[]
129
     */
130
    public function getLanguageColumnsForPageLayoutContext(PageLayoutContext $context): iterable
131
    {
132
        $languageColumns = [];
133
        foreach ($context->getLanguagesToShow() as $siteLanguage) {
134
            $localizedLanguageId = $siteLanguage->getLanguageId();
135
            if ($localizedLanguageId > 0) {
136
                $localizedContext = $context->cloneForLanguage($siteLanguage);
137
                if (!$localizedContext->getLocalizedPageRecord()) {
138
                    continue;
139
                }
140
            } else {
141
                $localizedContext = $context;
142
            }
143
            $translationInfo = $this->contentFetcher->getTranslationData(
144
                $this->contentFetcher->getFlatContentRecords($localizedLanguageId),
145
                $localizedContext->getSiteLanguage()->getLanguageId()
146
            );
147
            $languageColumnObject = GeneralUtility::makeInstance(
148
                LanguageColumn::class,
149
                $localizedContext,
150
                $this->getGridForPageLayoutContext($localizedContext),
151
                $translationInfo
152
            );
153
            $languageColumns[] = $languageColumnObject;
154
        }
155
        return $languageColumns;
156
    }
157
158
    /**
159
     * @param bool $renderUnused If true, renders the bottom column with unused records
160
     * @return string
161
     */
162
    public function drawContent(bool $renderUnused = true): string
163
    {
164
        $this->view->assign('hideRestrictedColumns', (bool)(BackendUtility::getPagesTSconfig($this->context->getPageId())['mod.']['web_layout.']['hideRestrictedCols'] ?? false));
165
        $this->view->assign('newContentTitle', $this->getLanguageService()->getLL('newContentElement'));
166
        $this->view->assign('newContentTitleShort', $this->getLanguageService()->getLL('content'));
167
        $this->view->assign('allowEditContent', $this->getBackendUser()->check('tables_modify', 'tt_content'));
168
169
        if ($this->context->getDrawingConfiguration()->getLanguageMode()) {
170
            $this->view->assign('languageColumns', $this->getLanguageColumnsForPageLayoutContext($this->context));
171
        } else {
172
            $this->view->assign('grid', $this->getGridForPageLayoutContext($this->context));
173
        }
174
175
        $rendered = $this->view->render('PageLayout');
176
        if ($renderUnused) {
177
            $unusedRecords = $this->contentFetcher->getUnusedRecords();
178
179
            if (!empty($unusedRecords)) {
180
                $unusedElementsMessage = GeneralUtility::makeInstance(
181
                    FlashMessage::class,
182
                    $this->getLanguageService()->getLL('staleUnusedElementsWarning'),
183
                    $this->getLanguageService()->getLL('staleUnusedElementsWarningTitle'),
184
                    FlashMessage::WARNING
185
                );
186
                $service = GeneralUtility::makeInstance(FlashMessageService::class);
187
                $queue = $service->getMessageQueueByIdentifier();
188
                $queue->addMessage($unusedElementsMessage);
189
190
                $unusedGrid = GeneralUtility::makeInstance(Grid::class, $this->context);
191
                $unusedRow = GeneralUtility::makeInstance(GridRow::class, $this->context);
192
                $unusedColumn = GeneralUtility::makeInstance(GridColumn::class, $this->context, ['colPos' => false, 'name' => 'unused']);
193
194
                $unusedGrid->addRow($unusedRow);
195
                $unusedRow->addColumn($unusedColumn);
196
197
                foreach ($unusedRecords as $unusedRecord) {
198
                    $item = GeneralUtility::makeInstance(GridColumnItem::class, $this->context, $unusedColumn, $unusedRecord);
199
                    $unusedColumn->addItem($item);
200
                }
201
202
                $this->view->assign('grid', $unusedGrid);
203
                $rendered .= $this->view->render('UnusedRecords');
204
            }
205
        }
206
        return $rendered;
207
    }
208
209
    /**
210
     * Initializes the clipboard for generating paste links
211
     *
212
     * @see \TYPO3\CMS\Backend\Controller\ContextMenuController::clipboardAction()
213
     * @see \TYPO3\CMS\Filelist\Controller\FileListController::indexAction()
214
     */
215
    protected function initializeClipboard(): void
216
    {
217
        $this->clipboard = GeneralUtility::makeInstance(Clipboard::class);
218
        $this->clipboard->initializeClipboard();
219
        $this->clipboard->lockToNormal();
220
        $this->clipboard->cleanCurrent();
221
        $this->clipboard->endClipboard();
222
223
        $elFromTable = $this->clipboard->elFromTable('tt_content');
224
        if (!empty($elFromTable) && $this->isContentEditable()) {
225
            $pasteItem = (int)substr(key($elFromTable), 11);
226
            $pasteRecord = BackendUtility::getRecord('tt_content', (int)$pasteItem);
227
            $pasteTitle = (string)($pasteRecord['header'] ?: $pasteItem);
228
            $copyMode = $this->clipboard->clipData['normal']['mode'] ? '-' . $this->clipboard->clipData['normal']['mode'] : '';
229
            $addExtOnReadyCode = '
230
                     top.pasteIntoLinkTemplate = '
231
                . $this->drawPasteIcon($pasteItem, $pasteTitle, $copyMode, 't3js-paste-into', 'pasteIntoColumn')
232
                . ';
233
                    top.pasteAfterLinkTemplate = '
234
                . $this->drawPasteIcon($pasteItem, $pasteTitle, $copyMode, 't3js-paste-after', 'pasteAfterRecord')
235
                . ';';
236
        } else {
237
            $addExtOnReadyCode = '
238
                top.pasteIntoLinkTemplate = \'\';
239
                top.pasteAfterLinkTemplate = \'\';';
240
        }
241
        GeneralUtility::makeInstance(PageRenderer::class)->addJsInlineCode('pasteLinkTemplates', $addExtOnReadyCode);
242
    }
243
244
    /**
245
     * Draw a paste icon either for pasting into a column or for pasting after a record
246
     *
247
     * @param int $pasteItem ID of the item in the clipboard
248
     * @param string $pasteTitle Title for the JS modal
249
     * @param string $copyMode copy or cut
250
     * @param string $cssClass CSS class to determine if pasting is done into column or after record
251
     * @param string $title title attribute of the generated link
252
     *
253
     * @return string Generated HTML code with link and icon
254
     */
255
    private function drawPasteIcon(int $pasteItem, string $pasteTitle, string $copyMode, string $cssClass, string $title): string
256
    {
257
        $pasteIcon = json_encode(
258
            ' <a data-content="' . htmlspecialchars((string)$pasteItem) . '"'
259
            . ' data-title="' . htmlspecialchars($pasteTitle) . '"'
260
            . ' data-severity="warning"'
261
            . ' class="t3js-paste t3js-paste' . htmlspecialchars($copyMode) . ' ' . htmlspecialchars($cssClass) . ' btn btn-default btn-sm"'
262
            . ' title="' . htmlspecialchars($this->getLanguageService()->getLL($title)) . '">'
263
            . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
264
            . '</a>'
265
        );
266
        return $pasteIcon;
267
    }
268
269
    protected function isContentEditable(): bool
270
    {
271
        if ($this->getBackendUser()->isAdmin()) {
272
            return true;
273
        }
274
275
        $pageRecord = $this->context->getPageRecord();
276
        return !$pageRecord['editlock']
277
            && $this->getBackendUser()->check('tables_modify', 'tt_content')
278
            && $this->getBackendUser()->doesUserHaveAccess($pageRecord, Permission::CONTENT_EDIT);
279
    }
280
281
    protected function getBackendUser(): BackendUserAuthentication
282
    {
283
        return $GLOBALS['BE_USER'];
284
    }
285
286
    protected function getLanguageService(): LanguageService
287
    {
288
        return $GLOBALS['LANG'];
289
    }
290
}
291