Passed
Push — master ( c4c6dd...2cad69 )
by
unknown
20:24
created

StandardContentPreviewRenderer   F

Complexity

Total Complexity 71

Size/Duplication

Total Lines 386
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 216
c 4
b 0
f 0
dl 0
loc 386
rs 2.7199
wmc 71

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getProcessedValue() 0 9 3
A getIconFactory() 0 3 1
A linkEditContent() 0 17 3
D renderPageModulePreviewContent() 0 125 27
B renderContentElementPreviewFromFluidTemplate() 0 46 11
B renderPageModulePreviewFooter() 0 33 8
A getLanguageService() 0 3 1
A wrapPageModulePreview() 0 32 6
A renderText() 0 5 1
A getBackendUser() 0 3 1
A generateListForCTypeMenu() 0 20 4
A renderPageModulePreviewHeader() 0 23 4
A getThumbCodeUnlinked() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like StandardContentPreviewRenderer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StandardContentPreviewRenderer, and based on these observations, apply Extract Interface, too.

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
        // Check if a Fluid-based preview template was defined for this CType
86
        // and render it via Fluid. Possible option:
87
        // mod.web_layout.tt_content.preview.media = EXT:site_mysite/Resources/Private/Templates/Preview/Media.html
88
        $infoArr = [];
89
        $this->getProcessedValue($item, 'header_position,header_layout,header_link', $infoArr);
90
        $tsConfig = BackendUtility::getPagesTSconfig($record['pid'])['mod.']['web_layout.']['tt_content.']['preview.'] ?? [];
91
        if (!empty($tsConfig[$record['CType']])) {
92
            $fluidPreview = $this->renderContentElementPreviewFromFluidTemplate($record);
93
            if ($fluidPreview !== null) {
94
                return $fluidPreview;
95
            }
96
        }
97
98
        // Draw preview of the item depending on its CType
99
        switch ($record['CType']) {
100
            case 'header':
101
                if ($record['subheader']) {
102
                    $out .= $this->linkEditContent($this->renderText($record['subheader']), $record) . '<br />';
103
                }
104
                break;
105
            case 'uploads':
106
                if ($record['media']) {
107
                    $out .= $this->linkEditContent($this->getThumbCodeUnlinked($record, 'tt_content', 'media'), $record) . '<br />';
108
                }
109
                break;
110
            case 'menu':
111
                $contentType = $contentTypeLabels[$record['CType']];
112
                $out .= $this->linkEditContent('<strong>' . htmlspecialchars($contentType) . '</strong>', $record) . '<br />';
113
                // Add Menu Type
114
                $menuTypeLabel = $languageService->sL(
115
                    BackendUtility::getLabelFromItemListMerged($record['pid'], 'tt_content', 'menu_type', $record['menu_type'])
116
                );
117
                $menuTypeLabel = $menuTypeLabel ?: 'invalid menu type';
118
                $out .= $this->linkEditContent($menuTypeLabel, $record);
119
                if ($record['menu_type'] !== '2' && ($record['pages'] || $record['selected_categories'])) {
120
                    // Show pages if menu type is not "Sitemap"
121
                    $out .= ':' . $this->linkEditContent($this->generateListForCTypeMenu($record), $record) . '<br />';
122
                }
123
                break;
124
            case 'shortcut':
125
                if (!empty($record['records'])) {
126
                    $shortcutContent = [];
127
                    $recordList = explode(',', $record['records']);
128
                    foreach ($recordList as $recordIdentifier) {
129
                        $split = BackendUtility::splitTable_Uid($recordIdentifier);
130
                        $tableName = empty($split[0]) ? 'tt_content' : $split[0];
131
                        $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

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