TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
@@ -0,0 +1,95 @@
<?php
/*
* 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\Core\Resource\Rendering;
use TYPO3\CMS\Core\Attribute\AsFileRenderer;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Utility\GeneralUtility;
#[AsFileRenderer]
class AudioTagRenderer implements FileRendererInterface
{
/**
* Mime types that can be used in the HTML Video tag
*
* @var array
*/
protected $possibleMimeTypes = ['audio/mpeg', 'audio/wav', 'audio/x-wav', 'audio/ogg'];
/**
* Check if given File(Reference) can be rendered
*
* @param FileInterface $file File or FileReference to render
*/
public function canRender(FileInterface $file): bool
{
return in_array($file->getMimeType(), $this->possibleMimeTypes, true);
}
/**
* Render for given File(Reference) HTML output
*
* @param int|string $width TYPO3 known format; examples: 220, 200m or 200c
* @param int|string $height TYPO3 known format; examples: 220, 200m or 200c
* @param array $options controls = TRUE/FALSE (default TRUE), autoplay = TRUE/FALSE (default FALSE), loop = TRUE/FALSE (default FALSE)
*/
public function render(FileInterface $file, int|string $width, int|string $height, array $options = []): string
{
// If autoplay isn't set manually check if $file is a FileReference take autoplay from there
if (!isset($options['autoplay']) && $file instanceof FileReference) {
$autoplay = $file->getProperty('autoplay');
if ($autoplay !== null) {
$options['autoplay'] = $autoplay;
}
}
$additionalAttributes = [];
if (isset($options['additionalAttributes']) && is_array($options['additionalAttributes'])) {
$additionalAttributes[] = GeneralUtility::implodeAttributes($options['additionalAttributes'], true, true);
}
if (isset($options['data']) && is_array($options['data'])) {
array_walk($options['data'], static function (string &$value, string $key): void {
$value = 'data-' . htmlspecialchars($key) . '="' . htmlspecialchars($value) . '"';
});
$additionalAttributes[] = implode(' ', $options['data']);
}
if (!isset($options['controls']) || !empty($options['controls'])) {
$additionalAttributes[] = 'controls';
}
if (!empty($options['autoplay'])) {
$additionalAttributes[] = 'autoplay';
}
if (!empty($options['muted'])) {
$additionalAttributes[] = 'muted';
}
if (!empty($options['loop'])) {
$additionalAttributes[] = 'loop';
}
foreach (['class', 'dir', 'id', 'lang', 'style', 'title', 'accesskey', 'tabindex', 'onclick', 'preload', 'controlsList'] as $key) {
if (!empty($options[$key])) {
$additionalAttributes[] = $key . '="' . htmlspecialchars($options[$key]) . '"';
}
}
return sprintf(
'<audio%s><source src="%s" type="%s"></audio>',
empty($additionalAttributes) ? '' : ' ' . implode(' ', $additionalAttributes),
htmlspecialchars((string)$file->getPublicUrl()),
$file->getMimeType()
);
}
}
@@ -0,0 +1,40 @@
<?php
/*
* 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\Core\Resource\Rendering;
use TYPO3\CMS\Core\Resource\FileInterface;
/**
* Interface for file renderers, which are registered as tagged services
* via the #[AsFileRenderer] attribute or the 'fal.file_renderer' service tag.
*/
interface FileRendererInterface
{
/**
* Check if given File(Reference) can be rendered
*
* @param FileInterface $file File or FileReference to render
*/
public function canRender(FileInterface $file): bool;
/**
* Render for given File(Reference) HTML output
*
* @param int|string $width TYPO3 known format; examples: 220, 200m or 200c
* @param int|string $height TYPO3 known format; examples: 220, 200m or 200c
*/
public function render(FileInterface $file, int|string $width, int|string $height, array $options = []): string;
}
@@ -0,0 +1,83 @@
<?php
/*
* 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\Core\Resource\Rendering;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\SingletonInterface;
/**
* Registry for file renderers, which are registered as tagged services
* via the #[AsFileRenderer] attribute or the 'fal.file_renderer' service
* tag. Renderers are ordered by their tag priority, a renderer with a
* higher priority is asked first whether it can render a file.
*
* @internal not part of TYPO3's Core API. Register file renderers via the #[AsFileRenderer] attribute instead.
*/
class RendererRegistry implements SingletonInterface
{
/**
* Instance cache for renderer classes
*
* @var FileRendererInterface[]|null
*/
protected ?array $instances = null;
/**
* @param iterable<FileRendererInterface> $renderers
*/
public function __construct(protected readonly iterable $renderers = []) {}
/**
* @deprecated since TYPO3 v15.0, this method is a no-op and will be removed in TYPO3 v16.0. Register the renderer as a tagged service using the #[AsFileRenderer] attribute instead.
*/
public function registerRendererClass(string $className): void
{
trigger_error(
'RendererRegistry->registerRendererClass() is a no-op since TYPO3 v15.0 and will be removed in TYPO3 v16.0.'
. ' Register "' . $className . '" as a tagged service using the #[AsFileRenderer] attribute instead.',
E_USER_DEPRECATED
);
}
/**
* Get all registered renderer instances
*
* @return FileRendererInterface[]
*/
protected function getRendererInstances(): array
{
if ($this->instances === null) {
$this->instances = [];
foreach ($this->renderers as $renderer) {
$this->instances[] = $renderer;
}
}
return $this->instances;
}
/**
* Get matching renderer with highest priority
*/
public function getRenderer(FileInterface $file): ?FileRendererInterface
{
foreach ($this->getRendererInstances() as $fileRenderer) {
if ($fileRenderer->canRender($file)) {
return $fileRenderer;
}
}
return null;
}
}
@@ -0,0 +1,127 @@
<?php
/*
* 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\Core\Resource\Rendering;
use TYPO3\CMS\Core\Attribute\AsFileRenderer;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Utility\GeneralUtility;
#[AsFileRenderer]
class VideoTagRenderer implements FileRendererInterface
{
/**
* Mime types that can be used in the HTML Video tag
*
* @var array
*/
protected $possibleMimeTypes = ['video/mp4', 'video/webm', 'video/ogg', 'video/x-m4v', 'application/ogg'];
/**
* Special attributes which do not exist on <video> tags and therefore should be omitted.
*
* @var string[]
*/
protected array $excludeAttributes = ['api', 'no-cookie'];
/**
* Check if given File(Reference) can be rendered
*
* @param FileInterface $file File or FileReference to render
*/
public function canRender(FileInterface $file): bool
{
return in_array($file->getMimeType(), $this->possibleMimeTypes, true);
}
/**
* Render for given File(Reference) HTML output
*
* @param int|string $width TYPO3 known format; examples: 220, 200m or 200c
* @param int|string $height TYPO3 known format; examples: 220, 200m or 200c
* @param array $options controls = TRUE/FALSE (default TRUE), autoplay = TRUE/FALSE (default FALSE), loop = TRUE/FALSE (default FALSE)
*/
public function render(FileInterface $file, int|string $width, int|string $height, array $options = []): string
{
// If autoplay isn't set manually check if $file is a FileReference take autoplay from there
if (!isset($options['autoplay']) && $file instanceof FileReference) {
$autoplay = $file->getProperty('autoplay');
if ($autoplay !== null) {
$options['autoplay'] = $autoplay;
}
}
$attributes = [];
if (isset($options['additionalAttributes']) && is_array($options['additionalAttributes'])) {
$attributes[] = GeneralUtility::implodeAttributes($options['additionalAttributes'], true, true);
}
if (isset($options['data']) && is_array($options['data'])) {
array_walk($options['data'], static function (string &$value, string $key): void {
$value = 'data-' . htmlspecialchars($key) . '="' . htmlspecialchars($value) . '"';
});
$attributes[] = implode(' ', $options['data']);
}
if ((int)$width > 0) {
$attributes[] = 'width="' . (int)$width . '"';
}
if ((int)$height > 0) {
$attributes[] = 'height="' . (int)$height . '"';
}
if (!isset($options['controls']) || !empty($options['controls'])) {
$attributes[] = 'controls';
}
if (!empty($options['autoplay'])) {
$attributes[] = 'autoplay';
// If autoplay is enabled, enforce muted, see https://developer.chrome.com/blog/autoplay/
$attributes[] = 'muted';
}
if (!empty($options['muted'])) {
$attributes[] = 'muted';
}
if (!empty($options['loop'])) {
$attributes[] = 'loop';
}
if (isset($options['additionalConfig']) && is_array($options['additionalConfig'])) {
foreach ($options['additionalConfig'] as $key => $value) {
if ($value && !in_array($key, $this->excludeAttributes, true)) {
if ((int)$value !== 1) {
$attributes[] = htmlspecialchars($key) . '="' . htmlspecialchars($value) . '"';
} else {
$attributes[] = htmlspecialchars($key);
}
// Ensure that the property is not set afterwards
$options[$key] = false;
}
}
}
foreach (['class', 'dir', 'id', 'lang', 'style', 'title', 'accesskey', 'tabindex', 'onclick', 'controlsList', 'preload'] as $key) {
if (!empty($options[$key])) {
$attributes[] = $key . '="' . htmlspecialchars($options[$key]) . '"';
}
}
// Clean up duplicate attributes
$attributes = array_unique($attributes);
return sprintf(
'<video%s><source src="%s" type="%s"></video>',
empty($attributes) ? '' : ' ' . implode(' ', $attributes),
htmlspecialchars((string)$file->getPublicUrl()),
$file->getMimeType()
);
}
}
@@ -0,0 +1,231 @@
<?php
/*
* 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\Core\Resource\Rendering;
use TYPO3\CMS\Core\Attribute\AsFileRenderer;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperInterface;
use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
use TYPO3\CMS\Core\Type\DocType;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Vimeo renderer class
*/
#[AsFileRenderer]
class VimeoRenderer implements FileRendererInterface
{
/**
* @var OnlineMediaHelperInterface|false
*/
protected $onlineMediaHelper;
/**
* Check if given File(Reference) can be rendered
*
* @param FileInterface $file File of FileReference to render
*/
public function canRender(FileInterface $file): bool
{
return ($file->getMimeType() === 'video/vimeo' || $file->getExtension() === 'vimeo') && $this->getOnlineMediaHelper($file) !== false;
}
/**
* Get online media helper
*
* @return false|OnlineMediaHelperInterface
*/
protected function getOnlineMediaHelper(FileInterface $file)
{
if ($this->onlineMediaHelper === null) {
$orgFile = $file;
if ($orgFile instanceof FileReference) {
$orgFile = $orgFile->getOriginalFile();
}
if ($orgFile instanceof File) {
$this->onlineMediaHelper = GeneralUtility::makeInstance(OnlineMediaHelperRegistry::class)->getOnlineMediaHelper($orgFile);
} else {
$this->onlineMediaHelper = false;
}
}
return $this->onlineMediaHelper;
}
/**
* Render for given File(Reference) html output
*
* @param int|string $width TYPO3 known format; examples: 220, 200m or 200c
* @param int|string $height TYPO3 known format; examples: 220, 200m or 200c
*/
public function render(FileInterface $file, int|string $width, int|string $height, array $options = []): string
{
$options = $this->collectOptions($options, $file);
$src = $this->createVimeoUrl($options, $file);
if ($src === '') {
return '';
}
$attributes = $this->collectIframeAttributes($width, $height, $options);
return sprintf(
'<iframe %s="%s"%s></iframe>',
$options['srcAttribute'] ?? 'src',
htmlspecialchars($src, ENT_QUOTES | ENT_HTML5),
empty($attributes) ? '' : ' ' . $this->implodeAttributes($attributes)
);
}
/**
* @return array
*/
protected function collectOptions(array $options, FileInterface $file)
{
// Check for an autoplay option at the file reference itself, if not overridden yet.
if (!isset($options['autoplay']) && $file instanceof FileReference) {
$autoplay = $file->getProperty('autoplay');
if ($autoplay !== null) {
$options['autoplay'] = $autoplay;
}
}
if (!isset($options['allow'])) {
$options['allow'] = 'fullscreen';
if (!empty($options['autoplay'])) {
$options['allow'] = 'autoplay; fullscreen';
}
}
return $options;
}
protected function createVimeoUrl(array $options, FileInterface $file): string
{
$videoIdRaw = $this->getVideoIdFromFile($file);
$videoIdRaw = GeneralUtility::trimExplode('/', $videoIdRaw, true);
$videoId = $videoIdRaw[0] ?? '';
if (empty($videoId)) {
return '';
}
$hash = $videoIdRaw[1] ?? null;
$urlParams = [];
if (!empty($hash)) {
$urlParams[] = 'h=' . $hash;
}
if (!empty($options['autoplay'])) {
$urlParams[] = 'autoplay=1';
// If autoplay is enabled, enforce muted=1, see https://developer.chrome.com/blog/autoplay/
$urlParams[] = 'muted=1';
}
if (!empty($options['loop'])) {
$urlParams[] = 'loop=1';
}
if (!empty($options['background'])) {
$urlParams[] = 'background=1';
}
if (isset($options['api']) && (int)$options['api'] === 1) {
$urlParams[] = 'api=1';
}
if (!isset($options['no-cookie']) || !empty($options['no-cookie'])) {
$urlParams[] = 'dnt=1';
}
$urlParams[] = 'title=' . (int)!empty($options['showinfo']);
$urlParams[] = 'byline=' . (int)!empty($options['showinfo']);
$urlParams[] = 'portrait=0';
return sprintf('https://player.vimeo.com/video/%s?%s', $videoId, implode('&', $urlParams));
}
/**
* @return string
*/
protected function getVideoIdFromFile(FileInterface $file)
{
if ($file instanceof FileReference) {
$orgFile = $file->getOriginalFile();
} else {
$orgFile = $file;
}
return $this->getOnlineMediaHelper($file)->getOnlineMediaId($orgFile);
}
/**
* @param int|string $width
* @param int|string $height
* @return array pairs of key/value; not yet html-escaped
*/
protected function collectIframeAttributes($width, $height, array $options)
{
$attributes = [];
$attributes['allowfullscreen'] = true;
if (isset($options['additionalAttributes']) && is_array($options['additionalAttributes'])) {
$attributes = array_merge($attributes, $options['additionalAttributes']);
}
if (isset($options['data']) && is_array($options['data'])) {
array_walk(
$options['data'],
static function (string $value, string|int $key) use (&$attributes): void {
$attributes['data-' . $key] = $value;
}
);
}
if ((int)$width > 0) {
$attributes['width'] = (int)$width;
}
if ((int)$height > 0) {
$attributes['height'] = (int)$height;
}
if ($this->shouldIncludeFrameBorderAttribute()) {
$attributes['frameborder'] = 0;
}
foreach (['class', 'dir', 'id', 'lang', 'style', 'title', 'accesskey', 'tabindex', 'onclick', 'allow'] as $key) {
if (!empty($options[$key])) {
$attributes[$key] = $options[$key];
}
}
return $attributes;
}
/**
* @internal
*/
protected function implodeAttributes(array $attributes): string
{
$attributeList = [];
foreach ($attributes as $name => $value) {
$name = preg_replace('/[^\p{L}0-9_.-]/u', '', $name);
if ($value === true) {
$attributeList[] = $name;
} else {
$attributeList[] = $name . '="' . htmlspecialchars($value, ENT_QUOTES | ENT_HTML5) . '"';
}
}
return implode(' ', $attributeList);
}
/**
* HTML5 deprecated the "frameborder" attribute as everything should be done via styling.
*
* @todo: This renderer has a dependency to Request / TypoScript. Model this explicitly.
*/
protected function shouldIncludeFrameBorderAttribute(): bool
{
return DocType::createFromRequest($GLOBALS['TYPO3_REQUEST'] ?? null)->shouldIncludeFrameBorderAttribute();
}
}
@@ -0,0 +1,234 @@
<?php
/*
* 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\Core\Resource\Rendering;
use TYPO3\CMS\Core\Attribute\AsFileRenderer;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperInterface;
use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
use TYPO3\CMS\Core\Type\DocType;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* YouTube renderer class
*/
#[AsFileRenderer]
class YouTubeRenderer implements FileRendererInterface
{
/**
* @var OnlineMediaHelperInterface|false
*/
protected $onlineMediaHelper;
/**
* Check if given File(Reference) can be rendered
*
* @param FileInterface $file File of FileReference to render
*/
public function canRender(FileInterface $file): bool
{
return ($file->getMimeType() === 'video/youtube' || $file->getExtension() === 'youtube') && $this->getOnlineMediaHelper($file) !== false;
}
/**
* Get online media helper
*
* @return false|OnlineMediaHelperInterface
*/
protected function getOnlineMediaHelper(FileInterface $file)
{
if ($this->onlineMediaHelper === null) {
$orgFile = $file;
if ($orgFile instanceof FileReference) {
$orgFile = $orgFile->getOriginalFile();
}
if ($orgFile instanceof File) {
$this->onlineMediaHelper = GeneralUtility::makeInstance(OnlineMediaHelperRegistry::class)->getOnlineMediaHelper($orgFile);
} else {
$this->onlineMediaHelper = false;
}
}
return $this->onlineMediaHelper;
}
/**
* Render for given File(Reference) html output
*
* @param int|string $width TYPO3 known format; examples: 220, 200m or 200c
* @param int|string $height TYPO3 known format; examples: 220, 200m or 200c
*/
public function render(FileInterface $file, int|string $width, int|string $height, array $options = []): string
{
$options = $this->collectOptions($options, $file);
$src = $this->createYouTubeUrl($options, $file);
if (empty($src)) {
return '';
}
$attributes = $this->collectIframeAttributes($width, $height, $options);
return sprintf(
'<iframe %s="%s"%s></iframe>',
$options['srcAttribute'] ?? 'src',
htmlspecialchars($src, ENT_QUOTES | ENT_HTML5),
empty($attributes) ? '' : ' ' . $this->implodeAttributes($attributes)
);
}
/**
* @return array
*/
protected function collectOptions(array $options, FileInterface $file)
{
// Check for an autoplay option at the file reference itself, if not overridden yet.
if (!isset($options['autoplay']) && $file instanceof FileReference) {
$autoplay = $file->getProperty('autoplay');
if ($autoplay !== null) {
$options['autoplay'] = $autoplay;
}
}
$showPlayerControls = 1;
$options['controls'] = (int)!empty($options['controls'] ?? $showPlayerControls);
if (!isset($options['allow'])) {
$options['allow'] = 'fullscreen';
if (!empty($options['autoplay'])) {
$options['allow'] = 'autoplay; fullscreen';
}
}
return $options;
}
protected function createYouTubeUrl(array $options, FileInterface $file): string
{
$videoId = $this->getVideoIdFromFile($file);
if (empty($videoId)) {
return '';
}
$urlParams = ['autohide=1'];
$urlParams[] = 'controls=' . $options['controls'];
if (!empty($options['autoplay'])) {
$urlParams[] = 'autoplay=1';
// If autoplay is enabled, enforce mute=1, see https://developer.chrome.com/blog/autoplay/
$urlParams[] = 'mute=1';
}
if (!empty($options['modestbranding'])) {
$urlParams[] = 'modestbranding=1';
}
if (!empty($options['loop'])) {
$urlParams[] = 'loop=1&playlist=' . rawurlencode($videoId);
}
if (isset($options['relatedVideos'])) {
$urlParams[] = 'rel=' . (int)(bool)$options['relatedVideos'];
}
if (!isset($options['enablejsapi']) || !empty($options['enablejsapi'])) {
// @todo: This renderer has a dependency to Request / TypoScript. Model this explicitly.
$urlParams[] = 'enablejsapi=1&origin=' . rawurlencode(
($GLOBALS['TYPO3_REQUEST'] ?? null)?->getAttribute('normalizedParams')?->getRequestHost() ?? ''
);
}
$youTubeUrl = sprintf(
'https://www.youtube%s.com/embed/%s?%s',
!isset($options['no-cookie']) || !empty($options['no-cookie']) ? '-nocookie' : '',
rawurlencode($videoId),
implode('&', $urlParams)
);
return $youTubeUrl;
}
/**
* @return string
*/
protected function getVideoIdFromFile(FileInterface $file)
{
if ($file instanceof FileReference) {
$orgFile = $file->getOriginalFile();
} else {
$orgFile = $file;
}
return $this->getOnlineMediaHelper($file)->getOnlineMediaId($orgFile);
}
/**
* @param int|string $width
* @param int|string $height
* @return array pairs of key/value; not yet html-escaped
*/
protected function collectIframeAttributes($width, $height, array $options)
{
$attributes = [];
$attributes['allowfullscreen'] = true;
if (isset($options['additionalAttributes']) && is_array($options['additionalAttributes'])) {
$attributes = array_merge($attributes, $options['additionalAttributes']);
}
if (isset($options['data']) && is_array($options['data'])) {
array_walk($options['data'], static function (string|int $value, string $key) use (&$attributes): void {
$attributes['data-' . $key] = $value;
});
}
if ((int)$width > 0) {
$attributes['width'] = (int)$width;
}
if ((int)$height > 0) {
$attributes['height'] = (int)$height;
}
if ($this->shouldIncludeFrameBorderAttribute()) {
$attributes['frameborder'] = 0;
}
foreach (['class', 'dir', 'id', 'lang', 'style', 'title', 'accesskey', 'tabindex', 'onclick', 'poster', 'preload', 'allow'] as $key) {
if (!empty($options[$key])) {
$attributes[$key] = $options[$key];
}
}
return $attributes;
}
/**
* @internal
*/
protected function implodeAttributes(array $attributes): string
{
$attributeList = [];
foreach ($attributes as $name => $value) {
$name = preg_replace('/[^\p{L}0-9_.-]/u', '', $name);
if ($value === true) {
$attributeList[] = $name;
} else {
$attributeList[] = $name . '="' . htmlspecialchars($value, ENT_QUOTES | ENT_HTML5) . '"';
}
}
return implode(' ', $attributeList);
}
/**
* HTML5 deprecated the "frameborder" attribute as everything should be done via styling.
*
* @todo: This renderer has a dependency to Request / TypoScript. Model this explicitly.
*/
protected function shouldIncludeFrameBorderAttribute(): bool
{
return DocType::createFromRequest($GLOBALS['TYPO3_REQUEST'] ?? null)->shouldIncludeFrameBorderAttribute();
}
}