TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<?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 Psr\Log\LoggerInterface;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Backend\View\Event\PageContentPreviewRenderingEvent;
|
||||
use TYPO3\CMS\Core\Attribute\AsEventListener;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\View\ViewFactoryData;
|
||||
use TYPO3\CMS\Core\View\ViewFactoryInterface;
|
||||
|
||||
/**
|
||||
* Check if a Fluid-based preview template was defined for a given CType and render it via Fluid.
|
||||
*
|
||||
* Example in page TSconfig:
|
||||
* mod.web_layout.tt_content.preview.textmedia = EXT:site_mysite/Resources/Private/Templates/Preview/Textmedia.fluid.html
|
||||
*
|
||||
* @internal not part of the TYPO3 Core API
|
||||
*/
|
||||
final readonly class FluidBasedContentPreviewRenderer
|
||||
{
|
||||
public function __construct(
|
||||
private LoggerInterface $logger,
|
||||
private ViewFactoryInterface $viewFactory,
|
||||
) {}
|
||||
|
||||
#[AsEventListener('typo3-backend/fluid-preview/content')]
|
||||
public function __invoke(PageContentPreviewRenderingEvent $event): void
|
||||
{
|
||||
$record = $event->getRecord();
|
||||
$context = $event->getPageLayoutContext();
|
||||
$fluidTemplateFile = BackendUtility::getPagesTSconfig($record->getPid())['mod.']['web_layout.'][$event->getTable() . '.']['preview.'][$event->getRecordType()] ?? '';
|
||||
if ($fluidTemplateFile === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$fluidTemplateFileAbsolutePath = GeneralUtility::getFileAbsFileName($fluidTemplateFile);
|
||||
if ($fluidTemplateFileAbsolutePath === '') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$event->setPreviewContent(
|
||||
$this->viewFactory
|
||||
->create(new ViewFactoryData(templatePathAndFilename: $fluidTemplateFileAbsolutePath, request: $context->getCurrentRequest()))
|
||||
->assign('record', $record)
|
||||
->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' => $record->getUid(),
|
||||
'file' => $fluidTemplateFileAbsolutePath,
|
||||
'exception' => $e,
|
||||
]);
|
||||
if ($this->getBackendUser()->shallDisplayDebugInformation()) {
|
||||
$event->setPreviewContent(
|
||||
$this->viewFactory
|
||||
->create(new ViewFactoryData(templatePathAndFilename: 'EXT:backend/Resources/Private/Templates/PageLayout/FluidBasedContentPreviewRenderingException.fluid.html'))
|
||||
->assign('error', [
|
||||
'message' => str_replace(Environment::getProjectPath(), '', $e->getMessage()),
|
||||
'title' => 'Error while rendering FluidTemplate preview using ' . str_replace(Environment::getProjectPath(), '', $fluidTemplateFileAbsolutePath),
|
||||
])
|
||||
->render()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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 TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem;
|
||||
|
||||
/**
|
||||
* Interface PreviewRendererInterface
|
||||
*
|
||||
* Contract for classes capable of rendering previews of a given record
|
||||
* from a table. Responsible for rendering preview header, preview content
|
||||
* and wrapping of those two values.
|
||||
*
|
||||
* Responsibilities are segmented into three methods, one for each responsibility,
|
||||
* which is done in order to allow overriding classes to change those parts
|
||||
* individually without having to replace other parts. Rather than relying on
|
||||
* implementations to be friendly and divide code into smaller pieces and
|
||||
* give them (at least) protected visibility, the key methods are instead required
|
||||
* on the interface directly.
|
||||
*
|
||||
* Callers are then responsible for calling each method and combining/wrapping
|
||||
* the output appropriately.
|
||||
*/
|
||||
interface PreviewRendererInterface
|
||||
{
|
||||
/**
|
||||
* Dedicated method for rendering preview header HTML for
|
||||
* the page module only. Receives the GridColumnItem
|
||||
* that contains the record for which a preview header
|
||||
* should be rendered and returned.
|
||||
*/
|
||||
public function renderPageModulePreviewHeader(GridColumnItem $item): string;
|
||||
|
||||
/**
|
||||
* Dedicated method for rendering preview body HTML for
|
||||
* the page module only. Receives the GridColumnItem
|
||||
* that contains the record for which a preview should be
|
||||
* rendered and returned.
|
||||
*/
|
||||
public function renderPageModulePreviewContent(GridColumnItem $item): string;
|
||||
|
||||
/**
|
||||
* Render a footer for the record to display in page module below
|
||||
* the body of the item's preview.
|
||||
*/
|
||||
public function renderPageModulePreviewFooter(GridColumnItem $item): string;
|
||||
|
||||
/**
|
||||
* Dedicated method for wrapping a preview header and body
|
||||
* HTML. Receives $item, an instance of GridColumnItem holding
|
||||
* among other things the record, which can be used to determine
|
||||
* appropriate wrapping.
|
||||
*/
|
||||
public function wrapPageModulePreview(string $previewHeader, string $previewContent, GridColumnItem $item): string;
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
<?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 Psr\Http\Message\ServerRequestInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\DataHandling\TableColumnType;
|
||||
use TYPO3\CMS\Core\Domain\RecordInterface;
|
||||
use TYPO3\CMS\Core\Html\SanitizerBuilderFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Resource\FileReference;
|
||||
use TYPO3\CMS\Core\Resource\ProcessedFile;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Use this to render certain fields (label + values) as kind of shortcut or helper
|
||||
* methods when implementing your own Preview Renderer.
|
||||
*
|
||||
* If you need your custom rendering, build your own renderer for your own PreviewRenderer
|
||||
* class that you can then inject.
|
||||
*
|
||||
* The result is always HTML, and it's always HSCed -> ready to be rendered.
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
final readonly class RecordFieldPreviewProcessor
|
||||
{
|
||||
public function __construct(
|
||||
private TcaSchemaFactory $schemaFactory,
|
||||
private UriBuilder $uriBuilder,
|
||||
private IconFactory $iconFactory,
|
||||
private SanitizerBuilderFactory $sanitizerBuilderFactory,
|
||||
#[Autowire(service: 'cache.runtime')]
|
||||
private FrontendInterface $runtimeCache,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Prepare the value of a field but prepend the label before.
|
||||
*/
|
||||
public function prepareFieldWithLabel(RecordInterface $record, string $fieldName): ?string
|
||||
{
|
||||
if ($record->has($fieldName)) {
|
||||
$table = $record->getMainType();
|
||||
$value = $record->get($fieldName);
|
||||
if ($value !== '' && $value !== null) {
|
||||
$itemLabels = $this->getItemLabels($record);
|
||||
$fieldValue = BackendUtility::getProcessedValue($table, $fieldName, $value, 0, false, false, $record->getUid(), true, $record->getPid(), $record->getRawRecord()->toArray()) ?? '';
|
||||
if ($fieldValue !== '') {
|
||||
return htmlspecialchars((string)($itemLabels[$fieldName] ?? '')) . ': ' . htmlspecialchars((string)$fieldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the value of a field if it exists and it is not empty.
|
||||
*/
|
||||
public function prepareField(RecordInterface $record, string $fieldName): ?string
|
||||
{
|
||||
if ($record->has($fieldName)) {
|
||||
$table = $record->getMainType();
|
||||
$value = $record->get($fieldName);
|
||||
if ($value !== '' && $value !== null) {
|
||||
$fieldValue = BackendUtility::getProcessedValue($table, $fieldName, $value, 0, false, false, $record->getUid(), true, $record->getPid(), $record->getRawRecord()->toArray()) ?? '';
|
||||
if ($fieldValue !== '') {
|
||||
return htmlspecialchars((string)$fieldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processing of larger amounts of text (usually from RTE/bodytext fields) with word wrapping, etc.
|
||||
*/
|
||||
public function prepareText(RecordInterface $record, string $fieldName, int $maxLength = 1500): ?string
|
||||
{
|
||||
if ($record->has($fieldName)) {
|
||||
$input = $record->get($fieldName);
|
||||
$schemaForType = $this->schemaFactory->get($record->getFullType());
|
||||
if (is_string($input) && $input !== '' && $schemaForType->hasField($fieldName)) {
|
||||
$isSimpleText = $schemaForType->getField($fieldName)->isType(TableColumnType::INPUT);
|
||||
if (!$isSimpleText) {
|
||||
$input = strip_tags($input);
|
||||
}
|
||||
$input = GeneralUtility::fixed_lgd_cs($input, $maxLength);
|
||||
return nl2br(htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8', false));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function preparePlainHtml(RecordInterface $record, string $fieldName, int $maxLines = 100): ?string
|
||||
{
|
||||
if ($record->has($fieldName)) {
|
||||
$html = GeneralUtility::trimExplode(LF, (string)$record->get($fieldName), true);
|
||||
if ($html !== []) {
|
||||
$html = array_slice($html, 0, $maxLines);
|
||||
return str_replace(LF, '<br />', htmlspecialchars(implode(LF, $html)));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function preparePreviewableHtml(RecordInterface $record, string $fieldName): ?string
|
||||
{
|
||||
if ($record->has($fieldName)) {
|
||||
$content = $record->get($fieldName);
|
||||
if (!is_string($content)) {
|
||||
return null;
|
||||
}
|
||||
$builder = $this->sanitizerBuilderFactory->build('preview');
|
||||
return $builder->build()->sanitize($content);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render thumbnails for a file collection or files.
|
||||
*/
|
||||
public function prepareFiles(iterable|FileReference $fileReferences): ?string
|
||||
{
|
||||
$thumbData = [];
|
||||
$fileReferences = $fileReferences instanceof FileReference ? [$fileReferences] : $fileReferences;
|
||||
foreach ($fileReferences as $fileReferenceObject) {
|
||||
// Do not show previews of hidden references
|
||||
if ($fileReferenceObject->getProperty('hidden')) {
|
||||
continue;
|
||||
}
|
||||
$fileObject = $fileReferenceObject->getOriginalFile();
|
||||
if ($fileObject->isMissing()) {
|
||||
$thumbData[] = $this->iconFactory
|
||||
->getIcon('mimetypes-other-other', IconSize::MEDIUM, 'overlay-missing')
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing') . ' ' . $fileObject->getName())
|
||||
->render();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Preview web image or media elements
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']
|
||||
&& ($fileReferenceObject->getOriginalFile()->isImage() || $fileReferenceObject->getOriginalFile()->isMediaFile())
|
||||
) {
|
||||
$cropVariantCollection = CropVariantCollection::create((string)$fileReferenceObject->getProperty('crop'));
|
||||
$cropArea = $cropVariantCollection->getCropArea();
|
||||
$processingConfiguration = [
|
||||
'maxWidth' => 64,
|
||||
'maxHeight' => 64,
|
||||
];
|
||||
if (!$cropArea->isEmpty()) {
|
||||
$processingConfiguration = [
|
||||
'maxWidth' => 64,
|
||||
'maxHeight' => 64,
|
||||
'crop' => $cropArea->makeAbsoluteBasedOnFile($fileReferenceObject),
|
||||
];
|
||||
}
|
||||
$processedImage = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
|
||||
$attributes = [
|
||||
'src' => $processedImage->getPublicUrl() ?? '',
|
||||
'width' => $processedImage->getProperty('width'),
|
||||
'height' => $processedImage->getProperty('height'),
|
||||
'alt' => $fileReferenceObject->getAlternative() ?: $fileReferenceObject->getName(),
|
||||
'loading' => 'lazy',
|
||||
];
|
||||
$thumbData[] = '<img ' . GeneralUtility::implodeAttributes($attributes, true) . '/>';
|
||||
} else {
|
||||
$thumbData[] = $this->iconFactory->getIconForResource($fileObject)->setTitle($fileObject->getName())->render();
|
||||
}
|
||||
}
|
||||
|
||||
if ($thumbData !== []) {
|
||||
$result = '';
|
||||
foreach ($thumbData as $thumbDataItem) {
|
||||
$result .= '<div class="preview-thumbnails-element"><div class="preview-thumbnails-element-image">' . $thumbDataItem . '</div></div>';
|
||||
}
|
||||
return '<div class="preview-thumbnails">' . $result . '</div>';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create a link on the input string and possibly a big button after the string which links to editing in the
|
||||
* RTE. Used for content element content displayed so the user can click the content / "Edit in Rich Text Editor"
|
||||
* button
|
||||
*
|
||||
* @param string $linkText String to link. Must be prepared for HTML output.
|
||||
* @return string If the whole thing was editable and $linkText is not empty $linkText is returned with the link
|
||||
* around. Otherwise just $linkText.
|
||||
*/
|
||||
public function linkToEditForm(string $linkText, RecordInterface $record, ServerRequestInterface $request): string
|
||||
{
|
||||
if ($linkText === '') {
|
||||
return $linkText;
|
||||
}
|
||||
$table = $record->getMainType();
|
||||
$backendUser = $this->getBackendUser();
|
||||
if ($backendUser->check('tables_modify', $table)
|
||||
&& $backendUser->checkRecordEditAccess($table, $record)->isAllowed
|
||||
&& (new Permission($backendUser->calcPerms(BackendUtility::getRecord('pages', $record->getPid()) ?? [])))->editContentPermissionIsGranted()
|
||||
) {
|
||||
$returnUrl = $request->getAttribute('normalizedParams')->getRequestUri() . '#element-' . $table . '-' . $record->getUid();
|
||||
$editParams = [
|
||||
'edit' => [$table => [$record->getUid() => 'edit']],
|
||||
'returnUrl' => $returnUrl,
|
||||
];
|
||||
return '<typo3-backend-contextual-record-edit-trigger'
|
||||
. ' url="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('record_edit_contextual', $editParams)) . '"'
|
||||
. ' edit-url="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('record_edit', $editParams)) . '"'
|
||||
. ' title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:edit')) . '"'
|
||||
. '>' . $linkText . '</typo3-backend-contextual-record-edit-trigger>';
|
||||
}
|
||||
return $linkText;
|
||||
}
|
||||
|
||||
private function getItemLabels(RecordInterface $record): array
|
||||
{
|
||||
$mainType = $record->getMainType();
|
||||
$cacheIdentifier = 'recordfieldpreviewprocessor-' . $mainType;
|
||||
$itemLabels = $this->runtimeCache->get($cacheIdentifier);
|
||||
if ($itemLabels === false) {
|
||||
$itemLabels = [];
|
||||
foreach ($this->schemaFactory->get($mainType)->getFields() as $field) {
|
||||
$itemLabels[$field->getName()] = $this->getLanguageService()->sL($field->getLabel());
|
||||
}
|
||||
$this->runtimeCache->set($cacheIdentifier, $itemLabels);
|
||||
}
|
||||
return $itemLabels;
|
||||
}
|
||||
|
||||
private function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
private function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
<?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'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?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\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Domain\RecordInterface;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Scans TCA configuration to detect:
|
||||
*
|
||||
* - TCA.$table.types.$typeFromTypeField.previewRenderer
|
||||
* - TCA.$table.ctrl.previewRenderer
|
||||
*
|
||||
* Depending on which one is defined and checking the first, type-specific
|
||||
* variant first.
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
readonly class StandardPreviewRendererResolver
|
||||
{
|
||||
public function __construct(
|
||||
protected TcaSchemaFactory $tcaSchemaFactory
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param RecordInterface $record A record from $table which will be previewed - allows returning a different PreviewRenderer based on record attributes
|
||||
* @throws \UnexpectedValueException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function resolveRendererFor(RecordInterface $record): PreviewRendererInterface
|
||||
{
|
||||
$table = $record->getMainType();
|
||||
$row = $record->getRawRecord()?->toArray() ?? [];
|
||||
$schema = $this->tcaSchemaFactory->get($table);
|
||||
$previewRendererClassName = null;
|
||||
if ($schema->supportsSubSchema()) {
|
||||
$tcaTypeOfRow = '';
|
||||
$subSchemaTypeInformation = $schema->getSubSchemaTypeInformation();
|
||||
if ($subSchemaTypeInformation->isPointerToForeignFieldInForeignSchema()) {
|
||||
if ($this->tcaSchemaFactory->has($subSchemaTypeInformation->getForeignSchemaName())) {
|
||||
// Note: We override the schema here to work on the foreign schema from now on.
|
||||
$schema = $this->tcaSchemaFactory->get($subSchemaTypeInformation->getForeignSchemaName());
|
||||
if (isset($row[$subSchemaTypeInformation->getFieldName()]) && $schema->hasField($subSchemaTypeInformation->getForeignFieldName())) {
|
||||
$foreignRecord = BackendUtility::getRecord($subSchemaTypeInformation->getForeignSchemaName(), $row[$subSchemaTypeInformation->getFieldName()], $subSchemaTypeInformation->getForeignFieldName());
|
||||
$tcaTypeOfRow = (string)($foreignRecord[$subSchemaTypeInformation->getForeignFieldName()] ?? '');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$tcaTypeOfRow = (string)($row[$subSchemaTypeInformation->getFieldName()] ?? '');
|
||||
}
|
||||
if ($schema->hasSubSchema($tcaTypeOfRow)) {
|
||||
// Outdated subschemas may still be present in the database fields, this must not block backend rendering and utilize fallback.
|
||||
$subSchema = $schema->getSubSchema($record->getRecordType());
|
||||
if (is_string($subSchema->getRawConfiguration()['previewRenderer'] ?? false) && $subSchema->getRawConfiguration()['previewRenderer'] !== '') {
|
||||
// A type-specific preview renderer was configured for the TCA type
|
||||
$previewRendererClassName = $subSchema->getRawConfiguration()['previewRenderer'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$previewRendererClassName) {
|
||||
// Table either has no type field or no custom preview renderer was defined for the type.
|
||||
// Use table's standard renderer if any is defined.
|
||||
$previewRendererClassName = $schema->getRawConfiguration()['previewRenderer'] ?? null;
|
||||
}
|
||||
|
||||
if (is_string($previewRendererClassName) && $previewRendererClassName !== '') {
|
||||
if (!is_a($previewRendererClassName, PreviewRendererInterface::class, true)) {
|
||||
throw new \UnexpectedValueException(
|
||||
sprintf(
|
||||
'Class %s must implement %s',
|
||||
$previewRendererClassName,
|
||||
PreviewRendererInterface::class
|
||||
),
|
||||
1477512798
|
||||
);
|
||||
}
|
||||
return GeneralUtility::makeInstance($previewRendererClassName);
|
||||
}
|
||||
throw new \RuntimeException(sprintf('No Preview renderer registered for table %s', $table), 1477520356);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user