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, '
', 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[] = ''; } else { $thumbData[] = $this->iconFactory->getIconForResource($fileObject)->setTitle($fileObject->getName())->render(); } } if ($thumbData !== []) { $result = ''; foreach ($thumbData as $thumbDataItem) { $result .= '
' . $thumbDataItem . '
'; } return '
' . $result . '
'; } 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 '' . $linkText . ''; } 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']; } }