diff --git a/core/basepage.php b/core/basepage.php index d92dafa4..3bd5f550 100644 --- a/core/basepage.php +++ b/core/basepage.php @@ -23,7 +23,7 @@ class BasePage /** @var string */ public $mode = PageMode::PAGE; /** @var string */ - private $type = "text/html; charset=utf-8"; + private $mime; /** * Set what this page should do; "page", "data", or "redirect". @@ -36,13 +36,14 @@ class BasePage /** * Set the page's MIME type. */ - public function set_type(string $type): void + public function set_mime(string $mime): void { - $this->type = $type; + $this->mime = $mime; } public function __construct() { + $this->mime = MimeType::add_parameters(MimeType::HTML, MimeType::CHARSET_UTF8); if (@$_GET["flash"]) { $this->flash[] = $_GET['flash']; unset($_GET["flash"]); @@ -243,7 +244,7 @@ class BasePage { if (!headers_sent()) { header("HTTP/1.0 {$this->code} Shimmie"); - header("Content-type: " . $this->type); + header("Content-type: " . $this->mime); header("X-Powered-By: Shimmie-" . VERSION); foreach ($this->http_headers as $head) { diff --git a/core/basethemelet.php b/core/basethemelet.php index bcae8d9f..b8289ebc 100644 --- a/core/basethemelet.php +++ b/core/basethemelet.php @@ -53,8 +53,9 @@ class BaseThemelet $h_tip = html_escape($image->get_tooltip()); $h_tags = html_escape(strtolower($image->get_tag_list())); - $extArr = array_flip([EXTENSION_FLASH, EXTENSION_SVG, EXTENSION_MP3]); //List of thumbless filetypes - if (!isset($extArr[$image->ext])) { + // TODO: Set up a function for fetching what kind of files are currently thumbnailable + $mimeArr = array_flip([MimeType::MP3]); //List of thumbless filetypes + if (!isset($mimeArr[$image->get_mime()])) { $tsize = get_thumbnail_size($image->width, $image->height); } else { //Use max thumbnail size if using thumbless filetype diff --git a/core/extension.php b/core/extension.php index 18aa9703..c65418c4 100644 --- a/core/extension.php +++ b/core/extension.php @@ -291,11 +291,11 @@ abstract class DataHandlerExtension extends Extension public function onDataUpload(DataUploadEvent $event) { - $supported_ext = $this->supported_ext($event->type); + $supported_mime = $this->supported_mime($event->mime); $check_contents = $this->check_contents($event->tmpname); - if ($supported_ext && $check_contents) { + if ($supported_mime && $check_contents) { $this->move_upload_to_archive($event); - send_event(new ThumbnailGenerationEvent($event->hash, $event->type)); + send_event(new ThumbnailGenerationEvent($event->hash, $event->mime)); /* Check if we are replacing an image */ if (!is_null($event->replace_id)) { @@ -317,8 +317,8 @@ abstract class DataHandlerExtension extends Extension if (is_null($image)) { throw new UploadException("Data handler failed to create image object from data"); } - if (empty($image->ext)) { - throw new UploadException("Unable to determine extension for ". $event->tmpname); + if (empty($image->get_mime())) { + throw new UploadException("Unable to determine MIME for ". $event->tmpname); } try { send_event(new MediaCheckPropertiesEvent($image)); @@ -333,8 +333,8 @@ abstract class DataHandlerExtension extends Extension if (is_null($image)) { throw new UploadException("Data handler failed to create image object from data"); } - if (empty($image->ext)) { - throw new UploadException("Unable to determine extension for ". $event->tmpname); + if (empty($image->get_mime())) { + throw new UploadException("Unable to determine MIME for ". $event->tmpname); } try { send_event(new MediaCheckPropertiesEvent($image)); @@ -358,7 +358,7 @@ abstract class DataHandlerExtension extends Extension send_event(new LockSetEvent($image, !empty($locked))); } } - } elseif ($supported_ext && !$check_contents) { + } elseif ($supported_mime && !$check_contents) { // We DO support this extension - but the file looks corrupt throw new UploadException("Invalid or corrupted file"); } @@ -367,15 +367,15 @@ abstract class DataHandlerExtension extends Extension public function onThumbnailGeneration(ThumbnailGenerationEvent $event) { $result = false; - if ($this->supported_ext($event->type)) { + if ($this->supported_mime($event->mime)) { if ($event->force) { - $result = $this->create_thumb($event->hash, $event->type); + $result = $this->create_thumb($event->hash, $event->mime); } else { $outname = warehouse_path(Image::THUMBNAIL_DIR, $event->hash); if (file_exists($outname)) { return; } - $result = $this->create_thumb($event->hash, $event->type); + $result = $this->create_thumb($event->hash, $event->mime); } } if ($result) { @@ -386,7 +386,7 @@ abstract class DataHandlerExtension extends Extension public function onDisplayingImage(DisplayingImageEvent $event) { global $page; - if ($this->supported_ext($event->image->ext)) { + if ($this->supported_mime($event->image->get_mime())) { /** @noinspection PhpPossiblePolymorphicInvocationInspection */ $this->theme->display_image($page, $event->image); } @@ -394,7 +394,7 @@ abstract class DataHandlerExtension extends Extension public function onMediaCheckProperties(MediaCheckPropertiesEvent $event) { - if ($this->supported_ext($event->ext)) { + if ($this->supported_mime($event->mime)) { $this->media_check_properties($event); } } @@ -408,11 +408,11 @@ abstract class DataHandlerExtension extends Extension $image->filesize = $metadata['size']; $image->hash = $metadata['hash']; $image->filename = (($pos = strpos($metadata['filename'], '?')) !== false) ? substr($metadata['filename'], 0, $pos) : $metadata['filename']; - if ($config->get_bool("upload_use_mime")) { - $image->ext = get_extension_for_file($filename); - } - if (empty($image->ext)) { - $image->ext = (($pos = strpos($metadata['extension'], '?')) !== false) ? substr($metadata['extension'], 0, $pos) : $metadata['extension']; + + if (array_key_exists("extension", $metadata)) { + $image->set_mime(MimeType::get_for_file($filename, $metadata["extension"])); + } else { + $image->set_mime(MimeType::get_for_file($filename)); } $image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']); @@ -423,22 +423,35 @@ abstract class DataHandlerExtension extends Extension abstract protected function media_check_properties(MediaCheckPropertiesEvent $event): void; abstract protected function check_contents(string $tmpname): bool; - abstract protected function create_thumb(string $hash, string $type): bool; + abstract protected function create_thumb(string $hash, string $mime): bool; - protected function supported_ext(string $ext): bool + protected function supported_mime(string $mime): bool { - return in_array(get_mime_for_extension($ext), $this->SUPPORTED_MIME); + return MimeType::matches_array($mime, $this->SUPPORTED_MIME); + } + + public static function get_all_supported_mimes(): array + { + $arr = []; + foreach (getSubclassesOf("DataHandlerExtension") as $handler) { + $handler = (new $handler()); + $arr = array_merge($arr, $handler->SUPPORTED_MIME); + } + + // Not sure how to handle this otherwise, don't want to set up a whole other event for this one class + if (class_exists("TranscodeImage")) { + $arr = array_merge($arr, TranscodeImage::get_enabled_mimes()); + } + + $arr = array_unique($arr); + return $arr; } public static function get_all_supported_exts(): array { $arr = []; - foreach (getSubclassesOf("DataHandlerExtension") as $handler) { - $handler = (new $handler()); - - foreach ($handler->SUPPORTED_MIME as $mime) { - $arr = array_merge($arr, get_all_extension_for_mime($mime)); - } + foreach (self::get_all_supported_mimes() as $mime) { + $arr = array_merge($arr, FileExtension::get_all_for_mime($mime)); } $arr = array_unique($arr); return $arr; diff --git a/core/filetypes.php b/core/filetypes.php deleted file mode 100644 index 74d5050c..00000000 --- a/core/filetypes.php +++ /dev/null @@ -1,448 +0,0 @@ - [ - MIME_TYPE_MAP_NAME => "ANI Cursor", - MIME_TYPE_MAP_EXT => [EXTENSION_ANI], - MIME_TYPE_MAP_MIME => [MIME_TYPE_ANI], - ], - MIME_TYPE_AVI => [ - MIME_TYPE_MAP_NAME => "AVI", - MIME_TYPE_MAP_EXT => [EXTENSION_AVI], - MIME_TYPE_MAP_MIME => [MIME_TYPE_AVI,'video/avi','video/msvideo'], - ], - MIME_TYPE_ASF => [ - MIME_TYPE_MAP_NAME => "ASF/WMV", - MIME_TYPE_MAP_EXT => [EXTENSION_ASF,EXTENSION_WMA,EXTENSION_WMV], - MIME_TYPE_MAP_MIME => [MIME_TYPE_ASF,'audio/x-ms-wma','video/x-ms-wmv'], - ], - MIME_TYPE_BMP => [ - MIME_TYPE_MAP_NAME => "BMP", - MIME_TYPE_MAP_EXT => [EXTENSION_BMP], - MIME_TYPE_MAP_MIME => [MIME_TYPE_BMP], - ], - MIME_TYPE_BZIP => [ - MIME_TYPE_MAP_NAME => "BZIP", - MIME_TYPE_MAP_EXT => [EXTENSION_BZIP], - MIME_TYPE_MAP_MIME => [MIME_TYPE_BZIP], - ], - MIME_TYPE_BZIP2 => [ - MIME_TYPE_MAP_NAME => "BZIP2", - MIME_TYPE_MAP_EXT => [EXTENSION_BZIP2], - MIME_TYPE_MAP_MIME => [MIME_TYPE_BZIP2], - ], - MIME_TYPE_COMIC_ZIP => [ - MIME_TYPE_MAP_NAME => "CBZ", - MIME_TYPE_MAP_EXT => [EXTENSION_CBZ], - MIME_TYPE_MAP_MIME => [MIME_TYPE_COMIC_ZIP], - ], - MIME_TYPE_CSS => [ - MIME_TYPE_MAP_NAME => "Cascading Style Sheet", - MIME_TYPE_MAP_EXT => [EXTENSION_CSS], - MIME_TYPE_MAP_MIME => [MIME_TYPE_CSS], - ], - MIME_TYPE_CSV => [ - MIME_TYPE_MAP_NAME => "CSV", - MIME_TYPE_MAP_EXT => [EXTENSION_CSV], - MIME_TYPE_MAP_MIME => [MIME_TYPE_CSV], - ], - MIME_TYPE_FLASH => [ - MIME_TYPE_MAP_NAME => "Flash", - MIME_TYPE_MAP_EXT => [EXTENSION_FLASH], - MIME_TYPE_MAP_MIME => [MIME_TYPE_FLASH], - ], - MIME_TYPE_FLASH_VIDEO => [ - MIME_TYPE_MAP_NAME => "Flash Video", - MIME_TYPE_MAP_EXT => [EXTENSION_FLASH_VIDEO], - MIME_TYPE_MAP_MIME => [MIME_TYPE_FLASH_VIDEO,'video/flv'], - ], - MIME_TYPE_GIF => [ - MIME_TYPE_MAP_NAME => "GIF", - MIME_TYPE_MAP_EXT => [EXTENSION_GIF], - MIME_TYPE_MAP_MIME => [MIME_TYPE_GIF], - ], - MIME_TYPE_GZIP => [ - MIME_TYPE_MAP_NAME => "GZIP", - MIME_TYPE_MAP_EXT => [EXTENSION_GZIP], - MIME_TYPE_MAP_MIME => [MIME_TYPE_TAR], - ], - MIME_TYPE_HTML => [ - MIME_TYPE_MAP_NAME => "HTML", - MIME_TYPE_MAP_EXT => [EXTENSION_HTM, EXTENSION_HTML], - MIME_TYPE_MAP_MIME => [MIME_TYPE_HTML], - ], - MIME_TYPE_ICO => [ - MIME_TYPE_MAP_NAME => "Icon", - MIME_TYPE_MAP_EXT => [EXTENSION_ICO, EXTENSION_CUR], - MIME_TYPE_MAP_MIME => [MIME_TYPE_ICO, MIME_TYPE_WIN_BITMAP], - ], - MIME_TYPE_JPEG => [ - MIME_TYPE_MAP_NAME => "JPEG", - MIME_TYPE_MAP_EXT => [EXTENSION_JPG, EXTENSION_JPEG, EXTENSION_JFIF, EXTENSION_JFI], - MIME_TYPE_MAP_MIME => [MIME_TYPE_JPEG], - ], - MIME_TYPE_JS => [ - MIME_TYPE_MAP_NAME => "JavaScript", - MIME_TYPE_MAP_EXT => [EXTENSION_JS], - MIME_TYPE_MAP_MIME => [MIME_TYPE_JS], - ], - MIME_TYPE_JSON => [ - MIME_TYPE_MAP_NAME => "JSON", - MIME_TYPE_MAP_EXT => [EXTENSION_JSON], - MIME_TYPE_MAP_MIME => [MIME_TYPE_JSON], - ], - MIME_TYPE_MKV => [ - MIME_TYPE_MAP_NAME => "Matroska", - MIME_TYPE_MAP_EXT => [EXTENSION_MKV], - MIME_TYPE_MAP_MIME => [MIME_TYPE_MKV], - ], - MIME_TYPE_MP3 => [ - MIME_TYPE_MAP_NAME => "MP3", - MIME_TYPE_MAP_EXT => [EXTENSION_MP3], - MIME_TYPE_MAP_MIME => [MIME_TYPE_MP3], - ], - MIME_TYPE_MP4_AUDIO => [ - MIME_TYPE_MAP_NAME => "MP4 Audio", - MIME_TYPE_MAP_EXT => [EXTENSION_M4A], - MIME_TYPE_MAP_MIME => [MIME_TYPE_MP4_AUDIO,"audio/m4a"], - ], - MIME_TYPE_MP4_VIDEO => [ - MIME_TYPE_MAP_NAME => "MP4 Video", - MIME_TYPE_MAP_EXT => [EXTENSION_MP4,EXTENSION_M4V], - MIME_TYPE_MAP_MIME => [MIME_TYPE_MP4_VIDEO,'video/x-m4v'], - ], - MIME_TYPE_MPEG => [ - MIME_TYPE_MAP_NAME => "MPEG", - MIME_TYPE_MAP_EXT => [EXTENSION_MPG,EXTENSION_MPEG], - MIME_TYPE_MAP_MIME => [MIME_TYPE_MPEG], - ], - MIME_TYPE_PDF => [ - MIME_TYPE_MAP_NAME => "PDF", - MIME_TYPE_MAP_EXT => [EXTENSION_PDF], - MIME_TYPE_MAP_MIME => [MIME_TYPE_PDF], - ], - MIME_TYPE_PHP => [ - MIME_TYPE_MAP_NAME => "PHP", - MIME_TYPE_MAP_EXT => [EXTENSION_PHP,EXTENSION_PHP5], - MIME_TYPE_MAP_MIME => [MIME_TYPE_PHP], - ], - MIME_TYPE_PNG => [ - MIME_TYPE_MAP_NAME => "PNG", - MIME_TYPE_MAP_EXT => [EXTENSION_PNG], - MIME_TYPE_MAP_MIME => [MIME_TYPE_PNG], - ], - MIME_TYPE_PSD => [ - MIME_TYPE_MAP_NAME => "PSD", - MIME_TYPE_MAP_EXT => [EXTENSION_PSD], - MIME_TYPE_MAP_MIME => [MIME_TYPE_PSD], - ], - MIME_TYPE_OGG_AUDIO => [ - MIME_TYPE_MAP_NAME => "Ogg Vorbis", - MIME_TYPE_MAP_EXT => [EXTENSION_OGG_AUDIO,EXTENSION_OGG], - MIME_TYPE_MAP_MIME => [MIME_TYPE_OGG_AUDIO,MIME_TYPE_OGG], - ], - MIME_TYPE_OGG_VIDEO => [ - MIME_TYPE_MAP_NAME => "Ogg Theora", - MIME_TYPE_MAP_EXT => [EXTENSION_OGG_VIDEO], - MIME_TYPE_MAP_MIME => [MIME_TYPE_OGG_VIDEO], - ], - MIME_TYPE_QUICKTIME => [ - MIME_TYPE_MAP_NAME => "Quicktime", - MIME_TYPE_MAP_EXT => [EXTENSION_MOV], - MIME_TYPE_MAP_MIME => [MIME_TYPE_QUICKTIME], - ], - MIME_TYPE_RSS => [ - MIME_TYPE_MAP_NAME => "RSS", - MIME_TYPE_MAP_EXT => [EXTENSION_RSS], - MIME_TYPE_MAP_MIME => [MIME_TYPE_RSS], - ], - MIME_TYPE_SVG => [ - MIME_TYPE_MAP_NAME => "SVG", - MIME_TYPE_MAP_EXT => [EXTENSION_SVG], - MIME_TYPE_MAP_MIME => [MIME_TYPE_SVG], - ], - MIME_TYPE_TAR => [ - MIME_TYPE_MAP_NAME => "TAR", - MIME_TYPE_MAP_EXT => [EXTENSION_TAR], - MIME_TYPE_MAP_MIME => [MIME_TYPE_TAR], - ], - MIME_TYPE_TEXT => [ - MIME_TYPE_MAP_NAME => "Text", - MIME_TYPE_MAP_EXT => [EXTENSION_TEXT, EXTENSION_ASC], - MIME_TYPE_MAP_MIME => [MIME_TYPE_TEXT], - ], - MIME_TYPE_TIFF => [ - MIME_TYPE_MAP_NAME => "TIFF", - MIME_TYPE_MAP_EXT => [EXTENSION_TIF,EXTENSION_TIFF], - MIME_TYPE_MAP_MIME => [MIME_TYPE_TIFF], - ], - MIME_TYPE_WAV => [ - MIME_TYPE_MAP_NAME => "Wave", - MIME_TYPE_MAP_EXT => [EXTENSION_WAV], - MIME_TYPE_MAP_MIME => [MIME_TYPE_WAV], - ], - MIME_TYPE_WEBM => [ - MIME_TYPE_MAP_NAME => "WebM", - MIME_TYPE_MAP_EXT => [EXTENSION_WEBM], - MIME_TYPE_MAP_MIME => [MIME_TYPE_WEBM], - ], - MIME_TYPE_WEBP => [ - MIME_TYPE_MAP_NAME => "WebP", - MIME_TYPE_MAP_EXT => [EXTENSION_WEBP], - MIME_TYPE_MAP_MIME => [MIME_TYPE_WEBP], - ], - MIME_TYPE_XML => [ - MIME_TYPE_MAP_NAME => "XML", - MIME_TYPE_MAP_EXT => [EXTENSION_XML], - MIME_TYPE_MAP_MIME => [MIME_TYPE_XML,MIME_TYPE_XML_APPLICATION], - ], - MIME_TYPE_XSL => [ - MIME_TYPE_MAP_NAME => "XSL", - MIME_TYPE_MAP_EXT => [EXTENSION_XSL], - MIME_TYPE_MAP_MIME => [MIME_TYPE_XSL], - ], - MIME_TYPE_ZIP => [ - MIME_TYPE_MAP_NAME => "ZIP", - MIME_TYPE_MAP_EXT => [EXTENSION_ZIP], - MIME_TYPE_MAP_MIME => [MIME_TYPE_ZIP], - ], -]; - -/** - * Returns the mimetype that matches the provided extension. - */ -function get_mime_for_extension(string $ext): ?string -{ - $ext = strtolower($ext); - - foreach (MIME_TYPE_MAP as $key=>$value) { - if (in_array($ext, $value[MIME_TYPE_MAP_EXT])) { - return $key; - } - } - return null; -} - -/** - * Returns the mimetype for the specified file, trying file inspection methods before falling back on extension-based detection. - * @param String $file - * @param String $ext The files extension, for if the current filename somehow lacks the extension - * @return String The extension that was found. - */ -function get_mime(string $file, string $ext=""): string -{ - if (!file_exists($file)) { - throw new SCoreException("File not found: ".$file); - } - - $type = false; - - if (extension_loaded('fileinfo')) { - $finfo = finfo_open(FILEINFO_MIME_TYPE); - try { - $type = finfo_file($finfo, $file); - } finally { - finfo_close($finfo); - } - } elseif (function_exists('mime_content_type')) { - // If anyone is still using mime_content_type() - $type = trim(mime_content_type($file)); - } - - if ($type===false || empty($type)) { - // Checking by extension is our last resort - if ($ext==null||strlen($ext) == 0) { - $ext = pathinfo($file, PATHINFO_EXTENSION); - } - - $type = get_mime_for_extension($ext); - } - - if ($type !== false && strlen($type) > 0) { - return $type; - } - - return MIME_TYPE_OCTET_STREAM; -} - -/** - * Returns the file extension associated with the specified mimetype. - */ -function get_extension(?string $mime_type): ?string -{ - if (empty($mime_type)) { - return null; - } - - if ($mime_type==MIME_TYPE_OCTET_STREAM) { - return null; - } - - foreach (MIME_TYPE_MAP as $key=>$value) { - if (in_array($mime_type, $value[MIME_TYPE_MAP_MIME])) { - return $value[MIME_TYPE_MAP_EXT][0]; - } - } - return null; -} - -/** - * Returns all of the file extensions associated with the specified mimetype. - */ -function get_all_extension_for_mime(?string $mime_type): array -{ - $output = []; - if (empty($mime_type)) { - return $output; - } - - foreach (MIME_TYPE_MAP as $key=>$value) { - if (in_array($mime_type, $value[MIME_TYPE_MAP_MIME])) { - $output = array_merge($output, $value[MIME_TYPE_MAP_EXT]); - } - } - return $output; -} - -/** - * Gets an the extension defined in MIME_TYPE_MAP for a file. - * - * @param String $file_path - * @return String The extension that was found, or null if one can not be found. - */ -function get_extension_for_file(String $file_path): ?String -{ - $mime = get_mime($file_path); - if (!empty($mime)) { - if ($mime==MIME_TYPE_OCTET_STREAM) { - return null; - } else { - $ext = get_extension($mime); - } - if (!empty($ext)) { - return $ext; - } - } - return null; -} diff --git a/core/imageboard/event.php b/core/imageboard/event.php index 27a00f96..a6fa55b0 100644 --- a/core/imageboard/event.php +++ b/core/imageboard/event.php @@ -91,7 +91,7 @@ class ThumbnailGenerationEvent extends Event /** @var string */ public $hash; /** @var string */ - public $type; + public $mime; /** @var bool */ public $force; @@ -101,11 +101,11 @@ class ThumbnailGenerationEvent extends Event /** * Request a thumbnail be made for an image object */ - public function __construct(string $hash, string $type, bool $force=false) + public function __construct(string $hash, string $mime, bool $force=false) { parent::__construct(); $this->hash = $hash; - $this->type = $type; + $this->mime = $mime; $this->force = $force; $this->generated = false; } diff --git a/core/imageboard/image.php b/core/imageboard/image.php index 6b786472..e4fa0c5d 100644 --- a/core/imageboard/image.php +++ b/core/imageboard/image.php @@ -34,7 +34,10 @@ class Image public $filename; /** @var string */ - public $ext; + private $ext; + + /** @var string */ + private $mime; /** @var string[]|null */ public $tag_array; @@ -396,22 +399,22 @@ class Image "INSERT INTO images( owner_id, owner_ip, filename, filesize, - hash, ext, + hash, mime, ext, width, height, posted, source ) VALUES ( :owner_id, :owner_ip, :filename, :filesize, - :hash, :ext, + :hash, :mime, :ext, 0, 0, now(), :source )", [ "owner_id" => $user->id, "owner_ip" => $_SERVER['REMOTE_ADDR'], "filename" => $cut_name, "filesize" => $this->filesize, - "hash" => $this->hash, "ext" => strtolower($this->ext), - "source" => $this->source + "hash" => $this->hash, "mime" => strtolower($this->mime), + "ext" => strtolower($this->ext), "source" => $this->source ] ); $this->id = $database->get_last_insert_id('images_id_seq'); @@ -419,12 +422,13 @@ class Image $database->execute( "UPDATE images SET ". "filename = :filename, filesize = :filesize, hash = :hash, ". - "ext = :ext, width = 0, height = 0, source = :source ". + "mime = :mime, ext = :ext, width = 0, height = 0, source = :source ". "WHERE id = :id", [ "filename" => $cut_name, "filesize" => $this->filesize, "hash" => $this->hash, + "mime" => strtolower($this->mime), "ext" => strtolower($this->ext), "source" => $this->source, "id" => $this->id, @@ -503,7 +507,8 @@ class Image public function get_thumb_link(): string { global $config; - $ext = $config->get_string(ImageConfig::THUMB_TYPE); + $mime = $config->get_string(ImageConfig::THUMB_MIME); + $ext = FileExtension::get_for_mime($mime); return $this->get_link(ImageConfig::TLINK, '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext); } @@ -566,21 +571,34 @@ class Image } /** - * Get the image's mime type. - */ - public function get_mime_type(): string - { - return get_mime($this->get_image_filename(), $this->get_ext()); - } - - /** - * Get the image's filename extension + * Get the image's extension. */ public function get_ext(): string { return $this->ext; } + /** + * Get the image's mime type. + */ + public function get_mime(): string + { + if ($this->mime===MimeType::WEBP&&$this->lossless) { + return MimeType::WEBP_LOSSLESS; + } + return $this->mime; + } + + /** + * Set the image's mime type. + */ + public function set_mime($mime): void + { + $this->mime = $mime; + $this->ext = FileExtension::get_for_mime($this->get_mime()); + } + + /** * Get the image's source URL */ diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php index 7d588955..dc9b8231 100644 --- a/core/imageboard/misc.php +++ b/core/imageboard/misc.php @@ -136,7 +136,7 @@ function get_thumbnail_max_size_scaled(): array } -function create_image_thumb(string $hash, string $type, string $engine = null) +function create_image_thumb(string $hash, string $mime, string $engine = null) { global $config; @@ -147,7 +147,7 @@ function create_image_thumb(string $hash, string $type, string $engine = null) $inname, $outname, $tsize, - $type, + $mime, $engine, $config->get_string(ImageConfig::THUMB_FIT) ); @@ -155,7 +155,7 @@ function create_image_thumb(string $hash, string $type, string $engine = null) -function create_scaled_image(string $inname, string $outname, array $tsize, string $type, ?string $engine = null, ?string $resize_type = null) +function create_scaled_image(string $inname, string $outname, array $tsize, string $mime, ?string $engine = null, ?string $resize_type = null) { global $config; if (empty($engine)) { @@ -165,20 +165,17 @@ function create_scaled_image(string $inname, string $outname, array $tsize, stri $resize_type = $config->get_string(ImageConfig::THUMB_FIT); } - $output_format = $config->get_string(ImageConfig::THUMB_TYPE); - if ($output_format==EXTENSION_WEBP) { - $output_format = Media::WEBP_LOSSY; - } + $output_mime = $config->get_string(ImageConfig::THUMB_MIME); send_event(new MediaResizeEvent( $engine, $inname, - $type, + $mime, $outname, $tsize[0], $tsize[1], $resize_type, - $output_format, + $output_mime, $config->get_int(ImageConfig::THUMB_QUALITY), true, true diff --git a/core/polyfills.php b/core/polyfills.php index 7a744eb3..328ebca2 100644 --- a/core/polyfills.php +++ b/core/polyfills.php @@ -3,9 +3,6 @@ * Things which should be in the core API * \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -require_once "filetypes.php"; - - /** * Return the unique elements of an array, case insensitively */ diff --git a/ext/admin/main.php b/ext/admin/main.php index 6e41e952..91dbc5e2 100644 --- a/ext/admin/main.php +++ b/ext/admin/main.php @@ -103,7 +103,7 @@ class AdminPage extends Extension $uid = $event->args[0]; $image = Image::by_id_or_hash($uid); if ($image) { - send_event(new ThumbnailGenerationEvent($image->hash, $image->ext, true)); + send_event(new ThumbnailGenerationEvent($image->hash, $image->get_mime(), true)); } else { print("No post with ID '$uid'\n"); } diff --git a/ext/alias_editor/main.php b/ext/alias_editor/main.php index f3d4f979..73eb94f0 100644 --- a/ext/alias_editor/main.php +++ b/ext/alias_editor/main.php @@ -96,7 +96,7 @@ class AliasEditor extends Extension $this->theme->display_aliases($t->table($t->query()), $t->paginator()); } elseif ($event->get_arg(0) == "export") { $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_CSV); + $page->set_mime(MimeType::CSV); $page->set_filename("aliases.csv"); $page->set_data($this->get_alias_csv($database)); } elseif ($event->get_arg(0) == "import") { diff --git a/ext/auto_tagger/main.php b/ext/auto_tagger/main.php index dade5b69..c534ebbf 100644 --- a/ext/auto_tagger/main.php +++ b/ext/auto_tagger/main.php @@ -102,7 +102,7 @@ class AutoTagger extends Extension $this->theme->display_auto_tagtable($t->table($t->query()), $t->paginator()); } elseif ($event->get_arg(0) == "export") { $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_CSV); + $page->set_mime(MimeType::CSV); $page->set_filename("auto_tag.csv"); $page->set_data($this->get_auto_tag_csv($database)); } elseif ($event->get_arg(0) == "import") { diff --git a/ext/autocomplete/main.php b/ext/autocomplete/main.php index 4d459651..0ede9244 100644 --- a/ext/autocomplete/main.php +++ b/ext/autocomplete/main.php @@ -20,7 +20,7 @@ class AutoComplete extends Extension } $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_JSON); + $page->set_mime(MimeType::JSON); $s = strtolower($_GET["s"]); if ( diff --git a/ext/browser_search/main.php b/ext/browser_search/main.php index c3965896..4781c9a2 100644 --- a/ext/browser_search/main.php +++ b/ext/browser_search/main.php @@ -42,7 +42,7 @@ class BrowserSearch extends Extension // And now to send it to the browser $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_XML); + $page->set_mime(MimeType::XML); $page->set_data($xml); } elseif ($event->page_matches("browser_search")) { $suggestions = $config->get_string("search_suggestions_results_order"); diff --git a/ext/bulk_import_export/main.php b/ext/bulk_import_export/main.php index b9ae1b4d..f4dffdc4 100644 --- a/ext/bulk_import_export/main.php +++ b/ext/bulk_import_export/main.php @@ -5,14 +5,14 @@ class BulkImportExport extends DataHandlerExtension { const EXPORT_ACTION_NAME = "bulk_export"; const EXPORT_INFO_FILE_NAME = "export.json"; - protected $SUPPORTED_MIME = [MIME_TYPE_ZIP]; + protected $SUPPORTED_MIME = [MimeType::ZIP]; public function onDataUpload(DataUploadEvent $event) { global $user, $database; - if ($this->supported_ext($event->type) && + if ($this->supported_mime($event->mime) && $user->can(Permissions::BULK_IMPORT)) { $zip = new ZipArchive; diff --git a/ext/cron_uploader/main.php b/ext/cron_uploader/main.php index fc94ab78..f43c12dd 100644 --- a/ext/cron_uploader/main.php +++ b/ext/cron_uploader/main.php @@ -545,7 +545,7 @@ class CronUploader extends Extension global $page; $page->set_mode(PageMode::MANUAL); - $page->set_type(MIME_TYPE_TEXT); + $page->set_mime(MimeType::TEXT); $page->send_headers(); } } diff --git a/ext/danbooru_api/main.php b/ext/danbooru_api/main.php index 085a7cc1..df845532 100644 --- a/ext/danbooru_api/main.php +++ b/ext/danbooru_api/main.php @@ -10,13 +10,13 @@ class DanbooruApi extends Extension if ($event->page_matches("api/danbooru/add_post") || $event->page_matches("api/danbooru/post/create.xml")) { // No XML data is returned from this function - $page->set_type(MIME_TYPE_TEXT); + $page->set_mime(MimeType::TEXT); $this->api_add_post(); } elseif ($event->page_matches("api/danbooru/find_posts") || $event->page_matches("api/danbooru/post/index.xml")) { - $page->set_type(MIME_TYPE_XML_APPLICATION); + $page->set_mime(MimeType::XML_APPLICATION); $page->set_data($this->api_find_posts()); } elseif ($event->page_matches("api/danbooru/find_tags")) { - $page->set_type(MIME_TYPE_XML_APPLICATION); + $page->set_mime(MimeType::XML_APPLICATION); $page->set_data($this->api_find_tags()); } diff --git a/ext/download/main.php b/ext/download/main.php index 8aefec47..23aa6092 100644 --- a/ext/download/main.php +++ b/ext/download/main.php @@ -15,7 +15,7 @@ class Download extends Extension { global $page; - $page->set_type($event->mime); + $page->set_mime($event->mime); $page->set_mode(PageMode::FILE); diff --git a/ext/et/main.php b/ext/et/main.php index f95da649..9ca4fbae 100644 --- a/ext/et/main.php +++ b/ext/et/main.php @@ -76,7 +76,7 @@ class ET extends Extension "extensions" => [ "core" => $core_exts, "extra" => $extra_exts, - "handled_extensions" => DataHandlerExtension::get_all_supported_exts(), + "handled_mimes" => DataHandlerExtension::get_all_supported_mimes(), ], "stats" => [ 'images' => (int)$database->get_one("SELECT COUNT(*) FROM images"), @@ -94,7 +94,7 @@ class ET extends Extension "width" => $config->get_int(ImageConfig::THUMB_WIDTH), "height" => $config->get_int(ImageConfig::THUMB_HEIGHT), "scaling" => $config->get_int(ImageConfig::THUMB_SCALING), - "type" => $config->get_string(ImageConfig::THUMB_TYPE), + "mime" => $config->get_string(ImageConfig::THUMB_MIME), ], ]; diff --git a/ext/featured/main.php b/ext/featured/main.php index 5979ba21..18f90470 100644 --- a/ext/featured/main.php +++ b/ext/featured/main.php @@ -30,7 +30,7 @@ class Featured extends Extension $image = Image::by_id($config->get_int("featured_id")); if (!is_null($image)) { $page->set_mode(PageMode::DATA); - $page->set_type($image->get_mime_type()); + $page->set_mime($image->get_mime()); $page->set_data(file_get_contents($image->get_image_filename())); } } diff --git a/ext/handle_archive/main.php b/ext/handle_archive/main.php index c1e1effb..d6a57098 100644 --- a/ext/handle_archive/main.php +++ b/ext/handle_archive/main.php @@ -2,7 +2,7 @@ class ArchiveFileHandler extends DataHandlerExtension { - protected $SUPPORTED_MIME = [MIME_TYPE_ZIP]; + protected $SUPPORTED_MIME = [MimeType::ZIP]; public function onInitExt(InitExtEvent $event) { @@ -21,7 +21,7 @@ class ArchiveFileHandler extends DataHandlerExtension public function onDataUpload(DataUploadEvent $event) { - if ($this->supported_ext($event->type)) { + if ($this->supported_mime($event->mime)) { global $config, $page; $tmp = sys_get_temp_dir(); $tmpdir = "$tmp/shimmie-archive-{$event->hash}"; diff --git a/ext/handle_cbz/main.php b/ext/handle_cbz/main.php index c6ce429e..eaf7503d 100644 --- a/ext/handle_cbz/main.php +++ b/ext/handle_cbz/main.php @@ -2,7 +2,7 @@ class CBZFileHandler extends DataHandlerExtension { - public $SUPPORTED_MIME = [MIME_TYPE_COMIC_ZIP]; + protected $SUPPORTED_MIME = [MimeType::COMIC_ZIP]; protected function media_check_properties(MediaCheckPropertiesEvent $event): void { @@ -20,14 +20,14 @@ class CBZFileHandler extends DataHandlerExtension unlink($tmp); } - protected function create_thumb(string $hash, string $type): bool + protected function create_thumb(string $hash, string $mime): bool { $cover = $this->get_representative_image(warehouse_path(Image::IMAGE_DIR, $hash)); create_scaled_image( $cover, warehouse_path(Image::THUMBNAIL_DIR, $hash), get_thumbnail_max_size_scaled(), - get_extension(get_mime($cover)), + MimeType::get_for_file($cover), null ); return true; diff --git a/ext/handle_flash/main.php b/ext/handle_flash/main.php index 2bad8e3e..b4b0969c 100644 --- a/ext/handle_flash/main.php +++ b/ext/handle_flash/main.php @@ -2,7 +2,7 @@ class FlashFileHandler extends DataHandlerExtension { - protected $SUPPORTED_MIME = [MIME_TYPE_FLASH]; + protected $SUPPORTED_MIME = [MimeType::FLASH]; protected function media_check_properties(MediaCheckPropertiesEvent $event): void { diff --git a/ext/handle_ico/main.php b/ext/handle_ico/main.php index 50e570fd..10d91ea1 100644 --- a/ext/handle_ico/main.php +++ b/ext/handle_ico/main.php @@ -2,14 +2,14 @@ class IcoFileHandler extends DataHandlerExtension { - protected $SUPPORTED_MIME = [MIME_TYPE_ICO, MIME_TYPE_ANI]; + protected $SUPPORTED_MIME = [MimeType::ICO, MimeType::ANI, MimeType::WIN_BITMAP]; protected function media_check_properties(MediaCheckPropertiesEvent $event): void { $event->image->lossless = true; $event->image->video = false; $event->image->audio = false; - $event->image->image = ($event->ext!="ani"); + $event->image->image = ($event->mime!= MimeType::ANI); $fp = fopen($event->file_name, "r"); try { @@ -25,10 +25,10 @@ class IcoFileHandler extends DataHandlerExtension $event->image->height = $height == 0 ? 256 : $height; } - protected function create_thumb(string $hash, string $type): bool + protected function create_thumb(string $hash, string $mime): bool { try { - create_image_thumb($hash, $type, MediaEngine::IMAGICK); + create_image_thumb($hash, $mime, MediaEngine::IMAGICK); return true; } catch (MediaException $e) { log_warning("handle_ico", "Could not generate thumbnail. " . $e->getMessage()); diff --git a/ext/handle_mp3/main.php b/ext/handle_mp3/main.php index 17123bff..48663bc9 100644 --- a/ext/handle_mp3/main.php +++ b/ext/handle_mp3/main.php @@ -1,8 +1,11 @@ ext, Media::LOSSLESS_FORMATS)) { - $event->image->lossless = true; - } elseif ($event->ext==EXTENSION_WEBP) { - $event->image->lossless = Media::is_lossless_webp($event->file_name); - } - - if ($event->image->lossless==null) { - $event->image->lossless = false; - } + $event->image->lossless = Media::is_lossless($event->file_name, $event->mime); $event->image->audio = false; - switch ($event->ext) { - case EXTENSION_GIF: - $event->image->video = Media::is_animated_gif($event->file_name); + switch ($event->mime) { + case MimeType::GIF: + $event->image->video = MimeType::is_animated_gif($event->file_name); break; - case EXTENSION_WEBP: - $event->image->video = Media::is_animated_webp($event->file_name); + case MimeType::WEBP: + $event->image->video = MimeType::is_animated_webp($event->file_name); break; default: $event->image->video = false; diff --git a/ext/handle_svg/main.php b/ext/handle_svg/main.php index 7b366a4a..1c8cafb3 100644 --- a/ext/handle_svg/main.php +++ b/ext/handle_svg/main.php @@ -3,7 +3,7 @@ use enshrined\svgSanitize\Sanitizer; class SVGFileHandler extends DataHandlerExtension { - protected $SUPPORTED_MIME = [MIME_TYPE_SVG]; + protected $SUPPORTED_MIME = [MimeType::SVG]; /** @var SVGFileHandlerTheme */ protected $theme; @@ -16,7 +16,7 @@ class SVGFileHandler extends DataHandlerExtension $image = Image::by_id($id); $hash = $image->hash; - $page->set_type(MIME_TYPE_SVG); + $page->set_mime(MimeType::SVG); $page->set_mode(PageMode::DATA); $sanitizer = new Sanitizer(); @@ -67,7 +67,7 @@ class SVGFileHandler extends DataHandlerExtension protected function check_contents(string $file): bool { - if (get_mime($file)!==MIME_TYPE_SVG) { + if (MimeType::get_for_file($file)!==MimeType::SVG) { return false; } diff --git a/ext/handle_video/main.php b/ext/handle_video/main.php index 4fd5d80a..b5b53465 100644 --- a/ext/handle_video/main.php +++ b/ext/handle_video/main.php @@ -11,14 +11,14 @@ abstract class VideoFileHandlerConfig class VideoFileHandler extends DataHandlerExtension { public const SUPPORTED_MIME = [ - MIME_TYPE_ASF, - MIME_TYPE_AVI, - MIME_TYPE_FLASH_VIDEO, - MIME_TYPE_MKV, - MIME_TYPE_MP4_VIDEO, - MIME_TYPE_OGG_VIDEO, - MIME_TYPE_QUICKTIME, - MIME_TYPE_WEBM, + MimeType::ASF, + MimeType::AVI, + MimeType::FLASH_VIDEO, + MimeType::MKV, + MimeType::MP4_VIDEO, + MimeType::OGG_VIDEO, + MimeType::QUICKTIME, + MimeType::WEBM, ]; protected $SUPPORTED_MIME = self::SUPPORTED_MIME; @@ -31,15 +31,15 @@ class VideoFileHandler extends DataHandlerExtension $config->set_default_bool(VideoFileHandlerConfig::PLAYBACK_MUTE, false); $config->set_default_array( VideoFileHandlerConfig::ENABLED_FORMATS, - [MIME_TYPE_FLASH_VIDEO, MIME_TYPE_MP4_VIDEO, MIME_TYPE_OGG_VIDEO, MIME_TYPE_WEBM] + [MimeType::FLASH_VIDEO, MimeType::MP4_VIDEO, MimeType::OGG_VIDEO, MimeType::WEBM] ); } private function get_options(): array { $output = []; - foreach ($this->SUPPORTED_MIME as $format) { - $output[MIME_TYPE_MAP[$format][MIME_TYPE_MAP_NAME]] = $format; + foreach ($this->SUPPORTED_MIME as $mime) { + $output[MimeMap::get_name_for_mime($mime)] = $mime; } return $output; } @@ -108,17 +108,13 @@ class VideoFileHandler extends DataHandlerExtension } } - protected function supported_ext(string $ext): bool + protected function supported_mime(string $mime): bool { global $config; $enabled_formats = $config->get_array(VideoFileHandlerConfig::ENABLED_FORMATS); - foreach ($enabled_formats as $format) { - if (in_array($ext, MIME_TYPE_MAP[$format][MIME_TYPE_MAP_EXT])) { - return true; - } - } - return false; + + return MimeType::matches_array($mime, $enabled_formats, true); } protected function create_thumb(string $hash, string $type): bool @@ -131,13 +127,11 @@ class VideoFileHandler extends DataHandlerExtension global $config; if (file_exists($tmpname)) { - $mime = get_mime($tmpname); + $mime = MimeType::get_for_file($tmpname); $enabled_formats = $config->get_array(VideoFileHandlerConfig::ENABLED_FORMATS); - foreach ($enabled_formats as $format) { - if (in_array($mime, MIME_TYPE_MAP[$format][MIME_TYPE_MAP_MIME])) { - return true; - } + if (MimeType::matches_array($mime, $enabled_formats)) { + return true; } } return false; diff --git a/ext/handle_video/theme.php b/ext/handle_video/theme.php index 446dfce5..8dd4eb60 100644 --- a/ext/handle_video/theme.php +++ b/ext/handle_video/theme.php @@ -7,7 +7,7 @@ class VideoFileHandlerTheme extends Themelet global $config; $ilink = $image->get_image_link(); $thumb_url = make_http($image->get_thumb_link()); //used as fallback image - $ext = strtolower($image->get_ext()); + $mime = strtolower($image->get_mime()); $full_url = make_http($ilink); $autoplay = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY); $loop = $config->get_bool(VideoFileHandlerConfig::PLAYBACK_LOOP); @@ -26,11 +26,10 @@ class VideoFileHandlerTheme extends Themelet $html = "Video not playing? Click here to download the file."; //Browser media format support: https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats - $mime = get_mime_for_extension($ext); - if (in_array($mime, VideoFileHandler::SUPPORTED_MIME)) { + if (MimeType::matches_array($mime, VideoFileHandler::SUPPORTED_MIME)) { //FLV isn't supported by , but it should always fallback to the flash-based method. - if ($mime == MIME_TYPE_WEBM) { + if ($mime == MimeType::WEBM) { //Several browsers still lack WebM support sadly: https://caniuse.com/#feat=webm $html .= ""; } @@ -51,7 +50,7 @@ class VideoFileHandlerTheme extends Themelet "; - if ($mime == MIME_TYPE_FLASH_VIDEO) { + if ($mime == MimeType::FLASH_VIDEO) { //FLV doesn't support . $html .= $html_fallback; } else { diff --git a/ext/image/config.php b/ext/image/config.php index 2b078113..2f967588 100644 --- a/ext/image/config.php +++ b/ext/image/config.php @@ -2,12 +2,14 @@ abstract class ImageConfig { + const VERSION = 'ext_image_version'; + const THUMB_ENGINE = 'thumb_engine'; const THUMB_WIDTH = 'thumb_width'; const THUMB_HEIGHT = 'thumb_height'; const THUMB_SCALING = 'thumb_scaling'; const THUMB_QUALITY = 'thumb_quality'; - const THUMB_TYPE = 'thumb_type'; + const THUMB_MIME = 'thumb_mime'; const THUMB_FIT = 'thumb_fit'; const SHOW_META = 'image_show_meta'; @@ -17,6 +19,6 @@ abstract class ImageConfig const EXPIRES = 'image_expires'; const UPLOAD_COLLISION_HANDLER = 'upload_collision_handler'; - const COLLISION_MERGE = 'merge'; - const COLLISION_ERROR = 'error'; + const COLLISION_MERGE = 'merge'; + const COLLISION_ERROR = 'error'; } diff --git a/ext/image/main.php b/ext/image/main.php index 50941e58..e8a8937f 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -10,19 +10,21 @@ class ImageIO extends Extension /** @var ImageIOTheme */ protected $theme; - const COLLISION_OPTIONS = ['Error'=>ImageConfig::COLLISION_ERROR, 'Merge'=>ImageConfig::COLLISION_MERGE]; + const COLLISION_OPTIONS = [ + 'Error'=>ImageConfig::COLLISION_ERROR, + 'Merge'=>ImageConfig::COLLISION_MERGE + ]; const EXIF_READ_FUNCTION = "exif_read_data"; - const THUMBNAIL_ENGINES = [ 'Built-in GD' => MediaEngine::GD, 'ImageMagick' => MediaEngine::IMAGICK ]; const THUMBNAIL_TYPES = [ - 'JPEG' => EXTENSION_JPG, - 'WEBP (Not IE/Safari compatible)' => EXTENSION_WEBP + 'JPEG' => MimeType::JPEG, + 'WEBP (Not IE/Safari compatible)' => MimeType::WEBP ]; public function onInitExt(InitExtEvent $event) @@ -33,7 +35,7 @@ class ImageIO extends Extension $config->set_default_int(ImageConfig::THUMB_HEIGHT, 192); $config->set_default_int(ImageConfig::THUMB_SCALING, 100); $config->set_default_int(ImageConfig::THUMB_QUALITY, 75); - $config->set_default_string(ImageConfig::THUMB_TYPE, EXTENSION_JPG); + $config->set_default_string(ImageConfig::THUMB_MIME, MimeType::JPEG); $config->set_default_string(ImageConfig::THUMB_FIT, Media::RESIZE_TYPE_FIT); if (function_exists(self::EXIF_READ_FUNCTION)) { @@ -46,6 +48,25 @@ class ImageIO extends Extension $config->set_default_int(ImageConfig::EXPIRES, (60*60*24*31)); // defaults to one month } + public function onDatabaseUpgrade(DatabaseUpgradeEvent $event) + { + global $config; + + if ($this->get_version(ImageConfig::VERSION) < 1) { + switch ($config->get_string("thumb_type")) { + case FileExtension::WEBP: + $config->set_string(ImageConfig::THUMB_MIME, MimeType::WEBP); + break; + case FileExtension::JPEG: + $config->set_string(ImageConfig::THUMB_MIME, MimeType::JPEG); + break; + } + $config->set_string("thumb_type", null); + + $this->set_version(ImageConfig::VERSION, 1); + } + } + public function onPageRequest(PageRequestEvent $event) { if ($event->page_matches("image/delete")) { @@ -164,6 +185,10 @@ class ImageIO extends Extension $id = $event->id; $image = $event->image; + $image->set_mime( + MimeType::get_for_file($image->get_image_filename()) + ); + /* Check to make sure the image exists. */ $existing = Image::by_id($id); @@ -197,7 +222,7 @@ class ImageIO extends Extension $existing->remove_image_only(); // Actually delete the old image file from disk /* Generate new thumbnail */ - send_event(new ThumbnailGenerationEvent($image->hash, strtolower($image->ext))); + send_event(new ThumbnailGenerationEvent($image->hash, strtolower($image->get_mime()))); log_info("image", "Replaced Image #{$id} with ({$image->hash})"); } catch (ImageReplaceException $e) { @@ -236,7 +261,7 @@ class ImageIO extends Extension $sb = new SetupBlock("Thumbnailing"); $sb->start_table(); $sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine", true); - $sb->add_choice_option(ImageConfig::THUMB_TYPE, self::THUMBNAIL_TYPES, "Filetype", true); + $sb->add_choice_option(ImageConfig::THUMB_MIME, self::THUMBNAIL_TYPES, "Filetype", true); $sb->add_int_option(ImageConfig::THUMB_WIDTH, "Max Width", true); $sb->add_int_option(ImageConfig::THUMB_HEIGHT, "Max Height", true); @@ -277,12 +302,10 @@ class ImageIO extends Extension $image = Image::by_id($image_id); if (!is_null($image)) { if ($type == "thumb") { - $ext = $config->get_string(ImageConfig::THUMB_TYPE); - $page->set_type(get_mime_for_extension($ext)); - + $mime = $config->get_string(ImageConfig::THUMB_MIME); $file = $image->get_thumb_filename(); } else { - $page->set_type($image->get_mime_type()); + $mime = $image->get_mime(); $file = $image->get_image_filename(); } if (!file_exists($file)) { @@ -290,6 +313,8 @@ class ImageIO extends Extension die(); } + $page->set_mime($mime); + if (isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) { $if_modified_since = preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]); @@ -319,7 +344,7 @@ class ImageIO extends Extension $page->add_http_header('Expires: ' . $expires); } - send_event(new ImageDownloadingEvent($image, $file, $image->get_mime_type())); + send_event(new ImageDownloadingEvent($image, $file, $image->get_mime())); } else { $page->set_title("Not Found"); $page->set_heading("Not Found"); diff --git a/ext/index/info.php b/ext/index/info.php index 8bbab3ff..67e453a3 100644 --- a/ext/index/info.php +++ b/ext/index/info.php @@ -20,22 +20,26 @@ Shimmie extensions may provide other filters: pie somethi* -- wildcards are supported + size (=, <, >, <=, >=) width x height, eg size=1024x768 -- a specific wallpaper size size>=500x500 -- no small images size<1000x1000 -- no large images + width (=, <, >, <=, >=) width, eg width=1024 -- find images with 1024 width width>2000 -- find images bigger than 2000 width + height (=, <, >, <=, >=) height, eg height=768 -- find images with 768 height height>1000 -- find images bigger than 1000 height + ratio (=, <, >, <=, >=) width : height, eg ratio=4:3, ratio=16:9 -- standard wallpaper @@ -43,66 +47,73 @@ Shimmie extensions may provide other filters: ratio<1:1 -- tall images ratio>1:1 -- wide images + filesize (=, <, >, <=, >=) size, eg filesize>1024 -- no images under 1KB filesize<=3MB -- shorthand filesizes are supported too + id (=, <, >, <=, >=) number, eg id<20 -- search only the first few images id>=500 -- search later images + user=Username & poster=Username, eg user=Shish -- find all of Shish's posts poster=Shish -- same as above + user_id=userID & poster_id=userID, eg user_id=2 -- find all posts by user id 2 poster_id=2 -- same as above + hash=md5sum & md5=md5sum, eg hash=bf5b59173f16b6937a4021713dbfaa72 -- find the \"Taiga want up!\" image md5=bf5b59173f16b6937a4021713dbfaa72 -- same as above - filetype=type & ext=type, eg - - filetype=png -- find all PNG images - ext=png -- same as above - + filename=blah & name=blah, eg filename=kitten -- find all images with \"kitten\" in the original filename name=kitten -- same as above + posted (=, <, >, <=, >=) date, eg posted>=2009-12-25 posted<=2010-01-01 -- find images posted between christmas and new year + tags (=, <, >, <=, >=) count, eg tags=1 -- search for images with only 1 tag tags>=10 -- search for images with 10 or more tags tags<25 -- search for images with less than 25 tags + source=(URL, any, none) eg source=http://example.com -- find all images with \"http://example.com\" in the source source=any -- find all images with a source source=none -- find all images without a source + order=(id, width, height, filesize, filename)_(ASC, DESC), eg order=width -- find all images sorted from highest > lowest width order=filesize_asc -- find all images sorted from lowest > highest filesize + order=random_####, eg order=random_8547 -- find all images sorted randomly using 8547 as a seed + Search items can be combined to search for images which match both, or you can stick \"-\" in front of an item to search for things that don't diff --git a/ext/index/main.php b/ext/index/main.php index 5c8b2ab8..e282f1db 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -208,9 +208,6 @@ class Index extends Extension } elseif (preg_match("/^(phash)[=|:]([0-9a-fA-F]*)$/i", $event->term, $matches)) { $phash = strtolower($matches[2]); $event->add_querylet(new Querylet('images.phash = :phash', ["phash" => $phash])); - } elseif (preg_match("/^(filetype|ext)[=|:]([a-zA-Z0-9]*)$/i", $event->term, $matches)) { - $ext = strtolower($matches[2]); - $event->add_querylet(new Querylet('images.ext = :ext', ["ext" => $ext])); } elseif (preg_match("/^(filename|name)[=|:]([a-zA-Z0-9]*)$/i", $event->term, $matches)) { $filename = strtolower($matches[2]); $event->add_querylet(new Querylet("images.filename LIKE :filename{$this->stpen}", ["filename{$this->stpen}"=>"%$filename%"])); diff --git a/ext/index/theme.php b/ext/index/theme.php index 4a65f4e6..1b5c0d11 100644 --- a/ext/index/theme.php +++ b/ext/index/theme.php @@ -187,44 +187,44 @@ and of course start organising your images :-) tagname Returns images that are tagged with "tagname". - + tagname othertagname - Returns images that are tagged with "tagname" and "othertagname". + Returns images that are tagged with "tagname" and "othertagname". Most tags and keywords can be prefaced with a negative sign (-) to indicate that you want to search for images that do not match something. -tagname - Returns images that are not tagged with "tagname". + Returns images that are not tagged with "tagname". -tagname -othertagname - Returns images that are not tagged with "tagname" and "othertagname". This is different than without the negative sign, as images with "tagname" or "othertagname" can still be returned as long as the other one is not present. + Returns images that are not tagged with "tagname" and "othertagname". This is different than without the negative sign, as images with "tagname" or "othertagname" can still be returned as long as the other one is not present. tagname -othertagname - Returns images that are tagged with "tagname", but are not tagged with "othertagname". + Returns images that are tagged with "tagname", but are not tagged with "othertagname". Wildcard searches are possible as well using * for "any one, more, or none" and ? for "any one". tagn* - Returns images that are tagged with "tagname", "tagnot", or anything else that starts with "tagn". + Returns images that are tagged with "tagname", "tagnot", or anything else that starts with "tagn". tagn?me - Returns images that are tagged with "tagname", "tagnome", or anything else that starts with "tagn", has one character, and ends with "me". + Returns images that are tagged with "tagname", "tagnome", or anything else that starts with "tagn", has one character, and ends with "me". tags=1 - Returns images with exactly 1 tag. + Returns images with exactly 1 tag. @@ -235,12 +235,12 @@ and of course start organising your images :-) Can use <, <=, >, >=, or =. - + Search for images by aspect ratio - + ratio=4:3 - Returns images with an aspect ratio of 4:3. + Returns images with an aspect ratio of 4:3. @@ -249,14 +249,14 @@ and of course start organising your images :-) Can use <, <=, >, >=, or =. The relation is calculated by dividing width by height. - + Search for images by file size - + filesize=1 - Returns images exactly 1 byte in size. + Returns images exactly 1 byte in size. @@ -265,71 +265,62 @@ and of course start organising your images :-) Can use <, <=, >, >=, or =. Supported suffixes are kb, mb, and gb. Uses multiples of 1024. - + Search for images by MD5 hash - + hash=0D3512CAA964B2BA5D7851AF5951F33B - Returns image with an MD5 hash 0D3512CAA964B2BA5D7851AF5951F33B. - - - - - Search for images by file type - - - filetype=jpg - Returns images that are of type "jpg". + Returns image with an MD5 hash 0D3512CAA964B2BA5D7851AF5951F33B. Search for images by file name - + filename=picasso.jpg - Returns images that are named "picasso.jpg". + Returns images that are named "picasso.jpg". Search for images by source - + source=http://google.com/ - Returns images with a source of "http://google.com/". + Returns images with a source of "http://google.com/". source=any - Returns images with a source set. + Returns images with a source set. source=none - Returns images without a source set. + Returns images without a source set. - + Search for images by date posted. posted>=07-19-2019 - Returns images posted on or after 07-19-2019. + Returns images posted on or after 07-19-2019. Can use <, <=, >, >=, or =. Date format is mm-dd-yyyy. Date posted includes time component, so = will not work unless the time is exact. - + Search for images by image dimensions size=640x480 - Returns images exactly 640 pixels wide by 480 pixels high. + Returns images exactly 640 pixels wide by 480 pixels high. @@ -348,21 +339,21 @@ and of course start organising your images :-) Can use <, <=, >, >=, or =. - + - + Sorting search results can be done using the pattern order:field_direction. _direction can be either _asc or _desc, indicating ascending (123) or descending (321) order. - + order:id_asc - Returns images sorted by ID, smallest first. + Returns images sorted by ID, smallest first. order:width_desc Returns images sorted by width, largest first. - + These fields are supported: id diff --git a/ext/media/events.php b/ext/media/events.php index ffa69a49..b76c9245 100644 --- a/ext/media/events.php +++ b/ext/media/events.php @@ -4,9 +4,9 @@ class MediaResizeEvent extends Event { public $engine; public $input_path; - public $input_type; + public $input_mime; public $output_path; - public $target_format; + public $target_mime; public $target_width; public $target_height; public $target_quality; @@ -17,12 +17,12 @@ class MediaResizeEvent extends Event public function __construct( String $engine, string $input_path, - string $input_type, + string $input_mime, string $output_path, int $target_width, int $target_height, string $resize_type = Media::RESIZE_TYPE_FIT, - string $target_format = null, + string $target_mime = null, int $target_quality = 80, bool $minimize = false, bool $allow_upscale = true @@ -31,11 +31,11 @@ class MediaResizeEvent extends Event assert(in_array($engine, MediaEngine::ALL)); $this->engine = $engine; $this->input_path = $input_path; - $this->input_type = $input_type; + $this->input_mime = $input_mime; $this->output_path = $output_path; $this->target_height = $target_height; $this->target_width = $target_width; - $this->target_format = $target_format; + $this->target_mime = $target_mime; $this->target_quality = $target_quality; $this->minimize = $minimize; $this->allow_upscale = $allow_upscale; @@ -47,13 +47,13 @@ class MediaCheckPropertiesEvent extends Event { public $image; public $file_name; - public $ext; + public $mime; public function __construct(Image $image) { parent::__construct(); $this->image = $image; $this->file_name = warehouse_path(Image::IMAGE_DIR, $image->hash); - $this->ext = strtolower($image->ext); + $this->mime = strtolower($image->get_mime()); } } diff --git a/ext/media/info.php b/ext/media/info.php index b230f331..a0f20c2b 100644 --- a/ext/media/info.php +++ b/ext/media/info.php @@ -11,4 +11,5 @@ class MediaInfo extends ExtensionInfo public $license = self::LICENSE_WTFPL; public $description = "Provides common functions and settings used for media operations."; public $core = true; + public $visibility = self::VISIBLE_HIDDEN; } diff --git a/ext/media/main.php b/ext/media/main.php index ae68d477..916ec261 100644 --- a/ext/media/main.php +++ b/ext/media/main.php @@ -16,36 +16,25 @@ class Media extends Extension /** @var MediaTheme */ protected $theme; - const WEBP_LOSSY = "webp-lossy"; - const WEBP_LOSSLESS = "webp-lossless"; - const IMAGE_MEDIA_ENGINES = [ "GD" => MediaEngine::GD, "ImageMagick" => MediaEngine::IMAGICK, ]; const LOSSLESS_FORMATS = [ - self::WEBP_LOSSLESS, - EXTENSION_PNG, - EXTENSION_PSD, - EXTENSION_BMP, - EXTENSION_ICO, - EXTENSION_CUR, - EXTENSION_ANI, - EXTENSION_GIF - + MimeType::WEBP_LOSSLESS, + MimeType::PNG, + MimeType::PSD, + MimeType::BMP, + MimeType::ICO, + MimeType::ANI, + MimeType::GIF ]; const ALPHA_FORMATS = [ - self::WEBP_LOSSLESS, - self::WEBP_LOSSY, - EXTENSION_WEBP, - EXTENSION_PNG, - ]; - - const FORMAT_ALIASES = [ - EXTENSION_TIF => EXTENSION_TIFF, - EXTENSION_JPEG => EXTENSION_JPG, + MimeType::WEBP_LOSSLESS, + MimeType::WEBP, + MimeType::PNG, ]; const RESIZE_TYPE_FIT = "Fit"; @@ -53,16 +42,6 @@ class Media extends Extension const RESIZE_TYPE_FILL = "Fill"; const RESIZE_TYPE_STRETCH = "Stretch"; - //RIFF####WEBPVP8?..............ANIM - private const WEBP_ANIMATION_HEADER = - [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0x41, 0x4E, 0x49, 0x4D]; - - //RIFF####WEBPVP8L - private const WEBP_LOSSLESS_HEADER = - [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, 0x4C]; - - public static function imagick_available(): bool { return extension_loaded("imagick"); @@ -128,22 +107,6 @@ class Media extends Extension $event->panel->add_block($sb); } - public function onAdminBuilding(AdminBuildingEvent $event) - { - global $database; - $types = $database->get_all("SELECT ext, count(*) count FROM images group by ext"); - - $this->theme->display_form($types); - } - - public function onAdminAction(AdminActionEvent $event) - { - $action = $event->action; - if (method_exists($this, $action)) { - $event->redirect = $this->$action(); - } - } - public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) { global $user; @@ -210,7 +173,10 @@ class Media extends Extension */ public function onMediaResize(MediaResizeEvent $event) { - if (!in_array($event->resize_type, MediaEngine::RESIZE_TYPE_SUPPORT[MediaEngine::IMAGICK])) { + if (!in_array( + $event->resize_type, + MediaEngine::RESIZE_TYPE_SUPPORT[$event->engine] + )) { throw new MediaException("Resize type $event->resize_type not supported by selected media engine $event->engine"); } @@ -227,7 +193,7 @@ class Media extends Extension $event->target_width, $event->target_height, $event->output_path, - $event->target_format, + $event->target_mime, $event->resize_type, $event->target_quality, $event->allow_upscale @@ -239,11 +205,11 @@ class Media extends Extension // } else { self::image_resize_convert( $event->input_path, - $event->input_type, + $event->input_mime, $event->target_width, $event->target_height, $event->output_path, - $event->target_format, + $event->target_mime, $event->resize_type, $event->target_quality, $event->minimize, @@ -315,8 +281,6 @@ class Media extends Extension $s = ((int)($event->image->length / 100))/10; $event->replace('$size', "${s}s"); } - - $event->replace('$ext', $event->image->ext); } /** @@ -372,7 +336,7 @@ class Media extends Extension $codec = "mjpeg"; $quality = $config->get_int(ImageConfig::THUMB_QUALITY); - if ($config->get_string(ImageConfig::THUMB_TYPE) == EXTENSION_WEBP) { + if ($config->get_string(ImageConfig::THUMB_MIME) == MimeType::WEBP) { $codec = "libwebp"; } else { // mjpeg quality ranges from 2-31, with 2 being the best quality. @@ -399,7 +363,7 @@ class Media extends Extension if ((int)$ret == (int)0) { log_debug('media', "Generating thumbnail with command `$cmd`, returns $ret"); - create_scaled_image($tmpname, $outname, $scaled_size, "png"); + create_scaled_image($tmpname, $outname, $scaled_size, MimeType::PNG); return true; @@ -442,22 +406,19 @@ class Media extends Extension } } - public static function determine_ext(string $format): string + public static function determine_ext(string $mime): string { - $format = self::normalize_format($format); - switch ($format) { - case self::WEBP_LOSSLESS: - case self::WEBP_LOSSY: - return EXTENSION_WEBP; - default: - return $format; + $ext = FileExtension::get_for_mime($mime); + if (empty($ext)) { + throw new SCoreException("Could not determine extension for $mime"); } + return $ext; } // private static function image_save_imagick(Imagick $image, string $path, string $format, int $output_quality = 80, bool $minimize) // { // switch ($format) { -// case EXTENSION_PNG: +// case FileExtension::PNG: // $result = $image->setOption('png:compression-level', 9); // if ($result !== true) { // throw new GraphicsException("Could not set png compression option"); @@ -561,14 +522,14 @@ class Media extends Extension // } // } - public static function is_lossless(string $filename, string $format) + public static function is_lossless(string $filename, string $mime) { - if (in_array($format, self::LOSSLESS_FORMATS)) { + if (in_array($mime, self::LOSSLESS_FORMATS)) { return true; } - switch ($format) { - case EXTENSION_WEBP: - return self::is_lossless_webp($filename); + switch ($mime) { + case MimeType::WEBP: + return MimeType::is_lossless_webp($filename); break; } return false; @@ -576,11 +537,11 @@ class Media extends Extension public static function image_resize_convert( string $input_path, - string $input_type, + string $input_mime, int $new_width, int $new_height, string $output_filename, - string $output_type = null, + string $output_mime = null, string $resize_type = self::RESIZE_TYPE_FIT, int $output_quality = 80, bool $minimize = false, @@ -594,21 +555,18 @@ class Media extends Extension throw new MediaException("convert command not configured"); } - if (empty($output_type)) { - $output_type = $input_type; + if (empty($output_mime)) { + $output_mime = $input_mime; } - if ($output_type==EXTENSION_WEBP && self::is_lossless($input_path, $input_type)) { - $output_type = self::WEBP_LOSSLESS; + if ($output_mime==MimeType::WEBP && self::is_lossless($input_path, $input_mime)) { + $output_mime = MimeType::WEBP_LOSSLESS; } $bg = "black"; - if (self::supports_alpha($output_type)) { + if (self::supports_alpha($output_mime)) { $bg = "none"; } - if (!empty($input_type)) { - $input_type = $input_type . ":"; - } $resize_suffix = ""; if (!$allow_upscale) { @@ -625,7 +583,9 @@ class Media extends Extension $resize_arg = "-thumbnail"; } - $file_arg = "${input_type}\"${input_path}[0]\""; + $input_ext = self::determine_ext($input_mime); + + $file_arg = "${input_ext}:\"${input_path}[0]\""; switch ($resize_type) { case Media::RESIZE_TYPE_FIT: @@ -633,24 +593,24 @@ class Media extends Extension $args .= "${resize_arg} ${new_width}x${new_height}${resize_suffix} ${file_arg}"; break; case Media::RESIZE_TYPE_FILL: - $args .= "${resize_arg} ${new_width}x${new_height}\^ -gravity center -extent ${new_width}x${new_height} ${file_arg}"; + $args .= "${resize_arg} ${new_width}x${new_height}\^ -background none -gravity center -extent ${new_width}x${new_height} ${file_arg}"; break; case Media::RESIZE_TYPE_FIT_BLUR: $blur_size = max(ceil(max($new_width, $new_height) / 25), 5); $args .= "${file_arg} ". - "\( -clone 0 -resize ${new_width}x${new_height}\^ -gravity center -fill black -colorize 50% -extent ${new_width}x${new_height} -blur 0x${blur_size} \) ". + "\( -clone 0 -resize ${new_width}x${new_height}\^ -background none -gravity center -fill black -colorize 50% -extent ${new_width}x${new_height} -blur 0x${blur_size} \) ". "\( -clone 0 -resize ${new_width}x${new_height} \) ". "-delete 0 -gravity center -compose over -composite"; break; } - switch ($output_type) { - case Media::WEBP_LOSSLESS: - $args .= '-define webp:lossless=true'; + switch ($output_mime) { + case MimeType::WEBP_LOSSLESS: + $args .= ' -define webp:lossless=true'; break; - case EXTENSION_PNG: - $args .= '-define png:compression-level=9'; + case MimeType::PNG: + $args .= ' -define png:compression-level=9'; break; } @@ -658,7 +618,7 @@ class Media extends Extension $args .= " -quality ${output_quality} -background ${bg}"; - $output_ext = self::determine_ext($output_type); + $output_ext = self::determine_ext($output_mime); $format = '"%s" %s %s:"%s" 2>&1'; $cmd = sprintf($format, $convert, $args, $output_ext, $output_filename); @@ -679,7 +639,7 @@ class Media extends Extension * @param int $new_width * @param int $new_height * @param string $output_filename - * @param string|null $output_type If set to null, the output file type will be automatically determined via the $info parameter. Otherwise an exception will be thrown. + * @param string|null $output_mime If set to null, the output file type will be automatically determined via the $info parameter. Otherwise an exception will be thrown. * @param int $output_quality Defaults to 80. * @throws MediaException * @throws InsufficientMemoryException if the estimated memory usage exceeds the memory limit. @@ -690,7 +650,7 @@ class Media extends Extension int $new_width, int $new_height, string $output_filename, - string $output_type = null, + string $output_mime = null, string $resize_type = self::RESIZE_TYPE_FIT, int $output_quality = 80, bool $allow_upscale = true @@ -698,26 +658,26 @@ class Media extends Extension $width = $info[0]; $height = $info[1]; - if ($output_type == null) { + if ($output_mime == null) { /* If not specified, output to the same format as the original image */ switch ($info[2]) { case IMAGETYPE_GIF: - $output_type = EXTENSION_GIF; + $output_mime = MimeType::GIF; break; case IMAGETYPE_JPEG: - $output_type = EXTENSION_JPEG; + $output_mime = MimeType::JPEG; break; case IMAGETYPE_PNG: - $output_type = EXTENSION_PNG; + $output_mime = MimeType::PNG; break; case IMAGETYPE_WEBP: - $output_type = EXTENSION_WEBP; + $output_mime = MimeType::WEBP; break; case IMAGETYPE_BMP: - $output_type = EXTENSION_BMP; + $output_mime = MimeType::BMP; break; default: - throw new MediaException("Failed to save the new image - Unsupported image type."); + throw new MediaException("Failed to save the new image - Unsupported MIME type."); } } @@ -809,29 +769,27 @@ class Media extends Extension throw new MediaException("Unable to copy resized image data to new image"); } - switch ($output_type) { - case EXTENSION_BMP: + switch ($output_mime) { + case MimeType::BMP: $result = imagebmp($image_resized, $output_filename, true); break; - case EXTENSION_WEBP: - case Media::WEBP_LOSSY: + case MimeType::WEBP: $result = imagewebp($image_resized, $output_filename, $output_quality); break; - case EXTENSION_JPG: - case EXTENSION_JPEG: + case MimeType::JPEG: $result = imagejpeg($image_resized, $output_filename, $output_quality); break; - case EXTENSION_PNG: + case MimeType::PNG: $result = imagepng($image_resized, $output_filename, 9); break; - case EXTENSION_GIF: + case MimeType::GIF: $result = imagegif($image_resized, $output_filename); break; default: - throw new MediaException("Failed to save the new image - Unsupported image type: $output_type"); + throw new MediaException("Failed to save the new image - Unsupported image type: $output_mime"); } if ($result === false) { - throw new MediaException("Failed to save the new image, function returned false when saving type: $output_type"); + throw new MediaException("Failed to save the new image, function returned false when saving type: $output_mime"); } } finally { @imagedestroy($image); @@ -839,117 +797,10 @@ class Media extends Extension } } - /** - * Determines if a file is an animated gif. - * - * @param String $image_filename The path of the file to check. - * @return bool true if the file is an animated gif, false if it is not. - */ - public static function is_animated_gif(string $image_filename): bool + + public static function supports_alpha(string $mime): bool { - $is_anim_gif = 0; - if (($fh = @fopen($image_filename, 'rb'))) { - try { - //check if gif is animated (via https://www.php.net/manual/en/function.imagecreatefromgif.php#104473) - while (!feof($fh) && $is_anim_gif < 2) { - $chunk = fread($fh, 1024 * 100); - $is_anim_gif += preg_match_all('#\x00\x21\xF9\x04.{4}\x00[\x2C\x21]#s', $chunk, $matches); - } - } finally { - @fclose($fh); - } - } - return ($is_anim_gif == 0); - } - - - private static function compare_file_bytes(string $file_name, array $comparison): bool - { - $size = filesize($file_name); - if ($size < count($comparison)) { - // Can't match because it's too small - return false; - } - - if (($fh = @fopen($file_name, 'rb'))) { - try { - $chunk = unpack("C*", fread($fh, count($comparison))); - - for ($i = 0; $i < count($comparison); $i++) { - $byte = $comparison[$i]; - if ($byte == null) { - continue; - } else { - $fileByte = $chunk[$i + 1]; - if ($fileByte != $byte) { - return false; - } - } - } - return true; - } finally { - @fclose($fh); - } - } else { - throw new MediaException("Unable to open file for byte check: $file_name"); - } - } - - public static function is_animated_webp(string $image_filename): bool - { - return self::compare_file_bytes($image_filename, self::WEBP_ANIMATION_HEADER); - } - - public static function is_lossless_webp(string $image_filename): bool - { - return self::compare_file_bytes($image_filename, self::WEBP_LOSSLESS_HEADER); - } - - public static function supports_alpha(string $format): bool - { - return in_array(self::normalize_format($format), self::ALPHA_FORMATS); - } - - public static function is_input_supported(string $engine, string $format, ?bool $lossless = null): bool - { - $format = self::normalize_format($format, $lossless); - if (!in_array($format, MediaEngine::INPUT_SUPPORT[$engine])) { - return false; - } - return true; - } - - public static function is_output_supported(string $engine, string $format, ?bool $lossless = false): bool - { - $format = self::normalize_format($format, $lossless); - if (!in_array($format, MediaEngine::OUTPUT_SUPPORT[$engine])) { - return false; - } - return true; - } - - /** - * Checks if a format (normally a file extension) is a variant name of another format (ie, jpg and jpeg). - * If one is found, then the maine name that the Media extension will recognize is returned, - * otherwise the incoming format is returned. - * - * @param $format - * @return string|null The format name that the media extension will recognize. - */ - public static function normalize_format(string $format, ?bool $lossless = null): ?string - { - if ($format == EXTENSION_WEBP) { - if ($lossless === true) { - $format = Media::WEBP_LOSSLESS; - } else { - $format = Media::WEBP_LOSSY; - } - } - - if (array_key_exists($format, Media::FORMAT_ALIASES)) { - return self::FORMAT_ALIASES[$format]; - } - return $format; + return MimeType::matches_array($mime, self::ALPHA_FORMATS, true); } diff --git a/ext/media/media_engine.php b/ext/media/media_engine.php index e07073df..94636bb8 100644 --- a/ext/media/media_engine.php +++ b/ext/media/media_engine.php @@ -13,65 +13,61 @@ abstract class MediaEngine MediaEngine::IMAGICK, MediaEngine::STATIC, ]; - public const OUTPUT_SUPPORT = [ + private const OUTPUT_SUPPORT = [ MediaEngine::GD => [ - EXTENSION_GIF, - EXTENSION_JPG, - EXTENSION_PNG, - EXTENSION_WEBP, - Media::WEBP_LOSSY, + MimeType::GIF, + MimeType::JPEG, + MimeType::PNG, + MimeType::WEBP ], MediaEngine::IMAGICK => [ - EXTENSION_GIF, - EXTENSION_JPG, - EXTENSION_PNG, - EXTENSION_WEBP, - Media::WEBP_LOSSY, - Media::WEBP_LOSSLESS, + MimeType::GIF, + MimeType::JPEG, + MimeType::PNG, + MimeType::WEBP, + MimeType::WEBP_LOSSLESS, ], MediaEngine::FFMPEG => [ - EXTENSION_JPG, - EXTENSION_WEBP, - EXTENSION_PNG, + MimeType::JPEG, + MimeType::WEBP, + MimeType::PNG, ], MediaEngine::STATIC => [ - EXTENSION_JPG, + MimeType::JPEG, ], ]; - public const INPUT_SUPPORT = [ + private const INPUT_SUPPORT = [ MediaEngine::GD => [ - EXTENSION_BMP, - EXTENSION_GIF, - EXTENSION_JPG, - EXTENSION_PNG, - EXTENSION_WEBP, - Media::WEBP_LOSSY, - Media::WEBP_LOSSLESS, + MimeType::BMP, + MimeType::GIF, + MimeType::JPEG, + MimeType::PNG, + MimeType::WEBP, + MimeType::WEBP_LOSSLESS, ], MediaEngine::IMAGICK => [ - EXTENSION_BMP, - EXTENSION_GIF, - EXTENSION_JPG, - EXTENSION_PNG, - EXTENSION_PSD, - EXTENSION_TIFF, - EXTENSION_WEBP, - Media::WEBP_LOSSY, - Media::WEBP_LOSSLESS, - EXTENSION_ICO, + MimeType::BMP, + MimeType::GIF, + MimeType::JPEG, + MimeType::PNG, + MimeType::PSD, + MimeType::TIFF, + MimeType::WEBP, + MimeType::WEBP_LOSSLESS, + MimeType::ICO, ], MediaEngine::FFMPEG => [ - EXTENSION_AVI, - EXTENSION_MKV, - EXTENSION_WEBM, - EXTENSION_MP4, - EXTENSION_MOV, - EXTENSION_FLASH_VIDEO, + MimeType::AVI, + MimeType::MKV, + MimeType::WEBM, + MimeType::MP4_VIDEO, + MimeType::QUICKTIME, + MimeType::FLASH_VIDEO, ], MediaEngine::STATIC => [ - EXTENSION_JPG, - EXTENSION_GIF, - EXTENSION_PNG, + MimeType::JPEG, + MimeType::GIF, + MimeType::PNG, ], ]; public const RESIZE_TYPE_SUPPORT = [ @@ -92,4 +88,21 @@ abstract class MediaEngine Media::RESIZE_TYPE_FIT ] ]; + + public static function is_output_supported(string $engine, string $mime): bool + { + return MimeType::matches_array( + $mime, + MediaEngine::OUTPUT_SUPPORT[$engine], + true + ); + } + + public static function is_input_supported(string $engine, string $mime): bool + { + return MimeType::matches_array( + $mime, + MediaEngine::INPUT_SUPPORT[$engine] + ); + } } diff --git a/ext/media/theme.php b/ext/media/theme.php index 8b504bca..f950361c 100644 --- a/ext/media/theme.php +++ b/ext/media/theme.php @@ -9,28 +9,6 @@ use function MicroHTML\OPTION; class MediaTheme extends Themelet { - public function display_form(array $types) - { - global $page; - - $select = SELECT(["name"=>"media_rescan_type"]); - $select->appendChild(OPTION(["value"=>""], "All")); - foreach ($types as $type) { - $select->appendChild(OPTION(["value"=>$type["ext"]], "{$type["ext"]} ({$type["count"]})")); - } - - $html = (string)SHM_SIMPLE_FORM( - "admin/media_rescan", - "Use this to force scanning for media properties.", - TABLE( - ["class"=>"form"], - TR(TH("Image Type"), TD($select)), - TR(TD(["colspan"=>"2"], SHM_SUBMIT('Scan Media Information'))) - ) - ); - $page->add_block(new Block("Media Tools", $html)); - } - public function get_buttons_html(int $image_id): string { return (string)SHM_SIMPLE_FORM( diff --git a/ext/mime/file_extension.php b/ext/mime/file_extension.php new file mode 100644 index 00000000..0f093fab --- /dev/null +++ b/ext/mime/file_extension.php @@ -0,0 +1,105 @@ +"matthew@darkholme.net"]; + public $license = self::LICENSE_WTFPL; + public $description = "Provides system mime-related functionality"; + public $core = true; + public $visibility = self::VISIBLE_HIDDEN; +} diff --git a/ext/mime/main.php b/ext/mime/main.php new file mode 100644 index 00000000..c9291ee4 --- /dev/null +++ b/ext/mime/main.php @@ -0,0 +1,85 @@ +replace('$ext', $event->image->get_ext()); + $event->replace('$mime', $event->image->get_mime()); + } + + + public function onDatabaseUpgrade(DatabaseUpgradeEvent $event) + { + global $database; + + // These upgrades are primarily for initializing mime types on upgrade, and for adjusting mime types whenever an + // adjustment needs to be made to the mime types. + + if ($this->get_version(self::VERSION) < 1) { + if ($database->transaction) { + // Each of these commands could hit a lot of data, combining + // them into one big transaction would not be a good idea. + $database->commit(); + } + $database->set_timeout(300000); // These updates can take a little bit + + $extensions = $database->get_col_iterable("SELECT DISTINCT ext FROM images"); + + foreach ($extensions as $ext) { + $mime = MimeType::get_for_extension($ext); + + if (empty($mime) || $mime===MimeType::OCTET_STREAM) { + throw new SCoreException("Unknown extension: $ext"); + } + + $normalized_extension = FileExtension::get_for_mime($mime); + + $database->execute( + $database->scoreql_to_sql( + "UPDATE images SET mime = :mime, ext = :new_ext WHERE ext = :ext" + ), + ["mime" => $mime, "new_ext" => $normalized_extension, "ext" => $ext] + ); + } + + $this->set_version(self::VERSION, 1); + } + } + + public function onHelpPageBuilding(HelpPageBuildingEvent $event) + { + if ($event->key===HelpPages::SEARCH) { + $block = new Block(); + $block->header = "File Types"; + $block->body = $this->theme->get_help_html(); + $event->add_block($block); + } + } + + public function onSearchTermParse(SearchTermParseEvent $event) + { + if (is_null($event->term)) { + return; + } + + $matches = []; + // check for tags first as tag based searches are more common. + if (preg_match("/^ext[=|:]([a-zA-Z0-9]+)$/i", $event->term, $matches)) { + $ext = strtolower($matches[1]); + $event->add_querylet(new Querylet('images.ext = :ext', ["ext" => $ext])); + } elseif (preg_match("/^mime[=|:](.+)$/i", $event->term, $matches)) { + $mime = strtolower($matches[1]); + $event->add_querylet(new Querylet("images.mime = :mime", ["mime"=>$mime])); + } + } +} diff --git a/ext/mime/mime_map.php b/ext/mime/mime_map.php new file mode 100644 index 00000000..80ba68a2 --- /dev/null +++ b/ext/mime/mime_map.php @@ -0,0 +1,256 @@ + [ + self::MAP_NAME => "ANI Cursor", + self::MAP_EXT => [FileExtension::ANI], + self::MAP_MIME => [MimeType::ANI], + ], + MimeType::AVI => [ + self::MAP_NAME => "AVI", + self::MAP_EXT => [FileExtension::AVI], + self::MAP_MIME => [MimeType::AVI, 'video/avi', 'video/msvideo'], + ], + MimeType::ASF => [ + self::MAP_NAME => "ASF/WMV", + self::MAP_EXT => [FileExtension::ASF, FileExtension::ASX, FileExtension::WMA, FileExtension::WMV], + self::MAP_MIME => [MimeType::ASF, MimeType::WMA, MimeType::WMV], + ], + MimeType::BMP => [ + self::MAP_NAME => "BMP", + self::MAP_EXT => [FileExtension::BMP], + self::MAP_MIME => [MimeType::BMP], + ], + MimeType::BZIP => [ + self::MAP_NAME => "BZIP", + self::MAP_EXT => [FileExtension::BZIP], + self::MAP_MIME => [MimeType::BZIP], + ], + MimeType::BZIP2 => [ + self::MAP_NAME => "BZIP2", + self::MAP_EXT => [FileExtension::BZIP2], + self::MAP_MIME => [MimeType::BZIP2], + ], + MimeType::COMIC_ZIP => [ + self::MAP_NAME => "CBZ", + self::MAP_EXT => [FileExtension::CBZ], + self::MAP_MIME => [MimeType::COMIC_ZIP], + ], + MimeType::CSS => [ + self::MAP_NAME => "Cascading Style Sheet", + self::MAP_EXT => [FileExtension::CSS], + self::MAP_MIME => [MimeType::CSS], + ], + MimeType::CSV => [ + self::MAP_NAME => "CSV", + self::MAP_EXT => [FileExtension::CSV], + self::MAP_MIME => [MimeType::CSV], + ], + MimeType::FLASH => [ + self::MAP_NAME => "Flash", + self::MAP_EXT => [FileExtension::FLASH], + self::MAP_MIME => [MimeType::FLASH], + ], + MimeType::FLASH_VIDEO => [ + self::MAP_NAME => "Flash Video", + self::MAP_EXT => [FileExtension::FLASH_VIDEO], + self::MAP_MIME => [MimeType::FLASH_VIDEO, 'video/flv'], + ], + MimeType::GIF => [ + self::MAP_NAME => "GIF", + self::MAP_EXT => [FileExtension::GIF], + self::MAP_MIME => [MimeType::GIF], + ], + MimeType::GZIP => [ + self::MAP_NAME => "GZIP", + self::MAP_EXT => [FileExtension::GZIP], + self::MAP_MIME => [MimeType::TAR], + ], + MimeType::HTML => [ + self::MAP_NAME => "HTML", + self::MAP_EXT => [FileExtension::HTM, FileExtension::HTML], + self::MAP_MIME => [MimeType::HTML], + ], + MimeType::ICO => [ + self::MAP_NAME => "Icon", + self::MAP_EXT => [FileExtension::ICO, FileExtension::CUR], + self::MAP_MIME => [MimeType::ICO, MimeType::WIN_BITMAP], + ], + MimeType::JPEG => [ + self::MAP_NAME => "JPEG", + self::MAP_EXT => [FileExtension::JPG, FileExtension::JPEG, FileExtension::JFIF, FileExtension::JFI], + self::MAP_MIME => [MimeType::JPEG], + ], + MimeType::JS => [ + self::MAP_NAME => "JavaScript", + self::MAP_EXT => [FileExtension::JS], + self::MAP_MIME => [MimeType::JS], + ], + MimeType::JSON => [ + self::MAP_NAME => "JSON", + self::MAP_EXT => [FileExtension::JSON], + self::MAP_MIME => [MimeType::JSON], + ], + MimeType::MKV => [ + self::MAP_NAME => "Matroska", + self::MAP_EXT => [FileExtension::MKV], + self::MAP_MIME => [MimeType::MKV], + ], + MimeType::MP3 => [ + self::MAP_NAME => "MP3", + self::MAP_EXT => [FileExtension::MP3], + self::MAP_MIME => [MimeType::MP3], + ], + MimeType::MP4_AUDIO => [ + self::MAP_NAME => "MP4 Audio", + self::MAP_EXT => [FileExtension::M4A], + self::MAP_MIME => [MimeType::MP4_AUDIO, "audio/m4a"], + ], + MimeType::MP4_VIDEO => [ + self::MAP_NAME => "MP4 Video", + self::MAP_EXT => [FileExtension::MP4, FileExtension::M4V], + self::MAP_MIME => [MimeType::MP4_VIDEO, 'video/x-m4v'], + ], + MimeType::MPEG => [ + self::MAP_NAME => "MPEG", + self::MAP_EXT => [FileExtension::MPG, FileExtension::MPEG], + self::MAP_MIME => [MimeType::MPEG], + ], + MimeType::PDF => [ + self::MAP_NAME => "PDF", + self::MAP_EXT => [FileExtension::PDF], + self::MAP_MIME => [MimeType::PDF], + ], + MimeType::PHP => [ + self::MAP_NAME => "PHP", + self::MAP_EXT => [FileExtension::PHP, FileExtension::PHP5], + self::MAP_MIME => [MimeType::PHP], + ], + MimeType::PNG => [ + self::MAP_NAME => "PNG", + self::MAP_EXT => [FileExtension::PNG], + self::MAP_MIME => [MimeType::PNG], + ], + MimeType::PSD => [ + self::MAP_NAME => "PSD", + self::MAP_EXT => [FileExtension::PSD], + self::MAP_MIME => [MimeType::PSD], + ], + MimeType::OGG_AUDIO => [ + self::MAP_NAME => "Ogg Vorbis", + self::MAP_EXT => [FileExtension::OGG_AUDIO, FileExtension::OGG], + self::MAP_MIME => [MimeType::OGG_AUDIO, MimeType::OGG], + ], + MimeType::OGG_VIDEO => [ + self::MAP_NAME => "Ogg Theora", + self::MAP_EXT => [FileExtension::OGG_VIDEO], + self::MAP_MIME => [MimeType::OGG_VIDEO], + ], + MimeType::QUICKTIME => [ + self::MAP_NAME => "Quicktime", + self::MAP_EXT => [FileExtension::MOV], + self::MAP_MIME => [MimeType::QUICKTIME], + ], + MimeType::RSS => [ + self::MAP_NAME => "RSS", + self::MAP_EXT => [FileExtension::RSS], + self::MAP_MIME => [MimeType::RSS], + ], + MimeType::SVG => [ + self::MAP_NAME => "SVG", + self::MAP_EXT => [FileExtension::SVG], + self::MAP_MIME => [MimeType::SVG], + ], + MimeType::TAR => [ + self::MAP_NAME => "TAR", + self::MAP_EXT => [FileExtension::TAR], + self::MAP_MIME => [MimeType::TAR], + ], + MimeType::TEXT => [ + self::MAP_NAME => "Text", + self::MAP_EXT => [FileExtension::TEXT, FileExtension::ASC], + self::MAP_MIME => [MimeType::TEXT], + ], + MimeType::TIFF => [ + self::MAP_NAME => "TIFF", + self::MAP_EXT => [FileExtension::TIF, FileExtension::TIFF], + self::MAP_MIME => [MimeType::TIFF], + ], + MimeType::WAV => [ + self::MAP_NAME => "Wave", + self::MAP_EXT => [FileExtension::WAV], + self::MAP_MIME => [MimeType::WAV], + ], + MimeType::WEBM => [ + self::MAP_NAME => "WebM", + self::MAP_EXT => [FileExtension::WEBM], + self::MAP_MIME => [MimeType::WEBM], + ], + MimeType::WEBP => [ + self::MAP_NAME => "WebP", + self::MAP_EXT => [FileExtension::WEBP], + self::MAP_MIME => [MimeType::WEBP, MimeType::WEBP_LOSSLESS], + ], + MimeType::XML => [ + self::MAP_NAME => "XML", + self::MAP_EXT => [FileExtension::XML], + self::MAP_MIME => [MimeType::XML, MimeType::XML_APPLICATION], + ], + MimeType::XSL => [ + self::MAP_NAME => "XSL", + self::MAP_EXT => [FileExtension::XSL], + self::MAP_MIME => [MimeType::XSL], + ], + MimeType::ZIP => [ + self::MAP_NAME => "ZIP", + self::MAP_EXT => [FileExtension::ZIP], + self::MAP_MIME => [MimeType::ZIP], + ], + ]; + + public static function get_for_extension(string $ext): ?array + { + $ext = strtolower($ext); + + foreach (self::MAP as $key => $value) { + if (in_array($ext, $value[self::MAP_EXT])) { + return $value; + } + } + return null; + } + + public static function get_for_mime(string $mime): ?array + { + $mime = strtolower(MimeType::remove_parameters($mime)); + + foreach (self::MAP as $key => $value) { + if (in_array($mime, $value[self::MAP_MIME])) { + return $value; + } + } + return null; + } + + public static function get_name_for_mime(string $mime): ?string + { + $data = self::get_for_mime($mime); + if ($data!==null) { + return $data[self::MAP_NAME]; + } + return null; + } +} diff --git a/ext/mime/mime_type.php b/ext/mime/mime_type.php new file mode 100644 index 00000000..bf7d0ede --- /dev/null +++ b/ext/mime/mime_type.php @@ -0,0 +1,254 @@ +assertEquals($result, MimeType::JPEG); + } +} diff --git a/ext/mime/theme.php b/ext/mime/theme.php new file mode 100644 index 00000000..024fbf69 --- /dev/null +++ b/ext/mime/theme.php @@ -0,0 +1,41 @@ +", $mimes); + sort($exts); + $exts = join("", $exts); + + return 'Search for images by extension + + + ext=jpg + Returns images with the extension "jpg". + + + These extensions are available in the system: + '.$exts.' + + + + Search for images by MIME type + + + mime=image/jpeg + Returns images that have the MIME type "image/jpeg". + + + These MIME types are available in the system: + '.$mimes.' + + '; + } +} diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php index ee52555c..94fe4bcf 100644 --- a/ext/ouroboros_api/main.php +++ b/ext/ouroboros_api/main.php @@ -174,7 +174,7 @@ class _SafeOuroborosImage // file $this->height = intval($img->height); $this->width = intval($img->width); - $this->file_ext = $img->ext; + $this->file_ext = $img->get_ext(); $this->file_size = intval($img->filesize); $this->file_url = make_http($img->get_image_link()); $this->md5 = $img->hash; @@ -374,9 +374,9 @@ class OuroborosAPI extends Extension $this->event = $event; $this->type = $matches[1]; if ($this->type == 'json') { - $page->set_type('application/json; charset=utf-8'); + $page->set_mime('application/json; charset=utf-8'); } elseif ($this->type == 'xml') { - $page->set_type('text/xml; charset=utf-8'); + $page->set_mime('text/xml; charset=utf-8'); } $page->set_mode(PageMode::DATA); $this->tryAuth(); diff --git a/ext/qr_code/main.php b/ext/qr_code/main.php index 76b3db56..21dfb73b 100644 --- a/ext/qr_code/main.php +++ b/ext/qr_code/main.php @@ -7,6 +7,6 @@ class QRImage extends Extension public function onDisplayingImage(DisplayingImageEvent $event) { - $this->theme->links_block(make_http(make_link('image/'.$event->image->id.'.'.$event->image->ext))); + $this->theme->links_block(make_http(make_link('image/'.$event->image->id.'.'.$event->image->get_ext()))); } } diff --git a/ext/random_image/main.php b/ext/random_image/main.php index e65546c5..a7fa55b5 100644 --- a/ext/random_image/main.php +++ b/ext/random_image/main.php @@ -29,13 +29,13 @@ class RandomImage extends Extension if ($action === "download") { if (!is_null($image)) { - send_event(new ImageDownloadingEvent($image, $image->get_image_filename(), $image->get_mime_type())); + send_event(new ImageDownloadingEvent($image, $image->get_image_filename(), $image->get_mime())); } } elseif ($action === "view") { send_event(new DisplayingImageEvent($image)); } elseif ($action === "widget") { $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_HTML); + $page->set_mime(MimeType::HTML); $page->set_data($this->theme->build_thumb_html($image)); } } diff --git a/ext/regen_thumb/main.php b/ext/regen_thumb/main.php index dea7d447..79e90469 100644 --- a/ext/regen_thumb/main.php +++ b/ext/regen_thumb/main.php @@ -6,10 +6,10 @@ class RegenThumb extends Extension /** @var RegenThumbTheme */ protected $theme; - public function regenerate_thumbnail($image, $force = true): bool + public function regenerate_thumbnail(Image $image, bool $force = true): bool { global $cache; - $event = new ThumbnailGenerationEvent($image->hash, $image->ext, $force); + $event = new ThumbnailGenerationEvent($image->hash, $image->get_mime(), $force); send_event($event); $cache->delete("thumb-block:{$image->id}"); return $event->generated; @@ -109,11 +109,11 @@ class RegenThumb extends Extension $limit=intval($_POST["regen_thumb_limit"]); } - $type = ""; - if (isset($_POST["regen_thumb_limit"])) { - $type = $_POST["regen_thumb_type"]; + $mime = ""; + if (isset($_POST["regen_thumb_mime"])) { + $mime = $_POST["regen_thumb_mime"]; } - $images = $this->get_images($type); + $images = $this->get_images($mime); $i = 0; foreach ($images as $image) { @@ -123,7 +123,7 @@ class RegenThumb extends Extension continue; } } - $event = new ThumbnailGenerationEvent($image["hash"], $image["ext"], $force); + $event = new ThumbnailGenerationEvent($image["hash"], $image["mime"], $force); send_event($event); if ($event->generated) { $i++; @@ -137,8 +137,8 @@ class RegenThumb extends Extension case "delete_thumbs": $event->redirect = true; - if (isset($_POST["delete_thumb_type"])&&$_POST["delete_thumb_type"]!="") { - $images = $this->get_images($_POST["delete_thumb_type"]); + if (isset($_POST["delete_thumb_mime"])&&$_POST["delete_thumb_mime"]!="") { + $images = $this->get_images($_POST["delete_thumb_mime"]); $i = 0; foreach ($images as $image) { @@ -148,7 +148,7 @@ class RegenThumb extends Extension $i++; } } - $page->flash("Deleted $i thumbnails for ".$_POST["delete_thumb_type"]." images"); + $page->flash("Deleted $i thumbnails for ".$_POST["delete_thumb_mime"]." images"); } else { $dir = "data/thumbs/"; $this->remove_dir_recursively($dir); @@ -160,15 +160,15 @@ class RegenThumb extends Extension } } - public function get_images(String $ext = null) + public function get_images(String $mime = null) { global $database; - $query = "SELECT hash, ext FROM images"; + $query = "SELECT hash, mime FROM images"; $args = []; - if ($ext!=null&&$ext!="") { - $query .= " WHERE ext = :ext"; - $args["ext"] = $ext; + if (!empty($mime)) { + $query .= " WHERE mime = :mime"; + $args["mime"] = $mime; } return $database->get_all($query, $args); diff --git a/ext/regen_thumb/theme.php b/ext/regen_thumb/theme.php index 5463713a..97e0a044 100644 --- a/ext/regen_thumb/theme.php +++ b/ext/regen_thumb/theme.php @@ -36,10 +36,10 @@ class RegenThumbTheme extends Themelet { global $page, $database; - $types = []; - $results = $database->get_all("SELECT ext, count(*) count FROM images group by ext"); + $mimes = []; + $results = $database->get_all("SELECT mime, count(*) count FROM images group by mime"); foreach ($results as $result) { - array_push($types, "".$result["ext"]." (".$result["count"].")"); + array_push($mimes, "".$result["mime"]." (".$result["count"].")"); } $html = " @@ -48,10 +48,10 @@ class RegenThumbTheme extends Themelet Force Limit - Type - + MIME + All - ".implode($types)." + ".implode($mimes)." @@ -59,10 +59,10 @@ class RegenThumbTheme extends Themelet ".make_form(make_link("admin/delete_thumbs"), "POST", false, "", "return confirm('Are you sure you want to delete all thumbnails?')")." - Type - + MIME + All - ".implode($types)." + ".implode($mimes)." diff --git a/ext/resize/main.php b/ext/resize/main.php index 7658cc89..3def0f86 100644 --- a/ext/resize/main.php +++ b/ext/resize/main.php @@ -40,7 +40,7 @@ class ResizeImage extends Extension { global $user, $config; if ($user->can(Permissions::EDIT_FILES) && $config->get_bool(ResizeConfig::ENABLED) - && $this->can_resize_format($event->image->ext, $event->image->lossless)) { + && $this->can_resize_mime($event->image->get_mime())) { /* Add a link to resize the image */ $event->add_part($this->theme->get_resize_html($event->image)); } @@ -74,7 +74,7 @@ class ResizeImage extends Extension $image_obj = Image::by_id($event->image_id); if ($config->get_bool(ResizeConfig::UPLOAD) == true - && $this->can_resize_format($event->type, $image_obj->lossless)) { + && $this->can_resize_mime($event->mime)) { $width = $height = 0; if ($config->get_int(ResizeConfig::DEFAULT_WIDTH) !== 0) { @@ -84,7 +84,7 @@ class ResizeImage extends Extension $height = $config->get_int(ResizeConfig::DEFAULT_HEIGHT); } $isanigif = 0; - if ($image_obj->ext == EXTENSION_GIF) { + if ($image_obj->get_mime() == MimeType::GIF) { $image_filename = warehouse_path(Image::IMAGE_DIR, $image_obj->hash); if (($fh = @fopen($image_filename, 'rb'))) { //check if gif is animated (via https://www.php.net/manual/en/function.imagecreatefromgif.php#104473) @@ -104,7 +104,7 @@ class ResizeImage extends Extension //Need to generate thumbnail again... //This only seems to be an issue if one of the sizes was set to 0. $image_obj = Image::by_id($event->image_id); //Must be a better way to grab the new hash than setting this again.. - send_event(new ThumbnailGenerationEvent($image_obj->hash, $image_obj->ext, true)); + send_event(new ThumbnailGenerationEvent($image_obj->hash, $image_obj->get_mime(), true)); log_info("resize", "Image #{$event->image_id} has been resized to: ".$width."x".$height); //TODO: Notify user that image has been resized. @@ -161,12 +161,12 @@ class ResizeImage extends Extension } } - private function can_resize_format($format, ?bool $lossless = null): bool + private function can_resize_mime($mime): bool { global $config; $engine = $config->get_string(ResizeConfig::ENGINE); - return Media::is_input_supported($engine, $format, $lossless) - && Media::is_output_supported($engine, $format, $lossless); + return MediaEngine::is_input_supported($engine, $mime) + && MediaEngine::is_output_supported($engine, $mime); } @@ -183,7 +183,7 @@ class ResizeImage extends Extension $engine = $config->get_string(ResizeConfig::ENGINE); - if (!$this->can_resize_format($image_obj->ext, $image_obj->lossless)) { + if (!$this->can_resize_mime($image_obj->get_mime())) { throw new ImageResizeException("Engine $engine cannot resize selected image"); } @@ -206,11 +206,11 @@ class ResizeImage extends Extension send_event(new MediaResizeEvent( $engine, $image_filename, - $image_obj->ext, + $image_obj->get_mime(), $tmp_filename, $new_width, $new_height, - true + Media::RESIZE_TYPE_STRETCH )); $new_image = new Image(); @@ -219,7 +219,6 @@ class ResizeImage extends Extension $new_image->filename = 'resized-'.$image_obj->filename; $new_image->width = $new_width; $new_image->height = $new_height; - $new_image->ext = $image_obj->ext; /* Move the new image into the main storage location */ $target = warehouse_path(Image::IMAGE_DIR, $new_image->hash); diff --git a/ext/rotate/main.php b/ext/rotate/main.php index 9b1bfbfa..216d2a54 100644 --- a/ext/rotate/main.php +++ b/ext/rotate/main.php @@ -1,5 +1,7 @@ can(Permissions::EDIT_FILES) && $config->get_bool("rotate_enabled") - && in_array(get_mime_for_extension($event->image->ext), self::SUPPORTED_MIME)) { + && MimeType::matches_array($event->image->get_mime(), self::SUPPORTED_MIME)) { /* Add a link to rotate the image */ $event->add_part($this->theme->get_rotate_html($event->image->id)); } @@ -126,28 +128,6 @@ class RotateImage extends Extension throw new ImageRotateException("Could not load image: ".$image_filename); } - /* Rotate and resample the image */ - /* - $image_rotated = imagecreatetruecolor( $new_width, $new_height ); - - if ( ($info[2] == IMAGETYPE_GIF) || ($info[2] == IMAGETYPE_PNG) ) { - $transparency = imagecolortransparent($image); - - if ($transparency >= 0) { - $transparent_color = imagecolorsforindex($image, $trnprt_indx); - $transparency = imagecolorallocate($image_rotated, $trnprt_color['red'], $trnprt_color['green'], $trnprt_color['blue']); - imagefill($image_rotated, 0, 0, $transparency); - imagecolortransparent($image_rotated, $transparency); - } - elseif ($info[2] == IMAGETYPE_PNG) { - imagealphablending($image_rotated, false); - $color = imagecolorallocatealpha($image_rotated, 0, 0, 0, 127); - imagefill($image_rotated, 0, 0, $color); - imagesavealpha($image_rotated, true); - } - } - */ - $background_color = 0; switch ($info[2]) { case IMAGETYPE_PNG: @@ -193,7 +173,6 @@ class RotateImage extends Extension $new_image->filename = 'rotated-'.$image_obj->filename; $new_image->width = $new_width; $new_image->height = $new_height; - $new_image->ext = $image_obj->ext; /* Move the new image into the main storage location */ $target = warehouse_path(Image::IMAGE_DIR, $new_image->hash); diff --git a/ext/rss_comments/main.php b/ext/rss_comments/main.php index 77dfb08a..09298f13 100644 --- a/ext/rss_comments/main.php +++ b/ext/rss_comments/main.php @@ -16,7 +16,7 @@ class RSSComments extends Extension global $config, $database, $page; if ($event->page_matches("rss/comments")) { $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_RSS); + $page->set_mime(MimeType::RSS); $comments = $database->get_all(" SELECT diff --git a/ext/rss_comments/test.php b/ext/rss_comments/test.php index 83936ca6..27d2605d 100644 --- a/ext/rss_comments/test.php +++ b/ext/rss_comments/test.php @@ -9,7 +9,7 @@ class RSSCommentsTest extends ShimmiePHPUnitTestCase send_event(new CommentPostingEvent($image_id, $user, "ASDFASDF")); $this->get_page('rss/comments'); - //$this->assert_mime(MIME_TYPE_RSS); + //$this->assert_mime(MimeType::RSS); $this->assert_no_content("Exception"); $this->assert_content("ASDFASDF"); } diff --git a/ext/rss_images/main.php b/ext/rss_images/main.php index e0ca5d48..e99c6fae 100644 --- a/ext/rss_images/main.php +++ b/ext/rss_images/main.php @@ -43,7 +43,7 @@ class RSSImages extends Extension global $page; global $config; $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_RSS); + $page->set_mime(MimeType::RSS); $data = ""; foreach ($images as $image) { diff --git a/ext/rss_images/test.php b/ext/rss_images/test.php index a3aa9323..12136211 100644 --- a/ext/rss_images/test.php +++ b/ext/rss_images/test.php @@ -8,26 +8,26 @@ class RSSImagesTest extends ShimmiePHPUnitTestCase $this->log_out(); $this->get_page('rss/images'); - //$this->assert_mime(MIME_TYPE_RSS); + //$this->assert_mime(MimeType::RSS); $this->assert_no_content("Exception"); $this->get_page('rss/images/1'); - //$this->assert_mime(MIME_TYPE_RSS); + //$this->assert_mime(MimeType::RSS); $this->assert_no_content("Exception"); # FIXME: test that the image is actually found $this->get_page('rss/images/computer/1'); - //$this->assert_mime(MIME_TYPE_RSS); + //$this->assert_mime(MimeType::RSS); $this->assert_no_content("Exception"); # valid tag, invalid page $this->get_page('rss/images/computer/2'); - //$this->assert_mime(MIME_TYPE_RSS); + //$this->assert_mime(MimeType::RSS); $this->assert_no_content("Exception"); # not found $this->get_page('rss/images/waffle/2'); - //$this->assert_mime(MIME_TYPE_RSS); + //$this->assert_mime(MimeType::RSS); $this->assert_no_content("Exception"); } } diff --git a/ext/shimmie_api/main.php b/ext/shimmie_api/main.php index ecada937..5fd27052 100644 --- a/ext/shimmie_api/main.php +++ b/ext/shimmie_api/main.php @@ -8,6 +8,7 @@ class _SafeImage public $hash; public $filesize; public $ext; + public $mime; public $posted; public $source; public $owner_id; @@ -20,7 +21,8 @@ class _SafeImage $this->width = $img->width; $this->hash = $img->hash; $this->filesize = $img->filesize; - $this->ext = $img->ext; + $this->ext = $img->get_ext(); + $this->mime = $img->get_mime(); $this->posted = strtotime($img->posted); $this->source = $img->source; $this->owner_id = $img->owner_id; @@ -36,7 +38,7 @@ class ShimmieApi extends Extension if ($event->page_matches("api/shimmie")) { $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_TEXT); + $page->set_mime(MimeType::TEXT); if ($event->page_matches("api/shimmie/get_tags")) { if ($event->count_args() > 0) { diff --git a/ext/sitemap/main.php b/ext/sitemap/main.php index 02ef3ccc..5c940d4e 100644 --- a/ext/sitemap/main.php +++ b/ext/sitemap/main.php @@ -155,7 +155,7 @@ class XMLSitemap extends Extension // Generate new sitemap file_put_contents($this->sitemap_filepath, $xml); $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_XML_APPLICATION); + $page->set_mime(MimeType::XML_APPLICATION); $page->set_data($xml); } @@ -191,7 +191,7 @@ class XMLSitemap extends Extension $xml = file_get_contents($this->sitemap_filepath); $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_XML_APPLICATION); + $page->set_mime(MimeType::XML_APPLICATION); $page->set_data($xml); } } diff --git a/ext/static_files/main.php b/ext/static_files/main.php index fd541e37..407cce0a 100644 --- a/ext/static_files/main.php +++ b/ext/static_files/main.php @@ -22,7 +22,7 @@ class StaticFiles extends Extension $page->set_mode(PageMode::DATA); $page->set_data(file_get_contents($filename)); - $page->set_type(get_mime($filename)); + $page->set_mime(MimeType::get_for_file($filename)); } } } diff --git a/ext/tag_list/main.php b/ext/tag_list/main.php index 9071ded7..fee5d483 100644 --- a/ext/tag_list/main.php +++ b/ext/tag_list/main.php @@ -77,7 +77,7 @@ class TagList extends Extension } $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_TEXT); + $page->set_mime(MimeType::TEXT); $page->set_data(implode("\n", $res)); } } diff --git a/ext/tagger_xml/main.php b/ext/tagger_xml/main.php index 32fc62be..f09f35ab 100644 --- a/ext/tagger_xml/main.php +++ b/ext/tagger_xml/main.php @@ -30,7 +30,7 @@ class TaggerXML extends Extension ""; $page->set_mode(PageMode::DATA); - $page->set_type(MIME_TYPE_XML); + $page->set_mime(MimeType::XML); $page->set_data($xml); } } diff --git a/ext/transcode/config.php b/ext/transcode/config.php index f5855027..7d6a454d 100644 --- a/ext/transcode/config.php +++ b/ext/transcode/config.php @@ -2,6 +2,7 @@ class TranscodeConfig { + const VERSION = "ext_transcode_version"; const ENGINE = "transcode_engine"; const ENABLED = "transcode_enabled"; const GET_ENABLED = "transcode_get_enabled"; diff --git a/ext/transcode/main.php b/ext/transcode/main.php index 134e18df..8c94678f 100644 --- a/ext/transcode/main.php +++ b/ext/transcode/main.php @@ -16,23 +16,23 @@ class TranscodeImage extends Extension const ACTION_BULK_TRANSCODE = "bulk_transcode"; - const INPUT_FORMATS = [ - "BMP" => EXTENSION_BMP, - "GIF" => EXTENSION_GIF, - "ICO" => EXTENSION_ICO, - "JPG" => EXTENSION_JPG, - "PNG" => EXTENSION_PNG, - "PSD" => EXTENSION_PSD, - "TIFF" => EXTENSION_TIFF, - "WEBP" => EXTENSION_WEBP, + const INPUT_MIMES = [ + "BMP" => MimeType::BMP, + "GIF" => MimeType::GIF, + "ICO" => MimeType::ICO, + "JPG" => MimeType::JPEG, + "PNG" => MimeType::PNG, + "PSD" => MimeType::PSD, + "TIFF" => MimeType::TIFF, + "WEBP" => MimeType::WEBP ]; - const OUTPUT_FORMATS = [ + const OUTPUT_MIMES = [ "" => "", - "JPEG (lossy)" => EXTENSION_JPG, - "PNG (lossless)" => EXTENSION_PNG, - "WEBP (lossy)" => Media::WEBP_LOSSY, - "WEBP (lossless)" => Media::WEBP_LOSSLESS, + "JPEG (lossy)" => MimeType::JPEG, + "PNG (lossless)" => MimeType::PNG, + "WEBP (lossy)" => MimeType::WEBP, + "WEBP (lossless)" => MimeType::WEBP_LOSSLESS, ]; /** @@ -52,19 +52,82 @@ class TranscodeImage extends Extension $config->set_default_string(TranscodeConfig::ENGINE, MediaEngine::GD); $config->set_default_int(TranscodeConfig::QUALITY, 80); - foreach (array_values(self::INPUT_FORMATS) as $format) { - $config->set_default_string(TranscodeConfig::UPLOAD_PREFIX.$format, ""); + foreach (array_values(self::INPUT_MIMES) as $mime) { + $config->set_default_string(self::get_mapping_name($mime), ""); } } + private static function get_mapping_name(string $mime): string + { + $mime = str_replace(".", "_", $mime); + $mime = str_replace("/", "_", $mime); + return TranscodeConfig::UPLOAD_PREFIX.$mime; + } + + private static function get_mapping(String $mime): ?string + { + global $config; + return $config->get_string(self::get_mapping_name($mime)); + } + private static function set_mapping(String $from_mime, ?String $to_mime): void + { + global $config; + $config->set_string(self::get_mapping_name($from_mime), $to_mime); + } + + public static function get_enabled_mimes(): array + { + $output = []; + foreach (array_values(self::INPUT_MIMES) as $mime) { + $value = self::get_mapping($mime); + if (!empty($value)) { + $output[] = $mime; + } + } + return $output; + } + + public function onDatabaseUpgrade(DatabaseUpgradeEvent $event) + { + global $config; + + if ($this->get_version(TranscodeConfig::VERSION) < 1) { + $old_extensions =[]; + foreach (array_values(self::INPUT_MIMES) as $mime) { + $old_extensions = array_merge($old_extensions, FileExtension::get_all_for_mime($mime)); + } + + foreach ($old_extensions as $old_extension) { + $oldValue = $this->get_mapping($old_extension); + if (!empty($oldValue)) { + $from_mime = MimeType::get_for_extension($old_extension); + if (empty($from_mime)) { + continue; + } + + $to_mime = MimeType::get_for_extension($oldValue); + if (empty($to_mime)) { + continue; + } + + $this->set_mapping($from_mime, $to_mime); + $this->set_mapping($old_extension, null); + } + } + + $this->set_version(TranscodeConfig::VERSION, 1); + } + } + + public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) { global $user, $config; if ($user->can(Permissions::EDIT_FILES)) { $engine = $config->get_string(TranscodeConfig::ENGINE); - if ($this->can_convert_format($engine, $event->image->ext, $event->image->lossless)) { - $options = $this->get_supported_output_formats($engine, $event->image->ext, $event->image->lossless??false); + if ($this->can_convert_mime($engine, $event->image->get_mime())) { + $options = $this->get_supported_output_mimes($engine, $event->image->get_mime()); $event->add_part($this->theme->get_transcode_html($event->image, $options)); } } @@ -82,10 +145,10 @@ class TranscodeImage extends Extension $sb->add_bool_option(TranscodeConfig::ENABLED, "Allow transcoding images: ", true); $sb->add_bool_option(TranscodeConfig::UPLOAD, "Transcode on upload: ", true); $sb->add_choice_option(TranscodeConfig::ENGINE, Media::IMAGE_MEDIA_ENGINES, "Engine", true); - foreach (self::INPUT_FORMATS as $display=>$format) { - if (in_array($format, MediaEngine::INPUT_SUPPORT[$engine])) { - $outputs = $this->get_supported_output_formats($engine, $format); - $sb->add_choice_option(TranscodeConfig::UPLOAD_PREFIX.$format, $outputs, "$display", true); + foreach (self::INPUT_MIMES as $display=> $mime) { + if (MediaEngine::is_input_supported($engine, $mime)) { + $outputs = $this->get_supported_output_mimes($engine, $mime); + $sb->add_choice_option(self::get_mapping_name($mime), $outputs, "$display", true); } } $sb->add_int_option(TranscodeConfig::QUALITY, "Lossy Format Quality", true); @@ -98,22 +161,19 @@ class TranscodeImage extends Extension global $config; if ($config->get_bool(TranscodeConfig::UPLOAD) == true) { - $ext = strtolower($event->type); - - $ext = Media::normalize_format($ext); - - if ($event->type==EXTENSION_GIF&&Media::is_animated_gif($event->tmpname)) { + $mime = strtolower($event->mime); + if ($mime===MimeType::GIF&&MimeType::is_animated_gif($event->tmpname)) { return; } - if (in_array($ext, array_values(self::INPUT_FORMATS))) { - $target_format = $config->get_string(TranscodeConfig::UPLOAD_PREFIX.$ext); - if (empty($target_format)) { + if (in_array($mime, array_values(self::INPUT_MIMES))) { + $target_mime = self::get_mapping($mime); + if (empty($target_mime)) { return; } try { - $new_image = $this->transcode_image($event->tmpname, $ext, $target_format); - $event->set_type(Media::determine_ext($target_format)); + $new_image = $this->transcode_image($event->tmpname, $mime, $target_mime); + $event->set_mime($target_mime); $event->set_tmpname($new_image); } catch (Exception $e) { log_error("transcode", "Error while performing upload transcode: ".$e->getMessage()); @@ -142,9 +202,9 @@ class TranscodeImage extends Extension if (is_null($image_obj)) { $this->theme->display_error(404, "Image not found", "No image in the database has the ID #$image_id"); } else { - if (isset($_POST['transcode_format'])) { + if (isset($_POST['transcode_mime'])) { try { - $this->transcode_and_replace_image($image_obj, $_POST['transcode_format']); + $this->transcode_and_replace_image($image_obj, $_POST['transcode_mime']); $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("post/view/".$image_id)); } catch (ImageTranscodeException $e) { @@ -163,7 +223,7 @@ class TranscodeImage extends Extension $engine = $config->get_string(TranscodeConfig::ENGINE); if ($user->can(Permissions::EDIT_FILES)) { - $event->add_action(self::ACTION_BULK_TRANSCODE, "Transcode", null, "", $this->theme->get_transcode_picker_html($this->get_supported_output_formats($engine))); + $event->add_action(self::ACTION_BULK_TRANSCODE, "Transcode", null, "", $this->theme->get_transcode_picker_html($this->get_supported_output_mimes($engine))); } } @@ -173,11 +233,11 @@ class TranscodeImage extends Extension switch ($event->action) { case self::ACTION_BULK_TRANSCODE: - if (!isset($_POST['transcode_format'])) { + if (!isset($_POST['transcode_mime'])) { return; } if ($user->can(Permissions::EDIT_FILES)) { - $format = $_POST['transcode_format']; + $mime = $_POST['transcode_mime']; $total = 0; $size_difference = 0; foreach ($event->items as $image) { @@ -186,7 +246,7 @@ class TranscodeImage extends Extension $before_size = $image->filesize; - $new_image = $this->transcode_and_replace_image($image, $format); + $new_image = $this->transcode_and_replace_image($image, $mime); // If a subsequent transcode fails, the database needs to have everything about the previous // transcodes recorded already, otherwise the image entries will be stuck pointing to // missing image files @@ -194,7 +254,7 @@ class TranscodeImage extends Extension $total++; $size_difference += ($before_size - $new_image->filesize); } catch (Exception $e) { - log_error("transcode", "Error while bulk transcode on item {$image->id} to $format: ".$e->getMessage()); + log_error("transcode", "Error while bulk transcode on item {$image->id} to $mime: ".$e->getMessage()); try { $database->rollback(); } catch (Exception $e) { @@ -215,27 +275,24 @@ class TranscodeImage extends Extension } - private function can_convert_format($engine, $format, ?bool $lossless = null): bool + private function can_convert_mime($engine, $mime): bool { - return Media::is_input_supported($engine, $format, $lossless); + return MediaEngine::is_input_supported($engine, $mime); } - private function get_supported_output_formats($engine, ?String $omit_format = null, ?bool $lossless = null): array + private function get_supported_output_mimes($engine, ?String $omit_mime = null): array { - if ($omit_format!=null) { - $omit_format = Media::normalize_format($omit_format, $lossless); - } $output = []; - foreach (self::OUTPUT_FORMATS as $key=>$value) { + foreach (self::OUTPUT_MIMES as $key=> $value) { if ($value=="") { $output[$key] = $value; continue; } - if (Media::is_output_supported($engine, $value) - &&(empty($omit_format)||$omit_format!=$value)) { + if (MediaEngine::is_output_supported($engine, $value) + &&(empty($omit_mime)||$omit_mime!=$value)) { $output[$key] = $value; } } @@ -244,11 +301,11 @@ class TranscodeImage extends Extension - private function transcode_and_replace_image(Image $image_obj, String $target_format): Image + private function transcode_and_replace_image(Image $image_obj, String $target_mime): Image { $original_file = warehouse_path(Image::IMAGE_DIR, $image_obj->hash); - $tmp_filename = $this->transcode_image($original_file, $image_obj->ext, $target_format); + $tmp_filename = $this->transcode_image($original_file, $image_obj->get_mime(), $target_mime); $new_image = new Image(); $new_image->hash = md5_file($tmp_filename); @@ -256,7 +313,6 @@ class TranscodeImage extends Extension $new_image->filename = $image_obj->filename; $new_image->width = $image_obj->width; $new_image->height = $image_obj->height; - $new_image->ext = Media::determine_ext($target_format); /* Move the new image into the main storage location */ $target = warehouse_path(Image::IMAGE_DIR, $new_image->hash); @@ -273,36 +329,36 @@ class TranscodeImage extends Extension } - private function transcode_image(String $source_name, String $source_format, string $target_format): string + private function transcode_image(String $source_name, String $source_mime, string $target_mime): string { global $config; - if ($source_format==$target_format) { - throw new ImageTranscodeException("Source and target formats are the same: ".$source_format); + if ($source_mime==$target_mime) { + throw new ImageTranscodeException("Source and target MIMEs are the same: ".$source_mime); } $engine = $config->get_string("transcode_engine"); - if (!$this->can_convert_format($engine, $source_format)) { - throw new ImageTranscodeException("Engine $engine does not support input format $source_format"); + if (!$this->can_convert_mime($engine, $source_mime)) { + throw new ImageTranscodeException("Engine $engine does not support input MIME $source_mime"); } - if (!in_array($target_format, MediaEngine::OUTPUT_SUPPORT[$engine])) { - throw new ImageTranscodeException("Engine $engine does not support output format $target_format"); + if (!MediaEngine::is_output_supported($engine, $target_mime)) { + throw new ImageTranscodeException("Engine $engine does not support output MIME $target_mime"); } switch ($engine) { case "gd": - return $this->transcode_image_gd($source_name, $source_format, $target_format); + return $this->transcode_image_gd($source_name, $source_mime, $target_mime); case "convert": - return $this->transcode_image_convert($source_name, $source_format, $target_format); + return $this->transcode_image_convert($source_name, $source_mime, $target_mime); default: throw new ImageTranscodeException("No engine specified"); } } - private function transcode_image_gd(String $source_name, String $source_format, string $target_format): string + private function transcode_image_gd(String $source_name, String $source_mime, string $target_mime): string { global $config; @@ -313,15 +369,14 @@ class TranscodeImage extends Extension $image = imagecreatefromstring(file_get_contents($source_name)); try { $result = false; - switch ($target_format) { - case EXTENSION_WEBP: - case Media::WEBP_LOSSY: + switch ($target_mime) { + case MimeType::WEBP: $result = imagewebp($image, $tmp_name, $q); break; - case EXTENSION_PNG: + case MimeType::PNG: $result = imagepng($image, $tmp_name, 9); break; - case EXTENSION_JPG: + case MimeType::JPEG: // In case of alpha channels $width = imagesx($image); $height = imagesy($image); @@ -350,12 +405,12 @@ class TranscodeImage extends Extension imagedestroy($image); } if ($result===false) { - throw new ImageTranscodeException("Error while transcoding ".$source_name." to ".$target_format); + throw new ImageTranscodeException("Error while transcoding ".$source_name." to ".$target_mime); } return $tmp_name; } - private function transcode_image_convert(String $source_name, String $source_format, string $target_format): string + private function transcode_image_convert(String $source_name, String $source_mime, string $target_mime): string { global $config; @@ -365,35 +420,35 @@ class TranscodeImage extends Extension if ($convert==null||$convert=="") { throw new ImageTranscodeException("ImageMagick path not configured"); } - $ext = Media::determine_ext($target_format); + $ext = Media::determine_ext($target_mime); - $args = " -flatten "; - $bg = "none"; - switch ($target_format) { - case Media::WEBP_LOSSLESS: - $args .= '-define webp:lossless=true'; + $args = " -flatten -background "; + + if (Media::supports_alpha($target_mime)) { + $args .= "none "; + } else { + $args .= "black "; + } + + switch ($target_mime) { + case MimeType::PNG: + $args .= ' -define png:compression-level=9'; break; - case Media::WEBP_LOSSY: - $args .= ''; - break; - case EXTENSION_PNG: - $args .= '-define png:compression-level=9'; + case MimeType::WEBP_LOSSLESS: + $args .= ' -define webp:lossless=true -quality 100 '; break; default: - $bg = "black"; + $args .= ' -quality '.$q; break; } $tmp_name = tempnam(sys_get_temp_dir(), "shimmie_transcode"); - $source_type = ""; - switch ($source_format) { - case EXTENSION_ICO: - $source_type = "ico:"; - } + $source_type = FileExtension::get_for_mime($source_mime); + + $format = '"%s" %s:"%s" %s %s:"%s" 2>&1'; + $cmd = sprintf($format, $convert, $source_type, $source_name, $args, $ext, $tmp_name); - $format = '"%s" %s -quality %u -background %s %s"%s" %s:"%s" 2>&1'; - $cmd = sprintf($format, $convert, $args, $q, $bg, $source_type, $source_name, $ext, $tmp_name); $cmd = str_replace("\"convert\"", "convert", $cmd); // quotes are only needed if the path to convert contains a space; some other times, quotes break things, see github bug #27 exec($cmd, $output, $ret); diff --git a/ext/transcode/script.js b/ext/transcode/script.js index 6f78ac33..3e962f92 100644 --- a/ext/transcode/script.js +++ b/ext/transcode/script.js @@ -1,6 +1,6 @@ function transcodeSubmit(e) { - var format = document.getElementById('transcode_format').value; - if(format!="webp-lossless" && format != "png") { + var mime = document.getElementById('transcode_mime').value; + if(!mime.includes("lossless=true") && format != "image/png") { var lossless = document.getElementById('image_lossless'); if(lossless!=null && lossless.value=='1') { return confirm('You are about to transcode from a lossless format to a lossy format. Lossless formats compress with no quality loss, but converting to a lossy format always results in quality loss, and it will lose more quality every time it is done again on the same image. Are you sure you want to perform this transcode?'); @@ -8,4 +8,4 @@ function transcodeSubmit(e) { return confirm('Converting to a lossy format always results in quality loss, and it will lose more quality every time it is done again on the same image. Are you sure you want to perform this transcode?'); } } -} \ No newline at end of file +} diff --git a/ext/transcode/theme.php b/ext/transcode/theme.php index e6230a64..d6c0e5f0 100644 --- a/ext/transcode/theme.php +++ b/ext/transcode/theme.php @@ -27,7 +27,7 @@ class TranscodeImageTheme extends Themelet public function get_transcode_picker_html(array $options) { - $html = ""; + $html = ""; foreach ($options as $display=>$value) { $html .= "$display"; } diff --git a/ext/update/main.php b/ext/update/main.php index bd562bb8..bcd8c0fb 100644 --- a/ext/update/main.php +++ b/ext/update/main.php @@ -67,7 +67,7 @@ class Update extends Extension log_info("update", "Attempting to download Shimmie commit: ".$commitSHA); if ($headers = transload($url, $filename)) { - if (($headers['Content-Type'] !== MIME_TYPE_ZIP) || ((int) $headers['Content-Length'] !== filesize($filename))) { + if (($headers['Content-Type'] !== MimeType::ZIP) || ((int) $headers['Content-Length'] !== filesize($filename))) { unlink("./data/update_{$commitSHA}.zip"); log_warning("update", "Download failed: not zip / not same size as remote file."); return false; diff --git a/ext/upgrade/main.php b/ext/upgrade/main.php index 6d896c63..70bd407c 100644 --- a/ext/upgrade/main.php +++ b/ext/upgrade/main.php @@ -186,6 +186,19 @@ class Upgrade extends Extension $database->execute($database->scoreql_to_sql("UPDATE images SET lossless = SCORE_BOOL_N, video = SCORE_BOOL_Y WHERE ext IN ('flv','mp4','m4v','ogv','webm')")); $this->set_version("db_version", 18); } + + if ($this->get_version("db_version") < 19) { + log_info("upgrade", "Adding MIME type column"); + + $database->execute($database->scoreql_to_sql( + "ALTER TABLE images ADD COLUMN mime varchar(512) NULL" + )); + // Column is primed in mime extension + log_info("upgrade", "Setting index for mime column"); + $database->execute('CREATE INDEX images_mime_idx ON images(mime)'); + + $this->set_version("db_version", 19); + } } public function get_priority(): int diff --git a/ext/upload/main.php b/ext/upload/main.php index 3aefa767..eb884f0e 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -12,7 +12,7 @@ class DataUploadEvent extends Event /** @var string */ public $hash; /** @var string */ - public $type = ""; + public $mime = ""; /** @var int */ public $image_id = -1; /** @var int */ @@ -44,29 +44,23 @@ class DataUploadEvent extends Event $this->set_tmpname($tmpname); - if ($config->get_bool("upload_use_mime")) { - $filetype = get_extension_for_file($tmpname); + if (array_key_exists("extension", $metadata)) { + $mime = MimeType::get_for_file($tmpname, $metadata["extension"]); + } else { + $mime = MimeType::get_for_file($tmpname); } - if (empty($filetype)) { - if (array_key_exists('extension', $metadata) && !empty($metadata['extension'])) { - $filetype = strtolower($metadata['extension']); - } else { - throw new UploadException("Could not determine extension for file " . $metadata["filename"]); - } + if (empty($mime)) { + throw new UploadException("Could not determine mime type for file " . $metadata["filename"]); } - if (empty($filetype)) { - throw new UploadException("Could not determine extension for file " . $metadata["filename"]); - } - - $this->set_type($filetype); + $this->set_mime($mime); } - public function set_type(String $type) + public function set_mime(String $mime) { - $this->type = strtolower($type); - $this->metadata["extension"] = $this->type; + $this->mime = strtolower($mime); + $this->metadata["mime"] = $this->mime; } public function set_tmpname(String $tmpname) @@ -111,7 +105,6 @@ class Upload extends Extension $config->set_default_int('upload_size', parse_shorthand_int('1MB')); $config->set_default_int('upload_min_free_space', parse_shorthand_int('100MB')); $config->set_default_bool('upload_tlsource', true); - $config->set_default_bool('upload_use_mime', false); $this->is_full = false; @@ -147,7 +140,6 @@ class Upload extends Extension $sb->add_label("PHP Limit = " . ini_get('upload_max_filesize') . ""); $sb->add_choice_option("transload_engine", $tes, "Transload: "); $sb->add_bool_option("upload_tlsource", "Use transloaded URL as source if none is provided: "); - $sb->add_bool_option("upload_use_mime", "Use mime type to determine file types: "); $event->panel->add_block($sb); } @@ -347,9 +339,15 @@ class Upload extends Extension $pathinfo = pathinfo($file['name']); $metadata = []; $metadata['filename'] = $pathinfo['basename']; + $metadata['extension'] = ""; if (array_key_exists('extension', $pathinfo)) { $metadata['extension'] = $pathinfo['extension']; } + $metadata['mime'] = MimeType::get_for_file($file['tmp_name'], $metadata['extension']); + if (empty($metadata['mime'])) { + throw new UploadException("Unable to determine MIME for file ".$metadata['filename']); + } + $metadata['tags'] = $tags; $metadata['source'] = $source; @@ -357,7 +355,7 @@ class Upload extends Extension $event->replace_id = $replace_id; send_event($event); if ($event->image_id == -1) { - throw new UploadException("File type not supported: " . $metadata['extension']); + throw new UploadException("MIME type not supported: " . $metadata['mime']); } $page->add_http_header("X-Shimmie-Image-ID: " . $event->image_id); } catch (UploadException $ex) { @@ -425,15 +423,6 @@ class Upload extends Extension $metadata['tags'] = $tags; $metadata['source'] = (($url == $source) && !$config->get_bool('upload_tlsource') ? "" : $source); - $ext = false; - if (is_array($headers)) { - $ext = get_extension(findHeader($headers, 'Content-Type')); - } - if ($ext === false) { - $ext = $pathinfo['extension']; - } - $metadata['extension'] = $ext; - /* check for locked > adds to metadata if it has */ if (!empty($locked)) { $metadata['locked'] = $locked ? "on" : ""; diff --git a/ext/upload/theme.php b/ext/upload/theme.php index 5de75ddd..8e8a9173 100644 --- a/ext/upload/theme.php +++ b/ext/upload/theme.php @@ -242,8 +242,8 @@ class UploadTheme extends Themelet "; } - protected function get_accept() + protected function get_accept(): string { - return join(",", DataHandlerExtension::get_all_supported_exts()); + return ".".join(",.", DataHandlerExtension::get_all_supported_exts()); } } diff --git a/themes/danbooru/view.theme.php b/themes/danbooru/view.theme.php index a0e11426..059be8ea 100644 --- a/themes/danbooru/view.theme.php +++ b/themes/danbooru/view.theme.php @@ -17,7 +17,7 @@ class CustomViewImageTheme extends ViewImageTheme $h_owner = html_escape($image->get_owner()->name); $h_ownerlink = "$h_owner"; $h_ip = html_escape($image->owner_ip); - $h_type = html_escape($image->get_mime_type()); + $h_type = html_escape($image->get_mime()); $h_date = autodate($image->posted); $h_filesize = to_shorthand_int($image->filesize); diff --git a/themes/danbooru2/view.theme.php b/themes/danbooru2/view.theme.php index 6cce5847..421fece6 100644 --- a/themes/danbooru2/view.theme.php +++ b/themes/danbooru2/view.theme.php @@ -16,7 +16,7 @@ class CustomViewImageTheme extends ViewImageTheme $h_owner = html_escape($image->get_owner()->name); $h_ownerlink = "$h_owner"; $h_ip = html_escape($image->owner_ip); - $h_type = html_escape($image->get_mime_type()); + $h_type = html_escape($image->get_mime()); $h_date = autodate($image->posted); $h_filesize = to_shorthand_int($image->filesize); diff --git a/themes/lite/view.theme.php b/themes/lite/view.theme.php index 3c38ccc1..fa25386e 100644 --- a/themes/lite/view.theme.php +++ b/themes/lite/view.theme.php @@ -18,7 +18,7 @@ class CustomViewImageTheme extends ViewImageTheme $h_owner = html_escape($image->get_owner()->name); $h_ownerlink = "$h_owner"; $h_ip = html_escape($image->owner_ip); - $h_type = html_escape($image->get_mime_type()); + $h_type = html_escape($image->get_mime()); $h_date = autodate($image->posted); $h_filesize = to_shorthand_int($image->filesize); diff --git a/themes/rule34v2/themelet.class.php b/themes/rule34v2/themelet.class.php index 54556b1b..e8a5efc6 100644 --- a/themes/rule34v2/themelet.class.php +++ b/themes/rule34v2/themelet.class.php @@ -16,10 +16,10 @@ class Themelet extends BaseThemelet $h_thumb_link = $image->get_thumb_link(); $h_tip = html_escape($image->get_tooltip()); $h_tags = strtolower($image->get_tag_list()); - $h_ext = strtolower($image->ext); + $h_ext = strtolower($image->get_ext()); // If file is flash or svg then sets thumbnail to max size. - if ($image->ext === EXTENSION_FLASH || $image->ext === EXTENSION_SVG) { + if ($image->get_mime() === MimeType::FLASH || $image->get_mime() === MimeType::SVG) { $tsize = get_thumbnail_size($config->get_int('thumb_width'), $config->get_int('thumb_height')); } else { $tsize = get_thumbnail_size($image->width, $image->height);
Search items can be combined to search for images which match both, or you can stick \"-\" in front of an item to search for things that don't diff --git a/ext/index/main.php b/ext/index/main.php index 5c8b2ab8..e282f1db 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -208,9 +208,6 @@ class Index extends Extension } elseif (preg_match("/^(phash)[=|:]([0-9a-fA-F]*)$/i", $event->term, $matches)) { $phash = strtolower($matches[2]); $event->add_querylet(new Querylet('images.phash = :phash', ["phash" => $phash])); - } elseif (preg_match("/^(filetype|ext)[=|:]([a-zA-Z0-9]*)$/i", $event->term, $matches)) { - $ext = strtolower($matches[2]); - $event->add_querylet(new Querylet('images.ext = :ext', ["ext" => $ext])); } elseif (preg_match("/^(filename|name)[=|:]([a-zA-Z0-9]*)$/i", $event->term, $matches)) { $filename = strtolower($matches[2]); $event->add_querylet(new Querylet("images.filename LIKE :filename{$this->stpen}", ["filename{$this->stpen}"=>"%$filename%"])); diff --git a/ext/index/theme.php b/ext/index/theme.php index 4a65f4e6..1b5c0d11 100644 --- a/ext/index/theme.php +++ b/ext/index/theme.php @@ -187,44 +187,44 @@ and of course start organising your images :-)
tagname
Returns images that are tagged with "tagname".
tagname othertagname
Returns images that are tagged with "tagname" and "othertagname".
Most tags and keywords can be prefaced with a negative sign (-) to indicate that you want to search for images that do not match something.
-tagname
Returns images that are not tagged with "tagname".
-tagname -othertagname
Returns images that are not tagged with "tagname" and "othertagname". This is different than without the negative sign, as images with "tagname" or "othertagname" can still be returned as long as the other one is not present.
tagname -othertagname
Returns images that are tagged with "tagname", but are not tagged with "othertagname".
Wildcard searches are possible as well using * for "any one, more, or none" and ? for "any one".
tagn*
Returns images that are tagged with "tagname", "tagnot", or anything else that starts with "tagn".
tagn?me
Returns images that are tagged with "tagname", "tagnome", or anything else that starts with "tagn", has one character, and ends with "me".
tags=1
Returns images with exactly 1 tag.
Can use <, <=, >, >=, or =.
Search for images by aspect ratio
ratio=4:3
Returns images with an aspect ratio of 4:3.
Can use <, <=, >, >=, or =. The relation is calculated by dividing width by height.
Search for images by file size
filesize=1
Returns images exactly 1 byte in size.
Can use <, <=, >, >=, or =. Supported suffixes are kb, mb, and gb. Uses multiples of 1024.
Search for images by MD5 hash
hash=0D3512CAA964B2BA5D7851AF5951F33B
Returns image with an MD5 hash 0D3512CAA964B2BA5D7851AF5951F33B.
Search for images by file type
filetype=jpg
Returns images that are of type "jpg".
Search for images by file name
filename=picasso.jpg
Returns images that are named "picasso.jpg".
Search for images by source
source=http://google.com/
Returns images with a source of "http://google.com/".
source=any
Returns images with a source set.
source=none
Returns images without a source set.
Search for images by date posted.
posted>=07-19-2019
Returns images posted on or after 07-19-2019.
Can use <, <=, >, >=, or =. Date format is mm-dd-yyyy. Date posted includes time component, so = will not work unless the time is exact.
Search for images by image dimensions
size=640x480
Returns images exactly 640 pixels wide by 480 pixels high.
Sorting search results can be done using the pattern order:field_direction. _direction can be either _asc or _desc, indicating ascending (123) or descending (321) order.
order:id_asc
Returns images sorted by ID, smallest first.
order:width_desc
Returns images sorted by width, largest first.
These fields are supported:
Search for images by extension
ext=jpg
Returns images with the extension "jpg".
Search for images by MIME type
mime=image/jpeg
Returns images that have the MIME type "image/jpeg".
".make_form(make_link("admin/delete_thumbs"), "POST", false, "", "return confirm('Are you sure you want to delete all thumbnails?')")."