The great MIMEing

This commit is contained in:
Matthew Barbour 2020-06-14 11:05:55 -05:00 committed by Shish
parent 8dd5ad16f3
commit 984c9702ec
73 changed files with 1386 additions and 1148 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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;

View File

@ -1,448 +0,0 @@
<?php
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* MIME types and extension information and resolvers *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
const EXTENSION_ANI = 'ani';
const EXTENSION_ASC = 'asc';
const EXTENSION_ASF = 'asf';
const EXTENSION_AVI = 'avi';
const EXTENSION_BMP = 'bmp';
const EXTENSION_BZIP = 'bz';
const EXTENSION_BZIP2 = 'bz2';
const EXTENSION_CBR = 'cbr';
const EXTENSION_CBZ = 'cbz';
const EXTENSION_CBT = 'cbt';
const EXTENSION_CBA = 'cbA';
const EXTENSION_CB7 = 'cb7';
const EXTENSION_CSS = 'css';
const EXTENSION_CSV = 'csv';
const EXTENSION_CUR = 'cur';
const EXTENSION_FLASH = 'swf';
const EXTENSION_FLASH_VIDEO = 'flv';
const EXTENSION_GIF = 'gif';
const EXTENSION_GZIP = 'gz';
const EXTENSION_HTML = 'html';
const EXTENSION_HTM = 'htm';
const EXTENSION_ICO = 'ico';
const EXTENSION_JFIF = 'jfif';
const EXTENSION_JFI = 'jfi';
const EXTENSION_JPEG = 'jpeg';
const EXTENSION_JPG = 'jpg';
const EXTENSION_JS = 'js';
const EXTENSION_JSON = 'json';
const EXTENSION_MKV = 'mkv';
const EXTENSION_MP3 = 'mp3';
const EXTENSION_MP4 = 'mp4';
const EXTENSION_M4V = 'm4v';
const EXTENSION_M4A = 'm4a';
const EXTENSION_MPEG = 'mpeg';
const EXTENSION_MPG = 'mpg';
const EXTENSION_OGG = 'ogg';
const EXTENSION_OGG_VIDEO = 'ogv';
const EXTENSION_OGG_AUDIO = 'oga';
const EXTENSION_PDF = 'pdf';
const EXTENSION_PHP = 'php';
const EXTENSION_PHP5 = 'php5';
const EXTENSION_PNG = 'png';
const EXTENSION_PSD = 'psd';
const EXTENSION_MOV = 'mov';
const EXTENSION_RSS = 'rss';
const EXTENSION_SVG = 'svg';
const EXTENSION_TAR = 'tar';
const EXTENSION_TEXT = 'txt';
const EXTENSION_TIFF = 'tiff';
const EXTENSION_TIF = 'tif';
const EXTENSION_WAV = 'wav';
const EXTENSION_WEBM = 'webm';
const EXTENSION_WEBP = 'webp';
const EXTENSION_WMA = 'wma';
const EXTENSION_WMV = 'wmv';
const EXTENSION_XML = 'xml';
const EXTENSION_XSL = 'xsl';
const EXTENSION_ZIP = 'zip';
// Couldn't find a mimetype for ani, so made one up based on it being a riff container
const MIME_TYPE_ANI = 'application/riff+ani';
const MIME_TYPE_ASF = 'video/x-ms-asf';
const MIME_TYPE_AVI = 'video/x-msvideo';
// Went with mime types from http://fileformats.archiveteam.org/wiki/Comic_Book_Archive
const MIME_TYPE_COMIC_ZIP = 'application/vnd.comicbook+zip';
const MIME_TYPE_COMIC_RAR = 'application/vnd.comicbook-rar';
const MIME_TYPE_BMP = 'image/x-ms-bmp';
const MIME_TYPE_BZIP = 'application/x-bzip';
const MIME_TYPE_BZIP2 = 'application/x-bzip2';
const MIME_TYPE_CSS = 'text/css';
const MIME_TYPE_CSV = 'text/csv';
const MIME_TYPE_FLASH = 'application/x-shockwave-flash';
const MIME_TYPE_FLASH_VIDEO = 'video/x-flv';
const MIME_TYPE_GIF = 'image/gif';
const MIME_TYPE_GZIP = 'application/x-gzip';
const MIME_TYPE_HTML = 'text/html';
const MIME_TYPE_ICO = 'image/x-icon';
const MIME_TYPE_JPEG = 'image/jpeg';
const MIME_TYPE_JS = 'text/javascript';
const MIME_TYPE_JSON = 'application/json';
const MIME_TYPE_MKV = 'video/x-matroska';
const MIME_TYPE_MP3 = 'audio/mpeg';
const MIME_TYPE_MP4_AUDIO = 'audio/mp4';
const MIME_TYPE_MP4_VIDEO = 'video/mp4';
const MIME_TYPE_MPEG = 'video/mpeg';
const MIME_TYPE_OCTET_STREAM = 'application/octet-stream';
const MIME_TYPE_OGG = 'application/ogg';
const MIME_TYPE_OGG_VIDEO = 'video/ogg';
const MIME_TYPE_OGG_AUDIO = 'audio/ogg';
const MIME_TYPE_PDF = 'application/pdf';
const MIME_TYPE_PHP = 'text/x-php';
const MIME_TYPE_PNG = 'image/png';
const MIME_TYPE_PSD = 'image/vnd.adobe.photoshop';
const MIME_TYPE_QUICKTIME = 'video/quicktime';
const MIME_TYPE_RSS = 'application/rss+xml';
const MIME_TYPE_SVG = 'image/svg+xml';
const MIME_TYPE_TAR = 'application/x-tar';
const MIME_TYPE_TEXT = 'text/plain';
const MIME_TYPE_TIFF = 'image/tiff';
const MIME_TYPE_WAV = 'audio/x-wav';
const MIME_TYPE_WEBM = 'video/webm';
const MIME_TYPE_WEBP = 'image/webp';
const MIME_TYPE_WIN_BITMAP = 'image/x-win-bitmap';
const MIME_TYPE_XML = 'text/xml';
const MIME_TYPE_XML_APPLICATION = 'application/xml';
const MIME_TYPE_XSL = 'application/xsl+xml';
const MIME_TYPE_ZIP = 'application/zip';
const MIME_TYPE_MAP_NAME = 'name';
const MIME_TYPE_MAP_EXT = 'ext';
const MIME_TYPE_MAP_MIME = 'mime';
// Mime type map. Each entry in the MIME_TYPE_ARRAY represents a kind of file, identified by the "correct" mimetype as the key.
// The value for each entry is a map of twokeys, ext and mime.
// ext's value is an array of all of the extensions that the file type can use, with the "correct" one being first.
// mime's value is an array of all mime types that the file type is known to use, with the current "correct" one being first.
const MIME_TYPE_MAP = [
MIME_TYPE_ANI => [
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;
}

View File

@ -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;
}

View File

@ -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
*/

View File

@ -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

View File

@ -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
*/

View File

@ -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");
}

View File

@ -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") {

View File

@ -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") {

View File

@ -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 (

View File

@ -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");

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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());
}

View File

@ -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);

View File

@ -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),
],
];

View File

@ -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()));
}
}

View File

@ -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}";

View File

@ -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;

View File

@ -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
{

View File

@ -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());

View File

@ -1,8 +1,11 @@
<?php declare(strict_types=1);
// TODO: Add support for generating an icon from embedded cover art
// TODO: MORE AUDIO FORMATS
class MP3FileHandler extends DataHandlerExtension
{
protected $SUPPORTED_MIME = [MIME_TYPE_MP3];
protected $SUPPORTED_MIME = [MimeType::MP3];
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
{
@ -23,6 +26,6 @@ class MP3FileHandler extends DataHandlerExtension
protected function check_contents(string $tmpname): bool
{
return get_mime($tmpname) === MIME_TYPE_MP3;
return MimeType::get_for_file($tmpname) === MimeType::MP3;
}
}

View File

@ -2,26 +2,18 @@
class PixelFileHandler extends DataHandlerExtension
{
protected $SUPPORTED_MIME = [MIME_TYPE_JPEG, MIME_TYPE_GIF, MIME_TYPE_PNG, MIME_TYPE_WEBP];
protected $SUPPORTED_MIME = [MimeType::JPEG, MimeType::GIF, MimeType::PNG, MimeType::WEBP];
protected function media_check_properties(MediaCheckPropertiesEvent $event): void
{
if (in_array($event->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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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? <a href='$ilink'>Click here</a> to download the file.<br/>";
//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 <video>, 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 .= "<!--[if IE]><p>To view webm files with IE, please <a href='https://tools.google.com/dlpage/webmmf/' target='_blank'>download this plugin</a>.</p><![endif]-->";
}
@ -51,7 +50,7 @@ class VideoFileHandlerTheme extends Themelet
<img alt='thumb' src=\"{$thumb_url}\" />
</object>";
if ($mime == MIME_TYPE_FLASH_VIDEO) {
if ($mime == MimeType::FLASH_VIDEO) {
//FLV doesn't support <video>.
$html .= $html_fallback;
} else {

View File

@ -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';
}

View File

@ -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");

View File

@ -20,22 +20,26 @@ Shimmie extensions may provide other filters:
<li>pie
<li>somethi* -- wildcards are supported
</ul>
</li>
<li>size (=, &lt;, &gt;, &lt;=, &gt;=) width x height, eg
<ul>
<li>size=1024x768 -- a specific wallpaper size
<li>size&gt;=500x500 -- no small images
<li>size&lt;1000x1000 -- no large images
</ul>
</li>
<li>width (=, &lt;, &gt;, &lt;=, &gt;=) width, eg
<ul>
<li>width=1024 -- find images with 1024 width
<li>width>2000 -- find images bigger than 2000 width
</ul>
</li>
<li>height (=, &lt;, &gt;, &lt;=, &gt;=) height, eg
<ul>
<li>height=768 -- find images with 768 height
<li>height>1000 -- find images bigger than 1000 height
</ul>
</li>
<li>ratio (=, &lt;, &gt;, &lt;=, &gt;=) width : height, eg
<ul>
<li>ratio=4:3, ratio=16:9 -- standard wallpaper
@ -43,66 +47,73 @@ Shimmie extensions may provide other filters:
<li>ratio<1:1 -- tall images
<li>ratio>1:1 -- wide images
</ul>
</li>
<li>filesize (=, &lt;, &gt;, &lt;=, &gt;=) size, eg
<ul>
<li>filesize&gt;1024 -- no images under 1KB
<li>filesize&lt=3MB -- shorthand filesizes are supported too
</ul>
</li>
<li>id (=, &lt;, &gt;, &lt;=, &gt;=) number, eg
<ul>
<li>id<20 -- search only the first few images
<li>id>=500 -- search later images
</ul>
</li>
<li>user=Username & poster=Username, eg
<ul>
<li>user=Shish -- find all of Shish's posts
<li>poster=Shish -- same as above
</ul>
</li>
<li>user_id=userID & poster_id=userID, eg
<ul>
<li>user_id=2 -- find all posts by user id 2
<li>poster_id=2 -- same as above
</ul>
</li>
<li>hash=md5sum & md5=md5sum, eg
<ul>
<li>hash=bf5b59173f16b6937a4021713dbfaa72 -- find the \"Taiga want up!\" image
<li>md5=bf5b59173f16b6937a4021713dbfaa72 -- same as above
</ul>
<li>filetype=type & ext=type, eg
<ul>
<li>filetype=png -- find all PNG images
<li>ext=png -- same as above
</ul>
</li>
<li>filename=blah & name=blah, eg
<ul>
<li>filename=kitten -- find all images with \"kitten\" in the original filename
<li>name=kitten -- same as above
</ul>
</li>
<li>posted (=, &lt;, &gt;, &lt;=, &gt;=) date, eg
<ul>
<li>posted&gt;=2009-12-25 posted&lt;=2010-01-01 -- find images posted between christmas and new year
</ul>
</li>
<li>tags (=, &lt;, &gt;, &lt;=, &gt;=) count, eg
<ul>
<li>tags=1 -- search for images with only 1 tag
<li>tags>=10 -- search for images with 10 or more tags
<li>tags<25 -- search for images with less than 25 tags
</ul>
</li>
<li>source=(URL, any, none) eg
<ul>
<li>source=http://example.com -- find all images with \"http://example.com\" in the source
<li>source=any -- find all images with a source
<li>source=none -- find all images without a source
</ul>
</li>
<li>order=(id, width, height, filesize, filename)_(ASC, DESC), eg
<ul>
<li>order=width -- find all images sorted from highest > lowest width
<li>order=filesize_asc -- find all images sorted from lowest > highest filesize
</ul>
</li>
<li>order=random_####, eg
<ul>
<li>order=random_8547 -- find all images sorted randomly using 8547 as a seed
</ul>
</li>
</ul>
<p>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

View File

@ -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%"]));

View File

@ -187,44 +187,44 @@ and of course start organising your images :-)
<pre>tagname</pre>
<p>Returns images that are tagged with "tagname".</p>
</div>
<div class="command_example">
<pre>tagname othertagname</pre>
<p>Returns images that are tagged with "tagname" and "othertagname".</p>
<p>Returns images that are tagged with "tagname" and "othertagname".</p>
</div>
<p>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.</p>
<div class="command_example">
<pre>-tagname</pre>
<p>Returns images that are not tagged with "tagname".</p>
<p>Returns images that are not tagged with "tagname".</p>
</div>
<div class="command_example">
<pre>-tagname -othertagname</pre>
<p>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.</p>
<p>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.</p>
</div>
<div class="command_example">
<pre>tagname -othertagname</pre>
<p>Returns images that are tagged with "tagname", but are not tagged with "othertagname".</p>
<p>Returns images that are tagged with "tagname", but are not tagged with "othertagname".</p>
</div>
<p>Wildcard searches are possible as well using * for "any one, more, or none" and ? for "any one".</p>
<div class="command_example">
<pre>tagn*</pre>
<p>Returns images that are tagged with "tagname", "tagnot", or anything else that starts with "tagn".</p>
<p>Returns images that are tagged with "tagname", "tagnot", or anything else that starts with "tagn".</p>
</div>
<div class="command_example">
<pre>tagn?me</pre>
<p>Returns images that are tagged with "tagname", "tagnome", or anything else that starts with "tagn", has one character, and ends with "me".</p>
<p>Returns images that are tagged with "tagname", "tagnome", or anything else that starts with "tagn", has one character, and ends with "me".</p>
</div>
<div class="command_example">
<pre>tags=1</pre>
<p>Returns images with exactly 1 tag.</p>
<p>Returns images with exactly 1 tag.</p>
</div>
<div class="command_example">
@ -235,12 +235,12 @@ and of course start organising your images :-)
<p>Can use &lt;, &lt;=, &gt;, &gt;=, or =.</p>
<hr/>
<p>Search for images by aspect ratio</p>
<div class="command_example">
<pre>ratio=4:3</pre>
<p>Returns images with an aspect ratio of 4:3.</p>
<p>Returns images with an aspect ratio of 4:3.</p>
</div>
<div class="command_example">
@ -249,14 +249,14 @@ and of course start organising your images :-)
</div>
<p>Can use &lt;, &lt;=, &gt;, &gt;=, or =. The relation is calculated by dividing width by height.</p>
<hr/>
<p>Search for images by file size</p>
<div class="command_example">
<pre>filesize=1</pre>
<p>Returns images exactly 1 byte in size.</p>
<p>Returns images exactly 1 byte in size.</p>
</div>
<div class="command_example">
@ -265,71 +265,62 @@ and of course start organising your images :-)
</div>
<p>Can use &lt;, &lt;=, &gt;, &gt;=, or =. Supported suffixes are kb, mb, and gb. Uses multiples of 1024.</p>
<hr/>
<p>Search for images by MD5 hash</p>
<div class="command_example">
<pre>hash=0D3512CAA964B2BA5D7851AF5951F33B</pre>
<p>Returns image with an MD5 hash 0D3512CAA964B2BA5D7851AF5951F33B.</p>
</div>
<hr/>
<p>Search for images by file type</p>
<div class="command_example">
<pre>filetype=jpg</pre>
<p>Returns images that are of type "jpg".</p>
<p>Returns image with an MD5 hash 0D3512CAA964B2BA5D7851AF5951F33B.</p>
</div>
<hr/>
<p>Search for images by file name</p>
<div class="command_example">
<pre>filename=picasso.jpg</pre>
<p>Returns images that are named "picasso.jpg".</p>
<p>Returns images that are named "picasso.jpg".</p>
</div>
<hr/>
<p>Search for images by source</p>
<div class="command_example">
<pre>source=http://google.com/</pre>
<p>Returns images with a source of "http://google.com/".</p>
<p>Returns images with a source of "http://google.com/".</p>
</div>
<div class="command_example">
<pre>source=any</pre>
<p>Returns images with a source set.</p>
<p>Returns images with a source set.</p>
</div>
<div class="command_example">
<pre>source=none</pre>
<p>Returns images without a source set.</p>
<p>Returns images without a source set.</p>
</div>
<hr/>
<p>Search for images by date posted.</p>
<div class="command_example">
<pre>posted>=07-19-2019</pre>
<p>Returns images posted on or after 07-19-2019.</p>
<p>Returns images posted on or after 07-19-2019.</p>
</div>
<p>Can use &lt;, &lt;=, &gt;, &gt;=, or =. Date format is mm-dd-yyyy. Date posted includes time component, so = will not work unless the time is exact.</p>
<hr/>
<p>Search for images by image dimensions</p>
<div class="command_example">
<pre>size=640x480</pre>
<p>Returns images exactly 640 pixels wide by 480 pixels high.</p>
<p>Returns images exactly 640 pixels wide by 480 pixels high.</p>
</div>
<div class="command_example">
@ -348,21 +339,21 @@ and of course start organising your images :-)
</div>
<p>Can use &lt;, &lt;=, &gt;, &gt;=, or =.</p>
<hr/>
<p>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.</p>
<div class="command_example">
<pre>order:id_asc</pre>
<p>Returns images sorted by ID, smallest first.</p>
<p>Returns images sorted by ID, smallest first.</p>
</div>
<div class="command_example">
<pre>order:width_desc</pre>
<p>Returns images sorted by width, largest first.</p>
</div>
<p>These fields are supported:
<ul>
<li>id</li>

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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]
);
}
}

View File

@ -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(

105
ext/mime/file_extension.php Normal file
View File

@ -0,0 +1,105 @@
<?php declare(strict_types=1);
class FileExtension
{
public const ANI = 'ani';
public const ASC = 'asc';
public const ASF = 'asf';
public const ASX = 'asx';
public const AVI = 'avi';
public const BMP = 'bmp';
public const BZIP = 'bz';
public const BZIP2 = 'bz2';
public const CBR = 'cbr';
public const CBZ = 'cbz';
public const CBT = 'cbt';
public const CBA = 'cbA';
public const CB7 = 'cb7';
public const CSS = 'css';
public const CSV = 'csv';
public const CUR = 'cur';
public const FLASH = 'swf';
public const FLASH_VIDEO = 'flv';
public const GIF = 'gif';
public const GZIP = 'gz';
public const HTML = 'html';
public const HTM = 'htm';
public const ICO = 'ico';
public const JFIF = 'jfif';
public const JFI = 'jfi';
public const JPEG = 'jpeg';
public const JPG = 'jpg';
public const JS = 'js';
public const JSON = 'json';
public const MKV = 'mkv';
public const MP3 = 'mp3';
public const MP4 = 'mp4';
public const M4V = 'm4v';
public const M4A = 'm4a';
public const MPEG = 'mpeg';
public const MPG = 'mpg';
public const OGG = 'ogg';
public const OGG_VIDEO = 'ogv';
public const OGG_AUDIO = 'oga';
public const PDF = 'pdf';
public const PHP = 'php';
public const PHP5 = 'php5';
public const PNG = 'png';
public const PSD = 'psd';
public const MOV = 'mov';
public const RSS = 'rss';
public const SVG = 'svg';
public const TAR = 'tar';
public const TEXT = 'txt';
public const TIFF = 'tiff';
public const TIF = 'tif';
public const WAV = 'wav';
public const WEBM = 'webm';
public const WEBP = 'webp';
public const WMA = 'wma';
public const WMV = 'wmv';
public const XML = 'xml';
public const XSL = 'xsl';
public const ZIP = 'zip';
/**
* Returns the main file extension associated with the specified mimetype.
*/
public static function get_for_mime(string $mime): ?string
{
if (empty($mime)) {
return null;
}
if ($mime==MimeType::OCTET_STREAM) {
return null;
}
$data = MimeMap::get_for_mime($mime);
if ($data!=null) {
return $data[MimeMap::MAP_EXT][0];
}
return null;
}
/**
* Returns all the file extension associated with the specified mimetype.
*/
public static function get_all_for_mime(string $mime): array
{
if (empty($mime)) {
return [];
}
if ($mime==MimeType::OCTET_STREAM) {
return [];
}
$data = MimeMap::get_for_mime($mime);
if ($data!=null) {
return $data[MimeMap::MAP_EXT];
}
return [];
}
}

14
ext/mime/info.php Normal file
View File

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
class MimeSystemInfo extends ExtensionInfo
{
public const KEY = "mime";
public $key = self::KEY;
public $name = "MIME";
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public $license = self::LICENSE_WTFPL;
public $description = "Provides system mime-related functionality";
public $core = true;
public $visibility = self::VISIBLE_HIDDEN;
}

85
ext/mime/main.php Normal file
View File

@ -0,0 +1,85 @@
<?php declare(strict_types=1);
require_once "mime_map.php";
require_once "file_extension.php";
require_once "mime_type.php";
class MimeSystem extends Extension
{
/** @var MimeSystemTheme */
protected $theme;
const VERSION = "ext_mime_version";
public function onParseLinkTemplate(ParseLinkTemplateEvent $event)
{
$event->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]));
}
}
}

256
ext/mime/mime_map.php Normal file
View File

@ -0,0 +1,256 @@
<?php declare(strict_types=1);
class MimeMap
{
public const MAP_NAME = 'name';
public const MAP_EXT = 'ext';
public const MAP_MIME = 'mime';
// Mime type map. Each entry in the self::ARRAY represents a kind of file, identified by the "correct" mimetype as the key.
// The value for each entry is a map of three keys, ext, mime, and name.
// ext's value is an array of all of the extensions that the file type can use, with the "correct" one being first.
// mime's value is an array of all mime types that the file type is known to use, with the current "correct" one being first.
// name's value is a human-readable name for the file format.
private const MAP = [
MimeType::ANI => [
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;
}
}

254
ext/mime/mime_type.php Normal file
View File

@ -0,0 +1,254 @@
<?php declare(strict_types=1);
require_once "file_extension.php";
class MimeType
{
// Couldn't find a mimetype for ani, so made one up based on it being a riff container
public const ANI = 'application/riff+ani';
public const ASF = 'video/x-ms-asf';
public const AVI = 'video/x-msvideo';
// Went with mime types from http://fileformats.archiveteam.org/wiki/Comic_Book_Archive
public const COMIC_ZIP = 'application/vnd.comicbook+zip';
public const COMIC_RAR = 'application/vnd.comicbook-rar';
public const BMP = 'image/x-ms-bmp';
public const BZIP = 'application/x-bzip';
public const BZIP2 = 'application/x-bzip2';
public const CSS = 'text/css';
public const CSV = 'text/csv';
public const FLASH = 'application/x-shockwave-flash';
public const FLASH_VIDEO = 'video/x-flv';
public const GIF = 'image/gif';
public const GZIP = 'application/x-gzip';
public const HTML = 'text/html';
public const ICO = 'image/x-icon';
public const JPEG = 'image/jpeg';
public const JS = 'text/javascript';
public const JSON = 'application/json';
public const MKV = 'video/x-matroska';
public const MP3 = 'audio/mpeg';
public const MP4_AUDIO = 'audio/mp4';
public const MP4_VIDEO = 'video/mp4';
public const MPEG = 'video/mpeg';
public const OCTET_STREAM = 'application/octet-stream';
public const OGG = 'application/ogg';
public const OGG_VIDEO = 'video/ogg';
public const OGG_AUDIO = 'audio/ogg';
public const PDF = 'application/pdf';
public const PHP = 'text/x-php';
public const PNG = 'image/png';
public const PSD = 'image/vnd.adobe.photoshop';
public const QUICKTIME = 'video/quicktime';
public const RSS = 'application/rss+xml';
public const SVG = 'image/svg+xml';
public const TAR = 'application/x-tar';
public const TEXT = 'text/plain';
public const TIFF = 'image/tiff';
public const WAV = 'audio/x-wav';
public const WEBM = 'video/webm';
public const WEBP = 'image/webp';
public const WEBP_LOSSLESS = self::WEBP."; ".self::LOSSLESS_PARAMETER;
public const WIN_BITMAP = 'image/x-win-bitmap';
public const WMA = 'audio/x-ms-wma';
public const WMV = 'video/x-ms-wmv';
public const XML = 'text/xml';
public const XML_APPLICATION = 'application/xml';
public const XSL = 'application/xsl+xml';
public const ZIP = 'application/zip';
public const LOSSLESS_PARAMETER = "lossless=true";
public const CHARSET_UTF8 = "charset=utf-8";
//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];
private const REGEX_MIME_TYPE = "/^([-\w.]+)\/([-\w.]+)(;.+)?$/";
public static function is_mime(string $value): bool
{
return preg_match(self::REGEX_MIME_TYPE, $value)===1;
}
public static function add_parameters(String $mime, String...$parameters): string
{
if (empty($parameters)) {
return $mime;
}
return $mime."; ".join("; ", $parameters);
}
public static function remove_parameters(string $mime): string
{
$i = strpos($mime, ";");
if ($i!==false) {
return substr($mime, 0, $i);
}
return $mime;
}
public static function matches_array(string $mime, array $mime_array, bool $exact = false): bool
{
// If there's an exact match, find it and that's it
if (in_array($mime, $mime_array)) {
return true;
}
if ($exact) {
return false;
}
$mime = self::remove_parameters($mime);
return in_array($mime, $mime_array);
}
public static function matches(string $mime1, string $mime2, bool $exact = false): bool
{
if (!$exact) {
$mime1 = self::remove_parameters($mime1);
$mime2 = self::remove_parameters($mime2);
}
return strtolower($mime1)===strtolower($mime2);
}
/**
* 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
{
$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);
}
/**
* Returns the mimetype that matches the provided extension.
*/
public static function get_for_extension(string $ext): ?string
{
$data = MimeMap::get_for_extension($ext);
if ($data!=null) {
return $data[MimeMap::MAP_MIME][0];
}
// This was an old solution for differentiating lossless webps
if ($ext==="webp-lossless") {
return MimeType::WEBP_LOSSLESS;
}
return null;
}
/**
* Returns the mimetype for the specified file via file inspection
* @param String $file
* @return String The mimetype that was found. Returns generic octet binary mimetype if not found.
*/
public static function get_for_file(string $file, ?string $ext = null): string
{
if (!file_exists($file)) {
throw new SCoreException("File not found: ".$file);
}
$output = self::OCTET_STREAM;
$type = false;
$finfo = finfo_open(FILEINFO_MIME_TYPE);
try {
$type = finfo_file($finfo, $file);
} finally {
finfo_close($finfo);
}
if ($type !== false && !empty($type)) {
$output = $type;
}
if (!empty($ext)) {
// Here we handle the few file types that need extension-based handling
$ext = strtolower($ext);
if ($type===MimeType::ZIP && $ext===FileExtension::CBZ) {
$output = MimeType::COMIC_ZIP;
}
if ($type===MimeType::OCTET_STREAM) {
switch ($ext) {
case FileExtension::ANI:
$output = MimeType::ANI;
break;
// TODO: There is no uniquely defined Mime type for the cursor format. Need to figure this out.
// case FileExtension::CUR:
// $output = MimeType::CUR;
// break;
}
}
}
// TODO: Implement manual byte inspections for supported esoteric formats, like ANI
return $output;
}
}

9
ext/mime/test.php Normal file
View File

@ -0,0 +1,9 @@
<?php declare(strict_types=1);
class MimeSystemTest extends ShimmiePHPUnitTestCase
{
public function testJPEG()
{
$result = MimeType::get_for_file("tests/bedroom_workshop.jpg");
$this->assertEquals($result, MimeType::JPEG);
}
}

41
ext/mime/theme.php Normal file
View File

@ -0,0 +1,41 @@
<?php declare(strict_types=1);
class MimeSystemTheme extends Themelet
{
public function get_help_html()
{
$mimes = DataHandlerExtension::get_all_supported_mimes();
sort($mimes);
$exts = [];
foreach ($mimes as $mime) {
$exts[] = FileExtension::get_for_mime($mime);
}
$mimes = join("</li><li>", $mimes);
sort($exts);
$exts = join("</li><li>", $exts);
return '<p>Search for images by extension</p>
<div class="command_example">
<pre>ext=jpg</pre>
<p>Returns images with the extension "jpg".</p>
</div>
These extensions are available in the system:
<ul><li>'.$exts.'</li></ul>
<hr/>
<p>Search for images by MIME type</p>
<div class="command_example">
<pre>mime=image/jpeg</pre>
<p>Returns images that have the MIME type "image/jpeg".</p>
</div>
These MIME types are available in the system:
<ul><li>'.$mimes.'</li></ul>
';
}
}

View File

@ -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();

View File

@ -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())));
}
}

View File

@ -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));
}
}

View File

@ -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);

View File

@ -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, "<option value='".$result["ext"]."'>".$result["ext"]." (".$result["count"].")</option>");
array_push($mimes, "<option value='".$result["mime"]."'>".$result["mime"]." (".$result["count"].")</option>");
}
$html = "
@ -48,10 +48,10 @@ class RegenThumbTheme extends Themelet
<table class='form'>
<tr><th><label for='regen_thumb_force'>Force</label></th><td><input type='checkbox' name='regen_thumb_force' id='regen_thumb_force' value='true' /></td></tr>
<tr><th><label for='regen_thumb_limit'>Limit</label></th><td><input type='number' name='regen_thumb_limit' id='regen_thumb_limit' value='1000' /></td></tr>
<tr><th><label for='regen_thumb_type'>Type</label></th><td>
<select name='regen_thumb_type' id='regen_thumb_type'>
<tr><th><label for='regen_thumb_mime'>MIME</label></th><td>
<select name='regen_thumb_mime' id='regen_thumb_mime'>
<option value=''>All</option>
".implode($types)."
".implode($mimes)."
</select>
</td></tr>
<tr><td colspan='2'><input type='submit' value='Regenerate Thumbnails'></td></tr>
@ -59,10 +59,10 @@ class RegenThumbTheme extends Themelet
</form></p>
<p>".make_form(make_link("admin/delete_thumbs"), "POST", false, "", "return confirm('Are you sure you want to delete all thumbnails?')")."
<table class='form'>
<tr><th><label for='delete_thumb_type'>Type</label></th><td>
<select name='delete_thumb_type' id='delete_thumb_type'>
<tr><th><label for='delete_thumb_mime'>MIME</label></th><td>
<select name='delete_thumb_mime' id='delete_thumb_mime'>
<option value=''>All</option>
".implode($types)."
".implode($mimes)."
</select>
</td></tr>
<tr><td colspan='2'><input type='submit' value='Delete Thumbnails'></td></tr>

View File

@ -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);

View File

@ -1,5 +1,7 @@
<?php declare(strict_types=1);
// TODO Add warning that rotate doesn't support lossless webp output
/**
* This class is just a wrapper around SCoreException.
*/
@ -15,7 +17,7 @@ class RotateImage extends Extension
/** @var RotateImageTheme */
protected $theme;
const SUPPORTED_MIME = [MIME_TYPE_JPEG, MIME_TYPE_PNG, MIME_TYPE_GIF, MIME_TYPE_WEBP];
const SUPPORTED_MIME = [MimeType::JPEG, MimeType::PNG, MimeType::GIF, MimeType::WEBP];
public function onInitExt(InitExtEvent $event)
{
@ -28,7 +30,7 @@ class RotateImage extends Extension
{
global $user, $config;
if ($user->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);

View File

@ -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

View File

@ -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");
}

View File

@ -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) {

View File

@ -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");
}
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}

View File

@ -30,7 +30,7 @@ class TaggerXML extends Extension
"</tags>";
$page->set_mode(PageMode::DATA);
$page->set_type(MIME_TYPE_XML);
$page->set_mime(MimeType::XML);
$page->set_data($xml);
}
}

View File

@ -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";

View File

@ -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);

View File

@ -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?');
}
}
}
}

View File

@ -27,7 +27,7 @@ class TranscodeImageTheme extends Themelet
public function get_transcode_picker_html(array $options)
{
$html = "<select id='transcode_format' name='transcode_format' required='required' >";
$html = "<select id='transcode_mime' name='transcode_mime' required='required' >";
foreach ($options as $display=>$value) {
$html .= "<option value='$value'>$display</option>";
}

View File

@ -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;

View File

@ -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

View File

@ -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("<i>PHP Limit = " . ini_get('upload_max_filesize') . "</i>");
$sb->add_choice_option("transload_engine", $tes, "<br/>Transload: ");
$sb->add_bool_option("upload_tlsource", "<br/>Use transloaded URL as source if none is provided: ");
$sb->add_bool_option("upload_use_mime", "<br/>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" : "";

View File

@ -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());
}
}

View File

@ -17,7 +17,7 @@ class CustomViewImageTheme extends ViewImageTheme
$h_owner = html_escape($image->get_owner()->name);
$h_ownerlink = "<a href='".make_link("user/$h_owner")."'>$h_owner</a>";
$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);

View File

@ -16,7 +16,7 @@ class CustomViewImageTheme extends ViewImageTheme
$h_owner = html_escape($image->get_owner()->name);
$h_ownerlink = "<a href='".make_link("user/$h_owner")."'>$h_owner</a>";
$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);

View File

@ -18,7 +18,7 @@ class CustomViewImageTheme extends ViewImageTheme
$h_owner = html_escape($image->get_owner()->name);
$h_ownerlink = "<a href='".make_link("user/$h_owner")."'>$h_owner</a>";
$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);

View File

@ -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);