2021-12-14 18:32:47 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
2012-08-18 20:10:35 +00:00
|
|
|
|
2020-06-14 16:05:55 +00:00
|
|
|
// TODO Add warning that rotate doesn't support lossless webp output
|
|
|
|
|
2012-08-18 20:10:35 +00:00
|
|
|
/**
|
|
|
|
* This class is just a wrapper around SCoreException.
|
|
|
|
*/
|
2019-05-28 16:59:38 +00:00
|
|
|
class ImageRotateException extends SCoreException
|
|
|
|
{
|
2012-08-18 20:10:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This class handles image rotate requests.
|
|
|
|
*/
|
2019-05-28 16:59:38 +00:00
|
|
|
class RotateImage extends Extension
|
|
|
|
{
|
2020-02-04 00:46:36 +00:00
|
|
|
/** @var RotateImageTheme */
|
2021-03-14 23:43:50 +00:00
|
|
|
protected ?Themelet $theme;
|
2020-02-04 00:46:36 +00:00
|
|
|
|
2021-12-14 18:32:47 +00:00
|
|
|
public const SUPPORTED_MIME = [MimeType::JPEG, MimeType::PNG, MimeType::GIF, MimeType::WEBP];
|
2019-06-18 13:06:05 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
public function onInitExt(InitExtEvent $event)
|
|
|
|
{
|
|
|
|
global $config;
|
|
|
|
$config->set_default_bool('rotate_enabled', true);
|
|
|
|
$config->set_default_int('rotate_default_deg', 180);
|
|
|
|
}
|
2012-08-18 20:10:35 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
|
|
|
|
{
|
|
|
|
global $user, $config;
|
2019-09-29 18:00:51 +00:00
|
|
|
if ($user->can(Permissions::EDIT_FILES) && $config->get_bool("rotate_enabled")
|
2020-06-14 16:05:55 +00:00
|
|
|
&& MimeType::matches_array($event->image->get_mime(), self::SUPPORTED_MIME)) {
|
2019-05-28 16:59:38 +00:00
|
|
|
/* Add a link to rotate the image */
|
|
|
|
$event->add_part($this->theme->get_rotate_html($event->image->id));
|
|
|
|
}
|
|
|
|
}
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
public function onSetupBuilding(SetupBuildingEvent $event)
|
|
|
|
{
|
2020-10-26 15:13:28 +00:00
|
|
|
$sb = $event->panel->create_new_block("Image Rotate");
|
2019-05-28 16:59:38 +00:00
|
|
|
$sb->add_bool_option("rotate_enabled", "Allow rotating images: ");
|
|
|
|
$sb->add_label("<br>Default Orientation: ");
|
|
|
|
$sb->add_int_option("rotate_default_deg");
|
|
|
|
$sb->add_label(" deg");
|
|
|
|
}
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
public function onPageRequest(PageRequestEvent $event)
|
|
|
|
{
|
|
|
|
global $page, $user;
|
2012-08-18 20:10:35 +00:00
|
|
|
|
2019-09-29 18:00:51 +00:00
|
|
|
if ($event->page_matches("rotate") && $user->can(Permissions::EDIT_FILES)) {
|
2019-05-28 16:59:38 +00:00
|
|
|
// Try to get the image ID
|
|
|
|
$image_id = int_escape($event->get_arg(0));
|
|
|
|
if (empty($image_id)) {
|
|
|
|
$image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null;
|
|
|
|
}
|
|
|
|
if (empty($image_id)) {
|
2020-10-26 15:21:17 +00:00
|
|
|
throw new ImageRotateException("Can not rotate Image: No valid Post ID given.");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
$image = Image::by_id($image_id);
|
|
|
|
if (is_null($image)) {
|
2020-10-26 15:21:17 +00:00
|
|
|
$this->theme->display_error(404, "Post not found", "No image in the database has the ID #$image_id");
|
2019-05-28 16:59:38 +00:00
|
|
|
} else {
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/* Check if options were given to rotate an image. */
|
|
|
|
if (isset($_POST['rotate_deg'])) {
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/* get options */
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
$deg = 0;
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
if (isset($_POST['rotate_deg'])) {
|
|
|
|
$deg = int_escape($_POST['rotate_deg']);
|
|
|
|
}
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/* Attempt to rotate the image */
|
|
|
|
try {
|
|
|
|
$this->rotate_image($image_id, $deg);
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
//$this->theme->display_rotate_page($page, $image_id);
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-06-19 01:58:28 +00:00
|
|
|
$page->set_mode(PageMode::REDIRECT);
|
2019-05-28 16:59:38 +00:00
|
|
|
$page->set_redirect(make_link("post/view/".$image_id));
|
|
|
|
} catch (ImageRotateException $e) {
|
|
|
|
$this->theme->display_rotate_error($page, "Error Rotating", $e->error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-07 19:53:59 +00:00
|
|
|
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
// Private functions
|
|
|
|
/* ----------------------------- */
|
|
|
|
private function rotate_image(int $image_id, int $deg)
|
|
|
|
{
|
|
|
|
if (($deg <= -360) || ($deg >= 360)) {
|
|
|
|
throw new ImageRotateException("Invalid options for rotation angle. ($deg)");
|
|
|
|
}
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
$image_obj = Image::by_id($image_id);
|
|
|
|
$hash = $image_obj->hash;
|
|
|
|
if (is_null($hash)) {
|
2020-10-26 15:21:17 +00:00
|
|
|
throw new ImageRotateException("Post does not have a hash associated with it.");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-06-15 16:18:52 +00:00
|
|
|
$image_filename = warehouse_path(Image::IMAGE_DIR, $hash);
|
2019-10-02 09:10:47 +00:00
|
|
|
if (file_exists($image_filename)===false) {
|
2019-05-28 16:59:38 +00:00
|
|
|
throw new ImageRotateException("$image_filename does not exist.");
|
|
|
|
}
|
2019-06-09 18:22:48 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
$info = getimagesize($image_filename);
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-06-24 15:05:16 +00:00
|
|
|
$memory_use = Media::calc_memory_use($info);
|
2019-05-28 16:59:38 +00:00
|
|
|
$memory_limit = get_memory_limit();
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
if ($memory_use > $memory_limit) {
|
|
|
|
throw new ImageRotateException("The image is too large to rotate given the memory limits. ($memory_use > $memory_limit)");
|
|
|
|
}
|
2019-08-07 19:53:59 +00:00
|
|
|
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/* Attempt to load the image */
|
2019-06-14 12:16:58 +00:00
|
|
|
$image = imagecreatefromstring(file_get_contents($image_filename));
|
|
|
|
if ($image == false) {
|
|
|
|
throw new ImageRotateException("Could not load image: ".$image_filename);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-06-09 18:22:48 +00:00
|
|
|
$background_color = 0;
|
2019-06-14 12:47:50 +00:00
|
|
|
switch ($info[2]) {
|
2019-06-09 18:22:48 +00:00
|
|
|
case IMAGETYPE_PNG:
|
|
|
|
case IMAGETYPE_WEBP:
|
|
|
|
$background_color = imagecolorallocatealpha($image, 0, 0, 0, 127);
|
|
|
|
break;
|
|
|
|
}
|
2019-06-14 12:47:50 +00:00
|
|
|
if ($background_color===false) {
|
2019-06-12 22:40:43 +00:00
|
|
|
throw new ImageRotateException("Unable to allocate transparent color");
|
|
|
|
}
|
|
|
|
|
2019-06-09 18:22:48 +00:00
|
|
|
$image_rotated = imagerotate($image, $deg, $background_color);
|
2019-06-14 12:47:50 +00:00
|
|
|
if ($image_rotated===false) {
|
2019-06-12 22:40:43 +00:00
|
|
|
throw new ImageRotateException("Image rotate failed");
|
|
|
|
}
|
2019-06-09 18:22:48 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/* Temp storage while we rotate */
|
|
|
|
$tmp_filename = tempnam(ini_get('upload_tmp_dir'), 'shimmie_rotate');
|
|
|
|
if (empty($tmp_filename)) {
|
|
|
|
throw new ImageRotateException("Unable to save temporary image file.");
|
|
|
|
}
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/* Output to the same format as the original image */
|
|
|
|
switch ($info[2]) {
|
2019-06-09 18:22:48 +00:00
|
|
|
case IMAGETYPE_GIF: $result = imagegif($image_rotated, $tmp_filename); break;
|
|
|
|
case IMAGETYPE_JPEG: $result = imagejpeg($image_rotated, $tmp_filename); break;
|
2019-06-14 12:47:50 +00:00
|
|
|
case IMAGETYPE_PNG: $result = imagepng($image_rotated, $tmp_filename, 9); break;
|
2019-06-09 18:22:48 +00:00
|
|
|
case IMAGETYPE_WEBP: $result = imagewebp($image_rotated, $tmp_filename); break;
|
2019-06-14 12:47:50 +00:00
|
|
|
case IMAGETYPE_BMP: $result = imagebmp($image_rotated, $tmp_filename, true); break;
|
2019-05-28 16:59:38 +00:00
|
|
|
default:
|
|
|
|
throw new ImageRotateException("Unsupported image type.");
|
|
|
|
}
|
2019-06-09 18:22:48 +00:00
|
|
|
|
2019-06-14 12:47:50 +00:00
|
|
|
if ($result===false) {
|
2019-06-09 18:22:48 +00:00
|
|
|
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;
|
2022-03-21 05:47:13 +00:00
|
|
|
$new_image->posted = $image_obj->posted;
|
2019-06-09 18:22:48 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/* Move the new image into the main storage location */
|
2019-06-15 16:18:52 +00:00
|
|
|
$target = warehouse_path(Image::IMAGE_DIR, $new_image->hash);
|
2019-05-28 16:59:38 +00:00
|
|
|
if (!@copy($tmp_filename, $target)) {
|
|
|
|
throw new ImageRotateException("Failed to copy new image file from temporary location ({$tmp_filename}) to archive ($target)");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Remove temporary file */
|
|
|
|
@unlink($tmp_filename);
|
|
|
|
|
2019-06-09 18:22:48 +00:00
|
|
|
send_event(new ImageReplaceEvent($image_id, $new_image));
|
|
|
|
|
2020-10-09 12:47:42 +00:00
|
|
|
log_info("rotate", "Rotated >>{$image_id} - New hash: {$new_image->hash}");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
2012-08-18 20:10:35 +00:00
|
|
|
}
|