diff --git a/core/core.services.yml b/core/core.services.yml index a8d9d234490f6903eaa13b0cfc7d928e83c73545..8bd4ccb155aa4de559013a809606fefd08ae17ae 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -825,3 +825,12 @@ services: element_info: class: Drupal\Core\Render\ElementInfo arguments: ['@module_handler'] + file.mime_type.guesser: + class: Drupal\Core\File\MimeType\MimeTypeGuesser + tags: + - { name: service_collector, tag: mime_type_guesser, call: addGuesser } + file.mime_type.guesser.extension: + class: Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser + arguments: ['@module_handler'] + tags: + - { name: mime_type_guesser } diff --git a/core/includes/file.inc b/core/includes/file.inc index 02bd1078b16acb9a544c2518eec5b643b153af84..18c28208f569097a8f5566c82b3a227bb5d90e1d 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -6,7 +6,6 @@ */ use Drupal\Component\Utility\UrlHelper; -use Drupal\Core\StreamWrapper\LocalStream; use Drupal\Component\PhpStorage\FileStorage; use Drupal\Component\Utility\Bytes; use Drupal\Component\Utility\String; @@ -1300,17 +1299,11 @@ function file_upload_max_size() { * The internet media type registered for the extension or * application/octet-stream for unknown extensions. * - * @see file_default_mimetype_mapping() + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal::service('file.mime_type.guesser')->guess($uri). */ function file_get_mimetype($uri, $mapping = NULL) { - if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) { - return $wrapper->getMimeType($uri, $mapping); - } - else { - // getMimeType() is not implementation specific, so we can directly - // call it without an instance. - return LocalStream::getMimeType($uri, $mapping); - } + return \Drupal::service('file.mime_type.guesser')->guess($uri); } /** diff --git a/core/includes/file.mimetypes.inc b/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php similarity index 90% rename from core/includes/file.mimetypes.inc rename to core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php index b6bd404ea9a297337ad1a9af85ab33c78a17992c..53712cae10085d35dcf4f0adc87a422040886f13 100644 --- a/core/includes/file.mimetypes.inc +++ b/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php @@ -2,39 +2,26 @@ /** * @file - * Provides mimetype mappings. + * Contains \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser */ -/** - * Return an array of MIME extension mappings. - * - * Returns the mapping after modules have altered the default mapping. - * - * @return - * Array of mimetypes correlated to the extensions that relate to them. - * - * @see file_get_mimetype() - */ -function file_mimetype_mapping() { - $mapping = &drupal_static(__FUNCTION__); - if (!isset($mapping)) { - $mapping = file_default_mimetype_mapping(); - // Allow modules to alter the default mapping. - \Drupal::moduleHandler()->alter('file_mimetype_mapping', $mapping); - } - return $mapping; -} +namespace Drupal\Core\File\MimeType; + +use Drupal\Core\Extension\ModuleHandlerInterface; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface; /** - * Default MIME extension mapping. - * - * @return - * Array of mimetypes correlated to the extensions that relate to them. - * - * @see file_get_mimetype() + * Makes possible to guess the MIME type of a file using its extension. */ -function file_default_mimetype_mapping() { - return array( +class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface { + + /** + * Default MIME extension mapping. + * + * @var array + * Array of mimetypes correlated to the extensions that relate to them. + */ + protected $defaultMapping = array( 'mimetypes' => array( 0 => 'application/andrew-inset', 1 => 'application/atom', @@ -876,4 +863,71 @@ function file_default_mimetype_mapping() { 'vtt' => 358, ), ); + + /** + * The MIME types mapping array after going through the module handler. + * + * @var array + */ + protected $mapping; + + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * Constructs a new ExtensionMimeTypeGuesser. + * + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + */ + public function __construct(ModuleHandlerInterface $module_handler) { + $this->moduleHandler = $module_handler; + } + + /** + * {@inheritdoc} + */ + public function guess($path) { + if ($this->mapping === NULL) { + $mapping = $this->defaultMapping; + // Allow modules to alter the default mapping. + $this->moduleHandler->alter('file_mimetype_mapping', $mapping); + $this->mapping = $mapping; + } + + $extension = ''; + $file_parts = explode('.', drupal_basename($path)); + + // Remove the first part: a full filename should not match an extension. + array_shift($file_parts); + + // Iterate over the file parts, trying to find a match. + // For my.awesome.image.jpeg, we try: + // - jpeg + // - image.jpeg, and + // - awesome.image.jpeg + while ($additional_part = array_pop($file_parts)) { + $extension = strtolower($additional_part . ($extension ? '.' . $extension : '')); + if (isset($this->mapping['extensions'][$extension])) { + return $this->mapping['mimetypes'][$this->mapping['extensions'][$extension]]; + } + } + + return 'application/octet-stream'; + } + + /** + * Sets the mimetypes/extension mapping to use when guessing mimetype. + * + * @param array|null $mapping + * Passing a NULL mapping will cause guess() to use self::$defaultMapping. + */ + public function setMapping(array $mapping = NULL) { + $this->mapping = $mapping; + } + } diff --git a/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php b/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php new file mode 100644 index 0000000000000000000000000000000000000000..d0d51ae447496724cc9ddd423039742fc494d5dc --- /dev/null +++ b/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php @@ -0,0 +1,92 @@ +realpath(); + } + + if ($this->sortedGuessers === NULL) { + // Sort is not trigerred yet. + $this->sortedGuessers = $this->sortGuessers(); + } + + foreach ($this->sortedGuessers as $guesser) { + $mime_type = $guesser->guess($path); + if ($mime_type !== NULL) { + return $mime_type; + } + } + } + + /** + * Appends a MIME type guesser to the guessers chain. + * + * @param \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $guesser + * The guesser to be appended. + * @param int $priority + * The priority of the guesser being added. + * + * @return self + * The called object. + */ + public function addGuesser(MimeTypeGuesserInterface $guesser, $priority = 0) { + $this->guessers[$priority][] = $guesser; + // Mark sorted guessers for rebuild. + $this->sortedGuessers = NULL; + return $this; + } + + /** + * Sorts guessers according to priority. + * + * @return \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface[] + * A sorted array of MIME type guesser objects. + */ + protected function sortGuessers() { + $sorted = array(); + krsort($this->guessers); + + foreach ($this->guessers as $guesser) { + $sorted = array_merge($sorted, $guesser); + } + return $sorted; + } + +} diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php index 86d0e63c97218e950ec76ed19976c358e20f7482..937953f2f49bc8e2eb8ac08d8eab94acfc0bd2e1 100644 --- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php +++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php @@ -93,38 +93,6 @@ protected function getTarget($uri = NULL) { return trim($target, '\/'); } - /** - * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::getMimeType(). - */ - static function getMimeType($uri, $mapping = NULL) { - if (!isset($mapping)) { - // The default file map, defined in file.mimetypes.inc is quite big. - // We only load it when necessary. - include_once DRUPAL_ROOT . '/core/includes/file.mimetypes.inc'; - $mapping = file_mimetype_mapping(); - } - - $extension = ''; - $file_parts = explode('.', drupal_basename($uri)); - - // Remove the first part: a full filename should not match an extension. - array_shift($file_parts); - - // Iterate over the file parts, trying to find a match. - // For my.awesome.image.jpeg, we try: - // - jpeg - // - image.jpeg, and - // - awesome.image.jpeg - while ($additional_part = array_pop($file_parts)) { - $extension = strtolower($additional_part . ($extension ? '.' . $extension : '')); - if (isset($mapping['extensions'][$extension])) { - return $mapping['mimetypes'][$mapping['extensions'][$extension]]; - } - } - - return 'application/octet-stream'; - } - /** * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::realpath(). */ diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php index 11ad30d9175bfb4e9b179dd135eb90b93d418828..3f9f5d412dee4a551fd35c7a4dc1f1b325ad4dbe 100644 --- a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php +++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php @@ -61,22 +61,6 @@ public function getUri(); */ public function getExternalUrl(); - /** - * Returns the MIME type of the resource. - * - * @param string $uri - * The URI, path, or filename. - * @param array $mapping - * An optional map of extensions to their mimetypes, in the form: - * - 'mimetypes': a list of mimetypes, keyed by an identifier, - * - 'extensions': the mapping itself, an associative array in which - * the key is the extension and the value is the mimetype identifier. - * - * @return string - * Returns a string containing the MIME type of the resource. - */ - public static function getMimeType($uri, $mapping = NULL); - /** * Returns canonical, absolute path of the resource. * diff --git a/core/modules/system/src/Tests/File/MimeTypeTest.php b/core/modules/system/src/Tests/File/MimeTypeTest.php index e9c388d23c24c46d4fd5a6a414b80418f20aa2f1..85d7e7ffc3da819ca98400b0d1699355d91172a7 100644 --- a/core/modules/system/src/Tests/File/MimeTypeTest.php +++ b/core/modules/system/src/Tests/File/MimeTypeTest.php @@ -50,18 +50,19 @@ public function testFileMimeTypeDetection() { 'test.ogg' => 'audio/ogg', ); + $guesser = $this->container->get('file.mime_type.guesser'); // Test using default mappings. foreach ($test_case as $input => $expected) { // Test stream [URI]. - $output = file_get_mimetype($prefix . $input); + $output = $guesser->guess($prefix . $input); $this->assertIdentical($output, $expected, format_string('Mimetype for %input is %output (expected: %expected).', array('%input' => $input, '%output' => $output, '%expected' => $expected))); // Test normal path equivalent - $output = file_get_mimetype($input); + $output = $guesser->guess($input); $this->assertIdentical($output, $expected, format_string('Mimetype (using default mappings) for %input is %output (expected: %expected).', array('%input' => $input, '%output' => $output, '%expected' => $expected))); } - // Now test passing in the map. + // Now test the extension gusser by passing in a custom mapping. $mapping = array( 'mimetypes' => array( 0 => 'application/java-archive', @@ -88,9 +89,11 @@ public function testFileMimeTypeDetection() { 'foo.doc' => 'application/octet-stream', 'test.ogg' => 'application/octet-stream', ); + $extension_guesser = $this->container->get('file.mime_type.guesser.extension'); + $extension_guesser->setMapping($mapping); foreach ($test_case as $input => $expected) { - $output = file_get_mimetype($input, $mapping); + $output = $extension_guesser->guess($input); $this->assertIdentical($output, $expected, format_string('Mimetype (using passed-in mappings) for %input is %output (expected: %expected).', array('%input' => $input, '%output' => $output, '%expected' => $expected))); } }