julienmonnerie/kirby/src/Filesystem/Mime.php

339 lines
9.3 KiB
PHP
Raw Normal View History

2022-06-17 17:51:59 +02:00
<?php
namespace Kirby\Filesystem;
2025-04-21 18:57:21 +02:00
use Kirby\Toolkit\A;
2022-06-17 17:51:59 +02:00
use Kirby\Toolkit\Str;
use SimpleXMLElement;
/**
* The `Mime` class provides method
* for MIME type detection or guessing
* from different criteria like
* extensions etc.
*
* @package Kirby Filesystem
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://opensource.org/licenses/MIT
*/
class Mime
{
2022-08-31 15:02:43 +02:00
/**
* Extension to MIME type map
*
* @var array
*/
public static $types = [
'ai' => 'application/postscript',
'aif' => 'audio/x-aiff',
'aifc' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'avi' => 'video/x-msvideo',
2025-04-21 18:57:21 +02:00
'avif' => 'image/avif',
2022-08-31 15:02:43 +02:00
'bmp' => 'image/bmp',
'css' => 'text/css',
'csv' => ['text/csv', 'text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream'],
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'dvi' => 'application/x-dvi',
'eml' => 'message/rfc822',
'eps' => 'application/postscript',
'exe' => ['application/octet-stream', 'application/x-msdownload'],
'gif' => 'image/gif',
'gtar' => 'application/x-gtar',
'gz' => 'application/x-gzip',
'htm' => 'text/html',
'html' => 'text/html',
'ico' => 'image/x-icon',
'ics' => 'text/calendar',
'js' => ['application/javascript', 'application/x-javascript'],
'json' => ['application/json', 'text/json'],
'j2k' => ['image/jp2'],
'jp2' => ['image/jp2'],
'jpg' => ['image/jpeg', 'image/pjpeg'],
'jpeg' => ['image/jpeg', 'image/pjpeg'],
'jpe' => ['image/jpeg', 'image/pjpeg'],
'log' => ['text/plain', 'text/x-log'],
'm4a' => 'audio/mp4',
'm4v' => 'video/mp4',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'mif' => 'application/vnd.mif',
'mjs' => 'text/javascript',
'mov' => 'video/quicktime',
'movie' => 'video/x-sgi-movie',
'mp2' => 'audio/mpeg',
'mp3' => ['audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'],
'mp4' => 'video/mp4',
'mpe' => 'video/mpeg',
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpga' => 'audio/mpeg',
'odc' => 'application/vnd.oasis.opendocument.chart',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'odt' => 'application/vnd.oasis.opendocument.text',
'pdf' => ['application/pdf', 'application/x-download'],
'php' => ['text/php', 'text/x-php', 'application/x-httpd-php', 'application/php', 'application/x-php', 'application/x-httpd-php-source'],
'php3' => ['text/php', 'text/x-php', 'application/x-httpd-php', 'application/php', 'application/x-php', 'application/x-httpd-php-source'],
'phps' => ['text/php', 'text/x-php', 'application/x-httpd-php', 'application/php', 'application/x-php', 'application/x-httpd-php-source'],
2024-12-20 12:37:52 +01:00
'pht' => ['text/php', 'text/x-php', 'application/x-httpd-php', 'application/php', 'application/x-php', 'application/x-httpd-php-source'],
2022-08-31 15:02:43 +02:00
'phtml' => ['text/php', 'text/x-php', 'application/x-httpd-php', 'application/php', 'application/x-php', 'application/x-httpd-php-source'],
'png' => 'image/png',
'ppt' => ['application/powerpoint', 'application/vnd.ms-powerpoint'],
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'ps' => 'application/postscript',
'psd' => 'application/x-photoshop',
'qt' => 'video/quicktime',
'rss' => 'application/rss+xml',
'rtf' => 'text/rtf',
'rtx' => 'text/richtext',
'shtml' => 'text/html',
'svg' => 'image/svg+xml',
'swf' => 'application/x-shockwave-flash',
'tar' => 'application/x-tar',
'text' => 'text/plain',
'txt' => 'text/plain',
'tgz' => ['application/x-tar', 'application/x-gzip-compressed'],
'tif' => 'image/tiff',
'tiff' => 'image/tiff',
'wav' => 'audio/x-wav',
'wbxml' => 'application/wbxml',
'webm' => 'video/webm',
'webp' => 'image/webp',
'word' => ['application/msword', 'application/octet-stream'],
'xhtml' => 'application/xhtml+xml',
'xht' => 'application/xhtml+xml',
'xml' => 'text/xml',
'xl' => 'application/excel',
'xls' => ['application/excel', 'application/vnd.ms-excel', 'application/msexcel'],
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'xsl' => 'text/xml',
'yaml' => ['application/yaml', 'text/yaml'],
'yml' => ['application/yaml', 'text/yaml'],
'zip' => ['application/x-zip', 'application/zip', 'application/x-zip-compressed'],
];
/**
* Fixes an invalid MIME type guess for the given file
*/
2025-04-21 18:57:21 +02:00
public static function fix(
string $file,
string|null $mime = null,
string|null $extension = null
): string|null {
2022-08-31 15:02:43 +02:00
// fixing map
$map = [
'text/html' => [
2025-04-21 18:57:21 +02:00
'svg' => [Mime::class, 'fromSvg'],
2022-08-31 15:02:43 +02:00
],
'text/plain' => [
'css' => 'text/css',
'json' => 'application/json',
'mjs' => 'text/javascript',
2025-04-21 18:57:21 +02:00
'svg' => [Mime::class, 'fromSvg'],
2022-08-31 15:02:43 +02:00
],
'text/x-asm' => [
'css' => 'text/css'
],
'text/x-java' => [
'mjs' => 'text/javascript',
],
'image/svg' => [
'svg' => 'image/svg+xml'
],
'application/octet-stream' => [
'mjs' => 'text/javascript'
]
];
if ($mode = ($map[$mime][$extension] ?? null)) {
if (is_callable($mode) === true) {
return $mode($file, $mime, $extension);
}
if (is_string($mode) === true) {
return $mode;
}
}
return $mime;
}
/**
* Guesses a MIME type by extension
*/
2022-12-19 14:56:05 +01:00
public static function fromExtension(string $extension): string|null
2022-08-31 15:02:43 +02:00
{
$mime = static::$types[$extension] ?? null;
return is_array($mime) === true ? array_shift($mime) : $mime;
}
/**
* Returns the MIME type of a file
*/
2025-04-21 18:57:21 +02:00
public static function fromFileInfo(string $file): string|false
2022-08-31 15:02:43 +02:00
{
if (function_exists('finfo_file') === true && file_exists($file) === true) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file);
finfo_close($finfo);
return $mime;
}
return false;
}
/**
* Returns the MIME type of a file
*/
2025-04-21 18:57:21 +02:00
public static function fromMimeContentType(string $file): string|false
2022-08-31 15:02:43 +02:00
{
2025-04-21 18:57:21 +02:00
if (
function_exists('mime_content_type') === true &&
file_exists($file) === true
) {
2022-08-31 15:02:43 +02:00
return mime_content_type($file);
}
return false;
}
/**
* Tries to detect a valid SVG and returns the MIME type accordingly
*/
2025-04-21 18:57:21 +02:00
public static function fromSvg(string $file): string|false
2022-08-31 15:02:43 +02:00
{
if (file_exists($file) === true) {
libxml_use_internal_errors(true);
$svg = new SimpleXMLElement(file_get_contents($file));
if ($svg !== false && $svg->getName() === 'svg') {
return 'image/svg+xml';
}
}
return false;
}
/**
* Tests if a given MIME type is matched by an `Accept` header
* pattern; returns true if the MIME type is contained at all
*/
public static function isAccepted(string $mime, string $pattern): bool
{
$accepted = Str::accepted($pattern);
foreach ($accepted as $m) {
if (static::matches($mime, $m['value']) === true) {
return true;
}
}
return false;
}
/**
* Tests if a MIME wildcard pattern from an `Accept` header
* matches a given type
* @since 3.3.0
*/
public static function matches(string $test, string $wildcard): bool
{
return fnmatch($wildcard, $test, FNM_PATHNAME) === true;
}
/**
* Returns the extension for a given MIME type
*/
2025-04-21 18:57:21 +02:00
public static function toExtension(string|null $mime = null): string|false
2022-08-31 15:02:43 +02:00
{
foreach (static::$types as $key => $value) {
if (is_array($value) === true && in_array($mime, $value) === true) {
return $key;
}
if ($value === $mime) {
return $key;
}
}
return false;
}
/**
* Returns all available extensions for a given MIME type
*/
2025-04-21 18:57:21 +02:00
public static function toExtensions(string|null $mime = null, bool $matchWildcards = false): array
2022-08-31 15:02:43 +02:00
{
$extensions = [];
2025-04-21 18:57:21 +02:00
$testMime = fn (string $v) => static::matches($v, $mime);
2022-08-31 15:02:43 +02:00
foreach (static::$types as $key => $value) {
2025-04-21 18:57:21 +02:00
if (is_array($value) === true) {
if ($matchWildcards === true) {
if (A::some($value, $testMime)) {
$extensions[] = $key;
}
} else {
if (in_array($mime, $value) === true) {
$extensions[] = $key;
}
}
} else {
if ($matchWildcards === true) {
if ($testMime($value) === true) {
$extensions[] = $key;
}
} else {
if ($value === $mime) {
$extensions[] = $key;
}
}
2022-08-31 15:02:43 +02:00
}
}
return $extensions;
}
/**
* Returns the MIME type of a file
*/
2025-04-21 18:57:21 +02:00
public static function type(
string $file,
string|null $extension = null
): string|null {
2022-08-31 15:02:43 +02:00
// use the standard finfo extension
$mime = static::fromFileInfo($file);
// use the mime_content_type function
if ($mime === false) {
$mime = static::fromMimeContentType($file);
}
// get the extension or extract it from the filename
$extension ??= F::extension($file);
// try to guess the mime type at least
if ($mime === false) {
$mime = static::fromExtension($extension);
}
// fix broken mime detection
return static::fix($file, $mime, $extension);
}
/**
* Returns all detectable MIME types
*/
public static function types(): array
{
return static::$types;
}
2022-06-17 17:51:59 +02:00
}