From 66df295ec178e2a6c4cbf273ef148e10dae6ba12 Mon Sep 17 00:00:00 2001
From: Matthew Barbour
Date: Wed, 5 Jun 2019 18:03:22 -0500
Subject: [PATCH 01/19] Bulk action extension
---
ext/admin/main.php | 14 +--
ext/bulk_actions/main.php | 228 +++++++++++++++++++++++++++++++++++++
ext/bulk_actions/script.js | 197 ++++++++++++++++++++++++++++++++
ext/bulk_actions/style.css | 10 ++
ext/bulk_actions/theme.php | 74 ++++++++++++
ext/rating/main.php | 43 +++++--
ext/rating/theme.php | 9 ++
ext/regen_thumb/main.php | 51 +++++++--
ext/tag_edit/main.php | 14 +--
9 files changed, 612 insertions(+), 28 deletions(-)
create mode 100644 ext/bulk_actions/main.php
create mode 100644 ext/bulk_actions/script.js
create mode 100644 ext/bulk_actions/style.css
create mode 100644 ext/bulk_actions/theme.php
diff --git a/ext/admin/main.php b/ext/admin/main.php
index 4ca59eb7..212b07fb 100644
--- a/ext/admin/main.php
+++ b/ext/admin/main.php
@@ -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()
{
diff --git a/ext/bulk_actions/main.php b/ext/bulk_actions/main.php
new file mode 100644
index 00000000..66ce1d6b
--- /dev/null
+++ b/ext/bulk_actions/main.php
@@ -0,0 +1,228 @@
+, contributions by Shish and Agasa.
+ */
+
+
+class BulkActionBlockBuildingEvent extends Event {
+ /** @var array */
+ public $actions = array();
+ /**
+ * @param string $name
+ */
+ public function add_action(String $action, 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,
+ "position"=>$position)
+ );
+ }
+}
+
+class BulkActionEvent extends Event {
+ public $action;
+ public $items;
+ 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;
+
+ $this->theme->display_selector($page, $event, $config, Tag::implode($event->search_terms));
+ }
+
+ public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
+ {
+ global $user;
+
+ if ($user->can("delete_image")) {
+ $event->add_action("Delete","Delete selected images?","",10);
+ }
+
+ if ($user->can("bulk_edit_image_tag")) {
+ $event->add_action("Tag","",$this->theme->render_tag_input(),10);
+ }
+
+ if ($user->can("bulk_edit_image_source")) {
+ $event->add_action("Set Source","",$this->theme->render_source_input(),10);
+ }
+
+ }
+
+ public function onBulkAction(BulkActionEvent $event)
+ {
+ global $user;
+
+ switch($event->action) {
+ case "Delete":
+ if ($user->can("delete_image")) {
+ $this->delete_items($event->items);
+ }
+ break;
+ case "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;
+ }
+
+ $this->tag_items($event->items, $tags, $replace);
+ }
+ break;
+ case "Set Source":
+ if (!isset($_POST['bulk_source'])) {
+ return;
+ }
+ if ($user->can("bulk_edit_image_source")) {
+ $source = $_POST['bulk_source'];
+ $this->set_source($event->items, $source);
+ }
+ 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)) {
+ $item = Image::by_id(int_escape($id));
+ array_push($items, $item);
+ }
+ }
+ }
+ if(sizeof($items)>0) {
+ reset($items); // rewind to first element in array.
+ $newEvent = new BulkActionEvent($action, $event, $items);
+ send_event($newEvent);
+ }
+ } else if(isset($_POST['bulk_query'])&&$_POST['bulk_query']!="") {
+ $query = $_POST['bulk_query'];
+ if($query!=null&&$query!="") {
+ $n = 0;
+ while (true) {
+ $items = Image::find_images($n, 100, Tag::explode($query));
+ if (count($items) == 0) {
+ break;
+ }
+
+ reset($items); // rewind to first element in array.
+ $newEvent = new BulkActionEvent($action, $event, $items);
+ send_event($newEvent);
+
+ $n += 100;
+ }
+ }
+ }
+
+
+
+ $page->set_mode("redirect");
+ if (!isset($_SERVER['HTTP_REFERER'])) {
+ $_SERVER['HTTP_REFERER'] = make_link();
+ }
+ $page->set_redirect($_SERVER['HTTP_REFERER']);
+ }
+ }
+
+ private function delete_items(array $items) {
+ $total = 0;
+ foreach ($items as $item) {
+ try {
+ send_event(new ImageDeletionEvent($item));
+ $total++;
+ } catch(Exception $e) {
+ flash_message("Error while removing $item->id: ".$e->getMessage(), "error");
+ }
+ }
+
+ flash_message("Deleted $total items");
+
+ }
+
+ private function tag_items(array $items, string $tags, bool $replace) {
+ $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 $item) {
+ send_event(new TagSetEvent($item, $tags));
+ $total++;
+ }
+ } else {
+ foreach ($items as $item) {
+ $img_tags = [];
+ if (!empty($neg_tag_array)) {
+ $img_tags = array_merge($pos_tag_array, $item->get_tag_array());
+ $img_tags = array_diff($img_tags, $neg_tag_array);
+ } else {
+ $img_tags =array_merge($tags, $item->get_tag_array());
+ }
+ send_event(new TagSetEvent($item, $img_tags));
+ $total++;
+ }
+ }
+
+ flash_message("Tagged $total items");
+ }
+
+ private function set_source(array $items, String $source) {
+ $total = 0;
+ foreach ($items as $item) {
+ try {
+ send_event(new SourceSetEvent($item, $source));
+ $total++;
+ } catch(Exception $e) {
+ flash_message("Error while setting source for $item->id: ".$e->getMessage(), "error");
+ }
+ }
+
+ flash_message("Set source for $total items");
+
+ }
+}
diff --git a/ext/bulk_actions/script.js b/ext/bulk_actions/script.js
new file mode 100644
index 00000000..f96466e1
--- /dev/null
+++ b/ext/bulk_actions/script.js
@@ -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="bulk_action"]').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_itemis_logged_in()) {
+ $event = new BulkActionBlockBuildingEvent();
+ send_event($event);
+
+ if(sizeof($event->actions)==0)
+ return;
+
+ $body ="
+
+
+
+ Click on images to mark them.
+
+
+ ";
+
+ $hasQuery = ($query!=null&&$query!="");
+
+ if($hasQuery) {
+ $body .= "
";
+ }
+ usort($event->actions, array($this, "sort_blocks"));
+
+ foreach($event->actions as $action) {
+ $body .= "".make_form(make_link("bulk_action"), "POST", False, "", "return validate_selections(this,'".html_escape($action["confirmation_message"])."');").
+ "".
+ "".
+ "".
+ $action["block"].
+ "".
+ "
";
+ }
+
+ if(!$hasQuery) {
+ $body .= "";
+ }
+ $block = new Block("Bulk Actions", $body, "left", 30);
+ $page->add_block($block);
+ }
+ }
+
+ public function render_tag_input() {
+ return "".
+ "";
+ }
+
+ public function render_source_input() {
+ return "";
+ }
+
+}
diff --git a/ext/rating/main.php b/ext/rating/main.php
index eba2c262..34f67b39 100644
--- a/ext/rating/main.php
+++ b/ext/rating/main.php
@@ -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,35 @@ class Ratings extends Extension
}
}
+ public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
+ {
+ global $user;
+
+ if ($user->is_admin()) {
+ $event->add_action("Set Rating","",$this->theme->get_selection_rater_html("bulk_rating"));
+ }
+
+ }
+
+ public function onBulkAction(BulkActionEvent $event)
+ {
+ global $user;
+
+ switch($event->action) {
+ case "Set Rating":
+ if (!isset($_POST['bulk_rating'])) {
+ return;
+ }
+ if ($user->is_admin()) {
+ $rating = $_POST['bulk_rating'];
+ foreach ($event->items as $image) {
+ send_event(new RatingSetEvent($image, $rating));
+ }
+ }
+ break;
+ }
+ }
+
public function onPageRequest(PageRequestEvent $event)
{
global $user, $page;
diff --git a/ext/rating/theme.php b/ext/rating/theme.php
index 241c20c7..67f85831 100644
--- a/ext/rating/theme.php
+++ b/ext/rating/theme.php
@@ -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 "";
+ }
}
diff --git a/ext/regen_thumb/main.php b/ext/regen_thumb/main.php
index 5df24698..40a49487 100644
--- a/ext/regen_thumb/main.php
+++ b/ext/regen_thumb/main.php
@@ -15,14 +15,23 @@
class RegenThumb extends Extension
{
+ public function regenerate_thumbnail($image)
+ {
+ global $database;
+
+ send_event(new ThumbnailGenerationEvent($image->hash, $image->ext, true));
+ $database->cache->delete("thumb-block:{$image->id}");
+ }
+
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 +39,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 +55,40 @@ 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("Regen Thumbnails");
+ }
+
+ }
+
+ public function onBulkAction(BulkActionEvent $event)
+ {
+ global $user;
+
+ switch($event->action) {
+ case "Regen Thumbnails":
+ if ($user->can("delete_image")) {
+ $total = 0;
+ foreach ($event->items as $image) {
+ $this->regenerate_thumbnail($image);
+ $total++;
+ }
+ flash_message("Regenerated thumbnails for $total items");
+ }
+ break;
}
}
+
}
diff --git a/ext/tag_edit/main.php b/ext/tag_edit/main.php
index 8a74750a..19f0dfa0 100644
--- a/ext/tag_edit/main.php
+++ b/ext/tag_edit/main.php
@@ -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)
{
From 8612a07a5a3cca518ba53fcb0db6de250b701051 Mon Sep 17 00:00:00 2001
From: Matthew Barbour
Date: Wed, 5 Jun 2019 19:37:07 -0500
Subject: [PATCH 02/19] cleanup
---
ext/bulk_actions/main.php | 95 ++++++++++++++++++++------------------
ext/bulk_actions/theme.php | 65 +++++++++++++-------------
2 files changed, 82 insertions(+), 78 deletions(-)
diff --git a/ext/bulk_actions/main.php b/ext/bulk_actions/main.php
index 66ce1d6b..bae4353f 100644
--- a/ext/bulk_actions/main.php
+++ b/ext/bulk_actions/main.php
@@ -9,36 +9,40 @@
*/
-class BulkActionBlockBuildingEvent extends Event {
- /** @var array */
- public $actions = array();
- /**
- * @param string $name
- */
- public function add_action(String $action, String $confirmation_message = "", String $block = "", int $position = 40) {
- if($block==null)
+class BulkActionBlockBuildingEvent extends Event
+{
+ /** @var array */
+ public $actions = array();
+
+ public function add_action(String $action, 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,
- "position"=>$position)
+ array_push(
+ $this->actions,
+ array(
+ "block" => $block,
+ "confirmation_message" => $confirmation_message,
+ "action" => $action,
+ "position" => $position
+ )
);
- }
+ }
}
-class BulkActionEvent extends Event {
+class BulkActionEvent extends Event
+{
public $action;
public $items;
public $page_request;
- function __construct (String $action, PageRequestEvent $pageRequestEvent, array $items) {
+ function __construct(String $action, PageRequestEvent $pageRequestEvent, array $items)
+ {
$this->action = $action;
$this->page_request = $pageRequestEvent;
$this->items = $items;
}
-
}
class BulkActions extends Extension
@@ -46,33 +50,32 @@ class BulkActions extends Extension
public function onPostListBuilding(PostListBuildingEvent $event)
{
global $config, $page, $user;
-
+
$this->theme->display_selector($page, $event, $config, Tag::implode($event->search_terms));
}
- public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
+ public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
{
global $user;
if ($user->can("delete_image")) {
- $event->add_action("Delete","Delete selected images?","",10);
+ $event->add_action("Delete", "Delete selected images?", "", 10);
}
if ($user->can("bulk_edit_image_tag")) {
- $event->add_action("Tag","",$this->theme->render_tag_input(),10);
+ $event->add_action("Tag", "", $this->theme->render_tag_input(), 10);
}
if ($user->can("bulk_edit_image_source")) {
- $event->add_action("Set Source","",$this->theme->render_source_input(),10);
+ $event->add_action("Set Source", "", $this->theme->render_source_input(), 10);
}
-
}
public function onBulkAction(BulkActionEvent $event)
{
global $user;
- switch($event->action) {
+ switch ($event->action) {
case "Delete":
if ($user->can("delete_image")) {
$this->delete_items($event->items);
@@ -85,10 +88,10 @@ class BulkActions extends Extension
if ($user->can("bulk_edit_image_tag")) {
$tags = $_POST['bulk_tags'];
$replace = false;
- if(isset($_POST['bulk_tags_replace']) && $_POST['bulk_tags_replace']=="true") {
+ if (isset($_POST['bulk_tags_replace']) && $_POST['bulk_tags_replace'] == "true") {
$replace = true;
}
-
+
$this->tag_items($event->items, $tags, $replace);
}
break;
@@ -102,7 +105,6 @@ class BulkActions extends Extension
}
break;
}
-
}
public function onPageRequest(PageRequestEvent $event)
@@ -116,35 +118,35 @@ class BulkActions extends Extension
$action = $_POST['bulk_action'];
$items = [];
- if(isset($_POST['bulk_selected_ids'])&&$_POST['bulk_selected_ids']!="") {
+ if (isset($_POST['bulk_selected_ids']) && $_POST['bulk_selected_ids'] != "") {
$data = json_decode($_POST['bulk_selected_ids']);
- if(is_array($data)) {
+ if (is_array($data)) {
foreach ($data as $id) {
- if(is_numeric($id)) {
+ if (is_numeric($id)) {
$item = Image::by_id(int_escape($id));
array_push($items, $item);
}
}
}
- if(sizeof($items)>0) {
+ if (sizeof($items) > 0) {
reset($items); // rewind to first element in array.
$newEvent = new BulkActionEvent($action, $event, $items);
send_event($newEvent);
}
- } else if(isset($_POST['bulk_query'])&&$_POST['bulk_query']!="") {
+ } else if (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
$query = $_POST['bulk_query'];
- if($query!=null&&$query!="") {
+ if ($query != null && $query != "") {
$n = 0;
while (true) {
$items = Image::find_images($n, 100, Tag::explode($query));
if (count($items) == 0) {
break;
}
-
+
reset($items); // rewind to first element in array.
$newEvent = new BulkActionEvent($action, $event, $items);
send_event($newEvent);
-
+
$n += 100;
}
}
@@ -160,22 +162,23 @@ class BulkActions extends Extension
}
}
- private function delete_items(array $items) {
+ private function delete_items(array $items)
+ {
$total = 0;
foreach ($items as $item) {
try {
send_event(new ImageDeletionEvent($item));
$total++;
- } catch(Exception $e) {
- flash_message("Error while removing $item->id: ".$e->getMessage(), "error");
+ } catch (Exception $e) {
+ flash_message("Error while removing $item->id: " . $e->getMessage(), "error");
}
- }
+ }
flash_message("Deleted $total items");
-
}
- private function tag_items(array $items, string $tags, bool $replace) {
+ private function tag_items(array $items, string $tags, bool $replace)
+ {
$tags = Tag::explode($tags);
$pos_tag_array = [];
@@ -201,7 +204,7 @@ class BulkActions extends Extension
$img_tags = array_merge($pos_tag_array, $item->get_tag_array());
$img_tags = array_diff($img_tags, $neg_tag_array);
} else {
- $img_tags =array_merge($tags, $item->get_tag_array());
+ $img_tags = array_merge($tags, $item->get_tag_array());
}
send_event(new TagSetEvent($item, $img_tags));
$total++;
@@ -211,18 +214,18 @@ class BulkActions extends Extension
flash_message("Tagged $total items");
}
- private function set_source(array $items, String $source) {
+ private function set_source(array $items, String $source)
+ {
$total = 0;
foreach ($items as $item) {
try {
send_event(new SourceSetEvent($item, $source));
$total++;
- } catch(Exception $e) {
- flash_message("Error while setting source for $item->id: ".$e->getMessage(), "error");
+ } catch (Exception $e) {
+ flash_message("Error while setting source for $item->id: " . $e->getMessage(), "error");
}
}
flash_message("Set source for $total items");
-
}
}
diff --git a/ext/bulk_actions/theme.php b/ext/bulk_actions/theme.php
index 80675509..a6c3ad3c 100644
--- a/ext/bulk_actions/theme.php
+++ b/ext/bulk_actions/theme.php
@@ -2,24 +2,24 @@
class BulkActionsTheme extends Themelet
{
- private function sort_blocks($a, $b)
- {
- return $a["position"] - $b["position"];
- }
+ private function sort_blocks($a, $b)
+ {
+ return $a["position"] - $b["position"];
+ }
- public function display_selector(Page $page, Event $event, $config, $query)
- {
+ public function display_selector(Page $page, Event $event, $config, $query)
+ {
global $user;
- if($user->is_logged_in()) {
- $event = new BulkActionBlockBuildingEvent();
- send_event($event);
+ if ($user->is_logged_in()) {
+ $event = new BulkActionBlockBuildingEvent();
+ send_event($event);
- if(sizeof($event->actions)==0)
- return;
+ if (sizeof($event->actions) == 0)
+ return;
- $body ="
+ $body = "
@@ -37,38 +37,39 @@ class BulkActionsTheme extends Themelet
";
- $hasQuery = ($query!=null&&$query!="");
+ $hasQuery = ($query != null && $query != "");
- if($hasQuery) {
+ if ($hasQuery) {
$body .= "
";
}
- usort($event->actions, array($this, "sort_blocks"));
+ usort($event->actions, array($this, "sort_blocks"));
- foreach($event->actions as $action) {
- $body .= "".make_form(make_link("bulk_action"), "POST", False, "", "return validate_selections(this,'".html_escape($action["confirmation_message"])."');").
- "".
- "".
- "".
- $action["block"].
- "".
- "
";
+ foreach ($event->actions as $action) {
+ $body .= "" . make_form(make_link("bulk_action"), "POST", False, "", "return validate_selections(this,'" . html_escape($action["confirmation_message"]) . "');") .
+ "" .
+ "" .
+ "" .
+ $action["block"] .
+ "" .
+ "
";
}
- if(!$hasQuery) {
+ if (!$hasQuery) {
$body .= "";
}
$block = new Block("Bulk Actions", $body, "left", 30);
$page->add_block($block);
}
}
-
- public function render_tag_input() {
- return "".
- "";
+
+ public function render_tag_input()
+ {
+ return "" .
+ "";
}
- public function render_source_input() {
- return "";
- }
-
+ public function render_source_input()
+ {
+ return "";
+ }
}
From 49cb6f723387674f256b0e9690ff26cb374283e7 Mon Sep 17 00:00:00 2001
From: Matthew Barbour
Date: Wed, 5 Jun 2019 20:09:03 -0500
Subject: [PATCH 03/19] Added thumb_scaling option for generating high-dpi
thumbnails
---
ext/et/main.php | 1 +
ext/et/theme.php | 1 +
ext/handle_ico/main.php | 6 ++++--
ext/handle_pixel/main.php | 28 +++++++++-------------------
ext/handle_video/main.php | 2 +-
ext/image/main.php | 7 ++++++-
6 files changed, 22 insertions(+), 23 deletions(-)
diff --git a/ext/et/main.php b/ext/et/main.php
index c56c0f4d..7f8efe3d 100644
--- a/ext/et/main.php
+++ b/ext/et/main.php
@@ -57,6 +57,7 @@ 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_mem'] = $config->get_int("thumb_mem_limit");
$info['stat_images'] = $database->get_one("SELECT COUNT(*) FROM images");
diff --git a/ext/et/theme.php b/ext/et/theme.php
index f23ea296..a4b60ca9 100644
--- a/ext/et/theme.php
+++ b/ext/et/theme.php
@@ -41,6 +41,7 @@ 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']}
diff --git a/ext/handle_ico/main.php b/ext/handle_ico/main.php
index 504b09e1..5a52cda5 100644
--- a/ext/handle_ico/main.php
+++ b/ext/handle_ico/main.php
@@ -88,8 +88,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
diff --git a/ext/handle_pixel/main.php b/ext/handle_pixel/main.php
index 6d196910..fd74d517 100644
--- a/ext/handle_pixel/main.php
+++ b/ext/handle_pixel/main.php
@@ -103,8 +103,6 @@ class PixelFileHandler extends DataHandlerExtension
{
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");
@@ -114,12 +112,10 @@ class PixelFileHandler extends DataHandlerExtension
//$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;
- }
+ $tsize = get_thumbnail_size_scaled($size[0] , $size[1]);
+ $w = $tsize[0];
+ $h = $tsize[1];
+
// 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"';
@@ -158,24 +154,18 @@ class PixelFileHandler extends DataHandlerExtension
$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));
+ $tsize = get_thumbnail_size_scaled($width, $height);
+ $w = $tsize[0];
+ $h = $tsize[1];
+ $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);
+ $tsize = get_thumbnail_size_scaled($width, $height);
$thumb = imagecreatetruecolor($tsize[0], $tsize[1]);
imagecopyresampled(
diff --git a/ext/handle_video/main.php b/ext/handle_video/main.php
index 683ced7e..9a17735d 100644
--- a/ext/handle_video/main.php
+++ b/ext/handle_video/main.php
@@ -64,7 +64,7 @@ class VideoFileHandler extends DataHandlerExtension
$outname = warehouse_path("thumbs", $hash);
$orig_size = $this->video_size($inname);
- $scaled_size = get_thumbnail_size($orig_size[0], $orig_size[1]);
+ $scaled_size = get_thumbnail_size_scaled($orig_size[0], $orig_size[1]);
$cmd = escapeshellcmd(implode(" ", [
escapeshellarg($ffmpeg),
"-y", "-i", escapeshellarg($inname),
diff --git a/ext/image/main.php b/ext/image/main.php
index 116ae5db..ef11be8f 100644
--- a/ext/image/main.php
+++ b/ext/image/main.php
@@ -19,6 +19,7 @@ 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_int('thumb_mem_limit', parse_shorthand_int('8MB'));
$config->set_default_string('thumb_convert_path', 'convert');
@@ -147,7 +148,11 @@ class ImageIO extends Extension
$sb->add_label(" px at ");
$sb->add_int_option("thumb_quality");
$sb->add_label(" % quality ");
-
+
+ $sb->add_label("
High-DPI scaling ");
+ $sb->add_int_option("thumb_scaling");
+ $sb->add_label("%");
+
if ($config->get_string("thumb_engine") == "convert") {
$sb->add_label("
ImageMagick Binary: ");
$sb->add_text_option("thumb_convert_path");
From eb4292316d191b785eeb955e16ff8cd990a20707 Mon Sep 17 00:00:00 2001
From: Matthew Barbour
Date: Sun, 9 Jun 2019 13:22:48 -0500
Subject: [PATCH 04/19] Added webp upload and thumbnailing support Bug fixes
and consolidation of various thumbnail and resize functionality Changed
resize/rotate extensions to use replace image event Added content-disposition
header to image responses to provide a human-friendly filename when saving
Added more bulk thumbnail regeneration tools Tweaks to bulk actions to
correct totals when batching items
---
.htaccess | 3 +-
core/exceptions.php | 7 +
core/extension.php | 14 +-
core/imageboard/event.php | 5 +
core/imageboard/image.php | 12 +-
core/imageboard/misc.php | 306 ++++++++++++++++++++++++++++++++++++++
core/polyfills.php | 3 +-
ext/bulk_actions/main.php | 30 ++--
ext/et/main.php | 1 +
ext/et/theme.php | 1 +
ext/handle_flash/main.php | 8 +-
ext/handle_ico/main.php | 7 -
ext/handle_pixel/main.php | 114 +++-----------
ext/handle_svg/main.php | 17 +--
ext/handle_video/main.php | 52 +------
ext/image/main.php | 25 +++-
ext/qr_code/main.php | 2 +-
ext/rating/main.php | 1 +
ext/regen_thumb/main.php | 130 ++++++++++++++--
ext/regen_thumb/theme.php | 44 ++++++
ext/resize/main.php | 129 ++--------------
ext/rotate/main.php | 101 +++++--------
ext/upload/theme.php | 2 +-
23 files changed, 650 insertions(+), 364 deletions(-)
diff --git a/.htaccess b/.htaccess
index 3050e2e7..e2f1ca28 100644
--- a/.htaccess
+++ b/.htaccess
@@ -27,7 +27,7 @@
ExpiresActive On
-
+
Header set Cache-Control "public, max-age=2629743"
@@ -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
diff --git a/core/exceptions.php b/core/exceptions.php
index a201eba4..0e510243 100644
--- a/core/exceptions.php
+++ b/core/exceptions.php
@@ -35,3 +35,10 @@ 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
+{
+}
\ No newline at end of file
diff --git a/core/extension.php b/core/extension.php
index 0b6134f2..7274f868 100644
--- a/core/extension.php
+++ b/core/extension.php
@@ -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)
diff --git a/core/imageboard/event.php b/core/imageboard/event.php
index 6ed27177..2fc40fef 100644
--- a/core/imageboard/event.php
+++ b/core/imageboard/event.php
@@ -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;
}
}
diff --git a/core/imageboard/image.php b/core/imageboard/image.php
index 5d31ff90..4a0e554e 100644
--- a/core/imageboard/image.php
+++ b/core/imageboard/image.php
@@ -385,12 +385,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);
}
/**
diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php
index 5ae727f3..c1b9e242 100644
--- a/core/imageboard/misc.php
+++ b/core/imageboard/misc.php
@@ -105,3 +105,309 @@ 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 .= "\>";
+ }
+
+ if($type=="webp") {
+ $format = '"%s" -thumbnail %ux%u%s -quality %u -background none "%s[0]" %s:"%s"';
+ } else {
+ $format = '"%s" -flatten -strip -thumbnail %ux%u%s -quality %u "%s[0]" %s:"%s"';
+ }
+ $cmd = sprintf($format, $convert, $w, $h, $options, $q, $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));
+
+ if($image==false) {
+ throw new ImageResizeException("Could not load image: ".$image_filename);
+ }
+
+ $image_resized = imagecreatetruecolor($new_width, $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']);
+
+ // 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);
+ }
+ break;
+ case IMAGETYPE_PNG:
+ case IMAGETYPE_WEBP:
+ //
+ // 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);
+ break;
+ }
+
+ // Actually resize the image.
+ imagecopyresampled(
+ $image_resized,
+ $image,
+ 0,
+ 0,
+ 0,
+ 0,
+ $new_width,
+ $new_height,
+ $width,
+ $height
+ );
+
+ 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");
+ }
+ imagedestroy($image_resized);
+}
\ No newline at end of file
diff --git a/core/polyfills.php b/core/polyfills.php
index e543bb5a..4628e8d7 100644
--- a/core/polyfills.php
+++ b/core/polyfills.php
@@ -264,7 +264,8 @@ const MIME_TYPE_MAP = [
'ogg' => 'application/ogg', 'mp3' => 'audio/mpeg', 'wav' => 'audio/x-wav',
'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg',
'mov' => 'video/quicktime', 'flv' => 'video/x-flv', 'php' => 'text/x-php',
- 'mp4' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm'
+ 'mp4' => 'video/mp4', 'ogv' => 'video/ogg', 'webm' => 'video/webm',
+ 'webp' => 'image/webp'
];
/**
diff --git a/ext/bulk_actions/main.php b/ext/bulk_actions/main.php
index bae4353f..2ab4d4d7 100644
--- a/ext/bulk_actions/main.php
+++ b/ext/bulk_actions/main.php
@@ -36,12 +36,14 @@ class BulkActionEvent extends Event
public $action;
public $items;
public $page_request;
+ public $running_total;
- function __construct(String $action, PageRequestEvent $pageRequestEvent, array $items)
+ function __construct(String $action, PageRequestEvent $pageRequestEvent, array $items, int $running_total = 0)
{
$this->action = $action;
$this->page_request = $pageRequestEvent;
$this->items = $items;
+ $this->running_total = $running_total;
}
}
@@ -78,7 +80,8 @@ class BulkActions extends Extension
switch ($event->action) {
case "Delete":
if ($user->can("delete_image")) {
- $this->delete_items($event->items);
+ $event->running_total += $this->delete_items($event->items);
+ flash_message("Deleted $event->running_total items");
}
break;
case "Tag":
@@ -92,7 +95,8 @@ class BulkActions extends Extension
$replace = true;
}
- $this->tag_items($event->items, $tags, $replace);
+ $event->running_total += $this->tag_items($event->items, $tags, $replace);
+ flash_message("Tagged $event->running_total items");
}
break;
case "Set Source":
@@ -101,7 +105,8 @@ class BulkActions extends Extension
}
if ($user->can("bulk_edit_image_source")) {
$source = $_POST['bulk_source'];
- $this->set_source($event->items, $source);
+ $event->running_total += $this->set_source($event->items, $source);
+ flash_message("Set source for $event->running_total items");
}
break;
}
@@ -144,10 +149,10 @@ class BulkActions extends Extension
}
reset($items); // rewind to first element in array.
- $newEvent = new BulkActionEvent($action, $event, $items);
+ $newEvent = new BulkActionEvent($action, $event, $items, $n);
send_event($newEvent);
- $n += 100;
+ $n = $newEvent->running_total;
}
}
}
@@ -162,7 +167,7 @@ class BulkActions extends Extension
}
}
- private function delete_items(array $items)
+ private function delete_items(array $items): int
{
$total = 0;
foreach ($items as $item) {
@@ -173,11 +178,10 @@ class BulkActions extends Extension
flash_message("Error while removing $item->id: " . $e->getMessage(), "error");
}
}
-
- flash_message("Deleted $total items");
+ return $total;
}
- private function tag_items(array $items, string $tags, bool $replace)
+ private function tag_items(array $items, string $tags, bool $replace): int
{
$tags = Tag::explode($tags);
@@ -211,10 +215,10 @@ class BulkActions extends Extension
}
}
- flash_message("Tagged $total items");
+ return $total;
}
- private function set_source(array $items, String $source)
+ private function set_source(array $items, String $source): int
{
$total = 0;
foreach ($items as $item) {
@@ -226,6 +230,6 @@ class BulkActions extends Extension
}
}
- flash_message("Set source for $total items");
+ return $total;
}
}
diff --git a/ext/et/main.php b/ext/et/main.php
index 7f8efe3d..702c9c9c 100644
--- a/ext/et/main.php
+++ b/ext/et/main.php
@@ -58,6 +58,7 @@ class ET extends Extension
$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");
diff --git a/ext/et/theme.php b/ext/et/theme.php
index a4b60ca9..cb55ffb9 100644
--- a/ext/et/theme.php
+++ b/ext/et/theme.php
@@ -37,6 +37,7 @@ 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']}
diff --git a/ext/handle_flash/main.php b/ext/handle_flash/main.php
index c1ef4bdb..cec86d13 100644
--- a/ext/handle_flash/main.php
+++ b/ext/handle_flash/main.php
@@ -3,14 +3,18 @@
* Name: Handle Flash
* Author: Shish
* 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;
}
diff --git a/ext/handle_ico/main.php b/ext/handle_ico/main.php
index 5a52cda5..56e3f373 100644
--- a/ext/handle_ico/main.php
+++ b/ext/handle_ico/main.php
@@ -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;
diff --git a/ext/handle_pixel/main.php b/ext/handle_pixel/main.php
index fd74d517..04b26448 100644
--- a/ext/handle_pixel/main.php
+++ b/ext/handle_pixel/main.php
@@ -3,14 +3,14 @@
* Name: Handle Pixel
* Author: Shish
* 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,90 +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;
- $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);
- $tsize = get_thumbnail_size_scaled($size[0] , $size[1]);
- $w = $tsize[0];
- $h = $tsize[1];
-
-
- // 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) {
- $tsize = get_thumbnail_size_scaled($width, $height);
- $w = $tsize[0];
- $h = $tsize[1];
- $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 {
- $image = imagecreatefromstring(file_get_contents($tmpname));
- $tsize = get_thumbnail_size_scaled($width, $height);
-
- $thumb = imagecreatetruecolor($tsize[0], $tsize[1]);
- imagecopyresampled(
- $thumb,
- $image,
- 0,
- 0,
- 0,
- 0,
- $tsize[0],
- $tsize[1],
- $width,
- $height
- );
- return $thumb;
- }
- }
- // }}}
}
diff --git a/ext/handle_svg/main.php b/ext/handle_svg/main.php
index 0b74a552..97127816 100644
--- a/ext/handle_svg/main.php
+++ b/ext/handle_svg/main.php
@@ -3,12 +3,12 @@
* Name: Handle SVG
* Author: Shish
* 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;
diff --git a/ext/handle_video/main.php b/ext/handle_video/main.php
index 9a17735d..192547b2 100644
--- a/ext/handle_video/main.php
+++ b/ext/handle_video/main.php
@@ -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_scaled($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
diff --git a/ext/image/main.php b/ext/image/main.php
index ef11be8f..362cb944 100644
--- a/ext/image/main.php
+++ b/ext/image/main.php
@@ -21,6 +21,7 @@ class ImageIO extends Extension
$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');
@@ -138,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("
");
+ $sb->add_choice_option("thumb_type", $thumb_types, "Filetype: ");
$sb->add_label("
Size ");
$sb->add_int_option("thumb_width");
@@ -245,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());
@@ -264,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")) {
@@ -296,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();
}
@@ -306,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
@@ -324,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
diff --git a/ext/qr_code/main.php b/ext/qr_code/main.php
index 6d21cc37..236c5c6d 100644
--- a/ext/qr_code/main.php
+++ b/ext/qr_code/main.php
@@ -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)));
}
}
diff --git a/ext/rating/main.php b/ext/rating/main.php
index 34f67b39..42663322 100644
--- a/ext/rating/main.php
+++ b/ext/rating/main.php
@@ -166,6 +166,7 @@ class Ratings extends Extension
$rating = $_POST['bulk_rating'];
foreach ($event->items as $image) {
send_event(new RatingSetEvent($image, $rating));
+ $event->running_total++;
}
}
break;
diff --git a/ext/regen_thumb/main.php b/ext/regen_thumb/main.php
index 40a49487..7fcff488 100644
--- a/ext/regen_thumb/main.php
+++ b/ext/regen_thumb/main.php
@@ -15,12 +15,13 @@
class RegenThumb extends Extension
{
- public function regenerate_thumbnail($image)
+ public function regenerate_thumbnail($image, $force = true): string
{
global $database;
-
- send_event(new ThumbnailGenerationEvent($image->hash, $image->ext, true));
+ $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)
@@ -68,7 +69,7 @@ class RegenThumb extends Extension
global $user;
if ($user->can("delete_image")) {
- $event->add_action("Regen Thumbnails");
+ $event->add_action("Regen Thumbnails","",$this->theme->bulk_html());
}
}
@@ -80,15 +81,126 @@ class RegenThumb extends Extension
switch($event->action) {
case "Regen Thumbnails":
if ($user->can("delete_image")) {
- $total = 0;
- foreach ($event->items as $image) {
- $this->regenerate_thumbnail($image);
- $total++;
+ $force = true;
+ if(isset($_POST["bulk_regen_thumb_missing_only"])
+ &&$_POST["bulk_regen_thumb_missing_only"]=="true")
+ {
+ $force=false;
}
- flash_message("Regenerated thumbnails for $total items");
+
+
+ foreach ($event->items as $image) {
+ if($this->regenerate_thumbnail($image, $force)) {
+ $event->running_total++;
+ }
+ }
+ flash_message("Regenerated thumbnails for $event->running_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);
+ }
+ }
+
}
diff --git a/ext/regen_thumb/theme.php b/ext/regen_thumb/theme.php
index fc40d835..b118f194 100644
--- a/ext/regen_thumb/theme.php
+++ b/ext/regen_thumb/theme.php
@@ -37,4 +37,48 @@ class RegenThumbTheme extends Themelet
";
return $html;
}
+
+ public function bulk_html() {
+ return "";
+ }
+
+ 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,"");
+ }
+
+ $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.
+ ".make_form(make_link("admin/regen_thumbs"))."
+
+
+ ".make_form(make_link("admin/delete_thumbs"),"POST",False, "","return confirm('Are you sure you want to delete all thumbnails?')")."
+
+
+ ";
+ $page->add_block(new Block("Regen Thumbnails", $html));
+ }
}
diff --git a/ext/resize/main.php b/ext/resize/main.php
index 39de0850..7d8e219b 100644
--- a/ext/resize/main.php
+++ b/ext/resize/main.php
@@ -69,7 +69,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 +159,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 +169,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}");
}
/**
diff --git a/ext/rotate/main.php b/ext/rotate/main.php
index 56c1e2fb..17a7762a 100644
--- a/ext/rotate/main.php
+++ b/ext/rotate/main.php
@@ -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) {
@@ -155,12 +136,10 @@ class RotateImage extends Extension
/* 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 ImageRotateException("Unsupported image type or ");
+ $image = imagecreatefromstring(file_get_contents($image_filename));
+
+ if($image==false) {
+ throw new ImageRotateException("Could not load image: ".$image_filename);
}
/* Rotate and resample the image */
@@ -184,9 +163,17 @@ class RotateImage extends Extension
}
}
*/
+
+ $background_color = 0;
+ switch($info[2]){
+ case IMAGETYPE_PNG:
+ case IMAGETYPE_WEBP:
+ $background_color = imagecolorallocatealpha($image, 0, 0, 0, 127);
+ break;
+ }
- $image_rotated = imagerotate($image, $deg, 0);
-
+ $image_rotated = imagerotate($image, $deg, $background_color);
+
/* Temp storage while we rotate */
$tmp_filename = tempnam(ini_get('upload_tmp_dir'), 'shimmie_rotate');
if (empty($tmp_filename)) {
@@ -195,48 +182,40 @@ class RotateImage extends Extension
/* Output to the same format as the original image */
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}");
}
}
diff --git a/ext/upload/theme.php b/ext/upload/theme.php
index 6f0c11da..71041030 100644
--- a/ext/upload/theme.php
+++ b/ext/upload/theme.php
@@ -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";
}
From 4410baeb9cb3d30399d3b3c45881b721478d246e Mon Sep 17 00:00:00 2001
From: Matthew Barbour
Date: Sun, 9 Jun 2019 14:17:13 -0500
Subject: [PATCH 05/19] Changed cron upload so that an unrecognised file type
results in an error instead of a success
---
ext/cron_uploader/main.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ext/cron_uploader/main.php b/ext/cron_uploader/main.php
index 6168e776..dbcf549c 100644
--- a/ext/cron_uploader/main.php
+++ b/ext/cron_uploader/main.php
@@ -339,7 +339,7 @@ class CronUploader extends Extension
// Generate info message
$infomsg = ""; // Will contain info message
if ($event->image_id == -1) {
- $infomsg = "File type not recognised. Filename: {$filename}";
+ throw new Exception("File type not recognised. Filename: {$filename}");
} else {
$infomsg = "Image uploaded. ID: {$event->image_id} - Filename: {$filename} - Tags: {$tags}";
}
From b7945b098e78fab8971e2f80326a1993f5ba4daa Mon Sep 17 00:00:00 2001
From: Matthew Barbour
Date: Sun, 9 Jun 2019 14:18:25 -0500
Subject: [PATCH 06/19] Changed to prevent writing duplicate image tag IDs
---
core/imageboard/image.php | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/core/imageboard/image.php b/core/imageboard/image.php
index 4a0e554e..400f1821 100644
--- a/core/imageboard/image.php
+++ b/core/imageboard/image.php
@@ -603,6 +603,9 @@ class Image
if (Tag::implode($tags) != $this->get_tag_list()) {
// delete old
$this->delete_tags_from_image();
+
+ $written_tags = [];
+
// insert each new tags
foreach ($tags as $tag) {
$id = $database->get_one(
@@ -625,11 +628,17 @@ class Image
["id"=>$this->id, "tag"=>$tag]
);
} else {
- // user of an existing tag
+ // check if tag has already been written
+ if(in_array($id, $written_tags)) {
+ continue;
+ }
+
$database->execute("
- INSERT INTO image_tags(image_id, tag_id)
- VALUES(:iid, :tid)
- ", ["iid"=>$this->id, "tid"=>$id]);
+ INSERT INTO image_tags(image_id, tag_id)
+ VALUES(:iid, :tid)
+ ", ["iid"=>$this->id, "tid"=>$id]);
+
+ array_push($written_tags, $id);
}
$database->execute(
$database->scoreql_to_sql("
From b31a9164778f6a2c8b50da03cc020c5ce1576931 Mon Sep 17 00:00:00 2001
From: Matthew Barbour
Date: Tue, 11 Jun 2019 09:05:54 -0500
Subject: [PATCH 07/19] Changed clamp function to allow null values
---
core/polyfills.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/core/polyfills.php b/core/polyfills.php
index 4628e8d7..ffbf31f4 100644
--- a/core/polyfills.php
+++ b/core/polyfills.php
@@ -508,7 +508,7 @@ function no_escape(string $input): string
return $input;
}
-function clamp(int $val, int $min=null, int $max=null): int
+function clamp(?int $val, ?int $min=null, ?int $max=null): int
{
if (!is_numeric($val) || (!is_null($min) && $val < $min)) {
$val = $min;
From f2fb040a5bc7c7afd3ab0644bcd41e136e2334f1 Mon Sep 17 00:00:00 2001
From: Matthew Barbour
Date: Tue, 11 Jun 2019 09:06:47 -0500
Subject: [PATCH 08/19] Moved ImageResizeException to the core space so that
the core space image resize code can use it
---
core/exceptions.php | 12 ++++++++++++
ext/resize/main.php | 14 --------------
2 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/core/exceptions.php b/core/exceptions.php
index 0e510243..bf923d96 100644
--- a/core/exceptions.php
+++ b/core/exceptions.php
@@ -41,4 +41,16 @@ class InvalidInput extends SCoreException
*/
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;
+ }
}
\ No newline at end of file
diff --git a/ext/resize/main.php b/ext/resize/main.php
index 7d8e219b..ac90aac6 100644
--- a/ext/resize/main.php
+++ b/ext/resize/main.php
@@ -11,20 +11,6 @@
* 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.
*/
From 8f3c20134f4c53d8c2762082c9164a42e646d904 Mon Sep 17 00:00:00 2001
From: Matthew Barbour
Date: Tue, 11 Jun 2019 09:08:16 -0500
Subject: [PATCH 09/19] Added