Completed
Push — master ( 7e342e...13c67c )
by
unknown
19:15
created

renderPageModulePreviewHeader()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 15
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 23
rs 9.7666
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\Drawing\DrawingConfiguration;
24
use TYPO3\CMS\Backend\View\PageLayoutView;
25
use TYPO3\CMS\Backend\View\PageLayoutViewDrawFooterHookInterface;
26
use TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface;
27
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
28
use TYPO3\CMS\Core\Core\Environment;
29
use TYPO3\CMS\Core\Imaging\Icon;
30
use TYPO3\CMS\Core\Imaging\IconFactory;
31
use TYPO3\CMS\Core\Localization\LanguageService;
32
use TYPO3\CMS\Core\Service\FlexFormService;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
use TYPO3\CMS\Fluid\View\StandaloneView;
35
36
/**
37
 * Class StandardContentPreviewRenderer
38
 *
39
 * Legacy preview rendering refactored from PageLayoutView.
40
 * Provided as default preview rendering mechanism via
41
 * StandardPreviewRendererResolver which detects the renderer
42
 * based on TCA configuration.
43
 *
44
 * Can be replaced and/or subclassed by custom implementations
45
 * by changing this TCA configuration.
46
 *
47
 * See also PreviewRendererInterface documentation.
48
 */
49
class StandardContentPreviewRenderer implements PreviewRendererInterface, LoggerAwareInterface
50
{
51
    use LoggerAwareTrait;
52
53
    public function renderPageModulePreviewHeader(GridColumnItem $item): string
54
    {
55
        $record = $item->getRecord();
56
        $itemLabels = $item->getBackendLayout()->getDrawingConfiguration()->getItemLabels();
57
58
        $outHeader = '';
59
60
        if ($record['header']) {
61
            $infoArr = [];
62
            $this->getProcessedValue($item, 'header_position,header_layout,header_link', $infoArr);
63
            $hiddenHeaderNote = '';
64
            // If header layout is set to 'hidden', display an accordant note:
65
            if ($record['header_layout'] == 100) {
66
                $hiddenHeaderNote = ' <em>[' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.hidden')) . ']</em>';
67
            }
68
            $outHeader = $record['date']
69
                ? htmlspecialchars($itemLabels['date'] . ' ' . BackendUtility::date($record['date'])) . '<br />'
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
70
                : '';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
71
            $outHeader .= '<strong>' . $this->linkEditContent($this->renderText($record['header']), $record)
72
                . $hiddenHeaderNote . '</strong><br />';
73
        }
74
75
        return $outHeader;
76
    }
77
78
    public function renderPageModulePreviewContent(GridColumnItem $item): string
79
    {
80
        $out = '';
81
        $record = $item->getRecord();
82
83
        $contentTypeLabels = $item->getBackendLayout()->getDrawingConfiguration()->getContentTypeLabels();
84
        $languageService = $this->getLanguageService();
85
86
        // Check if a Fluid-based preview template was defined for this CType
87
        // and render it via Fluid. Possible option:
88
        // mod.web_layout.tt_content.preview.media = EXT:site_mysite/Resources/Private/Templates/Preview/Media.html
89
        $infoArr = [];
90
        $this->getProcessedValue($item, 'header_position,header_layout,header_link', $infoArr);
91
        $tsConfig = BackendUtility::getPagesTSconfig($record['pid'])['mod.']['web_layout.']['tt_content.']['preview.'] ?? [];
92
        if (!empty($tsConfig[$record['CType']])) {
93
            $fluidPreview = $this->renderContentElementPreviewFromFluidTemplate($record);
94
            if ($fluidPreview !== null) {
95
                return $fluidPreview;
96
            }
97
        }
98
99
        // Draw preview of the item depending on its CType
100
        switch ($record['CType']) {
101
            case 'header':
102
                if ($record['subheader']) {
103
                    $out .= $this->linkEditContent($this->renderText($record['subheader']), $record) . '<br />';
104
                }
105
                break;
106
            case 'uploads':
107
                if ($record['media']) {
108
                    $out .= $this->linkEditContent($this->getThumbCodeUnlinked($record, 'tt_content', 'media'), $record) . '<br />';
109
                }
110
                break;
111
            case 'menu':
112
                $contentType = $contentTypeLabels[$record['CType']];
113
                $out .= $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $record) . '<br />';
114
                // Add Menu Type
115
                $menuTypeLabel = $languageService->sL(
116
                    BackendUtility::getLabelFromItemListMerged($record['pid'], 'tt_content', 'menu_type', $record['menu_type'])
117
                );
118
                $menuTypeLabel = $menuTypeLabel ?: 'invalid menu type';
119
                $out .= $this->linkEditContent($menuTypeLabel, $record);
120
                if ($record['menu_type'] !== '2' && ($record['pages'] || $record['selected_categories'])) {
121
                    // Show pages if menu type is not "Sitemap"
122
                    $out .= ':' . $this->linkEditContent($this->generateListForCTypeMenu($record), $record) . '<br />';
123
                }
124
                break;
125
            case 'shortcut':
126
                if (!empty($record['records'])) {
127
                    $shortcutContent = [];
128
                    $recordList = explode(',', $record['records']);
129
                    foreach ($recordList as $recordIdentifier) {
130
                        $split = BackendUtility::splitTable_Uid($recordIdentifier);
131
                        $tableName = empty($split[0]) ? 'tt_content' : $split[0];
132
                        $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

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