Merge branch 'master' into mistress

This commit is contained in:
Shish 2021-01-21 21:51:52 +00:00 committed by GitHub
commit e043f01cfb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
93 changed files with 3369 additions and 729 deletions

View file

@ -5,6 +5,12 @@
"license" : "GPL-2.0-or-later", "license" : "GPL-2.0-or-later",
"minimum-stability" : "dev", "minimum-stability" : "dev",
"config": {
"platform": {
"php": "7.3.0"
}
},
"repositories" : [ "repositories" : [
{ {
"type": "composer", "type": "composer",
@ -47,9 +53,9 @@
}, },
"require-dev" : { "require-dev" : {
"phpunit/phpunit" : "^9.0" "phpunit/phpunit" : "^9.0",
"friendsofphp/php-cs-fixer" : "*"
}, },
"suggest": { "suggest": {
"ext-memcache": "memcache caching", "ext-memcache": "memcache caching",
"ext-memcached": "memcached caching", "ext-memcached": "memcached caching",

2480
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -72,7 +72,7 @@ class BaseThemelet
} }
} }
return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-post-id='$i_id'>". return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-height='$image->height' data-width='$image->width' data-mime='{$image->get_mime()}' data-post-id='$i_id'>".
"<img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' src='$h_thumb_link'>". "<img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' src='$h_thumb_link'>".
"</a>\n"; "</a>\n";
} }

View file

@ -483,6 +483,7 @@ class Image
WHERE image_id=:id WHERE image_id=:id
ORDER BY tag ORDER BY tag
", ["id"=>$this->id]); ", ["id"=>$this->id]);
sort($this->tag_array);
} }
return $this->tag_array; return $this->tag_array;
} }

View file

@ -7,6 +7,9 @@ abstract class Permissions
{ {
public const CHANGE_SETTING = "change_setting"; # modify web-level settings, eg the config table public const CHANGE_SETTING = "change_setting"; # modify web-level settings, eg the config table
public const OVERRIDE_CONFIG = "override_config"; # modify sys-level settings, eg shimmie.conf.php public const OVERRIDE_CONFIG = "override_config"; # modify sys-level settings, eg shimmie.conf.php
public const CHANGE_USER_SETTING = "change_user_setting"; # modify own user-level settings
public const CHANGE_OTHER_USER_SETTING = "change_other_user_setting"; # modify own user-level settings
public const BIG_SEARCH = "big_search"; # search for more than 3 tags at once (speed mode only) public const BIG_SEARCH = "big_search"; # search for more than 3 tags at once (speed mode only)
public const MANAGE_EXTENSION_LIST = "manage_extension_list"; public const MANAGE_EXTENSION_LIST = "manage_extension_list";
@ -100,6 +103,7 @@ abstract class Permissions
public const SET_PRIVATE_IMAGE = "set_private_image"; public const SET_PRIVATE_IMAGE = "set_private_image";
public const SET_OTHERS_PRIVATE_IMAGES = "set_others_private_images"; public const SET_OTHERS_PRIVATE_IMAGES = "set_others_private_images";
public const CRON_RUN = "cron_run";
public const BULK_IMPORT = "bulk_import"; public const BULK_IMPORT = "bulk_import";
public const BULK_EXPORT = "bulk_export"; public const BULK_EXPORT = "bulk_export";
public const BULK_DOWNLOAD = "bulk_download"; public const BULK_DOWNLOAD = "bulk_download";

View file

@ -100,6 +100,7 @@ new UserClass("user", "base", [
Permissions::READ_PM => true, Permissions::READ_PM => true,
Permissions::SET_PRIVATE_IMAGE => true, Permissions::SET_PRIVATE_IMAGE => true,
Permissions::BULK_DOWNLOAD => true, Permissions::BULK_DOWNLOAD => true,
Permissions::CHANGE_USER_SETTING => true
]); ]);
new UserClass("hellbanned", "user", [ new UserClass("hellbanned", "user", [
@ -108,6 +109,8 @@ new UserClass("hellbanned", "user", [
new UserClass("admin", "base", [ new UserClass("admin", "base", [
Permissions::CHANGE_SETTING => true, Permissions::CHANGE_SETTING => true,
Permissions::CHANGE_USER_SETTING => true,
Permissions::CHANGE_OTHER_USER_SETTING => true,
Permissions::OVERRIDE_CONFIG => true, Permissions::OVERRIDE_CONFIG => true,
Permissions::BIG_SEARCH => true, Permissions::BIG_SEARCH => true,
@ -200,6 +203,8 @@ new UserClass("admin", "base", [
Permissions::APPROVE_IMAGE => true, Permissions::APPROVE_IMAGE => true,
Permissions::APPROVE_COMMENT => true, Permissions::APPROVE_COMMENT => true,
Permissions::CRON_RUN =>true,
Permissions::BULK_IMPORT =>true, Permissions::BULK_IMPORT =>true,
Permissions::BULK_EXPORT =>true, Permissions::BULK_EXPORT =>true,
Permissions::BULK_DOWNLOAD => true, Permissions::BULK_DOWNLOAD => true,

View file

@ -9,15 +9,7 @@ class AdminPageInfo extends ExtensionInfo
public $url = self::SHIMMIE_URL; public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR; public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2; public $license = self::LICENSE_GPLV2;
public $description = "Various things to make admins' lives easier"; public $description = "Provides a base for various small admin functions";
public $documentation = public $core = true;
"Various moderate-level tools for admins; for advanced, obscure, and possibly dangerous tools see the shimmie2-utils script set public $visibility = self::VISIBLE_HIDDEN;
<p>Lowercase all tags:
<br>Set all tags to lowercase for consistency
<p>Recount tag use:
<br>If the counts of posts per tag get messed up somehow, this will reset them, and remove any unused tags
<p>Database dump:
<br>Download the contents of the database in plain text format, useful for backups.
<p>Post dump:
<br>Download all the posts as a .zip file (Requires ZipArchive)";
} }

View file

@ -132,7 +132,6 @@ class AdminPage extends Extension
public function onAdminBuilding(AdminBuildingEvent $event) public function onAdminBuilding(AdminBuildingEvent $event)
{ {
$this->theme->display_page(); $this->theme->display_page();
$this->theme->display_form();
} }
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event) public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
@ -152,46 +151,4 @@ class AdminPage extends Extension
$event->add_link("Board Admin", make_link("admin")); $event->add_link("Board Admin", make_link("admin"));
} }
} }
public function onAdminAction(AdminActionEvent $event)
{
$action = $event->action;
if (method_exists($this, $action)) {
$event->redirect = $this->$action();
}
}
private function set_tag_case()
{
global $database;
$database->execute(
"UPDATE tags SET tag=:tag1 WHERE LOWER(tag) = LOWER(:tag2)",
["tag1" => $_POST['tag'], "tag2" => $_POST['tag']]
);
log_info("admin", "Fixed the case of {$_POST['tag']}", "Fixed case");
return true;
}
private function lowercase_all_tags()
{
global $database;
$database->execute("UPDATE tags SET tag=lower(tag)");
log_warning("admin", "Set all tags to lowercase", "Set all tags to lowercase");
return true;
}
private function recount_tag_use()
{
global $database;
$database->execute("
UPDATE tags
SET count = COALESCE(
(SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id),
0
)
");
$database->execute("DELETE FROM tags WHERE count=0");
log_warning("admin", "Re-counted tags", "Re-counted tags");
return true;
}
} }

View file

@ -19,59 +19,6 @@ class AdminPageTest extends ShimmiePHPUnitTestCase
$this->assertEquals("Admin Tools", $page->title); $this->assertEquals("Admin Tools", $page->title);
} }
public function testLowercaseAndSetCase()
{
// Create a problem
$ts = time(); // we need a tag that hasn't been used before
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "TeStCase$ts");
// Validate problem
$page = $this->get_page("post/view/$image_id_1");
$this->assertEquals("Post $image_id_1: TeStCase$ts", $page->title);
// Fix
send_event(new AdminActionEvent('lowercase_all_tags'));
// Validate fix
$this->get_page("post/view/$image_id_1");
$this->assert_title("Post $image_id_1: testcase$ts");
// Change
$_POST["tag"] = "TestCase$ts";
send_event(new AdminActionEvent('set_tag_case'));
// Validate change
$this->get_page("post/view/$image_id_1");
$this->assert_title("Post $image_id_1: TestCase$ts");
}
# FIXME: make sure the admin tools actually work
public function testRecount()
{
global $database;
// Create a problem
$ts = time(); // we need a tag that hasn't been used before
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
$database->execute(
"INSERT INTO tags(tag, count) VALUES(:tag, :count)",
["tag"=>"tes$ts", "count"=>42]
);
// Fix
send_event(new AdminActionEvent('recount_tag_use'));
// Validate fix
$this->assertEquals(
0,
$database->get_one(
"SELECT count FROM tags WHERE tag = :tag",
["tag"=>"tes$ts"]
)
);
}
public function testCommands() public function testCommands()
{ {
send_event(new UserLoginEvent(User::by_name(self::$admin_name))); send_event(new UserLoginEvent(User::by_name(self::$admin_name)));

View file

@ -14,41 +14,4 @@ class AdminPageTheme extends Themelet
$page->set_heading("Admin Tools"); $page->set_heading("Admin Tools");
$page->add_block(new NavBlock()); $page->add_block(new NavBlock());
} }
protected function button(string $name, string $action, bool $protected=false): string
{
$c_protected = $protected ? " protected" : "";
$html = make_form(make_link("admin/$action"), "POST", false, "admin$c_protected");
if ($protected) {
$html .= "<input type='submit' id='$action' value='$name' disabled='disabled'>";
$html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>";
} else {
$html .= "<input type='submit' id='$action' value='$name'>";
}
$html .= "</form>\n";
return $html;
}
/*
* Show a form which links to admin_utils with POST[action] set to one of:
* 'lowercase all tags'
* 'recount tag use'
* etc
*/
public function display_form()
{
global $page;
$html = "";
$html .= $this->button("All tags to lowercase", "lowercase_all_tags", true);
$html .= $this->button("Recount tag use", "recount_tag_use", false);
$page->add_block(new Block("Misc Admin Tools", $html));
$html = (string)SHM_SIMPLE_FORM(
"admin/set_tag_case",
INPUT(["type"=>'text', "name"=>'tag', "placeholder"=>'Enter tag with correct case', "class"=>'autocomplete_tags', "autocomplete"=>'off']),
SHM_SUBMIT('Set Tag Case'),
);
$page->add_block(new Block("Set Tag Case", $html));
}
} }

View file

@ -41,9 +41,8 @@ class ApprovalTheme extends Themelet
public function display_admin_block(SetupBuildingEvent $event) public function display_admin_block(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Approval"); $sb = $event->panel->create_new_block("Approval");
$sb->add_bool_option(ApprovalConfig::IMAGES, "Posts: "); $sb->add_bool_option(ApprovalConfig::IMAGES, "Posts: ");
$event->panel->add_block($sb);
} }
public function display_admin_form() public function display_admin_form()

View file

@ -55,7 +55,7 @@ xanax
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Banned Phrases"); $sb = $event->panel->create_new_block("Banned Phrases");
$sb->add_label("One per line, lines that start with slashes are treated as regex<br/>"); $sb->add_label("One per line, lines that start with slashes are treated as regex<br/>");
$sb->add_longtext_option("banned_words"); $sb->add_longtext_option("banned_words");
$failed = []; $failed = [];
@ -69,7 +69,6 @@ xanax
if ($failed) { if ($failed) {
$sb->add_label("Failed regexes: ".join(", ", $failed)); $sb->add_label("Failed regexes: ".join(", ", $failed));
} }
$event->panel->add_block($sb);
} }
/** /**

View file

@ -12,21 +12,44 @@ class BBCodeInfo extends ExtensionInfo
public $core = true; public $core = true;
public $description = "Turns BBCode into HTML"; public $description = "Turns BBCode into HTML";
public $documentation = public $documentation =
" Supported tags: " Basic formatting tags:
<ul> <ul>
<li>[img]url[/img]
<li>[url]<a href=\"{self::SHIMMIE_URL}\">https://code.shishnet.org/</a>[/url]
<li>[email]<a href=\"mailto:{self::SHISH_EMAIL}\">webmaster@shishnet.org</a>[/email]
<li>[b]<b>bold</b>[/b] <li>[b]<b>bold</b>[/b]
<li>[i]<i>italic</i>[/i] <li>[i]<i>italic</i>[/i]
<li>[u]<u>underline</u>[/u] <li>[u]<u>underline</u>[/u]
<li>[s]<s>strikethrough</s>[/s] <li>[s]<s>strikethrough</s>[/s]
<li>[sup]<sup>superscript</sup>[/sup] <li>[sup]<sup>superscript</sup>[/sup]
<li>[sub]<sub>subscript</sub>[/sub] <li>[sub]<sub>subscript</sub>[/sub]
<li>[h1]Heading 1[/h1]
<li>[h2]Heading 2[/h2]
<li>[h3]Heading 3[/h3]
<li>[h4]Heading 4[/h4]
<li>[align=left|center|right]Aligned Text[/align]
</ul>
<br>
Link tags:
<ul>
<li>[img]url[/img]
<li>[url]<a href=\"{self::SHIMMIE_URL}\">https://code.shishnet.org/</a>[/url]
<li>[url=<a href=\"{self::SHIMMIE_URL}\">https://code.shishnet.org/</a>]some text[/url]
<li>[url]site://ext_doc/bbcode[/url]
<li>[url=site://ext_doc/bbcode]Link to BBCode docs[/url]
<li>[email]<a href=\"mailto:{self::SHISH_EMAIL}\">webmaster@shishnet.org</a>[/email]
<li>[[wiki article]] <li>[[wiki article]]
<li>[[wiki article|with some text]] <li>[[wiki article|with some text]]
<li>[quote]text[/quote]
<li>[quote=Username]text[/quote]
<li>&gt;&gt;123 (link to post #123) <li>&gt;&gt;123 (link to post #123)
<li>[anchor=target]Scroll to #bb-target[/anchor]
</ul>
<br>
More format Tags:
<ul>
<li>[list]Unordered list[/list]
<li>[ul]Unordered list[/ul]
<li>[ol]Ordered list[/ol]
<li>[li]List Item[/li]
<li>[code]<pre>print(\"Hello World!\");</pre>[/code]
<li>[spoiler]<span style=\"background-color:#000; color:#000;\">Voldemort is bad</span>[/spoiler]
<li>[quote]<blockquote><small>To be or not to be...</small></blockquote>[/quote]
<li>[quote=Shakespeare]<blockquote><em>Shakespeare said:</em><br><small>... That is the question</small></blockquote>[/quote]
</ul>"; </ul>";
} }

View file

@ -40,11 +40,10 @@ class Blotter extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Blotter"); $sb = $event->panel->create_new_block("Blotter");
$sb->add_int_option("blotter_recent", "<br />Number of recent entries to display: "); $sb->add_int_option("blotter_recent", "<br />Number of recent entries to display: ");
$sb->add_text_option("blotter_color", "<br />Color of important updates: (ABCDEF format) "); $sb->add_text_option("blotter_color", "<br />Color of important updates: (ABCDEF format) ");
$sb->add_choice_option("blotter_position", ["Top of page" => "subheading", "In navigation bar" => "left"], "<br>Position: "); $sb->add_choice_option("blotter_position", ["Top of page" => "subheading", "In navigation bar" => "left"], "<br>Position: ");
$event->panel->add_block($sb);
} }
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event) public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)

View file

@ -78,8 +78,7 @@ class BrowserSearch extends Extension
$sort_by['Tag Count'] = 't'; $sort_by['Tag Count'] = 't';
$sort_by['Disabled'] = 'n'; $sort_by['Disabled'] = 'n';
$sb = new SetupBlock("Browser Search"); $sb = $event->panel->create_new_block("Browser Search");
$sb->add_choice_option("search_suggestions_results_order", $sort_by, "Sort the suggestions by:"); $sb->add_choice_option("search_suggestions_results_order", $sort_by, "Sort the suggestions by:");
$event->panel->add_block($sb);
} }
} }

View file

@ -127,8 +127,8 @@ class BulkActions extends Extension
switch ($event->action) { switch ($event->action) {
case "bulk_delete": case "bulk_delete":
if ($user->can(Permissions::DELETE_IMAGE)) { if ($user->can(Permissions::DELETE_IMAGE)) {
$i = $this->delete_items($event->items); $i = $this->delete_posts($event->items);
$page->flash("Deleted $i items"); $page->flash("Deleted $i[0] items, totaling ".human_filesize($i[1]));
} }
break; break;
case "bulk_tag": case "bulk_tag":
@ -227,25 +227,27 @@ class BulkActions extends Extension
return $a["position"] - $b["position"]; return $a["position"] - $b["position"];
} }
private function delete_items(iterable $items): int private function delete_posts(iterable $posts): array
{ {
global $page; global $page;
$total = 0; $total = 0;
foreach ($items as $image) { $size = 0;
foreach ($posts as $post) {
try { try {
if (class_exists("ImageBan") && isset($_POST['bulk_ban_reason'])) { if (class_exists("ImageBan") && isset($_POST['bulk_ban_reason'])) {
$reason = $_POST['bulk_ban_reason']; $reason = $_POST['bulk_ban_reason'];
if ($reason) { if ($reason) {
send_event(new AddImageHashBanEvent($image->hash, $reason)); send_event(new AddImageHashBanEvent($post->hash, $reason));
} }
} }
send_event(new ImageDeletionEvent($image)); send_event(new ImageDeletionEvent($post));
$total++; $total++;
$size += $post->filesize;
} catch (Exception $e) { } catch (Exception $e) {
$page->flash("Error while removing {$image->id}: " . $e->getMessage()); $page->flash("Error while removing {$post->id}: " . $e->getMessage());
} }
} }
return $total; return [$total, $size];
} }
private function tag_items(iterable $items, string $tags, bool $replace): int private function tag_items(iterable $items, string $tags, bool $replace): int

View file

@ -30,13 +30,11 @@ class BulkDownload extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Bulk Download"); $sb = $event->panel->create_new_block("Bulk Download");
$sb->start_table(); $sb->start_table();
$sb->add_shorthand_int_option(BulkDownloadConfig::SIZE_LIMIT, "Size Limit", true); $sb->add_shorthand_int_option(BulkDownloadConfig::SIZE_LIMIT, "Size Limit", true);
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
public function onBulkAction(BulkActionEvent $event) public function onBulkAction(BulkActionEvent $event)

View file

@ -379,7 +379,7 @@ class CommentList extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Comment Options"); $sb = $event->panel->create_new_block("Comment Options");
$sb->add_bool_option("comment_captcha", "Require CAPTCHA for anonymous comments: "); $sb->add_bool_option("comment_captcha", "Require CAPTCHA for anonymous comments: ");
$sb->add_label("<br>Limit to "); $sb->add_label("<br>Limit to ");
$sb->add_int_option("comment_limit"); $sb->add_int_option("comment_limit");
@ -394,7 +394,6 @@ class CommentList extends Extension
$sb->add_label(" comments per image on the list"); $sb->add_label(" comments per image on the list");
$sb->add_label("<br>Make samefags public "); $sb->add_label("<br>Make samefags public ");
$sb->add_bool_option("comment_samefags_public"); $sb->add_bool_option("comment_samefags_public");
$event->panel->add_block($sb);
} }
public function onSearchTermParse(SearchTermParseEvent $event) public function onSearchTermParse(SearchTermParseEvent $event)

View file

@ -5,66 +5,8 @@ abstract class CronUploaderConfig
{ {
public const DEFAULT_PATH = "cron_uploader"; public const DEFAULT_PATH = "cron_uploader";
public const KEY = "cron_uploader_key";
public const DIR = "cron_uploader_dir"; public const DIR = "cron_uploader_dir";
public const USER = "cron_uploader_user";
public const STOP_ON_ERROR = "cron_uploader_stop_on_error"; public const STOP_ON_ERROR = "cron_uploader_stop_on_error";
public const INCLUDE_ALL_LOGS = "cron_uploader_include_all_logs"; public const INCLUDE_ALL_LOGS = "cron_uploader_include_all_logs";
public const LOG_LEVEL = "cron_uploader_log_level"; public const LOG_LEVEL = "cron_uploader_log_level";
public static function set_defaults(): void
{
global $config;
$config->set_default_string(self::DIR, data_path(self::DEFAULT_PATH));
$config->set_default_bool(self::INCLUDE_ALL_LOGS, false);
$config->set_default_bool(self::STOP_ON_ERROR, false);
$config->set_default_int(self::LOG_LEVEL, SCORE_LOG_INFO);
$upload_key = $config->get_string(self::KEY, "");
if (empty($upload_key)) {
$upload_key = generate_key();
$config->set_string(self::KEY, $upload_key);
}
}
public static function get_user(): int
{
global $config;
return $config->get_int(self::USER);
}
public static function set_user(int $value): void
{
global $config;
$config->set_int(self::USER, $value);
}
public static function get_key(): string
{
global $config;
return $config->get_string(self::KEY);
}
public static function set_key(string $value): void
{
global $config;
$config->set_string(self::KEY, $value);
}
public static function get_dir(): string
{
global $config;
$value = $config->get_string(self::DIR);
if (empty($value)) {
$value = data_path("cron_uploader");
self::set_dir($value);
}
return $value;
}
public static function set_dir(string $value): void
{
global $config;
$config->set_string(self::DIR, $value);
}
} }

View file

@ -17,12 +17,40 @@ class CronUploader extends Extension
private static $IMPORT_RUNNING = false; private static $IMPORT_RUNNING = false;
public function onInitExt(InitExtEvent $event) public function onInitUserConfig(InitUserConfigEvent $event)
{ {
// Set default values $event->user_config->set_default_string(
CronUploaderConfig::set_defaults(); CronUploaderConfig::DIR,
data_path(CronUploaderConfig::DEFAULT_PATH.DIRECTORY_SEPARATOR.$event->user->name)
);
$event->user_config->set_default_bool(CronUploaderConfig::INCLUDE_ALL_LOGS, false);
$event->user_config->set_default_bool(CronUploaderConfig::STOP_ON_ERROR, false);
$event->user_config->set_default_int(CronUploaderConfig::LOG_LEVEL, SCORE_LOG_INFO);
} }
public function onUserOptionsBuilding(UserOptionsBuildingEvent $event)
{
if ($event->user->can(Permissions::CRON_ADMIN)) {
$documentation_link = make_http(make_link("cron_upload"));
$sb = $event->panel->create_new_block("Cron Uploader");
$sb->start_table();
$sb->add_text_option(CronUploaderConfig::DIR, "Root dir", true);
$sb->add_bool_option(CronUploaderConfig::STOP_ON_ERROR, "Stop On Error", true);
$sb->add_choice_option(CronUploaderConfig::LOG_LEVEL, [
LOGGING_LEVEL_NAMES[SCORE_LOG_DEBUG] => SCORE_LOG_DEBUG,
LOGGING_LEVEL_NAMES[SCORE_LOG_INFO] => SCORE_LOG_INFO,
LOGGING_LEVEL_NAMES[SCORE_LOG_WARNING] => SCORE_LOG_WARNING,
LOGGING_LEVEL_NAMES[SCORE_LOG_ERROR] => SCORE_LOG_ERROR,
LOGGING_LEVEL_NAMES[SCORE_LOG_CRITICAL] => SCORE_LOG_CRITICAL,
], "Output Log Level: ", true);
$sb->add_bool_option(CronUploaderConfig::INCLUDE_ALL_LOGS, "Include All Logs", true);
$sb->end_table();
$sb->add_label("<a href='$documentation_link'>Read the documentation</a> for cron setup instructions.");
}
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event) public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
{ {
if ($event->parent=="system") { if ($event->parent=="system") {
@ -39,42 +67,14 @@ class CronUploader extends Extension
global $user; global $user;
if ($event->page_matches("cron_upload")) { if ($event->page_matches("cron_upload")) {
if ($event->count_args() == 1) { if ($event->count_args() == 1 && $event->get_arg(0) =="run") {
$this->process_upload($event->get_arg(0)); // Start upload $this->process_upload(); // Start upload
} elseif ($user->can(Permissions::CRON_ADMIN)) { } elseif ($user->can(Permissions::CRON_RUN)) {
$this->display_documentation(); $this->display_documentation();
} }
} }
} }
public function onSetupBuilding(SetupBuildingEvent $event)
{
global $database;
$documentation_link = make_http(make_link("cron_upload"));
$users = $database->get_pairs("SELECT name, id FROM users UNION ALL SELECT '', null order by name");
$sb = new SetupBlock("Cron Uploader");
$sb->start_table();
$sb->add_text_option(CronUploaderConfig::DIR, "Root dir", true);
$sb->add_text_option(CronUploaderConfig::KEY, "Key", true);
$sb->add_choice_option(CronUploaderConfig::USER, $users, "User", true);
$sb->add_bool_option(CronUploaderConfig::STOP_ON_ERROR, "Stop On Error", true);
$sb->add_choice_option(CronUploaderConfig::LOG_LEVEL, [
LOGGING_LEVEL_NAMES[SCORE_LOG_DEBUG] => SCORE_LOG_DEBUG,
LOGGING_LEVEL_NAMES[SCORE_LOG_INFO] => SCORE_LOG_INFO,
LOGGING_LEVEL_NAMES[SCORE_LOG_WARNING] => SCORE_LOG_WARNING,
LOGGING_LEVEL_NAMES[SCORE_LOG_ERROR] => SCORE_LOG_ERROR,
LOGGING_LEVEL_NAMES[SCORE_LOG_CRITICAL] => SCORE_LOG_CRITICAL,
], "Output Log Level: ", true);
$sb->add_bool_option(CronUploaderConfig::INCLUDE_ALL_LOGS, "Include All Logs", true);
$sb->end_table();
$sb->add_label("<a href='$documentation_link'>Read the documentation</a> for cron setup instructions.");
$event->panel->add_block($sb);
}
public function onAdminBuilding(AdminBuildingEvent $event) public function onAdminBuilding(AdminBuildingEvent $event)
{ {
$failed_dir = $this->get_failed_dir(); $failed_dir = $this->get_failed_dir();
@ -118,19 +118,20 @@ class CronUploader extends Extension
public function onLog(LogEvent $event) public function onLog(LogEvent $event)
{ {
global $config; global $user_config;
$all = $config->get_bool(CronUploaderConfig::INCLUDE_ALL_LOGS);
if (self::$IMPORT_RUNNING &&
$event->priority >= $config->get_int(CronUploaderConfig::LOG_LEVEL) &&
($event->section==self::NAME || $all)
) {
$output = "[" . date('Y-m-d H:i:s') . "] " . ($all ? '['. $event->section .'] ' :'') . "[" . LOGGING_LEVEL_NAMES[$event->priority] . "] " . $event->message ;
echo $output . "\r\n"; if (self::$IMPORT_RUNNING) {
flush_output(); $all = $user_config->get_bool(CronUploaderConfig::INCLUDE_ALL_LOGS);
if ($event->priority >= $user_config->get_int(CronUploaderConfig::LOG_LEVEL) &&
($event->section==self::NAME || $all)) {
$output = "[" . date('Y-m-d H:i:s') . "] " . ($all ? '[' . $event->section . '] ' : '') . "[" . LOGGING_LEVEL_NAMES[$event->priority] . "] " . $event->message;
$log_path = $this->get_log_file(); echo $output . "\r\n";
file_put_contents($log_path, $output); flush_output();
$log_path = $this->get_log_file();
file_put_contents($log_path, $output);
}
} }
} }
@ -193,8 +194,8 @@ class CronUploader extends Extension
private function clear_folder($folder) private function clear_folder($folder)
{ {
global $page; global $page, $user_config;
$path = join_path(CronUploaderConfig::get_dir(), $folder); $path = join_path($user_config->get_string(CronUploaderConfig::DIR), $folder);
deltree($path); deltree($path);
$page->flash("Cleared $path"); $page->flash("Cleared $path");
} }
@ -202,7 +203,11 @@ class CronUploader extends Extension
private function get_cron_url() private function get_cron_url()
{ {
return make_http(make_link("/cron_upload/" . CronUploaderConfig::get_key())); global $user_config;
$user_api_key = $user_config->get_string(UserConfig::API_KEY);
return make_http(make_link("/cron_upload/run", "api_key=".urlencode($user_api_key)));
} }
private function get_cron_cmd() private function get_cron_cmd()
@ -258,26 +263,34 @@ class CronUploader extends Extension
public function get_queue_dir() public function get_queue_dir()
{ {
$dir = CronUploaderConfig::get_dir(); global $user_config;
$dir = $user_config->get_string(CronUploaderConfig::DIR);
return join_path($dir, self::QUEUE_DIR); return join_path($dir, self::QUEUE_DIR);
} }
public function get_uploaded_dir() public function get_uploaded_dir()
{ {
$dir = CronUploaderConfig::get_dir(); global $user_config;
$dir = $user_config->get_string(CronUploaderConfig::DIR);
return join_path($dir, self::UPLOADED_DIR); return join_path($dir, self::UPLOADED_DIR);
} }
public function get_failed_dir() public function get_failed_dir()
{ {
$dir = CronUploaderConfig::get_dir(); global $user_config;
$dir = $user_config->get_string(CronUploaderConfig::DIR);
return join_path($dir, self::FAILED_DIR); return join_path($dir, self::FAILED_DIR);
} }
private function prep_root_dir(): string private function prep_root_dir(): string
{ {
global $user_config;
// Determine directory (none = default) // Determine directory (none = default)
$dir = CronUploaderConfig::get_dir(); $dir = $user_config->get_string(CronUploaderConfig::DIR);
// Make the directory if it doesn't exist yet // Make the directory if it doesn't exist yet
if (!is_dir($this->get_queue_dir())) { if (!is_dir($this->get_queue_dir())) {
@ -295,35 +308,36 @@ class CronUploader extends Extension
private function get_lock_file(): string private function get_lock_file(): string
{ {
$root_dir = CronUploaderConfig::get_dir(); global $user_config;
$root_dir = $user_config->get_string(CronUploaderConfig::DIR);
return join_path($root_dir, ".lock"); return join_path($root_dir, ".lock");
} }
/** /**
* Uploads the image & handles everything * Uploads the image & handles everything
*/ */
public function process_upload(string $key, ?int $upload_count = null): bool public function process_upload(): bool
{ {
global $database, $config, $_shm_load_start; global $database, $user, $user_config, $config, $_shm_load_start;
$max_time = intval(ini_get('max_execution_time'))*.8; $max_time = intval(ini_get('max_execution_time'))*.8;
$this->set_headers(); $this->set_headers();
if ($key!=CronUploaderConfig::get_key()) { if (!$config->get_bool(UserConfig::ENABLE_API_KEYS)) {
throw new SCoreException("Cron upload key incorrect"); throw new SCoreException("User API keys are note enabled. Please enable them for the cron upload functionality to work.");
}
$user_id = CronUploaderConfig::get_user();
if (empty($user_id)) {
throw new SCoreException("Cron upload user not set");
}
$my_user = User::by_id($user_id);
if ($my_user == null) {
throw new SCoreException("No user found for cron upload user $user_id");
} }
send_event(new UserLoginEvent($my_user)); if ($user->is_anonymous()) {
$this->log_message(SCORE_LOG_INFO, "Logged in as user {$my_user->name}"); throw new SCoreException("User not present. Please specify the api_key for the user to run cron upload as.");
}
$this->log_message(SCORE_LOG_INFO, "Logged in as user {$user->name}");
if (!$user->can(Permissions::CRON_RUN)) {
throw new SCoreException("User does not have permission to run cron upload");
}
$lockfile = fopen($this->get_lock_file(), "w"); $lockfile = fopen($this->get_lock_file(), "w");
if (!flock($lockfile, LOCK_EX | LOCK_NB)) { if (!flock($lockfile, LOCK_EX | LOCK_NB)) {
@ -335,7 +349,7 @@ class CronUploader extends Extension
//set_time_limit(0); //set_time_limit(0);
$output_subdir = date('Ymd-His', time()); $output_subdir = date('Ymd-His', time());
$image_queue = $this->generate_image_queue(CronUploaderConfig::get_dir()); $image_queue = $this->generate_image_queue($user_config->get_string(CronUploaderConfig::DIR));
// Randomize Images // Randomize Images
//shuffle($this->image_queue); //shuffle($this->image_queue);
@ -349,6 +363,9 @@ class CronUploader extends Extension
$execution_time = microtime(true) - $_shm_load_start; $execution_time = microtime(true) - $_shm_load_start;
if ($execution_time>$max_time) { if ($execution_time>$max_time) {
break; break;
} else {
$remaining = $max_time - $execution_time;
$this->log_message(SCORE_LOG_DEBUG, "Max run time remaining: $remaining");
} }
try { try {
$database->begin_transaction(); $database->begin_transaction();
@ -374,7 +391,7 @@ class CronUploader extends Extension
$failed++; $failed++;
$this->log_message(SCORE_LOG_ERROR, "(" . gettype($e) . ") " . $e->getMessage()); $this->log_message(SCORE_LOG_ERROR, "(" . gettype($e) . ") " . $e->getMessage());
$this->log_message(SCORE_LOG_ERROR, $e->getTraceAsString()); $this->log_message(SCORE_LOG_ERROR, $e->getTraceAsString());
if ($config->get_bool(CronUploaderConfig::STOP_ON_ERROR)) { if ($user_config->get_bool(CronUploaderConfig::STOP_ON_ERROR)) {
break; break;
} else { } else {
$this->move_uploaded($img[0], $img[1], $output_subdir, true); $this->move_uploaded($img[0], $img[1], $output_subdir, true);
@ -403,7 +420,9 @@ class CronUploader extends Extension
private function move_uploaded(string $path, string $filename, string $output_subdir, bool $corrupt = false) private function move_uploaded(string $path, string $filename, string $output_subdir, bool $corrupt = false)
{ {
$rootDir = CronUploaderConfig::get_dir(); global $user_config;
$rootDir = $user_config->get_string(CronUploaderConfig::DIR);
$rootLength = strlen($rootDir); $rootLength = strlen($rootDir);
if ($rootDir[$rootLength-1]=="/"||$rootDir[$rootLength-1]=="\\") { if ($rootDir[$rootLength-1]=="/"||$rootDir[$rootLength-1]=="\\") {
$rootLength--; $rootLength--;
@ -540,7 +559,11 @@ class CronUploader extends Extension
private function get_log_file(): string private function get_log_file(): string
{ {
return join_path(CronUploaderConfig::get_dir(), "uploads.log"); global $user_config;
$dir = $user_config->get_string(CronUploaderConfig::DIR);
return join_path($dir, "uploads.log");
} }
private function set_headers(): void private function set_headers(): void

View file

@ -0,0 +1,7 @@
function copyInputToClipboard(inputId) {
// Referenced from https://www.w3schools.com/howto/howto_js_copy_clipboard.asp
let source = document.getElementById(inputId);
source.select();
source.setSelectionRange(0, 99999); /*For mobile devices*/
document.execCommand("copy");
}

View file

@ -1,5 +1,18 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
use function MicroHTML\LABEL;
use function MicroHTML\TABLE;
use function MicroHTML\TBODY;
use function MicroHTML\TFOOT;
use function MicroHTML\TR;
use function MicroHTML\TH;
use function MicroHTML\TD;
use function MicroHTML\INPUT;
use function MicroHTML\rawHTML;
use function MicroHTML\emptyHTML;
use function MicroHTML\SELECT;
use function MicroHTML\OPTION;
class CronUploaderTheme extends Themelet class CronUploaderTheme extends Themelet
{ {
public function display_documentation( public function display_documentation(
@ -11,11 +24,19 @@ class CronUploaderTheme extends Themelet
string $cron_url, string $cron_url,
?array $log_entries ?array $log_entries
) { ) {
global $page; global $page, $config, $user_config;
$info_html = "";
$page->set_title("Cron Uploader");
$page->set_heading("Cron Uploader");
$info_html = "<b>Information</b> if (!$config->get_bool(UserConfig::ENABLE_API_KEYS)) {
$info_html .= "<b style='color:red'>THIS EXTENSION REQUIRES USER API KEYS TO BE ENABLED IN <a href=''>BOARD ADMIN</a></b>";
} else {
}
$info_html .= "<b>Information</b>
<br> <br>
<table style='width:470px;'> <table style='width:470px;'>
" . ($running ? "<tr><td colspan='4'><b style='color:red'>Cron upload is currently running</b></td></tr>" : "") . " " . ($running ? "<tr><td colspan='4'><b style='color:red'>Cron upload is currently running</b></td></tr>" : "") . "
@ -41,9 +62,13 @@ class CronUploaderTheme extends Themelet
<td>{$failed_dirinfo['path']}</td> <td>{$failed_dirinfo['path']}</td>
</tr></table> </tr></table>
<br>Cron Command: <input type='text' size='60' value='$cron_cmd'><br> <div>Cron Command: <input type='text' size='60' value='$cron_cmd' id='cron_command'>
Create a cron job with the command above.<br/> <button onclick='copyInputToClipboard(\"cron_command\")'>Copy</button></div>
Read the documentation if you're not sure what to do.<br>"; <div>Create a cron job with the command above.
Read the documentation if you're not sure what to do.</div>
<div>URL: <input type='text' size='60' value='$cron_url' id='cron_url'>
<button onclick='copyInputToClipboard(\"cron_url\")'>Copy</button></div>";
$install_html = " $install_html = "
This cron uploader is fairly easy to use but has to be configured first. This cron uploader is fairly easy to use but has to be configured first.
@ -76,12 +101,10 @@ class CronUploaderTheme extends Themelet
<li>If an import is already running, another cannot start until it is done.</li> <li>If an import is already running, another cannot start until it is done.</li>
<li>Each time it runs it will import for up to ".number_format($max_time)." seconds. This is controlled by the PHP max execution time.</li> <li>Each time it runs it will import for up to ".number_format($max_time)." seconds. This is controlled by the PHP max execution time.</li>
<li>Uploaded images will be moved to the 'uploaded' directory into a subfolder named after the time the import started. It's recommended that you remove everything out of this directory from time to time. If you have admin controls enabled, this can be done from <a href='".make_link("admin")."'>Board Admin</a>.</li> <li>Uploaded images will be moved to the 'uploaded' directory into a subfolder named after the time the import started. It's recommended that you remove everything out of this directory from time to time. If you have admin controls enabled, this can be done from <a href='".make_link("admin")."'>Board Admin</a>.</li>
<li>If you enable the db logging extension, you can view the log output on this screen. Otherwise the log will be written to a file at ".CronUploaderConfig::get_dir().DIRECTORY_SEPARATOR."uploads.log</li> <li>If you enable the db logging extension, you can view the log output on this screen. Otherwise the log will be written to a file at ".$user_config->get_string(CronUploaderConfig::DIR).DIRECTORY_SEPARATOR."uploads.log</li>
</ul> </ul>
"; ";
$page->set_title("Cron Uploader");
$page->set_heading("Cron Uploader");
$block = new Block("Cron Uploader", $info_html, "main", 10); $block = new Block("Cron Uploader", $info_html, "main", 10);
$block_install = new Block("Setup Guide", $install_html, "main", 30); $block_install = new Block("Setup Guide", $install_html, "main", 30);
@ -101,6 +124,40 @@ class CronUploaderTheme extends Themelet
} }
} }
public function get_user_options(string $dir, bool $stop_on_error, int $log_level, bool $all_logs): string
{
$form = SHM_SIMPLE_FORM(
"user_admin/cron_uploader",
TABLE(
["class"=>"form"],
TBODY(
TR(
TH("Cron Uploader")
),
TR(
TH("Root dir"),
TD(INPUT(["type"=>'text', "name"=>'name', "required"=>true]))
),
TR(
TH(),
TD(
LABEL(INPUT(["type"=>'checkbox', "name"=>'stop_on_error']), "Stop On Error")
)
),
TR(
TH(rawHTML("Repeat&nbsp;Password")),
TD(INPUT(["type"=>'password', "name"=>'pass2', "required"=>true]))
)
),
TFOOT(
TR(TD(["colspan"=>"2"], INPUT(["type"=>"submit", "value"=>"Save Settings"])))
)
)
);
$html = emptyHTML($form);
return (string)$html;
}
public function display_form(array $failed_dirs) public function display_form(array $failed_dirs)
{ {
global $page; global $page;

View file

@ -5,7 +5,7 @@ class CustomHtmlHeaders extends Extension
# Adds setup block for custom <head> content # Adds setup block for custom <head> content
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Custom HTML Headers"); $sb = $event->panel->create_new_block("Custom HTML Headers");
// custom headers // custom headers
$sb->add_longtext_option( $sb->add_longtext_option(
@ -19,8 +19,6 @@ class CustomHtmlHeaders extends Extension
"as prefix" => "prefix", "as prefix" => "prefix",
"as suffix" => "suffix" "as suffix" => "suffix"
], "<br>Add website name in title"); ], "<br>Add website name in title");
$event->panel->add_block($sb);
} }
public function onInitExt(InitExtEvent $event) public function onInitExt(InitExtEvent $event)

View file

@ -12,10 +12,9 @@ class Downtime extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Downtime"); $sb = $event->panel->create_new_block("Downtime");
$sb->add_bool_option("downtime", "Disable non-admin access: "); $sb->add_bool_option("downtime", "Disable non-admin access: ");
$sb->add_longtext_option("downtime_message", "<br>"); $sb->add_longtext_option("downtime_message", "<br>");
$event->panel->add_block($sb);
} }
public function onPageRequest(PageRequestEvent $event) public function onPageRequest(PageRequestEvent $event)

View file

@ -38,13 +38,11 @@ class Eokm extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("EOKM Filter"); $sb = $event->panel->create_new_block("EOKM Filter");
$sb->start_table(); $sb->start_table();
$sb->add_text_option("eokm_username", "Username", true); $sb->add_text_option("eokm_username", "Username", true);
$sb->add_text_option("eokm_password", "Password", true); $sb->add_text_option("eokm_password", "Password", true);
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
} }

View file

@ -62,13 +62,12 @@ class Forum extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Forum"); $sb = $event->panel->create_new_block("Forum");
$sb->add_int_option("forumTitleSubString", "Title max long: "); $sb->add_int_option("forumTitleSubString", "Title max long: ");
$sb->add_int_option("forumThreadsPerPage", "<br>Threads per page: "); $sb->add_int_option("forumThreadsPerPage", "<br>Threads per page: ");
$sb->add_int_option("forumPostsPerPage", "<br>Posts per page: "); $sb->add_int_option("forumPostsPerPage", "<br>Posts per page: ");
$sb->add_int_option("forumMaxCharsPerPost", "<br>Max chars per post: "); $sb->add_int_option("forumMaxCharsPerPost", "<br>Max chars per post: ");
$event->panel->add_block($sb);
} }
public function onUserPageBuilding(UserPageBuildingEvent $event) public function onUserPageBuilding(UserPageBuildingEvent $event)

View file

@ -5,10 +5,9 @@ class GoogleAnalytics extends Extension
# Add analytics to config # Add analytics to config
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Google Analytics"); $sb = $event->panel->create_new_block("Google Analytics");
$sb->add_text_option("google_analytics_id", "Analytics ID: "); $sb->add_text_option("google_analytics_id", "Analytics ID: ");
$sb->add_label("<br>(eg. UA-xxxxxxxx-x)"); $sb->add_label("<br>(eg. UA-xxxxxxxx-x)");
$event->panel->add_block($sb);
} }
# Load Analytics tracking code on page request # Load Analytics tracking code on page request

View file

@ -12,11 +12,10 @@ class ArchiveFileHandler extends DataHandlerExtension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Archive Handler Options"); $sb = $event->panel->create_new_block("Archive Handler Options");
$sb->add_text_option("archive_tmp_dir", "Temporary folder: "); $sb->add_text_option("archive_tmp_dir", "Temporary folder: ");
$sb->add_text_option("archive_extract_command", "<br>Extraction command: "); $sb->add_text_option("archive_extract_command", "<br>Extraction command: ");
$sb->add_label("<br>%f for archive, %d for temporary directory"); $sb->add_label("<br>%f for archive, %d for temporary directory");
$event->panel->add_block($sb);
} }
public function onDataUpload(DataUploadEvent $event) public function onDataUpload(DataUploadEvent $event)

View file

@ -30,7 +30,7 @@ class PixelFileHandlerTheme extends Themelet
} }
$html = "<img alt='main image' class='shm-main-image' id='main_image' src='$u_ilink' ". $html = "<img alt='main image' class='shm-main-image' id='main_image' src='$u_ilink' ".
"data-width='{$image->width}' data-height='{$image->height}'>"; "data-width='{$image->width}' data-height='{$image->height} data-mime='{$image->get_mime()}'>";
$page->add_block(new Block("Image", $html, "main", 10)); $page->add_block(new Block("Image", $html, "main", 10));
} }
} }

View file

@ -46,14 +46,13 @@ class VideoFileHandler extends DataHandlerExtension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Video Options"); $sb = $event->panel->create_new_block("Video Options");
$sb->start_table(); $sb->start_table();
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY, "Autoplay", true); $sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_AUTOPLAY, "Autoplay", true);
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_LOOP, "Loop", true); $sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_LOOP, "Loop", true);
$sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_MUTE, "Mute", true); $sb->add_bool_option(VideoFileHandlerConfig::PLAYBACK_MUTE, "Mute", true);
$sb->add_multichoice_option(VideoFileHandlerConfig::ENABLED_FORMATS, $this->get_options(), "Enabled Formats", true); $sb->add_multichoice_option(VideoFileHandlerConfig::ENABLED_FORMATS, $this->get_options(), "Enabled Formats", true);
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
protected function media_check_properties(MediaCheckPropertiesEvent $event): void protected function media_check_properties(MediaCheckPropertiesEvent $event): void

View file

@ -13,9 +13,8 @@ class Holiday extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Holiday Theme"); $sb = $event->panel->create_new_block("Holiday Theme");
$sb->add_bool_option("holiday_aprilfools", "Enable April Fools"); $sb->add_bool_option("holiday_aprilfools", "Enable April Fools");
$event->panel->add_block($sb);
} }
public function onPageRequest(PageRequestEvent $event) public function onPageRequest(PageRequestEvent $event)

View file

@ -27,11 +27,10 @@ class Home extends Extension
$counters[ucfirst($name)] = $name; $counters[ucfirst($name)] = $name;
} }
$sb = new SetupBlock("Home Page"); $sb = $event->panel->create_new_block("Home Page");
$sb->add_longtext_option("home_links", 'Page Links (Use BBCode, leave blank for defaults)'); $sb->add_longtext_option("home_links", 'Page Links (Use BBCode, leave blank for defaults)');
$sb->add_longtext_option("home_text", "<br>Page Text:<br>"); $sb->add_longtext_option("home_text", "<br>Page Text:<br>");
$sb->add_choice_option("home_counter", $counters, "<br>Counter: "); $sb->add_choice_option("home_counter", $counters, "<br>Counter: ");
$event->panel->add_block($sb);
} }

View file

@ -256,7 +256,7 @@ class ImageIO extends Extension
{ {
global $config; global $config;
$sb = new SetupBlock("Post Options"); $sb = $event->panel->create_new_block("Post Options");
$sb->start_table(); $sb->start_table();
$sb->position = 30; $sb->position = 30;
// advanced only // advanced only
@ -270,9 +270,8 @@ class ImageIO extends Extension
$sb->add_bool_option(ImageConfig::SHOW_META, "Show metadata", true); $sb->add_bool_option(ImageConfig::SHOW_META, "Show metadata", true);
} }
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
$sb = new SetupBlock("Thumbnailing"); $sb = $event->panel->create_new_block("Thumbnailing");
$sb->start_table(); $sb->start_table();
$sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine", true); $sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine", true);
$sb->add_choice_option(ImageConfig::THUMB_MIME, self::THUMBNAIL_TYPES, "Filetype", true); $sb->add_choice_option(ImageConfig::THUMB_MIME, self::THUMBNAIL_TYPES, "Filetype", true);
@ -294,8 +293,6 @@ class ImageIO extends Extension
} }
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
public function onParseLinkTemplate(ParseLinkTemplateEvent $event) public function onParseLinkTemplate(ParseLinkTemplateEvent $event)

View file

@ -9,11 +9,11 @@ class ImageIOTheme extends Themelet
*/ */
public function get_deleter_html(int $image_id): string public function get_deleter_html(int $image_id): string
{ {
return (string)SHM_SIMPLE_FORM( return (string)"<span id='image_delete_form'>".SHM_SIMPLE_FORM(
"image/delete", "image/delete",
INPUT(["type"=>'hidden', "name"=>'image_id', "value"=>$image_id]), INPUT(["type"=>'hidden', "name"=>'image_id', "value"=>$image_id]),
INPUT(["type"=>'submit', "value"=>'Delete', "onclick"=>'return confirm("Delete the image?");']), INPUT(["type"=>'submit', "value"=>'Delete', "onclick"=>'return confirm("Delete the image?");', "id"=>"image_delete_button"]),
); )."</span>";
} }
/** /**

View file

@ -5,6 +5,14 @@ class ImageViewCounter extends Extension
protected $theme; protected $theme;
private $view_interval = 3600; # allows views to be added each hour private $view_interval = 3600; # allows views to be added each hour
# Add Setup Block with options for view counter
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = $event->panel->create_new_block("Post View Counter");
$sb->add_bool_option("image_viewcounter_adminonly", "Display view counter only to admin");
}
# Adds view to database if needed
public function onDisplayingImage(DisplayingImageEvent $event) public function onDisplayingImage(DisplayingImageEvent $event)
{ {
global $database, $user; global $database, $user;

View file

@ -120,14 +120,12 @@ class Index extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Index Options"); $sb = $event->panel->create_new_block("Index Options");
$sb->position = 20; $sb->position = 20;
$sb->add_label("Show "); $sb->add_label("Show ");
$sb->add_int_option(IndexConfig::IMAGES); $sb->add_int_option(IndexConfig::IMAGES);
$sb->add_label(" images on the post list"); $sb->add_label(" images on the post list");
$event->panel->add_block($sb);
} }
public function onPageNavBuilding(PageNavBuildingEvent $event) public function onPageNavBuilding(PageNavBuildingEvent $event)

View file

@ -209,7 +209,7 @@ class IPBan extends Extension
{ {
global $config; global $config;
$sb = new SetupBlock("IP Ban"); $sb = $event->panel->create_new_block("IP Ban");
$sb->add_longtext_option("ipban_message", 'Message to show to banned users:<br>(with $IP, $DATE, $ADMIN, $REASON, and $CONTACT)'); $sb->add_longtext_option("ipban_message", 'Message to show to banned users:<br>(with $IP, $DATE, $ADMIN, $REASON, and $CONTACT)');
if ($config->get_string("ipban_message_ghost")) { if ($config->get_string("ipban_message_ghost")) {
$sb->add_longtext_option("ipban_message_ghost", 'Message to show to ghost users:'); $sb->add_longtext_option("ipban_message_ghost", 'Message to show to ghost users:');
@ -217,7 +217,6 @@ class IPBan extends Extension
if ($config->get_string("ipban_message_anon-ghost")) { if ($config->get_string("ipban_message_anon-ghost")) {
$sb->add_longtext_option("ipban_message_anon-ghost", 'Message to show to ghost anons:'); $sb->add_longtext_option("ipban_message_anon-ghost", 'Message to show to ghost anons:');
} }
$event->panel->add_block($sb);
} }
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event) public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)

View file

@ -13,9 +13,8 @@ class LinkImage extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Link to Post"); $sb = $event->panel->create_new_block("Link to Post");
$sb->add_text_option("ext_link-img_text-link_format", "Text Link Format: "); $sb->add_text_option("ext_link-img_text-link_format", "Text Link Format: ");
$event->panel->add_block($sb);
} }
public function onInitExt(InitExtEvent $event) public function onInitExt(InitExtEvent $event)

View file

@ -4,9 +4,8 @@ class LiveFeed extends Extension
{ {
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Live Feed"); $sb = $event->panel->create_new_block("Live Feed");
$sb->add_text_option("livefeed_host", "IP:port to send events to: "); $sb->add_text_option("livefeed_host", "IP:port to send events to: ");
$event->panel->add_block($sb);
} }
public function onUserCreation(UserCreationEvent $event) public function onUserCreation(UserCreationEvent $event)

View file

@ -235,7 +235,7 @@ class LogDatabase extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Logging (Database)"); $sb = $event->panel->create_new_block("Logging (Database)");
$sb->add_choice_option("log_db_priority", [ $sb->add_choice_option("log_db_priority", [
LOGGING_LEVEL_NAMES[SCORE_LOG_DEBUG] => SCORE_LOG_DEBUG, LOGGING_LEVEL_NAMES[SCORE_LOG_DEBUG] => SCORE_LOG_DEBUG,
LOGGING_LEVEL_NAMES[SCORE_LOG_INFO] => SCORE_LOG_INFO, LOGGING_LEVEL_NAMES[SCORE_LOG_INFO] => SCORE_LOG_INFO,
@ -243,7 +243,6 @@ class LogDatabase extends Extension
LOGGING_LEVEL_NAMES[SCORE_LOG_ERROR] => SCORE_LOG_ERROR, LOGGING_LEVEL_NAMES[SCORE_LOG_ERROR] => SCORE_LOG_ERROR,
LOGGING_LEVEL_NAMES[SCORE_LOG_CRITICAL] => SCORE_LOG_CRITICAL, LOGGING_LEVEL_NAMES[SCORE_LOG_CRITICAL] => SCORE_LOG_CRITICAL,
], "Debug Level: "); ], "Debug Level: ");
$event->panel->add_block($sb);
} }
public function onPageRequest(PageRequestEvent $event) public function onPageRequest(PageRequestEvent $event)

View file

@ -79,7 +79,7 @@ class Media extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Media Engines"); $sb = $event->panel->create_new_block("Media Engines");
// if (self::imagick_available()) { // if (self::imagick_available()) {
// try { // try {
@ -101,8 +101,6 @@ class Media extends Extension
$sb->add_shorthand_int_option(MediaConfig::MEM_LIMIT, "Mem limit", true); $sb->add_shorthand_int_option(MediaConfig::MEM_LIMIT, "Mem limit", true);
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)

View file

@ -47,6 +47,7 @@ abstract class MediaEngine
MimeType::GIF, MimeType::GIF,
MimeType::JPEG, MimeType::JPEG,
MimeType::PNG, MimeType::PNG,
MimeType::TGA,
MimeType::WEBP, MimeType::WEBP,
MimeType::WEBP_LOSSLESS, MimeType::WEBP_LOSSLESS,
], ],
@ -57,6 +58,7 @@ abstract class MediaEngine
MimeType::PNG, MimeType::PNG,
MimeType::PPM, MimeType::PPM,
MimeType::PSD, MimeType::PSD,
MimeType::TGA,
MimeType::TIFF, MimeType::TIFF,
MimeType::WEBP, MimeType::WEBP,
MimeType::WEBP_LOSSLESS, MimeType::WEBP_LOSSLESS,

View file

@ -51,6 +51,7 @@ class FileExtension
public const RSS = 'rss'; public const RSS = 'rss';
public const SVG = 'svg'; public const SVG = 'svg';
public const TAR = 'tar'; public const TAR = 'tar';
public const TGA = 'tga';
public const TEXT = 'txt'; public const TEXT = 'txt';
public const TIFF = 'tiff'; public const TIFF = 'tiff';
public const TIF = 'tif'; public const TIF = 'tif';

View file

@ -184,6 +184,11 @@ class MimeMap
self::MAP_EXT => [FileExtension::TAR], self::MAP_EXT => [FileExtension::TAR],
self::MAP_MIME => [MimeType::TAR], self::MAP_MIME => [MimeType::TAR],
], ],
MimeType::TGA => [
self::MAP_NAME => "TGA",
self::MAP_EXT => [FileExtension::TGA],
self::MAP_MIME => [MimeType::TGA, 'image/x-targa'],
],
MimeType::TEXT => [ MimeType::TEXT => [
self::MAP_NAME => "Text", self::MAP_NAME => "Text",
self::MAP_EXT => [FileExtension::TEXT, FileExtension::ASC], self::MAP_EXT => [FileExtension::TEXT, FileExtension::ASC],

View file

@ -44,6 +44,7 @@ class MimeType
public const RSS = 'application/rss+xml'; public const RSS = 'application/rss+xml';
public const SVG = 'image/svg+xml'; public const SVG = 'image/svg+xml';
public const TAR = 'application/x-tar'; public const TAR = 'application/x-tar';
public const TGA = 'image/x-tga';
public const TEXT = 'text/plain'; public const TEXT = 'text/plain';
public const TIFF = 'image/tiff'; public const TIFF = 'image/tiff';
public const WAV = 'audio/x-wav'; public const WAV = 'audio/x-wav';

View file

@ -177,7 +177,7 @@ class Pools extends Extension
// Add a block to the Board Config / Setup // Add a block to the Board Config / Setup
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Pools"); $sb = $event->panel->create_new_block("Pools");
$sb->add_int_option(PoolsConfig::MAX_IMPORT_RESULTS, "Max results on import: "); $sb->add_int_option(PoolsConfig::MAX_IMPORT_RESULTS, "Max results on import: ");
$sb->add_int_option(PoolsConfig::IMAGES_PER_PAGE, "<br>Posts per page: "); $sb->add_int_option(PoolsConfig::IMAGES_PER_PAGE, "<br>Posts per page: ");
$sb->add_int_option(PoolsConfig::LISTS_PER_PAGE, "<br>Index list items per page: "); $sb->add_int_option(PoolsConfig::LISTS_PER_PAGE, "<br>Index list items per page: ");
@ -186,8 +186,6 @@ class Pools extends Extension
$sb->add_bool_option(PoolsConfig::SHOW_NAV_LINKS, "<br>Show 'Prev' & 'Next' links when viewing pool images: "); $sb->add_bool_option(PoolsConfig::SHOW_NAV_LINKS, "<br>Show 'Prev' & 'Next' links when viewing pool images: ");
$sb->add_bool_option(PoolsConfig::AUTO_INCREMENT_ORDER, "<br>Autoincrement order when post is added to pool:"); $sb->add_bool_option(PoolsConfig::AUTO_INCREMENT_ORDER, "<br>Autoincrement order when post is added to pool:");
//$sb->add_bool_option(PoolsConfig::ADDER_ON_VIEW_IMAGE, "<br>Show pool adder on image: "); //$sb->add_bool_option(PoolsConfig::ADDER_ON_VIEW_IMAGE, "<br>Show pool adder on image: ");
$event->panel->add_block($sb);
} }
public function onPageNavBuilding(PageNavBuildingEvent $event) public function onPageNavBuilding(PageNavBuildingEvent $event)

12
ext/post_peek/info.php Normal file
View file

@ -0,0 +1,12 @@
<?php declare(strict_types=1);
class PostPeekInfo extends ExtensionInfo
{
public const KEY = "post_peek";
public $key = self::KEY;
public $name = "Postt Peek";
public $url = self::SHIMMIE_URL;
public $authors = "Matthew Barbour";
public $license = self::LICENSE_WTFPL;
}

5
ext/post_peek/main.php Normal file
View file

@ -0,0 +1,5 @@
<?php declare(strict_types=1);
class PostPeek extends Extension
{
}

167
ext/post_peek/script.js Normal file
View file

@ -0,0 +1,167 @@
/*jshint bitwise:true, curly:true, forin:false, noarg:true, noempty:true, nonew:true, undef:true, strict:false, browser:true, jquery:true */
var peekerOpen = false;
function calculatePeekerSize(imageWidth, imageHeight, maxWidth, maxHeight) {
let xscale = maxWidth / imageWidth;
let yscale = maxHeight / imageHeight;
let scale;
if(yscale < xscale) {
scale = yscale;
} else {
scale = xscale;
}
return [imageWidth * scale, imageHeight * scale];
}
function postPeekAddPeeker() {
const mimeRegex = /^image\/.+/g;
const cursorMargin = 20;
const windowMargin = 50;
var peekerElement = document.createElement("DIV");
peekerElement.style.position = "absolute";
peekerElement.style.border = "solid 1px black";
peekerElement.style.boxShadow = "5px 5px 10px black";
var image_elements = document.querySelectorAll(".shm-image-list a img");
image_elements.forEach(function(item) {
var parent = item.parentElement;
parent.style.position = "relative";
var mime =parent.dataset["mime"];
if(mime.match(mimeRegex)) {
var linkElement = document.createElement("DIV");
linkElement.innerHTML = "&#x1F50D;";
linkElement.style.position = "absolute";
linkElement.style.top = "4px";
linkElement.style.left = "4px";
linkElement.style.width = "10px";
linkElement.style.height = "10px";
linkElement.style.fontSize = "20px";
linkElement.style.color = "red";
var width = parseInt(parent.dataset["width"]);
var height = parseInt(parent.dataset["height"]);
var ratio = width/height;
linkElement.onmouseenter = function(e) {
let imgElement = document.createElement("IMG");
imgElement.src = item.src.replace("thumb/", "image/");
imgElement.style.width = "100%";
imgElement.style.aheight = "100%";
peekerElement.innerHTML = "";
let sizeCandidates = [];
// Add arrays defining each possible area to render
// Array is [left, right, top, bottom, [width, height]]
// Calculate for the right area
let dimensions = calculatePeekerSize(width, height, window.innerWidth - e.clientX - windowMargin, window.innerHeight - windowMargin);
sizeCandidates.push([
(e.clientX + cursorMargin - window.scrollX) + "px", // left
"", // right
"", // top
((window.innerHeight - dimensions[1]) / 2 - window.scrollY) + "px", // bottom
dimensions
]);
// Calculate for the bottom area
dimensions = calculatePeekerSize(width, height, window.innerWidth - windowMargin, window.innerHeight - e.clientY - windowMargin);
sizeCandidates.push([
((window.innerWidth - dimensions[0]) / 2 - window.scrollX) + "px", // left
"", // right
(e.clientY + cursorMargin + window.scrollY) + "px", // top
"", // bottom
dimensions
]);
// Calculate for the left area
dimensions = calculatePeekerSize(width, height, e.clientX - windowMargin, window.innerHeight - windowMargin);
sizeCandidates.push([
"", // left
(window.innerWidth - e.clientX + cursorMargin - window.scrollX) + "px", // right
"", // top
((window.innerHeight - dimensions[1]) / 2 - window.scrollY) + "px", // bottom
dimensions
]);
// Calculate for the top area
dimensions = calculatePeekerSize(width, height, window.innerWidth - windowMargin, e.clientY - windowMargin);
sizeCandidates.push([
((window.innerWidth - dimensions[0]) / 2 - window.scrollX) + "px", // left
"", // right
"", // top
(window.innerHeight - e.clientY + cursorMargin - window.scrollY) + "px", // bottom
dimensions
]);
let candidate = null;
let candidateSize = 0;
for(let i = 0; i < sizeCandidates.length; i++) {
let newCandidate = sizeCandidates[i];
let newCandidateSize = newCandidate[4][0] * newCandidate[4][1];
if(newCandidateSize>candidateSize) {
candidateSize = newCandidateSize;
candidate = newCandidate;
}
}
peekerElement.style.left = candidate[0];
peekerElement.style.right = candidate[1];
peekerElement.style.top =candidate[2];
peekerElement.style.bottom = candidate[3];
peekerElement.style.width = candidate[4][0] + "px";
peekerElement.style.height = candidate[4][1] + "px";
peekerElement.appendChild(imgElement);
if(!peekerOpen) {
document.body.appendChild(peekerElement);
}
peekerOpen = true;
}
linkElement.onmouseleave = function (e) {
if(peekerOpen) {
document.body.removeChild(peekerElement);
peekerOpen = false;
}
}
parent.appendChild(linkElement);
//
// var offsetX = (item.offsetWidth - newWidth)/2;
// var offsetY = (item.offsetHeight - newHeight)/2;
//
// var scaleX = newWidth / frameWidth;
// var scaleY = newHeight / frameHeight;
// var scale = scaleX;
// if(scaleY<scaleX) {
// scale = scaleY;
// }
// frameWidth = frameWidth * scale;
// frameHeight = frameHeight * scale;
//
// offsetX = offsetX + ((newWidth - frameWidth)/2);
// offsetY = offsetY + ((newHeight - frameHeight)/2);
//
// console.log("test");
// var frame = $("<div class='frame" + inWidth + "x" + inHeight + "' style='position:absolute; left:" + offsetX + "px; top:" + offsetY + "px; width:" + frameWidth + "px; height:" + frameHeight + "px;outline:solid 1px " + color + ";color:" + color + ";vertical-align: bottom; '><div style='position:absolute; left:0; bottom:0;'>" + inWidth + ":" + inHeight + "</div></div>");
// $(parent).append(frame);
}
});
}
document.addEventListener('DOMContentLoaded', () => {
postPeekAddPeeker();
});

5
ext/post_peek/theme.php Normal file
View file

@ -0,0 +1,5 @@
<?php declare(strict_types=1);
class PostPeekTheme extends Themelet
{
}

View file

@ -64,13 +64,11 @@ class PostTitles extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Post Titles"); $sb = $event->panel->create_new_block("Post Titles");
$sb->start_table(); $sb->start_table();
$sb->add_bool_option(PostTitlesConfig::DEFAULT_TO_FILENAME, "Default to filename", true); $sb->add_bool_option(PostTitlesConfig::DEFAULT_TO_FILENAME, "Default to filename", true);
$sb->add_bool_option(PostTitlesConfig::SHOW_IN_WINDOW_TITLE, "Show in window title", true); $sb->add_bool_option(PostTitlesConfig::SHOW_IN_WINDOW_TITLE, "Show in window title", true);
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
public function onBulkExport(BulkExportEvent $event) public function onBulkExport(BulkExportEvent $event)

View file

@ -25,15 +25,11 @@ class PrivateImage extends Extension
public function onUserOptionsBuilding(UserOptionsBuildingEvent $event) public function onUserOptionsBuilding(UserOptionsBuildingEvent $event)
{ {
global $user, $user_config; $sb = $event->panel->create_new_block("Private Posts");
$sb->start_table();
$event->add_html( $sb->add_bool_option(PrivateImageConfig::USER_SET_DEFAULT, "Mark posts private by default", true);
$this->theme->get_user_options( $sb->add_bool_option(PrivateImageConfig::USER_VIEW_DEFAULT, "View private posts by default", true);
$user, $sb->end_table();
$user_config->get_bool(PrivateImageConfig::USER_SET_DEFAULT),
$user_config->get_bool(PrivateImageConfig::USER_VIEW_DEFAULT),
)
);
} }
public function onPageRequest(PageRequestEvent $event) public function onPageRequest(PageRequestEvent $event)

View file

@ -36,31 +36,4 @@ class PrivateImageTheme extends Themelet
</div> </div>
'; ';
} }
public function get_user_options(User $user, bool $set_by_default, bool $view_by_default): string
{
$html = "
<p>".make_form(make_link("user_admin/private_image"))."
<input type='hidden' name='id' value='$user->id'>
<table style='width: 300px;'>
<tbody>
<tr><th colspan='2'>Private Images</th></tr>
<tr>
<td>
<label><input type='checkbox' name='set_default' value='true' " .($set_by_default ? 'checked=checked': ''). " />Mark images private by default</label>
</td>
</tr><tr>
<td>
<label><input type='checkbox' name='view_default' value='true' " .($view_by_default ? 'checked=checked': ''). " />View private images by default</label>
</td>
</tr>
</tbody>
<tfoot>
<tr><td><input type='submit' value='Save'></td></tr>
</tfoot>
</table>
</form>
";
return $html;
}
} }

View file

@ -43,9 +43,8 @@ class RandomImage extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Random Post"); $sb = $event->panel->create_new_block("Random Post");
$sb->add_bool_option("show_random_block", "Show Random Block: "); $sb->add_bool_option("show_random_block", "Show Random Block: ");
$event->panel->add_block($sb);
} }
public function onPostListBuilding(PostListBuildingEvent $event) public function onPostListBuilding(PostListBuildingEvent $event)

View file

@ -57,15 +57,13 @@ class RandomList extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Random Posts List"); $sb = $event->panel->create_new_block("Random Posts List");
// custom headers // custom headers
$sb->add_int_option( $sb->add_int_option(
"random_images_list_count", "random_images_list_count",
"Amount of Random posts to display " "Amount of Random posts to display "
); );
$event->panel->add_block($sb);
} }
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event) public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)

View file

@ -143,15 +143,19 @@ class Ratings extends Extension
public function onUserOptionsBuilding(UserOptionsBuildingEvent $event) public function onUserOptionsBuilding(UserOptionsBuildingEvent $event)
{ {
global $user; global $user, $_shm_ratings;
$event->add_html( $levels = self::get_user_class_privs($user);
$this->theme->get_user_options( $options = [];
$user, foreach ($levels as $level) {
self::get_user_default_ratings($user), $options[$_shm_ratings[$level]->name] = $level;
self::get_user_class_privs($user) }
)
); $sb = $event->panel->create_new_block("Default Rating Filter");
$sb->start_table();
$sb->add_multichoice_option(RatingsConfig::USER_DEFAULTS, $options, "Output Log Level: ", true);
$sb->end_table();
$sb->add_label("This controls the default rating search results will be filtered by, and nothing else. To override in your search results, add rating:* to your search.");
} }
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
@ -165,7 +169,7 @@ class Ratings extends Extension
$options[$rating->name] = $rating->code; $options[$rating->name] = $rating->code;
} }
$sb = new SetupBlock("Post Ratings"); $sb = $event->panel->create_new_block("Post Ratings");
$sb->start_table(); $sb->start_table();
foreach (array_keys($_shm_user_classes) as $key) { foreach (array_keys($_shm_user_classes) as $key) {
if ($key == "base" || $key == "hellbanned") { if ($key == "base" || $key == "hellbanned") {
@ -174,8 +178,6 @@ class Ratings extends Extension
$sb->add_multichoice_option("ext_rating_" . $key . "_privs", $options, $key, true); $sb->add_multichoice_option("ext_rating_" . $key . "_privs", $options, $key, true);
} }
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
public function onDisplayingImage(DisplayingImageEvent $event) public function onDisplayingImage(DisplayingImageEvent $event)
@ -417,33 +419,6 @@ class Ratings extends Extension
$page->set_redirect(make_link("post/list")); $page->set_redirect(make_link("post/list"));
} }
} }
if ($event->page_matches("user_admin")) {
if (!$user->check_auth_token()) {
return;
}
switch ($event->get_arg(0)) {
case "default_ratings":
if (!array_key_exists("id", $_POST) || empty($_POST["id"])) {
return;
}
if (!array_key_exists("rating", $_POST) || empty($_POST["rating"])) {
return;
}
$id = intval($_POST["id"]);
if ($id != $user->id) {
throw new SCoreException("Cannot change another user's settings");
}
$ratings = $_POST["rating"];
$user_config->set_array(RatingsConfig::USER_DEFAULTS, $ratings);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("user"));
break;
}
}
} }
public static function get_sorted_ratings(): array public static function get_sorted_ratings(): array
@ -464,9 +439,9 @@ class Ratings extends Extension
return $config->get_array("ext_rating_".$user->class->name."_privs"); return $config->get_array("ext_rating_".$user->class->name."_privs");
} }
public static function get_user_default_ratings(User $user): array public static function get_user_default_ratings(): array
{ {
global $user_config; global $user_config, $user;
$available = self::get_user_class_privs($user); $available = self::get_user_class_privs($user);
$selected = $user_config->get_array(RatingsConfig::USER_DEFAULTS); $selected = $user_config->get_array(RatingsConfig::USER_DEFAULTS);

View file

@ -96,7 +96,7 @@ class RatingsTheme extends Themelet
<input type='hidden' name='id' value='$user->id'> <input type='hidden' name='id' value='$user->id'>
<table style='width: 300px;'> <table style='width: 300px;'>
<thead> <thead>
<tr><th colspan='2'>Default Rating Filter</th></tr> <tr><th colspan='2'></th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td>This controls the default rating search results will be filtered by, and nothing else. To override in your search results, add rating:* to your search.</td></tr> <tr><td>This controls the default rating search results will be filtered by, and nothing else. To override in your search results, add rating:* to your search.</td></tr>

View file

@ -157,7 +157,7 @@ class ReportImage extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Post Reports"); $sb = $event->panel->create_new_block("Post Reports");
$opts = [ $opts = [
"Reporter Only" => "user", "Reporter Only" => "user",
@ -166,8 +166,6 @@ class ReportImage extends Extension
"None" => "none", "None" => "none",
]; ];
$sb->add_choice_option("report_image_publicity", $opts, "Show publicly: "); $sb->add_choice_option("report_image_publicity", $opts, "Show publicly: ");
$event->panel->add_block($sb);
} }
public function delete_reports_by(int $user_id) public function delete_reports_by(int $user_id)

View file

@ -58,7 +58,7 @@ class ResolutionLimit extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Resolution Limits"); $sb = $event->panel->create_new_block("Resolution Limits");
$sb->add_label("Min "); $sb->add_label("Min ");
$sb->add_int_option("upload_min_width"); $sb->add_int_option("upload_min_width");
@ -77,7 +77,5 @@ class ResolutionLimit extends Extension
$sb->add_label("<br>Ratios "); $sb->add_label("<br>Ratios ");
$sb->add_text_option("upload_ratios"); $sb->add_text_option("upload_ratios");
$sb->add_label("<br>(eg. '4:3 16:9', blank for no limit)"); $sb->add_label("<br>(eg. '4:3 16:9', blank for no limit)");
$event->panel->add_block($sb);
} }
} }

View file

@ -50,7 +50,7 @@ class ResizeImage extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Image Resize"); $sb = $event->panel->create_new_block("Image Resize");
$sb->start_table(); $sb->start_table();
$sb->add_choice_option(ResizeConfig::ENGINE, MediaEngine::IMAGE_ENGINES, "Engine", true); $sb->add_choice_option(ResizeConfig::ENGINE, MediaEngine::IMAGE_ENGINES, "Engine", true);
$sb->add_bool_option(ResizeConfig::ENABLED, "Allow resizing images", true); $sb->add_bool_option(ResizeConfig::ENABLED, "Allow resizing images", true);
@ -67,7 +67,6 @@ class ResizeImage extends Extension
$sb->add_label("</td><td>px</td></tr>"); $sb->add_label("</td><td>px</td></tr>");
$sb->add_label("<tr><td></td><td>(enter 0 for no default)</td></tr>"); $sb->add_label("<tr><td></td><td>(enter 0 for no default)</td></tr>");
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
public function onDataUpload(DataUploadEvent $event) public function onDataUpload(DataUploadEvent $event)

View file

@ -38,12 +38,11 @@ class RotateImage extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Image Rotate"); $sb = $event->panel->create_new_block("Image Rotate");
$sb->add_bool_option("rotate_enabled", "Allow rotating images: "); $sb->add_bool_option("rotate_enabled", "Allow rotating images: ");
$sb->add_label("<br>Default Orientation: "); $sb->add_label("<br>Default Orientation: ");
$sb->add_int_option("rotate_default_deg"); $sb->add_int_option("rotate_default_deg");
$sb->add_label(" deg"); $sb->add_label(" deg");
$event->panel->add_block($sb);
} }
public function onPageRequest(PageRequestEvent $event) public function onPageRequest(PageRequestEvent $event)

View file

@ -40,10 +40,19 @@ class SetupPanel
{ {
/** @var SetupBlock[] */ /** @var SetupBlock[] */
public $blocks = []; public $blocks = [];
/** @var BaseConfig */
public $config;
public function add_block(SetupBlock $block) public function __construct(BaseConfig $config)
{ {
$this->config = $config;
}
public function create_new_block(string $title): SetupBlock
{
$block = new SetupBlock($title, $this->config);
$this->blocks[] = $block; $this->blocks[] = $block;
return $block;
} }
} }
@ -53,10 +62,13 @@ class SetupBlock extends Block
public $header; public $header;
/** @var string */ /** @var string */
public $body; public $body;
/** @var BaseConfig */
public $config;
public function __construct(string $title) public function __construct(string $title, BaseConfig $config)
{ {
parent::__construct($title, "", "main", 50); parent::__construct($title, "", "main", 50);
$this->config = $config;
} }
public function add_label(string $text) public function add_label(string $text)
@ -166,8 +178,7 @@ class SetupBlock extends Block
public function add_text_option(string $name, string $label=null, bool $table_row = false) public function add_text_option(string $name, string $label=null, bool $table_row = false)
{ {
global $config; $val = html_escape($this->config->get_string($name));
$val = html_escape($config->get_string($name));
$html = "<input type='text' id='{$name}' name='_config_{$name}' value='{$val}'>\n"; $html = "<input type='text' id='{$name}' name='_config_{$name}' value='{$val}'>\n";
$html .= "<input type='hidden' name='_type_{$name}' value='string'>\n"; $html .= "<input type='hidden' name='_type_{$name}' value='string'>\n";
@ -177,8 +188,7 @@ class SetupBlock extends Block
public function add_longtext_option(string $name, string $label=null, bool $table_row = false) public function add_longtext_option(string $name, string $label=null, bool $table_row = false)
{ {
global $config; $val = html_escape($this->config->get_string($name));
$val = html_escape($config->get_string($name));
$rows = max(3, min(10, count(explode("\n", $val)))); $rows = max(3, min(10, count(explode("\n", $val))));
$html = "<textarea rows='$rows' id='$name' name='_config_$name'>$val</textarea>\n"; $html = "<textarea rows='$rows' id='$name' name='_config_$name'>$val</textarea>\n";
@ -189,8 +199,7 @@ class SetupBlock extends Block
public function add_bool_option(string $name, string $label=null, bool $table_row = false) public function add_bool_option(string $name, string $label=null, bool $table_row = false)
{ {
global $config; $checked = $this->config->get_bool($name) ? " checked" : "";
$checked = $config->get_bool($name) ? " checked" : "";
$html = ""; $html = "";
if (!$table_row&&!is_null($label)) { if (!$table_row&&!is_null($label)) {
@ -215,8 +224,7 @@ class SetupBlock extends Block
public function add_int_option(string $name, string $label=null, bool $table_row = false) public function add_int_option(string $name, string $label=null, bool $table_row = false)
{ {
global $config; $val = $this->config->get_int($name);
$val = $config->get_int($name);
$html = "<input type='number' id='$name' name='_config_$name' value='$val' size='4' style='text-align: center;' step='1' />\n"; $html = "<input type='number' id='$name' name='_config_$name' value='$val' size='4' style='text-align: center;' step='1' />\n";
$html .= "<input type='hidden' name='_type_$name' value='int' />\n"; $html .= "<input type='hidden' name='_type_$name' value='int' />\n";
@ -226,8 +234,7 @@ class SetupBlock extends Block
public function add_shorthand_int_option(string $name, string $label=null, bool $table_row = false) public function add_shorthand_int_option(string $name, string $label=null, bool $table_row = false)
{ {
global $config; $val = to_shorthand_int($this->config->get_int($name));
$val = to_shorthand_int($config->get_int($name));
$html = "<input type='text' id='$name' name='_config_$name' value='$val' size='6' style='text-align: center;'>\n"; $html = "<input type='text' id='$name' name='_config_$name' value='$val' size='6' style='text-align: center;'>\n";
$html .= "<input type='hidden' name='_type_$name' value='int'>\n"; $html .= "<input type='hidden' name='_type_$name' value='int'>\n";
@ -236,11 +243,10 @@ class SetupBlock extends Block
public function add_choice_option(string $name, array $options, string $label=null, bool $table_row = false) public function add_choice_option(string $name, array $options, string $label=null, bool $table_row = false)
{ {
global $config;
if (is_int(array_values($options)[0])) { if (is_int(array_values($options)[0])) {
$current = $config->get_int($name); $current = $this->config->get_int($name);
} else { } else {
$current = $config->get_string($name); $current = $this->config->get_string($name);
} }
$html = "<select id='$name' name='_config_$name'>"; $html = "<select id='$name' name='_config_$name'>";
@ -260,8 +266,7 @@ class SetupBlock extends Block
public function add_multichoice_option(string $name, array $options, string $label=null, bool $table_row = false) public function add_multichoice_option(string $name, array $options, string $label=null, bool $table_row = false)
{ {
global $config; $current = $this->config->get_array($name);
$current = $config->get_array($name);
$html = "<select id='$name' name='_config_{$name}[]' multiple size='5'>"; $html = "<select id='$name' name='_config_{$name}[]' multiple size='5'>";
foreach ($options as $optname => $optval) { foreach ($options as $optname => $optval) {
@ -281,8 +286,7 @@ class SetupBlock extends Block
public function add_color_option(string $name, string $label=null, bool $table_row = false) public function add_color_option(string $name, string $label=null, bool $table_row = false)
{ {
global $config; $val = html_escape($this->config->get_string($name));
$val = html_escape($config->get_string($name));
$html = "<input type='color' id='{$name}' name='_config_{$name}' value='{$val}'>\n"; $html = "<input type='color' id='{$name}' name='_config_{$name}' value='{$val}'>\n";
$html .= "<input type='hidden' name='_type_{$name}' value='string'>\n"; $html .= "<input type='hidden' name='_type_{$name}' value='string'>\n";
@ -320,7 +324,7 @@ class Setup extends Extension
$this->theme->display_permission_denied(); $this->theme->display_permission_denied();
} else { } else {
if ($event->count_args() == 0) { if ($event->count_args() == 0) {
$panel = new SetupPanel(); $panel = new SetupPanel($config);
send_event(new SetupBuildingEvent($panel)); send_event(new SetupBuildingEvent($panel));
$this->theme->display_page($page, $panel); $this->theme->display_page($page, $panel);
} elseif ($event->get_arg(0) == "save" && $user->check_auth_token()) { } elseif ($event->get_arg(0) == "save" && $user->check_auth_token()) {
@ -370,7 +374,7 @@ class Setup extends Extension
} }
}); });
</script>"; </script>";
$sb = new SetupBlock("General"); $sb = $event->panel->create_new_block("General");
$sb->position = 0; $sb->position = 0;
$sb->add_text_option(SetupConfig::TITLE, "Site title: "); $sb->add_text_option(SetupConfig::TITLE, "Site title: ");
$sb->add_text_option(SetupConfig::FRONT_PAGE, "<br>Front page: "); $sb->add_text_option(SetupConfig::FRONT_PAGE, "<br>Front page: ");
@ -380,20 +384,18 @@ class Setup extends Extension
//$sb->add_multichoice_option("testarray", array("a" => "b", "c" => "d"), "<br>Test Array: "); //$sb->add_multichoice_option("testarray", array("a" => "b", "c" => "d"), "<br>Test Array: ");
$sb->add_bool_option("nice_urls", "<br>Nice URLs: "); $sb->add_bool_option("nice_urls", "<br>Nice URLs: ");
$sb->add_label("<span title='$test_url' id='nicetest'>(Javascript inactive, can't test!)</span>$nicescript"); $sb->add_label("<span title='$test_url' id='nicetest'>(Javascript inactive, can't test!)</span>$nicescript");
$event->panel->add_block($sb);
$sb = new SetupBlock("Remote API Integration"); $sb = $event->panel->create_new_block("Remote API Integration");
$sb->add_label("<a href='https://akismet.com/'>Akismet</a>"); $sb->add_label("<a href='https://akismet.com/'>Akismet</a>");
$sb->add_text_option("comment_wordpress_key", "<br>API key: "); $sb->add_text_option("comment_wordpress_key", "<br>API key: ");
$sb->add_label("<br>&nbsp;<br><a href='https://www.google.com/recaptcha/admin'>ReCAPTCHA</a>"); $sb->add_label("<br>&nbsp;<br><a href='https://www.google.com/recaptcha/admin'>ReCAPTCHA</a>");
$sb->add_text_option("api_recaptcha_privkey", "<br>Secret key: "); $sb->add_text_option("api_recaptcha_privkey", "<br>Secret key: ");
$sb->add_text_option("api_recaptcha_pubkey", "<br>Site key: "); $sb->add_text_option("api_recaptcha_pubkey", "<br>Site key: ");
$event->panel->add_block($sb);
} }
public function onConfigSave(ConfigSaveEvent $event) public function onConfigSave(ConfigSaveEvent $event)
{ {
global $config; $config = $event->config;
foreach ($_POST as $_name => $junk) { foreach ($_POST as $_name => $junk) {
if (substr($_name, 0, 6) == "_type_") { if (substr($_name, 0, 6) == "_type_") {
$name = substr($_name, 6); $name = substr($_name, 6);

View file

@ -8,7 +8,7 @@ class SetupTheme extends Themelet
* $panel = the container of the blocks * $panel = the container of the blocks
* $panel->blocks the blocks to be displayed, unsorted * $panel->blocks the blocks to be displayed, unsorted
* *
* It's recommented that the theme sort the blocks before doing anything * It's recommended that the theme sort the blocks before doing anything
* else, using: usort($panel->blocks, "blockcmp"); * else, using: usort($panel->blocks, "blockcmp");
* *
* The page should wrap all the options in a form which links to setup_save * The page should wrap all the options in a form which links to setup_save

View file

@ -17,9 +17,8 @@ class SiteDescription extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Site Description"); $sb = $event->panel->create_new_block("Site Description");
$sb->add_text_option("site_description", "Description: "); $sb->add_text_option("site_description", "Description: ");
$sb->add_text_option("site_keywords", "<br>Keywords: "); $sb->add_text_option("site_keywords", "<br>Keywords: ");
$event->panel->add_block($sb);
} }
} }

View file

@ -28,13 +28,11 @@ class XMLSitemap extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Sitemap"); $sb = $event->panel->create_new_block("Sitemap");
$sb->add_bool_option("sitemap_generatefull", "Generate full sitemap"); $sb->add_bool_option("sitemap_generatefull", "Generate full sitemap");
$sb->add_label("<br>(Enabled: every image and tag in sitemap, generation takes longer)"); $sb->add_label("<br>(Enabled: every image and tag in sitemap, generation takes longer)");
$sb->add_label("<br>(Disabled: only display the last 50 uploads in the sitemap)"); $sb->add_label("<br>(Disabled: only display the last 50 uploads in the sitemap)");
$event->panel->add_block($sb);
} }
// sitemap with only the latest 50 images // sitemap with only the latest 50 images

View file

@ -61,12 +61,11 @@ class SourceHistory extends Extension
// so let's default to -1 and the user can go advanced if // so let's default to -1 and the user can go advanced if
// they /really/ want to // they /really/ want to
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event) {
$sb = new SetupBlock("Source History"); $sb = $event->panel->create_new_block("Source History");
$sb->add_label("Limit to "); $sb->add_label("Limit to ");
$sb->add_int_option("history_limit"); $sb->add_int_option("history_limit");
$sb->add_label(" entires per image"); $sb->add_label(" entires per image");
$sb->add_label("<br>(-1 for unlimited)"); $sb->add_label("<br>(-1 for unlimited)");
$event->panel->add_block($sb);
} }
*/ */

View file

@ -34,7 +34,7 @@ class TagEditCloud extends Extension
{ {
$sort_by = ['Alphabetical'=>'a','Popularity'=>'p','Relevance'=>'r','Categories'=>'c']; $sort_by = ['Alphabetical'=>'a','Popularity'=>'p','Relevance'=>'r','Categories'=>'c'];
$sb = new SetupBlock("Tag Edit Cloud"); $sb = $event->panel->create_new_block("Tag Edit Cloud");
$sb->add_bool_option("tageditcloud_disable", "Disable Tag Selection Cloud: "); $sb->add_bool_option("tageditcloud_disable", "Disable Tag Selection Cloud: ");
$sb->add_choice_option("tageditcloud_sort", $sort_by, "<br>Sort the tags by:"); $sb->add_choice_option("tageditcloud_sort", $sort_by, "<br>Sort the tags by:");
$sb->add_bool_option("tageditcloud_usedfirst", "<br>Always show used tags first: "); $sb->add_bool_option("tageditcloud_usedfirst", "<br>Always show used tags first: ");
@ -47,8 +47,6 @@ class TagEditCloud extends Extension
$sb->add_label(" tags."); $sb->add_label(" tags.");
$sb->add_label("<br><b>Relevance sort</b>:<br>Ignore tags (space separated): "); $sb->add_label("<br><b>Relevance sort</b>:<br>Ignore tags (space separated): ");
$sb->add_text_option("tageditcloud_ignoretags"); $sb->add_text_option("tageditcloud_ignoretags");
$event->panel->add_block($sb);
} }
private function build_tag_map(Image $image): ?string private function build_tag_map(Image $image): ?string

View file

@ -61,12 +61,11 @@ class TagHistory extends Extension
// so let's default to -1 and the user can go advanced if // so let's default to -1 and the user can go advanced if
// they /really/ want to // they /really/ want to
public function onSetupBuilding(SetupBuildingEvent $event) { public function onSetupBuilding(SetupBuildingEvent $event) {
$sb = new SetupBlock("Tag History"); $sb = $event->panel->create_new_block("Tag History");
$sb->add_label("Limit to "); $sb->add_label("Limit to ");
$sb->add_int_option("history_limit"); $sb->add_int_option("history_limit");
$sb->add_label(" entires per image"); $sb->add_label(" entires per image");
$sb->add_label("<br>(-1 for unlimited)"); $sb->add_label("<br>(-1 for unlimited)");
$event->panel->add_block($sb);
} }
*/ */

View file

@ -14,11 +14,13 @@ class TagListConfig
public const OMIT_TAGS = "tag_list_omit_tags"; public const OMIT_TAGS = "tag_list_omit_tags";
public const TYPE_RELATED = "related"; public const TYPE_RELATED = "related";
public const TYPE_TAGS= "tags"; public const TYPE_TAGS = "tags";
public const TYPE_BOTH = "both";
public const TYPE_CHOICES = [ public const TYPE_CHOICES = [
"Post's tags only" => TagListConfig::TYPE_TAGS, "Post's tags only" => TagListConfig::TYPE_TAGS,
"Show related" => TagListConfig::TYPE_RELATED "Related tags only" => TagListConfig::TYPE_RELATED,
"Both" => TagListConfig::TYPE_BOTH
]; ];
public const SORT_ALPHABETICAL = "alphabetical"; public const SORT_ALPHABETICAL = "alphabetical";

View file

@ -81,27 +81,28 @@ class TagList extends Extension
{ {
global $config, $page; global $config, $page;
if ($config->get_int(TagListConfig::LENGTH) > 0) { if ($config->get_int(TagListConfig::LENGTH) > 0) {
if ($config->get_string(TagListConfig::IMAGE_TYPE) == TagListConfig::TYPE_RELATED) { $type = $config->get_string(TagListConfig::IMAGE_TYPE);
$this->add_related_block($page, $event->image); if ($type == TagListConfig::TYPE_TAGS || $type == TagListConfig::TYPE_BOTH) {
} else {
if (class_exists("TagCategories") and $config->get_bool(TagCategoriesConfig::SPLIT_ON_VIEW)) { if (class_exists("TagCategories") and $config->get_bool(TagCategoriesConfig::SPLIT_ON_VIEW)) {
$this->add_split_tags_block($page, $event->image); $this->add_split_tags_block($page, $event->image);
} else { } else {
$this->add_tags_block($page, $event->image); $this->add_tags_block($page, $event->image);
} }
} }
if ($type == TagListConfig::TYPE_RELATED || $type == TagListConfig::TYPE_BOTH) {
$this->add_related_block($page, $event->image);
}
} }
} }
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Tag Map Options"); $sb = $event->panel->create_new_block("Tag Map Options");
$sb->add_int_option(TagListConfig::TAGS_MIN, "Only show tags used at least "); $sb->add_int_option(TagListConfig::TAGS_MIN, "Only show tags used at least ");
$sb->add_label(" times"); $sb->add_label(" times");
$sb->add_bool_option(TagListConfig::PAGES, "<br>Paged tag lists: "); $sb->add_bool_option(TagListConfig::PAGES, "<br>Paged tag lists: ");
$event->panel->add_block($sb);
$sb = new SetupBlock("Popular / Related Tag List"); $sb = $event->panel->create_new_block("Popular / Related Tag List");
$sb->add_int_option(TagListConfig::LENGTH, "Show top "); $sb->add_int_option(TagListConfig::LENGTH, "Show top ");
$sb->add_label(" related tags"); $sb->add_label(" related tags");
$sb->add_int_option(TagListConfig::POPULAR_TAG_LIST_LENGTH, "<br>Show top "); $sb->add_int_option(TagListConfig::POPULAR_TAG_LIST_LENGTH, "<br>Show top ");
@ -129,7 +130,6 @@ class TagList extends Extension
); );
$sb->add_bool_option("tag_list_numbers", "Show tag counts", true); $sb->add_bool_option("tag_list_numbers", "Show tag counts", true);
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
/** /**
@ -441,7 +441,7 @@ class TagList extends Extension
$tags = $database->get_all($query, $args); $tags = $database->get_all($query, $args);
if (count($tags) > 0) { if (count($tags) > 0) {
$this->theme->display_related_block($page, $tags); $this->theme->display_related_block($page, $tags, "Related Tags");
} }
} }
@ -479,7 +479,7 @@ class TagList extends Extension
$tags = $database->get_all($query, $args); $tags = $database->get_all($query, $args);
if (count($tags) > 0) { if (count($tags) > 0) {
$this->theme->display_related_block($page, $tags); $this->theme->display_related_block($page, $tags, "Tags");
} }
} }

View file

@ -118,11 +118,7 @@ class TagListTheme extends Themelet
} }
if ($main_html != null) { if ($main_html != null) {
if ($config->get_string(TagListConfig::IMAGE_TYPE)==TagListConfig::TYPE_TAGS) { $page->add_block(new Block("Tags", $main_html, "left", 10));
$page->add_block(new Block("Tags", $main_html, "left", 10));
} else {
$page->add_block(new Block("Related Tags", $main_html, "left", 10));
}
} }
} }
@ -164,7 +160,7 @@ class TagListTheme extends Themelet
* ... * ...
* ) * )
*/ */
public function display_related_block(Page $page, $tag_infos) public function display_related_block(Page $page, $tag_infos, $block_name)
{ {
global $config; global $config;
@ -173,11 +169,7 @@ class TagListTheme extends Themelet
$config->get_string(TagListConfig::RELATED_SORT) $config->get_string(TagListConfig::RELATED_SORT)
); );
if ($config->get_string(TagListConfig::IMAGE_TYPE)==TagListConfig::TYPE_TAGS) { $page->add_block(new Block($block_name, $main_html, "left", 10));
$page->add_block(new Block("Tags", $main_html, "left", 10));
} else {
$page->add_block(new Block("Related Tags", $main_html, "left", 10));
}
} }

13
ext/tag_tools/info.php Normal file
View file

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
class TagToolsInfo extends ExtensionInfo
{
public const KEY = "tag_tools";
public $key = self::KEY;
public $name = "Tag Tools";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "Recount / Rename / Etc";
}

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

@ -0,0 +1,55 @@
<?php /** @noinspection PhpUnusedPrivateMethodInspection */
declare(strict_types=1);
class TagTools extends Extension
{
/** @var TagToolsTheme */
protected $theme;
public function onAdminBuilding(AdminBuildingEvent $event)
{
$this->theme->display_form();
}
public function onAdminAction(AdminActionEvent $event)
{
$action = $event->action;
if (method_exists($this, $action)) {
$event->redirect = $this->$action();
}
}
private function set_tag_case()
{
global $database;
$database->execute(
"UPDATE tags SET tag=:tag1 WHERE LOWER(tag) = LOWER(:tag2)",
["tag1" => $_POST['tag'], "tag2" => $_POST['tag']]
);
log_info("admin", "Fixed the case of {$_POST['tag']}", "Fixed case");
return true;
}
private function lowercase_all_tags()
{
global $database;
$database->execute("UPDATE tags SET tag=lower(tag)");
log_warning("admin", "Set all tags to lowercase", "Set all tags to lowercase");
return true;
}
private function recount_tag_use()
{
global $database;
$database->execute("
UPDATE tags
SET count = COALESCE(
(SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id),
0
)
");
$database->execute("DELETE FROM tags WHERE count=0");
log_warning("admin", "Re-counted tags", "Re-counted tags");
return true;
}
}

56
ext/tag_tools/test.php Normal file
View file

@ -0,0 +1,56 @@
<?php declare(strict_types=1);
class TagToolsTest extends ShimmiePHPUnitTestCase
{
public function testLowercaseAndSetCase()
{
// Create a problem
$ts = time(); // we need a tag that hasn't been used before
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "TeStCase$ts");
// Validate problem
$page = $this->get_page("post/view/$image_id_1");
$this->assertEquals("Post $image_id_1: TeStCase$ts", $page->title);
// Fix
send_event(new AdminActionEvent('lowercase_all_tags'));
// Validate fix
$this->get_page("post/view/$image_id_1");
$this->assert_title("Post $image_id_1: testcase$ts");
// Change
$_POST["tag"] = "TestCase$ts";
send_event(new AdminActionEvent('set_tag_case'));
// Validate change
$this->get_page("post/view/$image_id_1");
$this->assert_title("Post $image_id_1: TestCase$ts");
}
# FIXME: make sure the admin tools actually work
public function testRecount()
{
global $database;
// Create a problem
$ts = time(); // we need a tag that hasn't been used before
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
$database->execute(
"INSERT INTO tags(tag, count) VALUES(:tag, :count)",
["tag"=>"tes$ts", "count"=>42]
);
// Fix
send_event(new AdminActionEvent('recount_tag_use'));
// Validate fix
$this->assertEquals(
0,
$database->get_one(
"SELECT count FROM tags WHERE tag = :tag",
["tag"=>"tes$ts"]
)
);
}
}

42
ext/tag_tools/theme.php Normal file
View file

@ -0,0 +1,42 @@
<?php declare(strict_types=1);
use function MicroHTML\INPUT;
class TagToolsTheme extends Themelet
{
protected function button(string $name, string $action, bool $protected=false): string
{
$c_protected = $protected ? " protected" : "";
$html = make_form(make_link("admin/$action"), "POST", false, "admin$c_protected");
if ($protected) {
$html .= "<input type='submit' id='$action' value='$name' disabled='disabled'>";
$html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>";
} else {
$html .= "<input type='submit' id='$action' value='$name'>";
}
$html .= "</form>\n";
return $html;
}
/*
* Show a form which links to admin_utils with POST[action] set to one of:
* 'lowercase all tags'
* 'recount tag use'
* etc
*/
public function display_form()
{
global $page;
$html = "";
$html .= $this->button("All tags to lowercase", "lowercase_all_tags", true);
$html .= $this->button("Recount tag use", "recount_tag_use", false);
$page->add_block(new Block("Misc Admin Tools", $html));
$html = (string)SHM_SIMPLE_FORM(
"admin/set_tag_case",
INPUT(["type"=>'text', "name"=>'tag', "placeholder"=>'Enter tag with correct case', "class"=>'autocomplete_tags', "autocomplete"=>'off']),
SHM_SUBMIT('Set Tag Case'),
);
$page->add_block(new Block("Set Tag Case", $html));
}
}

View file

@ -25,7 +25,8 @@ class TranscodeImage extends Extension
"PPM" => MimeType::PPM, "PPM" => MimeType::PPM,
"PSD" => MimeType::PSD, "PSD" => MimeType::PSD,
"TIFF" => MimeType::TIFF, "TIFF" => MimeType::TIFF,
"WEBP" => MimeType::WEBP "WEBP" => MimeType::WEBP,
"TGA" => MimeType::TGA
]; ];
const OUTPUT_MIMES = [ const OUTPUT_MIMES = [
@ -141,7 +142,7 @@ class TranscodeImage extends Extension
$engine = $config->get_string(TranscodeConfig::ENGINE); $engine = $config->get_string(TranscodeConfig::ENGINE);
$sb = new SetupBlock("Image Transcode"); $sb = $event->panel->create_new_block("Image Transcode");
$sb->start_table(); $sb->start_table();
$sb->add_bool_option(TranscodeConfig::ENABLED, "Allow transcoding images", true); $sb->add_bool_option(TranscodeConfig::ENABLED, "Allow transcoding images", true);
$sb->add_bool_option(TranscodeConfig::GET_ENABLED, "Enable GET args", true); $sb->add_bool_option(TranscodeConfig::GET_ENABLED, "Enable GET args", true);
@ -156,7 +157,6 @@ class TranscodeImage extends Extension
$sb->add_int_option(TranscodeConfig::QUALITY, "Lossy Format Quality", true); $sb->add_int_option(TranscodeConfig::QUALITY, "Lossy Format Quality", true);
$sb->add_color_option(TranscodeConfig::ALPHA_COLOR, "Alpha Conversion Color", true); $sb->add_color_option(TranscodeConfig::ALPHA_COLOR, "Alpha Conversion Color", true);
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
public function onDataUpload(DataUploadEvent $event) public function onDataUpload(DataUploadEvent $event)

View file

@ -54,12 +54,13 @@ class TranscodeVideo extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Video Transcode"); global $config;
$sb = $event->panel->create_new_block("Video Transcode");
$sb->start_table(); $sb->start_table();
$sb->add_bool_option(TranscodeVideoConfig::ENABLED, "Allow transcoding images: ", true); $sb->add_bool_option(TranscodeVideoConfig::ENABLED, "Allow transcoding images: ", true);
$sb->add_bool_option(TranscodeVideoConfig::UPLOAD_TO_NATIVE_CONTAINER, "Convert videos using MPEG-4 or WEBM to their native containers:", true); $sb->add_bool_option(TranscodeVideoConfig::UPLOAD_TO_NATIVE_CONTAINER, "Convert videos using MPEG-4 or WEBM to their native containers:", true);
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
public function onDataUpload(DataUploadEvent $event) public function onDataUpload(DataUploadEvent $event)

View file

@ -15,9 +15,8 @@ class Update extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Update"); $sb = $event->panel->create_new_block("Update");
$sb->add_text_option("update_guserrepo", "User/Repo: "); $sb->add_text_option("update_guserrepo", "User/Repo: ");
$event->panel->add_block($sb);
} }
public function onAdminBuilding(AdminBuildingEvent $event) public function onAdminBuilding(AdminBuildingEvent $event)

View file

@ -132,7 +132,7 @@ class Upload extends Extension
$tes["fopen"] = "fopen"; $tes["fopen"] = "fopen";
$tes["WGet"] = "wget"; $tes["WGet"] = "wget";
$sb = new SetupBlock("Upload"); $sb = $event->panel->create_new_block("Upload");
$sb->position = 10; $sb->position = 10;
// Output the limits from PHP so the user has an idea of what they can set. // Output the limits from PHP so the user has an idea of what they can set.
$sb->add_int_option(UploadConfig::COUNT, "Max uploads: "); $sb->add_int_option(UploadConfig::COUNT, "Max uploads: ");
@ -141,7 +141,6 @@ class Upload extends Extension
$sb->add_label("<i>PHP Limit = " . ini_get('upload_max_filesize') . "</i>"); $sb->add_label("<i>PHP Limit = " . ini_get('upload_max_filesize') . "</i>");
$sb->add_choice_option(UploadConfig::TRANSLOAD_ENGINE, $tes, "<br/>Transload: "); $sb->add_choice_option(UploadConfig::TRANSLOAD_ENGINE, $tes, "<br/>Transload: ");
$sb->add_bool_option(UploadConfig::TLSOURCE, "<br/>Use transloaded URL as source if none is provided: "); $sb->add_bool_option(UploadConfig::TLSOURCE, "<br/>Use transloaded URL as source if none is provided: ");
$event->panel->add_block($sb);
} }

View file

@ -14,10 +14,21 @@ class UserBlockBuildingEvent extends Event
} }
} }
class UserOptionsBuildingEvent extends Event class UserOperationsBuildingEvent extends Event
{ {
/** @var array */ /** @var array */
public $parts = []; public $parts = [];
/** @var User */
public $user = [];
/** @var BaseConfig */
public $user_config = [];
public function __construct(User $user, BaseConfig $user_config)
{
parent::__construct();
$this->user = $user;
$this->user_config = $user_config;
}
public function add_html(string $html) public function add_html(string $html)
{ {

View file

@ -240,10 +240,11 @@ class UserPage extends Extension
if (!$user->is_anonymous()) { if (!$user->is_anonymous()) {
if ($user->id == $event->display_user->id || $user->can("edit_user_info")) { if ($user->id == $event->display_user->id || $user->can("edit_user_info")) {
$uobe = new UserOptionsBuildingEvent(); $user_config = UserConfig::get_for_user($event->display_user->id);
send_event($uobe);
$page->add_block(new Block("Options", $this->theme->build_options($event->display_user, $uobe), "main", 60)); $uobe = new UserOperationsBuildingEvent($event->display_user, $user_config);
send_event($uobe);
$page->add_block(new Block("Operations", $this->theme->build_operations($event->display_user, $uobe), "main", 60));
} }
} }
@ -275,7 +276,7 @@ class UserPage extends Extension
"Gravatar" => "gravatar" "Gravatar" => "gravatar"
]; ];
$sb = new SetupBlock("User Options"); $sb = $event->panel->create_new_block("User Options");
$sb->start_table(); $sb->start_table();
$sb->add_bool_option(UserConfig::ENABLE_API_KEYS, "Enable user API keys", true); $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("login_signup_enabled", "Allow new signups", true);
@ -316,8 +317,6 @@ class UserPage extends Extension
); );
} }
$sb->end_table(); $sb->end_table();
$event->panel->add_block($sb);
} }
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event) public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
@ -361,8 +360,8 @@ class UserPage extends Extension
} }
} }
public const USER_SEARCH_REGEX = "/^(?:poster|user)[=|:](.*)$/i"; public const USER_SEARCH_REGEX = "/^(?:poster|user)(!?)[=|:](.*)$/i";
public const USER_ID_SEARCH_REGEX = "/^(?:poster|user)_id[=|:]([0-9]+)$/i"; public const USER_ID_SEARCH_REGEX = "/^(?:poster|user)_id(!?)[=|:]([0-9]+)$/i";
public static function has_user_query(array $context): bool public static function has_user_query(array $context): bool
{ {
@ -385,11 +384,11 @@ class UserPage extends Extension
$matches = []; $matches = [];
if (preg_match(self::USER_SEARCH_REGEX, $event->term, $matches)) { if (preg_match(self::USER_SEARCH_REGEX, $event->term, $matches)) {
$user_id = User::name_to_id($matches[1]); $user_id = User::name_to_id($matches[2]);
$event->add_querylet(new Querylet("images.owner_id = $user_id")); $event->add_querylet(new Querylet("images.owner_id ${matches[1]}= $user_id"));
} elseif (preg_match(self::USER_ID_SEARCH_REGEX, $event->term, $matches)) { } elseif (preg_match(self::USER_ID_SEARCH_REGEX, $event->term, $matches)) {
$user_id = int_escape($matches[1]); $user_id = int_escape($matches[2]);
$event->add_querylet(new Querylet("images.owner_id = $user_id")); $event->add_querylet(new Querylet("images.owner_id ${matches[1]}= $user_id"));
} elseif ($user->can(Permissions::VIEW_IP) && preg_match("/^(?:poster|user)_ip[=|:]([0-9\.]+)$/i", $event->term, $matches)) { } elseif ($user->can(Permissions::VIEW_IP) && preg_match("/^(?:poster|user)_ip[=|:]([0-9\.]+)$/i", $event->term, $matches)) {
$user_ip = $matches[1]; // FIXME: ip_escape? $user_ip = $matches[1]; // FIXME: ip_escape?
$event->add_querylet(new Querylet("images.owner_ip = '$user_ip'")); $event->add_querylet(new Querylet("images.owner_ip = '$user_ip'"));

View file

@ -5,12 +5,12 @@ class UserPageTest extends ShimmiePHPUnitTestCase
{ {
$this->get_page('user'); $this->get_page('user');
$this->assert_title("Not Logged In"); $this->assert_title("Not Logged In");
$this->assert_no_text("Options"); $this->assert_no_text("Stats");
$this->assert_no_text("More Options");
$this->get_page('user/demo'); $this->get_page('user/demo');
$this->assert_title("demo's Page"); $this->assert_title("demo's Page");
$this->assert_text("Joined:"); $this->assert_text("Joined:");
$this->assert_no_text("Operations");
$this->get_page('user/MauMau'); $this->get_page('user/MauMau');
$this->assert_title("No Such User"); $this->assert_title("No Such User");
@ -19,7 +19,7 @@ class UserPageTest extends ShimmiePHPUnitTestCase
// should be on the user page // should be on the user page
$this->get_page('user/test'); $this->get_page('user/test');
$this->assert_title("test's Page"); $this->assert_title("test's Page");
$this->assert_text("Options"); $this->assert_text("Operations");
// FIXME: check class // FIXME: check class
//$this->assert_no_text("Admin:"); //$this->assert_no_text("Admin:");
$this->log_out(); $this->log_out();
@ -28,7 +28,7 @@ class UserPageTest extends ShimmiePHPUnitTestCase
// should be on the user page // should be on the user page
$this->get_page('user/demo'); $this->get_page('user/demo');
$this->assert_title("demo's Page"); $this->assert_title("demo's Page");
$this->assert_text("Options"); $this->assert_text("Operations");
// FIXME: check class // FIXME: check class
//$this->assert_text("Admin:"); //$this->assert_text("Admin:");
$this->log_out(); $this->log_out();

View file

@ -229,7 +229,8 @@ class UserPageTheme extends Themelet
$page->add_block(new Block("Stats", join("<br>", $stats), "main", 10)); $page->add_block(new Block("Stats", join("<br>", $stats), "main", 10));
} }
public function build_options(User $duser, UserOptionsBuildingEvent $event): string
public function build_operations(User $duser, UserOperationsBuildingEvent $event): string
{ {
global $config, $user; global $config, $user;
$html = emptyHTML(); $html = emptyHTML();

View file

@ -19,6 +19,27 @@ class InitUserConfigEvent extends Event
} }
} }
class UserOptionsBuildingEvent extends Event
{
/** @var SetupTheme */
protected $theme;
/** @var SetupPanel */
public $panel;
/** @var User */
public $user = [];
public function __construct(User $user, SetupPanel $panel)
{
parent::__construct();
$this->user = $user;
$this->panel = $panel;
}
}
class UserConfig extends Extension class UserConfig extends Extension
{ {
/** @var UserConfigTheme */ /** @var UserConfigTheme */
@ -36,10 +57,20 @@ class UserConfig extends Extension
public function onUserLogin(UserLoginEvent $event) public function onUserLogin(UserLoginEvent $event)
{ {
global $database, $user_config; global $user_config;
$user_config = new DatabaseConfig($database, "user_config", "user_id", "{$event->user->id}"); $user_config = self::get_for_user($event->user->id);
send_event(new InitUserConfigEvent($event->user, $user_config)); }
public static function get_for_user(int $id): BaseConfig
{
global $database;
$user = User::by_id($id);
$user_config = new DatabaseConfig($database, "user_config", "user_id", "$id");
send_event(new InitUserConfigEvent($user, $user_config));
return $user_config;
} }
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void
@ -60,9 +91,17 @@ class UserConfig extends Extension
} }
} }
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
{
global $user;
if ($event->parent==="user" && !$user->is_anonymous()) {
$event->add_nav_link("user_config", new Link('user_config'), "User Options", false, 40);
}
}
public function onPageRequest(PageRequestEvent $event) public function onPageRequest(PageRequestEvent $event)
{ {
global $user, $database, $config, $page; global $user, $database, $config, $page, $user_config;
if ($config->get_bool(self::ENABLE_API_KEYS)) { if ($config->get_bool(self::ENABLE_API_KEYS)) {
if (!empty($_GET["api_key"]) && $user->is_anonymous()) { if (!empty($_GET["api_key"]) && $user->is_anonymous()) {
@ -79,8 +118,6 @@ class UserConfig extends Extension
} }
} }
global $user_config;
if ($event->page_matches("user_admin")) { if ($event->page_matches("user_admin")) {
if (!$user->check_auth_token()) { if (!$user->check_auth_token()) {
return; return;
@ -96,22 +133,58 @@ class UserConfig extends Extension
} }
} }
} }
}
public function onUserOptionsBuilding(UserOptionsBuildingEvent $event) if ($event->page_matches("user_config")) {
{ if (!$user->can(Permissions::CHANGE_USER_SETTING)) {
global $config, $user_config; $this->theme->display_permission_denied();
} else {
if ($event->count_args() == 0) {
$display_user = ($event->count_args() == 0) ? $user : User::by_name($event->get_arg(0));
if ($config->get_bool(self::ENABLE_API_KEYS)) { if ($user->id!=$display_user->id && !$user->can(Permissions::CHANGE_OTHER_USER_SETTING)) {
$key = $user_config->get_string(self::API_KEY, ""); $this->theme->display_permission_denied();
if (empty($key)) { return;
$key = generate_key(); }
$user_config->set_string(self::API_KEY, $key);
$uobe = new UserOptionsBuildingEvent($display_user, new SetupPanel($user_config));
send_event($uobe);
$this->theme->display_user_config_page($page, $uobe->user, $uobe->panel);
} elseif ($event->get_arg(0) == "save" && $user->check_auth_token()) {
$input = validate_input([
'id' => 'user_id,exists'
]);
$duser = User::by_id($input['id']);
if ($user->id!=$duser->id && !$user->can(Permissions::CHANGE_OTHER_USER_SETTING)) {
$this->theme->display_permission_denied();
return;
}
$target_config = UserConfig::get_for_user($duser->id);
send_event(new ConfigSaveEvent($target_config));
$target_config->save();
$page->flash("Config saved");
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("user_config"));
}
} }
$event->add_html($this->theme->get_user_options($key));
} }
} }
public function onUserOperationsBuilding(UserOperationsBuildingEvent $event)
{
global $config;
if ($config->get_bool(self::ENABLE_API_KEYS)) {
$key = $event->user_config->get_string(self::API_KEY, "");
if (empty($key)) {
$key = generate_key();
$event->user_config->set_string(self::API_KEY, $key);
}
$event->add_html($this->theme->get_user_operations($key));
}
}
// This needs to happen before any other events, but after db upgrade // This needs to happen before any other events, but after db upgrade
public function get_priority(): int public function get_priority(): int

43
ext/user_config/style.css Normal file
View file

@ -0,0 +1,43 @@
.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;
}

17
ext/user_config/test.php Normal file
View file

@ -0,0 +1,17 @@
<?php declare(strict_types=1);
class UserConfigTest extends ShimmiePHPUnitTestCase
{
private const OPTIONS_BLOCK_TITLE = "User Options";
public function testUserConfigPage()
{
$this->get_page('user_config');
$this->assert_title("Permission Denied");
$this->assert_no_text(self::OPTIONS_BLOCK_TITLE);
$this->log_in_as_user();
$this->get_page('user_config');
$this->assert_title(self::OPTIONS_BLOCK_TITLE);
$this->log_out();
}
}

View file

@ -2,7 +2,7 @@
class UserConfigTheme extends Themelet class UserConfigTheme extends Themelet
{ {
public function get_user_options(string $key): string public function get_user_operations(string $key): string
{ {
$html = " $html = "
<p>".make_form(make_link("user_admin/reset_api_key"))." <p>".make_form(make_link("user_admin/reset_api_key"))."
@ -22,4 +22,57 @@ class UserConfigTheme extends Themelet
"; ";
return $html; return $html;
} }
/*
* Display a set of setup option blocks
*
* $panel = the container of the blocks
* $panel->blocks the blocks to be displayed, unsorted
*
* It's recommended that the theme sort the blocks before doing anything
* else, using: usort($panel->blocks, "blockcmp");
*
* The page should wrap all the options in a form which links to setup_save
*/
public function display_user_config_page(Page $page, User $user, SetupPanel $panel)
{
usort($panel->blocks, "blockcmp");
/*
* Try and keep the two columns even; count the line breaks in
* each an calculate where a block would work best
*/
$setupblock_html = "";
foreach ($panel->blocks as $block) {
$setupblock_html .= $this->sb_to_html($block);
}
$table = "
".make_form(make_link("user_config/save"))."
<input type='hidden' name='id' value='".$user->id."'>
<div class='setupblocks'>$setupblock_html</div>
<input type='submit' value='Save Settings'>
</form>
";
$page->set_title("User Options");
$page->set_heading("User Options");
$page->add_block(new Block("User Options", $table));
$page->set_mode(PageMode::PAGE);
}
protected function sb_to_html(SetupBlock $block)
{
$h = $block->header;
$b = $block->body;
$i = preg_replace('/[^a-zA-Z0-9]/', '_', $h) . "-setup";
$html = "
<section class='setupblock'>
<b class='shm-toggler' data-toggle-sel='#$i'>$h</b>
<br><div id='$i'>$b</div>
</section>
";
return $html;
}
} }

View file

@ -77,7 +77,13 @@ class ViewImage extends Extension
if (!$image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK)) { if (!$image->is_locked() || $user->can(Permissions::EDIT_IMAGE_LOCK)) {
send_event(new ImageInfoSetEvent($image)); send_event(new ImageInfoSetEvent($image));
$page->set_mode(PageMode::REDIRECT); $page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("post/view/$image_id", url_escape(@$_POST['query'])));
if (isset($_GET['search'])) {
$query = "search=" . url_escape($_GET['search']);
} else {
$query = null;
}
$page->set_redirect(make_link("post/view/$image_id", null, $query));
} else { } else {
$this->theme->display_error(403, "Post Locked", "An admin has locked this post"); $this->theme->display_error(403, "Post Locked", "An admin has locked this post");
} }

View file

@ -1,18 +1,29 @@
function joinUrlSegments(base, query) {
let separatorChar = "?";
if(base.includes("?")) {
separatorChar = "&";
}
return base + separatorChar + query;
}
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
if(document.location.hash.length > 3) { if(document.location.hash.length > 3) {
var query = document.location.hash.substring(1); var query = document.location.hash.substring(1);
$('LINK#prevlink').attr('href', function(i, attr) { $('LINK#prevlink').attr('href', function(i, attr) {
return attr + '?' + query; return joinUrlSegments(attr,query);
}); });
$('LINK#nextlink').attr('href', function(i, attr) { $('LINK#nextlink').attr('href', function(i, attr) {
return attr + '?' + query; return joinUrlSegments(attr,query);
}); });
$('A#prevlink').attr('href', function(i, attr) { $('A#prevlink').attr('href', function(i, attr) {
return attr + '?' + query; return joinUrlSegments(attr,query);
}); });
$('A#nextlink').attr('href', function(i, attr) { $('A#nextlink').attr('href', function(i, attr) {
return attr + '?' + query; return joinUrlSegments(attr,query);
}); });
$('span#image_delete_form form').attr('action', function(i, attr) {
return joinUrlSegments(attr,query);
});
} }
}); });

View file

@ -101,6 +101,7 @@ abstract class WikiConfig
const TAG_PAGE_TEMPLATE = "wiki_tag_page_template"; const TAG_PAGE_TEMPLATE = "wiki_tag_page_template";
const EMPTY_TAGINFO = "wiki_empty_taginfo"; const EMPTY_TAGINFO = "wiki_empty_taginfo";
const TAG_SHORTWIKIS = "shortwikis_on_tags"; const TAG_SHORTWIKIS = "shortwikis_on_tags";
const ENABLE_REVISIONS = "wiki_revisions";
} }
class Wiki extends Extension class Wiki extends Extension
@ -118,21 +119,17 @@ class Wiki extends Extension
[b]Auto tags: [/b][i]{autotags}[/i]"); [b]Auto tags: [/b][i]{autotags}[/i]");
$config->set_default_string(WikiConfig::EMPTY_TAGINFO, "none"); $config->set_default_string(WikiConfig::EMPTY_TAGINFO, "none");
$config->set_default_bool(WikiConfig::TAG_SHORTWIKIS, false); $config->set_default_bool(WikiConfig::TAG_SHORTWIKIS, false);
$config->set_default_bool(WikiConfig::ENABLE_REVISIONS, true);
} }
// Add a block to the Board Config / Setup // Add a block to the Board Config / Setup
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Wiki"); $sb = $event->panel->create_new_block("Wiki");
$sb->add_bool_option(WikiConfig::ENABLE_REVISIONS, "Enable wiki revisions: ");
$sb->start_table(); $sb->add_longtext_option(WikiConfig::TAG_PAGE_TEMPLATE, "Tag page template: ");
$sb->add_longtext_option(WikiConfig::TAG_PAGE_TEMPLATE, "Tag page template", true); $sb->add_text_option(WikiConfig::EMPTY_TAGINFO, "Empty list text: ");
$sb->add_text_option(WikiConfig::EMPTY_TAGINFO, "Empty list text", true);
$sb->end_table();
$sb->add_bool_option(WikiConfig::TAG_SHORTWIKIS, "Show shortwiki entry when searching for a single tag: "); $sb->add_bool_option(WikiConfig::TAG_SHORTWIKIS, "Show shortwiki entry when searching for a single tag: ");
$event->panel->add_block($sb);
} }
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event) public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
@ -243,16 +240,29 @@ class Wiki extends Extension
public function onWikiUpdate(WikiUpdateEvent $event) public function onWikiUpdate(WikiUpdateEvent $event)
{ {
global $database; global $database, $config;
$wpage = $event->wikipage; $wpage = $event->wikipage;
$exists = $database->exists("SELECT id FROM wiki_pages WHERE title = :title", ["title"=>$wpage->title]);
try { try {
$database->execute( if ($config->get_bool(WikiConfig::ENABLE_REVISIONS) || ! $exists) {
" $database->execute(
INSERT INTO wiki_pages(owner_id, owner_ip, date, title, revision, locked, body) "
VALUES (:owner_id, :owner_ip, now(), :title, :revision, :locked, :body)", INSERT INTO wiki_pages(owner_id, owner_ip, date, title, revision, locked, body)
["owner_id"=>$event->user->id, "owner_ip"=>$_SERVER['REMOTE_ADDR'], VALUES (:owner_id, :owner_ip, now(), :title, :revision, :locked, :body)",
"title"=>$wpage->title, "revision"=>$wpage->revision, "locked"=>$wpage->locked, "body"=>$wpage->body] ["owner_id"=>$event->user->id, "owner_ip"=>$_SERVER['REMOTE_ADDR'],
); "title"=>$wpage->title, "revision"=>$wpage->revision, "locked"=>$wpage->locked, "body"=>$wpage->body]
);
} else {
$database->execute(
"
UPDATE wiki_pages SET owner_id=:owner_id, owner_ip=:owner_ip, date=now(), locked=:locked, body=:body
WHERE title = :title ORDER BY revision DESC LIMIT 1",
["owner_id"=>$event->user->id, "owner_ip"=>$_SERVER['REMOTE_ADDR'],
"title"=>$wpage->title, "locked"=>$wpage->locked, "body"=>$wpage->body]
);
}
} catch (Exception $e) { } catch (Exception $e) {
throw new WikiUpdateException("Somebody else edited that page at the same time :-("); throw new WikiUpdateException("Somebody else edited that page at the same time :-(");
} }

View file

@ -16,10 +16,9 @@ class WordFilter extends Extension
public function onSetupBuilding(SetupBuildingEvent $event) public function onSetupBuilding(SetupBuildingEvent $event)
{ {
$sb = new SetupBlock("Word Filter"); $sb = $event->panel->create_new_block("Word Filter");
$sb->add_longtext_option("word_filter"); $sb->add_longtext_option("word_filter");
$sb->add_label("<br>(each line should be search term and replace term, separated by a comma)"); $sb->add_label("<br>(each line should be search term and replace term, separated by a comma)");
$event->panel->add_block($sb);
} }
private function filter(string $text): string private function filter(string $text): string

View file

@ -0,0 +1,42 @@
<?php declare(strict_types=1);
/**
* Class CustomSetupTheme
*
* A customised version of the Setup theme.
*
*/
class CustomUserConfigTheme extends UserConfigTheme
{
protected function sb_to_html(SetupBlock $block)
{
$h = $block->header;
$b = $block->body;
$i = preg_replace('/[^a-zA-Z0-9]/', '_', $h) . "-setup";
$html = "
<script type='text/javascript'><!--
document.addEventListener('DOMContentLoaded', () => {
$(\"#$i-toggle\").click(function() {
$(\"#$i\").slideToggle(\"slow\", function() {
if($(\"#$i\").is(\":hidden\")) {
Cookies.set(\"$i-hidden\", 'true', {path: '/'});
}
else {
Cookies.set(\"$i-hidden\", 'false', {path: '/'});
}
});
});
if(Cookies.get(\"$i-hidden\") == 'true') {
$(\"#$i\").hide();
}
});
//--></script>
<div class='setupblock'>
<b id='$i-toggle'>$h</b>
<br><div id='$i'>$b</div>
</div>
";
return $this->rr($html);
}
}

View file

@ -1,7 +1,7 @@
<table class="headbox"> <table class="headbox">
<tr> <tr>
<td colspan="4" id="big-logo"> <td colspan="4" id="big-logo">
<a class="vis-desktop" href="//rule34.paheal.net/post/list"><img alt="logo" src="//rule34.paheal.net/themes/rule34v2/rule34_logo_top.png" style="width: 240px; height: 104px;"/></a> <a class="vis-desktop" href="//rule34.paheal.net/post/list"><img alt="logo" src="//rule34.paheal.net/themes/rule34v2/rule34_logo_top.png" style="height: 104px;"/></a>
</td> </td>
</tr> </tr>