diff --git a/ext/artists/main.php b/ext/artists/main.php index cb94e0a0..dcb28021 100644 --- a/ext/artists/main.php +++ b/ext/artists/main.php @@ -38,8 +38,8 @@ class Artists extends Extension public function onImageInfoSet(ImageInfoSetEvent $event): void { global $user; - if ($user->can(Permissions::EDIT_IMAGE_ARTIST) && isset($event->params["tag_edit__author"])) { - send_event(new AuthorSetEvent($event->image, $user, $event->params["tag_edit__author"])); + if ($user->can(Permissions::EDIT_IMAGE_ARTIST) && isset($event->params["author"])) { + send_event(new AuthorSetEvent($event->image, $user, $event->params["author"])); } } diff --git a/ext/artists/theme.php b/ext/artists/theme.php index bf9947dc..8f52ef11 100644 --- a/ext/artists/theme.php +++ b/ext/artists/theme.php @@ -22,7 +22,7 @@ class ArtistsTheme extends Themelet return SHM_POST_INFO( "Author", $author, - INPUT(["type" => "text", "name" => "tag_edit__author", "value" => $author]) + INPUT(["type" => "text", "name" => "author", "value" => $author]) ); } diff --git a/ext/graphql/main.php b/ext/graphql/main.php index c42162db..1db6ab1a 100644 --- a/ext/graphql/main.php +++ b/ext/graphql/main.php @@ -29,11 +29,12 @@ class MetadataInput public static function update_post_metadata(int $post_id, MetadataInput $metadata): Image { global $user; - $_POST['tag_edit__tags'] = $metadata->tags; - $_POST['tag_edit__source'] = $metadata->source; $image = Image::by_id($post_id); if (!$image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK)) { - send_event(new ImageInfoSetEvent($image, $_POST)); + send_event(new ImageInfoSetEvent($image, [ + 'tags' => $metadata->tags, + 'source' => $metadata->source, + ])); } return Image::by_id($post_id); } diff --git a/ext/post_lock/info.php b/ext/post_lock/info.php new file mode 100644 index 00000000..fba20f56 --- /dev/null +++ b/ext/post_lock/info.php @@ -0,0 +1,18 @@ +image = $image; + $this->locked = $locked; + } +} + +class PostLock extends Extension +{ + /** @var PostLockTheme */ + protected Themelet $theme; + + public function onImageAddition(ImageAdditionEvent $event): void + { + if (!empty($event->metadata['locked'])) { + send_event(new LockSetEvent($event->image, $event->metadata['locked'])); + } + } + + public function onImageInfoSet(ImageInfoSetEvent $event): void + { + global $page, $user; + if ($event->image->is_locked() && !$user->can(Permissions::EDIT_IMAGE_LOCK)) { + throw new PermissionDenied("Error: This image is locked and cannot be edited."); + } + if ($user->can(Permissions::EDIT_IMAGE_LOCK)) { + $locked = isset($event->params['locked']) && $event->params['locked'] == "on"; + send_event(new LockSetEvent($event->image, $locked)); + } + } + + public function onLockSet(LockSetEvent $event): void + { + global $user; + if ($user->can(Permissions::EDIT_IMAGE_LOCK)) { + $event->image->set_locked($event->locked); + } + } + + public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event): void + { + $event->add_part($this->theme->get_lock_editor_html($event->image), 42); + } +} diff --git a/ext/post_lock/test.php b/ext/post_lock/test.php new file mode 100644 index 00000000..ba0eb2ee --- /dev/null +++ b/ext/post_lock/test.php @@ -0,0 +1,37 @@ +log_in_as_user(); + $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); + + // admin can lock + $this->log_in_as_admin(); + send_event(new ImageInfoSetEvent(Image::by_id($image_id), ["locked" => "on"])); + + // user can't edit locked post + $this->log_in_as_user(); + $this->assertException(PermissionDenied::class, function () use ($image_id) { + send_event(new ImageInfoSetEvent(Image::by_id($image_id), ["source" => "http://example.com"])); + }); + + // admin can edit locked post + $this->log_in_as_admin(); + send_event(new ImageInfoSetEvent(Image::by_id($image_id), ["source" => "http://example.com"])); + + // admin can unlock + $this->log_in_as_admin(); + send_event(new ImageInfoSetEvent(Image::by_id($image_id), [])); // "locked" is not set + + // user can edit un-locked post + $this->log_in_as_user(); + send_event(new ImageInfoSetEvent(Image::by_id($image_id), ["source" => "http://example.com"])); + } +} diff --git a/ext/post_lock/theme.php b/ext/post_lock/theme.php new file mode 100644 index 00000000..4d64d989 --- /dev/null +++ b/ext/post_lock/theme.php @@ -0,0 +1,22 @@ +is_locked() ? "Yes (Only admins may edit these details)" : "No", + $user->can(Permissions::EDIT_IMAGE_LOCK) ? INPUT(["type" => "checkbox", "name" => "locked", "checked" => $image->is_locked()]) : null + ); + } +} diff --git a/ext/post_owner/info.php b/ext/post_owner/info.php new file mode 100644 index 00000000..42b37e1f --- /dev/null +++ b/ext/post_owner/info.php @@ -0,0 +1,18 @@ +image = $image; + $this->owner = $owner; + } +} + +class PostOwner extends Extension +{ + /** @var PostOwnerTheme */ + protected Themelet $theme; + + public function onImageAddition(ImageAdditionEvent $event): void + { + // FIXME: send an event instead of implicit default owner? + } + + public function onImageInfoSet(ImageInfoSetEvent $event): void + { + global $page, $user; + if ($user->can(Permissions::EDIT_IMAGE_OWNER) && isset($event->params['owner'])) { + $owner = User::by_name($event->params['owner']); + if ($owner instanceof User) { + send_event(new OwnerSetEvent($event->image, $owner)); + } else { + throw new UserNotFound("Error: No user with that name was found."); + } + } + } + + public function onOwnerSet(OwnerSetEvent $event): void + { + global $user; + if ($user->can(Permissions::EDIT_IMAGE_OWNER)) { + $event->image->set_owner($event->owner); + } + } + + public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event): void + { + $event->add_part($this->theme->get_owner_editor_html($event->image), 39); + } +} diff --git a/ext/post_owner/test.php b/ext/post_owner/test.php new file mode 100644 index 00000000..741d08b0 --- /dev/null +++ b/ext/post_owner/test.php @@ -0,0 +1,22 @@ +log_in_as_user(); + $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); + $image = Image::by_id($image_id); + + $this->log_in_as_admin(); + send_event(new ImageInfoSetEvent($image, ["owner" => self::$admin_name])); + + $this->log_in_as_user(); + $this->get_page("post/view/$image_id"); + $this->assert_text(self::$admin_name); + } +} diff --git a/ext/post_owner/theme.php b/ext/post_owner/theme.php new file mode 100644 index 00000000..23350aca --- /dev/null +++ b/ext/post_owner/theme.php @@ -0,0 +1,34 @@ +get_owner()->name; + $date = rawHTML(autodate($image->posted)); + $ip = $user->can(Permissions::VIEW_IP) ? rawHTML(" (" . show_ip($image->owner_ip, "Post posted {$image->posted}") . ")") : ""; + $info = SHM_POST_INFO( + "Uploader", + emptyHTML(A(["class" => "username", "href" => make_link("user/$owner")], $owner), $ip, ", ", $date), + $user->can(Permissions::EDIT_IMAGE_OWNER) ? INPUT(["type" => "text", "name" => "owner", "value" => $owner]) : null + ); + // SHM_POST_INFO returns a TR, let's sneakily append + // a TD with the avatar in it + $info->appendChild( + TD( + ["width" => "80px", "rowspan" => "4"], + rawHTML($image->get_owner()->get_avatar_html()) + ) + ); + return $info; + } +} diff --git a/ext/post_source/info.php b/ext/post_source/info.php new file mode 100644 index 00000000..99c171f9 --- /dev/null +++ b/ext/post_source/info.php @@ -0,0 +1,18 @@ +image = $image; + $this->source = trim($source); + } +} + +class PostSource extends Extension +{ + /** @var PostSourceTheme */ + protected Themelet $theme; + + public function onPageRequest(PageRequestEvent $event): void + { + global $user, $page; + if ($event->page_matches("tag_edit/mass_source_set", method: "POST", permission: Permissions::MASS_TAG_EDIT)) { + $this->mass_source_edit($event->req_POST('tags'), $event->req_POST('source')); + $page->set_mode(PageMode::REDIRECT); + $page->set_redirect(search_link()); + } + } + + public function onImageAddition(ImageAdditionEvent $event): void + { + if(!empty($event->metadata['source'])) { + send_event(new SourceSetEvent($event->image, $event->metadata['source'])); + } + } + + public function onImageInfoSet(ImageInfoSetEvent $event): void + { + global $page, $user; + if ($user->can(Permissions::EDIT_IMAGE_SOURCE) && isset($event->params['source'])) { + if (isset($event->params['tags']) ? !preg_match('/source[=|:]/', $event->params["tags"]) : true) { + send_event(new SourceSetEvent($event->image, $event->params['source'])); + } + } + } + + public function onSourceSet(SourceSetEvent $event): void + { + global $user; + if ($user->can(Permissions::EDIT_IMAGE_SOURCE)) { + $event->image->set_source($event->source); + } + } + + public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event): void + { + $event->add_part($this->theme->get_source_editor_html($event->image), 41); + } + + public function onTagTermCheck(TagTermCheckEvent $event): void + { + if (preg_match("/^source[=|:](.*)$/i", $event->term)) { + $event->metatag = true; + } + } + + public function onTagTermParse(TagTermParseEvent $event): void + { + if (preg_match("/^source[=|:](.*)$/i", $event->term, $matches)) { + $source = ($matches[1] !== "none" ? $matches[1] : null); + send_event(new SourceSetEvent(Image::by_id($event->image_id), $source)); + } + } + + private function mass_source_edit(string $tags, string $source): void + { + $tags = Tag::explode($tags); + + $last_id = -1; + while (true) { + // make sure we don't look at the same images twice. + // search returns high-ids first, so we want to look + // at images with lower IDs than the previous. + $search_forward = $tags; + if ($last_id >= 0) { + $search_forward[] = "id<$last_id"; + } + + $images = Search::find_images(limit: 100, tags: $search_forward); + if (count($images) == 0) { + break; + } + + foreach ($images as $image) { + send_event(new SourceSetEvent($image, $source)); + $last_id = $image->id; + } + } + } +} diff --git a/ext/post_source/test.php b/ext/post_source/test.php new file mode 100644 index 00000000..8622d738 --- /dev/null +++ b/ext/post_source/test.php @@ -0,0 +1,21 @@ +log_in_as_user(); + $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); + $image = Image::by_id($image_id); + + send_event(new ImageInfoSetEvent($image, ["source" => "example.com"])); + send_event(new ImageInfoSetEvent($image, ["source" => "http://example.com"])); + + $this->get_page("post/view/$image_id"); + $this->assert_text("example.com"); + } +} diff --git a/ext/post_source/theme.php b/ext/post_source/theme.php new file mode 100644 index 00000000..40ef2ea5 --- /dev/null +++ b/ext/post_source/theme.php @@ -0,0 +1,54 @@ + + + + + "; + return $html; + } + + public function get_source_editor_html(Image $image): HTMLElement + { + global $user; + return SHM_POST_INFO( + "Source Link", + DIV( + ["style" => "overflow: hidden; white-space: nowrap; max-width: 350px; text-overflow: ellipsis;"], + $this->format_source($image->get_source()) + ), + $user->can(Permissions::EDIT_IMAGE_SOURCE) ? INPUT(["type" => "text", "name" => "source", "value" => $image->get_source()]) : null, + link: Extension::is_enabled(SourceHistoryInfo::KEY) ? make_link("source_history/{$image->id}") : null, + ); + } + + protected function format_source(string $source = null): HTMLElement + { + if (!empty($source)) { + if (!str_contains($source, "://")) { + $source = "https://" . $source; + } + $proto_domain = explode("://", $source); + $h_source = $proto_domain[1]; + if (str_ends_with($h_source, "/")) { + $h_source = substr($h_source, 0, -1); + } + return A(["href" => $source], $h_source); + } + return rawHTML("Unknown"); + } +} diff --git a/ext/tag_edit/info.php b/ext/post_tags/info.php similarity index 96% rename from ext/tag_edit/info.php rename to ext/post_tags/info.php index 30c1280e..6845b06a 100644 --- a/ext/tag_edit/info.php +++ b/ext/post_tags/info.php @@ -4,9 +4,9 @@ declare(strict_types=1); namespace Shimmie2; -class TagEditInfo extends ExtensionInfo +class PostTagsInfo extends ExtensionInfo { - public const KEY = "tag_edit"; + public const KEY = "post_tags"; public string $key = self::KEY; public string $name = "Tag Editor"; diff --git a/ext/tag_edit/main.php b/ext/post_tags/main.php similarity index 60% rename from ext/tag_edit/main.php rename to ext/post_tags/main.php index 687634ee..86486663 100644 --- a/ext/tag_edit/main.php +++ b/ext/post_tags/main.php @@ -8,40 +8,6 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\{InputInterface,InputArgument}; use Symfony\Component\Console\Output\OutputInterface; -/* - * OwnerSetEvent: - * $image_id - * $source - * - */ -class OwnerSetEvent extends Event -{ - public Image $image; - public User $owner; - - public function __construct(Image $image, User $owner) - { - parent::__construct(); - $this->image = $image; - $this->owner = $owner; - } -} - - -class SourceSetEvent extends Event -{ - public Image $image; - public ?string $source; - - public function __construct(Image $image, string $source = null) - { - parent::__construct(); - $this->image = $image; - $this->source = trim($source); - } -} - - class TagSetException extends UserError { public ?string $redirect; @@ -94,19 +60,6 @@ class TagSetEvent extends Event } } -class LockSetEvent extends Event -{ - public Image $image; - public bool $locked; - - public function __construct(Image $image, bool $locked) - { - parent::__construct(); - $this->image = $image; - $this->locked = $locked; - } -} - /** * Check whether or not a tag is a meta-tag */ @@ -138,9 +91,9 @@ class TagTermParseEvent extends Event } } -class TagEdit extends Extension +class PostTags extends Extension { - /** @var TagEditTheme */ + /** @var PostTagsTheme */ protected Themelet $theme; public function onPageRequest(PageRequestEvent $event): void @@ -151,11 +104,6 @@ class TagEdit extends Extension $page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("admin")); } - if ($event->page_matches("tag_edit/mass_source_set", method: "POST", permission: Permissions::MASS_TAG_EDIT)) { - $this->mass_source_edit($event->req_POST('tags'), $event->req_POST('source')); - $page->set_mode(PageMode::REDIRECT); - $page->set_redirect(search_link()); - } } public function onCliGen(CliGenEvent $event): void @@ -173,41 +121,19 @@ class TagEdit extends Extension }); } - // public function onPostListBuilding(PostListBuildingEvent $event): void - // { - // global $user; - // if ($user->can(UserAbilities::BULK_EDIT_IMAGE_SOURCE) && !empty($event->search_terms)) { - // $event->add_control($this->theme->mss_html(Tag::implode($event->search_terms))); - // } - // } - public function onImageAddition(ImageAdditionEvent $event): void { if(!empty($event->metadata['tags'])) { send_event(new TagSetEvent($event->image, $event->metadata['tags'])); } - if(!empty($event->metadata['source'])) { - send_event(new SourceSetEvent($event->image, $event->metadata['source'])); - } - if (!empty($event->metadata['locked'])) { - send_event(new LockSetEvent($event->image, $event->metadata['locked'])); - } } public function onImageInfoSet(ImageInfoSetEvent $event): void { global $page, $user; - if ($user->can(Permissions::EDIT_IMAGE_OWNER) && isset($event->params['tag_edit__owner'])) { - $owner = User::by_name($event->params['tag_edit__owner']); - if ($owner instanceof User) { - send_event(new OwnerSetEvent($event->image, $owner)); - } else { - throw new UserNotFound("Error: No user with that name was found."); - } - } - if ($user->can(Permissions::EDIT_IMAGE_TAG) && isset($event->params['tag_edit__tags'])) { + if ($user->can(Permissions::EDIT_IMAGE_TAG) && isset($event->params['tags'])) { try { - send_event(new TagSetEvent($event->image, Tag::explode($event->params['tag_edit__tags']))); + send_event(new TagSetEvent($event->image, Tag::explode($event->params['tags']))); } catch (TagSetException $e) { if ($e->redirect) { $page->flash("{$e->getMessage()}, please see {$e->redirect}"); @@ -216,23 +142,6 @@ class TagEdit extends Extension } } } - if ($user->can(Permissions::EDIT_IMAGE_SOURCE) && isset($event->params['tag_edit__source'])) { - if (isset($event->params['tag_edit__tags']) ? !preg_match('/source[=|:]/', $event->params["tag_edit__tags"]) : true) { - send_event(new SourceSetEvent($event->image, $event->params['tag_edit__source'])); - } - } - if ($user->can(Permissions::EDIT_IMAGE_LOCK)) { - $locked = isset($event->params['tag_edit__locked']) && $event->params['tag_edit__locked'] == "on"; - send_event(new LockSetEvent($event->image, $locked)); - } - } - - public function onOwnerSet(OwnerSetEvent $event): void - { - global $user; - if ($user->can(Permissions::EDIT_IMAGE_OWNER) && (!$event->image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK))) { - $event->image->set_owner($event->owner); - } } public function onTagSet(TagSetEvent $event): void @@ -245,23 +154,6 @@ class TagEdit extends Extension send_event(new TagTermParseEvent($tag, $event->image->id)); } } - - public function onSourceSet(SourceSetEvent $event): void - { - global $user; - if ($user->can(Permissions::EDIT_IMAGE_SOURCE) && (!$event->image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK))) { - $event->image->set_source($event->source); - } - } - - public function onLockSet(LockSetEvent $event): void - { - global $user; - if ($user->can(Permissions::EDIT_IMAGE_LOCK)) { - $event->image->set_locked($event->locked); - } - } - public function onImageDeletion(ImageDeletionEvent $event): void { $event->image->delete_tags_from_image(); @@ -289,25 +181,7 @@ class TagEdit extends Extension public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event): void { - $event->add_part($this->theme->get_user_editor_html($event->image), 39); $event->add_part($this->theme->get_tag_editor_html($event->image), 40); - $event->add_part($this->theme->get_source_editor_html($event->image), 41); - $event->add_part($this->theme->get_lock_editor_html($event->image), 42); - } - - public function onTagTermCheck(TagTermCheckEvent $event): void - { - if (preg_match("/^source[=|:](.*)$/i", $event->term)) { - $event->metatag = true; - } - } - - public function onTagTermParse(TagTermParseEvent $event): void - { - if (preg_match("/^source[=|:](.*)$/i", $event->term, $matches)) { - $source = ($matches[1] !== "none" ? $matches[1] : null); - send_event(new SourceSetEvent(Image::by_id($event->image_id), $source)); - } } public function onParseLinkTemplate(ParseLinkTemplateEvent $event): void @@ -380,30 +254,4 @@ class TagEdit extends Extension } } } - - private function mass_source_edit(string $tags, string $source): void - { - $tags = Tag::explode($tags); - - $last_id = -1; - while (true) { - // make sure we don't look at the same images twice. - // search returns high-ids first, so we want to look - // at images with lower IDs than the previous. - $search_forward = $tags; - if ($last_id >= 0) { - $search_forward[] = "id<$last_id"; - } - - $images = Search::find_images(limit: 100, tags: $search_forward); - if (count($images) == 0) { - break; - } - - foreach ($images as $image) { - send_event(new SourceSetEvent($image, $source)); - $last_id = $image->id; - } - } - } } diff --git a/ext/tag_edit/test.php b/ext/post_tags/test.php similarity index 76% rename from ext/tag_edit/test.php rename to ext/post_tags/test.php index 8a1034dd..1ffb8eea 100644 --- a/ext/tag_edit/test.php +++ b/ext/post_tags/test.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Shimmie2; -class TagEditTest extends ShimmiePHPUnitTestCase +class PostTagsTest extends ShimmiePHPUnitTestCase { public function testValidChange(): void { @@ -46,17 +46,4 @@ class TagEditTest extends ShimmiePHPUnitTestCase $this->get_page("post/view/$image_id"); $this->assert_title("Post $image_id: tagme"); } - - public function testSourceEdit(): void - { - $this->log_in_as_user(); - $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); - $image = Image::by_id($image_id); - - send_event(new SourceSetEvent($image, "example.com")); - send_event(new SourceSetEvent($image, "http://example.com")); - - $this->get_page("post/view/$image_id"); - $this->assert_text("example.com"); - } } diff --git a/ext/post_tags/theme.php b/ext/post_tags/theme.php new file mode 100644 index 00000000..4a3a932f --- /dev/null +++ b/ext/post_tags/theme.php @@ -0,0 +1,56 @@ + +