diff options
author | Alex Pott <alex.a.pott@googlemail.com> | 2025-04-16 09:58:40 +0200 |
---|---|---|
committer | Alex Pott <alex.a.pott@googlemail.com> | 2025-04-16 09:58:40 +0200 |
commit | 37737af85cf02359a55bb3346936d5e78b8d901b (patch) | |
tree | a94ae0182c05a48e103dfb2a74fd8b182e399003 | |
parent | e3894f6a6aa6cbc6778322878f68de8066a26516 (diff) | |
download | drupal-37737af85cf02359a55bb3346936d5e78b8d901b.tar.gz drupal-37737af85cf02359a55bb3346936d5e78b8d901b.zip |
Issue #2311679 by kim.pepper, mondrake, bhanu951, arla, alexpott, ParisLiakos, yogeshmpawar, pcambra, godotislate, mfb, berdir: Separate MIME type mapping from ExtensionMimeTypeGuesser
24 files changed, 1796 insertions, 190 deletions
diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php index 0abeb6f41cb..6f4a52139f1 100644 --- a/core/.phpstan-baseline.php +++ b/core/.phpstan-baseline.php @@ -7430,18 +7430,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/lib/Drupal/Core/File/HtaccessWriterInterface.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Core\\\\File\\\\MimeType\\\\ExtensionMimeTypeGuesser\\:\\:setMapping\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Core\\\\File\\\\MimeType\\\\MimeTypeGuesser\\:\\:registerWithSymfonyGuesser\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Core\\\\FileTransfer\\\\ChmodInterface\\:\\:chmodJailed\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, @@ -9400,12 +9388,6 @@ $ignoreErrors[] = [ 'path' => __DIR__ . '/lib/Drupal/Core/ProxyClass/Extension/ModuleInstaller.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Core\\\\ProxyClass\\\\File\\\\MimeType\\\\ExtensionMimeTypeGuesser\\:\\:setMapping\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/lib/Drupal/Core/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php', -]; -$ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Core\\\\ProxyClass\\\\File\\\\MimeType\\\\MimeTypeGuesser\\:\\:addMimeTypeGuesser\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, diff --git a/core/core.services.yml b/core/core.services.yml index bec73aa5ccf..4372e6512c3 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1805,10 +1805,17 @@ services: lazy: true file.mime_type.guesser.extension: class: Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser - arguments: ['@module_handler'] + arguments: ['@Drupal\Core\File\MimeType\MimeTypeMapInterface', '@file_system'] tags: - { name: mime_type_guesser } lazy: true + Drupal\Core\File\MimeType\MimeTypeMapFactory: + autowire: true + public: false + Drupal\Core\File\MimeType\MimeTypeMapInterface: + factory: ['@Drupal\Core\File\MimeType\MimeTypeMapFactory', create] + Drupal\Core\File\EventSubscriber\LegacyMimeTypeMapLoadedSubscriber: + autowire: true # Currently needs to be public as it is called by # \Drupal\Core\Render\Element\StatusMessages. # @todo Consider making this service private again after diff --git a/core/lib/Drupal/Core/File/Event/MimeTypeMapLoadedEvent.php b/core/lib/Drupal/Core/File/Event/MimeTypeMapLoadedEvent.php new file mode 100644 index 00000000000..8d7ea48630d --- /dev/null +++ b/core/lib/Drupal/Core/File/Event/MimeTypeMapLoadedEvent.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\File\Event; + +use Drupal\Core\File\MimeType\MimeTypeMapInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Event that is fired when the MIME type map is loaded. + */ +final class MimeTypeMapLoadedEvent extends Event { + + public function __construct( + public readonly MimeTypeMapInterface $map, + ) {} + +} diff --git a/core/lib/Drupal/Core/File/EventSubscriber/LegacyMimeTypeMapLoadedSubscriber.php b/core/lib/Drupal/Core/File/EventSubscriber/LegacyMimeTypeMapLoadedSubscriber.php new file mode 100644 index 00000000000..942cc65f709 --- /dev/null +++ b/core/lib/Drupal/Core/File/EventSubscriber/LegacyMimeTypeMapLoadedSubscriber.php @@ -0,0 +1,56 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\File\EventSubscriber; + +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\File\Event\MimeTypeMapLoadedEvent; +use Drupal\Core\File\MimeType\MimeTypeMap; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Modifies the MIME type map by calling hook_file_mimetype_mapping_alter(). + * + * This event subscriber provides BC support for the deprecated + * hook_file_mimetype_mapping_alter() and will be removed in drupal:12.0.0. + * + * @internal + * + * @see https://www.drupal.org/node/3494040 + */ +final class LegacyMimeTypeMapLoadedSubscriber implements + EventSubscriberInterface { + + public function __construct( + protected readonly ModuleHandlerInterface $moduleHandler, + ) {} + + /** + * Handle the event by calling deprecated hook_file_mimetype_mapping_alter(). + */ + public function onMimeTypeMapLoaded(MimeTypeMapLoadedEvent $event): void { + if (!$event->map instanceof MimeTypeMap) { + return; + } + // @phpstan-ignore-next-line method.deprecated + $mapping = $event->map->getMapping(); + $this->moduleHandler->alterDeprecated( + 'This hook is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Implement a \Drupal\Core\File\Event\MimeTypeMapLoadedEvent listener instead. See https://www.drupal.org/node/3494040', + 'file_mimetype_mapping', + $mapping, + ); + // @phpstan-ignore-next-line method.deprecated + $event->map->setMapping($mapping); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + MimeTypeMapLoadedEvent::class => 'onMimeTypeMapLoaded', + ]; + } + +} diff --git a/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php b/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php index 4ed1bb82d7d..066bee922b4 100644 --- a/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php +++ b/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php @@ -2,19 +2,28 @@ namespace Drupal\Core\File\MimeType; +use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\File\Event\MimeTypeMapLoadedEvent; +use Drupal\Core\File\FileSystemInterface; use Symfony\Component\Mime\MimeTypeGuesserInterface; /** * Makes possible to guess the MIME type of a file using its extension. */ class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface { + use DeprecatedServicePropertyTrait; /** * Default MIME extension mapping. * * @var array * Array of mimetypes correlated to the extensions that relate to them. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a + * MimeTypeMapInterface $map to the constructor instead. + * + * @see https://www.drupal.org/node/3494040 */ protected $defaultMapping = [ // cspell:disable @@ -877,39 +886,86 @@ class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface { * The MIME types mapping array after going through the module handler. * * @var array + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a + * MimeTypeMapInterface $map to the constructor instead. + * + * @see https://www.drupal.org/node/3494040 */ protected $mapping; /** - * The module handler. + * Deprecated service properties. + * + * @var string[] * - * @var \Drupal\Core\Extension\ModuleHandlerInterface + * @see https://www.drupal.org/node/3494040 */ - protected $moduleHandler; + protected $deprecatedProperties = [ + 'moduleHandler' => 'module_handler', + ]; + + /** + * The MIME type map. + */ + protected MimeTypeMapInterface $map; + + /** + * The file system. + */ + protected FileSystemInterface $fileSystem; /** * Constructs a new ExtensionMimeTypeGuesser. * - * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler - * The module handler. + * @param \Drupal\Core\Extension\ModuleHandlerInterface|\Drupal\Core\File\MimeType\MimeTypeMapInterface $map + * The MIME type map. + * @param \Drupal\Core\File\FileSystemInterface|null $fileSystem + * The file system. */ - public function __construct(ModuleHandlerInterface $module_handler) { - $this->moduleHandler = $module_handler; + public function __construct( + MimeTypeMapInterface|ModuleHandlerInterface $map, + ?FileSystemInterface $fileSystem = NULL, + ) { + if ($map instanceof ModuleHandlerInterface) { + @trigger_error( + 'Calling ' . __METHOD__ . '() with the $map argument as an instance of \Drupal\Core\Extension\ModuleHandlerInterface is deprecated in drupal:11.2.0 and an instance of \Drupal\Core\File\MimeType\MimeTypeMapInterface is required in drupal:12.0.0. See https://www.drupal.org/node/3494040', + E_USER_DEPRECATED + ); + $map = \Drupal::service(MimeTypeMapInterface::class); + } + $this->map = $map; + if (!$fileSystem) { + @trigger_error( + 'Calling ' . __METHOD__ . '() without the $fileSystem argument is deprecated in drupal:11.2.0 and is required in drupal:12.0.0. See https://www.drupal.org/node/3494040', + E_USER_DEPRECATED + ); + $fileSystem = \Drupal::service(FileSystemInterface::class); + } + $this->fileSystem = $fileSystem; } /** * {@inheritdoc} */ public function guessMimeType($path): ?string { - if ($this->mapping === NULL) { - $mapping = $this->defaultMapping; - // Allow modules to alter the default mapping. - $this->moduleHandler->alter('file_mimetype_mapping', $mapping); - $this->mapping = $mapping; + if (!isset($this->fileSystem)) { + @trigger_error( + 'Calling ' . __METHOD__ . '() without the file_system service already injected is deprecated in drupal:11.2.0 and throws an exception in drupal:12.0.0. See https://www.drupal.org/node/3494040', + E_USER_DEPRECATED + ); + $this->fileSystem = \Drupal::service(FileSystemInterface::class); + } + if (!isset($this->map)) { + @trigger_error( + 'Calling ' . __METHOD__ . '() without the MimeTypeMapInterface service already injected is deprecated in drupal:11.2.0 and throws an exception in drupal:12.0.0. See https://www.drupal.org/node/3494040', + E_USER_DEPRECATED + ); + $this->map = \Drupal::service(MimeTypeMapInterface::class); } $extension = ''; - $file_parts = explode('.', \Drupal::service('file_system')->basename($path)); + $file_parts = explode('.', $this->fileSystem->basename($path)); // Remove the first part: a full filename should not match an extension, // then iterate over the file parts, trying to find a match. @@ -919,8 +975,8 @@ class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface { // empty. while (array_shift($file_parts) !== NULL) { $extension = strtolower(implode('.', $file_parts)); - if (isset($this->mapping['extensions'][$extension])) { - return $this->mapping['mimetypes'][$this->mapping['extensions'][$extension]]; + if ($mimeType = $this->map->getMimeTypeForExtension($extension)) { + return $mimeType; } } @@ -932,9 +988,32 @@ class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface { * * @param array|null $mapping * Passing a NULL mapping will cause guess() to use self::$defaultMapping. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * \Drupal\Core\File\MimeType\MimeTypeMapInterface::addMapping() instead. + * + * @see https://www.drupal.org/node/3494040 */ - public function setMapping(?array $mapping = NULL) { - $this->mapping = $mapping; + public function setMapping(?array $mapping = NULL): void { + @trigger_error( + __METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use \Drupal\Core\File\MimeType\MimeTypeMapInterface::addMapping() instead or define your own MimeTypeMapInterface implementation. See https://www.drupal.org/node/3494040', + E_USER_DEPRECATED + ); + // Convert the mapping to be keyed by type. + $typeMapping = []; + foreach ($mapping['mimetypes'] as $index => $mimetype) { + $typeMapping[$mimetype] = array_keys($mapping['extensions'], $index); + } + + $this->map = new MimeTypeMap(); + foreach ($typeMapping as $type => $extensions) { + foreach ($extensions as $extension) { + $this->map->addMapping($type, $extension); + } + } + \Drupal::service('event_dispatcher')->dispatch( + new MimeTypeMapLoadedEvent($this->map) + ); } /** diff --git a/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php b/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php index d1ec980d68b..3a121c1146c 100644 --- a/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php +++ b/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php @@ -122,7 +122,9 @@ class MimeTypeGuesser implements MimeTypeGuesserInterface { * * @see \Symfony\Component\Mime\MimeTypes */ - public static function registerWithSymfonyGuesser(ContainerInterface $container) { + public static function registerWithSymfonyGuesser( + ContainerInterface $container, + ): void { $guesser = new MimeTypes(); $guesser->registerGuesser($container->get('file.mime_type.guesser')); MimeTypes::setDefault($guesser); diff --git a/core/lib/Drupal/Core/File/MimeType/MimeTypeMap.php b/core/lib/Drupal/Core/File/MimeType/MimeTypeMap.php new file mode 100644 index 00000000000..1a1203241fa --- /dev/null +++ b/core/lib/Drupal/Core/File/MimeType/MimeTypeMap.php @@ -0,0 +1,1018 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\File\MimeType; + +/** + * Provides a sensible mapping between filename extensions and MIME types. + */ +class MimeTypeMap implements MimeTypeMapInterface { + + /** + * The MIME extension mapping. + * + * @var array{'mimetypes': array{int,string}, 'extensions': array{string,int}} + * An array consisting of two arrays: + * - mimetypes: MIME types, keyed by a unique number. + * - extensions: an associative array with the MIME type key numbers as + * values. The keys are file extensions, in lower case and without any + * preceding dot. + * + * cspell:disable. + */ + protected array $mapping = [ + 'mimetypes' => [ + 0 => 'application/andrew-inset', + 1 => 'application/atom', + 2 => 'application/atomcat+xml', + 3 => 'application/atomserv+xml', + 4 => 'application/cap', + 5 => 'application/cu-seeme', + 6 => 'application/dsptype', + 350 => 'application/epub+zip', + 359 => 'application/gzip', + 7 => 'application/hta', + 8 => 'application/java-archive', + 9 => 'application/java-serialized-object', + 10 => 'application/java-vm', + 11 => 'application/mac-binhex40', + 12 => 'application/mathematica', + 13 => 'application/msaccess', + 14 => 'application/msword', + 15 => 'application/octet-stream', + 16 => 'application/oda', + 17 => 'application/ogg', + 18 => 'application/pdf', + 19 => 'application/pgp-keys', + 20 => 'application/pgp-signature', + 21 => 'application/pics-rules', + 22 => 'application/postscript', + 23 => 'application/rar', + 24 => 'application/rdf+xml', + 25 => 'application/rss+xml', + 26 => 'application/rtf', + 27 => 'application/smil', + 349 => 'application/vnd.amazon.ebook', + 28 => 'application/vnd.cinderella', + 29 => 'application/vnd.google-earth.kml+xml', + 30 => 'application/vnd.google-earth.kmz', + 31 => 'application/vnd.mozilla.xul+xml', + 32 => 'application/vnd.ms-excel', + 33 => 'application/vnd.ms-excel.addin.macroEnabled.12', + 34 => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 35 => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 36 => 'application/vnd.ms-excel.template.macroEnabled.12', + 37 => 'application/vnd.ms-pki.seccat', + 38 => 'application/vnd.ms-pki.stl', + 39 => 'application/vnd.ms-powerpoint', + 40 => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 41 => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 42 => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 43 => 'application/vnd.ms-powerpoint.template.macroEnabled.12', + 44 => 'application/vnd.ms-word.document.macroEnabled.12', + 45 => 'application/vnd.ms-word.template.macroEnabled.12', + 46 => 'application/vnd.ms-xpsdocument', + 47 => 'application/vnd.oasis.opendocument.chart', + 48 => 'application/vnd.oasis.opendocument.database', + 49 => 'application/vnd.oasis.opendocument.formula', + 50 => 'application/vnd.oasis.opendocument.graphics', + 51 => 'application/vnd.oasis.opendocument.graphics-template', + 52 => 'application/vnd.oasis.opendocument.image', + 53 => 'application/vnd.oasis.opendocument.presentation', + 54 => 'application/vnd.oasis.opendocument.presentation-template', + 55 => 'application/vnd.oasis.opendocument.spreadsheet', + 56 => 'application/vnd.oasis.opendocument.spreadsheet-template', + 57 => 'application/vnd.oasis.opendocument.text', + 58 => 'application/vnd.oasis.opendocument.text-master', + 59 => 'application/vnd.oasis.opendocument.text-template', + 60 => 'application/vnd.oasis.opendocument.text-web', + 61 => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 62 => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 63 => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 64 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 65 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 66 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 67 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 68 => 'application/vnd.rim.cod', + 69 => 'application/vnd.smaf', + 70 => 'application/vnd.stardivision.calc', + 71 => 'application/vnd.stardivision.chart', + 72 => 'application/vnd.stardivision.draw', + 73 => 'application/vnd.stardivision.impress', + 74 => 'application/vnd.stardivision.math', + 75 => 'application/vnd.stardivision.writer', + 76 => 'application/vnd.stardivision.writer-global', + 77 => 'application/vnd.sun.xml.calc', + 78 => 'application/vnd.sun.xml.calc.template', + 79 => 'application/vnd.sun.xml.draw', + 80 => 'application/vnd.sun.xml.draw.template', + 81 => 'application/vnd.sun.xml.impress', + 82 => 'application/vnd.sun.xml.impress.template', + 83 => 'application/vnd.sun.xml.math', + 84 => 'application/vnd.sun.xml.writer', + 85 => 'application/vnd.sun.xml.writer.global', + 86 => 'application/vnd.sun.xml.writer.template', + 87 => 'application/vnd.symbian.install', + 88 => 'application/vnd.visio', + 89 => 'application/vnd.wap.wbxml', + 90 => 'application/vnd.wap.wmlc', + 91 => 'application/vnd.wap.wmlscriptc', + 92 => 'application/wordperfect', + 93 => 'application/wordperfect5.1', + 94 => 'application/x-123', + 95 => 'application/x-7z-compressed', + 96 => 'application/x-abiword', + 97 => 'application/x-apple-diskimage', + 98 => 'application/x-bcpio', + 99 => 'application/x-bittorrent', + 100 => 'application/x-cab', + 101 => 'application/x-cbr', + 102 => 'application/x-cbz', + 103 => 'application/x-cdf', + 104 => 'application/x-cdlink', + 105 => 'application/x-chess-pgn', + 106 => 'application/x-cpio', + 107 => 'application/x-debian-package', + 108 => 'application/x-director', + 109 => 'application/x-dms', + 110 => 'application/x-doom', + 111 => 'application/x-dvi', + 113 => 'application/x-font', + 114 => 'application/x-freemind', + 115 => 'application/x-futuresplash', + 116 => 'application/x-gnumeric', + 117 => 'application/x-go-sgf', + 118 => 'application/x-graphing-calculator', + 119 => 'application/x-gtar', + 120 => 'application/x-hdf', + 121 => 'application/x-httpd-eruby', + 122 => 'application/x-httpd-php', + 123 => 'application/x-httpd-php-source', + 124 => 'application/x-httpd-php3', + 125 => 'application/x-httpd-php3-preprocessed', + 126 => 'application/x-httpd-php4', + 127 => 'application/x-ica', + 128 => 'application/x-internet-signup', + 129 => 'application/x-iphone', + 130 => 'application/x-iso9660-image', + 131 => 'application/x-java-jnlp-file', + // Per RFC 9239, text/javascript is preferred over application/javascript. + // @see https://www.rfc-editor.org/rfc/rfc9239 + 132 => 'text/javascript', + 133 => 'application/x-jmol', + 134 => 'application/x-kchart', + 135 => 'application/x-killustrator', + 136 => 'application/x-koan', + 137 => 'application/x-kpresenter', + 138 => 'application/x-kspread', + 139 => 'application/x-kword', + 140 => 'application/x-latex', + 141 => 'application/x-lha', + 142 => 'application/x-lyx', + 143 => 'application/x-lzh', + 144 => 'application/x-lzx', + 145 => 'application/x-maker', + 146 => 'application/x-mif', + 351 => 'application/x-mobipocket-ebook', + 352 => 'application/x-mobipocket-ebook', + 147 => 'application/x-ms-wmd', + 148 => 'application/x-ms-wmz', + 149 => 'application/x-msdos-program', + 150 => 'application/x-msi', + 151 => 'application/x-netcdf', + 152 => 'application/x-ns-proxy-autoconfig', + 153 => 'application/x-nwc', + 154 => 'application/x-object', + 155 => 'application/x-oz-application', + 156 => 'application/x-pkcs7-certreqresp', + 157 => 'application/x-pkcs7-crl', + 158 => 'application/x-python-code', + 159 => 'application/x-quicktimeplayer', + 160 => 'application/x-redhat-package-manager', + 161 => 'application/x-shar', + 162 => 'application/x-shockwave-flash', + 163 => 'application/x-stuffit', + 164 => 'application/x-sv4cpio', + 165 => 'application/x-sv4crc', + 166 => 'application/x-tar', + 167 => 'application/x-tcl', + 168 => 'application/x-tex-gf', + 169 => 'application/x-tex-pk', + 170 => 'application/x-texinfo', + 171 => 'application/x-trash', + 172 => 'application/x-troff', + 173 => 'application/x-troff-man', + 174 => 'application/x-troff-me', + 175 => 'application/x-troff-ms', + 176 => 'application/x-ustar', + 177 => 'application/x-wais-source', + 178 => 'application/x-wingz', + 179 => 'application/x-x509-ca-cert', + 180 => 'application/x-xcf', + 181 => 'application/x-xfig', + 182 => 'application/x-xpinstall', + 183 => 'application/xhtml+xml', + 184 => 'application/xml', + 185 => 'application/zip', + 360 => 'audio/aac', + 186 => 'audio/basic', + 112 => 'audio/flac', + 187 => 'audio/midi', + 346 => 'audio/mp4', + 188 => 'audio/mpeg', + 189 => 'audio/ogg', + 190 => 'audio/prs.sid', + 356 => 'audio/webm', + 191 => 'audio/x-aiff', + 192 => 'audio/x-gsm', + 354 => 'audio/x-matroska', + 193 => 'audio/x-mpegurl', + 194 => 'audio/x-ms-wax', + 195 => 'audio/x-ms-wma', + 196 => 'audio/x-pn-realaudio', + 197 => 'audio/x-realaudio', + 198 => 'audio/x-scpls', + 199 => 'audio/x-sd2', + 200 => 'audio/x-wav', + 201 => 'chemical/x-alchemy', + 202 => 'chemical/x-cache', + 203 => 'chemical/x-cache-csf', + 204 => 'chemical/x-cactvs-binary', + 205 => 'chemical/x-cdx', + 206 => 'chemical/x-cerius', + 207 => 'chemical/x-chem3d', + 208 => 'chemical/x-chemdraw', + 209 => 'chemical/x-cif', + 210 => 'chemical/x-cmdf', + 211 => 'chemical/x-cml', + 212 => 'chemical/x-compass', + 213 => 'chemical/x-crossfire', + 214 => 'chemical/x-csml', + 215 => 'chemical/x-ctx', + 216 => 'chemical/x-cxf', + 217 => 'chemical/x-embl-dl-nucleotide', + 218 => 'chemical/x-galactic-spc', + 219 => 'chemical/x-gamess-input', + 220 => 'chemical/x-gaussian-checkpoint', + 221 => 'chemical/x-gaussian-cube', + 222 => 'chemical/x-gaussian-input', + 223 => 'chemical/x-gaussian-log', + 224 => 'chemical/x-gcg8-sequence', + 225 => 'chemical/x-genbank', + 226 => 'chemical/x-hin', + 227 => 'chemical/x-isostar', + 228 => 'chemical/x-jcamp-dx', + 229 => 'chemical/x-kinemage', + 230 => 'chemical/x-macmolecule', + 231 => 'chemical/x-macromodel-input', + 232 => 'chemical/x-mdl-molfile', + 233 => 'chemical/x-mdl-rdfile', + 234 => 'chemical/x-mdl-rxnfile', + 235 => 'chemical/x-mdl-sdfile', + 236 => 'chemical/x-mdl-tgf', + 237 => 'chemical/x-mmcif', + 238 => 'chemical/x-mol2', + 239 => 'chemical/x-molconn-Z', + 240 => 'chemical/x-mopac-graph', + 241 => 'chemical/x-mopac-input', + 242 => 'chemical/x-mopac-out', + 243 => 'chemical/x-mopac-vib', + 244 => 'chemical/x-ncbi-asn1-ascii', + 245 => 'chemical/x-ncbi-asn1-binary', + 246 => 'chemical/x-ncbi-asn1-spec', + 247 => 'chemical/x-pdb', + 248 => 'chemical/x-rosdal', + 249 => 'chemical/x-swissprot', + 250 => 'chemical/x-vamas-iso14976', + 251 => 'chemical/x-vmd', + 252 => 'chemical/x-xtel', + 253 => 'chemical/x-xyz', + 362 => 'image/avif', + 254 => 'image/gif', + 255 => 'image/ief', + 256 => 'image/jpeg', + 257 => 'image/pcx', + 258 => 'image/png', + 259 => 'image/svg+xml', + 260 => 'image/tiff', + 261 => 'image/vnd.djvu', + 262 => 'image/vnd.microsoft.icon', + 263 => 'image/vnd.wap.wbmp', + 355 => 'image/webp', + 264 => 'image/x-cmu-raster', + 265 => 'image/x-coreldraw', + 266 => 'image/x-coreldrawpattern', + 267 => 'image/x-coreldrawtemplate', + 268 => 'image/x-corelphotopaint', + 269 => 'image/x-jg', + 270 => 'image/x-jng', + 271 => 'image/x-ms-bmp', + 272 => 'image/x-photoshop', + 273 => 'image/x-portable-anymap', + 274 => 'image/x-portable-bitmap', + 275 => 'image/x-portable-graymap', + 276 => 'image/x-portable-pixmap', + 277 => 'image/x-rgb', + 278 => 'image/x-xbitmap', + 279 => 'image/x-xpixmap', + 280 => 'image/x-xwindowdump', + 281 => 'message/rfc822', + 282 => 'model/iges', + 283 => 'model/mesh', + 284 => 'model/vrml', + 285 => 'text/calendar', + 286 => 'text/css', + 287 => 'text/csv', + 288 => 'text/h323', + 289 => 'text/html', + 290 => 'text/iuls', + 291 => 'text/mathml', + 292 => 'text/plain', + 293 => 'text/richtext', + 294 => 'text/scriptlet', + 295 => 'text/tab-separated-values', + 296 => 'text/texmacs', + 297 => 'text/vnd.sun.j2me.app-descriptor', + 298 => 'text/vnd.wap.wml', + 299 => 'text/vnd.wap.wmlscript', + 358 => 'text/vtt', + 300 => 'text/x-bibtex', + 301 => 'text/x-boo', + 302 => 'text/x-c++hdr', + 303 => 'text/x-c++src', + 304 => 'text/x-chdr', + 305 => 'text/x-component', + 306 => 'text/x-csh', + 307 => 'text/x-csrc', + 308 => 'text/x-diff', + 309 => 'text/x-dsrc', + 310 => 'text/x-haskell', + 311 => 'text/x-java', + 312 => 'text/x-literate-haskell', + 313 => 'text/x-moc', + 314 => 'text/x-pascal', + 315 => 'text/x-pcs-gcd', + 316 => 'text/x-perl', + 317 => 'text/x-python', + 318 => 'text/x-setext', + 319 => 'text/x-sh', + 320 => 'text/x-tcl', + 321 => 'text/x-tex', + 322 => 'text/x-vcalendar', + 323 => 'text/x-vcard', + 324 => 'video/3gpp', + 325 => 'video/dl', + 326 => 'video/dv', + 327 => 'video/fli', + 328 => 'video/gl', + 329 => 'video/mp4', + 330 => 'video/mpeg', + 331 => 'video/ogg', + 332 => 'video/quicktime', + 333 => 'video/vnd.mpegurl', + 357 => 'video/webm', + 347 => 'video/x-flv', + 334 => 'video/x-la-asf', + 348 => 'video/x-m4v', + 353 => 'video/x-matroska', + 335 => 'video/x-mng', + 336 => 'video/x-ms-asf', + 337 => 'video/x-ms-wm', + 338 => 'video/x-ms-wmv', + 339 => 'video/x-ms-wmx', + 340 => 'video/x-ms-wvx', + 341 => 'video/x-msvideo', + 342 => 'video/x-sgi-movie', + 343 => 'x-conference/x-cooltalk', + 344 => 'x-epoc/x-sisx-app', + 345 => 'x-world/x-vrml', + 361 => 'application/json', + ], + + // Extensions added to this list MUST be lower-case. + 'extensions' => [ + 'ez' => 0, + 'atom' => 1, + 'atomcat' => 2, + 'atomsrv' => 3, + 'cap' => 4, + 'pcap' => 4, + 'cu' => 5, + 'tsp' => 6, + 'hta' => 7, + 'jar' => 8, + 'ser' => 9, + 'class' => 10, + 'hqx' => 11, + 'nb' => 12, + 'mdb' => 13, + 'dot' => 14, + 'doc' => 14, + 'bin' => 15, + 'oda' => 16, + 'ogx' => 17, + 'pdf' => 18, + 'key' => 19, + 'pgp' => 20, + 'prf' => 21, + 'eps' => 22, + 'ai' => 22, + 'ps' => 22, + 'rar' => 23, + 'rdf' => 24, + 'rss' => 25, + 'rtf' => 26, + 'smi' => 27, + 'smil' => 27, + 'cdy' => 28, + 'kml' => 29, + 'kmz' => 30, + 'xul' => 31, + 'xlb' => 32, + 'xlt' => 32, + 'xls' => 32, + 'xlam' => 33, + 'xlsb' => 34, + 'xlsm' => 35, + 'xltm' => 36, + 'cat' => 37, + 'stl' => 38, + 'pps' => 39, + 'ppt' => 39, + 'ppam' => 40, + 'pptm' => 41, + 'ppsm' => 42, + 'potm' => 43, + 'docm' => 44, + 'dotm' => 45, + 'xps' => 46, + 'odc' => 47, + 'odb' => 48, + 'odf' => 49, + 'odg' => 50, + 'otg' => 51, + 'odi' => 52, + 'odp' => 53, + 'otp' => 54, + 'ods' => 55, + 'ots' => 56, + 'odt' => 57, + 'odm' => 58, + 'ott' => 59, + 'oth' => 60, + 'pptx' => 61, + 'ppsx' => 62, + 'potx' => 63, + 'xlsx' => 64, + 'xltx' => 65, + 'docx' => 66, + 'dotx' => 67, + 'cod' => 68, + 'mmf' => 69, + 'sdc' => 70, + 'sds' => 71, + 'sda' => 72, + 'sdd' => 73, + 'sdw' => 75, + 'sgl' => 76, + 'sxc' => 77, + 'stc' => 78, + 'sxd' => 79, + 'std' => 80, + 'sxi' => 81, + 'sti' => 82, + 'sxm' => 83, + 'sxw' => 84, + 'sxg' => 85, + 'stw' => 86, + 'sis' => 87, + 'vsd' => 88, + 'wbxml' => 89, + 'wmlc' => 90, + 'wmlsc' => 91, + 'wpd' => 92, + 'wp5' => 93, + 'wk' => 94, + '7z' => 95, + 'abw' => 96, + 'dmg' => 97, + 'bcpio' => 98, + 'torrent' => 99, + 'cab' => 100, + 'cbr' => 101, + 'cbz' => 102, + 'cdf' => 103, + 'vcd' => 104, + 'pgn' => 105, + 'cpio' => 106, + 'udeb' => 107, + 'deb' => 107, + 'dir' => 108, + 'dxr' => 108, + 'dcr' => 108, + 'dms' => 109, + 'wad' => 110, + 'dvi' => 111, + 'flac' => 112, + 'pfa' => 113, + 'pfb' => 113, + 'pcf' => 113, + 'gsf' => 113, + 'pcf.z' => 113, + 'mm' => 114, + 'spl' => 115, + 'gnumeric' => 116, + 'sgf' => 117, + 'gcf' => 118, + 'taz' => 119, + 'gtar' => 119, + 'tgz' => 119, + 'hdf' => 120, + 'rhtml' => 121, + 'phtml' => 122, + 'pht' => 122, + 'php' => 122, + 'phps' => 123, + 'php3' => 124, + 'php3p' => 125, + 'php4' => 126, + 'ica' => 127, + 'ins' => 128, + 'isp' => 128, + 'iii' => 129, + 'iso' => 130, + 'jnlp' => 131, + 'js' => 132, + 'jmz' => 133, + 'chrt' => 134, + 'kil' => 135, + 'skp' => 136, + 'skd' => 136, + 'skm' => 136, + 'skt' => 136, + 'kpr' => 137, + 'kpt' => 137, + 'ksp' => 138, + 'kwd' => 139, + 'kwt' => 139, + 'latex' => 140, + 'lha' => 141, + 'lyx' => 142, + 'lzh' => 143, + 'lzx' => 144, + 'maker' => 145, + 'frm' => 145, + 'frame' => 145, + 'fm' => 145, + 'book' => 145, + 'fb' => 145, + 'fbdoc' => 145, + 'mif' => 146, + 'wmd' => 147, + 'wmz' => 148, + 'dll' => 149, + 'bat' => 149, + 'exe' => 149, + 'com' => 149, + 'msi' => 150, + 'nc' => 151, + 'pac' => 152, + 'nwc' => 153, + 'o' => 154, + 'oza' => 155, + 'p7r' => 156, + 'crl' => 157, + 'pyo' => 158, + 'pyc' => 158, + 'qtl' => 159, + 'rpm' => 160, + 'shar' => 161, + 'swf' => 162, + 'swfl' => 162, + 'sitx' => 163, + 'sit' => 163, + 'sv4cpio' => 164, + 'sv4crc' => 165, + 'tar' => 166, + 'gf' => 168, + 'pk' => 169, + 'texi' => 170, + 'texinfo' => 170, + 'sik' => 171, + '~' => 171, + 'bak' => 171, + '%' => 171, + 'old' => 171, + 't' => 172, + 'roff' => 172, + 'tr' => 172, + 'man' => 173, + 'me' => 174, + 'ms' => 175, + 'ustar' => 176, + 'src' => 177, + 'wz' => 178, + 'crt' => 179, + 'xcf' => 180, + 'fig' => 181, + 'xpi' => 182, + 'xht' => 183, + 'xhtml' => 183, + 'xml' => 184, + 'xsl' => 184, + 'zip' => 185, + 'au' => 186, + 'snd' => 186, + 'mid' => 187, + 'midi' => 187, + 'kar' => 187, + 'mpega' => 188, + 'mpga' => 188, + 'mp3' => 188, + 'mp2' => 188, + 'ogg' => 189, + 'oga' => 189, + 'opus' => 189, + 'spx' => 189, + 'sid' => 190, + 'aif' => 191, + 'aiff' => 191, + 'aifc' => 191, + 'gsm' => 192, + 'm3u' => 193, + 'wax' => 194, + 'wma' => 195, + 'rm' => 196, + 'ram' => 196, + 'ra' => 197, + 'pls' => 198, + 'sd2' => 199, + 'wav' => 200, + 'alc' => 201, + 'cac' => 202, + 'cache' => 202, + 'csf' => 203, + 'cascii' => 204, + 'cbin' => 204, + 'ctab' => 204, + 'cdx' => 205, + 'cer' => 206, + 'c3d' => 207, + 'chm' => 208, + 'cif' => 209, + 'cmdf' => 210, + 'cml' => 211, + 'cpa' => 212, + 'bsd' => 213, + 'csml' => 214, + 'csm' => 214, + 'ctx' => 215, + 'cxf' => 216, + 'cef' => 216, + 'emb' => 217, + 'embl' => 217, + 'spc' => 218, + 'gam' => 219, + 'inp' => 219, + 'gamin' => 219, + 'fchk' => 220, + 'fch' => 220, + 'cub' => 221, + 'gau' => 222, + 'gjf' => 222, + 'gjc' => 222, + 'gal' => 223, + 'gcg' => 224, + 'gen' => 225, + 'hin' => 226, + 'istr' => 227, + 'ist' => 227, + 'dx' => 228, + 'jdx' => 228, + 'kin' => 229, + 'mcm' => 230, + 'mmd' => 231, + 'mmod' => 231, + 'mol' => 232, + 'rd' => 233, + 'rxn' => 234, + 'sdf' => 235, + 'sd' => 235, + 'tgf' => 236, + 'mcif' => 237, + 'mol2' => 238, + 'b' => 239, + 'gpt' => 240, + 'mopcrt' => 241, + 'zmt' => 241, + 'mpc' => 241, + 'dat' => 241, + 'mop' => 241, + 'moo' => 242, + 'mvb' => 243, + 'prt' => 244, + 'aso' => 245, + 'val' => 245, + 'asn' => 246, + 'ent' => 247, + 'pdb' => 247, + 'ros' => 248, + 'sw' => 249, + 'vms' => 250, + 'vmd' => 251, + 'xtel' => 252, + 'xyz' => 253, + 'gif' => 254, + 'ief' => 255, + 'jpeg' => 256, + 'jpe' => 256, + 'jpg' => 256, + 'pcx' => 257, + 'png' => 258, + 'svgz' => 259, + 'svg' => 259, + 'tif' => 260, + 'tiff' => 260, + 'djvu' => 261, + 'djv' => 261, + 'ico' => 262, + 'wbmp' => 263, + 'ras' => 264, + 'cdr' => 265, + 'pat' => 266, + 'cdt' => 267, + 'cpt' => 268, + 'art' => 269, + 'jng' => 270, + 'bmp' => 271, + 'psd' => 272, + 'pnm' => 273, + 'pbm' => 274, + 'pgm' => 275, + 'ppm' => 276, + 'rgb' => 277, + 'xbm' => 278, + 'xpm' => 279, + 'xwd' => 280, + 'eml' => 281, + 'igs' => 282, + 'iges' => 282, + 'silo' => 283, + 'msh' => 283, + 'mesh' => 283, + 'icz' => 285, + 'ics' => 285, + 'css' => 286, + 'csv' => 287, + '323' => 288, + 'html' => 289, + 'htm' => 289, + 'shtml' => 289, + 'uls' => 290, + 'mml' => 291, + 'txt' => 292, + 'pot' => 292, + 'text' => 292, + 'asc' => 292, + 'rtx' => 293, + 'wsc' => 294, + 'sct' => 294, + 'tsv' => 295, + 'ts' => 296, + 'tm' => 296, + 'jad' => 297, + 'wml' => 298, + 'wmls' => 299, + 'bib' => 300, + 'boo' => 301, + 'hpp' => 302, + 'hh' => 302, + 'h++' => 302, + 'hxx' => 302, + 'cxx' => 303, + 'cc' => 303, + 'cpp' => 303, + 'c++' => 303, + 'h' => 304, + 'htc' => 305, + 'csh' => 306, + 'c' => 307, + 'patch' => 308, + 'diff' => 308, + 'd' => 309, + 'hs' => 310, + 'java' => 311, + 'lhs' => 312, + 'moc' => 313, + 'pas' => 314, + 'p' => 314, + 'gcd' => 315, + 'pm' => 316, + 'pl' => 316, + 'py' => 317, + 'etx' => 318, + 'sh' => 319, + 'tk' => 320, + 'tcl' => 320, + 'cls' => 321, + 'ltx' => 321, + 'sty' => 321, + 'tex' => 321, + 'vcs' => 322, + 'vcf' => 323, + '3gp' => 324, + 'dl' => 325, + 'dif' => 326, + 'dv' => 326, + 'fli' => 327, + 'gl' => 328, + 'mp4' => 329, + 'f4v' => 329, + 'f4p' => 329, + 'mpe' => 330, + 'mpeg' => 330, + 'mpg' => 330, + 'ogv' => 331, + 'qt' => 332, + 'mov' => 332, + 'mxu' => 333, + 'lsf' => 334, + 'lsx' => 334, + 'mng' => 335, + 'asx' => 336, + 'asf' => 336, + 'wm' => 337, + 'wmv' => 338, + 'wmx' => 339, + 'wvx' => 340, + 'avi' => 341, + 'movie' => 342, + 'ice' => 343, + 'sisx' => 344, + 'wrl' => 345, + 'vrm' => 345, + 'vrml' => 345, + 'f4a' => 346, + 'f4b' => 346, + 'm4a' => 346, + 'flv' => 347, + 'm4v' => 348, + 'azw' => 349, + 'epub' => 350, + 'mobi' => 351, + 'prc' => 352, + 'mkv' => 353, + 'mka' => 354, + 'webp' => 355, + 'weba' => 356, + 'webm' => 357, + 'vtt' => 358, + 'gz' => 359, + 'mjs' => 132, + 'aac' => 360, + 'json' => 361, + 'avif' => 362, + ], + ]; + + // cspell:enable + + /** + * {@inheritdoc} + */ + public function addMapping(string $mimetype, string $extension): static { + $mimetype = strtolower($mimetype); + $extension = strtolower($extension); + $this->mapping['mimetypes'] ??= []; + $key = array_search($mimetype, $this->mapping['mimetypes'], TRUE); + if ($key === FALSE) { + $key = count($this->mapping['mimetypes']); + $this->mapping['mimetypes'][] = $mimetype; + } + $this->mapping['extensions'][$extension] = $key; + return $this; + } + + /** + * {@inheritdoc} + */ + public function removeMapping(string $mimetype, string $extension): bool { + $extension = strtolower($extension); + if (isset($this->mapping['extensions'][$extension])) { + unset($this->mapping['extensions'][$extension]); + return TRUE; + } + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function removeMimeType(string $mimetype): bool { + $mimetype = strtolower($mimetype); + $key = array_search($mimetype, $this->mapping['mimetypes'], TRUE); + if ($key === FALSE) { + return FALSE; + } + foreach ($this->getExtensionsForMimeType($mimetype) as $extension) { + $this->removeMapping($mimetype, $extension); + } + unset($this->mapping['mimetypes'][$key]); + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function listMimeTypes(): array { + $mimeTypes = array_values($this->mapping['mimetypes']); + sort($mimeTypes); + + return $mimeTypes; + } + + /** + * {@inheritdoc} + */ + public function listExtensions(): array { + $extensions = array_keys($this->mapping['extensions']); + sort($extensions); + + return $extensions; + } + + /** + * {@inheritdoc} + */ + public function hasMimeType(string $mimetype): bool { + return in_array( + strtolower($mimetype), + $this->mapping['mimetypes'], + TRUE + ); + } + + /** + * {@inheritdoc} + */ + public function hasExtension(string $extension): bool { + return isset($this->mapping['extensions'][strtolower($extension)]); + } + + /** + * {@inheritdoc} + */ + public function getMimeTypeForExtension($extension): ?string { + $extension = strtolower($extension); + $extensions = $this->mapping['extensions'] ?? []; + + return isset($extensions[$extension]) ? $this->mapping['mimetypes'][$extensions[$extension]] : NULL; + } + + /** + * {@inheritdoc} + */ + public function getExtensionsForMimeType($mimetype): array { + $mimetype = strtolower($mimetype); + $key = array_search($mimetype, $this->mapping['mimetypes'], TRUE); + if ($key === FALSE) { + return []; + } + $extensions = array_keys($this->mapping['extensions'], $key, TRUE); + sort($extensions); + return $extensions; + } + + /** + * Gets the underlying mapping array. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no + * replacement for this method. + * + * @see https://www.drupal.org/node/3494040 + * @see \Drupal\Core\File\EventSubscriber\LegacyMimeTypeMapLoadedSubscriber + */ + public function getMapping(): array { + // We do not trigger a deprecation error as this method is needed for + // calling the deprecated hook_file_mimetype_mapping_alter() in + // LegacyMimeTypeMapLoadedSubscriber::onMimeTypeMapLoaded(). + return $this->mapping; + } + + /** + * Sets the underlying mapping array. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no + * replacement for this method. + * + * @see https://www.drupal.org/node/3494040 + */ + public function setMapping(array $mapping): void { + // We do not trigger a deprecation error as this method is needed for + // calling the deprecated hook_file_mimetype_mapping_alter() in + // LegacyMimeTypeMapLoadedSubscriber::onMimeTypeMapLoaded(). + $this->mapping = $mapping; + } + +} diff --git a/core/lib/Drupal/Core/File/MimeType/MimeTypeMapFactory.php b/core/lib/Drupal/Core/File/MimeType/MimeTypeMapFactory.php new file mode 100644 index 00000000000..573a33fec5c --- /dev/null +++ b/core/lib/Drupal/Core/File/MimeType/MimeTypeMapFactory.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\File\MimeType; + +use Drupal\Core\File\Event\MimeTypeMapLoadedEvent; +use Psr\EventDispatcher\EventDispatcherInterface; + +/** + * Factory for creating the MIME type map. + */ +class MimeTypeMapFactory { + + public function __construct( + protected readonly EventDispatcherInterface $eventDispatcher, + ) {} + + /** + * Creates an instance of the MIME type map. + * + * @return \Drupal\Core\File\MimeType\MimeTypeMapInterface + * The MIME type map. + */ + public function create(): MimeTypeMapInterface { + $map = $this->doCreateMap(); + $this->eventDispatcher->dispatch(new MimeTypeMapLoadedEvent($map)); + return $map; + } + + protected function doCreateMap(): MimeTypeMapInterface { + return new MimeTypeMap(); + } + +} diff --git a/core/lib/Drupal/Core/File/MimeType/MimeTypeMapInterface.php b/core/lib/Drupal/Core/File/MimeType/MimeTypeMapInterface.php new file mode 100644 index 00000000000..5483c67d8a1 --- /dev/null +++ b/core/lib/Drupal/Core/File/MimeType/MimeTypeMapInterface.php @@ -0,0 +1,108 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\File\MimeType; + +/** + * Provides an interface for MIME type to file extension mapping. + */ +interface MimeTypeMapInterface { + + /** + * Adds a mapping between a MIME type and an extension. + * + * @param string $mimetype + * The MIME type the passed extension should map. + * @param string $extension + * The extension(s) that should map to the passed MIME type. + * + * @return $this + */ + public function addMapping(string $mimetype, string $extension): self; + + /** + * Removes the mapping between a MIME type and an extension. + * + * @param string $mimetype + * The MIME type to be removed from the mapping. + * @param string $extension + * The extension to be removed from the mapping. + * + * @return bool + * TRUE if the extension was present, FALSE otherwise. + */ + public function removeMapping(string $mimetype, string $extension): bool; + + /** + * Removes a MIME type and all its mapped extensions from the mapping. + * + * @param string $mimetype + * The MIME type to be removed from the mapping. + * + * @return bool + * TRUE if the MIME type was present, FALSE otherwise. + */ + public function removeMimeType(string $mimetype): bool; + + /** + * Returns known MIME types. + * + * @return string[] + * An array of MIME types. + */ + public function listMimeTypes(): array; + + /** + * Returns known file extensions. + * + * @return string[] + * An array of file extensions. + */ + public function listExtensions(): array; + + /** + * Determines if a MIME type exists. + * + * @param string $mimetype + * The mime type. + * + * @return bool + * TRUE if the MIME type exists, FALSE otherwise. + */ + public function hasMimeType(string $mimetype): bool; + + /** + * Determines if a file extension exists. + * + * @param string $extension + * The file extension. + * + * @return bool + * TRUE if the file extension exists, FALSE otherwise. + */ + public function hasExtension(string $extension): bool; + + /** + * Returns the appropriate MIME type for a given file extension. + * + * @param string $extension + * A file extension, without leading dot. + * + * @return string|null + * A matching MIME type, or NULL if no MIME type matches the extension. + */ + public function getMimeTypeForExtension(string $extension): ?string; + + /** + * Returns the appropriate extensions for a given MIME type. + * + * @param string $mimetype + * A MIME type. + * + * @return string[] + * An array of file extensions matching the MIME type, without leading dot. + */ + public function getExtensionsForMimeType(string $mimetype): array; + +} diff --git a/core/lib/Drupal/Core/File/file.api.php b/core/lib/Drupal/Core/File/file.api.php index 04e6e8a43df..0c18aece9cd 100644 --- a/core/lib/Drupal/Core/File/file.api.php +++ b/core/lib/Drupal/Core/File/file.api.php @@ -109,16 +109,19 @@ function hook_file_url_alter(&$uri) { /** * Alter MIME type mappings used to determine MIME type from a file extension. * - * Invoked by - * \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::guessMimeType(). It is - * used to allow modules to add to or modify the default mapping from - * \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::$defaultMapping. - * * @param array $mapping * An array of mimetypes correlated to the extensions that relate to them. * The array has 'mimetypes' and 'extensions' elements, each of which is an * array. * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Create a + * \Drupal\Core\File\Event\MimeTypeMapLoadedEvent subscriber instead. + * + * It is used to allow modules to add to or modify the default mapping of + * MIME type to file extensions. + * + * @see https://www.drupal.org/node/3494040 + * @see \Drupal\Core\File\EventSubscriber\LegacyMimeTypeMapLoadedSubscriber * @see \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::guessMimeType() * @see \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::$defaultMapping */ diff --git a/core/lib/Drupal/Core/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php b/core/lib/Drupal/Core/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php index 2cb59856a95..a9e4bd55f15 100644 --- a/core/lib/Drupal/Core/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php +++ b/core/lib/Drupal/Core/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php @@ -78,9 +78,9 @@ namespace Drupal\Core\ProxyClass\File\MimeType { /** * {@inheritdoc} */ - public function setMapping(?array $mapping = NULL) + public function setMapping(?array $mapping = NULL): void { - return $this->lazyLoadItself()->setMapping($mapping); + $this->lazyLoadItself()->setMapping($mapping); } /** diff --git a/core/lib/Drupal/Core/ProxyClass/File/MimeType/MimeTypeGuesser.php b/core/lib/Drupal/Core/ProxyClass/File/MimeType/MimeTypeGuesser.php index 0a259a396c0..5480214fc1e 100644 --- a/core/lib/Drupal/Core/ProxyClass/File/MimeType/MimeTypeGuesser.php +++ b/core/lib/Drupal/Core/ProxyClass/File/MimeType/MimeTypeGuesser.php @@ -91,6 +91,14 @@ namespace Drupal\Core\ProxyClass\File\MimeType { return $this->lazyLoadItself()->isGuesserSupported(); } + /** + * {@inheritdoc} + */ + public static function registerWithSymfonyGuesser(\Symfony\Component\DependencyInjection\ContainerInterface $container): void + { + \Drupal\Core\File\MimeType\MimeTypeGuesser::registerWithSymfonyGuesser($container); + } + } } diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php b/core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php index 7b749cfbce4..c00f4aeb397 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php @@ -6,6 +6,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Field\EntityReferenceFieldItemListInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\File\MimeType\MimeTypeMapInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Template\Attribute; @@ -75,12 +76,12 @@ abstract class FileMediaFormatterBase extends FileFormatterBase implements FileM if (!parent::isApplicable($field_definition)) { return FALSE; } - /** @var \Symfony\Component\Mime\MimeTypeGuesserInterface $extension_mime_type_guesser */ - $extension_mime_type_guesser = \Drupal::service('file.mime_type.guesser.extension'); + /** @var \Drupal\Core\File\MimeType\MimeTypeMapInterface $mime_type_map */ + $mime_type_map = \Drupal::service(MimeTypeMapInterface::class); $extension_list = array_filter(preg_split('/\s+/', $field_definition->getSetting('file_extensions'))); foreach ($extension_list as $extension) { - $mime_type = $extension_mime_type_guesser->guessMimeType('fakedFile.' . $extension); + $mime_type = $mime_type_map->getMimeTypeForExtension($extension); if ($mime_type !== NULL && static::mimeTypeApplies($mime_type)) { return TRUE; } diff --git a/core/modules/file/tests/file_deprecated_test/file_deprecated_test.info.yml b/core/modules/file/tests/file_deprecated_test/file_deprecated_test.info.yml new file mode 100644 index 00000000000..50907a4f62e --- /dev/null +++ b/core/modules/file/tests/file_deprecated_test/file_deprecated_test.info.yml @@ -0,0 +1,4 @@ +name: 'File deprecation test' +type: module +description: 'Support module for testing deprecated file features.' +package: Testing diff --git a/core/modules/file/tests/file_deprecated_test/file_deprecated_test.module b/core/modules/file/tests/file_deprecated_test/file_deprecated_test.module new file mode 100644 index 00000000000..625f5ee9dbb --- /dev/null +++ b/core/modules/file/tests/file_deprecated_test/file_deprecated_test.module @@ -0,0 +1,35 @@ +<?php + +/** + * @file + * Support module for testing deprecated file features. + */ + +declare(strict_types=1); + +// cspell:ignore garply tarz + +/** + * Implements hook_file_mimetype_mapping_alter(). + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Kept only for BC test coverage, see \Drupal\KernelTests\Core\File\MimeTypeLegacyTest. + * + * @see https://www.drupal.org/node/3494040 + */ +function file_deprecated_test_file_mimetype_mapping_alter(&$mapping): void { + // Add new mappings. + $mapping['mimetypes']['file_test_mimetype_1'] = 'made_up/file_test_1'; + $mapping['mimetypes']['file_test_mimetype_2'] = 'made_up/file_test_2'; + $mapping['mimetypes']['file_test_mimetype_3'] = 'made_up/doc'; + $mapping['mimetypes']['application-x-compress'] = 'application/x-compress'; + $mapping['mimetypes']['application-x-tarz'] = 'application/x-tarz'; + $mapping['mimetypes']['application-x-garply-waldo'] = 'application/x-garply-waldo'; + $mapping['extensions']['file_test_1'] = 'file_test_mimetype_1'; + $mapping['extensions']['file_test_2'] = 'file_test_mimetype_2'; + $mapping['extensions']['file_test_3'] = 'file_test_mimetype_2'; + $mapping['extensions']['z'] = 'application-x-compress'; + $mapping['extensions']['tar.z'] = 'application-x-tarz'; + $mapping['extensions']['garply.waldo'] = 'application-x-garply-waldo'; + // Override existing mapping. + $mapping['extensions']['doc'] = 'file_test_mimetype_3'; +} diff --git a/core/modules/file/tests/file_test/file_test.services.yml b/core/modules/file/tests/file_test/file_test.services.yml index e70db1ebd65..095f9e42c0c 100644 --- a/core/modules/file/tests/file_test/file_test.services.yml +++ b/core/modules/file/tests/file_test/file_test.services.yml @@ -20,3 +20,6 @@ services: tags: - { name: stream_wrapper, scheme: dummy1 } - { name: stream_wrapper, scheme: dummy2 } + Drupal\file_test\EventSubscriber\DummyMimeTypeMapLoadedSubscriber: + autowire: true + public: false diff --git a/core/modules/file/tests/file_test/src/EventSubscriber/DummyMimeTypeMapLoadedSubscriber.php b/core/modules/file/tests/file_test/src/EventSubscriber/DummyMimeTypeMapLoadedSubscriber.php new file mode 100644 index 00000000000..9f9e5e98438 --- /dev/null +++ b/core/modules/file/tests/file_test/src/EventSubscriber/DummyMimeTypeMapLoadedSubscriber.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\file_test\EventSubscriber; + +use Drupal\Core\File\Event\MimeTypeMapLoadedEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +// cspell:ignore garply tarz + +/** + * Modifies the MIME type map by adding dummy mappings. + */ +class DummyMimeTypeMapLoadedSubscriber implements EventSubscriberInterface { + + /** + * {@inheritdoc} + */ + public function onMimeTypeMapLoaded(MimeTypeMapLoadedEvent $event): void { + // Add new mappings. + $event->map->addMapping('made_up/file_test_1', 'file_test_1'); + $event->map->addMapping('made_up/file_test_2', 'file_test_2'); + $event->map->addMapping('made_up/file_test_2', 'file_test_3'); + $event->map->addMapping('application/x-compress', 'z'); + $event->map->addMapping('application/x-tarz', 'tar.z'); + $event->map->addMapping('application/x-garply-waldo', 'garply.waldo'); + // Override existing mapping. + $event->map->addMapping('made_up/doc', 'doc'); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + MimeTypeMapLoadedEvent::class => 'onMimeTypeMapLoaded', + ]; + } + +} diff --git a/core/modules/file/tests/file_test/src/Hook/FileTestHooks.php b/core/modules/file/tests/file_test/src/Hook/FileTestHooks.php index c58c2eba4fd..f23949b6b95 100644 --- a/core/modules/file/tests/file_test/src/Hook/FileTestHooks.php +++ b/core/modules/file/tests/file_test/src/Hook/FileTestHooks.php @@ -4,10 +4,10 @@ declare(strict_types=1); namespace Drupal\file_test\Hook; +use Drupal\Core\Hook\Attribute\Hook; use Drupal\file\Entity\File; use Drupal\file_test\FileTestCdn; use Drupal\file_test\FileTestHelper; -use Drupal\Core\Hook\Attribute\Hook; // cspell:ignore tarz // cspell:ignore garply @@ -167,28 +167,6 @@ class FileTestHooks { } /** - * Implements hook_file_mimetype_mapping_alter(). - */ - #[Hook('file_mimetype_mapping_alter')] - public function fileMimetypeMappingAlter(&$mapping): void { - // Add new mappings. - $mapping['mimetypes']['file_test_mimetype_1'] = 'made_up/file_test_1'; - $mapping['mimetypes']['file_test_mimetype_2'] = 'made_up/file_test_2'; - $mapping['mimetypes']['file_test_mimetype_3'] = 'made_up/doc'; - $mapping['mimetypes']['application-x-compress'] = 'application/x-compress'; - $mapping['mimetypes']['application-x-tarz'] = 'application/x-tarz'; - $mapping['mimetypes']['application-x-garply-waldo'] = 'application/x-garply-waldo'; - $mapping['extensions']['file_test_1'] = 'file_test_mimetype_1'; - $mapping['extensions']['file_test_2'] = 'file_test_mimetype_2'; - $mapping['extensions']['file_test_3'] = 'file_test_mimetype_2'; - $mapping['extensions']['z'] = 'application-x-compress'; - $mapping['extensions']['tar.z'] = 'application-x-tarz'; - $mapping['extensions']['garply.waldo'] = 'application-x-garply-waldo'; - // Override existing mapping. - $mapping['extensions']['doc'] = 'file_test_mimetype_3'; - } - - /** * Implements hook_entity_type_alter(). */ #[Hook('entity_type_alter')] diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module index 90b1fb26b01..9fe3dc72b50 100644 --- a/core/modules/responsive_image/responsive_image.module +++ b/core/modules/responsive_image/responsive_image.module @@ -4,6 +4,7 @@ * @file */ +use Drupal\Core\File\MimeType\MimeTypeMapInterface; use Drupal\Core\Template\Attribute; use Drupal\Core\Logger\RfcLogLevel; use Drupal\image\Entity\ImageStyle; @@ -435,22 +436,25 @@ function responsive_image_get_image_dimensions($image_style_name, array $dimensi * @param string $extension * The original extension of the image (without the leading dot). * - * @return string - * The MIME type of the image after the image style is applied. + * @return string|null + * The MIME type of the image after the image style is applied, or NULL if not + * found. */ function responsive_image_get_mime_type($image_style_name, $extension) { - if ($image_style_name == ResponsiveImageStyleInterface::EMPTY_IMAGE) { - return 'image/gif'; - } - // The MIME type guesser needs a full path, not just an extension, but the - // file doesn't have to exist. - if ($image_style_name === ResponsiveImageStyleInterface::ORIGINAL_IMAGE) { - $fake_path = 'responsive_image.' . $extension; - } - else { - $fake_path = 'responsive_image.' . ImageStyle::load($image_style_name)->getDerivativeExtension($extension); - } - return \Drupal::service('file.mime_type.guesser.extension')->guessMimeType($fake_path); + $extension = match ($image_style_name) { + // The file extension for the empty image is 'gif'. + ResponsiveImageStyleInterface::EMPTY_IMAGE => 'gif', + // If using the original image, the file extension is just passed on to the + // MIME type mapper. + ResponsiveImageStyleInterface::ORIGINAL_IMAGE => $extension, + // In all other cases, get the file extension of the derivative image from + // the image style configuration. + default => ImageStyle::load($image_style_name)->getDerivativeExtension($extension), + }; + + return \Drupal::service(MimeTypeMapInterface::class)->getMimeTypeForExtension( + $extension + ); } /** diff --git a/core/tests/Drupal/KernelTests/Core/File/ExtensionMimeTypeGuesserDeprecationTest.php b/core/tests/Drupal/KernelTests/Core/File/ExtensionMimeTypeGuesserDeprecationTest.php new file mode 100644 index 00000000000..e00864d2e45 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/File/ExtensionMimeTypeGuesserDeprecationTest.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\KernelTests\Core\File; + +use Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser; +use Drupal\KernelTests\KernelTestBase; + +/** + * Tests that deprecation messages are raised for deprecations. + * + * @covers \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser + * @group file + * @group legacy + * + * @todo Remove this class once deprecations are removed. + */ +class ExtensionMimeTypeGuesserDeprecationTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['system', 'file_deprecated_test']; + + /** + * Tests that deprecations are raised for missing constructor arguments. + * + * @covers \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::__construct + * @group legacy + */ + public function testConstructorDeprecation(): void { + $this->expectDeprecation( + 'Calling Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::__construct() with the $map argument as an instance of \Drupal\Core\Extension\ModuleHandlerInterface is deprecated in drupal:11.2.0 and an instance of \Drupal\Core\File\MimeType\MimeTypeMapInterface is required in drupal:12.0.0. See https://www.drupal.org/node/3494040' + ); + + new ExtensionMimeTypeGuesser( + \Drupal::service('module_handler'), + ); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/File/MimeType/ExtensionMimeTypeGuesserTest.php b/core/tests/Drupal/KernelTests/Core/File/MimeType/ExtensionMimeTypeGuesserTest.php new file mode 100644 index 00000000000..9dbd3ef8e9a --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/File/MimeType/ExtensionMimeTypeGuesserTest.php @@ -0,0 +1,133 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\KernelTests\Core\File\MimeType; + +use Drupal\KernelTests\KernelTestBase; + +// cspell:ignore garply tarz + +/** + * Tests filename mimetype detection. + * + * @group File + * @coversDefaultClass \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser + */ +class ExtensionMimeTypeGuesserTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['file_deprecated_test', 'file_test']; + + /** + * Tests mapping of mimetypes from filenames. + * + * @covers ::guessMimeType + * @group legacy + */ + public function testGuessMimeType(): void { + $prefixes = ['public://', 'private://', 'temporary://', 'dummy-remote://']; + + $test_case = [ + 'test.jar' => 'application/java-archive', + 'test.jpeg' => 'image/jpeg', + 'test.JPEG' => 'image/jpeg', + 'test.jpg' => 'image/jpeg', + 'test.jar.jpg' => 'image/jpeg', + 'test.jpg.jar' => 'application/java-archive', + 'test.pcf.Z' => 'application/x-font', + 'test.garply.waldo' => 'application/x-garply-waldo', + 'pcf.z' => 'application/x-compress', + 'jar' => NULL, + 'garply.waldo' => NULL, + 'some.junk' => NULL, + // Mime type added by file_test_mimetype_alter() + 'foo.file_test_1' => 'made_up/file_test_1', + 'foo.file_test_2' => 'made_up/file_test_2', + 'foo.doc' => 'made_up/doc', + 'test.ogg' => 'audio/ogg', + 'foobar.z' => 'application/x-compress', + 'foobar.tar' => 'application/x-tar', + 'foobar.tar.z' => 'application/x-tarz', + 'foobar.0.zip' => 'application/zip', + 'foobar..zip' => 'application/zip', + ]; + + $this->expectDeprecation( + 'The deprecated alter hook hook_file_mimetype_mapping_alter() is implemented in these locations: file_deprecated_test_file_mimetype_mapping_alter. This hook is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Implement a \Drupal\Core\File\Event\MimeTypeMapLoadedEvent listener instead. See https://www.drupal.org/node/3494040' + ); + + /** @var \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser $guesser */ + $guesser = \Drupal::service('file.mime_type.guesser.extension'); + // Test using default mappings. + foreach ($test_case as $input => $expected) { + // Test stream [URI]. + foreach ($prefixes as $prefix) { + $output = $guesser->guessMimeType($prefix . $input); + $this->assertSame($expected, $output); + } + + // Test normal path equivalent. + $output = $guesser->guessMimeType($input); + $this->assertSame($expected, $output); + } + } + + /** + * Tests mapping of mimetypes from filenames. + * + * @group legacy + * @covers ::guessMimeType + * @covers ::setMapping + */ + public function testFileMimeTypeDetectionCustomMapping(): void { + /** @var \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser $extension_guesser */ + $extension_guesser = \Drupal::service('file.mime_type.guesser.extension'); + + // Pass in a custom mapping. + $mapping = [ + 'mimetypes' => [ + 0 => 'application/java-archive', + 1 => 'image/jpeg', + ], + 'extensions' => [ + 'jar' => 0, + 'jpg' => 1, + ], + ]; + + $this->expectDeprecation( + 'Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::setMapping() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use \Drupal\Core\File\MimeType\MimeTypeMapInterface::addMapping() instead or define your own MimeTypeMapInterface implementation. See https://www.drupal.org/node/3494040' + ); + $extension_guesser->setMapping($mapping); + + $test_case = [ + 'test.jar' => 'application/java-archive', + 'test.jpeg' => 'image/jpeg', + 'test.jpg' => 'image/jpeg', + 'test.jar.jpg' => 'image/jpeg', + 'test.jpg.jar' => 'application/java-archive', + 'test.pcf.z' => 'application/x-font', + 'test.garply.waldo' => 'application/x-garply-waldo', + 'pcf.z' => 'application/x-compress', + 'jar' => NULL, + 'garply.waldo' => NULL, + 'some.junk' => NULL, + 'foo.file_test_1' => 'made_up/file_test_1', + 'foo.file_test_2' => 'made_up/file_test_2', + 'foo.doc' => 'made_up/doc', + 'test.ogg' => 'audio/ogg', + 'foobar.z' => 'application/x-compress', + 'foobar.tar' => 'application/x-tar', + 'foobar.tar.z' => 'application/x-tarz', + ]; + + foreach ($test_case as $input => $expected) { + $output = $extension_guesser->guessMimeType($input); + $this->assertSame($expected, $output, 'Failed for extension ' . $input); + } + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/File/MimeType/LegacyMimeTypeTest.php b/core/tests/Drupal/KernelTests/Core/File/MimeType/LegacyMimeTypeTest.php new file mode 100644 index 00000000000..ae713fc27ae --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/File/MimeType/LegacyMimeTypeTest.php @@ -0,0 +1,38 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\KernelTests\Core\File\MimeType; + +use Drupal\Core\File\MimeType\MimeTypeMapInterface; +use Drupal\KernelTests\KernelTestBase; + +/** + * Tests deprecated file features. + * + * @group legacy + * @group File + */ +class LegacyMimeTypeTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['system', 'file_deprecated_test']; + + /** + * Tests deprecation of hook_file_mimetype_mapping_alter. + * + * @group legacy + */ + public function testHookFileMimetypeMappingAlter(): void { + $this->expectDeprecation( + 'The deprecated alter hook hook_file_mimetype_mapping_alter() is implemented in these locations: file_deprecated_test_file_mimetype_mapping_alter. This hook is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Implement a \Drupal\Core\File\Event\MimeTypeMapLoadedEvent listener instead. See https://www.drupal.org/node/3494040' + ); + + $map = \Drupal::service(MimeTypeMapInterface::class); + $this->assertEquals(['file_test_2', 'file_test_3'], + $map->getExtensionsForMimeType('made_up/file_test_2')); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/File/MimeTypeTest.php b/core/tests/Drupal/KernelTests/Core/File/MimeTypeTest.php deleted file mode 100644 index 653902e9f58..00000000000 --- a/core/tests/Drupal/KernelTests/Core/File/MimeTypeTest.php +++ /dev/null @@ -1,106 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\KernelTests\Core\File; - -// cspell:ignore garply tarz - -/** - * Tests filename mimetype detection. - * - * @group File - */ -class MimeTypeTest extends FileTestBase { - - /** - * {@inheritdoc} - */ - protected static $modules = ['file_test']; - - /** - * Tests mapping of mimetypes from filenames. - */ - public function testFileMimeTypeDetection(): void { - $prefixes = ['public://', 'private://', 'temporary://', 'dummy-remote://']; - - $test_case = [ - 'test.jar' => 'application/java-archive', - 'test.jpeg' => 'image/jpeg', - 'test.JPEG' => 'image/jpeg', - 'test.jpg' => 'image/jpeg', - 'test.jar.jpg' => 'image/jpeg', - 'test.jpg.jar' => 'application/java-archive', - 'test.pcf.Z' => 'application/x-font', - 'test.garply.waldo' => 'application/x-garply-waldo', - 'pcf.z' => 'application/x-compress', - 'jar' => 'application/octet-stream', - 'garply.waldo' => 'application/octet-stream', - 'some.junk' => 'application/octet-stream', - 'foo.file_test_1' => 'made_up/file_test_1', - 'foo.file_test_2' => 'made_up/file_test_2', - 'foo.doc' => 'made_up/doc', - 'test.ogg' => 'audio/ogg', - 'foobar.z' => 'application/x-compress', - 'foobar.tar' => 'application/x-tar', - 'foobar.tar.z' => 'application/x-tarz', - 'foobar.0.zip' => 'application/zip', - 'foobar..zip' => 'application/zip', - ]; - - $guesser = $this->container->get('file.mime_type.guesser'); - // Test using default mappings. - foreach ($test_case as $input => $expected) { - // Test stream [URI]. - foreach ($prefixes as $prefix) { - $output = $guesser->guessMimeType($prefix . $input); - $this->assertSame($expected, $output); - } - - // Test normal path equivalent - $output = $guesser->guessMimeType($input); - $this->assertSame($expected, $output); - } - - // Now test the extension guesser by passing in a custom mapping. - $mapping = [ - 'mimetypes' => [ - 0 => 'application/java-archive', - 1 => 'image/jpeg', - ], - 'extensions' => [ - 'jar' => 0, - 'jpg' => 1, - ], - ]; - - $test_case = [ - 'test.jar' => 'application/java-archive', - 'test.jpeg' => NULL, - 'test.jpg' => 'image/jpeg', - 'test.jar.jpg' => 'image/jpeg', - 'test.jpg.jar' => 'application/java-archive', - 'test.pcf.z' => NULL, - 'test.garply.waldo' => NULL, - 'pcf.z' => NULL, - 'jar' => NULL, - 'garply.waldo' => NULL, - 'some.junk' => NULL, - 'foo.file_test_1' => NULL, - 'foo.file_test_2' => NULL, - 'foo.doc' => NULL, - 'test.ogg' => NULL, - 'foobar.z' => NULL, - 'foobar.tar' => NULL, - 'foobar.tar.z' => NULL, - ]; - $extension_guesser = $this->container->get('file.mime_type.guesser.extension'); - $extension_guesser->setMapping($mapping); - - foreach ($test_case as $input => $expected) { - $output = $extension_guesser->guessMimeType($input); - $this->assertSame($expected, $output); - } - } - -} diff --git a/core/tests/Drupal/Tests/Core/File/MimeType/MimeTypeMapTest.php b/core/tests/Drupal/Tests/Core/File/MimeType/MimeTypeMapTest.php new file mode 100644 index 00000000000..c0a7995b15c --- /dev/null +++ b/core/tests/Drupal/Tests/Core/File/MimeType/MimeTypeMapTest.php @@ -0,0 +1,116 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\Core\File\MimeType; + +use Drupal\Core\File\MimeType\MimeTypeMap; +use Drupal\Tests\UnitTestCase; + +/** + * Tests the MIME type mapper to extension. + * + * @coversDefaultClass \Drupal\Core\File\MimeType\MimeTypeMap + * + * @group File + */ +class MimeTypeMapTest extends UnitTestCase { + + /** + * The default MIME type map under test. + */ + protected MimeTypeMap $map; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->map = new MimeTypeMap(); + } + + /** + * @covers ::addMapping + */ + public function testAddMapping(): void { + $this->map->addMapping('image/gif', 'gif'); + $this->assertEquals( + 'image/gif', + $this->map->getMimeTypeForExtension('gif') + ); + + $this->map->addMapping('image/jpeg', 'jpeg'); + $this->assertEquals( + 'image/jpeg', + $this->map->getMimeTypeForExtension('jpeg') + ); + } + + /** + * @covers ::removeMapping + */ + public function testRemoveMapping(): void { + $this->assertTrue($this->map->removeMapping('image/jpeg', 'jpg')); + $this->assertNull($this->map->getMimeTypeForExtension('jpg')); + $this->assertFalse($this->map->removeMapping('bar', 'foo')); + } + + /** + * @covers ::removeMimeType + */ + public function testRemoveMimeType(): void { + $this->assertTrue($this->map->removeMimeType('image/jpeg')); + $this->assertNull($this->map->getMimeTypeForExtension('jpg')); + $this->assertFalse($this->map->removeMimeType('foo/bar')); + } + + /** + * @covers ::listMimeTypes + */ + public function testListMimeTypes(): void { + $mimeTypes = $this->map->listMimeTypes(); + $this->assertContains('application/java-archive', $mimeTypes); + $this->assertContains('image/jpeg', $mimeTypes); + } + + /** + * @covers ::hasMimeType + */ + public function testHasMimeType(): void { + $this->assertTrue($this->map->hasMimeType('image/jpeg')); + $this->assertFalse($this->map->hasMimeType('foo/bar')); + } + + /** + * @covers ::getMimeTypeForExtension + */ + public function testGetMimeTypeForExtension(): void { + $this->assertSame('image/jpeg', $this->map->getMimeTypeForExtension('jpe')); + } + + /** + * @covers ::getExtensionsForMimeType + */ + public function testGetExtensionsForMimeType(): void { + $this->assertEquals(['jpe', 'jpeg', 'jpg'], + $this->map->getExtensionsForMimeType('image/jpeg')); + } + + /** + * @covers ::listExtensions + */ + public function testListExtension(): void { + $extensions = $this->map->listExtensions(); + $this->assertContains('jar', $extensions); + $this->assertContains('jpg', $extensions); + } + + /** + * @covers ::hasExtension + */ + public function testHasExtension(): void { + $this->assertTrue($this->map->hasExtension('jpg')); + $this->assertFalse($this->map->hasExtension('foo')); + } + +} |