334 lines
15 KiB
PHP
334 lines
15 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* It is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* For the full copyright and license information, please read the
|
|
* LICENSE.txt file that was distributed with this source code.
|
|
*
|
|
* The TYPO3 project - inspiring people to share!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Backend\Preview;
|
|
|
|
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
|
use TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
|
|
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
|
use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem;
|
|
use TYPO3\CMS\Backend\View\BackendLayoutView;
|
|
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
|
use TYPO3\CMS\Core\DataHandling\TableColumnType;
|
|
use TYPO3\CMS\Core\Domain\RawRecord;
|
|
use TYPO3\CMS\Core\Domain\Record;
|
|
use TYPO3\CMS\Core\Imaging\IconFactory;
|
|
use TYPO3\CMS\Core\Imaging\IconSize;
|
|
use TYPO3\CMS\Core\Localization\LanguageService;
|
|
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
|
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
|
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
|
|
/**
|
|
* Legacy preview rendering refactored from PageLayoutView.
|
|
* Provided as default preview rendering mechanism via
|
|
* StandardPreviewRendererResolver which detects the renderer
|
|
* based on TCA configuration.
|
|
*
|
|
* Can be replaced by custom implementations by changing this TCA configuration.
|
|
*
|
|
* See also PreviewRendererInterface documentation.
|
|
*/
|
|
#[Autoconfigure(public: true)]
|
|
final readonly class StandardContentPreviewRenderer implements PreviewRendererInterface
|
|
{
|
|
public function __construct(
|
|
private RecordFieldPreviewProcessor $fieldProcessor,
|
|
private TcaSchemaFactory $tcaSchemaFactory,
|
|
private LocalizationRepository $localizationRepository,
|
|
private BackendLayoutView $backendLayoutView,
|
|
private IconFactory $iconFactory,
|
|
) {}
|
|
|
|
public function renderPageModulePreviewHeader(GridColumnItem $item): string
|
|
{
|
|
$record = $item->getRecord()->getRawRecord() ?? $item->getRecord();
|
|
$request = $item->getContext()->getCurrentRequest();
|
|
if (!$this->tcaSchemaFactory->has($record->getFullType())) {
|
|
return '';
|
|
}
|
|
|
|
$schema = $this->tcaSchemaFactory->get($item->getTable());
|
|
$outHeader = '';
|
|
|
|
if ($record->has('header_layout')) {
|
|
$headerLayout = (string)$record->get('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>';
|
|
}
|
|
}
|
|
|
|
$dateLabel = $this->fieldProcessor->prepareFieldWithLabel($record, 'date');
|
|
if ($dateLabel) {
|
|
$outHeader .= '<div class="element-preview-header-date">' . htmlspecialchars(strip_tags($dateLabel)) . ' </div>';
|
|
}
|
|
|
|
if ($schema->hasCapability(TcaSchemaCapability::Label)) {
|
|
$labelFieldName = $schema->getCapability(TcaSchemaCapability::Label)->getPrimaryFieldName();
|
|
$label = $this->fieldProcessor->prepareText($record, $labelFieldName);
|
|
if ($label !== null) {
|
|
$outHeader .= '<div class="element-preview-header-header">' . $this->fieldProcessor->linkToEditForm($label, $record, $request) . '</div>';
|
|
}
|
|
}
|
|
|
|
$subHeader = $this->fieldProcessor->prepareText($record, 'subheader');
|
|
if ($subHeader !== null) {
|
|
$outHeader .= '<div class="element-preview-header-subheader">' . $this->fieldProcessor->linkToEditForm($subHeader, $record, $request) . '</div>';
|
|
}
|
|
|
|
return $outHeader;
|
|
}
|
|
|
|
public function renderPageModulePreviewContent(GridColumnItem $item): string
|
|
{
|
|
$recordObj = $item->getRecord();
|
|
// This preview should only be used for tt_content records.
|
|
if ($recordObj->getMainType() !== 'tt_content') {
|
|
return '';
|
|
}
|
|
|
|
$languageService = $this->getLanguageService();
|
|
$recordType = $recordObj->getRecordType();
|
|
$schema = $this->tcaSchemaFactory->get($recordObj->getMainType());
|
|
|
|
// If the record type is unknown, render a warning message.
|
|
if (!$schema->hasSubSchema($recordType)) {
|
|
$message = sprintf(
|
|
$languageService->sL('core.core:labels.noMatchingValue'),
|
|
$recordType
|
|
);
|
|
return '<span class="badge badge-warning">' . htmlspecialchars($message) . '</span>';
|
|
}
|
|
|
|
if (!$this->backendLayoutView->isCTypeAllowedInColPosByPage(
|
|
$item->getRecordType(),
|
|
$item->getColumn()->getColumnNumber() ?? 0,
|
|
$item->getRecord()->getPid()
|
|
)) {
|
|
$message = sprintf(
|
|
$languageService->sL('core.core:labels.typeNotAllowedInColumn'),
|
|
$recordType
|
|
);
|
|
return '<span class="badge badge-warning">' . htmlspecialchars($message) . '</span>';
|
|
}
|
|
$subSchema = $schema->getSubSchema($recordType);
|
|
$request = $item->getContext()->getCurrentRequest();
|
|
|
|
// Draw preview of the item depending on its record type
|
|
switch ($recordType) {
|
|
case 'header':
|
|
break;
|
|
case 'shortcut':
|
|
if ($recordObj->has('records') && ($records = $recordObj->get('records'))) {
|
|
$shortcutContent = '';
|
|
$shortcutRecords = $records instanceof \Traversable ? $records : [$records];
|
|
foreach ($shortcutRecords as $shortcutRecord) {
|
|
$shortcutTableName = $shortcutRecord->getMainType();
|
|
$row = $shortcutRecord->getRawRecord()?->toArray() ?? [];
|
|
if ($recordObj instanceof Record) {
|
|
$shortcutRecord = $this->translateShortcutRecord($recordObj, $shortcutRecord, $shortcutTableName);
|
|
}
|
|
$icon = $this->iconFactory->getIconForRecord($shortcutTableName, $row, IconSize::SMALL)->render();
|
|
$icon = BackendUtility::wrapClickMenuOnIcon(
|
|
$icon,
|
|
$shortcutTableName,
|
|
$shortcutRecord->getUid(),
|
|
'1'
|
|
);
|
|
$pathToContainingPage = BackendUtility::getRecordPath($row['pid'], $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW), 0);
|
|
$title = BackendUtility::getRecordTitle($shortcutTableName, $row);
|
|
$itemContent = htmlspecialchars($title) . ' <span class="text-variant">[' . $recordObj->getUid() . '] ' . htmlspecialchars($pathToContainingPage) . '</span>';
|
|
$shortcutContent .= '<li class="list-group-item">'
|
|
. $icon
|
|
. ' '
|
|
. $this->fieldProcessor->linkToEditForm($itemContent, $shortcutRecord, $request)
|
|
. '</li>';
|
|
}
|
|
return $shortcutContent !== '' ? '<ul class="list-group">' . $shortcutContent . '</ul>' : '';
|
|
}
|
|
break;
|
|
case 'menu_abstract':
|
|
case 'menu_categorized_content':
|
|
case 'menu_categorized_pages':
|
|
case 'menu_pages':
|
|
case 'menu_recently_updated':
|
|
case 'menu_related_pages':
|
|
case 'menu_section':
|
|
case 'menu_section_pages':
|
|
case 'menu_sitemap':
|
|
case 'menu_sitemap_pages':
|
|
case 'menu_subpages':
|
|
$row = $recordObj->getRawRecord()?->toArray() ?? [];
|
|
if ($recordType !== 'menu_sitemap' && (($row['pages'] ?? false) || ($row['selected_categories'] ?? false))) {
|
|
// Show pages/categories if the menu type is not "Sitemap"
|
|
$content = $this->generateListForMenuContentTypes($row, $recordType);
|
|
return $this->fieldProcessor->linkToEditForm($content, $recordObj, $request);
|
|
}
|
|
break;
|
|
case 'bullets':
|
|
$list = GeneralUtility::trimExplode(LF, $recordObj->get('bodytext') ?? '', true);
|
|
if ($list !== []) {
|
|
switch ($recordObj->get('bullets_type')) {
|
|
case 0:
|
|
$list = array_map(
|
|
static fn(string $item) => '<li>' . htmlspecialchars($item) . '</li>',
|
|
$list
|
|
);
|
|
return '<ul>' . implode(LF, $list) . '</ul>';
|
|
case 1:
|
|
$list = array_map(
|
|
static fn(string $item) => '<li>' . htmlspecialchars($item) . '</li>',
|
|
$list
|
|
);
|
|
return '<ol>' . implode(LF, $list) . '</ol>';
|
|
case 2:
|
|
$list = array_map(
|
|
static fn(string $item): string
|
|
=> (static function () use ($item) {
|
|
$split = GeneralUtility::trimExplode('|', $item, true, 2);
|
|
|
|
return '<dt>' . htmlspecialchars($split[0]) . '</dt>'
|
|
. '<dd>' . htmlspecialchars($split[1] ?? '') . '</dd>';
|
|
})(),
|
|
$list
|
|
);
|
|
return '<dl>' . implode(LF, $list) . '</dl>';
|
|
}
|
|
}
|
|
break;
|
|
case 'html':
|
|
$html = (string)$this->fieldProcessor->preparePlainHtml($recordObj, 'bodytext');
|
|
return $this->fieldProcessor->linkToEditForm($html, $recordObj, $request);
|
|
default:
|
|
$content = (string)$this->fieldProcessor->preparePreviewableHtml($recordObj, 'bodytext');
|
|
foreach ($subSchema->getFieldsOfType(TableColumnType::FILE) as $field) {
|
|
$fieldName = $field->getName();
|
|
if ($recordObj->has($fieldName) && ($image = $recordObj->get($fieldName))) {
|
|
$content .= $this->fieldProcessor->prepareFiles($image);
|
|
}
|
|
}
|
|
return $this->fieldProcessor->linkToEditForm($content, $recordObj, $request);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Render a footer for the record
|
|
*/
|
|
public function renderPageModulePreviewFooter(GridColumnItem $item): string
|
|
{
|
|
$info = [];
|
|
$record = $item->getRecord()->getRawRecord() ?? $item->getRecord();
|
|
$schema = $this->tcaSchemaFactory->get($item->getTable());
|
|
if ($schema->hasCapability(TcaSchemaCapability::RestrictionStartTime)) {
|
|
$info[] = $this->fieldProcessor->prepareFieldWithLabel($record, $schema->getCapability(TcaSchemaCapability::RestrictionStartTime)->getFieldName());
|
|
}
|
|
if ($schema->hasCapability(TcaSchemaCapability::RestrictionEndTime)) {
|
|
$info[] = $this->fieldProcessor->prepareFieldWithLabel($record, $schema->getCapability(TcaSchemaCapability::RestrictionEndTime)->getFieldName());
|
|
}
|
|
if ($schema->hasCapability(TcaSchemaCapability::RestrictionUserGroup)) {
|
|
$info[] = $this->fieldProcessor->prepareFieldWithLabel($record, $schema->getCapability(TcaSchemaCapability::RestrictionUserGroup)->getFieldName());
|
|
}
|
|
if ($record->getMainType() === 'tt_content') {
|
|
foreach (['space_before_class', 'space_after_class'] as $additionalFieldName) {
|
|
$itm = $this->fieldProcessor->prepareFieldWithLabel($record, $additionalFieldName);
|
|
if ($itm !== null) {
|
|
$info[] = $itm;
|
|
}
|
|
}
|
|
}
|
|
if ($schema->hasCapability(TcaSchemaCapability::InternalDescription)) {
|
|
$item = $this->fieldProcessor->prepareField($record, $schema->getCapability(TcaSchemaCapability::InternalDescription)->getFieldName());
|
|
if ($item !== null) {
|
|
$info[] = $item;
|
|
}
|
|
}
|
|
|
|
$info = array_filter($info);
|
|
|
|
if ($info === []) {
|
|
return '';
|
|
}
|
|
|
|
return implode('<br>', $info);
|
|
}
|
|
|
|
public function wrapPageModulePreview(string $previewHeader, string $previewContent, GridColumnItem $item): string
|
|
{
|
|
$previewHeader = $previewHeader ? '<div class="element-preview-header">' . $previewHeader . '</div>' : '';
|
|
$previewContent = $previewContent ? '<div class="element-preview-content">' . $previewContent . '</div>' : '';
|
|
return $previewHeader || $previewContent ? '<div class="element-preview">' . $previewHeader . $previewContent . '</div>' : '';
|
|
}
|
|
|
|
private function translateShortcutRecord(Record $targetRecord, Record $shortcutRecord, string $tableName): RawRecord
|
|
{
|
|
$targetLanguage = ($targetRecord->getLanguageId() ?? 0);
|
|
if ($targetLanguage === 0
|
|
|| !$this->tcaSchemaFactory->get($tableName)->isLanguageAware()
|
|
|| $targetLanguage === ($shortcutRecord->getLanguageId() ?? 0)
|
|
) {
|
|
return $shortcutRecord->getRawRecord();
|
|
}
|
|
|
|
// record is localized - fetch the shortcut record translation, if available
|
|
$shortcutRecordLocalization = $this->localizationRepository->getRecordTranslation($tableName, $shortcutRecord, $targetLanguage);
|
|
return $shortcutRecordLocalization ?? $shortcutRecord->getRawRecord();
|
|
}
|
|
|
|
/**
|
|
* Generates a list of selected pages or categories for the menu content types
|
|
*
|
|
* @param array $record row from pages
|
|
*/
|
|
private function generateListForMenuContentTypes(array $record, string $contentType): string
|
|
{
|
|
$table = 'pages';
|
|
$field = 'pages';
|
|
// get categories instead of pages
|
|
if (str_contains($contentType, 'menu_categorized')) {
|
|
$table = 'sys_category';
|
|
$field = 'selected_categories';
|
|
}
|
|
if (trim($record[$field] ?? '') === '') {
|
|
return '';
|
|
}
|
|
$content = '';
|
|
$uidList = GeneralUtility::intExplode(',', $record[$field], true);
|
|
foreach ($uidList as $uid) {
|
|
$pageRecord = BackendUtility::getRecord($table, $uid);
|
|
if ($pageRecord) {
|
|
$title = BackendUtility::getRecordTitle($table, $pageRecord);
|
|
$pathToContainingPage = BackendUtility::getRecordPath($pageRecord['pid'], $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW), 0);
|
|
$content .= '<li class="list-group-item">' . htmlspecialchars($title) . ' <span class="text-variant">[' . $uid . '] ' . htmlspecialchars($pathToContainingPage) . '</span></li>';
|
|
}
|
|
}
|
|
return $content ? '<ul class="list-group">' . $content . '</ul>' : '';
|
|
}
|
|
|
|
private function getBackendUser(): BackendUserAuthentication
|
|
{
|
|
return $GLOBALS['BE_USER'];
|
|
}
|
|
|
|
private function getLanguageService(): LanguageService
|
|
{
|
|
return $GLOBALS['LANG'];
|
|
}
|
|
}
|