264 lines
11 KiB
PHP
264 lines
11 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 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'];
|
|
}
|
|
}
|