[tag_edit] separate responsibility for tags, source, owner, and lock

This commit is contained in:
Shish 2024-02-12 17:39:10 +00:00 committed by Shish
parent 3d905b1055
commit 9c4e306b58
26 changed files with 545 additions and 322 deletions

View file

@ -38,8 +38,8 @@ class Artists extends Extension
public function onImageInfoSet(ImageInfoSetEvent $event): void public function onImageInfoSet(ImageInfoSetEvent $event): void
{ {
global $user; global $user;
if ($user->can(Permissions::EDIT_IMAGE_ARTIST) && isset($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["tag_edit__author"])); send_event(new AuthorSetEvent($event->image, $user, $event->params["author"]));
} }
} }

View file

@ -22,7 +22,7 @@ class ArtistsTheme extends Themelet
return SHM_POST_INFO( return SHM_POST_INFO(
"Author", "Author",
$author, $author,
INPUT(["type" => "text", "name" => "tag_edit__author", "value" => $author]) INPUT(["type" => "text", "name" => "author", "value" => $author])
); );
} }

View file

@ -29,11 +29,12 @@ class MetadataInput
public static function update_post_metadata(int $post_id, MetadataInput $metadata): Image public static function update_post_metadata(int $post_id, MetadataInput $metadata): Image
{ {
global $user; global $user;
$_POST['tag_edit__tags'] = $metadata->tags;
$_POST['tag_edit__source'] = $metadata->source;
$image = Image::by_id($post_id); $image = Image::by_id($post_id);
if (!$image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK)) { 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); return Image::by_id($post_id);
} }

18
ext/post_lock/info.php Normal file
View file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class PostLockInfo extends ExtensionInfo
{
public const KEY = "post_lock";
public string $key = self::KEY;
public string $name = "Lock Editor";
public bool $core = true;
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public ExtensionCategory $category = ExtensionCategory::METADATA;
public string $description = "Allow images to have metadata locked";
}

56
ext/post_lock/main.php Normal file
View file

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
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;
}
}
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);
}
}

37
ext/post_lock/test.php Normal file
View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class PostLockTest extends ShimmiePHPUnitTestCase
{
public function testLockEdit(): void
{
// user can post
$this->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"]));
}
}

22
ext/post_lock/theme.php Normal file
View file

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
use MicroHTML\HTMLElement;
use function MicroHTML\{INPUT};
class PostLockTheme extends Themelet
{
public function get_lock_editor_html(Image $image): HTMLElement
{
global $user;
return SHM_POST_INFO(
"Locked",
$image->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
);
}
}

18
ext/post_owner/info.php Normal file
View file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class PostOwnerInfo extends ExtensionInfo
{
public const KEY = "post_owner";
public string $key = self::KEY;
public string $name = "Owner Editor";
public bool $core = true;
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public ExtensionCategory $category = ExtensionCategory::METADATA;
public string $description = "Allow images to have owners assigned to them";
}

55
ext/post_owner/main.php Normal file
View file

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
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 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);
}
}

22
ext/post_owner/test.php Normal file
View file

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class PostOwnerTest extends ShimmiePHPUnitTestCase
{
public function testOwnerEdit(): void
{
$this->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);
}
}

34
ext/post_owner/theme.php Normal file
View file

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
use MicroHTML\HTMLElement;
use function MicroHTML\{TR, TH, TD, emptyHTML, rawHTML, joinHTML, DIV, INPUT, A, TEXTAREA};
class PostOwnerTheme extends Themelet
{
public function get_owner_editor_html(Image $image): HTMLElement
{
global $user;
$owner = $image->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;
}
}

18
ext/post_source/info.php Normal file
View file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class PostSourceInfo extends ExtensionInfo
{
public const KEY = "post_source";
public string $key = self::KEY;
public string $name = "Source Editor";
public bool $core = true;
public string $url = self::SHIMMIE_URL;
public array $authors = self::SHISH_AUTHOR;
public ExtensionCategory $category = ExtensionCategory::METADATA;
public string $description = "Allow images to have sources assigned to them";
}

105
ext/post_source/main.php Normal file
View file

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
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 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;
}
}
}
}

21
ext/post_source/test.php Normal file
View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class PostSourceTest extends ShimmiePHPUnitTestCase
{
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 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");
}
}

54
ext/post_source/theme.php Normal file
View file

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
use MicroHTML\HTMLElement;
use function MicroHTML\{TR, TH, TD, emptyHTML, rawHTML, joinHTML, DIV, INPUT, A, TEXTAREA};
class PostSourceTheme extends Themelet
{
public function mss_html(string $terms): string
{
$h_terms = html_escape($terms);
$html = make_form(make_link("tag_edit/mass_source_set")) . "
<input type='hidden' name='tags' value='$h_terms'>
<input type='text' name='source' value=''>
<input type='submit' value='Set Source For All' onclick='return confirm(\"This will mass-edit all sources on the page.\\nAre you sure you want to do this?\")'>
</form>
";
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");
}
}

View file

@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Shimmie2; 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 $key = self::KEY;
public string $name = "Tag Editor"; public string $name = "Tag Editor";

View file

@ -8,40 +8,6 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\{InputInterface,InputArgument}; use Symfony\Component\Console\Input\{InputInterface,InputArgument};
use Symfony\Component\Console\Output\OutputInterface; 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 class TagSetException extends UserError
{ {
public ?string $redirect; 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 * 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; protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event): void public function onPageRequest(PageRequestEvent $event): void
@ -151,11 +104,6 @@ class TagEdit extends Extension
$page->set_mode(PageMode::REDIRECT); $page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("admin")); $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 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 public function onImageAddition(ImageAdditionEvent $event): void
{ {
if(!empty($event->metadata['tags'])) { if(!empty($event->metadata['tags'])) {
send_event(new TagSetEvent($event->image, $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 public function onImageInfoSet(ImageInfoSetEvent $event): void
{ {
global $page, $user; global $page, $user;
if ($user->can(Permissions::EDIT_IMAGE_OWNER) && isset($event->params['tag_edit__owner'])) { if ($user->can(Permissions::EDIT_IMAGE_TAG) && isset($event->params['tags'])) {
$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'])) {
try { 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) { } catch (TagSetException $e) {
if ($e->redirect) { if ($e->redirect) {
$page->flash("{$e->getMessage()}, please see {$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 public function onTagSet(TagSetEvent $event): void
@ -245,23 +154,6 @@ class TagEdit extends Extension
send_event(new TagTermParseEvent($tag, $event->image->id)); 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 public function onImageDeletion(ImageDeletionEvent $event): void
{ {
$event->image->delete_tags_from_image(); $event->image->delete_tags_from_image();
@ -289,25 +181,7 @@ class TagEdit extends Extension
public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event): void 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_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 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;
}
}
}
} }

View file

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Shimmie2; namespace Shimmie2;
class TagEditTest extends ShimmiePHPUnitTestCase class PostTagsTest extends ShimmiePHPUnitTestCase
{ {
public function testValidChange(): void public function testValidChange(): void
{ {
@ -46,17 +46,4 @@ class TagEditTest extends ShimmiePHPUnitTestCase
$this->get_page("post/view/$image_id"); $this->get_page("post/view/$image_id");
$this->assert_title("Post $image_id: tagme"); $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");
}
} }

56
ext/post_tags/theme.php Normal file
View file

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
use MicroHTML\HTMLElement;
use function MicroHTML\{joinHTML, A, TEXTAREA};
class PostTagsTheme extends Themelet
{
public function display_mass_editor(): void
{
global $page;
$html = "
" . make_form(make_link("tag_edit/replace")) . "
<table class='form'>
<tr><th>Search</th><td><input type='text' name='search' class='autocomplete_tags'></tr>
<tr><th>Replace</th><td><input type='text' name='replace' class='autocomplete_tags'></td></tr>
<tr><td colspan='2'><input type='submit' value='Replace'></td></tr>
</table>
</form>
";
$page->add_block(new Block("Mass Tag Edit", $html));
}
public function get_tag_editor_html(Image $image): HTMLElement
{
global $user;
$tag_links = [];
foreach ($image->get_tag_array() as $tag) {
$tag_links[] = A([
"href" => search_link([$tag]),
"class" => "tag",
"title" => "View all posts tagged $tag"
], $tag);
}
return SHM_POST_INFO(
"Tags",
joinHTML(", ", $tag_links),
$user->can(Permissions::EDIT_IMAGE_TAG) ? TEXTAREA([
"class" => "autocomplete_tags",
"type" => "text",
"name" => "tags",
"id" => "tag_editor",
"spellcheck" => "off",
], $image->get_tag_list()) : null,
link: Extension::is_enabled(TagHistoryInfo::KEY) ?
make_link("tag_history/{$image->id}") :
null,
);
}
}

View file

@ -56,8 +56,8 @@ class PostTitles extends Extension
{ {
global $user; global $user;
if ($user->can(Permissions::EDIT_IMAGE_TITLE) && isset($event->params["post_title"])) { if ($user->can(Permissions::EDIT_IMAGE_TITLE) && isset($event->params["title"])) {
send_event(new PostTitleSetEvent($event->image, $event->params["post_title"])); send_event(new PostTitleSetEvent($event->image, $event->params["title"]));
} }
} }

View file

@ -15,7 +15,7 @@ class PostTitlesTheme extends Themelet
return SHM_POST_INFO( return SHM_POST_INFO(
"Title", "Title",
$title, $title,
$can_set ? INPUT(["type" => "text", "name" => "post_title", "value" => $title]) : null $can_set ? INPUT(["type" => "text", "name" => "title", "value" => $title]) : null
); );
} }
} }

View file

@ -56,9 +56,9 @@ class Relationships extends Extension
{ {
global $user; global $user;
if ($user->can(Permissions::EDIT_IMAGE_RELATIONSHIPS)) { if ($user->can(Permissions::EDIT_IMAGE_RELATIONSHIPS)) {
if (isset($event->params['tag_edit__tags']) ? !preg_match('/parent[=|:]/', $event->params["tag_edit__tags"]) : true) { //Ignore tag_edit__parent if tags contain parent metatag if (isset($event->params['tags']) ? !preg_match('/parent[=|:]/', $event->params["tags"]) : true) { //Ignore parent if tags contain parent metatag
if (isset($event->params["tag_edit__parent"]) ? ctype_digit($event->params["tag_edit__parent"]) : false) { if (isset($event->params["parent"]) ? ctype_digit($event->params["parent"]) : false) {
send_event(new ImageRelationshipSetEvent($event->image->id, (int) $event->params["tag_edit__parent"])); send_event(new ImageRelationshipSetEvent($event->image->id, (int) $event->params["parent"]));
} else { } else {
$this->remove_parent($event->image->id); $this->remove_parent($event->image->id);
} }

View file

@ -40,7 +40,7 @@ class RelationshipsTheme extends Themelet
return SHM_POST_INFO( return SHM_POST_INFO(
"Parent", "Parent",
strval($image['parent_id']) ?: "None", strval($image['parent_id']) ?: "None",
!$user->is_anonymous() ? INPUT(["type" => "number", "name" => "tag_edit__parent", "value" => $image['parent_id']]) : null !$user->is_anonymous() ? INPUT(["type" => "number", "name" => "parent", "value" => $image['parent_id']]) : null
); );
} }

View file

@ -1,134 +0,0 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
use MicroHTML\HTMLElement;
use function MicroHTML\{TR, TH, TD, emptyHTML, rawHTML, joinHTML, DIV, INPUT, A, TEXTAREA};
class TagEditTheme extends Themelet
{
/*
* Display a form which links to tag_edit/replace with POST[search]
* and POST[replace] set appropriately
*/
public function display_mass_editor(): void
{
global $page;
$html = "
" . make_form(make_link("tag_edit/replace")) . "
<table class='form'>
<tr><th>Search</th><td><input type='text' name='search' class='autocomplete_tags'></tr>
<tr><th>Replace</th><td><input type='text' name='replace' class='autocomplete_tags'></td></tr>
<tr><td colspan='2'><input type='submit' value='Replace'></td></tr>
</table>
</form>
";
$page->add_block(new Block("Mass Tag Edit", $html));
}
public function mss_html(string $terms): string
{
$h_terms = html_escape($terms);
$html = make_form(make_link("tag_edit/mass_source_set")) . "
<input type='hidden' name='tags' value='$h_terms'>
<input type='text' name='source' value=''>
<input type='submit' value='Set Source For All' onclick='return confirm(\"This will mass-edit all sources on the page.\\nAre you sure you want to do this?\")'>
</form>
";
return $html;
}
public function get_tag_editor_html(Image $image): HTMLElement
{
global $user;
$tag_links = [];
foreach ($image->get_tag_array() as $tag) {
$tag_links[] = A([
"href" => search_link([$tag]),
"class" => "tag",
"title" => "View all posts tagged $tag"
], $tag);
}
return SHM_POST_INFO(
"Tags",
joinHTML(", ", $tag_links),
$user->can(Permissions::EDIT_IMAGE_TAG) ? TEXTAREA([
"class" => "autocomplete_tags",
"type" => "text",
"name" => "tag_edit__tags",
"id" => "tag_editor",
"spellcheck" => "off",
], $image->get_tag_list()) : null,
link: Extension::is_enabled(TagHistoryInfo::KEY) ?
make_link("tag_history/{$image->id}") :
null,
);
}
public function get_user_editor_html(Image $image): HTMLElement
{
global $user;
$owner = $image->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" => "tag_edit__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;
}
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" => "tag_edit__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");
}
public function get_lock_editor_html(Image $image): HTMLElement
{
global $user;
return SHM_POST_INFO(
"Locked",
$image->is_locked() ? "Yes (Only admins may edit these details)" : "No",
$user->can(Permissions::EDIT_IMAGE_LOCK) ? INPUT(["type" => "checkbox", "name" => "tag_edit__locked", "checked" => $image->is_locked()]) : null
);
}
}

View file

@ -7,11 +7,11 @@ namespace Shimmie2;
class ImageInfoSetEvent extends Event class ImageInfoSetEvent extends Event
{ {
public Image $image; public Image $image;
/** @var array<string, mixed> */ /** @var array<string, string> */
public array $params; public array $params;
/** /**
* @param array<string, mixed> $params * @param array<string, string> $params
*/ */
public function __construct(Image $image, array $params) public function __construct(Image $image, array $params)
{ {

View file

@ -75,7 +75,12 @@ class ViewPost extends Extension
$image_id = int_escape($event->req_POST('image_id')); $image_id = int_escape($event->req_POST('image_id'));
$image = Image::by_id($image_id); $image = Image::by_id($image_id);
if (!$image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK)) { if (!$image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK)) {
send_event(new ImageInfoSetEvent($image, $event->POST)); // currently all post metadata is string => string - in the future
// we might want to have a more complex type system, but for now
// we just filter out non-string keys
/** @var array<string, string> */
$kvs = array_filter($event->POST, 'is_string', ARRAY_FILTER_USE_KEY);
send_event(new ImageInfoSetEvent($image, $kvs));
$page->set_mode(PageMode::REDIRECT); $page->set_mode(PageMode::REDIRECT);
if ($event->get_GET('search')) { if ($event->get_GET('search')) {