From 8f07071667fe7a3015cb48678dad013ef024a78a Mon Sep 17 00:00:00 2001 From: Nikita Hovratov <nikita.h@live.de> Date: Fri, 12 Jan 2024 00:02:08 +0100 Subject: [PATCH] [TASK] Reduce hard-coded `tt_content` table in StandardPreviewRenderer The goal here is to have a generic PreviewRenderer, which is not tt_content specific. The StandardContentPreviewRenderer could then extend this generic renderer. This is a pre-patch to make the refactoring easier in the next patch. For this to work, GridColumnItem now exposes methods to retrieve the table from the context, the type column and the record type. Additionally, some drive-by refactorings have been done like early return statements and stricter checks. The unused variable $infoArr has been removed as well. Resolves: #102825 Releases: main, 12.4 Change-Id: I38eab3c095e06b789915600e79b7831556a25ca5 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/84705 Tested-by: core-ci <typo3@b13.com> Tested-by: Oliver Bartsch <bo@cedev.de> Reviewed-by: Oliver Bartsch <bo@cedev.de> --- .../StandardContentPreviewRenderer.php | 237 +++++++++++------- .../BackendLayout/Grid/GridColumnItem.php | 28 ++- 2 files changed, 165 insertions(+), 100 deletions(-) diff --git a/typo3/sysext/backend/Classes/Preview/StandardContentPreviewRenderer.php b/typo3/sysext/backend/Classes/Preview/StandardContentPreviewRenderer.php index c1ee46dcd44d..af323ef23298 100644 --- a/typo3/sysext/backend/Classes/Preview/StandardContentPreviewRenderer.php +++ b/typo3/sysext/backend/Classes/Preview/StandardContentPreviewRenderer.php @@ -72,52 +72,76 @@ class StandardContentPreviewRenderer implements PreviewRendererInterface, Logger { $record = $item->getRecord(); $itemLabels = $item->getContext()->getItemLabels(); - + $table = $item->getTable(); $outHeader = ''; - $outHeader .= $record['header_layout'] == 100 - ? '<div class="element-preview-header-status">' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:header_layout.I.6')) . '</div>' - : ''; - $outHeader .= $record['date'] - ? '<div class="element-preview-header-date">' . htmlspecialchars($itemLabels['date'] . ' ' . BackendUtility::date($record['date'])) . ' </div>' - : ''; - $outHeader .= $record['header'] - ? '<div class="element-preview-header-header">' . $this->linkEditContent($this->renderText($record['header']), $record) . '</div>' - : ''; - $outHeader .= $record['subheader'] - ? '<div class="element-preview-header-subheader">' . $this->linkEditContent($this->renderText($record['subheader']), $record) . '</div>' - : ''; + + $headerLayout = (string)($record['header_layout'] ?? ''); + if ($headerLayout === '100') { + $headerLayoutHiddenLabel = $this->getLanguageService()->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:header_layout.I.6'); + $outHeader .= '<div class="element-preview-header-status">' . htmlspecialchars($headerLayoutHiddenLabel) . '</div>'; + } + + $date = (string)($record['date'] ?? ''); + if ($date !== '0' && $date !== '') { + $dateLabel = $itemLabels['date'] . ' ' . BackendUtility::date($record['date']); + $outHeader .= '<div class="element-preview-header-date">' . htmlspecialchars($dateLabel) . ' </div>'; + } + + $labelField = $GLOBALS['TCA'][$table]['ctrl']['label'] ?? ''; + $label = (string)($record[$labelField] ?? ''); + if ($label !== '') { + $outHeader .= '<div class="element-preview-header-header">' . $this->linkEditContent($this->renderText($label), $record, $table) . '</div>'; + } + + $subHeader = (string)($record['subheader'] ?? ''); + if ($subHeader !== '') { + $outHeader .= '<div class="element-preview-header-subheader">' . $this->linkEditContent($this->renderText($subHeader), $record) . '</div>'; + } return $outHeader; } public function renderPageModulePreviewContent(GridColumnItem $item): string { - $out = ''; + $recordType = $item->getRecordType(); + $languageService = $this->getLanguageService(); + $table = $item->getTable(); $record = $item->getRecord(); + $out = ''; - $contentTypeLabels = $item->getContext()->getContentTypeLabels(); - $languageService = $this->getLanguageService(); + // If record type is unknown, render warning message. + if ($item->getTypeColumn() !== '' && !is_array($GLOBALS['TCA'][$table]['types'][$recordType] ?? null)) { + $message = sprintf( + $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'), + $recordType + ); + $out .= '<span class="badge badge-warning">' . htmlspecialchars($message) . '</span>'; + return $out; + } - // Check if a Fluid-based preview template was defined for this CType + // Check if a Fluid-based preview template was defined for this record type // and render it via Fluid. Possible option: // mod.web_layout.tt_content.preview.media = EXT:site_mysite/Resources/Private/Templates/Preview/Media.html - $infoArr = []; - $this->getProcessedValue($item, 'header_position,header_layout,header_link', $infoArr); - $tsConfig = BackendUtility::getPagesTSconfig($record['pid'])['mod.']['web_layout.']['tt_content.']['preview.'] ?? []; - if (!empty($tsConfig[$record['CType']]) || !empty($tsConfig[$record['CType'] . '.'])) { - $fluidPreview = $this->renderContentElementPreviewFromFluidTemplate($record); + $tsConfig = BackendUtility::getPagesTSconfig($record['pid'])['mod.']['web_layout.'][$table . '.']['preview.'] ?? []; + if (!empty($tsConfig[$recordType]) || !empty($tsConfig[$recordType . '.'])) { + $fluidPreview = $this->renderContentElementPreviewFromFluidTemplate($record, $item); if ($fluidPreview !== null) { return $fluidPreview; } } - // Draw preview of the item depending on its CType - switch ($record['CType']) { + // This preview should only be used for tt_content records. + if ($table !== 'tt_content') { + return $out; + } + + // Draw preview of the item depending on its record type + switch ($recordType) { case 'header': break; case 'uploads': if ($record['media']) { - $out .= $this->linkEditContent($this->getThumbCodeUnlinked($record, 'tt_content', 'media'), $record); + $out .= $this->linkEditContent($this->getThumbCodeUnlinked($record, $table, 'media'), $record); } break; case 'shortcut': @@ -126,18 +150,18 @@ class StandardContentPreviewRenderer implements PreviewRendererInterface, Logger $recordList = explode(',', $record['records']); foreach ($recordList as $recordIdentifier) { $split = BackendUtility::splitTable_Uid($recordIdentifier); - $tableName = empty($split[0]) ? 'tt_content' : $split[0]; - $shortcutRecord = BackendUtility::getRecord($tableName, $split[1]); + $shortcutTableName = empty($split[0]) ? $table : $split[0]; + $shortcutRecord = BackendUtility::getRecord($shortcutTableName, $split[1]); if (is_array($shortcutRecord)) { - $shortcutRecord = $this->translateShortcutRecord($record, $shortcutRecord, $tableName, (int)$split[1]); - $icon = $this->getIconFactory()->getIconForRecord($tableName, $shortcutRecord, Icon::SIZE_SMALL)->render(); + $shortcutRecord = $this->translateShortcutRecord($record, $shortcutRecord, $shortcutTableName, (int)$split[1]); + $icon = $this->getIconFactory()->getIconForRecord($shortcutTableName, $shortcutRecord, Icon::SIZE_SMALL)->render(); $icon = BackendUtility::wrapClickMenuOnIcon( $icon, - $tableName, + $shortcutTableName, $shortcutRecord['uid'], '1' ); - $shortcutContent .= '<li class="list-group-item">' . $icon . ' ' . htmlspecialchars(BackendUtility::getRecordTitle($tableName, $shortcutRecord)) . '</li>'; + $shortcutContent .= '<li class="list-group-item">' . $icon . ' ' . htmlspecialchars(BackendUtility::getRecordTitle($shortcutTableName, $shortcutRecord)) . '</li>'; } } $out .= $shortcutContent ? '<ul class="list-group">' . $shortcutContent . '</ul>' : ''; @@ -145,7 +169,7 @@ class StandardContentPreviewRenderer implements PreviewRendererInterface, Logger break; case 'list': if (!empty($record['list_type'])) { - $label = BackendUtility::getLabelFromItemListMerged((int)$record['pid'], 'tt_content', 'list_type', $record['list_type'], $record); + $label = BackendUtility::getLabelFromItemListMerged((int)$record['pid'], $table, 'list_type', $record['list_type'], $record); if (!empty($label)) { $out .= $this->linkEditContent('<strong>' . htmlspecialchars($languageService->sL($label)) . '</strong>', $record); } else { @@ -157,20 +181,11 @@ class StandardContentPreviewRenderer implements PreviewRendererInterface, Logger } break; default: - $contentTypeLabel = (string)($contentTypeLabels[$record['CType']] ?? ''); - if ($contentTypeLabel === '') { - $message = sprintf( - $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue'), - $record['CType'] - ); - $out .= '<span class="badge badge-warning">' . htmlspecialchars($message) . '</span>'; - break; - } // Handle menu content types - if (in_array($record['CType'], self::MENU_CONTENT_TYPES, true)) { - if ($record['CType'] !== 'menu_sitemap' && (($record['pages'] ?? false) || ($record['selected_categories'] ?? false))) { + if (in_array($recordType, self::MENU_CONTENT_TYPES, true)) { + if ($recordType !== 'menu_sitemap' && (($record['pages'] ?? false) || ($record['selected_categories'] ?? false))) { // Show pages/categories if menu type is not "Sitemap" - $out .= $this->linkEditContent($this->generateListForMenuContentTypes($record), $record); + $out .= $this->linkEditContent($this->generateListForMenuContentTypes($record, $recordType), $record); } break; } @@ -178,7 +193,7 @@ class StandardContentPreviewRenderer implements PreviewRendererInterface, Logger $out .= $this->linkEditContent($this->renderText($record['bodytext']), $record); } if ($record['image']) { - $out .= $this->linkEditContent($this->getThumbCodeUnlinked($record, 'tt_content', 'image'), $record); + $out .= $this->linkEditContent($this->getThumbCodeUnlinked($record, $table, 'image'), $record); } } @@ -192,13 +207,38 @@ class StandardContentPreviewRenderer implements PreviewRendererInterface, Logger { $info = []; $record = $item->getRecord(); - $this->getProcessedValue($item, 'starttime,endtime,fe_group,space_before_class,space_after_class', $info); + $table = $item->getTable(); + $fieldList = []; + $startTimeField = (string)($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['starttime'] ?? ''); + if ($startTimeField !== '') { + $fieldList[] = $startTimeField; + } + $endTimeField = (string)($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['endtime'] ?? ''); + if ($endTimeField !== '') { + $fieldList[] = $endTimeField; + } + $feGroupField = (string)($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['fe_group'] ?? ''); + if ($feGroupField !== '') { + $fieldList[] = $feGroupField; + } + if ($table === 'tt_content') { + if (is_array($GLOBALS['TCA'][$table]['columns']['space_before_class'] ?? null)) { + $fieldList[] = 'space_before_class'; + } + if (is_array($GLOBALS['TCA'][$table]['columns']['space_after_class'] ?? null)) { + $fieldList[] = 'space_after_class'; + } + } + if ($fieldList === []) { + return ''; + } + $this->getProcessedValue($item, implode(',', $fieldList), $info); - if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']) && !empty($record[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']])) { - $info[] = htmlspecialchars($record[$GLOBALS['TCA']['tt_content']['ctrl']['descriptionColumn']]); + if (!empty($GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']) && !empty($record[$GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']])) { + $info[] = htmlspecialchars($record[$GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']]); } - if (!empty($info)) { + if ($info !== []) { return implode('<br>', $info); } return ''; @@ -239,60 +279,73 @@ class StandardContentPreviewRenderer implements PreviewRendererInterface, Logger { $itemLabels = $item->getContext()->getItemLabels(); $record = $item->getRecord(); + $table = $item->getTable(); $fieldArr = explode(',', $fieldList); foreach ($fieldArr as $field) { if ($record[$field]) { - $fieldValue = BackendUtility::getProcessedValue('tt_content', $field, $record[$field], 0, false, false, $record['uid'] ?? 0, true, $record['pid'] ?? 0) ?? ''; + $fieldValue = BackendUtility::getProcessedValue($table, $field, $record[$field], 0, false, false, $record['uid'] ?? 0, true, $record['pid'] ?? 0) ?? ''; $info[] = '<strong>' . htmlspecialchars((string)($itemLabels[$field] ?? '')) . '</strong> ' . htmlspecialchars($fieldValue); } } } - protected function renderContentElementPreviewFromFluidTemplate(array $row): ?string + protected function renderContentElementPreviewFromFluidTemplate(array $row, ?GridColumnItem $item = null): ?string { - $tsConfig = BackendUtility::getPagesTSconfig($row['pid'])['mod.']['web_layout.']['tt_content.']['preview.'] ?? []; + // Backwards compatibility for call of this method with only 1 parameter. + $recordType = $item?->getRecordType() ?? $row['CType'] ?? null; + if ($recordType === null) { + return null; + } + $table = $item?->getTable() ?? 'tt_content'; + $tsConfig = BackendUtility::getPagesTSconfig($row['pid'])['mod.']['web_layout.'][$table . '.']['preview.'] ?? []; $fluidTemplateFile = ''; - if ($row['CType'] === 'list' && !empty($row['list_type']) + if ( + $table === 'tt_content' + && $recordType === 'list' + && !empty($row['list_type']) && !empty($tsConfig['list.'][$row['list_type']]) ) { $fluidTemplateFile = $tsConfig['list.'][$row['list_type']]; - } elseif (!empty($tsConfig[$row['CType']])) { - $fluidTemplateFile = $tsConfig[$row['CType']]; + } elseif (!empty($tsConfig[$recordType])) { + $fluidTemplateFile = $tsConfig[$recordType]; } - if ($fluidTemplateFile) { - $fluidTemplateFile = GeneralUtility::getFileAbsFileName($fluidTemplateFile); - if ($fluidTemplateFile) { - try { - $view = GeneralUtility::makeInstance(StandaloneView::class); - $view->setTemplatePathAndFilename($fluidTemplateFile); - $view->assignMultiple($row); - if (!empty($row['pi_flexform'])) { - $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); - $view->assign('pi_flexform_transformed', $flexFormService->convertFlexFormContentToArray($row['pi_flexform'])); - } - return $view->render(); - } catch (\Exception $e) { - $this->logger->warning('The backend preview for content element {uid} can not be rendered using the Fluid template file "{file}"', [ - 'uid' => $row['uid'], - 'file' => $fluidTemplateFile, - 'exception' => $e, - ]); - - if ($this->getBackendUser()->shallDisplayDebugInformation()) { - $view = GeneralUtility::makeInstance(StandaloneView::class); - $view->assign('error', [ - 'message' => str_replace(Environment::getProjectPath(), '', $e->getMessage()), - 'title' => 'Error while rendering FluidTemplate preview using ' . str_replace(Environment::getProjectPath(), '', $fluidTemplateFile), - ]); - $view->setTemplateSource('<f:be.infobox title="{error.title}" state="2">{error.message}</f:be.infobox>'); - return $view->render(); - } - } + if ($fluidTemplateFile === '') { + return null; + } + + $fluidTemplateFileAbsolutePath = GeneralUtility::getFileAbsFileName($fluidTemplateFile); + if ($fluidTemplateFileAbsolutePath === '') { + return null; + } + try { + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplatePathAndFilename($fluidTemplateFileAbsolutePath); + $view->assignMultiple($row); + if ($table === 'tt_content' && !empty($row['pi_flexform'])) { + $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); + $view->assign('pi_flexform_transformed', $flexFormService->convertFlexFormContentToArray($row['pi_flexform'])); + } + return $view->render(); + } catch (\Exception $e) { + $this->logger->warning('The backend preview for content element {uid} can not be rendered using the Fluid template file "{file}"', [ + 'uid' => $row['uid'], + 'file' => $fluidTemplateFileAbsolutePath, + 'exception' => $e, + ]); + + if ($this->getBackendUser()->shallDisplayDebugInformation()) { + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->assign('error', [ + 'message' => str_replace(Environment::getProjectPath(), '', $e->getMessage()), + 'title' => 'Error while rendering FluidTemplate preview using ' . str_replace(Environment::getProjectPath(), '', $fluidTemplateFileAbsolutePath), + ]); + $view->setTemplateSource('<f:be.infobox title="{error.title}" state="2">{error.message}</f:be.infobox>'); + return $view->render(); } + return null; } - return null; } /** @@ -326,12 +379,12 @@ class StandardContentPreviewRenderer implements PreviewRendererInterface, Logger * * @param array $record row from pages */ - protected function generateListForMenuContentTypes(array $record): string + protected function generateListForMenuContentTypes(array $record, string $contentType): string { $table = 'pages'; $field = 'pages'; // get categories instead of pages - if (str_contains($record['CType'], 'menu_categorized')) { + if (str_contains($contentType, 'menu_categorized')) { $table = 'sys_category'; $field = 'selected_categories'; } @@ -358,24 +411,24 @@ class StandardContentPreviewRenderer implements PreviewRendererInterface, Logger * @param array $row The row. * @return string If the whole thing was editable and $linkText is not empty $linkText is returned with link around. Otherwise just $linkText. */ - protected function linkEditContent(string $linkText, $row): string + protected function linkEditContent(string $linkText, $row, string $table = 'tt_content'): string { if (empty($linkText)) { return $linkText; } $backendUser = $this->getBackendUser(); - if ($backendUser->check('tables_modify', 'tt_content') - && $backendUser->recordEditAccessInternals('tt_content', $row) + if ($backendUser->check('tables_modify', $table) + && $backendUser->recordEditAccessInternals($table, $row) && (new Permission($backendUser->calcPerms(BackendUtility::getRecord('pages', $row['pid']) ?? [])))->editContentPermissionIsGranted() ) { $urlParameters = [ 'edit' => [ - 'tt_content' => [ + $table => [ $row['uid'] => 'edit', ], ], - 'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri() . '#element-tt_content-' . $row['uid'], + 'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri() . '#element-' . $table . '-' . $row['uid'], ]; $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters); diff --git a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumnItem.php b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumnItem.php index 0f01eb538ed0..a8291a15518d 100644 --- a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumnItem.php +++ b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumnItem.php @@ -168,10 +168,12 @@ class GridColumnItem extends AbstractGridObject if (($typeColumn = $this->getTypeColumn()) === '') { return ''; } - $contentType = $this->record[$typeColumn] ?? ''; + if (($recordType = $this->getRecordType()) === '') { + return ''; + } $contentTypeLabels = $this->context->getContentTypeLabels(); - return $contentTypeLabels[$contentType] ?? - BackendUtility::getLabelFromItemListMerged((int)($this->record['pid'] ?? 0), $this->table, $typeColumn, $contentType, $this->record); + return $contentTypeLabels[$recordType] ?? + BackendUtility::getLabelFromItemListMerged((int)($this->record['pid'] ?? 0), $this->table, $typeColumn, $recordType, $this->record); } public function getIcons(): string @@ -351,6 +353,21 @@ class GridColumnItem extends AbstractGridObject return $uriBuilder->buildUriFromRoute('record_edit', $urlParameters) . '#element-' . $this->table . '-' . $this->record['uid']; } + public function getTypeColumn(): string + { + return (string)($GLOBALS['TCA'][$this->table]['ctrl']['type'] ?? ''); + } + + public function getRecordType(): string + { + return (string)($this->record[$this->getTypeColumn()] ?? ''); + } + + public function getTable(): string + { + return $this->table; + } + /** * Gets the number of records referencing the record with the UID $uid in * the current table. @@ -361,9 +378,4 @@ class GridColumnItem extends AbstractGridObject { return GeneralUtility::makeInstance(ReferenceIndex::class)->getNumberOfReferencedRecords($this->table, $uid); } - - protected function getTypeColumn(): string - { - return (string)($GLOBALS['TCA'][$this->table]['ctrl']['type'] ?? ''); - } } -- GitLab