Merge from sanmadjack:develop
This commit is contained in:
commit
d1102cd635
33 changed files with 2032 additions and 430 deletions
|
@ -27,7 +27,7 @@
|
|||
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
<FilesMatch "([0-9a-f]{32}|\.(gif|jpe?g|png|css|js))$">
|
||||
<FilesMatch "([0-9a-f]{32}|\.(gif|jpe?g|png|webp|css|js))$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "public, max-age=2629743"
|
||||
</IfModule>
|
||||
|
@ -46,6 +46,7 @@
|
|||
AddType image/jpeg jpg jpeg
|
||||
AddType image/gif gif
|
||||
AddType image/png png
|
||||
AddType image/webp webp
|
||||
|
||||
#EXT: handle_ico
|
||||
AddType image/x-icon ico ani cur
|
||||
|
|
|
@ -35,3 +35,22 @@ class ImageDoesNotExist extends SCoreException
|
|||
class InvalidInput extends SCoreException
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* This is used by the image resizing code when there is not enough memory to perform a resize.
|
||||
*/
|
||||
class InsufficientMemoryException extends SCoreException
|
||||
{
|
||||
}
|
||||
/*
|
||||
* This is used by the image resizing code when there is an error while resizing
|
||||
*/
|
||||
class ImageResizeException extends SCoreException
|
||||
{
|
||||
public $error;
|
||||
|
||||
public function __construct(string $error)
|
||||
{
|
||||
$this->error = $error;
|
||||
}
|
||||
}
|
|
@ -219,13 +219,21 @@ abstract class DataHandlerExtension extends Extension
|
|||
|
||||
public function onThumbnailGeneration(ThumbnailGenerationEvent $event)
|
||||
{
|
||||
$result = false;
|
||||
if ($this->supported_ext($event->type)) {
|
||||
if (method_exists($this, 'create_thumb_force') && $event->force == true) {
|
||||
$this->create_thumb_force($event->hash);
|
||||
if($event->force) {
|
||||
$result = $this->create_thumb($event->hash);
|
||||
} else {
|
||||
$this->create_thumb($event->hash);
|
||||
$outname = warehouse_path("thumbs", $event->hash);
|
||||
if(file_exists($outname)) {
|
||||
return;
|
||||
}
|
||||
$result = $this->create_thumb($event->hash);
|
||||
}
|
||||
}
|
||||
if($result) {
|
||||
$event->generated = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
|
|
|
@ -99,6 +99,10 @@ class ThumbnailGenerationEvent extends Event
|
|||
/** @var bool */
|
||||
public $force;
|
||||
|
||||
/** @var bool */
|
||||
public $generated;
|
||||
|
||||
|
||||
/**
|
||||
* Request a thumbnail be made for an image object
|
||||
*/
|
||||
|
@ -107,6 +111,7 @@ class ThumbnailGenerationEvent extends Event
|
|||
$this->hash = $hash;
|
||||
$this->type = $type;
|
||||
$this->force = $force;
|
||||
$this->generated = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -145,6 +145,51 @@ class Image
|
|||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for an array of image IDs
|
||||
*
|
||||
* #param string[] $tags
|
||||
* #return int[]
|
||||
*/
|
||||
public static function find_image_ids(int $start, int $limit, array $tags=[]): array
|
||||
{
|
||||
global $database, $user, $config;
|
||||
|
||||
$images = [];
|
||||
|
||||
if ($start < 0) {
|
||||
$start = 0;
|
||||
}
|
||||
if ($limit < 1) {
|
||||
$limit = 1;
|
||||
}
|
||||
|
||||
if (SPEED_HAX) {
|
||||
if (!$user->can("big_search") and count($tags) > 3) {
|
||||
throw new SCoreException("Anonymous users may only search for up to 3 tags at a time");
|
||||
}
|
||||
}
|
||||
|
||||
$result = null;
|
||||
if (SEARCH_ACCEL) {
|
||||
$result = Image::get_accelerated_result($tags, $start, $limit);
|
||||
}
|
||||
|
||||
if (!$result) {
|
||||
$querylet = Image::build_search_querylet($tags);
|
||||
$querylet->append(new Querylet(" ORDER BY ".(Image::$order_sql ?: "images.".$config->get_string("index_order"))));
|
||||
$querylet->append(new Querylet(" LIMIT :limit OFFSET :offset", ["limit"=>$limit, "offset"=>$start]));
|
||||
#var_dump($querylet->sql); var_dump($querylet->variables);
|
||||
$result = $database->execute($querylet->sql, $querylet->variables);
|
||||
}
|
||||
|
||||
while ($row = $result->fetch()) {
|
||||
$images[] = $row["id"];
|
||||
}
|
||||
Image::$order_sql = null;
|
||||
return $images;
|
||||
}
|
||||
|
||||
/*
|
||||
* Accelerator stuff
|
||||
*/
|
||||
|
@ -385,12 +430,22 @@ class Image
|
|||
return $this->get_link('image_ilink', '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.$ext');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the nicely formatted version of the file name
|
||||
*/
|
||||
public function get_nice_image_name(): string
|
||||
{
|
||||
return $this->parse_link_template('$id - $tags.$ext');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the thumbnail
|
||||
*/
|
||||
public function get_thumb_link(): string
|
||||
{
|
||||
return $this->get_link('image_tlink', '_thumbs/$hash/thumb.jpg', 'thumb/$id.jpg');
|
||||
global $config;
|
||||
$ext = $config->get_string("thumb_type");
|
||||
return $this->get_link('image_tlink', '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -53,21 +53,34 @@ function add_image(string $tmpname, string $filename, string $tags): void
|
|||
assert(file_exists($tmpname));
|
||||
|
||||
$pathinfo = pathinfo($filename);
|
||||
if (!array_key_exists('extension', $pathinfo)) {
|
||||
throw new UploadException("File has no extension");
|
||||
}
|
||||
$metadata = [];
|
||||
$metadata['filename'] = $pathinfo['basename'];
|
||||
$metadata['extension'] = $pathinfo['extension'];
|
||||
if (array_key_exists('extension', $pathinfo)) {
|
||||
$metadata['extension'] = $pathinfo['extension'];
|
||||
}
|
||||
|
||||
$metadata['tags'] = Tag::explode($tags);
|
||||
$metadata['source'] = null;
|
||||
$event = new DataUploadEvent($tmpname, $metadata);
|
||||
send_event($event);
|
||||
if ($event->image_id == -1) {
|
||||
throw new UploadException("File type not recognised");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function get_extension_from_mime(String $file_path): ?String
|
||||
{
|
||||
global $config;
|
||||
$mime = mime_content_type($file_path);
|
||||
if(!empty($mime)) {
|
||||
$ext = get_extension($mime);
|
||||
if(!empty($ext)) {
|
||||
return $ext;
|
||||
}
|
||||
throw new UploadException("Could not determine extension for mimetype ".$mime);
|
||||
}
|
||||
throw new UploadException("Could not determine file mime type: ".$file_path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a full size pair of dimensions, return a pair scaled down to fit
|
||||
* into the configured thumbnail square, with ratio intact
|
||||
|
@ -105,3 +118,344 @@ function get_thumbnail_size(int $orig_width, int $orig_height): array
|
|||
return [(int)($orig_width*$scale), (int)($orig_height*$scale)];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a full size pair of dimensions, return a pair scaled down to fit
|
||||
* into the configured thumbnail square, with ratio intact, using thumb_scaling
|
||||
*
|
||||
* #return int[]
|
||||
*/
|
||||
function get_thumbnail_size_scaled(int $orig_width, int $orig_height): array
|
||||
{
|
||||
global $config;
|
||||
|
||||
if ($orig_width === 0) {
|
||||
$orig_width = 192;
|
||||
}
|
||||
if ($orig_height === 0) {
|
||||
$orig_height = 192;
|
||||
}
|
||||
|
||||
if ($orig_width > $orig_height * 5) {
|
||||
$orig_width = $orig_height * 5;
|
||||
}
|
||||
if ($orig_height > $orig_width * 5) {
|
||||
$orig_height = $orig_width * 5;
|
||||
}
|
||||
|
||||
$max_size = get_thumbnail_max_size_scaled();
|
||||
$max_width = $max_size[0];
|
||||
$max_height = $max_size[1];
|
||||
|
||||
$xscale = ($max_height / $orig_height);
|
||||
$yscale = ($max_width / $orig_width);
|
||||
$scale = ($xscale < $yscale) ? $xscale : $yscale;
|
||||
|
||||
if ($scale > 1 && $config->get_bool('thumb_upscale')) {
|
||||
return [(int)$orig_width, (int)$orig_height];
|
||||
} else {
|
||||
return [(int)($orig_width*$scale), (int)($orig_height*$scale)];
|
||||
}
|
||||
}
|
||||
|
||||
function get_thumbnail_max_size_scaled(): array
|
||||
{
|
||||
global $config;
|
||||
|
||||
$scaling = $config->get_int("thumb_scaling");
|
||||
$max_width = $config->get_int('thumb_width') * ($scaling/100);
|
||||
$max_height = $config->get_int('thumb_height') * ($scaling/100);
|
||||
return [$max_width, $max_height];
|
||||
}
|
||||
|
||||
function create_thumbnail_convert($hash): bool
|
||||
{
|
||||
global $config;
|
||||
|
||||
$inname = warehouse_path("images", $hash);
|
||||
$outname = warehouse_path("thumbs", $hash);
|
||||
|
||||
$q = $config->get_int("thumb_quality");
|
||||
$convert = $config->get_string("thumb_convert_path");
|
||||
|
||||
if($convert==null||$convert=="")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// ffff imagemagick fails sometimes, not sure why
|
||||
//$format = "'%s' '%s[0]' -format '%%[fx:w] %%[fx:h]' info:";
|
||||
//$cmd = sprintf($format, $convert, $inname);
|
||||
//$size = shell_exec($cmd);
|
||||
//$size = explode(" ", trim($size));
|
||||
$tsize = get_thumbnail_max_size_scaled();
|
||||
$w = $tsize[0];
|
||||
$h = $tsize[1];
|
||||
|
||||
|
||||
// running the call with cmd.exe requires quoting for our paths
|
||||
$type = $config->get_string('thumb_type');
|
||||
|
||||
$options = "";
|
||||
if (!$config->get_bool('thumb_upscale')) {
|
||||
$options .= "\>";
|
||||
}
|
||||
|
||||
$bg = "black";
|
||||
if($type=="webp") {
|
||||
$bg = "none";
|
||||
}
|
||||
$format = '"%s" -flatten -strip -thumbnail %ux%u%s -quality %u -background %s "%s[0]" %s:"%s"';
|
||||
|
||||
$cmd = sprintf($format, $convert, $w, $h, $options, $q, $bg, $inname, $type, $outname);
|
||||
$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);
|
||||
|
||||
log_debug('handle_pixel', "Generating thumbnail with command `$cmd`, returns $ret");
|
||||
|
||||
if ($config->get_bool("thumb_optim", false)) {
|
||||
exec("jpegoptim $outname", $output, $ret);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function create_thumbnail_ffmpeg($hash): bool
|
||||
{
|
||||
global $config;
|
||||
|
||||
$ffmpeg = $config->get_string("thumb_ffmpeg_path");
|
||||
if($ffmpeg==null||$ffmpeg=="") {
|
||||
return false;
|
||||
}
|
||||
|
||||
$inname = warehouse_path("images", $hash);
|
||||
$outname = warehouse_path("thumbs", $hash);
|
||||
|
||||
$orig_size = video_size($inname);
|
||||
$scaled_size = get_thumbnail_size_scaled($orig_size[0], $orig_size[1]);
|
||||
|
||||
$codec = "mjpeg";
|
||||
$quality = $config->get_int("thumb_quality");
|
||||
if($config->get_string("thumb_type")=="webp") {
|
||||
$codec = "libwebp";
|
||||
} else {
|
||||
// mjpeg quality ranges from 2-31, with 2 being the best quality.
|
||||
$quality = floor(31 - (31 * ($quality/100)));
|
||||
if($quality<2) {
|
||||
$quality = 2;
|
||||
}
|
||||
}
|
||||
|
||||
$args = [
|
||||
escapeshellarg($ffmpeg),
|
||||
"-y", "-i", escapeshellarg($inname),
|
||||
"-vf", "thumbnail,scale={$scaled_size[0]}:{$scaled_size[1]}",
|
||||
"-f", "image2",
|
||||
"-vframes", "1",
|
||||
"-c:v", $codec,
|
||||
"-q:v", $quality,
|
||||
escapeshellarg($outname),
|
||||
];
|
||||
|
||||
$cmd = escapeshellcmd(implode(" ", $args));
|
||||
|
||||
exec($cmd, $output, $ret);
|
||||
|
||||
if ((int)$ret == (int)0) {
|
||||
log_debug('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret");
|
||||
return true;
|
||||
} else {
|
||||
log_error('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function video_size(string $filename): array
|
||||
{
|
||||
global $config;
|
||||
$ffmpeg = $config->get_string("thumb_ffmpeg_path");
|
||||
$cmd = escapeshellcmd(implode(" ", [
|
||||
escapeshellarg($ffmpeg),
|
||||
"-y", "-i", escapeshellarg($filename),
|
||||
"-vstats"
|
||||
]));
|
||||
$output = shell_exec($cmd . " 2>&1");
|
||||
// error_log("Getting size with `$cmd`");
|
||||
|
||||
$regex_sizes = "/Video: .* ([0-9]{1,4})x([0-9]{1,4})/";
|
||||
if (preg_match($regex_sizes, $output, $regs)) {
|
||||
if (preg_match("/displaymatrix: rotation of (90|270).00 degrees/", $output)) {
|
||||
$size = [$regs[2], $regs[1]];
|
||||
} else {
|
||||
$size = [$regs[1], $regs[2]];
|
||||
}
|
||||
} else {
|
||||
$size = [1, 1];
|
||||
}
|
||||
log_debug('imageboard/misc', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]");
|
||||
return $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Memory usage limits
|
||||
*
|
||||
* Old check: $memory_use = (filesize($image_filename)*2) + ($width*$height*4) + (4*1024*1024);
|
||||
* New check: $memory_use = $width * $height * ($bits_per_channel) * channels * 2.5
|
||||
*
|
||||
* It didn't make sense to compute the memory usage based on the NEW size for the image. ($width*$height*4)
|
||||
* We need to consider the size that we are GOING TO instead.
|
||||
*
|
||||
* The factor of 2.5 is simply a rough guideline.
|
||||
* http://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize
|
||||
*/
|
||||
function calc_memory_use(array $info): int
|
||||
{
|
||||
if (isset($info['bits']) && isset($info['channels'])) {
|
||||
$memory_use = ($info[0] * $info[1] * ($info['bits'] / 8) * $info['channels'] * 2.5) / 1024;
|
||||
} else {
|
||||
// If we don't have bits and channel info from the image then assume default values
|
||||
// of 8 bits per color and 4 channels (R,G,B,A) -- ie: regular 24-bit color
|
||||
$memory_use = ($info[0] * $info[1] * 1 * 4 * 2.5) / 1024;
|
||||
}
|
||||
return (int)$memory_use;
|
||||
}
|
||||
|
||||
function image_resize_gd(String $image_filename, array $info, int $new_width, int $new_height,
|
||||
string $output_filename=null, string $output_type=null, int $output_quality = 80)
|
||||
{
|
||||
$width = $info[0];
|
||||
$height = $info[1];
|
||||
|
||||
if($output_type==null) {
|
||||
/* If not specified, output to the same format as the original image */
|
||||
switch ($info[2]) {
|
||||
case IMAGETYPE_GIF: $output_type = "gif"; break;
|
||||
case IMAGETYPE_JPEG: $output_type = "jpeg"; break;
|
||||
case IMAGETYPE_PNG: $output_type = "png"; break;
|
||||
case IMAGETYPE_WEBP: $output_type = "webp"; break;
|
||||
case IMAGETYPE_BMP: $output_type = "bmp"; break;
|
||||
default: throw new ImageResizeException("Failed to save the new image - Unsupported image type.");
|
||||
}
|
||||
}
|
||||
|
||||
$memory_use = calc_memory_use($info);
|
||||
$memory_limit = get_memory_limit();
|
||||
if ($memory_use > $memory_limit) {
|
||||
throw new InsufficientMemoryException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)");
|
||||
}
|
||||
|
||||
$image = imagecreatefromstring(file_get_contents($image_filename));
|
||||
$image_resized = imagecreatetruecolor($new_width, $new_height);
|
||||
try {
|
||||
if($image===false) {
|
||||
throw new ImageResizeException("Could not load image: ".$image_filename);
|
||||
}
|
||||
if($image_resized===false) {
|
||||
throw new ImageResizeException("Could not create output image with dimensions $new_width c $new_height ");
|
||||
}
|
||||
|
||||
// Handle transparent images
|
||||
switch($info[2]) {
|
||||
case IMAGETYPE_GIF:
|
||||
$transparency = imagecolortransparent($image);
|
||||
$palletsize = imagecolorstotal($image);
|
||||
|
||||
// If we have a specific transparent color
|
||||
if ($transparency >= 0 && $transparency < $palletsize) {
|
||||
// Get the original image's transparent color's RGB values
|
||||
$transparent_color = imagecolorsforindex($image, $transparency);
|
||||
|
||||
// Allocate the same color in the new image resource
|
||||
$transparency = imagecolorallocate($image_resized, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
|
||||
if($transparency===false) {
|
||||
throw new ImageResizeException("Unable to allocate transparent color");
|
||||
}
|
||||
|
||||
// Completely fill the background of the new image with allocated color.
|
||||
if(imagefill($image_resized, 0, 0, $transparency)===false) {
|
||||
throw new ImageResizeException("Unable to fill new image with transparent color");
|
||||
}
|
||||
|
||||
// Set the background color for new image to transparent
|
||||
imagecolortransparent($image_resized, $transparency);
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
case IMAGETYPE_WEBP:
|
||||
//
|
||||
// More info here: http://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php
|
||||
//
|
||||
if(imagealphablending($image_resized, false)===false) {
|
||||
throw new ImageResizeException("Unable to disable image alpha blending");
|
||||
}
|
||||
if(imagesavealpha($image_resized, true)===false) {
|
||||
throw new ImageResizeException("Unable to enable image save alpha");
|
||||
}
|
||||
$transparent_color = imagecolorallocatealpha($image_resized, 255, 255, 255, 127);
|
||||
if($transparent_color===false) {
|
||||
throw new ImageResizeException("Unable to allocate transparent color");
|
||||
}
|
||||
if(imagefilledrectangle($image_resized, 0, 0, $new_width, $new_height, $transparent_color)===false) {
|
||||
throw new ImageResizeException("Unable to fill new image with transparent color");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Actually resize the image.
|
||||
if(imagecopyresampled(
|
||||
$image_resized,
|
||||
$image,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
$new_width,
|
||||
$new_height,
|
||||
$width,
|
||||
$height
|
||||
)===false) {
|
||||
throw new ImageResizeException("Unable to copy resized image data to new image");
|
||||
}
|
||||
|
||||
$result = false;
|
||||
switch($output_type) {
|
||||
case "bmp":
|
||||
$result = imagebmp($image_resized, $output_filename, true);
|
||||
break;
|
||||
case "webp":
|
||||
$result = imagewebp($image_resized, $output_filename, $output_quality);
|
||||
break;
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
$result = imagejpeg($image_resized, $output_filename, $output_quality);
|
||||
break;
|
||||
case "png":
|
||||
$result = imagepng($image_resized, $output_filename, 9);
|
||||
break;
|
||||
case "gif":
|
||||
$result = imagegif($image_resized, $output_filename);
|
||||
break;
|
||||
default:
|
||||
throw new ImageResizeException("Failed to save the new image - Unsupported image type: $output_type");
|
||||
}
|
||||
if($result==false) {
|
||||
throw new ImageResizeException("Failed to save the new image, function returned false when saving type: $output_type");
|
||||
}
|
||||
} finally {
|
||||
imagedestroy($image);
|
||||
imagedestroy($image_resized);
|
||||
}
|
||||
}
|
||||
|
||||
function is_animated_gif(String $image_filename) {
|
||||
$isanigif = 0;
|
||||
if (($fh = @fopen($image_filename, 'rb'))) {
|
||||
//check if gif is animated (via http://www.php.net/manual/en/function.imagecreatefromgif.php#104473)
|
||||
while (!feof($fh) && $isanigif < 2) {
|
||||
$chunk = fread($fh, 1024 * 100);
|
||||
$isanigif += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
|
||||
}
|
||||
}
|
||||
return ($isanigif == 0);
|
||||
}
|
|
@ -124,13 +124,13 @@ class AdminPage extends Extension
|
|||
}
|
||||
}
|
||||
|
||||
public function onPostListBuilding(PostListBuildingEvent $event)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("manage_admintools") && !empty($event->search_terms)) {
|
||||
$event->add_control($this->theme->dbq_html(Tag::implode($event->search_terms)));
|
||||
}
|
||||
}
|
||||
// public function onPostListBuilding(PostListBuildingEvent $event)
|
||||
// {
|
||||
// global $user;
|
||||
// if ($user->can("manage_admintools") && !empty($event->search_terms)) {
|
||||
// $event->add_control($this->theme->dbq_html(Tag::implode($event->search_terms)));
|
||||
// }
|
||||
// }
|
||||
|
||||
private function delete_by_query()
|
||||
{
|
||||
|
|
270
ext/bulk_actions/main.php
Normal file
270
ext/bulk_actions/main.php
Normal file
|
@ -0,0 +1,270 @@
|
|||
<?php
|
||||
/*
|
||||
* Name: Bulk Actions
|
||||
* Author: Matthew Barbour
|
||||
* License: WTFPL
|
||||
* Description: Provides query and selection-based bulk action support
|
||||
* Documentation: Provides bulk action section in list view. Allows performing actions against a set of images based on query or manual selection.
|
||||
* Based on Mass Tagger by Christian Walde <walde.christian@googlemail.com>, contributions by Shish and Agasa.
|
||||
*/
|
||||
|
||||
|
||||
class BulkActionBlockBuildingEvent extends Event
|
||||
{
|
||||
/** @var array */
|
||||
public $actions = array();
|
||||
|
||||
public function add_action(String $action, string $button_text, String $confirmation_message = "", String $block = "", int $position = 40)
|
||||
{
|
||||
if ($block == null)
|
||||
$block = "";
|
||||
|
||||
array_push(
|
||||
$this->actions,
|
||||
array(
|
||||
"block" => $block,
|
||||
"confirmation_message" => $confirmation_message,
|
||||
"action" => $action,
|
||||
"button_text" => $button_text,
|
||||
"position" => $position
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BulkActionEvent extends Event
|
||||
{
|
||||
/** @var string */
|
||||
public $action;
|
||||
/** @var array */
|
||||
public $items;
|
||||
/** @var PageRequestEvent */
|
||||
public $page_request;
|
||||
|
||||
function __construct(String $action, PageRequestEvent $pageRequestEvent, array $items)
|
||||
{
|
||||
$this->action = $action;
|
||||
$this->page_request = $pageRequestEvent;
|
||||
$this->items = $items;
|
||||
}
|
||||
}
|
||||
|
||||
class BulkActions extends Extension
|
||||
{
|
||||
public function onPostListBuilding(PostListBuildingEvent $event)
|
||||
{
|
||||
global $config, $page, $user;
|
||||
|
||||
if ($user->is_logged_in()) {
|
||||
$babbe = new BulkActionBlockBuildingEvent();
|
||||
send_event($babbe);
|
||||
|
||||
if (sizeof($babbe->actions) == 0)
|
||||
return;
|
||||
|
||||
usort($babbe->actions, array($this, "sort_blocks"));
|
||||
|
||||
$this->theme->display_selector($page, $babbe->actions, Tag::implode($event->search_terms));
|
||||
}
|
||||
}
|
||||
|
||||
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||
{
|
||||
global $user;
|
||||
|
||||
if ($user->can("delete_image")) {
|
||||
$event->add_action("bulk_delete","Delete", "Delete selected images?", "", 10);
|
||||
}
|
||||
|
||||
if ($user->can("bulk_edit_image_tag")) {
|
||||
$event->add_action("bulk_tag","Tag", "", $this->theme->render_tag_input(), 10);
|
||||
}
|
||||
|
||||
if ($user->can("bulk_edit_image_source")) {
|
||||
$event->add_action("bulk_source","Set Source", "", $this->theme->render_source_input(), 10);
|
||||
}
|
||||
}
|
||||
|
||||
public function onBulkAction(BulkActionEvent $event)
|
||||
{
|
||||
global $user;
|
||||
|
||||
switch ($event->action) {
|
||||
case "bulk_delete":
|
||||
if ($user->can("delete_image")) {
|
||||
$i = $this->delete_items($event->items);
|
||||
flash_message("Deleted $i items");
|
||||
}
|
||||
break;
|
||||
case "bulk_tag":
|
||||
if (!isset($_POST['bulk_tags'])) {
|
||||
return;
|
||||
}
|
||||
if ($user->can("bulk_edit_image_tag")) {
|
||||
$tags = $_POST['bulk_tags'];
|
||||
$replace = false;
|
||||
if (isset($_POST['bulk_tags_replace']) && $_POST['bulk_tags_replace'] == "true") {
|
||||
$replace = true;
|
||||
}
|
||||
|
||||
$i= $this->tag_items($event->items, $tags, $replace);
|
||||
flash_message("Tagged $i items");
|
||||
}
|
||||
break;
|
||||
case "bulk_source":
|
||||
if (!isset($_POST['bulk_source'])) {
|
||||
return;
|
||||
}
|
||||
if ($user->can("bulk_edit_image_source")) {
|
||||
$source = $_POST['bulk_source'];
|
||||
$i = $this->set_source($event->items, $source);
|
||||
flash_message("Set source for $i items");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $page, $user;
|
||||
if ($event->page_matches("bulk_action") && $user->is_admin()) {
|
||||
if (!isset($_POST['bulk_action'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$action = $_POST['bulk_action'];
|
||||
|
||||
$items = [];
|
||||
if (isset($_POST['bulk_selected_ids']) && $_POST['bulk_selected_ids'] != "") {
|
||||
$data = json_decode($_POST['bulk_selected_ids']);
|
||||
if (is_array($data)) {
|
||||
foreach ($data as $id) {
|
||||
if (is_numeric($id)) {
|
||||
array_push($items, int_escape($id));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
|
||||
$query = $_POST['bulk_query'];
|
||||
if ($query != null && $query != "") {
|
||||
$n = 0;
|
||||
$tags = Tag::explode($query);
|
||||
while (true) {
|
||||
$results = Image::find_image_ids($n, 100, $tags);
|
||||
if (count($results) == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
reset($results); // rewind to first element in array.
|
||||
$items = array_merge($items, $results);
|
||||
$n += count($results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sizeof($items) > 0) {
|
||||
reset($items); // rewind to first element in array.
|
||||
$newEvent = new BulkActionEvent($action, $event, $items);
|
||||
send_event($newEvent);
|
||||
}
|
||||
|
||||
|
||||
$page->set_mode("redirect");
|
||||
if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
$_SERVER['HTTP_REFERER'] = make_link();
|
||||
}
|
||||
$page->set_redirect($_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
}
|
||||
|
||||
private function sort_blocks($a, $b)
|
||||
{
|
||||
return $a["position"] - $b["position"];
|
||||
}
|
||||
|
||||
private function delete_items(array $items): int
|
||||
{
|
||||
$total = 0;
|
||||
foreach ($items as $id) {
|
||||
try {
|
||||
$image = Image::by_id($id);
|
||||
if($image==null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
send_event(new ImageDeletionEvent($image));
|
||||
$total++;
|
||||
} catch (Exception $e) {
|
||||
flash_message("Error while removing $id: " . $e->getMessage(), "error");
|
||||
}
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
private function tag_items(array $items, string $tags, bool $replace): int
|
||||
{
|
||||
$tags = Tag::explode($tags);
|
||||
|
||||
$pos_tag_array = [];
|
||||
$neg_tag_array = [];
|
||||
foreach ($tags as $new_tag) {
|
||||
if (strpos($new_tag, '-') === 0) {
|
||||
$neg_tag_array[] = substr($new_tag, 1);
|
||||
} else {
|
||||
$pos_tag_array[] = $new_tag;
|
||||
}
|
||||
}
|
||||
|
||||
$total = 0;
|
||||
if ($replace) {
|
||||
foreach ($items as $id) {
|
||||
$image = Image::by_id($id);
|
||||
if($image==null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
send_event(new TagSetEvent($image, $tags));
|
||||
$total++;
|
||||
}
|
||||
} else {
|
||||
foreach ($items as $id) {
|
||||
$image = Image::by_id($id);
|
||||
if($image==null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$img_tags = [];
|
||||
if (!empty($neg_tag_array)) {
|
||||
$img_tags = array_merge($pos_tag_array, $image->get_tag_array());
|
||||
$img_tags = array_diff($img_tags, $neg_tag_array);
|
||||
} else {
|
||||
$img_tags = array_merge($tags, $image->get_tag_array());
|
||||
}
|
||||
send_event(new TagSetEvent($image, $img_tags));
|
||||
$total++;
|
||||
}
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
private function set_source(array $items, String $source): int
|
||||
{
|
||||
$total = 0;
|
||||
foreach ($items as $id) {
|
||||
try {
|
||||
$image = Image::by_id($id);
|
||||
if($image==null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
send_event(new SourceSetEvent($image, $source));
|
||||
$total++;
|
||||
} catch (Exception $e) {
|
||||
flash_message("Error while setting source for $id: " . $e->getMessage(), "error");
|
||||
}
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
}
|
197
ext/bulk_actions/script.js
Normal file
197
ext/bulk_actions/script.js
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*jshint bitwise:true, curly:true, forin:false, noarg:true, noempty:true, nonew:true, undef:true, strict:false, browser:true, jquery:true */
|
||||
|
||||
var bulk_selector_active = false;
|
||||
var bulk_selector_initialized = false;
|
||||
var bulk_selector_valid = false;
|
||||
|
||||
function validate_selections(form, confirmationMessage) {
|
||||
var queryOnly = false;
|
||||
if(bulk_selector_active) {
|
||||
var data = get_selected_items();
|
||||
if(data.length==0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
var query = $(form).find('input[name="bulk_query"]').val();
|
||||
|
||||
if (query == null || query == "") {
|
||||
return false;
|
||||
} else {
|
||||
queryOnly = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(confirmationMessage!=null&&confirmationMessage!="") {
|
||||
return confirm(confirmationMessage);
|
||||
} else if(queryOnly) {
|
||||
var action = $(form).find('input[name="submit_button"]').val();
|
||||
|
||||
return confirm("Perform bulk action \"" + action + "\" on all images matching the current search?");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function activate_bulk_selector () {
|
||||
set_selected_items([]);
|
||||
if(!bulk_selector_initialized) {
|
||||
$("a.shm-thumb").each(
|
||||
function (index, block) {
|
||||
add_selector_button($(block));
|
||||
}
|
||||
);
|
||||
}
|
||||
$('#bulk_selector_controls').show();
|
||||
$('#bulk_selector_activate').hide();
|
||||
bulk_selector_active = true;
|
||||
bulk_selector_initialized = true;
|
||||
}
|
||||
|
||||
function deactivate_bulk_selector() {
|
||||
set_selected_items([]);
|
||||
$('#bulk_selector_controls').hide();
|
||||
$('#bulk_selector_activate').show();
|
||||
bulk_selector_active = false;
|
||||
}
|
||||
|
||||
function get_selected_items() {
|
||||
var data = $('#bulk_selected_ids').val();
|
||||
if(data==""||data==null) {
|
||||
data = [];
|
||||
} else {
|
||||
data = JSON.parse(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function set_selected_items(items) {
|
||||
$("a.shm-thumb").removeClass('selected');
|
||||
|
||||
$(items).each(
|
||||
function(index,item) {
|
||||
$('a.shm-thumb[data-post-id="' + item + '"]').addClass('selected');
|
||||
}
|
||||
);
|
||||
|
||||
$('input[name="bulk_selected_ids"]').val(JSON.stringify(items));
|
||||
}
|
||||
|
||||
function select_item(id) {
|
||||
var data = get_selected_items();
|
||||
if(!data.includes(id))
|
||||
data.push(id);
|
||||
set_selected_items(data);
|
||||
}
|
||||
|
||||
function deselect_item(id) {
|
||||
var data = get_selected_items();
|
||||
if(data.includes(id))
|
||||
data.splice(data.indexOf(id, 1));
|
||||
set_selected_items(data);
|
||||
}
|
||||
|
||||
function toggle_selection( id ) {
|
||||
var data = get_selected_items();
|
||||
console.log(id);
|
||||
if(data.includes(id)) {
|
||||
data.splice(data.indexOf(id),1);
|
||||
set_selected_items(data);
|
||||
return false;
|
||||
} else {
|
||||
data.push(id);
|
||||
set_selected_items(data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function select_all() {
|
||||
var items = [];
|
||||
$("a.shm-thumb").each(
|
||||
function ( index, block ) {
|
||||
block = $(block);
|
||||
var id = block.data("post-id");
|
||||
items.push(id);
|
||||
}
|
||||
);
|
||||
set_selected_items(items);
|
||||
}
|
||||
|
||||
function select_invert() {
|
||||
var currentItems = get_selected_items();
|
||||
var items = [];
|
||||
$("a.shm-thumb").each(
|
||||
function ( index, block ) {
|
||||
block = $(block);
|
||||
var id = block.data("post-id");
|
||||
if(!currentItems.includes(id)) {
|
||||
items.push(id);
|
||||
}
|
||||
}
|
||||
);
|
||||
set_selected_items(items);
|
||||
}
|
||||
|
||||
function select_none() {
|
||||
set_selected_items([]);
|
||||
}
|
||||
|
||||
function select_range(start, end) {
|
||||
var data = get_selected_items();
|
||||
var selecting = false;
|
||||
$("a.shm-thumb").each(
|
||||
function ( index, block ) {
|
||||
block = $(block);
|
||||
var id = block.data("post-id");
|
||||
if(id==start)
|
||||
selecting = true;
|
||||
|
||||
if(selecting) {
|
||||
if(!data.includes(id))
|
||||
data.push(id);
|
||||
}
|
||||
|
||||
if(id==end) {
|
||||
selecting = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
set_selected_items(data);
|
||||
}
|
||||
|
||||
var last_clicked_item;
|
||||
|
||||
function add_selector_button($block) {
|
||||
var c = function(e) {
|
||||
if(!bulk_selector_active)
|
||||
return true;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var id = $block.data("post-id");
|
||||
if(e.shiftKey) {
|
||||
if(last_clicked_item<id) {
|
||||
select_range(id, last_clicked_item);
|
||||
} else {
|
||||
select_range(last_clicked_item, id);
|
||||
}
|
||||
} else {
|
||||
last_clicked_item = id;
|
||||
toggle_selection(id);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
$block.find("A").click(c);
|
||||
$block.click(c); // sometimes the thumbs *is* the A
|
||||
}
|
||||
|
||||
|
||||
|
||||
$(function () {
|
||||
// Clear the selection, in case it was autocompleted by the browser.
|
||||
$('#bulk_selected_ids').val("");
|
||||
});
|
10
ext/bulk_actions/style.css
Normal file
10
ext/bulk_actions/style.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
.selected {
|
||||
outline: 3px solid blue;
|
||||
}
|
||||
|
||||
.bulk_action {
|
||||
margin-top: 8pt;
|
||||
}
|
||||
.bulk_selector_controls table td {
|
||||
width: 33%;
|
||||
}
|
62
ext/bulk_actions/theme.php
Normal file
62
ext/bulk_actions/theme.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
class BulkActionsTheme extends Themelet
|
||||
{
|
||||
|
||||
|
||||
public function display_selector(Page $page, $actions, $query)
|
||||
{
|
||||
global $user;
|
||||
|
||||
$body = "<input type='hidden' name='bulk_selected_ids' id='bulk_selected_ids' />
|
||||
<input id='bulk_selector_activate' type='button' onclick='activate_bulk_selector();' value='Activate Selector'/>
|
||||
<div id='bulk_selector_controls' style='display: none;'>
|
||||
<input id='bulk_selector_deactivate' type='button' onclick='deactivate_bulk_selector();' value='Deactivate Selector'/>
|
||||
Click on images to mark them.
|
||||
<br />
|
||||
<table><tr><td>
|
||||
<input id='bulk_selector_select_all' type='button'
|
||||
onclick='select_all();' value='All'/>
|
||||
</td><td>
|
||||
<input id='bulk_selector_select_invert' type='button'
|
||||
onclick='select_invert();' value='Invert'/>
|
||||
</td><td>
|
||||
<input id='bulk)selector_select_none' type='button'
|
||||
onclick='select_none();' value='Clear'/>
|
||||
</td></tr></table>
|
||||
";
|
||||
|
||||
$hasQuery = ($query != null && $query != "");
|
||||
|
||||
if ($hasQuery) {
|
||||
$body .= "</div>";
|
||||
}
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$body .= "<div class='bulk_action'>" . make_form(make_link("bulk_action"), "POST", False, "", "return validate_selections(this,'" . html_escape($action["confirmation_message"]) . "');") .
|
||||
"<input type='hidden' name='bulk_query' value='" . html_escape($query) . "'>" .
|
||||
"<input type='hidden' name='bulk_selected_ids' />" .
|
||||
"<input type='hidden' name='bulk_action' value='" . $action["action"] . "' />" .
|
||||
$action["block"] .
|
||||
"<input type='submit' name='submit_button' value='" . $action["button_text"] . "'/>" .
|
||||
"</form></div>";
|
||||
}
|
||||
|
||||
if (!$hasQuery) {
|
||||
$body .= "</div>";
|
||||
}
|
||||
$block = new Block("Bulk Actions", $body, "left", 30);
|
||||
$page->add_block($block);
|
||||
}
|
||||
|
||||
public function render_tag_input()
|
||||
{
|
||||
return "<label><input type='checkbox' style='width:13px;' name='bulk_tags_replace' value='true'/>Replace tags</label>" .
|
||||
"<input type='text' name='bulk_tags' required='required' placeholder='Enter tags here' />";
|
||||
}
|
||||
|
||||
public function render_source_input()
|
||||
{
|
||||
return "<input type='text' name='bulk_source' required='required' placeholder='Enter source here' />";
|
||||
}
|
||||
}
|
|
@ -244,8 +244,11 @@ class CronUploader extends Extension
|
|||
*/
|
||||
public function process_upload(int $upload_count = 0): bool
|
||||
{
|
||||
global $config;
|
||||
global $config, $database;
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
$output_subdir = date('Ymd-His', time())."/";
|
||||
$this->set_dir();
|
||||
$this->generate_image_queue();
|
||||
|
||||
|
@ -262,24 +265,50 @@ class CronUploader extends Extension
|
|||
}
|
||||
|
||||
// Randomize Images
|
||||
shuffle($this->image_queue);
|
||||
//shuffle($this->image_queue);
|
||||
|
||||
$merged = 0;
|
||||
$added = 0;
|
||||
$failed = 0;
|
||||
|
||||
$failedItems = [];
|
||||
|
||||
// Upload the file(s)
|
||||
for ($i = 0; $i < $upload_count && sizeof($this->image_queue)>0; $i++) {
|
||||
$img = array_pop($this->image_queue);
|
||||
|
||||
try {
|
||||
$this->add_image($img[0], $img[1], $img[2]);
|
||||
$this->move_uploaded($img[0], $img[1], false);
|
||||
$database->beginTransaction();
|
||||
$result = $this->add_image($img[0], $img[1], $img[2]);
|
||||
$database->commit();
|
||||
$this->move_uploaded($img[0], $img[1], $output_subdir, false);
|
||||
if($result==null) {
|
||||
$merged++;
|
||||
} else {
|
||||
$added++;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->move_uploaded($img[0], $img[1], true);
|
||||
$failed++;
|
||||
$this->move_uploaded($img[0], $img[1], $output_subdir, true);
|
||||
$msgNumber = $this->add_upload_info("(".gettype($e).") ".$e->getMessage());
|
||||
$msgNumber = $this->add_upload_info($e->getTraceAsString());
|
||||
if (strpos($e->getMessage(), 'SQLSTATE') !== false) {
|
||||
// Postgres invalidates the transaction if there is an SQL error,
|
||||
// so all subsequence transactions will fail.
|
||||
break;
|
||||
}
|
||||
try {
|
||||
$database->rollback();
|
||||
} catch (Exception $e) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$msgNumber = $this->add_upload_info("Items added: $added");
|
||||
$msgNumber = $this->add_upload_info("Items merged: $merged");
|
||||
$msgNumber = $this->add_upload_info("Items failed: $failed");
|
||||
|
||||
|
||||
|
||||
// Display & save upload log
|
||||
$this->handle_log();
|
||||
|
@ -287,7 +316,7 @@ class CronUploader extends Extension
|
|||
return true;
|
||||
}
|
||||
|
||||
private function move_uploaded($path, $filename, $corrupt = false)
|
||||
private function move_uploaded($path, $filename, $output_subdir, $corrupt = false)
|
||||
{
|
||||
global $config;
|
||||
|
||||
|
@ -296,16 +325,17 @@ class CronUploader extends Extension
|
|||
|
||||
$relativeDir = dirname(substr($path, strlen($this->root_dir) + 7));
|
||||
|
||||
// Determine which dir to move to
|
||||
if ($corrupt) {
|
||||
// Move to corrupt dir
|
||||
$newDir .= "/failed_to_upload/".$relativeDir;
|
||||
$info = "ERROR: Image was not uploaded.";
|
||||
} else {
|
||||
$newDir .= "/uploaded/".$relativeDir;
|
||||
$info = "Image successfully uploaded. ";
|
||||
}
|
||||
$newDir = str_replace("//", "/", $newDir."/");
|
||||
// Determine which dir to move to
|
||||
if ($corrupt) {
|
||||
// Move to corrupt dir
|
||||
$newDir .= "/failed_to_upload/".$output_subdir.$relativeDir;
|
||||
$info = "ERROR: Image was not uploaded.";
|
||||
}
|
||||
else {
|
||||
$newDir .= "/uploaded/".$output_subdir.$relativeDir;
|
||||
$info = "Image successfully uploaded. ";
|
||||
}
|
||||
$newDir = str_replace ( "//", "/", $newDir."/" );
|
||||
|
||||
if (!is_dir($newDir)) {
|
||||
mkdir($newDir, 0775, true);
|
||||
|
@ -328,9 +358,9 @@ class CronUploader extends Extension
|
|||
$metadata = [];
|
||||
$metadata ['filename'] = $pathinfo ['basename'];
|
||||
if (array_key_exists('extension', $pathinfo)) {
|
||||
$metadata['extension'] = $pathinfo['extension'];
|
||||
$metadata ['extension'] = $pathinfo ['extension'];
|
||||
}
|
||||
$metadata ['tags'] = Tag::explode($tags);
|
||||
$metadata ['tags'] = Tag::explode($tags);
|
||||
$metadata ['source'] = null;
|
||||
$event = new DataUploadEvent($tmpname, $metadata);
|
||||
send_event($event);
|
||||
|
@ -339,10 +369,13 @@ class CronUploader extends Extension
|
|||
$infomsg = ""; // Will contain info message
|
||||
if ($event->image_id == -1) {
|
||||
throw new Exception("File type not recognised. Filename: {$filename}");
|
||||
} else if ($event->image_id == null) {
|
||||
$infomsg = "Image merged. Filename: {$filename}";
|
||||
} else {
|
||||
$infomsg = "Image uploaded. ID: {$event->image_id} - Filename: {$filename} - Tags: {$tags}";
|
||||
$infomsg = "Image uploaded. ID: {$event->image_id} - Filename: {$filename}";
|
||||
}
|
||||
$msgNumber = $this->add_upload_info($infomsg);
|
||||
return $event->image_id;
|
||||
}
|
||||
|
||||
private function generate_image_queue(): void
|
||||
|
|
|
@ -366,8 +366,8 @@ class DanbooruApi extends Extension
|
|||
$fileinfo = pathinfo($filename);
|
||||
$metadata = [];
|
||||
$metadata['filename'] = $fileinfo['basename'];
|
||||
if (array_key_exists('extension', $pathinfo)) {
|
||||
$metadata['extension'] = $pathinfo['extension'];
|
||||
if (array_key_exists('extension', $fileinfo)) {
|
||||
$metadata['extension'] = $fileinfo['extension'];
|
||||
}
|
||||
$metadata['tags'] = $posttags;
|
||||
$metadata['source'] = $source;
|
||||
|
|
|
@ -57,6 +57,8 @@ class ET extends Extension
|
|||
$info['thumb_quality'] = $config->get_int('thumb_quality');
|
||||
$info['thumb_width'] = $config->get_int('thumb_width');
|
||||
$info['thumb_height'] = $config->get_int('thumb_height');
|
||||
$info['thumb_scaling'] = $config->get_int('thumb_scaling');
|
||||
$info['thumb_type'] = $config->get_string('thumb_type');
|
||||
$info['thumb_mem'] = $config->get_int("thumb_mem_limit");
|
||||
|
||||
$info['stat_images'] = $database->get_one("SELECT COUNT(*) FROM images");
|
||||
|
|
|
@ -37,10 +37,12 @@ Disk use: {$info['sys_disk']}
|
|||
|
||||
Thumbnail Generation:
|
||||
Engine: {$info['thumb_engine']}
|
||||
Type: {$info['thumb_type']}
|
||||
Memory: {$info['thumb_mem']}
|
||||
Quality: {$info['thumb_quality']}
|
||||
Width: {$info['thumb_width']}
|
||||
Height: {$info['thumb_height']}
|
||||
Scaling: {$info['thumb_scaling']}
|
||||
|
||||
Shimmie stats:
|
||||
Images: {$info['stat_images']}
|
||||
|
|
|
@ -3,14 +3,18 @@
|
|||
* Name: Handle Flash
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* Link: http://code.shishnet.org/shimmie2/
|
||||
* Description: Handle Flash files. (No thumbnail is generated for flash files)
|
||||
* Description: Handle Flash files.
|
||||
*/
|
||||
|
||||
class FlashFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected function create_thumb(string $hash): bool
|
||||
{
|
||||
copy("ext/handle_flash/thumb.jpg", warehouse_path("thumbs", $hash));
|
||||
global $config;
|
||||
|
||||
if(!create_thumbnail_ffmpeg($hash)) {
|
||||
copy("ext/handle_flash/thumb.jpg", warehouse_path("thumbs", $hash));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,13 +24,6 @@ class IcoFileHandler extends Extension
|
|||
}
|
||||
}
|
||||
|
||||
public function onThumbnailGeneration(ThumbnailGenerationEvent $event)
|
||||
{
|
||||
if ($this->supported_ext($event->type)) {
|
||||
$this->create_thumb($event->hash);
|
||||
}
|
||||
}
|
||||
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
{
|
||||
global $page;
|
||||
|
@ -88,8 +81,10 @@ class IcoFileHandler extends Extension
|
|||
$inname = warehouse_path("images", $hash);
|
||||
$outname = warehouse_path("thumbs", $hash);
|
||||
|
||||
$w = $config->get_int("thumb_width");
|
||||
$h = $config->get_int("thumb_height");
|
||||
$tsize = get_thumbnail_size_scaled($width, $height);
|
||||
$w = $tsize[0];
|
||||
$h = $tsise[1];
|
||||
|
||||
$q = $config->get_int("thumb_quality");
|
||||
$mem = $config->get_int("thumb_mem_limit") / 1024 / 1024; // IM takes memory in MB
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* Name: Handle Pixel
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* Link: http://code.shishnet.org/shimmie2/
|
||||
* Description: Handle JPEG, PNG, GIF, etc files
|
||||
* Description: Handle JPEG, PNG, GIF, WEBP, etc files
|
||||
*/
|
||||
|
||||
class PixelFileHandler extends DataHandlerExtension
|
||||
{
|
||||
protected function supported_ext(string $ext): bool
|
||||
{
|
||||
$exts = ["jpg", "jpeg", "gif", "png"];
|
||||
$exts = ["jpg", "jpeg", "gif", "png", "webp"];
|
||||
$ext = (($pos = strpos($ext, '?')) !== false) ? substr($ext, 0, $pos) : $ext;
|
||||
return in_array(strtolower($ext), $exts);
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class PixelFileHandler extends DataHandlerExtension
|
|||
|
||||
protected function check_contents(string $tmpname): bool
|
||||
{
|
||||
$valid = [IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_JPEG];
|
||||
$valid = [IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_WEBP];
|
||||
if (!file_exists($tmpname)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -54,15 +54,6 @@ class PixelFileHandler extends DataHandlerExtension
|
|||
}
|
||||
|
||||
protected function create_thumb(string $hash): bool
|
||||
{
|
||||
$outname = warehouse_path("thumbs", $hash);
|
||||
if (file_exists($outname)) {
|
||||
return true;
|
||||
}
|
||||
return $this->create_thumb_force($hash);
|
||||
}
|
||||
|
||||
protected function create_thumb_force(string $hash): bool
|
||||
{
|
||||
global $config;
|
||||
|
||||
|
@ -77,7 +68,7 @@ class PixelFileHandler extends DataHandlerExtension
|
|||
$ok = $this->make_thumb_gd($inname, $outname);
|
||||
break;
|
||||
case 'convert':
|
||||
$ok = $this->make_thumb_convert($inname, $outname);
|
||||
$ok = create_thumbnail_convert($hash);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -98,100 +89,31 @@ class PixelFileHandler extends DataHandlerExtension
|
|||
", 20);
|
||||
}
|
||||
|
||||
// IM thumber {{{
|
||||
private function make_thumb_convert(string $inname, string $outname): bool
|
||||
// GD thumber {{{
|
||||
private function make_thumb_gd(string $inname, string $outname): bool
|
||||
{
|
||||
global $config;
|
||||
|
||||
$w = $config->get_int("thumb_width");
|
||||
$h = $config->get_int("thumb_height");
|
||||
$q = $config->get_int("thumb_quality");
|
||||
$convert = $config->get_string("thumb_convert_path");
|
||||
|
||||
// ffff imagemagick fails sometimes, not sure why
|
||||
//$format = "'%s' '%s[0]' -format '%%[fx:w] %%[fx:h]' info:";
|
||||
//$cmd = sprintf($format, $convert, $inname);
|
||||
//$size = shell_exec($cmd);
|
||||
//$size = explode(" ", trim($size));
|
||||
$size = getimagesize($inname);
|
||||
if ($size[0] > $size[1]*5) {
|
||||
$size[0] = $size[1]*5;
|
||||
}
|
||||
if ($size[1] > $size[0]*5) {
|
||||
$size[1] = $size[0]*5;
|
||||
}
|
||||
|
||||
// running the call with cmd.exe requires quoting for our paths
|
||||
$format = '"%s" "%s[0]" -extent %ux%u -flatten -strip -thumbnail %ux%u -quality %u jpg:"%s"';
|
||||
$cmd = sprintf($format, $convert, $inname, $size[0], $size[1], $w, $h, $q, $outname);
|
||||
$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);
|
||||
|
||||
log_debug('handle_pixel', "Generating thumbnail with command `$cmd`, returns $ret");
|
||||
|
||||
if ($config->get_bool("thumb_optim", false)) {
|
||||
exec("jpegoptim $outname", $output, $ret);
|
||||
try {
|
||||
$info = getimagesize($inname);
|
||||
$tsize = get_thumbnail_size_scaled($info[0], $info[1]);
|
||||
$image = image_resize_gd($inname, $info, $tsize[0], $tsize[1],
|
||||
$outname, $config->get_string('thumb_type'),$config->get_int('thumb_quality'));
|
||||
} catch(InsufficientMemoryException $e) {
|
||||
$tsize = get_thumbnail_max_size_scaled();
|
||||
$thumb = imagecreatetruecolor($tsize[0], min($tsize[1], 64));
|
||||
$white = imagecolorallocate($thumb, 255, 255, 255);
|
||||
$black = imagecolorallocate($thumb, 0, 0, 0);
|
||||
imagefill($thumb, 0, 0, $white);
|
||||
log_warning("handle_pixel","Insufficient memory while creating thumbnail: ".$e->getMessage());
|
||||
imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black);
|
||||
return true;
|
||||
} catch(Exception $e) {
|
||||
log_error("handle_pixel","Error while creating thumbnail: ".$e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// }}}
|
||||
// GD thumber {{{
|
||||
private function make_thumb_gd(string $inname, string $outname): bool
|
||||
{
|
||||
global $config;
|
||||
$thumb = $this->get_thumb($inname);
|
||||
$ok = imagejpeg($thumb, $outname, $config->get_int('thumb_quality'));
|
||||
imagedestroy($thumb);
|
||||
return $ok;
|
||||
}
|
||||
|
||||
private function get_thumb(string $tmpname)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$info = getimagesize($tmpname);
|
||||
$width = $info[0];
|
||||
$height = $info[1];
|
||||
|
||||
$memory_use = (filesize($tmpname)*2) + ($width*$height*4) + (4*1024*1024);
|
||||
$memory_limit = get_memory_limit();
|
||||
|
||||
if ($memory_use > $memory_limit) {
|
||||
$w = $config->get_int('thumb_width');
|
||||
$h = $config->get_int('thumb_height');
|
||||
$thumb = imagecreatetruecolor($w, min($h, 64));
|
||||
$white = imagecolorallocate($thumb, 255, 255, 255);
|
||||
$black = imagecolorallocate($thumb, 0, 0, 0);
|
||||
imagefill($thumb, 0, 0, $white);
|
||||
imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black);
|
||||
return $thumb;
|
||||
} else {
|
||||
if ($width > $height*5) {
|
||||
$width = $height*5;
|
||||
}
|
||||
if ($height > $width*5) {
|
||||
$height = $width*5;
|
||||
}
|
||||
|
||||
$image = imagecreatefromstring(file_get_contents($tmpname));
|
||||
$tsize = get_thumbnail_size($width, $height);
|
||||
|
||||
$thumb = imagecreatetruecolor($tsize[0], $tsize[1]);
|
||||
imagecopyresampled(
|
||||
$thumb,
|
||||
$image,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
$tsize[0],
|
||||
$tsize[1],
|
||||
$width,
|
||||
$height
|
||||
);
|
||||
return $thumb;
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
* Name: Handle SVG
|
||||
* Author: Shish <webmaster@shishnet.org>
|
||||
* Link: http://code.shishnet.org/shimmie2/
|
||||
* Description: Handle static SVG files. (No thumbnail is generated for SVG files)
|
||||
* Description: Handle static SVG files.
|
||||
*/
|
||||
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class SVGFileHandler extends Extension
|
||||
class SVGFileHandler extends DataHandlerExtension
|
||||
{
|
||||
public function onDataUpload(DataUploadEvent $event)
|
||||
{
|
||||
|
@ -32,13 +32,12 @@ class SVGFileHandler extends Extension
|
|||
}
|
||||
}
|
||||
|
||||
public function onThumbnailGeneration(ThumbnailGenerationEvent $event)
|
||||
protected function create_thumb(string $hash): bool
|
||||
{
|
||||
if ($this->supported_ext($event->type)) {
|
||||
$hash = $event->hash;
|
||||
|
||||
if(!create_thumbnail_convert($hash)) {
|
||||
copy("ext/handle_svg/thumb.jpg", warehouse_path("thumbs", $hash));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
|
@ -68,13 +67,13 @@ class SVGFileHandler extends Extension
|
|||
}
|
||||
}
|
||||
|
||||
private function supported_ext(string $ext): bool
|
||||
protected function supported_ext(string $ext): bool
|
||||
{
|
||||
$exts = ["svg"];
|
||||
return in_array(strtolower($ext), $exts);
|
||||
}
|
||||
|
||||
private function create_image_from_data(string $filename, array $metadata): Image
|
||||
protected function create_image_from_data(string $filename, array $metadata): Image
|
||||
{
|
||||
$image = new Image();
|
||||
|
||||
|
@ -92,7 +91,7 @@ class SVGFileHandler extends Extension
|
|||
return $image;
|
||||
}
|
||||
|
||||
private function check_contents(string $file): bool
|
||||
protected function check_contents(string $file): bool
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
return false;
|
||||
|
|
|
@ -55,62 +55,16 @@ class VideoFileHandler extends DataHandlerExtension
|
|||
*/
|
||||
protected function create_thumb(string $hash): bool
|
||||
{
|
||||
global $config;
|
||||
|
||||
$ok = false;
|
||||
|
||||
$ffmpeg = $config->get_string("thumb_ffmpeg_path");
|
||||
$inname = warehouse_path("images", $hash);
|
||||
$outname = warehouse_path("thumbs", $hash);
|
||||
|
||||
$orig_size = $this->video_size($inname);
|
||||
$scaled_size = get_thumbnail_size($orig_size[0], $orig_size[1]);
|
||||
$cmd = escapeshellcmd(implode(" ", [
|
||||
escapeshellarg($ffmpeg),
|
||||
"-y", "-i", escapeshellarg($inname),
|
||||
"-vf", "scale={$scaled_size[0]}:{$scaled_size[1]}",
|
||||
"-ss", "00:00:00.0",
|
||||
"-f", "image2",
|
||||
"-vframes", "1",
|
||||
escapeshellarg($outname),
|
||||
]));
|
||||
|
||||
exec($cmd, $output, $ret);
|
||||
|
||||
if ((int)$ret == (int)0) {
|
||||
$ok = true;
|
||||
log_error('handle_video', "Generating thumbnail with command `$cmd`, returns $ret");
|
||||
} else {
|
||||
log_debug('handle_video', "Generating thumbnail with command `$cmd`, returns $ret");
|
||||
}
|
||||
$ok = create_thumbnail_ffmpeg($hash);
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
protected function video_size(string $filename)
|
||||
protected function video_size(string $filename): array
|
||||
{
|
||||
global $config;
|
||||
$ffmpeg = $config->get_string("thumb_ffmpeg_path");
|
||||
$cmd = escapeshellcmd(implode(" ", [
|
||||
escapeshellarg($ffmpeg),
|
||||
"-y", "-i", escapeshellarg($filename),
|
||||
"-vstats"
|
||||
]));
|
||||
$output = shell_exec($cmd . " 2>&1");
|
||||
// error_log("Getting size with `$cmd`");
|
||||
|
||||
$regex_sizes = "/Video: .* ([0-9]{1,4})x([0-9]{1,4})/";
|
||||
if (preg_match($regex_sizes, $output, $regs)) {
|
||||
if (preg_match("/displaymatrix: rotation of (90|270).00 degrees/", $output)) {
|
||||
$size = [$regs[2], $regs[1]];
|
||||
} else {
|
||||
$size = [$regs[1], $regs[2]];
|
||||
}
|
||||
} else {
|
||||
$size = [1, 1];
|
||||
}
|
||||
log_debug('handle_video', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]");
|
||||
return $size;
|
||||
return video_size($filename);
|
||||
}
|
||||
|
||||
protected function supported_ext(string $ext): bool
|
||||
|
|
|
@ -19,7 +19,9 @@ class ImageIO extends Extension
|
|||
global $config;
|
||||
$config->set_default_int('thumb_width', 192);
|
||||
$config->set_default_int('thumb_height', 192);
|
||||
$config->set_default_int('thumb_scaling', 100);
|
||||
$config->set_default_int('thumb_quality', 75);
|
||||
$config->set_default_string('thumb_type', 'jpg');
|
||||
$config->set_default_int('thumb_mem_limit', parse_shorthand_int('8MB'));
|
||||
$config->set_default_string('thumb_convert_path', 'convert');
|
||||
|
||||
|
@ -137,8 +139,15 @@ class ImageIO extends Extension
|
|||
$thumbers['Built-in GD'] = "gd";
|
||||
$thumbers['ImageMagick'] = "convert";
|
||||
|
||||
$thumb_types = [];
|
||||
$thumb_types['JPEG'] = "jpg";
|
||||
$thumb_types['WEBP'] = "webp";
|
||||
|
||||
|
||||
$sb = new SetupBlock("Thumbnailing");
|
||||
$sb->add_choice_option("thumb_engine", $thumbers, "Engine: ");
|
||||
$sb->add_label("<br>");
|
||||
$sb->add_choice_option("thumb_type", $thumb_types, "Filetype: ");
|
||||
|
||||
$sb->add_label("<br>Size ");
|
||||
$sb->add_int_option("thumb_width");
|
||||
|
@ -147,7 +156,11 @@ class ImageIO extends Extension
|
|||
$sb->add_label(" px at ");
|
||||
$sb->add_int_option("thumb_quality");
|
||||
$sb->add_label(" % quality ");
|
||||
|
||||
|
||||
$sb->add_label("<br>High-DPI scaling ");
|
||||
$sb->add_int_option("thumb_scaling");
|
||||
$sb->add_label("%");
|
||||
|
||||
if ($config->get_string("thumb_engine") == "convert") {
|
||||
$sb->add_label("<br>ImageMagick Binary: ");
|
||||
$sb->add_text_option("thumb_convert_path");
|
||||
|
@ -240,7 +253,13 @@ class ImageIO extends Extension
|
|||
if (!is_null($image)) {
|
||||
$page->set_mode("data");
|
||||
if ($type == "thumb") {
|
||||
$page->set_type("image/jpeg");
|
||||
$ext = $config->get_string("thumb_type");
|
||||
if (array_key_exists($ext, MIME_TYPE_MAP)) {
|
||||
$page->set_type(MIME_TYPE_MAP[$ext]);
|
||||
} else {
|
||||
$page->set_type("image/jpeg");
|
||||
}
|
||||
|
||||
$file = $image->get_thumb_filename();
|
||||
} else {
|
||||
$page->set_type($image->get_mime_type());
|
||||
|
@ -259,6 +278,9 @@ class ImageIO extends Extension
|
|||
$page->set_data("");
|
||||
} else {
|
||||
$page->add_http_header("Last-Modified: $gmdate_mod");
|
||||
if ($type != "thumb") {
|
||||
$page->add_http_header("Content-Disposition: inline; filename=".$image->get_nice_image_name());
|
||||
}
|
||||
$page->set_data(file_get_contents($file));
|
||||
|
||||
if ($config->get_int("image_expires")) {
|
||||
|
@ -291,7 +313,7 @@ class ImageIO extends Extension
|
|||
if (is_null($existing)) {
|
||||
throw new ImageReplaceException("Image to replace does not exist!");
|
||||
}
|
||||
|
||||
|
||||
if (strlen(trim($image->source)) == 0) {
|
||||
$image->source = $existing->get_source();
|
||||
}
|
||||
|
@ -301,6 +323,7 @@ class ImageIO extends Extension
|
|||
and have it stored in a 'replaced images' list that could be
|
||||
inspected later by an admin?
|
||||
*/
|
||||
|
||||
log_debug("image", "Removing image with hash ".$existing->hash);
|
||||
$existing->remove_image_only(); // Actually delete the old image file from disk
|
||||
|
||||
|
@ -319,6 +342,9 @@ class ImageIO extends Extension
|
|||
]
|
||||
);
|
||||
|
||||
/* Generate new thumbnail */
|
||||
send_event(new ThumbnailGenerationEvent($image->hash, strtolower($image->ext)));
|
||||
|
||||
log_info("image", "Replaced Image #{$id} with ({$image->hash})");
|
||||
}
|
||||
// }}} end replace
|
||||
|
|
|
@ -11,6 +11,6 @@ class QRImage extends Extension
|
|||
{
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
{
|
||||
$this->theme->links_block(make_http(make_link('image/'.$event->image->id.'.jpg')));
|
||||
$this->theme->links_block(make_http(make_link('image/'.$event->image->id.'.'.$event->image->ext)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,13 +73,13 @@ class Ratings extends Extension
|
|||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
public function onPostListBuilding(PostListBuildingEvent $event)
|
||||
{
|
||||
global $user;
|
||||
if ($user->is_admin() && !empty($event->search_terms)) {
|
||||
$this->theme->display_bulk_rater(Tag::implode($event->search_terms));
|
||||
}
|
||||
}
|
||||
// public function onPostListBuilding(PostListBuildingEvent $event)
|
||||
// {
|
||||
// global $user;
|
||||
// if ($user->is_admin() && !empty($event->search_terms)) {
|
||||
// $this->theme->display_bulk_rater(Tag::implode($event->search_terms));
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
public function onDisplayingImage(DisplayingImageEvent $event)
|
||||
|
@ -143,6 +143,65 @@ class Ratings extends Extension
|
|||
}
|
||||
}
|
||||
|
||||
public function onTagTermParse(TagTermParseEvent $event)
|
||||
{
|
||||
$matches = [];
|
||||
|
||||
if (preg_match("/^rating[=|:](?:([sqeu]+)|(safe|questionable|explicit|unknown))$/D", strtolower($event->term), $matches) && $event->parse) {
|
||||
$ratings = $matches[1] ? $matches[1] : $matches[2][0];
|
||||
$ratings = array_intersect(str_split($ratings), str_split(Ratings::get_user_privs($user)));
|
||||
|
||||
$rating = $ratings[0];
|
||||
|
||||
$image = Image::by_id($event->id);
|
||||
|
||||
$re = new RatingSetEvent($image, $rating);
|
||||
|
||||
send_event($re);
|
||||
}
|
||||
|
||||
if (!empty($matches)) {
|
||||
$event->metatag = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||
{
|
||||
global $user;
|
||||
|
||||
if ($user->is_admin()) {
|
||||
$event->add_action("bulk_rate","Set Rating","",$this->theme->get_selection_rater_html("bulk_rating"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function onBulkAction(BulkActionEvent $event)
|
||||
{
|
||||
global $user;
|
||||
|
||||
switch($event->action) {
|
||||
case "bulk_rate":
|
||||
if (!isset($_POST['bulk_rating'])) {
|
||||
return;
|
||||
}
|
||||
if ($user->is_admin()) {
|
||||
$rating = $_POST['bulk_rating'];
|
||||
$total = 0;
|
||||
foreach ($event->items as $id) {
|
||||
$image = Image::by_id($id);
|
||||
if($image==null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
send_event(new RatingSetEvent($image, $rating));
|
||||
$total++;
|
||||
}
|
||||
flash_message("Rating set for $total items");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $user, $page;
|
||||
|
@ -271,8 +330,17 @@ class Ratings extends Extension
|
|||
}
|
||||
|
||||
if ($config->get_int("ext_ratings2_version") < 3) {
|
||||
$database->Execute("ALTER TABLE images CHANGE rating rating CHAR(1) NOT NULL DEFAULT 'u'");
|
||||
$config->set_int("ext_ratings2_version", 3);
|
||||
$database->Execute("UPDATE images SET rating = 'u' WHERE rating is null");
|
||||
switch($database->get_driver_name()) {
|
||||
case "mysql":
|
||||
$database->Execute("ALTER TABLE images CHANGE rating rating CHAR(1) NOT NULL DEFAULT 'u'");
|
||||
break;
|
||||
case "pgsql":
|
||||
$database->Execute("ALTER TABLE images ALTER COLUMN rating SET DEFAULT 'u'");
|
||||
$database->Execute("ALTER TABLE images ALTER COLUMN rating SET NOT NULL");
|
||||
break;
|
||||
}
|
||||
$config->set_int("ext_ratings2_version", 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,4 +45,13 @@ class RatingsTheme extends Themelet
|
|||
";
|
||||
$page->add_block(new Block("List Controls", $html, "left"));
|
||||
}
|
||||
|
||||
public function get_selection_rater_html(String $id = "select_rating") {
|
||||
return "<select name='".$id."'>
|
||||
<option value='s'>Safe</option>
|
||||
<option value='q'>Questionable</option>
|
||||
<option value='e'>Explicit</option>
|
||||
<option value='u'>Unrated</option>
|
||||
</select>";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,14 +15,24 @@
|
|||
|
||||
class RegenThumb extends Extension
|
||||
{
|
||||
public function regenerate_thumbnail($image, $force = true): string
|
||||
{
|
||||
global $database;
|
||||
$event = new ThumbnailGenerationEvent($image->hash, $image->ext, $force);
|
||||
send_event($event);
|
||||
$database->cache->delete("thumb-block:{$image->id}");
|
||||
return $event->generated;
|
||||
}
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $database, $page, $user;
|
||||
|
||||
if ($event->page_matches("regen_thumb/one") && $user->can("delete_image") && isset($_POST['image_id'])) {
|
||||
$image = Image::by_id(int_escape($_POST['image_id']));
|
||||
send_event(new ThumbnailGenerationEvent($image->hash, $image->ext, true));
|
||||
$database->cache->delete("thumb-block:{$image->id}");
|
||||
|
||||
$this->regenerate_thumbnail($image);
|
||||
|
||||
$this->theme->display_results($page, $image);
|
||||
}
|
||||
if ($event->page_matches("regen_thumb/mass") && $user->can("delete_image") && isset($_POST['tags'])) {
|
||||
|
@ -30,8 +40,7 @@ class RegenThumb extends Extension
|
|||
$images = Image::find_images(0, 10000, $tags);
|
||||
|
||||
foreach ($images as $image) {
|
||||
send_event(new ThumbnailGenerationEvent($image->hash, $image->ext, true));
|
||||
$database->cache->delete("thumb-block:{$image->id}");
|
||||
$this->regenerate_thumbnail($image);
|
||||
}
|
||||
|
||||
$page->set_mode("redirect");
|
||||
|
@ -47,11 +56,156 @@ class RegenThumb extends Extension
|
|||
}
|
||||
}
|
||||
|
||||
public function onPostListBuilding(PostListBuildingEvent $event)
|
||||
// public function onPostListBuilding(PostListBuildingEvent $event)
|
||||
// {
|
||||
// global $user;
|
||||
// if ($user->can("delete_image") && !empty($event->search_terms)) {
|
||||
// $event->add_control($this->theme->mtr_html(Tag::implode($event->search_terms)));
|
||||
// }
|
||||
// }
|
||||
|
||||
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("delete_image") && !empty($event->search_terms)) {
|
||||
$event->add_control($this->theme->mtr_html(Tag::implode($event->search_terms)));
|
||||
|
||||
if ($user->can("delete_image")) {
|
||||
$event->add_action("bulk_regen","Regen Thumbnails","",$this->theme->bulk_html());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function onBulkAction(BulkActionEvent $event)
|
||||
{
|
||||
global $user;
|
||||
|
||||
switch($event->action) {
|
||||
case "bulk_regen":
|
||||
if ($user->can("delete_image")) {
|
||||
$force = true;
|
||||
if(isset($_POST["bulk_regen_thumb_missing_only"])
|
||||
&&$_POST["bulk_regen_thumb_missing_only"]=="true")
|
||||
{
|
||||
$force=false;
|
||||
}
|
||||
|
||||
$total = 0;
|
||||
foreach ($event->items as $id) {
|
||||
$image = Image::by_id($id);
|
||||
if($image==null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if($this->regenerate_thumbnail($image, $force)) {
|
||||
$total++;
|
||||
}
|
||||
}
|
||||
flash_message("Regenerated thumbnails for $total items");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function onAdminBuilding(AdminBuildingEvent $event)
|
||||
{
|
||||
$this->theme->display_admin_block();
|
||||
}
|
||||
|
||||
public function onAdminAction(AdminActionEvent $event) {
|
||||
global $database;
|
||||
|
||||
switch($event->action) {
|
||||
case "regen_thumbs":
|
||||
$event->redirect = true;
|
||||
$force = false;
|
||||
if(isset($_POST["regen_thumb_force"])&&$_POST["regen_thumb_force"]=="true") {
|
||||
$force=true;
|
||||
}
|
||||
$limit = 1000;
|
||||
if(isset($_POST["regen_thumb_limit"])&&is_numeric($_POST["regen_thumb_limit"])) {
|
||||
$limit=intval($_POST["regen_thumb_limit"]);
|
||||
}
|
||||
|
||||
$type = "";
|
||||
if(isset($_POST["regen_thumb_limit"])) {
|
||||
$type = $_POST["regen_thumb_type"];
|
||||
}
|
||||
$images = $this->get_images($type);
|
||||
|
||||
$i = 0;
|
||||
foreach ($images as $image) {
|
||||
if(!$force) {
|
||||
$path = warehouse_path("thumbs", $image["hash"], false);
|
||||
if(file_exists($path)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$event = new ThumbnailGenerationEvent($image["hash"], $image["ext"], $force);
|
||||
send_event($event);
|
||||
if($event->generated) {
|
||||
$i++;
|
||||
}
|
||||
if($i>=$limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
flash_message("Re-generated $i thumbnails");
|
||||
break;
|
||||
case "delete_thumbs":
|
||||
$event->redirect = true;
|
||||
|
||||
if(isset($_POST["delete_thumb_type"])&&$_POST["delete_thumb_type"]!="") {
|
||||
$images = $this->get_images($_POST["delete_thumb_type"]);
|
||||
|
||||
$i = 0;
|
||||
foreach ($images as $image) {
|
||||
$outname = warehouse_path("thumbs", $image["hash"]);
|
||||
if(file_exists($outname)) {
|
||||
unlink($outname);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
flash_message("Deleted $i thumbnails for ".$_POST["delete_thumb_type"]." images");
|
||||
} else {
|
||||
$dir = "data/thumbs/";
|
||||
$this->remove_dir_recursively($dir);
|
||||
flash_message("Deleted all thumbnails");
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function get_images(String $ext = null)
|
||||
{
|
||||
global $database;
|
||||
|
||||
$query = "SELECT hash, ext FROM images";
|
||||
$args = [];
|
||||
if($ext!=null&&$ext!="") {
|
||||
$query .= " WHERE ext = :ext";
|
||||
$args["ext"] = $ext;
|
||||
}
|
||||
|
||||
return $database->get_all($query, $args);
|
||||
}
|
||||
|
||||
function remove_dir_recursively($dir)
|
||||
{
|
||||
if (is_dir($dir)) {
|
||||
$objects = scandir($dir);
|
||||
foreach ($objects as $object) {
|
||||
if ($object != "." && $object != "..") {
|
||||
if (filetype($dir."/".$object) == "dir") {
|
||||
$this->remove_dir_recursively($dir."/".$object);
|
||||
} else {
|
||||
unlink ($dir."/".$object);
|
||||
}
|
||||
}
|
||||
}
|
||||
reset($objects);
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -37,4 +37,48 @@ class RegenThumbTheme extends Themelet
|
|||
";
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function bulk_html() {
|
||||
return "<label><input type='checkbox' name='bulk_regen_thumb_missing_only' id='bulk_regen_thumb_missing_only' style='width:13px' value='true' />Only missing thumbs</label>";
|
||||
}
|
||||
|
||||
public function display_admin_block()
|
||||
{
|
||||
global $page, $database;
|
||||
|
||||
$types = [];
|
||||
$results = $database->get_all("SELECT ext, count(*) count FROM images group by ext");
|
||||
foreach ($results as $result) {
|
||||
array_push($types,"<option value='".$result["ext"]."'>".$result["ext"]." (".$result["count"].")</option>");
|
||||
}
|
||||
|
||||
$html = "
|
||||
Will only regenerate missing thumbnails, unless force is selected. Force will override the limit and will likely take a very long time to process.
|
||||
<p>".make_form(make_link("admin/regen_thumbs"))."
|
||||
<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' value='1000'>
|
||||
<option value=''>All</option>
|
||||
".implode($types)."
|
||||
</select>
|
||||
</td></tr>
|
||||
<tr><td colspan='2'><input type='submit' value='Regenerate Thumbnails'></td></tr>
|
||||
</table>
|
||||
</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' value='1000'>
|
||||
<option value=''>All</option>
|
||||
".implode($types)."
|
||||
</select>
|
||||
</td></tr>
|
||||
<tr><td colspan='2'><input type='submit' value='Delete Thumbnails'></td></tr>
|
||||
</table>
|
||||
</form></p>
|
||||
";
|
||||
$page->add_block(new Block("Regen Thumbnails", $html));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,25 +11,20 @@
|
|||
* Documentation:
|
||||
* This extension allows admins to resize images.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class is just a wrapper around SCoreException.
|
||||
*/
|
||||
class ImageResizeException extends SCoreException
|
||||
{
|
||||
public $error;
|
||||
|
||||
public function __construct(string $error)
|
||||
{
|
||||
$this->error = $error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class handles image resize requests.
|
||||
*/
|
||||
class ResizeImage extends Extension
|
||||
{
|
||||
/**
|
||||
* Needs to be after the data processing extensions
|
||||
*/
|
||||
public function get_priority(): int
|
||||
{
|
||||
return 55;
|
||||
}
|
||||
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
global $config;
|
||||
|
@ -69,7 +64,7 @@ class ResizeImage extends Extension
|
|||
|
||||
$image_obj = Image::by_id($event->image_id);
|
||||
|
||||
if ($config->get_bool("resize_upload") == true && ($image_obj->ext == "jpg" || $image_obj->ext == "png" || $image_obj->ext == "gif")) {
|
||||
if ($config->get_bool("resize_upload") == true && ($image_obj->ext == "jpg" || $image_obj->ext == "png" || $image_obj->ext == "gif" || $image_obj->ext == "webp")) {
|
||||
$width = $height = 0;
|
||||
|
||||
if ($config->get_int("resize_default_width") !== 0) {
|
||||
|
@ -159,11 +154,6 @@ class ResizeImage extends Extension
|
|||
|
||||
// Private functions
|
||||
/* ----------------------------- */
|
||||
|
||||
/**
|
||||
* This function could be made much smaller by using the ImageReplaceEvent
|
||||
* ie: Pretend that we are replacing the image with a resized copy.
|
||||
*/
|
||||
private function resize_image(Image $image_obj, int $width, int $height)
|
||||
{
|
||||
global $database;
|
||||
|
@ -174,134 +164,42 @@ class ResizeImage extends Extension
|
|||
|
||||
$hash = $image_obj->hash;
|
||||
$image_filename = warehouse_path("images", $hash);
|
||||
|
||||
$info = getimagesize($image_filename);
|
||||
/* Get the image file type */
|
||||
$pathinfo = pathinfo($image_obj->filename);
|
||||
$filetype = strtolower($pathinfo['extension']);
|
||||
|
||||
if (($image_obj->width != $info[0]) || ($image_obj->height != $info[1])) {
|
||||
throw new ImageResizeException("The current image size does not match what is set in the database! - Aborting Resize.");
|
||||
}
|
||||
|
||||
$memory_use = $this->calc_memory_use($info);
|
||||
$memory_limit = get_memory_limit();
|
||||
if ($memory_use > $memory_limit) {
|
||||
throw new ImageResizeException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)");
|
||||
}
|
||||
|
||||
list($new_height, $new_width) = $this->calc_new_size($image_obj, $width, $height);
|
||||
|
||||
/* Attempt to load the image */
|
||||
switch ($info[2]) {
|
||||
case IMAGETYPE_GIF: $image = imagecreatefromgif($image_filename); break;
|
||||
case IMAGETYPE_JPEG: $image = imagecreatefromjpeg($image_filename); break;
|
||||
case IMAGETYPE_PNG: $image = imagecreatefrompng($image_filename); break;
|
||||
default:
|
||||
throw new ImageResizeException("Unsupported image type (Only GIF, JPEG, and PNG are supported).");
|
||||
}
|
||||
|
||||
// Handle transparent images
|
||||
|
||||
$image_resized = imagecreatetruecolor($new_width, $new_height);
|
||||
|
||||
if ($info[2] == IMAGETYPE_GIF) {
|
||||
$transparency = imagecolortransparent($image);
|
||||
|
||||
// If we have a specific transparent color
|
||||
if ($transparency >= 0) {
|
||||
// Get the original image's transparent color's RGB values
|
||||
$transparent_color = imagecolorsforindex($image, $transparency);
|
||||
|
||||
// Allocate the same color in the new image resource
|
||||
$transparency = imagecolorallocate($image_resized, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
|
||||
|
||||
// Completely fill the background of the new image with allocated color.
|
||||
imagefill($image_resized, 0, 0, $transparency);
|
||||
|
||||
// Set the background color for new image to transparent
|
||||
imagecolortransparent($image_resized, $transparency);
|
||||
}
|
||||
} elseif ($info[2] == IMAGETYPE_PNG) {
|
||||
//
|
||||
// More info here: http://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php
|
||||
//
|
||||
imagealphablending($image_resized, false);
|
||||
imagesavealpha($image_resized, true);
|
||||
$transparent_color = imagecolorallocatealpha($image_resized, 255, 255, 255, 127);
|
||||
imagefilledrectangle($image_resized, 0, 0, $new_width, $new_height, $transparent_color);
|
||||
}
|
||||
|
||||
// Actually resize the image.
|
||||
imagecopyresampled($image_resized, $image, 0, 0, 0, 0, $new_width, $new_height, $image_obj->width, $image_obj->height);
|
||||
|
||||
/* Temp storage while we resize */
|
||||
$tmp_filename = tempnam("/tmp", 'shimmie_resize');
|
||||
if (empty($tmp_filename)) {
|
||||
throw new ImageResizeException("Unable to save temporary image file.");
|
||||
}
|
||||
|
||||
/* Output to the same format as the original image */
|
||||
switch ($info[2]) {
|
||||
case IMAGETYPE_GIF: imagegif($image_resized, $tmp_filename); break;
|
||||
case IMAGETYPE_JPEG: imagejpeg($image_resized, $tmp_filename); break;
|
||||
case IMAGETYPE_PNG: imagepng($image_resized, $tmp_filename); break;
|
||||
default:
|
||||
throw new ImageResizeException("Failed to save the new image - Unsupported image type.");
|
||||
}
|
||||
|
||||
|
||||
image_resize_gd($image_filename, $info, $new_width, $new_height, $tmp_filename);
|
||||
|
||||
$new_image = new Image();
|
||||
$new_image->hash = md5_file($tmp_filename);
|
||||
$new_image->filesize = filesize($tmp_filename);
|
||||
$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 */
|
||||
$new_hash = md5_file($tmp_filename);
|
||||
$new_size = filesize($tmp_filename);
|
||||
$target = warehouse_path("images", $new_hash);
|
||||
$target = warehouse_path("images", $new_image->hash);
|
||||
if (!@copy($tmp_filename, $target)) {
|
||||
throw new ImageResizeException("Failed to copy new image file from temporary location ({$tmp_filename}) to archive ($target)");
|
||||
}
|
||||
$new_filename = 'resized-'.$image_obj->filename;
|
||||
|
||||
/* Remove temporary file */
|
||||
@unlink($tmp_filename);
|
||||
|
||||
/* Delete original image and thumbnail */
|
||||
log_debug("image", "Removing image with hash ".$hash);
|
||||
$image_obj->remove_image_only();
|
||||
send_event(new ImageReplaceEvent($image_obj->id, $new_image));
|
||||
|
||||
/* Generate new thumbnail */
|
||||
send_event(new ThumbnailGenerationEvent($new_hash, $filetype));
|
||||
|
||||
/* Update the database */
|
||||
$database->Execute("
|
||||
UPDATE images SET filename = :filename, filesize = :filesize, hash = :hash, width = :width, height = :height
|
||||
WHERE id = :id
|
||||
", [
|
||||
"filename"=>$new_filename, "filesize"=>$new_size, "hash"=>$new_hash,
|
||||
"width"=>$new_width, "height"=>$new_height, "id"=>$image_obj->id
|
||||
]);
|
||||
|
||||
log_info("resize", "Resized Image #{$image_obj->id} - New hash: {$new_hash}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Memory usage limits
|
||||
*
|
||||
* Old check: $memory_use = (filesize($image_filename)*2) + ($width*$height*4) + (4*1024*1024);
|
||||
* New check: $memory_use = $width * $height * ($bits_per_channel) * channels * 2.5
|
||||
*
|
||||
* It didn't make sense to compute the memory usage based on the NEW size for the image. ($width*$height*4)
|
||||
* We need to consider the size that we are GOING TO instead.
|
||||
*
|
||||
* The factor of 2.5 is simply a rough guideline.
|
||||
* http://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize
|
||||
*/
|
||||
private function calc_memory_use(array $info): int
|
||||
{
|
||||
if (isset($info['bits']) && isset($info['channels'])) {
|
||||
$memory_use = ($info[0] * $info[1] * ($info['bits'] / 8) * $info['channels'] * 2.5) / 1024;
|
||||
} else {
|
||||
// If we don't have bits and channel info from the image then assume default values
|
||||
// of 8 bits per color and 4 channels (R,G,B,A) -- ie: regular 24-bit color
|
||||
$memory_use = ($info[0] * $info[1] * 1 * 4 * 2.5) / 1024;
|
||||
}
|
||||
return (int)$memory_use;
|
||||
log_info("resize", "Resized Image #{$image_obj->id} - New hash: {$new_image->hash}");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -106,11 +106,6 @@ class RotateImage extends Extension
|
|||
|
||||
// Private functions
|
||||
/* ----------------------------- */
|
||||
|
||||
/**
|
||||
* This function could be made much smaller by using the ImageReplaceEvent
|
||||
* ie: Pretend that we are replacing the image with a rotated copy.
|
||||
*/
|
||||
private function rotate_image(int $image_id, int $deg)
|
||||
{
|
||||
global $database;
|
||||
|
@ -129,24 +124,10 @@ class RotateImage extends Extension
|
|||
if (file_exists($image_filename)==false) {
|
||||
throw new ImageRotateException("$image_filename does not exist.");
|
||||
}
|
||||
|
||||
$info = getimagesize($image_filename);
|
||||
/* Get the image file type */
|
||||
$pathinfo = pathinfo($image_obj->filename);
|
||||
$filetype = strtolower($pathinfo['extension']);
|
||||
|
||||
/*
|
||||
Check Memory usage limits
|
||||
|
||||
Old check: $memory_use = (filesize($image_filename)*2) + ($width*$height*4) + (4*1024*1024);
|
||||
New check: memory_use = width * height * (bits per channel) * channels * 2.5
|
||||
|
||||
It didn't make sense to compute the memory usage based on the NEW size for the image. ($width*$height*4)
|
||||
We need to consider the size that we are GOING TO instead.
|
||||
|
||||
The factor of 2.5 is simply a rough guideline.
|
||||
http://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize
|
||||
*/
|
||||
$memory_use = ($info[0] * $info[1] * ($info['bits'] / 8) * $info['channels'] * 2.5) / 1024;
|
||||
$memory_use =calc_memory_use ($info);
|
||||
$memory_limit = get_memory_limit();
|
||||
|
||||
if ($memory_use > $memory_limit) {
|
||||
|
@ -181,9 +162,23 @@ class RotateImage extends Extension
|
|||
}
|
||||
}
|
||||
*/
|
||||
|
||||
$image_rotated = imagerotate($image, $deg, 0);
|
||||
|
||||
|
||||
$background_color = 0;
|
||||
switch($info[2]){
|
||||
case IMAGETYPE_PNG:
|
||||
case IMAGETYPE_WEBP:
|
||||
$background_color = imagecolorallocatealpha($image, 0, 0, 0, 127);
|
||||
break;
|
||||
}
|
||||
if($background_color===false) {
|
||||
throw new ImageRotateException("Unable to allocate transparent color");
|
||||
}
|
||||
|
||||
$image_rotated = imagerotate($image, $deg, $background_color);
|
||||
if($image_rotated===false) {
|
||||
throw new ImageRotateException("Image rotate failed");
|
||||
}
|
||||
|
||||
/* Temp storage while we rotate */
|
||||
$tmp_filename = tempnam(ini_get('upload_tmp_dir'), 'shimmie_rotate');
|
||||
if (empty($tmp_filename)) {
|
||||
|
@ -191,49 +186,42 @@ class RotateImage extends Extension
|
|||
}
|
||||
|
||||
/* Output to the same format as the original image */
|
||||
$result = false;
|
||||
switch ($info[2]) {
|
||||
case IMAGETYPE_GIF: imagegif($image_rotated, $tmp_filename); break;
|
||||
case IMAGETYPE_JPEG: imagejpeg($image_rotated, $tmp_filename); break;
|
||||
case IMAGETYPE_PNG: imagepng($image_rotated, $tmp_filename); break;
|
||||
case IMAGETYPE_GIF: $result = imagegif($image_rotated, $tmp_filename); break;
|
||||
case IMAGETYPE_JPEG: $result = imagejpeg($image_rotated, $tmp_filename); break;
|
||||
case IMAGETYPE_PNG: $result = imagepng($image_rotated, $tmp_filename,9); break;
|
||||
case IMAGETYPE_WEBP: $result = imagewebp($image_rotated, $tmp_filename); break;
|
||||
case IMAGETYPE_BMP: $result = imagebmp($image_rotated, $tmp_filename,true); break;
|
||||
default:
|
||||
throw new ImageRotateException("Unsupported image type.");
|
||||
}
|
||||
|
||||
|
||||
if($result===false) {
|
||||
throw new ImageRotateException("Could not save image: ".$tmp_filename);
|
||||
}
|
||||
|
||||
list($new_width, $new_height) = getimagesize($tmp_filename);
|
||||
|
||||
$new_image = new Image();
|
||||
$new_image->hash = md5_file($tmp_filename);
|
||||
$new_image->filesize = filesize($tmp_filename);
|
||||
$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 */
|
||||
$new_hash = md5_file($tmp_filename);
|
||||
$new_size = filesize($tmp_filename);
|
||||
$target = warehouse_path("images", $new_hash);
|
||||
$target = warehouse_path("images", $new_image->hash);
|
||||
if (!@copy($tmp_filename, $target)) {
|
||||
throw new ImageRotateException("Failed to copy new image file from temporary location ({$tmp_filename}) to archive ($target)");
|
||||
}
|
||||
$new_filename = 'rotated-'.$image_obj->filename;
|
||||
|
||||
list($new_width, $new_height) = getimagesize($target);
|
||||
|
||||
|
||||
/* Remove temporary file */
|
||||
@unlink($tmp_filename);
|
||||
|
||||
/* Delete original image and thumbnail */
|
||||
log_debug("image", "Removing image with hash ".$hash);
|
||||
$image_obj->remove_image_only();
|
||||
|
||||
/* Generate new thumbnail */
|
||||
send_event(new ThumbnailGenerationEvent($new_hash, $filetype));
|
||||
|
||||
/* Update the database */
|
||||
$database->Execute(
|
||||
"UPDATE images SET
|
||||
filename = :filename, filesize = :filesize, hash = :hash, width = :width, height = :height
|
||||
WHERE
|
||||
id = :id
|
||||
",
|
||||
[
|
||||
"filename"=>$new_filename, "filesize"=>$new_size, "hash"=>$new_hash,
|
||||
"width"=>$new_width, "height"=>$new_height, "id"=>$image_id
|
||||
]
|
||||
);
|
||||
|
||||
log_info("rotate", "Rotated Image #{$image_id} - New hash: {$new_hash}");
|
||||
send_event(new ImageReplaceEvent($image_id, $new_image));
|
||||
|
||||
log_info("rotate", "Rotated Image #{$image_id} - New hash: {$new_image->hash}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,13 +179,13 @@ class TagEdit extends Extension
|
|||
}
|
||||
}
|
||||
|
||||
public function onPostListBuilding(PostListBuildingEvent $event)
|
||||
{
|
||||
global $user;
|
||||
if ($user->can("bulk_edit_image_source") && !empty($event->search_terms)) {
|
||||
$event->add_control($this->theme->mss_html(Tag::implode($event->search_terms)));
|
||||
}
|
||||
}
|
||||
// public function onPostListBuilding(PostListBuildingEvent $event)
|
||||
// {
|
||||
// global $user;
|
||||
// if ($user->can("bulk_edit_image_source") && !empty($event->search_terms)) {
|
||||
// $event->add_control($this->theme->mss_html(Tag::implode($event->search_terms)));
|
||||
// }
|
||||
// }
|
||||
|
||||
public function onImageInfoSet(ImageInfoSetEvent $event)
|
||||
{
|
||||
|
|
459
ext/transcode/main.php
Normal file
459
ext/transcode/main.php
Normal file
|
@ -0,0 +1,459 @@
|
|||
<?php
|
||||
/*
|
||||
* Name: Transcode Image
|
||||
* Author: Matthew Barbour <matthew@darkholme.net>
|
||||
* Description: Allows admins to automatically and manually transcode images.
|
||||
* License: MIT
|
||||
* Version: 1.0
|
||||
* Documentation:
|
||||
* Can transcode on-demand and automatically on upload. Config screen allows choosing an output format for each of the supported input formats.
|
||||
* Supports GD and ImageMagick. Both support bmp, gif, jpg, png, and webp as inputs, and jpg, png, and lossy webp as outputs.
|
||||
* ImageMagick additionally supports tiff and psd inputs, and webp lossless output.
|
||||
* If and image is uanble to be transcoded for any reason, the upload will continue unaffected.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is used by the image transcoding code when there is an error while transcoding
|
||||
*/
|
||||
class ImageTranscodeException extends SCoreException{ }
|
||||
|
||||
|
||||
class TranscodeImage extends Extension
|
||||
{
|
||||
const CONVERSION_ENGINES = [
|
||||
"GD" => "gd",
|
||||
"ImageMagick" => "convert",
|
||||
];
|
||||
|
||||
const ENGINE_INPUT_SUPPORT = [
|
||||
"gd" => [
|
||||
"bmp",
|
||||
"gif",
|
||||
"jpg",
|
||||
"png",
|
||||
"webp",
|
||||
],
|
||||
"convert" => [
|
||||
"bmp",
|
||||
"gif",
|
||||
"jpg",
|
||||
"png",
|
||||
"psd",
|
||||
"tiff",
|
||||
"webp",
|
||||
]
|
||||
];
|
||||
|
||||
const ENGINE_OUTPUT_SUPPORT = [
|
||||
"gd" => [
|
||||
"jpg",
|
||||
"png",
|
||||
"webp-lossy",
|
||||
],
|
||||
"convert" => [
|
||||
"jpg",
|
||||
"png",
|
||||
"webp-lossy",
|
||||
"webp-lossless",
|
||||
]
|
||||
];
|
||||
|
||||
const LOSSLESS_FORMATS = [
|
||||
"webp-lossless",
|
||||
"png",
|
||||
];
|
||||
|
||||
const INPUT_FORMATS = [
|
||||
"BMP" => "bmp",
|
||||
"GIF" => "gif",
|
||||
"JPG" => "jpg",
|
||||
"PNG" => "png",
|
||||
"PSD" => "psd",
|
||||
"TIFF" => "tiff",
|
||||
"WEBP" => "webp",
|
||||
];
|
||||
|
||||
const FORMAT_ALIASES = [
|
||||
"tif" => "tiff",
|
||||
"jpeg" => "jpg",
|
||||
];
|
||||
|
||||
const OUTPUT_FORMATS = [
|
||||
"" => "",
|
||||
"JPEG (lossy)" => "jpg",
|
||||
"PNG (lossless)" => "png",
|
||||
"WEBP (lossy)" => "webp-lossy",
|
||||
"WEBP (lossless)" => "webp-lossless",
|
||||
];
|
||||
|
||||
/**
|
||||
* Needs to be after upload, but before the processing extensions
|
||||
*/
|
||||
public function get_priority(): int
|
||||
{
|
||||
return 45;
|
||||
}
|
||||
|
||||
|
||||
public function onInitExt(InitExtEvent $event)
|
||||
{
|
||||
global $config;
|
||||
$config->set_default_bool('transcode_enabled', true);
|
||||
$config->set_default_bool('transcode_upload', false);
|
||||
$config->set_default_string('transcode_engine', "gd");
|
||||
$config->set_default_int('transcode_quality', 80);
|
||||
|
||||
foreach(array_values(self::INPUT_FORMATS) as $format) {
|
||||
$config->set_default_string('transcode_upload_'.$format, "");
|
||||
}
|
||||
}
|
||||
|
||||
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
||||
{
|
||||
global $user, $config;
|
||||
|
||||
if ($user->is_admin() && $config->get_bool("resize_enabled")) {
|
||||
$engine = $config->get_string("transcode_engine");
|
||||
if($this->can_convert_format($engine,$event->image->ext)) {
|
||||
$options = $this->get_supported_output_formats($engine, $event->image->ext);
|
||||
$event->add_part($this->theme->get_transcode_html($event->image, $options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function onSetupBuilding(SetupBuildingEvent $event)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$engine = $config->get_string("transcode_engine");
|
||||
|
||||
|
||||
$sb = new SetupBlock("Image Transcode");
|
||||
$sb->add_bool_option("transcode_enabled", "Allow transcoding images: ");
|
||||
$sb->add_bool_option("transcode_upload", "<br>Transcode on upload: ");
|
||||
$sb->add_choice_option('transcode_engine',self::CONVERSION_ENGINES,"<br />Transcode engine: ");
|
||||
foreach(self::INPUT_FORMATS as $display=>$format) {
|
||||
if(in_array($format, self::ENGINE_INPUT_SUPPORT[$engine])) {
|
||||
$outputs = $this->get_supported_output_formats($engine, $format);
|
||||
$sb->add_choice_option('transcode_upload_'.$format,$outputs,"<br />$display to: ");
|
||||
}
|
||||
}
|
||||
$sb->add_int_option("transcode_quality", "<br/>Lossy format quality: ");
|
||||
$event->panel->add_block($sb);
|
||||
}
|
||||
|
||||
public function onDataUpload(DataUploadEvent $event)
|
||||
{
|
||||
global $config, $page;
|
||||
|
||||
if ($config->get_bool("transcode_upload") == true) {
|
||||
$ext = strtolower($event->type);
|
||||
|
||||
$ext = $this->clean_format($ext);
|
||||
|
||||
if($event->type=="gif"&&is_animated_gif($event->tmpname)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(in_array($ext, array_values(self::INPUT_FORMATS))) {
|
||||
$target_format = $config->get_string("transcode_upload_".$ext);
|
||||
if(empty($target_format)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$new_image = $this->transcode_image($event->tmpname, $ext, $target_format);
|
||||
$event->set_type($this->determine_ext($target_format));
|
||||
$event->set_tmpname($new_image);
|
||||
} catch(Exception $e) {
|
||||
log_error("transcode","Error while performing upload transcode: ".$e->getMessage());
|
||||
// We don't want to interfere with the upload process,
|
||||
// so if something goes wrong the untranscoded image jsut continues
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function onPageRequest(PageRequestEvent $event)
|
||||
{
|
||||
global $page, $user;
|
||||
|
||||
if ($event->page_matches("transcode") && $user->is_admin()) {
|
||||
$image_id = int_escape($event->get_arg(0));
|
||||
if (empty($image_id)) {
|
||||
$image_id = isset($_POST['image_id']) ? int_escape($_POST['image_id']) : null;
|
||||
}
|
||||
// Try to get the image ID
|
||||
if (empty($image_id)) {
|
||||
throw new ImageTranscodeException("Can not resize Image: No valid Image ID given.");
|
||||
}
|
||||
$image_obj = Image::by_id($image_id);
|
||||
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'])) {
|
||||
|
||||
try {
|
||||
$this->transcode_and_replace_image($image_obj, $_POST['transcode_format']);
|
||||
$page->set_mode("redirect");
|
||||
$page->set_redirect(make_link("post/view/".$image_id));
|
||||
} catch (ImageTranscodeException $e) {
|
||||
$this->theme->display_transcode_error($page, "Error Transcoding", $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
||||
{
|
||||
global $user, $config;
|
||||
|
||||
$engine = $config->get_string("transcode_engine");
|
||||
|
||||
if ($user->is_admin()) {
|
||||
$event->add_action("bulk_transcode","Transcode","",$this->theme->get_transcode_picker_html($this->get_supported_output_formats($engine)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function onBulkAction(BulkActionEvent $event)
|
||||
{
|
||||
global $user, $database;
|
||||
|
||||
switch($event->action) {
|
||||
case "bulk_transcode":
|
||||
if (!isset($_POST['transcode_format'])) {
|
||||
return;
|
||||
}
|
||||
if ($user->is_admin()) {
|
||||
$format = $_POST['transcode_format'];
|
||||
$total = 0;
|
||||
foreach ($event->items as $id) {
|
||||
try {
|
||||
$database->beginTransaction();
|
||||
$image = Image::by_id($id);
|
||||
if($image==null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->transcode_and_replace_image($image, $format);
|
||||
// If a subsequent transcode fails, the database need to have everything about the previous transcodes recorded already,
|
||||
// otherwise the image entries will be stuck pointing to missing image files
|
||||
$database->commit();
|
||||
$total++;
|
||||
} catch(Exception $e) {
|
||||
log_error("transcode", "Error while bulk transcode on item $id to $format: ".$e->getMessage());
|
||||
try {
|
||||
$database->rollback();
|
||||
} catch (Exception $e) {}
|
||||
}
|
||||
}
|
||||
flash_message("Transcoded $total items");
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function clean_format($format): ?string {
|
||||
if(array_key_exists($format, self::FORMAT_ALIASES)) {
|
||||
return self::FORMAT_ALIASES[$format];
|
||||
}
|
||||
return $format;
|
||||
}
|
||||
|
||||
private function can_convert_format($engine, $format): bool
|
||||
{
|
||||
$format = $this->clean_format($format);
|
||||
if(!in_array($format, self::ENGINE_INPUT_SUPPORT[$engine])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function get_supported_output_formats($engine, ?String $omit_format = null): array
|
||||
{
|
||||
$omit_format = $this->clean_format($omit_format);
|
||||
$output = [];
|
||||
foreach(self::OUTPUT_FORMATS as $key=>$value) {
|
||||
if($value=="") {
|
||||
$output[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
if(in_array($value, self::ENGINE_OUTPUT_SUPPORT[$engine])
|
||||
&&(empty($omit_format)||$omit_format!=$this->determine_ext($value))) {
|
||||
$output[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function determine_ext(String $format): String
|
||||
{
|
||||
switch($format) {
|
||||
case "webp-lossless":
|
||||
case "webp-lossy":
|
||||
return "webp";
|
||||
default:
|
||||
return $format;
|
||||
}
|
||||
}
|
||||
|
||||
private function transcode_and_replace_image(Image $image_obj, String $target_format)
|
||||
{
|
||||
$target_format = $this->clean_format($target_format);
|
||||
$original_file = warehouse_path("images", $image_obj->hash);
|
||||
|
||||
$tmp_filename = $this->transcode_image($original_file, $image_obj->ext, $target_format);
|
||||
|
||||
$new_image = new Image();
|
||||
$new_image->hash = md5_file($tmp_filename);
|
||||
$new_image->filesize = filesize($tmp_filename);
|
||||
$new_image->filename = $image_obj->filename;
|
||||
$new_image->width = $image_obj->width;
|
||||
$new_image->height = $image_obj->height;
|
||||
$new_image->ext = $this->determine_ext($target_format);
|
||||
|
||||
/* Move the new image into the main storage location */
|
||||
$target = warehouse_path("images", $new_image->hash);
|
||||
if (!@copy($tmp_filename, $target)) {
|
||||
throw new ImageTranscodeException("Failed to copy new image file from temporary location ({$tmp_filename}) to archive ($target)");
|
||||
}
|
||||
|
||||
/* Remove temporary file */
|
||||
@unlink($tmp_filename);
|
||||
|
||||
send_event(new ImageReplaceEvent($image_obj->id, $new_image));
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function transcode_image(String $source_name, String $source_format, string $target_format): string
|
||||
{
|
||||
global $config;
|
||||
|
||||
if($source_format==$this->determine_ext($target_format)) {
|
||||
throw new ImageTranscodeException("Source and target formats are the same: ".$source_format);
|
||||
}
|
||||
|
||||
$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(!in_array($target_format, self::ENGINE_OUTPUT_SUPPORT[$engine])) {
|
||||
throw new ImageTranscodeException("Engine $engine does not support output format $target_format");
|
||||
}
|
||||
|
||||
switch($engine) {
|
||||
case "gd":
|
||||
return $this->transcode_image_gd($source_name, $source_format, $target_format);
|
||||
case "convert":
|
||||
return $this->transcode_image_convert($source_name, $source_format, $target_format);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function transcode_image_gd(String $source_name, String $source_format, string $target_format): string
|
||||
{
|
||||
global $config;
|
||||
|
||||
$q = $config->get_int("transcode_quality");
|
||||
|
||||
$tmp_name = tempnam("/tmp", "shimmie_transcode");
|
||||
|
||||
$image = imagecreatefromstring(file_get_contents($source_name));
|
||||
try {
|
||||
$result = false;
|
||||
switch($target_format) {
|
||||
case "webp-lossy":
|
||||
$result = imagewebp($image, $tmp_name, $q);
|
||||
break;
|
||||
case "png":
|
||||
$result = imagepng($image, $tmp_name, 9);
|
||||
break;
|
||||
case "jpg":
|
||||
// In case of alpha channels
|
||||
$width = imagesx($image);
|
||||
$height = imagesy($image);
|
||||
$new_image = imagecreatetruecolor($width, $height);
|
||||
if($new_image===false) {
|
||||
throw new ImageTranscodeException("Could not create image with dimensions $width x $height");
|
||||
}
|
||||
try{
|
||||
$black = imagecolorallocate($new_image, 0, 0, 0);
|
||||
if($black===false) {
|
||||
throw new ImageTranscodeException("Could not allocate background color");
|
||||
}
|
||||
if(imagefilledrectangle($new_image, 0, 0, $width, $height, $black)===false) {
|
||||
throw new ImageTranscodeException("Could not fill background color");
|
||||
}
|
||||
if(imagecopy($new_image, $image, 0, 0, 0, 0, $width, $height)===false) {
|
||||
throw new ImageTranscodeException("Could not copy source image to new image");
|
||||
}
|
||||
$result = imagejpeg($new_image, $tmp_name, $q);
|
||||
} finally {
|
||||
imagedestroy($new_image);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if($result===false) {
|
||||
throw new ImageTranscodeException("Error while transcoding ".$source_name." to ".$target_format);
|
||||
}
|
||||
return $tmp_name;
|
||||
} finally {
|
||||
imagedestroy($image);
|
||||
}
|
||||
}
|
||||
|
||||
private function transcode_image_convert(String $source_name, String $source_format, string $target_format): string
|
||||
{
|
||||
global $config;
|
||||
|
||||
$q = $config->get_int("transcode_quality");
|
||||
$convert = $config->get_string("thumb_convert_path");
|
||||
|
||||
if($convert==null||$convert=="")
|
||||
{
|
||||
throw new ImageTranscodeException("ImageMagick path not configured");
|
||||
}
|
||||
$ext = $this->determine_ext($target_format);
|
||||
|
||||
$args = " -flatten ";
|
||||
$bg = "none";
|
||||
switch($target_format) {
|
||||
case "webp-lossless":
|
||||
$args .= '-define webp:lossless=true';
|
||||
break;
|
||||
case "webp-lossy":
|
||||
$args .= '';
|
||||
break;
|
||||
case "png":
|
||||
$args .= '-define png:compression-level=9';
|
||||
break;
|
||||
default:
|
||||
$bg = "black";
|
||||
break;
|
||||
}
|
||||
$tmp_name = tempnam("/tmp", "shimmie_transcode");
|
||||
|
||||
$format = '"%s" %s -quality %u -background %s "%s" %s:"%s"';
|
||||
$cmd = sprintf($format, $convert, $args, $q, $bg, $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);
|
||||
|
||||
log_debug('transcode', "Transcoding with command `$cmd`, returns $ret");
|
||||
|
||||
if($ret!==0) {
|
||||
throw new ImageTranscodeException("Transcoding failed with command ".$cmd);
|
||||
}
|
||||
|
||||
return $tmp_name;
|
||||
}
|
||||
|
||||
}
|
41
ext/transcode/theme.php
Normal file
41
ext/transcode/theme.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
class TranscodeImageTheme extends Themelet
|
||||
{
|
||||
/*
|
||||
* Display a link to resize an image
|
||||
*/
|
||||
public function get_transcode_html(Image $image, array $options)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$html = "
|
||||
".make_form(make_link("transcode/{$image->id}"), 'POST')."
|
||||
<input type='hidden' name='image_id' value='{$image->id}'>
|
||||
".$this->get_transcode_picker_html($options)."
|
||||
<br><input id='transcodebutton' type='submit' value='Transcode'>
|
||||
</form>
|
||||
";
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function get_transcode_picker_html(array $options) {
|
||||
$html = "<select id='transcode_format' name='transcode_format' required='required' >";
|
||||
foreach($options as $display=>$value) {
|
||||
$html .= "<option value='$value'>$display</option>";
|
||||
}
|
||||
|
||||
return $html."</select>";
|
||||
|
||||
}
|
||||
|
||||
public function display_transcode_error(Page $page, string $title, string $message)
|
||||
{
|
||||
$page->set_title("Transcode Image");
|
||||
$page->set_heading("Transcode Image");
|
||||
$page->add_block(new NavBlock());
|
||||
$page->add_block(new Block($title, $message));
|
||||
}
|
||||
|
||||
}
|
|
@ -18,9 +18,15 @@ class DataUploadEvent extends Event
|
|||
/** @var string */
|
||||
public $hash;
|
||||
/** @var string */
|
||||
public $type;
|
||||
public $type = "";
|
||||
/** @var int */
|
||||
public $image_id = -1;
|
||||
/** @var bool */
|
||||
public $handled = false;
|
||||
/** @var bool */
|
||||
public $merged = false;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Some data is being uploaded.
|
||||
|
@ -29,21 +35,39 @@ class DataUploadEvent extends Event
|
|||
*/
|
||||
public function __construct(string $tmpname, array $metadata)
|
||||
{
|
||||
global $config;
|
||||
|
||||
assert(file_exists($tmpname));
|
||||
assert(is_string($metadata["filename"]));
|
||||
assert(is_string($metadata["extension"]));
|
||||
assert(is_array($metadata["tags"]));
|
||||
assert(is_string($metadata["source"]) || is_null($metadata["source"]));
|
||||
|
||||
$this->tmpname = $tmpname;
|
||||
|
||||
$this->metadata = $metadata;
|
||||
|
||||
$this->set_tmpname($tmpname);
|
||||
|
||||
if($config->get_bool("upload_use_mime")) {
|
||||
$this->set_type(get_extension_from_mime($tmpname));
|
||||
} else {
|
||||
if(array_key_exists('extension',$metadata)&&!empty($metadata['extension'])) {
|
||||
$this->type = strtolower($metadata['extension']);
|
||||
} else {
|
||||
throw new UploadException("Could not determine extension for file ".$metadata["filename"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function set_type(String $type) {
|
||||
$this->type = strtolower($type);
|
||||
$this->metadata["extension"] = $this->type;
|
||||
}
|
||||
|
||||
public function set_tmpname(String $tmpname) {
|
||||
$this->tmpname = $tmpname;
|
||||
$this->metadata['hash'] = md5_file($tmpname);
|
||||
$this->metadata['size'] = filesize($tmpname);
|
||||
|
||||
// useful for most file handlers, so pull directly into fields
|
||||
$this->hash = $this->metadata['hash'];
|
||||
$this->type = strtolower($metadata['extension']);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,6 +100,7 @@ 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;
|
||||
|
||||
|
@ -108,6 +133,7 @@ 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);
|
||||
}
|
||||
|
||||
|
@ -320,9 +346,6 @@ class Upload extends Extension
|
|||
|
||||
$event = new DataUploadEvent($file['tmp_name'], $metadata);
|
||||
send_event($event);
|
||||
if ($event->image_id == -1) {
|
||||
throw new UploadException("File type not recognised");
|
||||
}
|
||||
$page->add_http_header("X-Shimmie-Image-ID: ".int_escape($event->image_id));
|
||||
} catch (UploadException $ex) {
|
||||
$this->theme->display_upload_error(
|
||||
|
|
|
@ -219,7 +219,7 @@ class UploadTheme extends Themelet
|
|||
$html .= ' (Drag & drop onto your bookmarks toolbar, then click when looking at an image)';
|
||||
|
||||
// Bookmarklet checks if shimmie supports ext. If not, won't upload to site/shows alert saying not supported.
|
||||
$supported_ext = "jpg jpeg gif png";
|
||||
$supported_ext = "jpg jpeg gif png webp";
|
||||
if (class_exists("FlashFileHandler")) {
|
||||
$supported_ext .= " swf";
|
||||
}
|
||||
|
|
Reference in a new issue