diff --git a/ext/image/main.php b/ext/image/main.php
index 76cac8615..a0acad933 100644
--- a/ext/image/main.php
+++ b/ext/image/main.php
@@ -95,18 +95,6 @@ public function onPageRequest(PageRequestEvent $event)
}
}
}
- } elseif ($event->page_matches("image/replace")) {
- global $page, $user;
- if ($user->can(Permissions::REPLACE_IMAGE) && isset($_POST['image_id']) && $user->check_auth_token()) {
- $image = Image::by_id(int_escape($_POST['image_id']));
- if ($image) {
- $page->set_mode(PageMode::REDIRECT);
- $page->set_redirect(make_link('upload/replace/'.$image->id));
- } else {
- /* Invalid image ID */
- throw new ImageReplaceException("Post to replace does not exist.");
- }
- }
} elseif ($event->page_matches("image")) {
$num = int_escape($event->get_arg(0));
$this->send_file($num, "image");
@@ -164,9 +152,7 @@ public function onImageAddition(ImageAdditionEvent $event)
$event->image = $im;
return;
} else {
- $error = "Post
{$existing->id} ".
- "already has hash {$image->hash}:
".$this->theme->build_thumb_html($existing);
- throw new ImageAdditionException($error);
+ throw new ImageAdditionException(">>{$existing->id} already has hash {$image->hash}");
}
}
@@ -228,9 +214,7 @@ public function onImageReplace(ImageReplaceEvent $event)
$duplicate = Image::by_hash($image->hash);
if (!is_null($duplicate) && $duplicate->id != $id) {
- $error = "Post {$duplicate->id} " .
- "already has hash {$image->hash}:
" . $this->theme->build_thumb_html($duplicate);
- throw new ImageReplaceException($error);
+ throw new ImageReplaceException(">>{$duplicate->id} already has hash {$image->hash}");
}
if (strlen(trim($image->source ?? '')) == 0) {
@@ -263,7 +247,7 @@ public function onImageReplace(ImageReplaceEvent $event)
public function onUserPageBuilding(UserPageBuildingEvent $event)
{
$u_name = url_escape($event->display_user->name);
- $i_image_count = Image::count_images(["user={$event->display_user->name}"]);
+ $i_image_count = Search::count_images(["user={$event->display_user->name}"]);
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
$h_image_rate = sprintf("%.1f", ($i_image_count / $i_days_old));
$images_link = search_link(["user=$u_name"]);
diff --git a/ext/image/test.php b/ext/image/test.php
index 881d56a36..3b5821ab1 100644
--- a/ext/image/test.php
+++ b/ext/image/test.php
@@ -31,17 +31,7 @@ public function testDeleteRequest()
$this->log_in_as_admin();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test");
$_POST['image_id'] = "$image_id";
- send_event(new PageRequestEvent("image/delete"));
+ send_event(new PageRequestEvent("POST", "image/delete"));
$this->assertTrue(true); // FIXME: assert image was deleted?
}
-
- public function testReplaceRequest()
- {
- global $page;
- $this->log_in_as_admin();
- $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test");
- $_POST['image_id'] = "$image_id";
- send_event(new PageRequestEvent("image/replace"));
- $this->assertEquals(PageMode::REDIRECT, $page->mode);
- }
}
diff --git a/ext/image/theme.php b/ext/image/theme.php
index 9d8fd9d47..9ee253271 100644
--- a/ext/image/theme.php
+++ b/ext/image/theme.php
@@ -26,10 +26,8 @@ public function get_deleter_html(int $image_id): string
*/
public function get_replace_html(int $image_id): string
{
- return (string)SHM_SIMPLE_FORM(
- "image/replace",
- INPUT(["type" => 'hidden', "name" => 'image_id', "value" => $image_id]),
- INPUT(["type" => 'submit', "value" => 'Replace']),
- );
+ $form = SHM_FORM("replace/$image_id", "GET");
+ $form->appendChild(INPUT(["type" => 'submit', "value" => 'Replace']));
+ return (string)$form;
}
}
diff --git a/ext/image_hash_ban/test.php b/ext/image_hash_ban/test.php
index 5c8e433fa..5ba09b80d 100644
--- a/ext/image_hash_ban/test.php
+++ b/ext/image_hash_ban/test.php
@@ -49,7 +49,7 @@ public function testBan()
$this->assertEquals(200, $page->code);
}
- public function onNotSuccessfulTest(\Throwable $t): void
+ public function onNotSuccessfulTest(\Throwable $t): never
{
send_event(new RemoveImageHashBanEvent($this->hash));
parent::onNotSuccessfulTest($t); // TODO: Change the autogenerated stub
diff --git a/ext/image_view_counter/main.php b/ext/image_view_counter/main.php
index 269bd0950..cb4b945a4 100644
--- a/ext/image_view_counter/main.php
+++ b/ext/image_view_counter/main.php
@@ -70,7 +70,7 @@ public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event)
["image_id" => $event->image->id]
);
- $event->add_part(SHM_POST_INFO("Views", false, $view_count, ""), 38);
+ $event->add_part(SHM_POST_INFO("Views", $view_count), 38);
}
}
diff --git a/ext/index/events.php b/ext/index/events.php
index f89f1eb75..0c9578722 100644
--- a/ext/index/events.php
+++ b/ext/index/events.php
@@ -67,7 +67,7 @@ class PostListBuildingEvent extends Event
public array $parts = [];
/**
- * #param string[] $search
+ * @param string[] $search
*/
public function __construct(array $search)
{
diff --git a/ext/index/main.php b/ext/index/main.php
index d8c57900a..9bfd6bfe9 100644
--- a/ext/index/main.php
+++ b/ext/index/main.php
@@ -22,7 +22,7 @@ public function onInitExt(InitExtEvent $event)
public function onPageRequest(PageRequestEvent $event)
{
- global $cache, $page, $user;
+ global $cache, $config, $page, $user;
if ($event->page_matches("post/list")) {
if (isset($_GET['search'])) {
$page->set_mode(PageMode::REDIRECT);
@@ -67,7 +67,7 @@ public function onPageRequest(PageRequestEvent $event)
return;
}
- $total_pages = Image::count_pages($search_terms);
+ $total_pages = (int)ceil(Search::count_images($search_terms) / $config->get_int(IndexConfig::IMAGES));
$images = [];
if (SPEED_HAX && $total_pages > $fast_page_limit && !$user->can("big_search")) {
@@ -77,16 +77,16 @@ public function onPageRequest(PageRequestEvent $event)
if (SPEED_HAX) {
if ($count_search_terms === 0 && ($page_number < 10)) {
// extra caching for the first few post/list pages
- $images = $cache->get("post-list:$page_number");
- if (is_null($images)) {
- $images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
- $cache->set("post-list:$page_number", $images, 60);
- }
+ $images = cache_get_or_set(
+ "post-list:$page_number",
+ fn () => Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms),
+ 60
+ );
}
}
if (!$images) {
- $images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
+ $images = Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
}
} catch (PermissionDeniedException $pde) {
$this->theme->display_error(403, "Permission denied", $pde->error);
@@ -156,7 +156,7 @@ public function onCommand(CommandEvent $event)
}
if ($event->cmd == "search") {
$query = count($event->args) > 0 ? Tag::explode($event->args[0]) : [];
- $items = Image::find_images(limit: 1000, tags: $query);
+ $items = Search::find_images(limit: 1000, tags: $query);
foreach ($items as $item) {
print("{$item->hash}\n");
}
diff --git a/ext/index/script.js b/ext/index/script.js
index 0f17ce939..a599a97e1 100644
--- a/ext/index/script.js
+++ b/ext/index/script.js
@@ -1,7 +1,7 @@
/*jshint bitwise:false, curly:true, eqeqeq:true, evil:true, forin:false, noarg:true, noempty:true, nonew:true, undef:false, strict:false, browser:true, jquery:true */
document.addEventListener('DOMContentLoaded', () => {
- var blocked_tags = (Cookies.get("ui-blocked-tags") || "").split(" ");
+ var blocked_tags = (shm_cookie_get("ui-blocked-tags") || "").split(" ");
var needs_refresh = false;
for(var i=0; i {
needs_refresh = true;
}
}
- // need to trigger a reflow in opera, because opera implements
- // text-align: justify with element margins and doesn't recalculate
- // these margins when part of the line disappears...
- if(needs_refresh) {
- $('.shm-image-list').hide(
- 0,
- function() {$('.shm-image-list').show();}
- );
- }
//Generate a random seed when using order:random
$('form > input[placeholder="Search"]').parent().submit(function(e){
@@ -38,20 +29,20 @@ document.addEventListener('DOMContentLoaded', () => {
* This allows us to cache the same thumb for all query
* strings, adding the query in the browser.
*/
- $(".shm-image-list").each(function(idx, elm) {
- var query = $(this).data("query");
+ document.querySelectorAll(".shm-image-list").forEach(function(list) {
+ var query = list.getAttribute("data-query");
if(query) {
- $(this).find(".shm-thumb-link").each(function(idx2, elm2) {
- $(this).attr("href", $(this).attr("href") + query);
+ list.querySelectorAll(".shm-thumb-link").forEach(function(thumb) {
+ thumb.setAttribute("href", thumb.getAttribute("href") + query);
});
}
});
});
function select_blocked_tags() {
- var blocked_tags = prompt("Enter tags to ignore", Cookies.get("ui-blocked-tags") || "AI-generated");
+ var blocked_tags = prompt("Enter tags to ignore", shm_cookie_get("ui-blocked-tags") || "AI-generated");
if(blocked_tags !== null) {
- Cookies.set("ui-blocked-tags", blocked_tags.toLowerCase(), {expires: 365});
+ shm_cookie_set("ui-blocked-tags", blocked_tags.toLowerCase());
location.reload(true);
}
}
diff --git a/ext/index/style.css b/ext/index/style.css
index 5ecd5a282..8fdee55d7 100644
--- a/ext/index/style.css
+++ b/ext/index/style.css
@@ -1,5 +1,3 @@
-
-/*noinspection CssRedundantUnit*/
#image-list .blockbody {
background: none;
border: none;
diff --git a/ext/index/test.php b/ext/index/test.php
index 6f21fa624..ab6d16f9b 100644
--- a/ext/index/test.php
+++ b/ext/index/test.php
@@ -4,6 +4,8 @@
namespace Shimmie2;
+use PHPUnit\Framework\Attributes\Depends;
+
class IndexTest extends ShimmiePHPUnitTestCase
{
public function testIndexPage()
@@ -47,177 +49,6 @@ public function testIndexPage()
$this->assert_response(200);
}
- public function testWeirdTags()
- {
- $this->log_in_as_user();
- $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "question? colon:thing exclamation!");
- $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "question. colon_thing exclamation%");
-
- $this->assert_search_results(["question?"], [$image_id_1]);
- $this->assert_search_results(["question."], [$image_id_2]);
- $this->assert_search_results(["colon:thing"], [$image_id_1]);
- $this->assert_search_results(["colon_thing"], [$image_id_2]);
- $this->assert_search_results(["exclamation!"], [$image_id_1]);
- $this->assert_search_results(["exclamation%"], [$image_id_2]);
- }
-
- // base case
- public function testUpload(): array
- {
- $this->log_in_as_user();
- $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "thing computer screenshot pbx phone");
- $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "thing computer computing bedroom workshop");
- $this->log_out();
-
- # make sure both uploads were ok
- $this->assertTrue($image_id_1 > 0);
- $this->assertTrue($image_id_2 > 0);
-
- return [$image_id_1, $image_id_2];
- }
-
- /* * * * * * * * * * *
- * Tag Search *
- * * * * * * * * * * */
- /** @depends testUpload */
- public function testTagSearchNoResults($image_ids)
- {
- $this->testUpload();
- $this->assert_search_results(["maumaumau"], []);
- }
-
- /** @depends testUpload */
- public function testTagSearchOneResult($image_ids)
- {
- $image_ids = $this->testUpload();
- $this->assert_search_results(["pbx"], [$image_ids[0]]);
- }
-
- /** @depends testUpload */
- public function testTagSearchManyResults($image_ids)
- {
- $image_ids = $this->testUpload();
- $this->assert_search_results(["computer"], [$image_ids[1], $image_ids[0]]);
- }
-
- /* * * * * * * * * * *
- * Multi-Tag Search *
- * * * * * * * * * * */
- /** @depends testUpload */
- public function testMultiTagSearchNoResults($image_ids)
- {
- $this->testUpload();
- # multiple tags, one of which doesn't exist
- # (test the "one tag doesn't exist = no hits" path)
- $this->assert_search_results(["computer", "asdfasdfwaffle"], []);
- }
-
- /** @depends testUpload */
- public function testMultiTagSearchOneResult($image_ids)
- {
- $image_ids = $this->testUpload();
- $this->assert_search_results(["computer", "screenshot"], [$image_ids[0]]);
- }
-
- /** @depends testUpload */
- public function testMultiTagSearchManyResults($image_ids)
- {
- $image_ids = $this->testUpload();
- $this->assert_search_results(["computer", "thing"], [$image_ids[1], $image_ids[0]]);
- }
-
- /* * * * * * * * * * *
- * Meta Search *
- * * * * * * * * * * */
- /** @depends testUpload */
- public function testMetaSearchNoResults($image_ids)
- {
- $this->testUpload();
- $this->assert_search_results(["hash=1234567890"], []);
- $this->assert_search_results(["ratio=42:12345"], []);
- }
-
- /** @depends testUpload */
- public function testMetaSearchOneResult($image_ids)
- {
- $image_ids = $this->testUpload();
- $this->assert_search_results(["hash=feb01bab5698a11dd87416724c7a89e3"], [$image_ids[0]]);
- $this->assert_search_results(["md5=feb01bab5698a11dd87416724c7a89e3"], [$image_ids[0]]);
- $this->assert_search_results(["id={$image_ids[1]}"], [$image_ids[1]]);
- $this->assert_search_results(["filename=screenshot"], [$image_ids[0]]);
- }
-
- /** @depends testUpload */
- public function testMetaSearchManyResults($image_ids)
- {
- $image_ids = $this->testUpload();
- $this->assert_search_results(["size=640x480"], [$image_ids[1], $image_ids[0]]);
- $this->assert_search_results(["tags=5"], [$image_ids[1], $image_ids[0]]);
- $this->assert_search_results(["ext=jpg"], [$image_ids[1], $image_ids[0]]);
- }
-
- /* * * * * * * * * * *
- * Wildcards *
- * * * * * * * * * * */
- /** @depends testUpload */
- public function testWildSearchNoResults($image_ids)
- {
- $this->testUpload();
- $this->assert_search_results(["asdfasdf*"], []);
- }
-
- /** @depends testUpload */
- public function testWildSearchOneResult($image_ids)
- {
- $image_ids = $this->testUpload();
- // Only the first image matches both the wildcard and the tag.
- // This checks for https://github.com/shish/shimmie2/issues/547
- $this->assert_search_results(["comp*", "screenshot"], [$image_ids[0]]);
- }
-
- /** @depends testUpload */
- public function testWildSearchManyResults($image_ids)
- {
- $image_ids = $this->testUpload();
- // two images match comp* - one matches it once,
- // one matches it twice
- $this->assert_search_results(["comp*"], [$image_ids[1], $image_ids[0]]);
- }
-
- /* * * * * * * * * * *
- * Mixed *
- * * * * * * * * * * */
- /** @depends testUpload */
- public function testMixedSearchTagMeta($image_ids)
- {
- $image_ids = $this->testUpload();
- // multiple tags, many results
- $this->assert_search_results(["computer", "size=640x480"], [$image_ids[1], $image_ids[0]]);
- }
- // tag + negative
- // wildcards + ???
-
- /* * * * * * * * * * *
- * Negative *
- * * * * * * * * * * */
- /** @depends testUpload */
- public function testNegative($image_ids)
- {
- $image_ids = $this->testUpload();
-
- // negative tag, should have one result
- $this->assert_search_results(["computer", "-pbx"], [$image_ids[1]]);
-
- // negative tag alone, should work
- $this->assert_search_results(["-pbx"], [$image_ids[1]]);
-
- // negative that doesn't exist
- $this->assert_search_results(["-not_a_tag"], [$image_ids[1], $image_ids[0]]);
-
- // multiple negative tags that don't exist
- $this->assert_search_results(["-not_a_tag", "-also_not_a_tag"], [$image_ids[1], $image_ids[0]]);
- }
-
// This isn't really an index thing, we just want to test this from
// SOMEWHERE because the default theme doesn't use them.
public function test_nav()
diff --git a/ext/index/theme.php b/ext/index/theme.php
index 6093c7a13..9932eb1fc 100644
--- a/ext/index/theme.php
+++ b/ext/index/theme.php
@@ -42,7 +42,7 @@ public function display_intro(Page $page)
}
/**
- * #param Image[] $images
+ * @param Image[] $images
*/
public function display_page(Page $page, array $images)
{
@@ -61,7 +61,7 @@ public function display_page(Page $page, array $images)
}
/**
- * #param string[] $parts
+ * @param string[] $parts
*/
public function display_admin_block(array $parts)
{
@@ -71,15 +71,13 @@ public function display_admin_block(array $parts)
/**
- * #param string[] $search_terms
+ * @param string[] $search_terms
*/
protected function build_navigation(int $page_number, int $total_pages, array $search_terms): string
{
$prev = $page_number - 1;
$next = $page_number + 1;
- $u_tags = url_escape(Tag::implode($search_terms));
-
$h_prev = ($page_number <= 1) ? "Prev" : 'Prev';
$h_index = "Index";
$h_next = ($page_number >= $total_pages) ? "Next" : 'Next';
@@ -88,7 +86,7 @@ protected function build_navigation(int $page_number, int $total_pages, array $s
$h_search_link = search_link();
$h_search = "
@@ -98,7 +96,7 @@ protected function build_navigation(int $page_number, int $total_pages, array $s
}
/**
- * #param Image[] $images
+ * @param Image[] $images
*/
protected function build_table(array $images, ?string $query): string
{
@@ -141,7 +139,7 @@ protected function display_shortwiki(Page $page)
}
/**
- * #param Image[] $images
+ * @param Image[] $images
*/
protected function display_page_header(Page $page, array $images)
{
@@ -167,7 +165,7 @@ protected function display_page_header(Page $page, array $images)
}
/**
- * #param Image[] $images
+ * @param Image[] $images
*/
protected function display_page_images(Page $page, array $images)
{
@@ -176,7 +174,7 @@ protected function display_page_images(Page $page, array $images)
// only index the first pages of each term
$page->add_html_header('');
}
- $query = url_escape(Tag::caret(Tag::implode($this->search_terms)));
+ $query = url_escape(Tag::implode($this->search_terms));
$page->add_block(new Block("Posts", $this->build_table($images, "#search=$query"), "main", 10, "image-list"));
$this->display_paginator($page, "post/list/$query", null, $this->page_number, $this->total_pages, true);
} else {
diff --git a/ext/log_console/main.php b/ext/log_console/main.php
index abfbe0bd5..5ff8b3580 100644
--- a/ext/log_console/main.php
+++ b/ext/log_console/main.php
@@ -20,13 +20,12 @@ public function onPageRequest(PageRequestEvent $event)
if (
$config->get_bool("log_console_access") &&
- isset($_SERVER['REQUEST_METHOD']) &&
isset($_SERVER['REQUEST_URI'])
) {
$this->log(new LogEvent(
"access",
SCORE_LOG_INFO,
- "{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']}"
+ "{$event->method} {$_SERVER['REQUEST_URI']}"
));
}
diff --git a/ext/media/main.php b/ext/media/main.php
index a5b29eb68..69c4d3066 100644
--- a/ext/media/main.php
+++ b/ext/media/main.php
@@ -277,6 +277,8 @@ public function onParseLinkTemplate(ParseLinkTemplateEvent $event)
} elseif ($event->image->length) {
$s = ((int)($event->image->length / 100)) / 10;
$event->replace('$size', "{$s}s");
+ } else {
+ $event->replace('$size', "unknown size");
}
}
diff --git a/ext/not_a_tag/main.php b/ext/not_a_tag/main.php
index 7b1028d8b..84ed8ee47 100644
--- a/ext/not_a_tag/main.php
+++ b/ext/not_a_tag/main.php
@@ -68,7 +68,7 @@ public function onTagSet(TagSetEvent $event)
}
/**
- * #param string[] $tags_mixed
+ * @param string[] $tags_mixed
*/
private function scan(array $tags_mixed)
{
@@ -90,7 +90,7 @@ private function scan(array $tags_mixed)
}
/**
- * #param string[] $tags
+ * @param string[] $tags
*/
private function strip(array $tags): array
{
diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php
index a42a25f69..028eca694 100644
--- a/ext/numeric_score/main.php
+++ b/ext/numeric_score/main.php
@@ -119,10 +119,10 @@ public function onUserPageBuilding(UserPageBuildingEvent $event)
$this->theme->get_nuller($event->display_user);
}
- $n_up = Image::count_images(["upvoted_by={$event->display_user->name}"]);
+ $n_up = Search::count_images(["upvoted_by={$event->display_user->name}"]);
$link_up = search_link(["upvoted_by={$event->display_user->name}"]);
- $n_down = Image::count_images(["downvoted_by={$event->display_user->name}"]);
- $link_down = search_link(["downvoted_by={$event->display_user->name}]"]);
+ $n_down = Search::count_images(["downvoted_by={$event->display_user->name}"]);
+ $link_down = search_link(["downvoted_by={$event->display_user->name}"]);
$event->add_stats("$n_up Upvotes / $n_down Downvotes");
}
diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php
index 29b78993b..93c348147 100644
--- a/ext/ouroboros_api/main.php
+++ b/ext/ouroboros_api/main.php
@@ -415,12 +415,12 @@ protected function postShow(int $id = null)
/**
* Wrapper for getting a list of posts
- * #param string[] $tags
+ * @param string[] $tags
*/
protected function postIndex(int $limit, int $page, array $tags)
{
$start = ($page - 1) * $limit;
- $results = Image::find_images(max($start, 0), min($limit, 100), $tags);
+ $results = Search::find_images(max($start, 0), min($limit, 100), $tags);
$posts = [];
foreach ($results as $img) {
if (!is_object($img)) {
diff --git a/ext/pm/main.php b/ext/pm/main.php
index 5600a2d94..4ff47a684 100644
--- a/ext/pm/main.php
+++ b/ext/pm/main.php
@@ -212,7 +212,7 @@ public function onUserPageBuilding(UserPageBuildingEvent $event)
if (!is_null($pms)) {
$this->theme->display_pms($page, $pms);
}
- if ($user->id != $duser->id) {
+ if ($user->can(Permissions::SEND_PM) && $user->id != $duser->id) {
$this->theme->display_composer($page, $user, $duser);
}
}
@@ -235,7 +235,11 @@ public function onPageRequest(PageRequestEvent $event)
$database->execute("UPDATE private_message SET is_read=true WHERE id = :id", ["id" => $pm_id]);
$cache->delete("pm-count-{$user->id}");
}
- $this->theme->display_message($page, $from_user, $user, PM::from_row($pm));
+ $pmo = PM::from_row($pm);
+ $this->theme->display_message($page, $from_user, $user, $pmo);
+ if($user->can(Permissions::SEND_PM)) {
+ $this->theme->display_composer($page, $user, $from_user, "Re: ".$pmo->subject);
+ }
} else {
$this->theme->display_permission_denied();
}
@@ -297,18 +301,17 @@ public function onSendPM(SendPMEvent $event)
private function count_pms(User $user)
{
- global $cache, $database;
+ global $database;
- $count = $cache->get("pm-count:{$user->id}");
- if (is_null($count)) {
- $count = $database->get_one("
- SELECT count(*)
- FROM private_message
- WHERE to_id = :to_id
- AND is_read = :is_read
- ", ["to_id" => $user->id, "is_read" => false]);
- $cache->set("pm-count:{$user->id}", $count, 600);
- }
- return $count;
+ return cache_get_or_set(
+ "pm-count:{$user->id}",
+ fn () => $database->get_one("
+ SELECT count(*)
+ FROM private_message
+ WHERE to_id = :to_id
+ AND is_read = :is_read
+ ", ["to_id" => $user->id, "is_read" => false]),
+ 600
+ );
}
}
diff --git a/ext/pm/theme.php b/ext/pm/theme.php
index c1cf4d064..c80e7f507 100644
--- a/ext/pm/theme.php
+++ b/ext/pm/theme.php
@@ -81,7 +81,6 @@ public function display_composer(Page $page, User $from, User $to, $subject = ""
public function display_message(Page $page, User $from, User $to, PM $pm)
{
- $this->display_composer($page, $to, $from, "Re: ".$pm->subject);
$page->set_title("Private Message");
$page->set_heading(html_escape($pm->subject));
$page->add_block(new NavBlock());
diff --git a/ext/pools/main.php b/ext/pools/main.php
index 226949564..e9f38cc6c 100644
--- a/ext/pools/main.php
+++ b/ext/pools/main.php
@@ -372,7 +372,7 @@ public function onPageRequest(PageRequestEvent $event)
break;
case "import":
if ($this->have_permission($user, $pool)) {
- $images = Image::find_images(
+ $images = Search::find_images(
limit: $config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000),
tags: Tag::explode($_POST["pool_tag"])
);
diff --git a/ext/pools/script.js b/ext/pools/script.js
index 16119d328..19b8ea9ad 100644
--- a/ext/pools/script.js
+++ b/ext/pools/script.js
@@ -3,7 +3,7 @@
document.addEventListener('DOMContentLoaded', () => {
$('#order_pool').change(function(){
var val = $("#order_pool option:selected").val();
- Cookies.set("shm_ui-order-pool", val, {path: '/', expires: 365}); //FIXME: This won't play nice if COOKIE_PREFIX is not "shm_".
+ shm_cookie_set("shm_ui-order-pool", val); //FIXME: This won't play nice if COOKIE_PREFIX is not "shm_".
window.location.href = '';
});
});
diff --git a/ext/pools/test.php b/ext/pools/test.php
index 67da7b489..a10f4cab2 100644
--- a/ext/pools/test.php
+++ b/ext/pools/test.php
@@ -4,6 +4,8 @@
namespace Shimmie2;
+use PHPUnit\Framework\Attributes\Depends;
+
class PoolsTest extends ShimmiePHPUnitTestCase
{
public function setUp(): void
@@ -50,7 +52,7 @@ public function testCreate(): array
return [$pool_id, [$image_id_1, $image_id_2]];
}
- /** @depends testCreate */
+ #[Depends('testCreate')]
public function testOnViewImage($args)
{
[$pool_id, $image_ids] = $this->testCreate();
@@ -64,7 +66,7 @@ public function testOnViewImage($args)
$this->assert_text("Pool");
}
- /** @depends testCreate */
+ #[Depends('testCreate')]
public function testSearch($args)
{
[$pool_id, $image_ids] = $this->testCreate();
@@ -76,7 +78,7 @@ public function testSearch($args)
$this->assert_text("Pool");
}
- /** @depends testCreate */
+ #[Depends('testCreate')]
public function testList($args)
{
$this->testCreate();
@@ -84,7 +86,7 @@ public function testList($args)
$this->assert_text("Pool");
}
- /** @depends testCreate */
+ #[Depends('testCreate')]
public function testView($args)
{
[$pool_id, $image_ids] = $this->testCreate();
@@ -93,7 +95,7 @@ public function testView($args)
$this->assert_text("Pool");
}
- /** @depends testCreate */
+ #[Depends('testCreate')]
public function testHistory($args)
{
[$pool_id, $image_ids] = $this->testCreate();
@@ -102,7 +104,7 @@ public function testHistory($args)
$this->assert_text("Pool");
}
- /** @depends testCreate */
+ #[Depends('testCreate')]
public function testImport($args)
{
[$pool_id, $image_ids] = $this->testCreate();
@@ -114,7 +116,7 @@ public function testImport($args)
$this->assert_text("Pool");
}
- /** @depends testCreate */
+ #[Depends('testCreate')]
public function testRemovePosts($args): array
{
[$pool_id, $image_ids] = $this->testCreate();
@@ -128,7 +130,7 @@ public function testRemovePosts($args): array
return [$pool_id, $image_ids];
}
- /** @depends testRemovePosts */
+ #[Depends('testRemovePosts')]
public function testAddPosts($args)
{
[$pool_id, $image_ids] = $this->testRemovePosts(null);
@@ -140,7 +142,7 @@ public function testAddPosts($args)
$this->assertEquals(PageMode::REDIRECT, $page->mode);
}
- /** @depends testCreate */
+ #[Depends('testCreate')]
public function testEditDescription($args): array
{
[$pool_id, $image_ids] = $this->testCreate();
diff --git a/ext/post_titles/theme.php b/ext/post_titles/theme.php
index 72ead5c88..f468fea23 100644
--- a/ext/post_titles/theme.php
+++ b/ext/post_titles/theme.php
@@ -14,9 +14,8 @@ public function get_title_set_html(string $title, bool $can_set): HTMLElement
{
return SHM_POST_INFO(
"Title",
- $can_set,
$title,
- INPUT(["type" => "text", "name" => "post_title", "value" => $title])
+ $can_set ? INPUT(["type" => "text", "name" => "post_title", "value" => $title]) : null
);
}
}
diff --git a/ext/random_list/theme.php b/ext/random_list/theme.php
index 0eac68f76..8ccd51342 100644
--- a/ext/random_list/theme.php
+++ b/ext/random_list/theme.php
@@ -9,7 +9,7 @@ class RandomListTheme extends Themelet
protected array $search_terms;
/**
- * #param string[] $search_terms
+ * @param string[] $search_terms
*/
public function set_page(array $search_terms)
{
@@ -17,7 +17,7 @@ public function set_page(array $search_terms)
}
/**
- * #param Image[] $images
+ * @param Image[] $images
*/
public function display_page(Page $page, array $images)
{
@@ -48,7 +48,7 @@ protected function build_navigation(array $search_terms): string
$h_search_link = make_link("random");
$h_search = "
diff --git a/ext/rating/main.php b/ext/rating/main.php
index 6137fb04c..d920a45c4 100644
--- a/ext/rating/main.php
+++ b/ext/rating/main.php
@@ -247,7 +247,9 @@ public function onImageInfoSet(ImageInfoSetEvent $event)
public function onParseLinkTemplate(ParseLinkTemplateEvent $event)
{
- $event->replace('$rating', $this->rating_to_human($event->image->rating));
+ if(!is_null($event->image->rating)) {
+ $event->replace('$rating', $this->rating_to_human($event->image->rating));
+ }
}
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
@@ -481,7 +483,7 @@ public function onPageRequest(PageRequestEvent $event)
} else {
$n = 0;
while (true) {
- $images = Image::find_images($n, 100, Tag::explode($_POST["query"]));
+ $images = Search::find_images($n, 100, Tag::explode($_POST["query"]));
if (count($images) == 0) {
break;
}
@@ -530,6 +532,11 @@ public static function get_ratings_dict(array $ratings = null): array
);
}
+ /**
+ * Figure out which ratings a user is allowed to see
+ *
+ * @return string[]
+ */
public static function get_user_class_privs(User $user): array
{
global $config;
@@ -537,6 +544,12 @@ public static function get_user_class_privs(User $user): array
return $config->get_array("ext_rating_".$user->class->name."_privs");
}
+ /**
+ * Figure out which ratings a user would like to see by default
+ * (Which will be a subset of what they are allowed to see)
+ *
+ * @return string[]
+ */
public static function get_user_default_ratings(): array
{
global $user_config, $user;
@@ -577,7 +590,7 @@ public static function rating_is_valid(string $rating): bool
}
/**
- * #param string[] $context
+ * @param string[] $context
*/
private function no_rating_query(array $context): bool
{
diff --git a/ext/rating/test.php b/ext/rating/test.php
index e9b73473d..999758349 100644
--- a/ext/rating/test.php
+++ b/ext/rating/test.php
@@ -38,4 +38,53 @@ public function testRatingExplicit()
$this->log_out();
$this->assert_search_results(["pbx"], []);
}
+
+ public function testUserConfig()
+ {
+ global $config, $user_config;
+
+ // post a safe image and an explicit image
+ $this->log_in_as_user();
+ $image_id_e = $this->post_image("tests/bedroom_workshop.jpg", "pbx");
+ $image_e = Image::by_id($image_id_e);
+ send_event(new RatingSetEvent($image_e, "e"));
+ $image_id_s = $this->post_image("tests/pbx_screenshot.jpg", "pbx");
+ $image_s = Image::by_id($image_id_s);
+ send_event(new RatingSetEvent($image_s, "s"));
+
+ // user is allowed to see all
+ $config->set_array("ext_rating_user_privs", ["s", "q", "e"]);
+
+ // user prefers safe-only by default
+ $user_config->set_array(RatingsConfig::USER_DEFAULTS, ["s"]);
+
+ // search with no tags should return only safe image
+ $this->assert_search_results([], [$image_id_s]);
+
+ // specifying a rating should return only that rating
+ $this->assert_search_results(["rating=e"], [$image_id_e]);
+ $this->assert_search_results(["rating=s"], [$image_id_s]);
+
+ // If user prefers to see all images, going to the safe image
+ // and clicking next should show the explicit image
+ $user_config->set_array(RatingsConfig::USER_DEFAULTS, ["s", "q", "e"]);
+ $this->assertEquals($image_s->get_next()->id, $image_id_e);
+
+ // If the user prefers to see only safe images by default, then
+ // going to the safe image and clicking next should not show
+ // the explicit image (See bug #984)
+ $user_config->set_array(RatingsConfig::USER_DEFAULTS, ["s"]);
+ $this->assertEquals($image_s->get_next(), null);
+ }
+
+ // reset the user config to defaults at the end of every test so
+ // that it doesn't mess with other unrelated tests
+ public function tearDown(): void
+ {
+ global $config, $user_config;
+ $config->set_array("ext_rating_user_privs", ["?", "s", "q", "e"]);
+
+ $this->log_in_as_user();
+ $user_config->set_array(RatingsConfig::USER_DEFAULTS, ["?", "s", "q", "e"]);
+ }
}
diff --git a/ext/rating/theme.php b/ext/rating/theme.php
index 654e9482b..a3bb0cd0c 100644
--- a/ext/rating/theme.php
+++ b/ext/rating/theme.php
@@ -20,9 +20,8 @@ public function get_rater_html(int $image_id, string $rating, bool $can_rate): H
{
return SHM_POST_INFO(
"Rating",
- $can_rate,
A(["href" => search_link(["rating=$rating"])], Ratings::rating_to_human($rating)),
- $this->get_selection_rater_html("rating", selected_options: [$rating])
+ $can_rate ? $this->get_selection_rater_html("rating", selected_options: [$rating]) : null
);
}
diff --git a/ext/regen_thumb/main.php b/ext/regen_thumb/main.php
index 227d23cdb..b5352cd14 100644
--- a/ext/regen_thumb/main.php
+++ b/ext/regen_thumb/main.php
@@ -30,7 +30,7 @@ public function onPageRequest(PageRequestEvent $event)
}
if ($event->page_matches("regen_thumb/mass") && $user->can(Permissions::DELETE_IMAGE) && isset($_POST['tags'])) {
$tags = Tag::explode(strtolower($_POST['tags']), false);
- $images = Image::find_images(limit: 10000, tags: $tags);
+ $images = Search::find_images(limit: 10000, tags: $tags);
foreach ($images as $image) {
$this->regenerate_thumbnail($image);
diff --git a/ext/relationships/test.php b/ext/relationships/test.php
index 6e24e0621..61bd83366 100644
--- a/ext/relationships/test.php
+++ b/ext/relationships/test.php
@@ -4,6 +4,8 @@
namespace Shimmie2;
+use PHPUnit\Framework\Attributes\Depends;
+
class RelationshipsTest extends ShimmiePHPUnitTestCase
{
//=================================================================
@@ -32,9 +34,7 @@ public function testNoParent(): array
return [$image_1, $image_2, $image_3];
}
- /**
- * @depends testNoParent
- */
+ #[Depends('testNoParent')]
public function testSetParent($imgs): array
{
[$image_1, $image_2, $image_3] = $this->testNoParent();
@@ -56,9 +56,7 @@ public function testSetParent($imgs): array
return [$image_1, $image_2, $image_3];
}
- /**
- * @depends testSetParent
- */
+ #[Depends('testSetParent')]
public function testChangeParent($imgs): array
{
[$image_1, $image_2, $image_3] = $this->testSetParent(null);
@@ -79,9 +77,7 @@ public function testChangeParent($imgs): array
return [$image_1, $image_2, $image_3];
}
- /**
- * @depends testSetParent
- */
+ #[Depends('testSetParent')]
public function testSearch($imgs)
{
[$image_1, $image_2, $image_3] = $this->testSetParent(null);
@@ -94,9 +90,7 @@ public function testSearch($imgs)
$this->assert_search_results(["child:none"], [$image_3->id, $image_2->id]);
}
- /**
- * @depends testChangeParent
- */
+ #[Depends('testChangeParent')]
public function testRemoveParent($imgs)
{
[$image_1, $image_2, $image_3] = $this->testChangeParent(null);
@@ -146,9 +140,7 @@ public function testSetParentByTagBase(): array
return [$image_1, $image_2, $image_3];
}
- /**
- * @depends testSetParentByTagBase
- */
+ #[Depends('testSetParentByTagBase')]
public function testSetParentByTag($imgs): array
{
[$image_1, $image_2, $image_3] = $this->testSetParentByTagBase();
@@ -171,9 +163,7 @@ public function testSetParentByTag($imgs): array
return [$image_1, $image_2, $image_3];
}
- /**
- * @depends testSetParentByTag
- */
+ #[Depends('testSetParentByTag')]
public function testSetChildByTag($imgs): array
{
[$image_1, $image_2, $image_3] = $this->testSetParentByTag(null);
@@ -196,9 +186,7 @@ public function testSetChildByTag($imgs): array
return [$image_1, $image_2, $image_3];
}
- /**
- * @depends testSetChildByTag
- */
+ #[Depends('testSetChildByTag')]
public function testRemoveParentByTag($imgs)
{
[$image_1, $image_2, $image_3] = $this->testSetChildByTag(null);
diff --git a/ext/relationships/theme.php b/ext/relationships/theme.php
index 0a8af4a56..9b976a32c 100644
--- a/ext/relationships/theme.php
+++ b/ext/relationships/theme.php
@@ -39,9 +39,8 @@ public function get_parent_editor_html(Image $image): HTMLElement
return SHM_POST_INFO(
"Parent",
- !$user->is_anonymous(),
strval($image->parent_id) ?: "None",
- INPUT(["type" => "number", "name" => "tag_edit__parent", "value" => $image->parent_id])
+ !$user->is_anonymous() ? INPUT(["type" => "number", "name" => "tag_edit__parent", "value" => $image->parent_id]) : null
);
}
diff --git a/ext/report_image/main.php b/ext/report_image/main.php
index 2cc5b4be4..4a348a7f1 100644
--- a/ext/report_image/main.php
+++ b/ext/report_image/main.php
@@ -240,14 +240,12 @@ public function get_reported_images(): array
public function count_reported_images(): int
{
- global $cache, $database;
-
- $count = $cache->get("image-report-count");
- if (is_null($count)) {
- $count = $database->get_one("SELECT count(*) FROM image_reports");
- $cache->set("image-report-count", $count, 600);
- }
+ global $database;
- return (int)$count;
+ return (int)cache_get_or_set(
+ "image-report-count",
+ fn () => $database->get_one("SELECT count(*) FROM image_reports"),
+ 600
+ );
}
}
diff --git a/ext/report_image/theme.php b/ext/report_image/theme.php
index 55a16b999..91af0b05f 100644
--- a/ext/report_image/theme.php
+++ b/ext/report_image/theme.php
@@ -56,7 +56,7 @@ public function display_reported_images(Page $page, array $reports)
}
/**
- * #param ImageReport[] $reports
+ * @param ImageReport[] $reports
*/
public function display_image_banner(Image $image, array $reports)
{
diff --git a/ext/rss_images/main.php b/ext/rss_images/main.php
index f1e015835..8d2ff3529 100644
--- a/ext/rss_images/main.php
+++ b/ext/rss_images/main.php
@@ -12,7 +12,7 @@ public function onPostListBuilding(PostListBuildingEvent $event)
$title = $config->get_string(SetupConfig::TITLE);
if (count($event->search_terms) > 0) {
- $search = url_escape(Tag::caret(Tag::implode($event->search_terms)));
+ $search = url_escape(Tag::implode($event->search_terms));
$page->add_html_header("");
} else {
@@ -31,7 +31,7 @@ public function onPageRequest(PageRequestEvent $event)
return;
}
try {
- $images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
+ $images = Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
$this->do_rss($images, $search_terms, $page_number);
} catch (SearchTermParseException $stpe) {
$this->theme->display_error(400, "Search parse error", $stpe->error);
diff --git a/ext/rule34/info.php b/ext/rule34/info.php
index cd01f775c..e2c9a4f65 100644
--- a/ext/rule34/info.php
+++ b/ext/rule34/info.php
@@ -17,4 +17,5 @@ class Rule34Info extends ExtensionInfo
public ?string $documentation =
"Probably not much use to other sites, but it gives a few examples of how a shimmie-based site can be integrated with other systems";
public array $db_support = [DatabaseDriverID::PGSQL]; # Only PG has the NOTIFY pubsub system
+ public ExtensionVisibility $visibility = ExtensionVisibility::HIDDEN;
}
diff --git a/ext/rule34/main.php b/ext/rule34/main.php
index 2ec39607c..6fce7c2ca 100644
--- a/ext/rule34/main.php
+++ b/ext/rule34/main.php
@@ -41,7 +41,6 @@ public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event)
$event->add_part(
SHM_POST_INFO(
"Links",
- false,
emptyHTML(
A(["href" => $url0], "File Only"),
" (",
@@ -84,7 +83,7 @@ public function onCommand(CommandEvent $event)
{
global $cache;
if ($event->cmd == "wipe-thumb-cache") {
- foreach (Image::find_images_iterable(0, null, Tag::explode($event->args[0])) as $image) {
+ foreach (Search::find_images_iterable(0, null, Tag::explode($event->args[0])) as $image) {
print($image->id . "\n");
$cache->delete("thumb-block:{$image->id}");
}
@@ -117,6 +116,9 @@ public function onPageRequest(PageRequestEvent $event)
# Database might not be connected at this point...
#$database->set_timeout(null); // deleting users can take a while
+ $page->add_html_header("");
+ $page->add_html_header("");
+
if (function_exists("sd_notify_watchdog")) {
\sd_notify_watchdog();
}
diff --git a/ext/rule34/script.js b/ext/rule34/script.js
index 9d22a5e69..6c41f30ed 100644
--- a/ext/rule34/script.js
+++ b/ext/rule34/script.js
@@ -1,22 +1,25 @@
+let tnc_div = document.createElement('div');
+tnc_div.innerHTML = `
+
+
+`;
+
+
document.addEventListener('DOMContentLoaded', () => {
- if(Cookies.get("ui-tnc-agreed") !== "true" && window.location.href.indexOf("/wiki/") == -1) {
- $("BODY").addClass("censored");
- $("BODY").append("");
- $("BODY").append(""+
- ""+
- "");
+ if(shm_cookie_get("ui-tnc-agreed") !== "true" && window.location.href.indexOf("/wiki/") == -1) {
+ document.body.classList.add('censored');
+ document.body.appendChild(tnc_div);
}
});
function tnc_agree() {
- Cookies.set("ui-tnc-agreed", "true", {path: '/', expires: 365});
- $("BODY").removeClass("censored");
- $(".tnc_bg").hide();
- $(".tnc").hide();
+ shm_cookie_set("ui-tnc-agreed", "true");
+ document.body.classList.remove('censored');
+ tnc_div.remove();
}
function image_hash_ban(id) {
diff --git a/ext/rule34/style.css b/ext/rule34/style.css
index 2e394c2dd..13aca22b5 100644
--- a/ext/rule34/style.css
+++ b/ext/rule34/style.css
@@ -20,7 +20,7 @@ BODY.censored FOOTER {
left: 20%;
right: 20%;
text-align: center;
- font-size: 2em;
+ font-size: 2rem;
background: #ACE4A3;
border: 1px solid #7EB977;
z-index: 9999999999999999999999;
diff --git a/ext/s3/config.php b/ext/s3/config.php
new file mode 100644
index 000000000..818f2a353
--- /dev/null
+++ b/ext/s3/config.php
@@ -0,0 +1,13 @@
+ self::SHISH_EMAIL];
+ public string $license = self::LICENSE_GPLV2;
+ public string $description = "Push post updates to S3";
+}
diff --git a/ext/s3/main.php b/ext/s3/main.php
new file mode 100644
index 000000000..f127de371
--- /dev/null
+++ b/ext/s3/main.php
@@ -0,0 +1,148 @@
+panel->create_new_block("S3 CDN");
+ $sb->add_text_option(S3Config::ACCESS_KEY_ID, "Access Key ID: ");
+ $sb->add_text_option(S3Config::ACCESS_KEY_SECRET, "
Access Key Secret: ");
+ $sb->add_text_option(S3Config::ENDPOINT, "
Endpoint: ");
+ $sb->add_text_option(S3Config::IMAGE_BUCKET, "
Image Bucket: ");
+ }
+
+ public function onCommand(CommandEvent $event)
+ {
+ if ($event->cmd == "help") {
+ print "\ts3-sync \n";
+ print "\t\tsync a post to s3\n\n";
+ print "\ts3-rm \n";
+ print "\t\tdelete a leftover file from s3\n\n";
+ }
+ if ($event->cmd == "s3-sync") {
+ if (preg_match('/^(\d+)-(\d+)$/', $event->args[0], $matches)) {
+ $start = (int)$matches[1];
+ $end = (int)$matches[2];
+ } else {
+ $start = (int)$event->args[0];
+ $end = $start;
+ }
+ foreach(Search::find_images_iterable(tags: ["order=id", "id>=$start", "id<=$end"]) as $image) {
+ print("{$image->id}: {$image->hash}\n");
+ ob_flush();
+ $this->sync_post($image);
+ }
+ }
+ if ($event->cmd == "s3-rm") {
+ foreach($event->args as $hash) {
+ print("{$hash}\n");
+ ob_flush();
+ $this->remove_file($hash);
+ }
+ }
+ }
+
+ public function onImageAddition(ImageAdditionEvent $event)
+ {
+ $this->sync_post($event->image);
+ }
+
+ public function onTagSet(TagSetEvent $event)
+ {
+ // pretend that tags were set already so that sync works
+ $orig_tags = $event->image->tag_array;
+ $event->image->tag_array = $event->tags;
+ $this->sync_post($event->image);
+ $event->image->tag_array = $orig_tags;
+ }
+
+ public function onImageDeletion(ImageDeletionEvent $event)
+ {
+ $this->remove_file($event->image->hash);
+ }
+
+ public function onImageReplace(ImageReplaceEvent $event)
+ {
+ $existing = Image::by_id($event->id);
+ $this->remove_file($existing->hash);
+ $this->sync_post($event->image);
+ }
+
+ // utils
+ private function get_client()
+ {
+ global $config;
+ $access_key_id = $config->get_string(S3Config::ACCESS_KEY_ID);
+ $access_key_secret = $config->get_string(S3Config::ACCESS_KEY_SECRET);
+ if(is_null($access_key_id) || is_null($access_key_secret)) {
+ return null;
+ }
+ $endpoint = $config->get_string(S3Config::ENDPOINT);
+ $credentials = new \Aws\Credentials\Credentials($access_key_id, $access_key_secret);
+ return new \Aws\S3\S3Client([
+ 'region' => 'auto',
+ 'endpoint' => $endpoint,
+ 'version' => 'latest',
+ 'credentials' => $credentials,
+ ]);
+ }
+
+ private function hash_to_path(string $hash)
+ {
+ $ha = substr($hash, 0, 2);
+ $sh = substr($hash, 2, 2);
+ return "$ha/$sh/$hash";
+ }
+
+ // underlying s3 interaction functions
+ private function sync_post(Image $image)
+ {
+ global $config;
+
+ // multiple events can trigger a sync,
+ // let's only do one per request
+ if(in_array($image->id, self::$synced)) {
+ return;
+ }
+ self::$synced[] = $image->id;
+
+ $client = $this->get_client();
+ if(is_null($client)) {
+ return;
+ }
+ $image_bucket = $config->get_string(S3Config::IMAGE_BUCKET);
+ $friendly = $image->parse_link_template('$id - $tags.$ext');
+ $client->putObject([
+ 'Bucket' => $image_bucket,
+ 'Key' => $this->hash_to_path($image->hash),
+ 'Body' => file_get_contents($image->get_image_filename()),
+ 'ACL' => 'public-read',
+ 'ContentType' => $image->get_mime(),
+ 'ContentDisposition' => "inline; filename=\"$friendly\"",
+ ]);
+ }
+
+ private function remove_file(string $hash)
+ {
+ global $config;
+ $client = $this->get_client();
+ if(is_null($client)) {
+ return;
+ }
+ $image_bucket = $config->get_string(S3Config::IMAGE_BUCKET);
+ $client->deleteObject([
+ 'Bucket' => $image_bucket,
+ 'Key' => $this->hash_to_path($hash),
+ ]);
+ }
+}
diff --git a/ext/setup/style.css b/ext/setup/style.css
index bc460fa4e..c8904a5b1 100644
--- a/ext/setup/style.css
+++ b/ext/setup/style.css
@@ -1,7 +1,5 @@
.setupblocks {
column-width: 400px;
- -moz-column-width: 400px;
- -webkit-column-width: 400px;
max-width: 1200px;
margin: auto;
}
@@ -9,31 +7,16 @@
.setupblock {
break-inside: avoid;
- -moz-break-inside: avoid;
- -webkit-break-inside: avoid;
column-break-inside: avoid;
- -moz-column-break-inside: avoid;
- -webkit-column-break-inside: avoid;
text-align: center;
width: 90%;
}
.setupblock TEXTAREA {
width: 100%;
- font-size: 0.75em;
+ font-size: 0.75rem;
resize: vertical;
}
-.helpable {
- border-bottom: 1px dashed gray;
-}
-
-.ok {
- background: #AFA;
-}
-.bad {
- background: #FAA;
-}
-
#Setupmain .blockbody {
background: none;
border: none;
diff --git a/ext/setup/theme.php b/ext/setup/theme.php
index bebf977aa..0baa14cad 100644
--- a/ext/setup/theme.php
+++ b/ext/setup/theme.php
@@ -94,11 +94,10 @@ protected function sb_to_html(SetupBlock $block): string
{
$h = $block->header;
$b = $block->body;
- $i = preg_replace('/[^a-zA-Z0-9]/', '_', $h) . "-setup";
$html = "
";
return $html;
diff --git a/ext/shimmie_api/main.php b/ext/shimmie_api/main.php
index cf590df2e..9fe638eed 100644
--- a/ext/shimmie_api/main.php
+++ b/ext/shimmie_api/main.php
@@ -70,7 +70,7 @@ public function onPageRequest(PageRequestEvent $event)
$search_terms = $event->get_search_terms();
$page_number = $event->get_page_number();
$page_size = $event->get_page_size();
- $images = Image::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
+ $images = Search::find_images(($page_number - 1) * $page_size, $page_size, $search_terms);
$safe_images = [];
foreach ($images as $image) {
$image->get_tag_array();
@@ -131,7 +131,7 @@ private function api_get_user(string $type, string $query): array
for ($i = 0; $i < 4; $i++) {
unset($all[$i]);
}
- $all['uploadcount'] = Image::count_images(["user_id=" . $all['id']]);
+ $all['uploadcount'] = Search::count_images(["user_id=" . $all['id']]);
$all['commentcount'] = $database->get_one(
"SELECT COUNT(*) AS count FROM comments WHERE owner_id=:owner_id",
["owner_id" => $all['id']]
diff --git a/ext/sitemap/main.php b/ext/sitemap/main.php
index c3fefe18a..0d08142b4 100644
--- a/ext/sitemap/main.php
+++ b/ext/sitemap/main.php
@@ -42,7 +42,7 @@ public function onSetupBuilding(SetupBuildingEvent $event)
private function handle_smaller_sitemap()
{
/* --- Add latest images to sitemap with higher priority --- */
- $latestimages = Image::find_images(limit: 50);
+ $latestimages = Search::find_images(limit: 50);
if (empty($latestimages)) {
return;
}
@@ -85,7 +85,7 @@ private function handle_full_sitemap()
$this->add_sitemap_queue($popular_tags, "monthly", "0.9" /* not sure how to deal with date here */);
/* --- Add latest images to sitemap with higher priority --- */
- $latestimages = Image::find_images(limit: 50);
+ $latestimages = Search::find_images(limit: 50);
$latestimages_urllist = [];
$latest_image = null;
foreach ($latestimages as $arrayid => $image) {
@@ -107,7 +107,7 @@ private function handle_full_sitemap()
$this->add_sitemap_queue($other_tags, "monthly", "0.7" /* not sure how to deal with date here */);
/* --- Add all other images to sitemap with lower priority --- */
- $otherimages = Image::find_images(offset: 51, limit: 10000000);
+ $otherimages = Search::find_images(offset: 51, limit: 10000000);
$image = null;
foreach ($otherimages as $arrayid => $image) {
// create url from image id's
diff --git a/ext/static_files/init.js b/ext/static_files/init.js
new file mode 100644
index 000000000..5cd4e5f85
--- /dev/null
+++ b/ext/static_files/init.js
@@ -0,0 +1,16 @@
+function shm_cookie_set(name, value) {
+ Cookies.set(name, value, {expires: 365, samesite: "lax", path: "/"});
+}
+function shm_cookie_get(name) {
+ return Cookies.get(name);
+}
+
+function shm_log(...message) {
+ window.dispatchEvent(new CustomEvent("shm_log", {detail: {message: message}}));
+}
+window.addEventListener("shm_log", function (e) {
+ console.log(...e.detail.message);
+});
+window.addEventListener("error", function (e) {
+ shm_log("Window error:", e.error);
+});
diff --git a/ext/static_files/modernizr-3.3.1.custom.js b/ext/static_files/modernizr-3.3.1.custom.js
deleted file mode 100644
index f5e084df4..000000000
--- a/ext/static_files/modernizr-3.3.1.custom.js
+++ /dev/null
@@ -1,3 +0,0 @@
-/*! modernizr 3.3.1 (Custom Build) | MIT *
- * https://modernizr.com/download/?-applicationcache-audio-backgroundsize-borderimage-borderradius-boxshadow-canvas-canvastext-cssanimations-csscolumns-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-flexbox-fontface-generatedcontent-hashchange-history-hsla-indexeddb-inlinesvg-input-inputtypes-localstorage-multiplebgs-opacity-postmessage-rgba-sessionstorage-smil-svg-svgclippaths-textshadow-video-webgl-websockets-websqldatabase-addtest-mq-setclasses-shiv !*/
-!function(e,t,n){function r(e,t){return typeof e===t}function a(){var e,t,n,a,o,i,s;for(var c in x)if(x.hasOwnProperty(c)){if(e=[],t=x[c],t.name&&(e.push(t.name.toLowerCase()),t.options&&t.options.aliases&&t.options.aliases.length))for(n=0;nf;f++)if(m=e[f],g=H.style[m],c(m,"-")&&(m=u(m)),H.style[m]!==n){if(o||r(a,"undefined"))return i(),"pfx"==t?m:!0;try{H.style[m]=a}catch(y){}if(H.style[m]!=g)return i(),"pfx"==t?m:!0}return i(),!1}function v(e,t,n,a,o){var i=e.charAt(0).toUpperCase()+e.slice(1),s=(e+" "+D.join(i+" ")+i).split(" ");return r(t,"string")||r(t,"undefined")?g(s,t,a,o):(s=(e+" "+V.join(i+" ")+i).split(" "),p(s,t,n))}function y(e,t,r){return v(e,n,n,t,r)}var b=[],x=[],T={_version:"3.3.1",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,t){var n=this;setTimeout(function(){t(n[e])},0)},addTest:function(e,t,n){x.push({name:e,fn:t,options:n})},addAsyncTest:function(e){x.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=T,Modernizr=new Modernizr,Modernizr.addTest("applicationcache","applicationCache"in e),Modernizr.addTest("history",function(){var t=navigator.userAgent;return-1===t.indexOf("Android 2.")&&-1===t.indexOf("Android 4.0")||-1===t.indexOf("Mobile Safari")||-1!==t.indexOf("Chrome")||-1!==t.indexOf("Windows Phone")?e.history&&"pushState"in e.history:!1}),Modernizr.addTest("postmessage","postMessage"in e),Modernizr.addTest("svg",!!t.createElementNS&&!!t.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect);var w=!1;try{w="WebSocket"in e&&2===e.WebSocket.CLOSING}catch(S){}Modernizr.addTest("websockets",w),Modernizr.addTest("localstorage",function(){var e="modernizr";try{return localStorage.setItem(e,e),localStorage.removeItem(e),!0}catch(t){return!1}}),Modernizr.addTest("sessionstorage",function(){var e="modernizr";try{return sessionStorage.setItem(e,e),sessionStorage.removeItem(e),!0}catch(t){return!1}}),Modernizr.addTest("websqldatabase","openDatabase"in e);var C=t.documentElement,E="svg"===C.nodeName.toLowerCase();E||!function(e,t){function n(e,t){var n=e.createElement("p"),r=e.getElementsByTagName("head")[0]||e.documentElement;return n.innerHTML="x",r.insertBefore(n.lastChild,r.firstChild)}function r(){var e=b.elements;return"string"==typeof e?e.split(" "):e}function a(e,t){var n=b.elements;"string"!=typeof n&&(n=n.join(" ")),"string"!=typeof e&&(e=e.join(" ")),b.elements=n+" "+e,l(t)}function o(e){var t=y[e[g]];return t||(t={},v++,e[g]=v,y[v]=t),t}function i(e,n,r){if(n||(n=t),u)return n.createElement(e);r||(r=o(n));var a;return a=r.cache[e]?r.cache[e].cloneNode():h.test(e)?(r.cache[e]=r.createElem(e)).cloneNode():r.createElem(e),!a.canHaveChildren||m.test(e)||a.tagUrn?a:r.frag.appendChild(a)}function s(e,n){if(e||(e=t),u)return e.createDocumentFragment();n=n||o(e);for(var a=n.frag.cloneNode(),i=0,s=r(),c=s.length;c>i;i++)a.createElement(s[i]);return a}function c(e,t){t.cache||(t.cache={},t.createElem=e.createElement,t.createFrag=e.createDocumentFragment,t.frag=t.createFrag()),e.createElement=function(n){return b.shivMethods?i(n,e,t):t.createElem(n)},e.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+r().join().replace(/[\w\-:]+/g,function(e){return t.createElem(e),t.frag.createElement(e),'c("'+e+'")'})+");return n}")(b,t.frag)}function l(e){e||(e=t);var r=o(e);return!b.shivCSS||d||r.hasCSS||(r.hasCSS=!!n(e,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),u||c(e,r),e}var d,u,f="3.7.3",p=e.html5||{},m=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,h=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,g="_html5shiv",v=0,y={};!function(){try{var e=t.createElement("a");e.innerHTML="",d="hidden"in e,u=1==e.childNodes.length||function(){t.createElement("a");var e=t.createDocumentFragment();return"undefined"==typeof e.cloneNode||"undefined"==typeof e.createDocumentFragment||"undefined"==typeof e.createElement}()}catch(n){d=!0,u=!0}}();var b={elements:p.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:f,shivCSS:p.shivCSS!==!1,supportsUnknownElements:u,shivMethods:p.shivMethods!==!1,type:"default",shivDocument:l,createElement:i,createDocumentFragment:s,addElements:a};e.html5=b,l(t),"object"==typeof module&&module.exports&&(module.exports=b)}("undefined"!=typeof e?e:this,t);var k;!function(){var e={}.hasOwnProperty;k=r(e,"undefined")||r(e.call,"undefined")?function(e,t){return t in e&&r(e.constructor.prototype[t],"undefined")}:function(t,n){return e.call(t,n)}}(),T._l={},T.on=function(e,t){this._l[e]||(this._l[e]=[]),this._l[e].push(t),Modernizr.hasOwnProperty(e)&&setTimeout(function(){Modernizr._trigger(e,Modernizr[e])},0)},T._trigger=function(e,t){if(this._l[e]){var n=this._l[e];setTimeout(function(){var e,r;for(e=0;e-1}),Modernizr.addTest("inlinesvg",function(){var e=s("div");return e.innerHTML="","http://www.w3.org/2000/svg"==("undefined"!=typeof SVGRect&&e.firstChild&&e.firstChild.namespaceURI)});var _=function(){function e(e,t){var a;return e?(t&&"string"!=typeof t||(t=s(t||"div")),e="on"+e,a=e in t,!a&&r&&(t.setAttribute||(t=s("div")),t.setAttribute(e,""),a="function"==typeof t[e],t[e]!==n&&(t[e]=n),t.removeAttribute(e)),a):!1}var r=!("onblur"in t.documentElement);return e}();T.hasEvent=_,Modernizr.addTest("hashchange",function(){return _("hashchange",e)===!1?!1:t.documentMode===n||t.documentMode>7});var P=s("input"),N="autocomplete autofocus list placeholder max min multiple pattern required step".split(" "),z={};Modernizr.input=function(t){for(var n=0,r=t.length;r>n;n++)z[t[n]]=!!(t[n]in P);return z.list&&(z.list=!(!s("datalist")||!e.HTMLDataListElement)),z}(N);var R="search tel url email datetime date month week time datetime-local number range color".split(" "),$={};Modernizr.inputtypes=function(e){for(var r,a,o,i=e.length,s="1)",c=0;i>c;c++)P.setAttribute("type",r=e[c]),o="text"!==P.type&&"style"in P,o&&(P.value=s,P.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(r)&&P.style.WebkitAppearance!==n?(C.appendChild(P),a=t.defaultView,o=a.getComputedStyle&&"textfield"!==a.getComputedStyle(P,null).WebkitAppearance&&0!==P.offsetHeight,C.removeChild(P)):/^(search|tel)$/.test(r)||(o=/^(url|email)$/.test(r)?P.checkValidity&&P.checkValidity()===!1:P.value!=s)),$[e[c]]=!!o;return $}(R);var A=T._config.usePrefixes?" -webkit- -moz- -o- -ms- ".split(" "):["",""];T._prefixes=A,Modernizr.addTest("cssgradients",function(){for(var e,t="background-image:",n="gradient(linear,left top,right bottom,from(#9f9),to(white));",r="",a=0,o=A.length-1;o>a;a++)e=0===a?"to ":"",r+=t+A[a]+"linear-gradient("+e+"left top, #9f9, white);";Modernizr._config.usePrefixes&&(r+=t+"-webkit-"+n);var i=s("a"),c=i.style;return c.cssText=r,(""+c.backgroundImage).indexOf("gradient")>-1}),Modernizr.addTest("opacity",function(){var e=s("a").style;return e.cssText=A.join("opacity:.55;"),/^0.55$/.test(e.opacity)}),Modernizr.addTest("hsla",function(){var e=s("a").style;return e.cssText="background-color:hsla(120,40%,100%,.5)",c(e.backgroundColor,"rgba")||c(e.backgroundColor,"hsla")});var O="CSS"in e&&"supports"in e.CSS,L="supportsCSS"in e;Modernizr.addTest("supports",O||L);var M={}.toString;Modernizr.addTest("svgclippaths",function(){return!!t.createElementNS&&/SVGClipPath/.test(M.call(t.createElementNS("http://www.w3.org/2000/svg","clipPath")))}),Modernizr.addTest("smil",function(){return!!t.createElementNS&&/SVGAnimate/.test(M.call(t.createElementNS("http://www.w3.org/2000/svg","animate")))});var B=function(){var t=e.matchMedia||e.msMatchMedia;return t?function(e){var n=t(e);return n&&n.matches||!1}:function(t){var n=!1;return d("@media "+t+" { #modernizr { position: absolute; } }",function(t){n="absolute"==(e.getComputedStyle?e.getComputedStyle(t,null):t.currentStyle).position}),n}}();T.mq=B;var j=T.testStyles=d;j('#modernizr{font:0/0 a}#modernizr:after{content:":)";visibility:hidden;font:7px/1 a}',function(e){Modernizr.addTest("generatedcontent",e.offsetHeight>=7)});var F=function(){var e=navigator.userAgent,t=e.match(/applewebkit\/([0-9]+)/gi)&&parseFloat(RegExp.$1),n=e.match(/w(eb)?osbrowser/gi),r=e.match(/windows phone/gi)&&e.match(/iemobile\/([0-9])+/gi)&&parseFloat(RegExp.$1)>=9,a=533>t&&e.match(/android/gi);return n||a||r}();F?Modernizr.addTest("fontface",!1):j('@font-face {font-family:"font";src:url("https://")}',function(e,n){var r=t.getElementById("smodernizr"),a=r.sheet||r.styleSheet,o=a?a.cssRules&&a.cssRules[0]?a.cssRules[0].cssText:a.cssText||"":"",i=/src/i.test(o)&&0===o.indexOf(n.split(" ")[0]);Modernizr.addTest("fontface",i)});var I="Moz O ms Webkit",D=T._config.usePrefixes?I.split(" "):[];T._cssomPrefixes=D;var q=function(t){var r,a=A.length,o=e.CSSRule;if("undefined"==typeof o)return n;if(!t)return!1;if(t=t.replace(/^@/,""),r=t.replace(/-/g,"_").toUpperCase()+"_RULE",r in o)return"@"+t;for(var i=0;a>i;i++){var s=A[i],c=s.toUpperCase()+"_"+r;if(c in o)return"@-"+s.toLowerCase()+"-"+t}return!1};T.atRule=q;var V=T._config.usePrefixes?I.toLowerCase().split(" "):[];T._domPrefixes=V;var W={elem:s("modernizr")};Modernizr._q.push(function(){delete W.elem});var H={style:W.elem.style};Modernizr._q.unshift(function(){delete H.style});var U=T.testProp=function(e,t,r){return g([e],n,t,r)};Modernizr.addTest("textshadow",U("textShadow","1px 1px")),T.testAllProps=v;var G,J=T.prefixed=function(e,t,n){return 0===e.indexOf("@")?q(e):(-1!=e.indexOf("-")&&(e=u(e)),t?v(e,t,n):v(e,"pfx"))};try{G=J("indexedDB",e)}catch(S){}Modernizr.addTest("indexeddb",!!G),G&&Modernizr.addTest("indexeddb.deletedatabase","deleteDatabase"in G),T.testAllProps=y,Modernizr.addTest("cssanimations",y("animationName","a",!0)),Modernizr.addTest("backgroundsize",y("backgroundSize","100%",!0)),Modernizr.addTest("borderimage",y("borderImage","url() 1",!0)),Modernizr.addTest("borderradius",y("borderRadius","0px",!0)),Modernizr.addTest("boxshadow",y("boxShadow","1px 1px",!0)),Modernizr.addTest("flexbox",y("flexBasis","1px",!0)),function(){Modernizr.addTest("csscolumns",function(){var e=!1,t=y("columnCount");try{(e=!!t)&&(e=new Boolean(e))}catch(n){}return e});for(var e,t,n=["Width","Span","Fill","Gap","Rule","RuleColor","RuleStyle","RuleWidth","BreakBefore","BreakAfter","BreakInside"],r=0;r {
/** Load jQuery extensions **/
//Code via: https://stackoverflow.com/a/13106698
@@ -32,7 +30,7 @@ document.addEventListener('DOMContentLoaded', () => {
/** Setup sidebar toggle **/
let sidebar_hidden = [];
try {
- sidebar_hidden = (Cookies.get("ui-sidebar-hidden") || "").split("|");
+ sidebar_hidden = (shm_cookie_get("ui-sidebar-hidden") || "").split("|");
for (let i=0; i 0) {
$(sidebar_hidden[i]+" .blockbody").hide();
@@ -55,7 +53,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
}
- Cookies.set("ui-sidebar-hidden", sidebar_hidden.join("|"), {expires: 365});
+ shm_cookie_set("ui-sidebar-hidden", sidebar_hidden.join("|"));
});
});
diff --git a/ext/static_files/style.css b/ext/static_files/style.css
index ded6fe841..b83b60f65 100644
--- a/ext/static_files/style.css
+++ b/ext/static_files/style.css
@@ -1,14 +1,15 @@
ARTICLE SELECT {width: 150px;}
INPUT, TEXTAREA {box-sizing: border-box;}
-TD>INPUT[type="button"],
-TD>INPUT[type="submit"],
-TD>INPUT[type="text"],
-TD>INPUT[type="password"],
-TD>INPUT[type="email"],
-TD>SELECT,
-TD>TEXTAREA,
-TD>BUTTON {width: 100%;}
+
+TD>INPUT[type="button"], TD>SPAN>INPUT[type="button"],
+TD>INPUT[type="submit"], TD>SPAN>INPUT[type="submit"],
+TD>INPUT[type="text"], TD>SPAN>INPUT[type="text"],
+TD>INPUT[type="password"], TD>SPAN>INPUT[type="password"],
+TD>INPUT[type="email"], TD>SPAN>INPUT[type="email"],
+TD>SELECT, TD>SPAN>SELECT,
+TD>TEXTAREA, TD>SPAN>TEXTAREA,
+TD>BUTTON, TD>SPAN>BUTTON {width: 100%;}
TABLE.form {width: 300px;}
TABLE.form TD, TABLE.form TH {vertical-align: middle;}
@@ -32,3 +33,7 @@ IMG.lazy {display: none;}
margin: 8px;
border: 1px solid #882;
}
+
+.tag {
+ overflow-wrap: anywhere;
+}
\ No newline at end of file
diff --git a/ext/tag_edit/main.php b/ext/tag_edit/main.php
index 47b0f13cc..309c4ae7f 100644
--- a/ext/tag_edit/main.php
+++ b/ext/tag_edit/main.php
@@ -56,7 +56,7 @@ class TagSetEvent extends Event
public array $metatags;
/**
- * #param string[] $tags
+ * @param string[] $tags
*/
public function __construct(Image $image, array $tags)
{
@@ -300,7 +300,7 @@ private function mass_tag_edit(string $search, string $replace, bool $commit)
log_info("tag_edit", "Mass editing tags: '$search' -> '$replace'");
if (count($search_set) == 1 && count($replace_set) == 1) {
- $images = Image::find_images(limit: 10, tags: $replace_set);
+ $images = Search::find_images(limit: 10, tags: $replace_set);
if (count($images) == 0) {
log_info("tag_edit", "No images found with target tag, doing in-place rename");
$database->execute(
@@ -329,7 +329,7 @@ private function mass_tag_edit(string $search, string $replace, bool $commit)
$search_forward[] = "id<$last_id";
}
- $images = Image::find_images(limit: 100, tags: $search_forward);
+ $images = Search::find_images(limit: 100, tags: $search_forward);
if (count($images) == 0) {
break;
}
@@ -364,7 +364,7 @@ private function mass_source_edit(string $tags, string $source)
$search_forward[] = "id<$last_id";
}
- $images = Image::find_images(limit: 100, tags: $search_forward);
+ $images = Search::find_images(limit: 100, tags: $search_forward);
if (count($images) == 0) {
break;
}
diff --git a/ext/tag_edit/theme.php b/ext/tag_edit/theme.php
index 66f4c8a24..408b976cc 100644
--- a/ext/tag_edit/theme.php
+++ b/ext/tag_edit/theme.php
@@ -6,7 +6,7 @@
use MicroHTML\HTMLElement;
-use function MicroHTML\{TR, TH, TD, emptyHTML, rawHTML, joinHTML, DIV, INPUT, A};
+use function MicroHTML\{TR, TH, TD, emptyHTML, rawHTML, joinHTML, DIV, INPUT, A, TEXTAREA};
class TagEditTheme extends Themelet
{
@@ -20,8 +20,8 @@ public function display_mass_editor()
$html = "
" . make_form(make_link("tag_edit/replace")) . "
@@ -56,16 +56,16 @@ public function get_tag_editor_html(Image $image): HTMLElement
return SHM_POST_INFO(
"Tags",
- $user->can(Permissions::EDIT_IMAGE_TAG),
joinHTML(", ", $tag_links),
- INPUT([
+ $user->can(Permissions::EDIT_IMAGE_TAG) ? TEXTAREA([
"class" => "autocomplete_tags",
"type" => "text",
"name" => "tag_edit__tags",
- "value" => $image->get_tag_list(),
"id" => "tag_editor",
- "autocomplete" => "off"
- ])
+ ], $image->get_tag_list()) : null,
+ link: Extension::is_enabled(TagHistoryInfo::KEY) ?
+ make_link("tag_history/{$image->id}") :
+ null,
);
}
@@ -77,9 +77,8 @@ public function get_user_editor_html(Image $image): HTMLElement
$ip = $user->can(Permissions::VIEW_IP) ? rawHTML(" (" . show_ip($image->owner_ip, "Post posted {$image->posted}") . ")") : "";
$info = SHM_POST_INFO(
"Uploader",
- $user->can(Permissions::EDIT_IMAGE_OWNER),
emptyHTML(A(["class" => "username", "href" => make_link("user/$owner")], $owner), $ip, ", ", $date),
- INPUT(["type" => "text", "name" => "tag_edit__owner", "value" => $owner])
+ $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
@@ -96,13 +95,13 @@ public function get_source_editor_html(Image $image): HTMLElement
{
global $user;
return SHM_POST_INFO(
- "Source",
- $user->can(Permissions::EDIT_IMAGE_SOURCE),
+ "Source Link",
DIV(
["style" => "overflow: hidden; white-space: nowrap; max-width: 350px; text-overflow: ellipsis;"],
$this->format_source($image->get_source())
),
- INPUT(["type" => "text", "name" => "tag_edit__source", "value" => $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,
);
}
@@ -127,9 +126,8 @@ public function get_lock_editor_html(Image $image): HTMLElement
global $user;
return SHM_POST_INFO(
"Locked",
- $user->can(Permissions::EDIT_IMAGE_LOCK),
$image->is_locked() ? "Yes (Only admins may edit these details)" : "No",
- INPUT(["type" => "checkbox", "name" => "tag_edit__locked", "checked" => $image->is_locked()])
+ $user->can(Permissions::EDIT_IMAGE_LOCK) ? INPUT(["type" => "checkbox", "name" => "tag_edit__locked", "checked" => $image->is_locked()]) : null
);
}
}
diff --git a/ext/tag_editcloud/style.css b/ext/tag_editcloud/style.css
index 0c5b9566d..995d2d56a 100644
--- a/ext/tag_editcloud/style.css
+++ b/ext/tag_editcloud/style.css
@@ -1,4 +1,4 @@
-span.tag-selected {
+.tageditcloud span.tag-selected {
background:#88EE88;
}
diff --git a/ext/tag_history/main.php b/ext/tag_history/main.php
index a748a5520..94da23e5f 100644
--- a/ext/tag_history/main.php
+++ b/ext/tag_history/main.php
@@ -346,7 +346,7 @@ public function process_revert_all_changes(?string $name, ?string $ip, ?string $
/**
* This function is called just before an images tag are changed.
*
- * #param string[] $tags
+ * @param string[] $tags
*/
private function add_tag_history(Image $image, array $tags)
{
diff --git a/ext/tag_list/main.php b/ext/tag_list/main.php
index ddea7eb07..9b77a1974 100644
--- a/ext/tag_list/main.php
+++ b/ext/tag_list/main.php
@@ -532,7 +532,7 @@ private function add_popular_block(Page $page)
}
/**
- * #param string[] $search
+ * @param string[] $search
*/
private function add_refine_block(Page $page, array $search)
{
@@ -555,7 +555,6 @@ public static function get_related_tags(array $search, int $limit): array
{
global $cache, $database;
-
$wild_tags = $search;
$cache_key = "related_tags:" . md5(Tag::implode($search));
$related_tags = $cache->get($cache_key);
diff --git a/ext/upload/bookmarklet.js b/ext/upload/bookmarklet.js
index 29c2945bf..bf22884f8 100644
--- a/ext/upload/bookmarklet.js
+++ b/ext/upload/bookmarklet.js
@@ -26,11 +26,10 @@ else if(CA === 2) { // New Tags
/*
* Danbooru2
- * jQuery should always active here, meaning we can use jQuery in this part of the bookmarklet.
*/
if(document.getElementById("image-container") !== null) {
- var imageContainer = $('#image-container')[0];
+ var imageContainer = document.querySelector('#image-container');
if (typeof tag !== "ftp://ftp." && chk !==1) {
var tag = imageContainer.getAttribute('data-tags');
}
@@ -40,9 +39,9 @@ if(document.getElementById("image-container") !== null) {
var rating = imageContainer.getAttribute('data-rating');
- var fileinfo = $('#sidebar > section:eq(3) > ul > :contains("Size") > a');
- var furl = "http://" + document.location.hostname + fileinfo.attr('href');
- var fs = fileinfo.text().split(" ");
+ var fileinfo = document.querySelector('#sidebar > section:eq(3) > ul > :contains("Size") > a');
+ var furl = "http://" + document.location.hostname + fileinfo.getAttribute('href');
+ var fs = fileinfo.innerText.split(" ");
var filesize = (fs[1] === "MB" ? fs[0] * 1024 : fs[0]);
if(supext.search(furl.match("[a-zA-Z0-9]+$")[0]) !== -1){
diff --git a/ext/upload/main.php b/ext/upload/main.php
index f22d55c41..bc53e1d05 100644
--- a/ext/upload/main.php
+++ b/ext/upload/main.php
@@ -58,6 +58,34 @@ class UploadException extends SCoreException
{
}
+abstract class UploadResult
+{
+ public function __construct(
+ public string $name
+ ) {
+ }
+}
+
+class UploadError extends UploadResult
+{
+ public function __construct(
+ string $name,
+ public string $error
+ ) {
+ parent::__construct($name);
+ }
+}
+
+class UploadSuccess extends UploadResult
+{
+ public function __construct(
+ string $name,
+ public int $image_id
+ ) {
+ parent::__construct($name);
+ }
+}
+
/**
* Main upload class.
* All files that are uploaded to the site are handled through this class.
@@ -183,7 +211,7 @@ public function onPageRequest(PageRequestEvent $event)
}
}
- if ($event->page_matches("upload/replace")) {
+ if ($event->page_matches("replace")) {
if (!$user->can(Permissions::REPLACE_IMAGE)) {
$this->theme->display_error(403, "Error", "{$user->name} doesn't have permission to replace images");
return;
@@ -193,35 +221,23 @@ public function onPageRequest(PageRequestEvent $event)
return;
}
- // Try to get the image ID
- if ($event->count_args() >= 1) {
- $image_id = int_escape($event->get_arg(0));
- } elseif (isset($_POST['image_id'])) {
- $image_id = int_escape($_POST['image_id']);
- } else {
- throw new UploadException("Can not replace Post: No valid Post ID given.");
- }
+ $image_id = int_escape($event->get_arg(0));
$image_old = Image::by_id($image_id);
if (is_null($image_old)) {
throw new UploadException("Can not replace Post: No post with ID $image_id");
}
- $source = $_POST['source'] ?? null;
-
- if (!empty($_POST["url"])) {
- $image_ids = $this->try_transload($_POST["url"], [], $source, $image_id);
- $cache->delete("thumb-block:{$image_id}");
- $this->theme->display_upload_status($page, $image_ids);
- } elseif (count($_FILES) > 0) {
- $image_ids = $this->try_upload($_FILES["data"], [], $source, $image_id);
- $cache->delete("thumb-block:{$image_id}");
- $this->theme->display_upload_status($page, $image_ids);
- } elseif (!empty($_GET['url'])) {
- $image_ids = $this->try_transload($_GET['url'], [], $source, $image_id);
- $cache->delete("thumb-block:{$image_id}");
- $this->theme->display_upload_status($page, $image_ids);
- } else {
+ if($event->method == "GET") {
$this->theme->display_replace_page($page, $image_id);
+ } elseif($event->method == "POST") {
+ $results = [];
+ if (!empty($_POST["url"])) {
+ $results = $this->try_transload($_POST["url"], [], $_POST['source'] ?? null, $image_id);
+ } elseif (count($_FILES) > 0) {
+ $results = $this->try_upload($_FILES["data"], [], $_POST['source'] ?? null, $image_id);
+ }
+ $cache->delete("thumb-block:{$image_id}");
+ $this->theme->display_upload_status($page, $results);
}
} elseif ($event->page_matches("upload")) {
if (!$user->can(Permissions::CREATE_IMAGE)) {
@@ -233,36 +249,32 @@ public function onPageRequest(PageRequestEvent $event)
return;
}
- /* Regular Upload Image */
- if (count($_FILES) > 0 || count($_POST) > 0) {
- $image_ids = [];
-
- foreach ($_FILES as $name => $file) {
- $tags = $this->tags_for_upload_slot(int_escape(substr($name, 4)));
- $source = $_POST['source'] ?? null;
- $image_ids += $this->try_upload($file, $tags, $source);
- }
- foreach ($_POST as $name => $value) {
- if (str_starts_with($name, "url") && strlen($value) > 0) {
- $tags = $this->tags_for_upload_slot(int_escape(substr($name, 3)));
- $source = $_POST['source'] ?? $value;
- $image_ids += $this->try_transload($value, $tags, $source);
- }
+ if($event->method == "GET") {
+ $this->theme->display_page($page);
+ } elseif($event->method == "POST") {
+ $results = [];
+
+ $files = array_filter($_FILES, function ($file) {
+ return !empty($file['name']);
+ });
+ foreach ($files as $name => $file) {
+ $slot = int_escape(substr($name, 4));
+ $tags = $this->tags_for_upload_slot($slot);
+ $source = $this->source_for_upload_slot($slot);
+ $results = array_merge($results, $this->try_upload($file, $tags, $source));
}
- $this->theme->display_upload_status($page, $image_ids);
- } elseif (!empty($_GET['url'])) {
- $url = $_GET['url'];
- $source = $_GET['source'] ?? $url;
- $tags = ['tagme'];
- if (!empty($_GET['tags']) && $_GET['tags'] != "null") {
- $tags = Tag::explode($_GET['tags']);
+ $urls = array_filter($_POST, function ($value, $key) {
+ return str_starts_with($key, "url") && strlen($value) > 0;
+ }, ARRAY_FILTER_USE_BOTH);
+ foreach ($urls as $name => $value) {
+ $slot = int_escape(substr($name, 3));
+ $tags = $this->tags_for_upload_slot($slot);
+ $source = $this->source_for_upload_slot($slot);
+ $results = array_merge($results, $this->try_transload($value, $tags, $source));
}
- $image_ids = $this->try_transload($url, $tags, $source);
- $this->theme->display_upload_status($page, $image_ids);
- } else {
- $this->theme->display_page($page);
+ $this->theme->display_upload_status($page, $results);
}
}
}
@@ -278,6 +290,11 @@ private function tags_for_upload_slot(int $id): array
);
}
+ private function source_for_upload_slot(int $id): ?string
+ {
+ return $_POST["source$id"] ?? $_POST['source'] ?? null;
+ }
+
/**
* Returns a descriptive error message for the specified PHP error code.
*
@@ -310,8 +327,9 @@ private function upload_error_message(int $error_code): string
/**
* Handle an upload.
- * #param string[] $file
- * #param string[] $tags
+ * @param mixed[] $file
+ * @param string[] $tags
+ * @return UploadResult[]
*/
private function try_upload(array $file, array $tags, ?string $source = null, ?int $replace_id = null): array
{
@@ -326,61 +344,50 @@ private function try_upload(array $file, array $tags, ?string $source = null, ?i
$source = null;
}
- $image_ids = [];
+ $results = [];
- $num_files = count($file['name']);
- $limit = $config->get_int(UploadConfig::COUNT);
- try {
- if ($num_files > $limit) {
- throw new UploadException("Upload limited to $limit");
- }
+ for ($i = 0; $i < count($file['name']); $i++) {
+ $name = $file['name'][$i];
+ $error = $file['error'][$i];
+ $tmp_name = $file['tmp_name'][$i];
- for ($i = 0; $i < $num_files; $i++) {
- if (empty($file['name'][$i])) {
- continue;
+ if (empty($name)) {
+ continue;
+ }
+ try {
+ // check if the upload was successful
+ if ($error !== UPLOAD_ERR_OK) {
+ throw new UploadException($this->upload_error_message($error));
}
- try {
- // check if the upload was successful
- if ($file['error'][$i] !== UPLOAD_ERR_OK) {
- throw new UploadException($this->upload_error_message($file['error'][$i]));
- }
-
- $metadata = [];
- $metadata['filename'] = pathinfo($file['name'][$i], PATHINFO_BASENAME);
- $metadata['tags'] = $tags;
- $metadata['source'] = $source;
-
- $event = new DataUploadEvent($file['tmp_name'][$i], $metadata, $replace_id);
- send_event($event);
- if ($event->image_id == -1) {
- throw new UploadException("MIME type not supported: " . $event->mime);
- }
- $image_ids[] = $event->image_id;
- $page->add_http_header("X-Shimmie-Post-ID: " . $event->image_id);
- } catch (UploadException $ex) {
- $this->theme->display_upload_error(
- $page,
- "Error with " . html_escape($file['name'][$i]),
- $ex->getMessage()
- );
+
+ $metadata = [];
+ $metadata['filename'] = pathinfo($name, PATHINFO_BASENAME);
+ $metadata['tags'] = $tags;
+ $metadata['source'] = $source;
+
+ $event = new DataUploadEvent($tmp_name, $metadata, $replace_id);
+ send_event($event);
+ if ($event->image_id == -1) {
+ throw new UploadException("MIME type not supported: " . $event->mime);
}
+ $results[] = new UploadSuccess($name, $event->image_id);
+ $page->add_http_header("X-Shimmie-Post-ID: " . $event->image_id);
+ } catch (UploadException $ex) {
+ $results[] = new UploadError($name, $ex->getMessage());
}
- } catch (UploadException $ex) {
- $this->theme->display_upload_error(
- $page,
- "Error with upload",
- $ex->getMessage()
- );
}
- return $image_ids;
+ return $results;
}
+ /**
+ * @return UploadResult[]
+ */
private function try_transload(string $url, array $tags, string $source = null, ?int $replace_id = null): array
{
global $page, $config, $user;
- $image_ids = [];
+ $results = [];
$tmp_filename = tempnam(ini_get('upload_tmp_dir'), "shimmie_transload");
try {
@@ -417,19 +424,15 @@ private function try_transload(string $url, array $tags, string $source = null,
if ($event->image_id == -1) {
throw new UploadException("File type not supported: " . $event->mime);
}
- $image_ids[] = $event->image_id;
+ $results[] = new UploadSuccess($url, $event->image_id);
} catch (UploadException $ex) {
- $this->theme->display_upload_error(
- $page,
- "Error with " . html_escape($url),
- $ex->getMessage()
- );
+ $results[] = new UploadError($url, $ex->getMessage());
} finally {
if (file_exists($tmp_filename)) {
unlink($tmp_filename);
}
}
- return $image_ids;
+ return $results;
}
}
diff --git a/ext/upload/minus.png b/ext/upload/minus.png
deleted file mode 100644
index 57c86dec5..000000000
Binary files a/ext/upload/minus.png and /dev/null differ
diff --git a/ext/upload/plus.png b/ext/upload/plus.png
deleted file mode 100644
index d7ec0ae30..000000000
Binary files a/ext/upload/plus.png and /dev/null differ
diff --git a/ext/upload/script.js b/ext/upload/script.js
new file mode 100644
index 000000000..cae836789
--- /dev/null
+++ b/ext/upload/script.js
@@ -0,0 +1,56 @@
+function fileSize(size) {
+ var i = Math.floor(Math.log(size) / Math.log(1024));
+ return (size / Math.pow(1024, i)).toFixed(2) * 1 + ['B', 'kB', 'MB', 'GB', 'TB'][i];
+}
+function updateTracker() {
+ var size = 0;
+ var upbtn = document.getElementById("uploadbutton");
+ var tracker = document.getElementById("upload_size_tracker");
+ var lockbtn = false;
+
+ // check that each individual file is less than the max file size
+ document.querySelectorAll("#large_upload_form input[type='file']").forEach((input) => {
+ var cancelbtn = document.getElementById("cancel"+input.id);
+ var toobig = false;
+ if (input.files.length) {
+ if(cancelbtn) cancelbtn.style.visibility = 'visible';
+ for (var i = 0; i < input.files.length; i++) {
+ size += input.files[i].size + 1024; // extra buffer for metadata
+ if (window.shm_max_size > 0 && input.files[i].size > window.shm_max_size) {
+ toobig = true;
+ }
+ }
+ if (toobig) {
+ lockbtn = true;
+ input.style = 'color:red';
+ } else {
+ input.style = 'color:initial';
+ }
+ } else {
+ if(cancelbtn) cancelbtn.style.visibility = 'hidden';
+ input.style = 'color:initial';
+ }
+ });
+
+ // check that the total is less than the max total size
+ if (size) {
+ tracker.innerText = fileSize(size);
+ if (window.shm_max_total_size > 0 && size > window.shm_max_total_size) {
+ lockbtn = true;
+ tracker.style = 'color:red';
+ } else {
+ tracker.style = 'color:initial';
+ }
+ } else {
+ tracker.innerText = '0MB';
+ }
+ upbtn.disabled = lockbtn;
+}
+document.addEventListener('DOMContentLoaded', () => {
+ if(document.getElementById("upload_size_tracker")) {
+ document.querySelectorAll("#large_upload_form input[type='file']").forEach((el) => {
+ el.addEventListener('change', updateTracker);
+ });
+ updateTracker();
+ }
+});
diff --git a/ext/upload/style.css b/ext/upload/style.css
index d6d803849..6dd19550d 100644
--- a/ext/upload/style.css
+++ b/ext/upload/style.css
@@ -1,17 +1,8 @@
-/* Only need to change the file/url inputs */
-#large_upload_form INPUT.wid {
- width: 100%;
-}
-#radio_button {
- width: auto;
-}
-#wrapper {
- opacity: 0.4;
- filter: alpha(opacity=40); /* msie */
+#large_upload_form TD,
+#large_upload_form TH {
+ vertical-align: middle;
}
-/* This is needed since the theme style.css forcibly sets vertical align to "top". */
-TABLE.vert TD, TABLE.vert TH {vertical-align: middle;}
.mini_upload INPUT {
width: 100%;
}
diff --git a/ext/upload/test.php b/ext/upload/test.php
index 9e12f99e0..be099ba6f 100644
--- a/ext/upload/test.php
+++ b/ext/upload/test.php
@@ -67,7 +67,7 @@ public function testRawReplace()
]
];
- $page = $this->post_page("upload/replace", ["image_id" => $image_id]);
+ $page = $this->post_page("replace/$image_id");
$this->assert_response(302);
$this->assertEquals("/test/post/view/$image_id", $page->redirect);
diff --git a/ext/upload/theme.php b/ext/upload/theme.php
index 77ba47c0b..78ee4005a 100644
--- a/ext/upload/theme.php
+++ b/ext/upload/theme.php
@@ -17,13 +17,12 @@
use function MicroHTML\DIV;
use function MicroHTML\BR;
use function MicroHTML\A;
+use function MicroHTML\SPAN;
use function MicroHTML\P;
class UploadTheme extends Themelet
{
- protected bool $has_errors = false;
-
public function display_block(Page $page): void
{
$b = new Block("Upload", (string)$this->build_upload_block(), "left", 20);
@@ -43,89 +42,41 @@ public function display_page(Page $page): void
$tl_enabled = ($config->get_string(UploadConfig::TRANSLOAD_ENGINE, "none") != "none");
$max_size = $config->get_int(UploadConfig::SIZE);
$max_kb = to_shorthand_int($max_size);
- $max_total_size = parse_shorthand_int(ini_get('post_max_size')) - 102400; //leave room for http request data
+ $max_total_size = parse_shorthand_int(ini_get('post_max_size'));
$max_total_kb = to_shorthand_int($max_total_size);
- $upload_list = $this->h_upload_list_1();
+ $upload_list = $this->build_upload_list();
$form = SHM_FORM("upload", "POST", true, "file_upload");
$form->appendChild(
TABLE(
- ["id" => "large_upload_form", "class" => "vert"],
+ ["id" => "large_upload_form"],
TR(
TD(["width" => "20"], rawHTML("Common Tags")),
- TD(["colspan" => "5"], INPUT(["name" => "tags", "type" => "text", "placeholder" => "tagme", "class" => "autocomplete_tags", "autocomplete" => "off"]))
+ TD(["colspan" => "6"], INPUT(["name" => "tags", "type" => "text", "placeholder" => "tagme", "class" => "autocomplete_tags"]))
),
TR(
TD(["width" => "20"], rawHTML("Common Source")),
- TD(["colspan" => "5"], INPUT(["name" => "source", "type" => "text"]))
+ TD(["colspan" => "6"], INPUT(["name" => "source", "type" => "text", "placeholder" => "https://..."]))
),
$upload_list,
TR(
- TD(["colspan" => $tl_enabled ? 2 : 4,"id" => "upload_size_tracker"], ""),
- TD(["colspan" => 2], ""),
- ),
- TR(
- TD(["colspan" => "6"], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"]))
+ TD(["colspan" => "7"], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"]))
),
)
);
$html = emptyHTML(
$form,
- SMALL("(Max file size is $max_kb)"),
- SMALL(BR(), "(Max total size is $max_total_kb)"),
+ SMALL(
+ "(",
+ $max_size > 0 ? "Per-file limit: $max_kb" : null,
+ $max_total_size > 0 ? " / Total limit: $max_total_kb" : null,
+ " / Current total: ",
+ SPAN(["id" => "upload_size_tracker"], "0KB"),
+ ")"
+ ),
rawHTML("")
);
@@ -134,11 +85,11 @@ function updateTracker(){
$page->add_block(new NavBlock());
$page->add_block(new Block("Upload", $html, "main", 20));
if ($tl_enabled) {
- $page->add_block(new Block("Bookmarklets", (string)$this->h_bookmarklets(), "left", 20));
+ $page->add_block(new Block("Bookmarklets", $this->build_bookmarklets(), "left", 20));
}
}
- protected function h_upload_list_1(): HTMLElement
+ protected function build_upload_list(): HTMLElement
{
global $config;
$upload_list = emptyHTML();
@@ -148,18 +99,53 @@ protected function h_upload_list_1(): HTMLElement
$upload_list->appendChild(
TR(
- TD(["colspan" => $tl_enabled ? 2 : 4], "Files"),
- $tl_enabled ? TD(["colspan" => "2"], "URLs") : emptyHTML(),
- TD(["colspan" => "2"], "Post-Specific Tags"),
+ TD(["colspan" => 2], "Select File"),
+ TD($tl_enabled ? "or URL" : null),
+ TD("Post-Specific Tags"),
+ TD("Post-Specific Source"),
)
);
for ($i = 0; $i < $upload_count; $i++) {
$upload_list->appendChild(
TR(
- TD(["colspan" => $tl_enabled ? 2 : 4], DIV(["name" => "canceldata{$i}[]","style" => "display:inline;margin-right:5px;font-size:15px;visibility:hidden;","onclick" => "$(\"input[name='data{$i}[]']\")[0].value='';updateTracker();"], "✖"), INPUT(["type" => "file", "name" => "data{$i}[]", "accept" => $accept, "multiple" => true])),
- $tl_enabled ? TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "url{$i}"])) : emptyHTML(),
- TD(["colspan" => "2"], INPUT(["type" => "text", "name" => "tags{$i}", "class" => "autocomplete_tags", "autocomplete" => "off"])),
+ TD(
+ ["colspan" => 2, "style" => "white-space: nowrap;"],
+ DIV([
+ "id" => "canceldata{$i}",
+ "style" => "display:inline;margin-right:5px;font-size:15px;visibility:hidden;",
+ "onclick" => "document.getElementById('data{$i}').value='';updateTracker();",
+ ], "✖"),
+ INPUT([
+ "type" => "file",
+ "id" => "data{$i}",
+ "name" => "data{$i}[]",
+ "accept" => $accept,
+ "multiple" => true,
+ ]),
+ ),
+ TD(
+ $tl_enabled ? INPUT([
+ "type" => "text",
+ "name" => "url{$i}",
+ "value" => ($i == 0) ? @$_GET['url'] : null,
+ ]) : null
+ ),
+ TD(
+ INPUT([
+ "type" => "text",
+ "name" => "tags{$i}",
+ "class" => "autocomplete_tags",
+ "value" => ($i == 0) ? @$_GET['tags'] : null,
+ ])
+ ),
+ TD(
+ INPUT([
+ "type" => "text",
+ "name" => "source{$i}",
+ "value" => ($i == 0) ? @$_GET['source'] : null,
+ ])
+ ),
)
);
}
@@ -167,7 +153,7 @@ protected function h_upload_list_1(): HTMLElement
return $upload_list;
}
- protected function h_bookmarklets(): HTMLElement
+ protected function build_bookmarklets(): HTMLElement
{
global $config;
$link = make_http(make_link("upload"));
@@ -217,7 +203,7 @@ function() {
';
$html2 = P(
A(["href" => $js], $title),
- rawHTML("(Click when looking at a post page. Works on sites running Shimmie / Danbooru / Gelbooru. (This also grabs the tags / rating / source!))"),
+ rawHTML(" (Click when looking at a post page. Works on sites running Shimmie / Danbooru / Gelbooru. (This also grabs the tags / rating / source!))"),
);
return emptyHTML($html1, $html2);
@@ -242,7 +228,7 @@ public function display_replace_page(Page $page, int $image_id)
$upload_list->appendChild(
TR(
TD("or URL"),
- TD(INPUT(["name" => "url", "type" => "text"]))
+ TD(INPUT(["name" => "url", "type" => "text", "value" => @$_GET['url']]))
)
);
}
@@ -253,11 +239,10 @@ public function display_replace_page(Page $page, int $image_id)
$image = Image::by_id($image_id);
$thumbnail = $this->build_thumb_html($image);
- $form = SHM_FORM("upload/replace/".$image_id, "POST", true);
+ $form = SHM_FORM("replace/".$image_id, "POST", true);
$form->appendChild(emptyHTML(
- INPUT(["type" => "hidden", "name" => "image_id", "value" => $image_id]),
TABLE(
- ["id" => "large_upload_form", "class" => "vert"],
+ ["id" => "large_upload_form"],
$upload_list,
TR(TD("Source"), TD(["colspan" => 3], INPUT(["name" => "source", "type" => "text"]))),
TR(TD(["colspan" => 4], INPUT(["id" => "uploadbutton", "type" => "submit", "value" => "Post"]))),
@@ -273,7 +258,7 @@ public function display_replace_page(Page $page, int $image_id)
$thumbnail,
BR(),
$form,
- SMALL("(Max file size is $max_kb)"),
+ $max_size > 0 ? SMALL("(Max file size is $max_kb)") : null,
);
$page->set_title("Replace Post");
@@ -282,37 +267,40 @@ public function display_replace_page(Page $page, int $image_id)
$page->add_block(new Block("Upload Replacement Post", $html, "main", 20));
}
- public function display_upload_status(Page $page, array $image_ids): void
+ /**
+ * @param UploadResult[] $results
+ */
+ public function display_upload_status(Page $page, array $results): void
{
global $user;
- if ($this->has_errors) {
+ /** @var UploadSuccess[] */
+ $successes = array_filter($results, fn ($r) => is_a($r, UploadSuccess::class));
+
+ /** @var UploadError[] */
+ $errors = array_filter($results, fn ($r) => is_a($r, UploadError::class));
+
+ if (count($errors) > 0) {
$page->set_title("Upload Status");
$page->set_heading("Upload Status");
$page->add_block(new NavBlock());
- } else {
- if (count($image_ids) < 1) {
- $page->set_title("No images uploaded");
- $page->set_heading("No images uploaded");
- $page->add_block(new NavBlock());
- } elseif (count($image_ids) == 1) {
- $page->set_mode(PageMode::REDIRECT);
- $page->set_redirect(make_link("post/view/{$image_ids[0]}"));
- } else {
- $page->set_mode(PageMode::REDIRECT);
- $page->set_redirect(search_link(["poster={$user->name}"]));
+ foreach($errors as $error) {
+ $page->add_block(new Block($error->name, format_text($error->error)));
}
+ } elseif (count($successes) == 0) {
+ $page->set_title("No images uploaded");
+ $page->set_heading("No images uploaded");
+ $page->add_block(new NavBlock());
+ $page->add_block(new Block("No images uploaded", "Upload attempted, but nothing succeeded and nothing failed?"));
+ } elseif (count($successes) == 1) {
+ $page->set_mode(PageMode::REDIRECT);
+ $page->set_redirect(make_link("post/view/{$successes[0]->image_id}"));
+ } else {
+ $page->set_mode(PageMode::REDIRECT);
+ $page->set_redirect(search_link(["poster={$user->name}"]));
}
}
- public function display_upload_error(Page $page, string $title, string $message): void
- {
- // this message has intentional HTML in it...
- $message = str_contains($message, "already has hash") ? $message : html_escape($message);
- $page->add_block(new Block($title, $message));
- $this->has_errors = true;
- }
-
protected function build_upload_block(): HTMLElement
{
global $config;
@@ -329,7 +317,7 @@ protected function build_upload_block(): HTMLElement
$form->appendChild(
emptyHTML(
INPUT(["id" => "data[]", "name" => "data[]", "size" => "16", "type" => "file", "accept" => $accept, "multiple" => true]),
- INPUT(["name" => "tags", "type" => "text", "placeholder" => "tagme", "class" => "autocomplete_tags", "required" => true, "autocomplete" => "off"]),
+ INPUT(["name" => "tags", "type" => "text", "placeholder" => "tagme", "class" => "autocomplete_tags", "required" => true]),
INPUT(["type" => "submit", "value" => "Post"]),
)
);
@@ -337,8 +325,8 @@ protected function build_upload_block(): HTMLElement
return DIV(
["class" => 'mini_upload'],
$form,
- SMALL("(Max file size is $max_kb)"),
- SMALL(BR(), "(Max total size is $max_total_kb)"),
+ $max_size > 0 ? SMALL("(Max file size is $max_kb)") : null,
+ $max_total_size > 0 ? SMALL(BR(), "(Max total size is $max_total_kb)") : null,
NOSCRIPT(BR(), A(["href" => make_link("upload")], "Larger Form"))
);
}
diff --git a/ext/user/main.php b/ext/user/main.php
index ce2c97334..9a3884f26 100644
--- a/ext/user/main.php
+++ b/ext/user/main.php
@@ -149,6 +149,7 @@ public function onInitExt(InitExtEvent $event)
$config->set_default_string("avatar_gravatar_default", "");
$config->set_default_string("avatar_gravatar_rating", "g");
$config->set_default_bool("login_tac_bbcode", true);
+ $config->set_default_bool("user_email_required", true);
}
public function onUserLogin(UserLoginEvent $event)
@@ -347,6 +348,7 @@ public function onSetupBuilding(SetupBuildingEvent $event)
$sb->start_table();
$sb->add_bool_option(UserConfig::ENABLE_API_KEYS, "Enable user API keys", true);
$sb->add_bool_option("login_signup_enabled", "Allow new signups", true);
+ $sb->add_bool_option("user_email_required", "Require email address", true);
$sb->add_longtext_option("login_tac", "Terms & Conditions", true);
$sb->add_choice_option(
"user_loginshowprofile",
@@ -423,11 +425,12 @@ public function onAdminBuilding(AdminBuildingEvent $event)
public function onUserCreation(UserCreationEvent $event)
{
+ global $config, $page, $user;
+
$name = $event->username;
//$pass = $event->password;
//$email = $event->email;
- global $config, $page, $user;
if (!$user->can(Permissions::CREATE_USER)) {
throw new UserCreationException("Account creation is currently disabled");
}
@@ -452,6 +455,12 @@ public function onUserCreation(UserCreationEvent $event)
if ($event->password != $event->password2) {
throw new UserCreationException("Passwords don't match");
}
+ if(
+ $user->can(Permissions::CREATE_OTHER_USER) ||
+ ($config->get_bool("user_email_required") && empty($event->email))
+ ) {
+ throw new UserCreationException("Email address is required");
+ }
$new_user = $this->create_user($event);
if ($event->login) {
diff --git a/ext/user/theme.php b/ext/user/theme.php
index dbba5ed74..d0997c258 100644
--- a/ext/user/theme.php
+++ b/ext/user/theme.php
@@ -63,14 +63,18 @@ public function display_user_block(Page $page, User $user, $parts)
public function display_signup_page(Page $page)
{
- global $config;
+ global $config, $user;
$tac = $config->get_string("login_tac", "");
if ($config->get_bool("login_tac_bbcode")) {
- $tfe = send_event(new TextFormattingEvent($tac));
- $tac = $tfe->formatted;
+ $tac = send_event(new TextFormattingEvent($tac))->formatted;
}
+ $email_required = (
+ $config->get_bool("user_email_required") &&
+ !$user->can(Permissions::CREATE_OTHER_USER)
+ );
+
$form = SHM_SIMPLE_FORM(
"user_admin/create",
TABLE(
@@ -89,8 +93,8 @@ public function display_signup_page(Page $page)
TD(INPUT(["type" => 'password', "name" => 'pass2', "required" => true]))
),
TR(
- TH(rawHTML("Email (Optional)")),
- TD(INPUT(["type" => 'email', "name" => 'email']))
+ TH($email_required ? "Email" : rawHTML("Email (Optional)")),
+ TD(INPUT(["type" => 'email', "name" => 'email', "required" => $email_required]))
),
TR(
TD(["colspan" => "2"], rawHTML(captcha_get_html()))
@@ -135,9 +139,12 @@ public function display_user_creator()
TD(INPUT(["type" => 'password', "name" => 'pass2', "required" => true]))
),
TR(
- TH(rawHTML("Email (Optional)")),
+ TH(rawHTML("Email")),
TD(INPUT(["type" => 'email', "name" => 'email']))
),
+ TR(
+ TD(["colspan" => 2], rawHTML("(Email is optional for admin-created accounts)")),
+ ),
),
TFOOT(
TR(TD(["colspan" => "2"], INPUT(["type" => "submit", "value" => "Create Account"])))
@@ -159,6 +166,11 @@ public function display_signups_disabled(Page $page)
}
public function display_login_block(Page $page)
+ {
+ $page->add_block(new Block("Login", $this->create_login_block(), "left", 90));
+ }
+
+ public function create_login_block(): HTMLElement
{
global $config, $user;
$form = SHM_SIMPLE_FORM(
@@ -187,7 +199,7 @@ public function display_login_block(Page $page)
$html->appendChild(SMALL(A(["href" => make_link("user_admin/create")], "Create Account")));
}
- $page->add_block(new Block("Login", $html, "left", 90));
+ return $html;
}
private function _ip_list(string $name, array $ips): HTMLElement
diff --git a/ext/user_config/style.css b/ext/user_config/style.css
deleted file mode 100644
index bc460fa4e..000000000
--- a/ext/user_config/style.css
+++ /dev/null
@@ -1,43 +0,0 @@
-.setupblocks {
- column-width: 400px;
- -moz-column-width: 400px;
- -webkit-column-width: 400px;
- max-width: 1200px;
- margin: auto;
-}
-.setupblocks > .setupblock:first-of-type { margin-top: 0; }
-
-.setupblock {
- break-inside: avoid;
- -moz-break-inside: avoid;
- -webkit-break-inside: avoid;
- column-break-inside: avoid;
- -moz-column-break-inside: avoid;
- -webkit-column-break-inside: avoid;
- text-align: center;
- width: 90%;
-}
-.setupblock TEXTAREA {
- width: 100%;
- font-size: 0.75em;
- resize: vertical;
-}
-
-.helpable {
- border-bottom: 1px dashed gray;
-}
-
-.ok {
- background: #AFA;
-}
-.bad {
- background: #FAA;
-}
-
-#Setupmain .blockbody {
- background: none;
- border: none;
- box-shadow: none;
- margin: 0;
- padding: 0;
-}
diff --git a/ext/view/info.php b/ext/view/info.php
index 27602aa08..e6cbd218f 100644
--- a/ext/view/info.php
+++ b/ext/view/info.php
@@ -4,7 +4,7 @@
namespace Shimmie2;
-class ViewImageInfo extends ExtensionInfo
+class ViewPostInfo extends ExtensionInfo
{
public const KEY = "view";
diff --git a/ext/view/main.php b/ext/view/main.php
index 52e674fb2..fbed253cd 100644
--- a/ext/view/main.php
+++ b/ext/view/main.php
@@ -13,20 +13,20 @@
use function MicroHTML\TH;
use function MicroHTML\TD;
-class ViewImage extends Extension
+class ViewPost extends Extension
{
- /** @var ViewImageTheme */
+ /** @var ViewPostTheme */
protected Themelet $theme;
public function onPageRequest(PageRequestEvent $event)
{
global $page, $user;
- if ($event->page_matches("post/prev") || $event->page_matches("post/next")) {
+ if ($event->page_matches("post/prev") || $event->page_matches("post/next")) {
$image_id = int_escape($event->get_arg(0));
if (isset($_GET['search'])) {
- $search_terms = Tag::explode(Tag::decaret($_GET['search']));
+ $search_terms = Tag::explode($_GET['search']);
$query = "#search=".url_escape($_GET['search']);
} else {
$search_terms = [];
@@ -123,7 +123,7 @@ public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event)
global $config;
$image_info = $config->get_string(ImageConfig::INFO);
if ($image_info) {
- $event->add_part(SHM_POST_INFO("Info", false, $event->image->get_info()), 85);
+ $event->add_part(SHM_POST_INFO("Info", $event->image->get_info()), 85);
}
}
}
diff --git a/ext/view/script.js b/ext/view/script.js
index 3c8799f3e..ed459c012 100644
--- a/ext/view/script.js
+++ b/ext/view/script.js
@@ -6,24 +6,35 @@ function joinUrlSegments(base, query) {
return base + separatorChar + query;
}
+function clearViewMode() {
+ document.querySelectorAll('.image_info').forEach((element) => {
+ element.classList.remove('infomode-view');
+ });
+}
+
+function updateAttr(selector, attr, value) {
+ document.querySelectorAll(selector).forEach(function(e) {
+ let current = e.getAttribute(attr);
+ let newval = joinUrlSegments(current, value);
+ e.setAttribute(attr, newval);
+ });
+}
+
document.addEventListener('DOMContentLoaded', () => {
+ // find elements with class image_info and set them to view mode
+ // (by default, with no js, they are in edit mode - so that no-js
+ // users can still edit them)
+ document.querySelectorAll('.image_info').forEach((element) => {
+ element.classList.add('infomode-view');
+ });
+
if(document.location.hash.length > 3) {
var query = document.location.hash.substring(1);
- $('LINK#prevlink').attr('href', function(i, attr) {
- return joinUrlSegments(attr,query);
- });
- $('LINK#nextlink').attr('href', function(i, attr) {
- return joinUrlSegments(attr,query);
- });
- $('A#prevlink').attr('href', function(i, attr) {
- return joinUrlSegments(attr,query);
- });
- $('A#nextlink').attr('href', function(i, attr) {
- return joinUrlSegments(attr,query);
- });
- $('span#image_delete_form form').attr('action', function(i, attr) {
- return joinUrlSegments(attr,query);
- });
+ updateAttr("LINK#prevlink", "href", query);
+ updateAttr("LINK#nextlink", "href", query);
+ updateAttr("A#prevlink", "href", query);
+ updateAttr("A#nextlink", "href", query);
+ updateAttr("span#image_delete_form form", "action", query);
}
});
diff --git a/ext/view/style.css b/ext/view/style.css
index d7d7c2c00..b321d98e0 100644
--- a/ext/view/style.css
+++ b/ext/view/style.css
@@ -1,15 +1,19 @@
-
-.js .image_info .edit {
- display: none;
-}
-.js .image_info .view {
- display: block;
+TABLE.form.image_info {
+ width: 550px;
+ max-width: 100%;
}
-.no-js .image_info .edit {
+.image_info .edit {
display: block;
}
-.no-js .image_info .view {
+.image_info .view {
display: none;
}
+.image_info.infomode-view .edit {
+ display: none;
+}
+.image_info.infomode-view .view {
+ display: block;
+}
+
diff --git a/ext/view/test.php b/ext/view/test.php
index 320270522..fc6898a07 100644
--- a/ext/view/test.php
+++ b/ext/view/test.php
@@ -4,7 +4,7 @@
namespace Shimmie2;
-class ViewImageTest extends ShimmiePHPUnitTestCase
+class ViewPostTest extends ShimmiePHPUnitTestCase
{
public function setUp(): void
{
diff --git a/ext/view/theme.php b/ext/view/theme.php
index f41b17d61..657a5404f 100644
--- a/ext/view/theme.php
+++ b/ext/view/theme.php
@@ -4,7 +4,11 @@
namespace Shimmie2;
-class ViewImageTheme extends Themelet
+use MicroHTML\HTMLElement;
+
+use function MicroHTML\{A, joinHTML, TABLE, TR, TD, INPUT, emptyHTML};
+
+class ViewPostTheme extends Themelet
{
public function display_meta_headers(Image $image)
{
@@ -45,21 +49,21 @@ public function display_admin_block(Page $page, $parts)
protected function get_query(): ?string
{
if (isset($_GET['search'])) {
- $query = "search=".url_escape(Tag::caret($_GET['search']));
+ $query = "search=".url_escape($_GET['search']);
} else {
$query = null;
}
return $query;
}
- protected function build_pin(Image $image): string
+ protected function build_pin(Image $image): HTMLElement
{
$query = $this->get_query();
- $h_prev = "Prev";
- $h_index = "Index";
- $h_next = "Next";
-
- return "$h_prev | $h_index | $h_next";
+ return joinHTML(" | ", [
+ A(["href" => make_link("post/prev/{$image->id}", $query), "id" => "prevlink"], "Prev"),
+ A(["href" => make_link()], "Index"),
+ A(["href" => make_link("post/next/{$image->id}", $query), "id" => "nextlink"], "Next"),
+ ]);
}
protected function build_navigation(Image $image): string
@@ -68,7 +72,7 @@ protected function build_navigation(Image $image): string
$h_search = "
";
@@ -76,36 +80,34 @@ protected function build_navigation(Image $image): string
return "$h_pin
$h_search";
}
- protected function build_info(Image $image, $editor_parts): string
+ protected function build_info(Image $image, $editor_parts): HTMLElement
{
global $user;
if (count($editor_parts) == 0) {
- return ($image->is_locked() ? "
[Post Locked]" : "");
+ return emptyHTML($image->is_locked() ? "[Post Locked]" : "");
}
- $html = make_form(make_link("post/set"))."
-
-
-
- ";
- return $html;
+
+ return SHM_SIMPLE_FORM(
+ "post/set",
+ INPUT(["type" => "hidden", "name" => "image_id", "value" => $image->id]),
+ TABLE(
+ [
+ "class" => "image_info form",
+ ],
+ ...$editor_parts,
+ ),
+ );
}
}
diff --git a/index.php b/index.php
index 7aebad1c4..1cc8e2c02 100644
--- a/index.php
+++ b/index.php
@@ -50,9 +50,7 @@
$cache = loadCache(CACHE_DSN);
$database = new Database(DATABASE_DSN);
$config = new DatabaseConfig($database);
-ExtensionInfo::load_all_extension_info();
-Extension::determine_enabled_extensions();
-require_all(zglob("ext/{".Extension::get_enabled_extensions_as_string()."}/main.php"));
+_load_extension_files();
_load_theme_files();
$page = new Page();
_load_event_listeners();
@@ -84,7 +82,7 @@
if (PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') {
send_event(new CommandEvent($argv));
} else {
- send_event(new PageRequestEvent(_get_query()));
+ send_event(new PageRequestEvent($_SERVER['REQUEST_METHOD'], _get_query()));
$page->display();
}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 2cd651ef3..05dcb4979 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -4,8 +4,6 @@
namespace Shimmie2;
-use PHPUnit\Framework\TestCase;
-
chdir(dirname(dirname(__FILE__)));
require_once "core/sanitize_php.php";
require_once "vendor/autoload.php";
@@ -15,8 +13,8 @@
require_once "core/util.php";
$_SERVER['QUERY_STRING'] = '/';
-if (file_exists("tests/trace.json")) {
- unlink("tests/trace.json");
+if (file_exists("data/test-trace.json")) {
+ unlink("data/test-trace.json");
}
global $cache, $config, $database, $user, $page, $_tracer;
@@ -26,242 +24,17 @@
$_tracer->begin("bootstrap");
_load_core_files();
$cache = loadCache(CACHE_DSN);
-$dsn = getenv("TEST_DSN");
-$database = new Database($dsn ? $dsn : "sqlite::memory:");
+$database = new Database(getenv("TEST_DSN") ?: "sqlite::memory:");
create_dirs();
create_tables($database);
$config = new DatabaseConfig($database);
-ExtensionInfo::load_all_extension_info();
-Extension::determine_enabled_extensions();
-require_all(zglob("ext/{".Extension::get_enabled_extensions_as_string()."}/main.php"));
+_load_extension_files();
_load_theme_files();
$page = new Page();
_load_event_listeners();
-$config->set_string("thumb_engine", "static"); # GD has less overhead per-call
+$config->set_string("thumb_engine", "static");
$config->set_bool("nice_urls", true);
send_event(new DatabaseUpgradeEvent());
send_event(new InitExtEvent());
$user = User::by_id($config->get_int("anon_id", 0));
$_tracer->end();
-
-abstract class ShimmiePHPUnitTestCase extends TestCase
-{
- protected static string $anon_name = "anonymous";
- protected static string $admin_name = "demo";
- protected static string $user_name = "test";
- protected string $wipe_time = "test";
-
- public static function setUpBeforeClass(): void
- {
- parent::setUpBeforeClass();
- global $_tracer;
- $_tracer->begin(get_called_class());
-
- self::create_user(self::$admin_name);
- self::create_user(self::$user_name);
- }
-
- public function setUp(): void
- {
- global $database, $_tracer;
- $_tracer->begin($this->getName());
- $_tracer->begin("setUp");
- $class = str_replace("Test", "", get_class($this));
- if (!ExtensionInfo::get_for_extension_class($class)->is_supported()) {
- $this->markTestSkipped("$class not supported with this database");
- }
-
- // Set up a clean environment for each test
- self::log_out();
- foreach ($database->get_col("SELECT id FROM images") as $image_id) {
- send_event(new ImageDeletionEvent(Image::by_id((int)$image_id), true));
- }
-
- $_tracer->end(); # setUp
- $_tracer->begin("test");
- }
-
- public function tearDown(): void
- {
- global $_tracer;
- $_tracer->end(); # test
- $_tracer->end(); # $this->getName()
- $_tracer->clear();
- $_tracer->flush("tests/trace.json");
- }
-
- public static function tearDownAfterClass(): void
- {
- parent::tearDownAfterClass();
- global $_tracer;
- $_tracer->end(); # get_called_class()
- }
-
- protected static function create_user(string $name): void
- {
- if (is_null(User::by_name($name))) {
- $userPage = new UserPage();
- $userPage->onUserCreation(new UserCreationEvent($name, $name, $name, "", false));
- assert(!is_null(User::by_name($name)), "Creation of user $name failed");
- }
- }
-
- private static function check_args(?array $args): array
- {
- if (!$args) {
- return [];
- }
- foreach ($args as $k => $v) {
- if (is_array($v)) {
- $args[$k] = $v;
- } else {
- $args[$k] = (string)$v;
- }
- }
- return $args;
- }
-
- protected static function request($page_name, $get_args = null, $post_args = null): Page
- {
- // use a fresh page
- global $page;
- $get_args = self::check_args($get_args);
- $post_args = self::check_args($post_args);
-
- if (str_contains($page_name, "?")) {
- throw new \RuntimeException("Query string included in page name");
- }
- $_SERVER['REQUEST_URI'] = make_link($page_name, http_build_query($get_args));
- $_GET = $get_args;
- $_POST = $post_args;
- $page = new Page();
- send_event(new PageRequestEvent($page_name));
- if ($page->mode == PageMode::REDIRECT) {
- $page->code = 302;
- }
- return $page;
- }
-
- protected static function get_page($page_name, $args = null): Page
- {
- return self::request($page_name, $args, null);
- }
-
- protected static function post_page($page_name, $args = null): Page
- {
- return self::request($page_name, null, $args);
- }
-
- // page things
- protected function assert_title(string $title): void
- {
- global $page;
- $this->assertStringContainsString($title, $page->title);
- }
-
- protected function assert_title_matches($title): void
- {
- global $page;
- $this->assertStringMatchesFormat($title, $page->title);
- }
-
- protected function assert_no_title(string $title): void
- {
- global $page;
- $this->assertStringNotContainsString($title, $page->title);
- }
-
- protected function assert_response(int $code): void
- {
- global $page;
- $this->assertEquals($code, $page->code);
- }
-
- protected function page_to_text(string $section = null): string
- {
- global $page;
- if ($page->mode == PageMode::PAGE) {
- $text = $page->title . "\n";
- foreach ($page->blocks as $block) {
- if (is_null($section) || $section == $block->section) {
- $text .= $block->header . "\n";
- $text .= $block->body . "\n\n";
- }
- }
- return $text;
- } elseif ($page->mode == PageMode::DATA) {
- return $page->data;
- } else {
- $this->fail("Page mode is not PAGE or DATA");
- }
- }
-
- protected function assert_text(string $text, string $section = null): void
- {
- $this->assertStringContainsString($text, $this->page_to_text($section));
- }
-
- protected function assert_no_text(string $text, string $section = null): void
- {
- $this->assertStringNotContainsString($text, $this->page_to_text($section));
- }
-
- protected function assert_content(string $content): void
- {
- global $page;
- $this->assertStringContainsString($content, $page->data);
- }
-
- protected function assert_no_content(string $content): void
- {
- global $page;
- $this->assertStringNotContainsString($content, $page->data);
- }
-
- protected function assert_search_results($tags, $results): void
- {
- $images = Image::find_images(0, null, $tags);
- $ids = [];
- foreach ($images as $image) {
- $ids[] = $image->id;
- }
- $this->assertEquals($results, $ids);
- }
-
- // user things
- protected static function log_in_as_admin(): void
- {
- send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
- }
-
- protected static function log_in_as_user(): void
- {
- send_event(new UserLoginEvent(User::by_name(self::$user_name)));
- }
-
- protected static function log_out(): void
- {
- global $config;
- send_event(new UserLoginEvent(User::by_id($config->get_int("anon_id", 0))));
- }
-
- // post things
- protected function post_image(string $filename, string $tags): int
- {
- $dae = send_event(new DataUploadEvent($filename, [
- "filename" => $filename,
- "tags" => Tag::explode($tags),
- "source" => null,
- ]));
- // if($dae->image_id == -1) throw new \Exception("Upload failed :(");
- return $dae->image_id;
- }
-
- protected function delete_image(int $image_id): void
- {
- $img = Image::by_id($image_id);
- if ($img) {
- send_event(new ImageDeletionEvent($img, true));
- }
- }
-}
diff --git a/tests/docker-init.sh b/tests/docker-init.sh
deleted file mode 100644
index e83854435..000000000
--- a/tests/docker-init.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-groupadd -g $GID shimmie || true
-useradd -ms /bin/bash -u $UID -g $GID shimmie
-mkdir -p /app/data
-chown $UID:$GID /app/data
-export PHP_CLI_SERVER_WORKERS=8
-exec gosu shimmie:shimmie \
- /usr/bin/php \
- -d upload_max_filesize=$UPLOAD_MAX_FILESIZE \
- -d post_max_size=$UPLOAD_MAX_FILESIZE \
- -S 0.0.0.0:8000 \
- tests/router.php 2>&1 | grep --line-buffered -vE " (Accepted|Closing)"
diff --git a/tests/phpstan.neon b/tests/phpstan.neon
index 2a379a7f0..9faf61e30 100644
--- a/tests/phpstan.neon
+++ b/tests/phpstan.neon
@@ -4,6 +4,6 @@ parameters:
- ../core
- ../ext
- ../tests
- - ../themes/default
+ - ../themes
ignoreErrors:
- '#Access to an undefined property Shimmie2\\Image::\$#'
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index 2ded9aa85..de47f9ef5 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -1,18 +1,25 @@
-
-
-
- ../core
- ../ext
- ../themes/default
-
-
+
+
- ../core/
+ ../core/
../ext/
+
diff --git a/tests/router.php b/tests/router.php
deleted file mode 100644
index 63a522b43..000000000
--- a/tests/router.php
+++ /dev/null
@@ -1,36 +0,0 @@
-get_string(SetupConfig::TITLE);
} else {
$search_string = Tag::implode($this->search_terms);
- $query = url_escape(Tag::caret($search_string));
+ $query = url_escape($search_string);
$page_title = html_escape($search_string);
}
@@ -42,7 +42,7 @@ public function display_page(Page $page, array $images)
}
/**
- * #param string[] $search_terms
+ * @param string[] $search_terms
*/
protected function build_navigation(int $page_number, int $total_pages, array $search_terms): string
{
diff --git a/themes/danbooru/page.class.php b/themes/danbooru/page.class.php
index 3eee4ac05..6ff8dba7f 100644
--- a/themes/danbooru/page.class.php
+++ b/themes/danbooru/page.class.php
@@ -50,13 +50,6 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
class Page extends BasePage
{
- public bool $left_enabled = true;
-
- public function disable_left()
- {
- $this->left_enabled = false;
- }
-
public function render()
{
global $config;
@@ -135,7 +128,7 @@ public function render()
print <<
-
+
$head_html
diff --git a/themes/danbooru/style.css b/themes/danbooru/style.css
index 598c05f91..b912670b4 100644
--- a/themes/danbooru/style.css
+++ b/themes/danbooru/style.css
@@ -219,21 +219,6 @@ margin:16px;
padding:8px;
width:350px;
}
-.helpable {
-border-bottom:1px dashed gray;
-}
-.ok {
--moz-background-clip:border;
--moz-background-inline-policy:continuous;
--moz-background-origin:padding;
-background:#AAFFAA none repeat scroll 0 0;
-}
-.bad {
--moz-background-clip:border;
--moz-background-inline-policy:continuous;
--moz-background-origin:padding;
-background:#FFAAAA none repeat scroll 0 0;
-}
.comment .username {
font-size:1.5em;
font-weight:bold;
diff --git a/themes/danbooru/view.theme.php b/themes/danbooru/view.theme.php
index 28facb934..121a93dc0 100644
--- a/themes/danbooru/view.theme.php
+++ b/themes/danbooru/view.theme.php
@@ -4,7 +4,7 @@
namespace Shimmie2;
-class CustomViewImageTheme extends ViewImageTheme
+class CustomViewPostTheme extends ViewPostTheme
{
public function display_page(Image $image, $editor_parts)
{
diff --git a/themes/danbooru2/index.theme.php b/themes/danbooru2/index.theme.php
index 51bf44730..e1fa8aa47 100644
--- a/themes/danbooru2/index.theme.php
+++ b/themes/danbooru2/index.theme.php
@@ -7,7 +7,7 @@
class CustomIndexTheme extends IndexTheme
{
/**
- * #param Image[] $images
+ * @param Image[] $images
*/
public function display_page(Page $page, array $images)
{
@@ -26,7 +26,7 @@ public function display_page(Page $page, array $images)
}
/**
- * #param string[] $search_terms
+ * @param string[] $search_terms
*/
protected function build_navigation(int $page_number, int $total_pages, array $search_terms): string
{
@@ -42,7 +42,7 @@ protected function build_navigation(int $page_number, int $total_pages, array $s
}
/**
- * #param Image[] $images
+ * @param Image[] $images
*/
protected function build_table(array $images, ?string $query): string
{
diff --git a/themes/danbooru2/page.class.php b/themes/danbooru2/page.class.php
index d4575d37f..d34354a32 100644
--- a/themes/danbooru2/page.class.php
+++ b/themes/danbooru2/page.class.php
@@ -51,12 +51,6 @@
class Page extends BasePage
{
- public bool $left_enabled = true;
- public function disable_left()
- {
- $this->left_enabled = false;
- }
-
public function render()
{
global $config;
@@ -135,7 +129,7 @@ public function render()
print <<
-
+
$head_html
diff --git a/themes/danbooru2/style.css b/themes/danbooru2/style.css
index ba42886f1..70d23acd2 100644
--- a/themes/danbooru2/style.css
+++ b/themes/danbooru2/style.css
@@ -269,21 +269,6 @@ width:100%;
margin-top:0.4rem;
padding:0.2rem 0.6rem;
}
-.helpable {
-border-bottom:1px dashed gray;
-}
-.ok {
-background:#AAFFAA none repeat scroll 0 0;
--moz-background-clip:border;
--moz-background-inline-policy:continuous;
--moz-background-origin:padding;
-}
-.bad {
-background:#FFAAAA none repeat scroll 0 0;
--moz-background-clip:border;
--moz-background-inline-policy:continuous;
--moz-background-origin:padding;
-}
.comment .username {
font-weight:bold;
font-size:1.5em;
diff --git a/themes/danbooru2/view.theme.php b/themes/danbooru2/view.theme.php
index 99e3c8607..febb71f42 100644
--- a/themes/danbooru2/view.theme.php
+++ b/themes/danbooru2/view.theme.php
@@ -4,7 +4,7 @@
namespace Shimmie2;
-class CustomViewImageTheme extends ViewImageTheme
+class CustomViewPostTheme extends ViewPostTheme
{
public function display_page(Image $image, $editor_parts)
{
diff --git a/themes/default/style.css b/themes/default/style.css
index 3ff7a787c..4fad4fff5 100644
--- a/themes/default/style.css
+++ b/themes/default/style.css
@@ -45,12 +45,14 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* things common to all pages *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+:root {
+ font-size: 14px;
+ font-family: sans-serif;
+}
BODY {
background: var(--page);
color: var(--text);
- font-family: sans-serif;
- font-size: 14px;
margin: 0;
}
H1 {
@@ -97,7 +99,7 @@ TABLE.zebra TR:nth-child(even) {background: var(--zebra-even);}
FOOTER {
clear: both;
- font-size: 0.7em;
+ font-size: 0.7rem;
text-align: center;
background: var(--title);
border: 1px solid var(--title-border);
@@ -129,7 +131,7 @@ NAV {
margin-left: 16px;
}
NAV .blockbody {
- font-size: 0.85em;
+ font-size: 0.85rem;
text-align: center;
overflow: hidden;
}
diff --git a/themes/futaba/page.class.php b/themes/futaba/page.class.php
index 83be4b913..60652ba29 100644
--- a/themes/futaba/page.class.php
+++ b/themes/futaba/page.class.php
@@ -6,12 +6,6 @@
class Page extends BasePage
{
- public bool $left_enabled = true;
- public function disable_left()
- {
- $this->left_enabled = false;
- }
-
public function render()
{
$left_block_html = "";
@@ -55,7 +49,7 @@ public function render()
print <<
-
+
$head_html
diff --git a/themes/futaba/style.css b/themes/futaba/style.css
index e0cf705d6..3d07afa8c 100644
--- a/themes/futaba/style.css
+++ b/themes/futaba/style.css
@@ -2,10 +2,12 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* things common to all pages *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
-
-BODY {
+:root {
font-family: arial,helvetica,sans-serif;
font-size: 10pt;
+}
+
+BODY {
background: #FFFFEE url(fade.png) top center repeat-x;
color: #800000;
padding-left: 5px;
@@ -24,7 +26,7 @@ H1 {
FOOTER {
clear: both;
padding-top: 8px;
- font-size: 0.7em;
+ font-size: 0.7rem;
text-align: center;
}
@@ -43,7 +45,7 @@ NAV {
margin-left: 16px;
}
NAV .blockbody {
- font-size: 0.85em;
+ font-size: 0.85rem;
text-align: center;
overflow: hidden;
}
@@ -113,7 +115,7 @@ TABLE.tag_list>TBODY>TR>TD:after {
#short-wiki-description > .blockbody {
padding-bottom: 15px;
- font-size: 1.1em;
+ font-size: 1.1rem;
}
#short-wiki-description h2 {
margin: 0 0 0.4em;
diff --git a/themes/futaba/view.theme.php b/themes/futaba/view.theme.php
index eb40ad1ec..7f4a9f19e 100644
--- a/themes/futaba/view.theme.php
+++ b/themes/futaba/view.theme.php
@@ -4,7 +4,7 @@
namespace Shimmie2;
-class CustomViewImageTheme extends ViewImageTheme
+class CustomViewPostTheme extends ViewPostTheme
{
public function display_page(Image $image, $editor_parts)
{
diff --git a/themes/lite/comment.theme.php b/themes/lite/comment.theme.php
index 20717a981..3be430a8d 100644
--- a/themes/lite/comment.theme.php
+++ b/themes/lite/comment.theme.php
@@ -8,11 +8,13 @@ class CustomCommentListTheme extends CommentListTheme
{
protected function comment_to_html(Comment $comment, bool $trim = false): string
{
- return $this->rr(parent::comment_to_html($comment, $trim));
+ $html = parent::comment_to_html($comment, $trim);
+ return "$html
";
}
protected function build_postbox(int $image_id): string
{
- return $this->rr(parent::build_postbox($image_id));
+ $html = parent::build_postbox($image_id);
+ return "$html
";
}
}
diff --git a/themes/lite/page.class.php b/themes/lite/page.class.php
index d6d606344..242ed9830 100644
--- a/themes/lite/page.class.php
+++ b/themes/lite/page.class.php
@@ -17,13 +17,6 @@
class Page extends BasePage
{
- public bool $left_enabled = true;
-
- public function disable_left()
- {
- $this->left_enabled = false;
- }
-
public function render()
{
global $config;
@@ -94,7 +87,7 @@ public function render()
print <<
-
+
$head_html
diff --git a/themes/lite/setup.theme.php b/themes/lite/setup.theme.php
index 34f185447..b1e90085b 100644
--- a/themes/lite/setup.theme.php
+++ b/themes/lite/setup.theme.php
@@ -4,43 +4,11 @@
namespace Shimmie2;
-/**
- * Class CustomSetupTheme
- *
- * A customised version of the Setup theme.
- *
- */
class CustomSetupTheme extends SetupTheme
{
protected function sb_to_html(SetupBlock $block): string
{
- $h = $block->header;
- $b = $block->body;
- $i = preg_replace('/[^a-zA-Z0-9]/', '_', $h) . "-setup";
- $html = "
-
-
- ";
-
- return $this->rr($html);
+ $html = parent::sb_to_html($block);
+ return "$html
";
}
}
diff --git a/themes/lite/style.css b/themes/lite/style.css
index 0d8fced9f..6ebe6fd4d 100644
--- a/themes/lite/style.css
+++ b/themes/lite/style.css
@@ -3,10 +3,12 @@
* http://qwebirc.org/
*/
-BODY {
- background: #F0F7FF;
+:root {
font-family: sans-serif;
font-size: 14px;
+}
+BODY {
+ background: #F0F7FF;
margin: 0;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
@@ -126,16 +128,7 @@ TD {
}
CODE {
background: #DEDEDE;
- font-size: 0.8em;
-}
-#subtitle {
- width: 256px;
- font-size: 0.75em;
- margin: -16px auto auto;
- text-align: center;
- border: 1px solid black;
- border-top: none;
- background: #DDD;
+ font-size: 0.8rem;
}
TABLE.zebra {border-spacing: 0; border: 2px solid #C3D2E0;}
@@ -164,7 +157,7 @@ INPUT:hover, button:hover, TEXTAREA:hover {
FOOTER {
clear: both;
padding: 8px;
- font-size: 0.7em;
+ font-size: 0.7rem;
text-align: center;
border-top: 1px solid #C3D2E0;
background: #E3EFFA;
@@ -194,7 +187,7 @@ NAV {
margin-left: 16px;
}
NAV .blockbody {
- font-size: 0.85em;
+ font-size: 0.85rem;
text-align: center;
}
NAV TABLE {
@@ -320,19 +313,9 @@ ARTICLE TABLE {
}
.setupblock TEXTAREA {
width: 300px;
- font-size: 0.75em;
-}
-
-.helpable {
- border-bottom: 1px dashed gray;
+ font-size: 0.75rem;
}
-.ok {
- background: #AFA;
-}
-.bad {
- background: #FAA;
-}
NAV .thumbblock {
float: none;
diff --git a/themes/lite/themelet.class.php b/themes/lite/themelet.class.php
index 39c61b720..fd293a38e 100644
--- a/themes/lite/themelet.class.php
+++ b/themes/lite/themelet.class.php
@@ -8,23 +8,8 @@
use function MicroHTML\{A,DIV,SPAN,joinHTML};
-/**
- * Class Themelet
- */
class Themelet extends BaseThemelet
{
- /**
- * Put something in a rounded rectangle box; specific to the default theme.
- */
- public function rr(string $html): string
- {
- return "
-
- $html
-
- ";
- }
-
public function display_paginator(Page $page, string $base, ?string $query, int $page_number, int $total_pages, bool $show_random = false)
{
if ($total_pages == 0) {
diff --git a/themes/lite/user_config.theme.php b/themes/lite/user_config.theme.php
index b0c9f1fe8..46d748184 100644
--- a/themes/lite/user_config.theme.php
+++ b/themes/lite/user_config.theme.php
@@ -4,43 +4,11 @@
namespace Shimmie2;
-/**
- * Class CustomSetupTheme
- *
- * A customised version of the Setup theme.
- *
- */
class CustomUserConfigTheme extends UserConfigTheme
{
protected function sb_to_html(SetupBlock $block): string
{
- $h = $block->header;
- $b = $block->body;
- $i = preg_replace('/[^a-zA-Z0-9]/', '_', $h) . "-setup";
- $html = "
-
-
- ";
-
- return $this->rr($html);
+ $html = parent::sb_to_html($block);
+ return "$html
";
}
}
diff --git a/themes/lite/view.theme.php b/themes/lite/view.theme.php
index 02d55c963..7b0f018b1 100644
--- a/themes/lite/view.theme.php
+++ b/themes/lite/view.theme.php
@@ -4,7 +4,7 @@
namespace Shimmie2;
-class CustomViewImageTheme extends ViewImageTheme
+class CustomViewPostTheme extends ViewPostTheme
{
public function display_page(Image $image, $editor_parts)
{
diff --git a/themes/rule34v2/bg.png b/themes/rule34v2/bg.png
deleted file mode 100644
index 45e1b42c0..000000000
Binary files a/themes/rule34v2/bg.png and /dev/null differ
diff --git a/themes/rule34v2/header.inc b/themes/rule34v2/header.inc
index b2fde29c4..5c48a6852 100644
--- a/themes/rule34v2/header.inc
+++ b/themes/rule34v2/header.inc
@@ -8,7 +8,7 @@
@@ -68,26 +68,28 @@
-
diff --git a/themes/rule34v2/home.theme.php b/themes/rule34v2/home.theme.php
deleted file mode 100644
index 065c56f94..000000000
--- a/themes/rule34v2/home.theme.php
+++ /dev/null
@@ -1,95 +0,0 @@
-set_mode(PageMode::DATA);
- $page->add_auto_html_headers();
- $hh = $page->get_all_html_headers();
- $page->set_data(
- <<