2021-12-14 18:32:47 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
2019-06-05 23:03:22 +00:00
|
|
|
|
2023-01-10 22:44:09 +00:00
|
|
|
namespace Shimmie2;
|
|
|
|
|
2020-06-22 00:08:06 +00:00
|
|
|
class BulkActionException extends SCoreException
|
|
|
|
{
|
|
|
|
}
|
2019-06-06 00:37:07 +00:00
|
|
|
class BulkActionBlockBuildingEvent extends Event
|
|
|
|
{
|
2021-03-14 23:43:50 +00:00
|
|
|
public array $actions = [];
|
|
|
|
public array $search_terms = [];
|
2019-06-21 20:28:20 +00:00
|
|
|
|
2023-08-17 17:12:36 +00:00
|
|
|
public function add_action(string $action, string $button_text, string $access_key = null, string $confirmation_message = "", string $block = "", int $position = 40)
|
2019-06-06 00:37:07 +00:00
|
|
|
{
|
2019-09-29 13:30:55 +00:00
|
|
|
if (!empty($access_key)) {
|
2023-11-11 21:49:12 +00:00
|
|
|
assert(strlen($access_key) == 1);
|
2019-06-27 03:41:42 +00:00
|
|
|
foreach ($this->actions as $existing) {
|
2023-11-11 21:49:12 +00:00
|
|
|
if ($existing["access_key"] == $access_key) {
|
2019-06-27 03:41:42 +00:00
|
|
|
throw new SCoreException("Access key $access_key is already in use");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-11 21:49:12 +00:00
|
|
|
$this->actions[] = [
|
2019-06-06 00:37:07 +00:00
|
|
|
"block" => $block,
|
2019-06-27 03:41:42 +00:00
|
|
|
"access_key" => $access_key,
|
2019-06-06 00:37:07 +00:00
|
|
|
"confirmation_message" => $confirmation_message,
|
|
|
|
"action" => $action,
|
2019-06-12 22:44:25 +00:00
|
|
|
"button_text" => $button_text,
|
2019-06-06 00:37:07 +00:00
|
|
|
"position" => $position
|
2019-06-27 03:41:42 +00:00
|
|
|
];
|
2019-06-06 00:37:07 +00:00
|
|
|
}
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
|
2019-06-06 00:37:07 +00:00
|
|
|
class BulkActionEvent extends Event
|
|
|
|
{
|
2021-03-14 23:43:50 +00:00
|
|
|
public string $action;
|
2023-01-10 22:44:09 +00:00
|
|
|
public \Generator $items;
|
2021-03-14 23:43:50 +00:00
|
|
|
public bool $redirect = true;
|
2019-06-05 23:03:22 +00:00
|
|
|
|
2023-08-17 17:12:36 +00:00
|
|
|
public function __construct(string $action, \Generator $items)
|
2019-06-06 00:37:07 +00:00
|
|
|
{
|
2020-01-26 13:19:35 +00:00
|
|
|
parent::__construct();
|
2019-06-05 23:03:22 +00:00
|
|
|
$this->action = $action;
|
|
|
|
$this->items = $items;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class BulkActions extends Extension
|
|
|
|
{
|
2020-02-04 00:46:36 +00:00
|
|
|
/** @var BulkActionsTheme */
|
2023-06-27 14:56:49 +00:00
|
|
|
protected Themelet $theme;
|
2020-02-04 00:46:36 +00:00
|
|
|
|
2019-06-05 23:03:22 +00:00
|
|
|
public function onPostListBuilding(PostListBuildingEvent $event)
|
|
|
|
{
|
2019-10-02 10:23:57 +00:00
|
|
|
global $page, $user;
|
2019-06-06 00:37:07 +00:00
|
|
|
|
2019-06-12 22:44:25 +00:00
|
|
|
if ($user->is_logged_in()) {
|
|
|
|
$babbe = new BulkActionBlockBuildingEvent();
|
2019-06-21 20:28:20 +00:00
|
|
|
$babbe->search_terms = $event->search_terms;
|
|
|
|
|
2019-06-12 22:44:25 +00:00
|
|
|
send_event($babbe);
|
|
|
|
|
2019-06-14 12:47:50 +00:00
|
|
|
if (sizeof($babbe->actions) == 0) {
|
|
|
|
return;
|
|
|
|
}
|
2019-06-12 22:44:25 +00:00
|
|
|
|
2019-06-14 12:47:50 +00:00
|
|
|
usort($babbe->actions, [$this, "sort_blocks"]);
|
2019-06-12 22:44:25 +00:00
|
|
|
|
|
|
|
$this->theme->display_selector($page, $babbe->actions, Tag::implode($event->search_terms));
|
|
|
|
}
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
|
2019-06-06 00:37:07 +00:00
|
|
|
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
|
2019-06-05 23:03:22 +00:00
|
|
|
{
|
|
|
|
global $user;
|
|
|
|
|
2019-07-09 14:10:21 +00:00
|
|
|
if ($user->can(Permissions::DELETE_IMAGE)) {
|
2019-06-17 09:52:05 +00:00
|
|
|
$event->add_action("bulk_delete", "(D)elete", "d", "Delete selected images?", $this->theme->render_ban_reason_input(), 10);
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
|
2019-07-09 14:10:21 +00:00
|
|
|
if ($user->can(Permissions::BULK_EDIT_IMAGE_TAG)) {
|
2019-06-27 03:41:42 +00:00
|
|
|
$event->add_action(
|
|
|
|
"bulk_tag",
|
|
|
|
"Tag",
|
|
|
|
"t",
|
|
|
|
"",
|
|
|
|
$this->theme->render_tag_input(),
|
2019-09-29 13:30:55 +00:00
|
|
|
10
|
|
|
|
);
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
|
2019-07-09 14:10:21 +00:00
|
|
|
if ($user->can(Permissions::BULK_EDIT_IMAGE_SOURCE)) {
|
2019-09-29 13:30:55 +00:00
|
|
|
$event->add_action("bulk_source", "Set (S)ource", "s", "", $this->theme->render_source_input(), 10);
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-04 19:50:49 +00:00
|
|
|
public function onCommand(CommandEvent $event)
|
|
|
|
{
|
|
|
|
if ($event->cmd == "help") {
|
|
|
|
print "\tbulk-action <action> <query>\n";
|
|
|
|
print "\t\tperform an action on all query results\n\n";
|
|
|
|
}
|
|
|
|
if ($event->cmd == "bulk-action") {
|
|
|
|
if (count($event->args) < 2) {
|
|
|
|
return;
|
|
|
|
}
|
2020-02-02 17:00:33 +00:00
|
|
|
$action = $event->args[0];
|
|
|
|
$query = $event->args[1];
|
|
|
|
$items = $this->yield_search_results($query);
|
|
|
|
log_info("bulk_actions", "Performing $action on {$event->args[1]}");
|
|
|
|
send_event(new BulkActionEvent($event->args[0], $items));
|
2019-10-04 19:50:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-05 23:03:22 +00:00
|
|
|
public function onBulkAction(BulkActionEvent $event)
|
|
|
|
{
|
2019-12-15 19:47:18 +00:00
|
|
|
global $page, $user;
|
2019-06-05 23:03:22 +00:00
|
|
|
|
2019-06-06 00:37:07 +00:00
|
|
|
switch ($event->action) {
|
2019-06-12 22:44:25 +00:00
|
|
|
case "bulk_delete":
|
2019-07-09 14:10:21 +00:00
|
|
|
if ($user->can(Permissions::DELETE_IMAGE)) {
|
2021-01-13 01:39:23 +00:00
|
|
|
$i = $this->delete_posts($event->items);
|
|
|
|
$page->flash("Deleted $i[0] items, totaling ".human_filesize($i[1]));
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
break;
|
2019-06-12 22:44:25 +00:00
|
|
|
case "bulk_tag":
|
2019-06-05 23:03:22 +00:00
|
|
|
if (!isset($_POST['bulk_tags'])) {
|
|
|
|
return;
|
|
|
|
}
|
2019-07-09 14:10:21 +00:00
|
|
|
if ($user->can(Permissions::BULK_EDIT_IMAGE_TAG)) {
|
2019-06-05 23:03:22 +00:00
|
|
|
$tags = $_POST['bulk_tags'];
|
|
|
|
$replace = false;
|
2019-06-06 00:37:07 +00:00
|
|
|
if (isset($_POST['bulk_tags_replace']) && $_POST['bulk_tags_replace'] == "true") {
|
2019-06-05 23:03:22 +00:00
|
|
|
$replace = true;
|
|
|
|
}
|
2019-06-06 00:37:07 +00:00
|
|
|
|
2023-11-11 21:49:12 +00:00
|
|
|
$i = $this->tag_items($event->items, $tags, $replace);
|
2019-12-15 19:47:18 +00:00
|
|
|
$page->flash("Tagged $i items");
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
break;
|
2019-06-12 22:44:25 +00:00
|
|
|
case "bulk_source":
|
2019-06-05 23:03:22 +00:00
|
|
|
if (!isset($_POST['bulk_source'])) {
|
|
|
|
return;
|
|
|
|
}
|
2019-07-09 14:10:21 +00:00
|
|
|
if ($user->can(Permissions::BULK_EDIT_IMAGE_SOURCE)) {
|
2019-06-05 23:03:22 +00:00
|
|
|
$source = $_POST['bulk_source'];
|
2019-06-12 22:44:25 +00:00
|
|
|
$i = $this->set_source($event->items, $source);
|
2019-12-15 19:47:18 +00:00
|
|
|
$page->flash("Set source for $i items");
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function onPageRequest(PageRequestEvent $event)
|
|
|
|
{
|
|
|
|
global $page, $user;
|
2019-07-13 22:39:27 +00:00
|
|
|
if ($event->page_matches("bulk_action") && $user->can(Permissions::PERFORM_BULK_ACTIONS)) {
|
2019-06-05 23:03:22 +00:00
|
|
|
if (!isset($_POST['bulk_action'])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$action = $_POST['bulk_action'];
|
|
|
|
|
2020-06-22 00:08:06 +00:00
|
|
|
try {
|
|
|
|
$items = null;
|
|
|
|
if (isset($_POST['bulk_selected_ids']) && !empty($_POST['bulk_selected_ids'])) {
|
|
|
|
$data = json_decode($_POST['bulk_selected_ids']);
|
|
|
|
if (empty($data)) {
|
|
|
|
throw new BulkActionException("No ids specified in bulk_selected_ids");
|
|
|
|
}
|
2023-02-03 16:58:16 +00:00
|
|
|
if (is_array($data)) {
|
2020-06-22 00:08:06 +00:00
|
|
|
$items = $this->yield_items($data);
|
|
|
|
}
|
|
|
|
} elseif (isset($_POST['bulk_query']) && $_POST['bulk_query'] != "") {
|
|
|
|
$query = $_POST['bulk_query'];
|
2023-06-27 16:45:35 +00:00
|
|
|
if (!empty($query)) {
|
2020-06-22 00:08:06 +00:00
|
|
|
$items = $this->yield_search_results($query);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new BulkActionException("No ids selected and no query present, cannot perform bulk operation on entire collection");
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
|
2020-06-22 00:08:06 +00:00
|
|
|
$bae = new BulkActionEvent($action, $items);
|
2020-06-16 23:30:31 +00:00
|
|
|
|
2020-06-22 00:08:06 +00:00
|
|
|
if (is_iterable($items)) {
|
|
|
|
send_event($bae);
|
|
|
|
}
|
2020-10-29 01:28:46 +00:00
|
|
|
|
|
|
|
if ($bae->redirect) {
|
|
|
|
$page->set_mode(PageMode::REDIRECT);
|
|
|
|
$page->set_redirect(referer_or(make_link()));
|
|
|
|
}
|
2020-06-22 00:08:06 +00:00
|
|
|
} catch (BulkActionException $e) {
|
|
|
|
log_error(BulkActionsInfo::KEY, $e->getMessage(), $e->getMessage());
|
2019-06-12 22:44:25 +00:00
|
|
|
}
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-10 22:44:09 +00:00
|
|
|
private function yield_items(array $data): \Generator
|
2019-07-05 15:24:46 +00:00
|
|
|
{
|
|
|
|
foreach ($data as $id) {
|
|
|
|
if (is_numeric($id)) {
|
|
|
|
$image = Image::by_id($id);
|
2023-11-11 21:49:12 +00:00
|
|
|
if ($image != null) {
|
2019-07-05 15:24:46 +00:00
|
|
|
yield $image;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-10 22:44:09 +00:00
|
|
|
private function yield_search_results(string $query): \Generator
|
2019-07-05 15:24:46 +00:00
|
|
|
{
|
|
|
|
$tags = Tag::explode($query);
|
2023-12-14 16:33:21 +00:00
|
|
|
return Search::find_images_iterable(0, null, $tags);
|
2019-07-05 15:24:46 +00:00
|
|
|
}
|
|
|
|
|
2019-06-12 22:44:25 +00:00
|
|
|
private function sort_blocks($a, $b)
|
2019-06-14 12:47:50 +00:00
|
|
|
{
|
|
|
|
return $a["position"] - $b["position"];
|
2019-06-12 22:44:25 +00:00
|
|
|
}
|
2019-08-07 19:53:59 +00:00
|
|
|
|
2021-01-13 01:39:23 +00:00
|
|
|
private function delete_posts(iterable $posts): array
|
2019-06-06 00:37:07 +00:00
|
|
|
{
|
2019-12-15 19:47:18 +00:00
|
|
|
global $page;
|
2019-06-05 23:03:22 +00:00
|
|
|
$total = 0;
|
2021-01-13 01:39:23 +00:00
|
|
|
$size = 0;
|
|
|
|
foreach ($posts as $post) {
|
2019-06-05 23:03:22 +00:00
|
|
|
try {
|
2023-01-10 22:44:09 +00:00
|
|
|
if (class_exists("Shimmie2\ImageBan") && isset($_POST['bulk_ban_reason'])) {
|
2019-06-17 09:52:05 +00:00
|
|
|
$reason = $_POST['bulk_ban_reason'];
|
|
|
|
if ($reason) {
|
2021-01-13 01:39:23 +00:00
|
|
|
send_event(new AddImageHashBanEvent($post->hash, $reason));
|
2019-06-17 09:52:05 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-13 01:39:23 +00:00
|
|
|
send_event(new ImageDeletionEvent($post));
|
2019-06-05 23:03:22 +00:00
|
|
|
$total++;
|
2021-01-13 01:39:23 +00:00
|
|
|
$size += $post->filesize;
|
2023-01-10 22:44:09 +00:00
|
|
|
} catch (\Exception $e) {
|
2021-01-13 01:39:23 +00:00
|
|
|
$page->flash("Error while removing {$post->id}: " . $e->getMessage());
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
2019-06-06 00:37:07 +00:00
|
|
|
}
|
2021-01-13 01:39:23 +00:00
|
|
|
return [$total, $size];
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
|
2019-07-05 15:24:46 +00:00
|
|
|
private function tag_items(iterable $items, string $tags, bool $replace): int
|
2019-06-06 00:37:07 +00:00
|
|
|
{
|
2019-06-05 23:03:22 +00:00
|
|
|
$tags = Tag::explode($tags);
|
|
|
|
|
|
|
|
$pos_tag_array = [];
|
|
|
|
$neg_tag_array = [];
|
|
|
|
foreach ($tags as $new_tag) {
|
2020-10-25 19:31:58 +00:00
|
|
|
if (str_starts_with($new_tag, '-')) {
|
2019-06-05 23:03:22 +00:00
|
|
|
$neg_tag_array[] = substr($new_tag, 1);
|
|
|
|
} else {
|
|
|
|
$pos_tag_array[] = $new_tag;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$total = 0;
|
|
|
|
if ($replace) {
|
2019-07-05 15:24:46 +00:00
|
|
|
foreach ($items as $image) {
|
2019-06-12 22:44:25 +00:00
|
|
|
send_event(new TagSetEvent($image, $tags));
|
2019-06-05 23:03:22 +00:00
|
|
|
$total++;
|
|
|
|
}
|
|
|
|
} else {
|
2019-07-05 15:24:46 +00:00
|
|
|
foreach ($items as $image) {
|
2019-09-29 13:30:55 +00:00
|
|
|
$img_tags = array_map("strtolower", $image->get_tag_array());
|
2019-06-12 22:44:25 +00:00
|
|
|
|
2019-06-05 23:03:22 +00:00
|
|
|
if (!empty($neg_tag_array)) {
|
2019-09-29 13:30:55 +00:00
|
|
|
$neg_tag_array = array_map("strtolower", $neg_tag_array);
|
2019-07-05 15:24:46 +00:00
|
|
|
|
|
|
|
$img_tags = array_merge($pos_tag_array, $img_tags);
|
2019-06-05 23:03:22 +00:00
|
|
|
$img_tags = array_diff($img_tags, $neg_tag_array);
|
|
|
|
} else {
|
2019-07-05 15:24:46 +00:00
|
|
|
$img_tags = array_merge($tags, $img_tags);
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
2019-06-12 22:44:25 +00:00
|
|
|
send_event(new TagSetEvent($image, $img_tags));
|
2019-06-05 23:03:22 +00:00
|
|
|
$total++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-09 18:22:48 +00:00
|
|
|
return $total;
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
|
2023-08-17 17:12:36 +00:00
|
|
|
private function set_source(iterable $items, string $source): int
|
2019-06-06 00:37:07 +00:00
|
|
|
{
|
2019-12-15 19:47:18 +00:00
|
|
|
global $page;
|
2019-06-05 23:03:22 +00:00
|
|
|
$total = 0;
|
2019-07-05 15:24:46 +00:00
|
|
|
foreach ($items as $image) {
|
2019-06-05 23:03:22 +00:00
|
|
|
try {
|
2019-06-12 22:44:25 +00:00
|
|
|
send_event(new SourceSetEvent($image, $source));
|
2019-06-05 23:03:22 +00:00
|
|
|
$total++;
|
2023-01-11 11:15:26 +00:00
|
|
|
} catch (\Exception $e) {
|
2020-01-26 13:19:35 +00:00
|
|
|
$page->flash("Error while setting source for {$image->id}: " . $e->getMessage());
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
}
|
2019-06-09 18:22:48 +00:00
|
|
|
return $total;
|
2019-06-05 23:03:22 +00:00
|
|
|
}
|
|
|
|
}
|