Completed
Push — master ( 61935d...e09082 )
by
unknown
19:31
created

StandardContentPreviewRenderer::getBackendUser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
declare(strict_types = 1);
3
namespace TYPO3\CMS\Backend\Preview;
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
use Psr\Log\LoggerAwareInterface;
19
use Psr\Log\LoggerAwareTrait;
20
use TYPO3\CMS\Backend\Routing\UriBuilder;
21
use TYPO3\CMS\Backend\Utility\BackendUtility;
22
use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem;
23
use TYPO3\CMS\Backend\View\PageLayoutView;
24
use TYPO3\CMS\Backend\View\PageLayoutViewDrawFooterHookInterface;
25
use TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface;
26
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
27
use TYPO3\CMS\Core\Core\Environment;
28
use TYPO3\CMS\Core\Imaging\Icon;
29
use TYPO3\CMS\Core\Imaging\IconFactory;
30
use TYPO3\CMS\Core\Localization\LanguageService;
31
use TYPO3\CMS\Core\Service\FlexFormService;
32
use TYPO3\CMS\Core\Utility\GeneralUtility;
33
use TYPO3\CMS\Fluid\View\StandaloneView;
34
35
/**
36
 * Class StandardContentPreviewRenderer
37
 *
38
 * Legacy preview rendering refactored from PageLayoutView.
39
 * Provided as default preview rendering mechanism via
40
 * StandardPreviewRendererResolver which detects the renderer
41
 * based on TCA configuration.
42
 *
43
 * Can be replaced and/or subclassed by custom implementations
44
 * by changing this TCA configuration.
45
 *
46
 * See also PreviewRendererInterface documentation.
47
 */
48
class StandardContentPreviewRenderer implements PreviewRendererInterface, LoggerAwareInterface
49
{
50
    use LoggerAwareTrait;
51
52
    public function renderPageModulePreviewHeader(GridColumnItem $item): string
53
    {
54
        $record = $item->getRecord();
55
        $itemLabels = $item->getBackendLayout()->getDrawingConfiguration()->getItemLabels();
56
57
        $outHeader = '';
58
59
        if ($record['header']) {
60
            $infoArr = [];
61
            $this->getProcessedValue($item, 'header_position,header_layout,header_link', $infoArr);
62
            $hiddenHeaderNote = '';
63
            // If header layout is set to 'hidden', display an accordant note:
64
            if ($record['header_layout'] == 100) {
65
                $hiddenHeaderNote = ' <em>[' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.hidden')) . ']</em>';
66
            }
67
            $outHeader = $record['date']
68
                ? htmlspecialchars($itemLabels['date'] . ' ' . BackendUtility::date($record['date'])) . '<br />'
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
69
                : '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
70
            $outHeader .= '<strong>' . $this->linkEditContent($this->renderText($record['header']), $record)
71
                . $hiddenHeaderNote . '</strong><br />';
72
        }
73
74
        return $outHeader;
75
    }
76
77
    public function renderPageModulePreviewContent(GridColumnItem $item): string
78
    {
79
        $out = '';
80
        $record = $item->getRecord();
81
82
        $contentTypeLabels = $item->getBackendLayout()->getDrawingConfiguration()->getContentTypeLabels();
83
        $languageService = $this->getLanguageService();
84
85
        $drawItem = true;
86
        $hookPreviewContent = '';
87
        // Hook: Render an own preview of a record
88
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem'])) {
89
            $pageLayoutView = PageLayoutView::createFromDrawingConfiguration($item->getBackendLayout()->getDrawingConfiguration());
90
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem'] ?? [] as $className) {
91
                $hookObject = GeneralUtility::makeInstance($className);
92
                if (!$hookObject instanceof PageLayoutViewDrawItemHookInterface) {
93
                    throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawItemHookInterface::class, 1582574553);
94
                }
95
                $hookObject->preProcess($pageLayoutView, $drawItem, $previewHeader, $hookPreviewContent, $record);
96
            }
97
            $item->setRecord($record);
98
        }
99
100
        if (!$drawItem) {
101
            return $hookPreviewContent;
102
        }
103
        // Check if a Fluid-based preview template was defined for this CType
104
        // and render it via Fluid. Possible option:
105
        // mod.web_layout.tt_content.preview.media = EXT:site_mysite/Resources/Private/Templates/Preview/Media.html
106
        $infoArr = [];
107
        $this->getProcessedValue($item, 'header_position,header_layout,header_link', $infoArr);
108
        $tsConfig = BackendUtility::getPagesTSconfig($record['pid'])['mod.']['web_layout.']['tt_content.']['preview.'] ?? [];
109
        if (!empty($tsConfig[$record['CType']])) {
110
            $fluidPreview = $this->renderContentElementPreviewFromFluidTemplate($record);
111
            if ($fluidPreview !== null) {
112
                return $fluidPreview;
113
            }
114
        }
115
116
        // Draw preview of the item depending on its CType
117
        switch ($record['CType']) {
118
            case 'header':
119
                if ($record['subheader']) {
120
                    $out .= $this->linkEditContent($this->renderText($record['subheader']), $record) . '<br />';
121
                }
122
                break;
123
            case 'uploads':
124
                if ($record['media']) {
125
                    $out .= $this->linkEditContent($this->getThumbCodeUnlinked($record, 'tt_content', 'media'), $record) . '<br />';
126
                }
127
                break;
128
            case 'menu':
129
                $contentType = $contentTypeLabels[$record['CType']];
130
                $out .= $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $record) . '<br />';
131
                // Add Menu Type
132
                $menuTypeLabel = $languageService->sL(
133
                    BackendUtility::getLabelFromItemListMerged($record['pid'], 'tt_content', 'menu_type', $record['menu_type'])
134
                );
135
                $menuTypeLabel = $menuTypeLabel ?: 'invalid menu type';
136
                $out .= $this->linkEditContent($menuTypeLabel, $record);
137
                if ($record['menu_type'] !== '2' && ($record['pages'] || $record['selected_categories'])) {
138
                    // Show pages if menu type is not "Sitemap"
139
                    $out .= ':' . $this->linkEditContent($this->generateListForCTypeMenu($record), $record) . '<br />';
140
                }
141
                break;
142
            case 'shortcut':
143
                if (!empty($record['records'])) {
144
                    $shortcutContent = [];
145
                    $recordList = explode(',', $record['records']);
146
                    foreach ($recordList as $recordIdentifier) {
147
                        $split = BackendUtility::splitTable_Uid($recordIdentifier);
148
                        $tableName = empty($split[0]) ? 'tt_content' : $split[0];
149
                        $shortcutRecord = BackendUtility::getRecord($tableName, $split[1]);
0 ignored issues
show
Bug introduced by
$split[1] of type string is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Backend\Utilit...endUtility::getRecord(). ( Ignorable by Annotation )

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

149
                        $shortcutRecord = BackendUtility::getRecord($tableName, /** @scrutinizer ignore-type */ $split[1]);
Loading history...
150
                        if (is_array($shortcutRecord)) {
151
                            $icon = $this->getIconFactory()->getIconForRecord($tableName, $shortcutRecord, Icon::SIZE_SMALL)->render();
152
                            $icon = BackendUtility::wrapClickMenuOnIcon(
153
                                $icon,
154
                                $tableName,
155
                                $shortcutRecord['uid'],
156
                                1,
157
                                '',
158
                                '+copy,info,edit,view'
159
                            );
160
                            $shortcutContent[] = $icon
161
                                . htmlspecialchars(BackendUtility::getRecordTitle($tableName, $shortcutRecord));
162
                        }
163
                    }
164
                    $out .= implode('<br />', $shortcutContent) . '<br />';
165
                }
166
                break;
167
            case 'list':
168
                $hookOut = '';
169
                if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'])) {
170
                    $pageLayoutView = PageLayoutView::createFromDrawingConfiguration($item->getBackendLayout()->getDrawingConfiguration());
171
                    $_params = ['pObj' => &$pageLayoutView, 'row' => $record];
172
                    foreach (
0 ignored issues
show
Coding Style introduced by
Space found after opening bracket of FOREACH loop
Loading history...
Coding Style introduced by
Space found before closing bracket of FOREACH loop
Loading history...
173
                        $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info'][$record['list_type']] ??
174
                        $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['list_type_Info']['_DEFAULT'] ??
175
                        [] as $_funcRef
176
                    ) {
177
                        $hookOut .= GeneralUtility::callUserFunction($_funcRef, $_params, $pageLayoutView);
178
                    }
179
                }
180
181
                if ((string)$hookOut !== '') {
182
                    $out .= $hookOut;
183
                } elseif (!empty($record['list_type'])) {
184
                    $label = BackendUtility::getLabelFromItemListMerged($record['pid'], 'tt_content', 'list_type', $record['list_type']);
185
                    if (!empty($label)) {
186
                        $out .= $this->linkEditContent('<strong>' . htmlspecialchars($languageService->sL($label)) . '</strong>', $record) . '<br />';
187
                    } else {
188
                        $message = sprintf($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue'), $record['list_type']);
189
                        $out .= '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
190
                    }
191
                } elseif (!empty($record['select_key'])) {
192
                    $out .= htmlspecialchars($languageService->sL(BackendUtility::getItemLabel('tt_content', 'select_key')))
193
                        . ' ' . htmlspecialchars($record['select_key']) . '<br />';
194
                } else {
195
                    $out .= '<strong>' . $languageService->getLL('noPluginSelected') . '</strong>';
196
                }
197
                $out .= htmlspecialchars($languageService->sL(BackendUtility::getLabelFromItemlist('tt_content', 'pages', $record['pages']))) . '<br />';
198
                break;
199
            default:
200
                $contentType = $contentTypeLabels[$record['CType']];
201
202
                if (isset($contentType)) {
203
                    $out .= $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $record) . '<br />';
204
                    if ($record['bodytext']) {
205
                        $out .= $this->linkEditContent($this->renderText($record['bodytext']), $record) . '<br />';
206
                    }
207
                    if ($record['image']) {
208
                        $out .= $this->linkEditContent($this->getThumbCodeUnlinked($record, 'tt_content', 'image'), $record) . '<br />';
209
                    }
210
                } else {
211
                    $message = sprintf(
212
                        $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue'),
213
                        $record['CType']
214
                    );
215
                    $out .= '<span class="label label-warning">' . htmlspecialchars($message) . '</span>';
216
                }
217
        }
218
219
        return $out;
220
    }
221
222
    /**
223
     * Render a footer for the record
224
     *
225
     * @param GridColumnItem $item
226
     * @return string
227
     */
228
    public function renderPageModulePreviewFooter(GridColumnItem $item): string
229
    {
230
        $content = '';
231
        $info = [];
232
        $record = $item->getRecord();
233
        $this->getProcessedValue($item, 'starttime,endtime,fe_group,space_before_class,space_after_class', $info);
234
235
        if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']) && !empty($record[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']])) {
236
            $info[] = $record[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']];
237
        }
238
239
        // Call drawFooter hooks
240
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawFooter'])) {
241
            $pageLayoutView = PageLayoutView::createFromDrawingConfiguration($item->getBackendLayout()->getDrawingConfiguration());
242
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawFooter'] ?? [] as $className) {
243
                $hookObject = GeneralUtility::makeInstance($className);
244
                if (!$hookObject instanceof PageLayoutViewDrawFooterHookInterface) {
245
                    throw new \UnexpectedValueException($className . ' must implement interface ' . PageLayoutViewDrawFooterHookInterface::class, 1582574541);
246
                }
247
                $hookObject->preProcess($pageLayoutView, $info, $record);
248
            }
249
            $item->setRecord($record);
250
        }
251
252
        if (!empty($info)) {
253
            $content = implode('<br>', $info);
254
        }
255
256
        if (!empty($content)) {
257
            $content = '<div class="t3-page-ce-footer">' . $content . '</div>';
258
        }
259
260
        return $content;
261
    }
262
263
    public function wrapPageModulePreview(string $previewHeader, string $previewContent, GridColumnItem $item): string
264
    {
265
        $content = '<span class="exampleContent">' . $previewHeader . $previewContent . '</span>';
266
        if ($item->isDisabled()) {
267
            return '<span class="text-muted">' . $content . '</span>';
268
        }
269
        return $content;
270
    }
271
272
    protected function getProcessedValue(GridColumnItem $item, string $fieldList, array &$info): void
273
    {
274
        $itemLabels = $item->getBackendLayout()->getDrawingConfiguration()->getItemLabels();
275
        $record = $item->getRecord();
276
        $fieldArr = explode(',', $fieldList);
277
        foreach ($fieldArr as $field) {
278
            if ($record[$field]) {
279
                $info[] = '<strong>' . htmlspecialchars($itemLabels[$field]) . '</strong> '
280
                    . htmlspecialchars(BackendUtility::getProcessedValue('tt_content', $field, $record[$field]));
281
            }
282
        }
283
    }
284
285
    protected function renderContentElementPreviewFromFluidTemplate(array $row): ?string
286
    {
287
        $tsConfig = BackendUtility::getPagesTSconfig($row['pid'])['mod.']['web_layout.']['tt_content.']['preview.'] ?? [];
288
        $fluidTemplateFile = '';
289
290
        if ($row['CType'] === 'list' && !empty($row['list_type'])
291
            && !empty($tsConfig['list.'][$row['list_type']])
292
        ) {
293
            $fluidTemplateFile = $tsConfig['list.'][$row['list_type']];
294
        } elseif (!empty($tsConfig[$row['CType']])) {
295
            $fluidTemplateFile = $tsConfig[$row['CType']];
296
        }
297
298
        if ($fluidTemplateFile) {
299
            $fluidTemplateFile = GeneralUtility::getFileAbsFileName($fluidTemplateFile);
300
            if ($fluidTemplateFile) {
301
                try {
302
                    $view = GeneralUtility::makeInstance(StandaloneView::class);
303
                    $view->setTemplatePathAndFilename($fluidTemplateFile);
304
                    $view->assignMultiple($row);
305
                    if (!empty($row['pi_flexform'])) {
306
                        $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
307
                        $view->assign('pi_flexform_transformed', $flexFormService->convertFlexFormContentToArray($row['pi_flexform']));
308
                    }
309
                    return $view->render();
310
                } catch (\Exception $e) {
311
                    $this->logger->warning(sprintf(
312
                        'The backend preview for content element %d can not be rendered using the Fluid template file "%s": %s',
313
                        $row['uid'],
314
                        $fluidTemplateFile,
315
                        $e->getMessage()
316
                    ));
317
318
                    if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] && $this->getBackendUser()->isAdmin()) {
319
                        $view = GeneralUtility::makeInstance(StandaloneView::class);
320
                        $view->assign('error', [
321
                            'message' => str_replace(Environment::getProjectPath(), '', $e->getMessage()),
322
                            'title' => 'Error while rendering FluidTemplate preview using ' . str_replace(Environment::getProjectPath(), '', $fluidTemplateFile),
323
                        ]);
324
                        $view->setTemplateSource('<f:be.infobox title="{error.title}" state="2">{error.message}</f:be.infobox>');
325
                        return $view->render();
326
                    }
327
                }
328
            }
329
        }
330
        return null;
331
    }
332
333
    /**
334
     * Create thumbnail code for record/field but not linked
335
     *
336
     * @param mixed[] $row Record array
337
     * @param string $table Table (record is from)
338
     * @param string $field Field name for which thumbnail are to be rendered.
339
     * @return string HTML for thumbnails, if any.
340
     */
341
    protected function getThumbCodeUnlinked($row, $table, $field): string
342
    {
343
        return BackendUtility::thumbCode($row, $table, $field, '', '', null, 0, '', '', false);
344
    }
345
346
    /**
347
     * Processing of larger amounts of text (usually from RTE/bodytext fields) with word wrapping etc.
348
     *
349
     * @param string $input Input string
350
     * @return string Output string
351
     */
352
    protected function renderText(string $input): string
353
    {
354
        $input = strip_tags($input);
355
        $input = GeneralUtility::fixed_lgd_cs($input, 1500);
356
        return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
357
    }
358
359
    /**
360
     * Generates a list of selected pages or categories for the CType menu
361
     *
362
     * @param array $record row from pages
363
     * @return string
364
     */
365
    protected function generateListForCTypeMenu(array $record): string
366
    {
367
        $table = 'pages';
368
        $field = 'pages';
369
        // get categories instead of pages
370
        if (strpos($record['menu_type'], 'categorized_') !== false) {
371
            $table = 'sys_category';
372
            $field = 'selected_categories';
373
        }
374
        if (trim($record[$field]) === '') {
375
            return '';
376
        }
377
        $content = '';
378
        $uidList = explode(',', $record[$field]);
379
        foreach ($uidList as $uid) {
380
            $uid = (int)$uid;
381
            $pageRecord = BackendUtility::getRecord($table, $uid, 'title');
382
            $content .= '<br>' . $pageRecord['title'] . ' (' . $uid . ')';
383
        }
384
        return $content;
385
    }
386
387
    /**
388
     * Will create a link on the input string and possibly a big button after the string which links to editing in the RTE.
389
     * Used for content element content displayed so the user can click the content / "Edit in Rich Text Editor" button
390
     *
391
     * @param string $linkText String to link. Must be prepared for HTML output.
392
     * @param array $row The row.
393
     * @return string If the whole thing was editable $str is return with link around. Otherwise just $str.
394
     */
395
    protected function linkEditContent(string $linkText, $row): string
396
    {
397
        $backendUser = $this->getBackendUser();
398
        if ($backendUser->check('tables_modify', 'tt_content') && $backendUser->recordEditAccessInternals('tt_content', $row)) {
399
            $urlParameters = [
400
                'edit' => [
401
                    'tt_content' => [
402
                        $row['uid'] => 'edit'
403
                    ]
404
                ],
405
                'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') . '#element-tt_content-' . $row['uid']
406
            ];
407
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
408
            $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
409
            return '<a href="' . htmlspecialchars($url) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">' . $linkText . '</a>';
410
        }
411
        return $linkText;
412
    }
413
414
    protected function getBackendUser(): BackendUserAuthentication
415
    {
416
        return $GLOBALS['BE_USER'];
417
    }
418
419
    protected function getLanguageService(): LanguageService
420
    {
421
        return $GLOBALS['LANG'];
422
    }
423
424
    protected function getIconFactory(): IconFactory
425
    {
426
        return GeneralUtility::makeInstance(IconFactory::class);
427
    }
428
}
429