577 lines
18 KiB
PHP
577 lines
18 KiB
PHP
<?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\Imaging;
|
|
|
|
use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
|
|
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
|
use TYPO3\CMS\Core\Exception;
|
|
use TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider;
|
|
use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;
|
|
use TYPO3\CMS\Core\Imaging\IconProvider\SvgSpriteIconProvider;
|
|
use TYPO3\CMS\Core\SingletonInterface;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
|
|
/**
|
|
* Class which makes it possible to register custom icons
|
|
* from within an extension.
|
|
*/
|
|
class IconRegistry implements SingletonInterface
|
|
{
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $fullInitialized = false;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $tcaInitialized = false;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $flagsInitialized = false;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $backendIconsInitialized = false;
|
|
|
|
/**
|
|
* Registered icons
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $icons = [];
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $backendIconDeclaration = 'EXT:core/Resources/Public/Icons/T3Icons/icons.json';
|
|
|
|
/**
|
|
* manually registered icons
|
|
* hopefully obsolete one day
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $staticIcons = [
|
|
|
|
/**
|
|
* Important Information:
|
|
*
|
|
* Icons are maintained in an external repository, if new icons are needed
|
|
* please request them at: https://github.com/typo3/typo3.icons/issues
|
|
*/
|
|
];
|
|
|
|
/**
|
|
* Mapping of file extensions to mimetypes
|
|
*
|
|
* @var string[]
|
|
*/
|
|
protected $fileExtensionMapping = [
|
|
'htm' => 'mimetypes-text-html',
|
|
'html' => 'mimetypes-text-html',
|
|
'css' => 'mimetypes-text-css',
|
|
'js' => 'mimetypes-text-js',
|
|
'csv' => 'mimetypes-text-csv',
|
|
'php' => 'mimetypes-text-php',
|
|
'php6' => 'mimetypes-text-php',
|
|
'php5' => 'mimetypes-text-php',
|
|
'php4' => 'mimetypes-text-php',
|
|
'php3' => 'mimetypes-text-php',
|
|
'inc' => 'mimetypes-text-php',
|
|
'ts' => 'mimetypes-text-ts',
|
|
'typoscript' => 'mimetypes-text-typoscript',
|
|
'txt' => 'mimetypes-text-text',
|
|
'class' => 'mimetypes-text-text',
|
|
'tmpl' => 'mimetypes-text-text',
|
|
'jpg' => 'mimetypes-media-image',
|
|
'jpeg' => 'mimetypes-media-image',
|
|
'gif' => 'mimetypes-media-image',
|
|
'png' => 'mimetypes-media-image',
|
|
'bmp' => 'mimetypes-media-image',
|
|
'tif' => 'mimetypes-media-image',
|
|
'tiff' => 'mimetypes-media-image',
|
|
'tga' => 'mimetypes-media-image',
|
|
'psd' => 'mimetypes-media-image',
|
|
'eps' => 'mimetypes-media-image',
|
|
'ai' => 'mimetypes-media-image',
|
|
'svg' => 'mimetypes-media-image',
|
|
'pcx' => 'mimetypes-media-image',
|
|
'avi' => 'mimetypes-media-video',
|
|
'mpg' => 'mimetypes-media-video',
|
|
'mpeg' => 'mimetypes-media-video',
|
|
'mov' => 'mimetypes-media-video',
|
|
'vimeo' => 'mimetypes-media-video-vimeo',
|
|
'youtube' => 'mimetypes-media-video-youtube',
|
|
'wav' => 'mimetypes-media-audio',
|
|
'mp3' => 'mimetypes-media-audio',
|
|
'ogg' => 'mimetypes-media-audio',
|
|
'flac' => 'mimetypes-media-audio',
|
|
'opus' => 'mimetypes-media-audio',
|
|
'mid' => 'mimetypes-media-audio',
|
|
'swf' => 'mimetypes-media-flash',
|
|
'swa' => 'mimetypes-media-flash',
|
|
'exe' => 'mimetypes-application',
|
|
'com' => 'mimetypes-application',
|
|
't3x' => 'mimetypes-compressed',
|
|
't3d' => 'mimetypes-compressed',
|
|
'zip' => 'mimetypes-compressed',
|
|
'tgz' => 'mimetypes-compressed',
|
|
'gz' => 'mimetypes-compressed',
|
|
'pdf' => 'mimetypes-pdf',
|
|
'doc' => 'mimetypes-word',
|
|
'dot' => 'mimetypes-word',
|
|
'docm' => 'mimetypes-word',
|
|
'docx' => 'mimetypes-word',
|
|
'dotm' => 'mimetypes-word',
|
|
'dotx' => 'mimetypes-word',
|
|
'sxw' => 'mimetypes-word',
|
|
'rtf' => 'mimetypes-word',
|
|
'xls' => 'mimetypes-excel',
|
|
'xlsm' => 'mimetypes-excel',
|
|
'xlsx' => 'mimetypes-excel',
|
|
'xltm' => 'mimetypes-excel',
|
|
'xltx' => 'mimetypes-excel',
|
|
'sxc' => 'mimetypes-excel',
|
|
'pps' => 'mimetypes-powerpoint',
|
|
'ppsx' => 'mimetypes-powerpoint',
|
|
'ppt' => 'mimetypes-powerpoint',
|
|
'pptm' => 'mimetypes-powerpoint',
|
|
'pptx' => 'mimetypes-powerpoint',
|
|
'potm' => 'mimetypes-powerpoint',
|
|
'potx' => 'mimetypes-powerpoint',
|
|
'mount' => 'apps-filetree-mount',
|
|
'folder' => 'apps-filetree-folder-default',
|
|
'default' => 'mimetypes-other-other',
|
|
];
|
|
|
|
/**
|
|
* Mapping of mime types to icons
|
|
*
|
|
* @var string[]
|
|
*/
|
|
protected $mimeTypeMapping = [
|
|
'video/*' => 'mimetypes-media-video',
|
|
'audio/*' => 'mimetypes-media-audio',
|
|
'image/*' => 'mimetypes-media-image',
|
|
'text/*' => 'mimetypes-text-text',
|
|
];
|
|
|
|
/**
|
|
* @var array<string, string>
|
|
*/
|
|
protected $iconAliases = [];
|
|
|
|
/**
|
|
* Array of deprecated icons, add deprecated icons to this array and remove it from registry
|
|
* - Index of this array contains the deprecated icon
|
|
* - Value of each entry may contain a possible new identifier
|
|
*
|
|
* Example:
|
|
* [
|
|
* 'deprecated-icon-identifier' => ['since' => 'TYPO3 v12', 'until' => 'TYPO3 v13', 'replacement' => 'new-icon-identifier'],
|
|
* 'another-deprecated-identifier' => ['since' => 'TYPO3 v12', 'until' => 'TYPO3 v13', 'replacement' => null],
|
|
* ]
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $deprecatedIcons = [];
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $defaultIconIdentifier = 'default-not-found';
|
|
|
|
/**
|
|
* @var FrontendInterface
|
|
*/
|
|
protected $cache;
|
|
|
|
private string $cacheIdentifier;
|
|
|
|
public function __construct(FrontendInterface $assetsCache, string $cacheIdentifier)
|
|
{
|
|
$this->cache = $assetsCache;
|
|
$this->cacheIdentifier = $cacheIdentifier;
|
|
$this->initialize();
|
|
}
|
|
|
|
/**
|
|
* Initialize the registry
|
|
* This method can be called multiple times, depending on initialization status.
|
|
* In some cases e.g. TCA is not available, the method must be called multiple times.
|
|
*/
|
|
protected function initialize()
|
|
{
|
|
if (!$this->backendIconsInitialized) {
|
|
$this->getCachedBackendIcons();
|
|
}
|
|
if (!$this->tcaInitialized && !empty($GLOBALS['TCA'])) {
|
|
$this->registerTCAIcons();
|
|
}
|
|
if (!$this->flagsInitialized) {
|
|
$this->getCachedFlagIcons();
|
|
}
|
|
if ($this->backendIconsInitialized
|
|
&& $this->tcaInitialized
|
|
&& $this->flagsInitialized) {
|
|
$this->fullInitialized = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
public function getBackendIconsCacheIdentifier(): string
|
|
{
|
|
return $this->cacheIdentifier;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the icons from cache render them when not cached yet
|
|
*/
|
|
protected function getCachedBackendIcons()
|
|
{
|
|
$cacheIdentifier = $this->getBackendIconsCacheIdentifier();
|
|
$cacheEntry = $this->cache->get($cacheIdentifier);
|
|
|
|
if ($cacheEntry !== false) {
|
|
$this->icons = $cacheEntry;
|
|
} else {
|
|
$this->registerBackendIcons();
|
|
// all found icons should now be present, for historic reasons now merge w/ the statically declared icons
|
|
$this->icons = array_merge($this->icons, $this->iconAliases, $this->staticIcons);
|
|
$this->cache->set($cacheIdentifier, $this->icons);
|
|
}
|
|
// if there's now at least one icon registered, consider it successful
|
|
if (is_array($this->icons) && (count($this->icons) >= count($this->staticIcons))) {
|
|
$this->backendIconsInitialized = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Automatically find and register the core backend icons
|
|
*/
|
|
protected function registerBackendIcons(): void
|
|
{
|
|
$dir = dirname($this->backendIconDeclaration);
|
|
$absoluteIconDeclarationPath = GeneralUtility::getFileAbsFileName($this->backendIconDeclaration);
|
|
$json = json_decode(file_get_contents($absoluteIconDeclarationPath) ?: '', true);
|
|
foreach ($json['icons'] ?? [] as $declaration) {
|
|
$iconOptions = [
|
|
'sprite' => $dir . '/' . $declaration['sprite'],
|
|
];
|
|
// kind of hotfix for now, needs a nicer concept later
|
|
if ($declaration['category'] === 'spinner') {
|
|
$iconOptions['spinning'] = true;
|
|
}
|
|
if (isset($declaration['bidi']) && $declaration['bidi'] === true) {
|
|
$iconOptions['bidi'] = true;
|
|
}
|
|
|
|
$this->registerIcon(
|
|
$declaration['identifier'],
|
|
SvgSpriteIconProvider::class,
|
|
$iconOptions
|
|
);
|
|
}
|
|
|
|
foreach ($json['aliases'] as $alias => $identifier) {
|
|
$this->registerAlias($alias, $identifier);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $identifier
|
|
* @return bool
|
|
*/
|
|
public function isRegistered($identifier)
|
|
{
|
|
if (!$this->fullInitialized) {
|
|
$this->initialize();
|
|
}
|
|
return isset($this->icons[$identifier]);
|
|
}
|
|
|
|
/**
|
|
* @param string $identifier
|
|
* @return bool
|
|
*/
|
|
public function isDeprecated($identifier)
|
|
{
|
|
return isset($this->deprecatedIcons[$identifier]);
|
|
}
|
|
|
|
public function getDefaultIconIdentifier(): string
|
|
{
|
|
return $this->defaultIconIdentifier;
|
|
}
|
|
|
|
/**
|
|
* Registers an icon to be available inside the Icon Factory
|
|
*
|
|
* @param string $identifier
|
|
* @param string $iconProviderClassName
|
|
*
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
public function registerIcon($identifier, $iconProviderClassName, array $options = [])
|
|
{
|
|
if (!in_array(IconProviderInterface::class, class_implements($iconProviderClassName) ?: [], true)) {
|
|
throw new \InvalidArgumentException('An IconProvider must implement '
|
|
. IconProviderInterface::class, 1437425803);
|
|
}
|
|
$this->icons[$identifier] = [
|
|
'provider' => $iconProviderClassName,
|
|
'options' => $options,
|
|
];
|
|
|
|
if (isset($options['deprecated'])) {
|
|
$this->deprecatedIcons[$identifier] = $options['deprecated'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers an icon to be available inside the Icon Factory
|
|
*
|
|
* @param string $alias
|
|
* @param string $identifier
|
|
*
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
public function registerAlias($alias, $identifier)
|
|
{
|
|
if (!isset($this->icons[$identifier])) {
|
|
throw new \InvalidArgumentException('No icon with identifier "' . $identifier . '" registered.', 1602251838);
|
|
}
|
|
$this->iconAliases[$alias] = $this->icons[$identifier];
|
|
}
|
|
|
|
/**
|
|
* Register an icon for a file extension
|
|
*
|
|
* @param string $fileExtension
|
|
* @param string $iconIdentifier
|
|
*/
|
|
public function registerFileExtension($fileExtension, $iconIdentifier)
|
|
{
|
|
$this->fileExtensionMapping[$fileExtension] = $iconIdentifier;
|
|
}
|
|
|
|
/**
|
|
* Register an icon for a mime-type
|
|
*
|
|
* @param string $mimeType
|
|
* @param string $iconIdentifier
|
|
*/
|
|
public function registerMimeTypeIcon($mimeType, $iconIdentifier)
|
|
{
|
|
$this->mimeTypeMapping[$mimeType] = $iconIdentifier;
|
|
}
|
|
|
|
/**
|
|
* Fetches the configuration provided by registerIcon()
|
|
*
|
|
* @param string $identifier the icon identifier
|
|
* @return mixed
|
|
* @throws Exception
|
|
*/
|
|
public function getIconConfigurationByIdentifier($identifier)
|
|
{
|
|
if (!$this->fullInitialized) {
|
|
$this->initialize();
|
|
}
|
|
if ($this->isDeprecated($identifier)) {
|
|
$deprecation = $this->deprecatedIcons[$identifier];
|
|
$since = $deprecation['since'] ?? null;
|
|
$until = $deprecation['until'] ?? null;
|
|
$replacement = $deprecation['replacement'] ?? null;
|
|
|
|
$message = 'The icon "%s" is deprecated%s%s.';
|
|
$arguments = [
|
|
$identifier,
|
|
$since !== null ? ' since ' . $since : '',
|
|
$until !== null ? ' and will be removed in ' . $until : '',
|
|
];
|
|
|
|
if ($replacement) {
|
|
$message .= ' Please use "%s" instead.';
|
|
$arguments[] = $replacement;
|
|
}
|
|
trigger_error(vsprintf($message, $arguments), E_USER_DEPRECATED);
|
|
}
|
|
if (!$this->isRegistered($identifier)) {
|
|
throw new Exception('Icon with identifier "' . $identifier . '" is not registered"', 1437425804);
|
|
}
|
|
return $this->icons[$identifier];
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getAllRegisteredIconIdentifiers()
|
|
{
|
|
if (!$this->fullInitialized) {
|
|
$this->initialize();
|
|
}
|
|
return array_keys($this->icons);
|
|
}
|
|
|
|
public function getDeprecatedIcons(): array
|
|
{
|
|
return $this->deprecatedIcons;
|
|
}
|
|
|
|
/**
|
|
* @param string $fileExtension
|
|
* @return string
|
|
*/
|
|
public function getIconIdentifierForFileExtension($fileExtension)
|
|
{
|
|
// If the file extension is not valid use the default one
|
|
if (!isset($this->fileExtensionMapping[$fileExtension])) {
|
|
$fileExtension = 'default';
|
|
}
|
|
return $this->fileExtensionMapping[$fileExtension];
|
|
}
|
|
|
|
/**
|
|
* Get iconIdentifier for given mimeType
|
|
*
|
|
* @param string $mimeType
|
|
* @return string|null Returns null if no icon is registered for the mimeType
|
|
*/
|
|
public function getIconIdentifierForMimeType($mimeType)
|
|
{
|
|
if (!isset($this->mimeTypeMapping[$mimeType])) {
|
|
return null;
|
|
}
|
|
return $this->mimeTypeMapping[$mimeType];
|
|
}
|
|
|
|
/**
|
|
* Load icons from TCA for each table and add them as "tcarecords-XX" to $this->icons
|
|
*/
|
|
protected function registerTCAIcons()
|
|
{
|
|
$resultArray = [];
|
|
|
|
$tcaTables = array_keys($GLOBALS['TCA'] ?? []);
|
|
// check every table in the TCA, if an icon is needed
|
|
foreach ($tcaTables as $tableName) {
|
|
// This method is only needed for TCA tables where typeicon_classes are not configured
|
|
$iconIdentifier = 'tcarecords-' . $tableName . '-default';
|
|
if (
|
|
isset($this->icons[$iconIdentifier])
|
|
|| !isset($GLOBALS['TCA'][$tableName]['ctrl']['iconfile'])
|
|
) {
|
|
continue;
|
|
}
|
|
$resultArray[$iconIdentifier] = $GLOBALS['TCA'][$tableName]['ctrl']['iconfile'];
|
|
}
|
|
|
|
foreach ($resultArray as $iconIdentifier => $iconFilePath) {
|
|
$iconProviderClass = $this->detectIconProvider($iconFilePath);
|
|
$this->icons[$iconIdentifier] = [
|
|
'provider' => $iconProviderClass,
|
|
'options' => [
|
|
'source' => $iconFilePath,
|
|
],
|
|
];
|
|
}
|
|
$this->tcaInitialized = true;
|
|
}
|
|
|
|
protected function getCachedFlagIcons(): void
|
|
{
|
|
$cacheIdentifier = $this->getBackendIconsCacheIdentifier() . '_flags';
|
|
$cacheEntry = $this->cache->get($cacheIdentifier);
|
|
|
|
if ($cacheEntry === false) {
|
|
$cacheEntry = $this->registerFlags();
|
|
$this->cache->set($cacheIdentifier, $cacheEntry);
|
|
}
|
|
$this->icons = array_merge($this->icons, $cacheEntry);
|
|
// if there's now at least one icon registered, consider it successful
|
|
if (is_array($cacheEntry) && $cacheEntry !== []) {
|
|
$this->flagsInitialized = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register flags
|
|
*/
|
|
protected function registerFlags(): array
|
|
{
|
|
$iconFolder = 'EXT:core/Resources/Public/Icons/Flags/';
|
|
$folderPath = GeneralUtility::getFileAbsFileName($iconFolder);
|
|
$flagIcons = [];
|
|
|
|
if ($handle = opendir($folderPath)) {
|
|
while (($file = readdir($handle)) !== false) {
|
|
$fileInfo = pathinfo($folderPath . $file);
|
|
if ($fileInfo['extension'] !== 'webp') {
|
|
continue;
|
|
}
|
|
$flagIcons['flags-' . strtolower($fileInfo['filename'])] = [
|
|
'provider' => BitmapIconProvider::class,
|
|
'options' => [
|
|
'source' => $iconFolder . $file,
|
|
],
|
|
];
|
|
}
|
|
closedir($handle);
|
|
}
|
|
|
|
return $flagIcons;
|
|
}
|
|
|
|
/**
|
|
* Detect the IconProvider of an icon
|
|
*
|
|
* @param string $iconReference
|
|
* @return string
|
|
*/
|
|
public function detectIconProvider($iconReference)
|
|
{
|
|
if (str_ends_with(strtolower((string)$iconReference), 'svg')) {
|
|
return SvgIconProvider::class;
|
|
}
|
|
return BitmapIconProvider::class;
|
|
}
|
|
|
|
public function warmupCaches(CacheWarmupEvent $event): void
|
|
{
|
|
if ($event->hasGroup('system')) {
|
|
$backupIcons = $this->icons;
|
|
$backupAliases = $this->iconAliases;
|
|
$this->icons = [];
|
|
$this->iconAliases = [];
|
|
|
|
$this->registerBackendIcons();
|
|
// all found icons should now be present, for historic reasons now merge w/ the statically declared icons
|
|
$this->icons = array_merge($this->icons, $this->iconAliases, $this->staticIcons);
|
|
$this->cache->set($this->getBackendIconsCacheIdentifier(), $this->icons);
|
|
|
|
$this->icons = $backupIcons;
|
|
$this->iconAliases = $backupAliases;
|
|
}
|
|
}
|
|
}
|