Merge branch 'master' into wiki-toggle-revisions

This commit is contained in:
Shish 2021-01-20 08:10:19 +00:00 committed by GitHub
commit 2025acd482
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 2907 additions and 528 deletions

View file

@ -53,9 +53,9 @@
},
"require-dev" : {
"phpunit/phpunit" : "^9.0"
"phpunit/phpunit" : "^9.0",
"friendsofphp/php-cs-fixer" : "*"
},
"suggest": {
"ext-memcache": "memcache caching",
"ext-memcached": "memcached caching",

2178
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'>".
"</a>\n";
}

View file

@ -483,6 +483,7 @@ class Image
WHERE image_id=:id
ORDER BY tag
", ["id"=>$this->id]);
sort($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 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 MANAGE_EXTENSION_LIST = "manage_extension_list";
@ -100,6 +103,7 @@ abstract class Permissions
public const SET_PRIVATE_IMAGE = "set_private_image";
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_EXPORT = "bulk_export";
public const BULK_DOWNLOAD = "bulk_download";

View file

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

View file

@ -41,9 +41,8 @@ class ApprovalTheme extends Themelet
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: ");
$event->panel->add_block($sb);
}
public function display_admin_form()

View file

@ -55,7 +55,7 @@ xanax
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_longtext_option("banned_words");
$failed = [];
@ -69,7 +69,6 @@ xanax
if ($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 $description = "Turns BBCode into HTML";
public $documentation =
" Supported tags:
" Basic formatting tags:
<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>[i]<i>italic</i>[/i]
<li>[u]<u>underline</u>[/u]
<li>[s]<s>strikethrough</s>[/s]
<li>[sup]<sup>superscript</sup>[/sup]
<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|with some text]]
<li>[quote]text[/quote]
<li>[quote=Username]text[/quote]
<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>";
}

View file

@ -40,11 +40,10 @@ class Blotter extends Extension
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_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: ");
$event->panel->add_block($sb);
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)

View file

@ -78,8 +78,7 @@ class BrowserSearch extends Extension
$sort_by['Tag Count'] = 't';
$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:");
$event->panel->add_block($sb);
}
}

View file

@ -127,8 +127,8 @@ class BulkActions extends Extension
switch ($event->action) {
case "bulk_delete":
if ($user->can(Permissions::DELETE_IMAGE)) {
$i = $this->delete_items($event->items);
$page->flash("Deleted $i items");
$i = $this->delete_posts($event->items);
$page->flash("Deleted $i[0] items, totaling ".human_filesize($i[1]));
}
break;
case "bulk_tag":
@ -227,25 +227,27 @@ class BulkActions extends Extension
return $a["position"] - $b["position"];
}
private function delete_items(iterable $items): int
private function delete_posts(iterable $posts): array
{
global $page;
$total = 0;
foreach ($items as $image) {
$size = 0;
foreach ($posts as $post) {
try {
if (class_exists("ImageBan") && isset($_POST['bulk_ban_reason'])) {
$reason = $_POST['bulk_ban_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++;
$size += $post->filesize;
} 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

View file

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

View file

@ -379,7 +379,7 @@ class CommentList extends Extension
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_label("<br>Limit to ");
$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("<br>Make samefags public ");
$sb->add_bool_option("comment_samefags_public");
$event->panel->add_block($sb);
}
public function onSearchTermParse(SearchTermParseEvent $event)

View file

@ -5,66 +5,8 @@ abstract class CronUploaderConfig
{
public const DEFAULT_PATH = "cron_uploader";
public const KEY = "cron_uploader_key";
public const DIR = "cron_uploader_dir";
public const USER = "cron_uploader_user";
public const STOP_ON_ERROR = "cron_uploader_stop_on_error";
public const INCLUDE_ALL_LOGS = "cron_uploader_include_all_logs";
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;
public function onInitExt(InitExtEvent $event)
public function onInitUserConfig(InitUserConfigEvent $event)
{
// Set default values
CronUploaderConfig::set_defaults();
$event->user_config->set_default_string(
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)
{
if ($event->parent=="system") {
@ -39,42 +67,14 @@ class CronUploader extends Extension
global $user;
if ($event->page_matches("cron_upload")) {
if ($event->count_args() == 1) {
$this->process_upload($event->get_arg(0)); // Start upload
} elseif ($user->can(Permissions::CRON_ADMIN)) {
if ($event->count_args() == 1 && $event->get_arg(0) =="run") {
$this->process_upload(); // Start upload
} elseif ($user->can(Permissions::CRON_RUN)) {
$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)
{
$failed_dir = $this->get_failed_dir();
@ -118,19 +118,20 @@ class CronUploader extends Extension
public function onLog(LogEvent $event)
{
global $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 ;
global $user_config;
echo $output . "\r\n";
flush_output();
if (self::$IMPORT_RUNNING) {
$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();
file_put_contents($log_path, $output);
echo $output . "\r\n";
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)
{
global $page;
$path = join_path(CronUploaderConfig::get_dir(), $folder);
global $page, $user_config;
$path = join_path($user_config->get_string(CronUploaderConfig::DIR), $folder);
deltree($path);
$page->flash("Cleared $path");
}
@ -202,7 +203,11 @@ class CronUploader extends Extension
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()
@ -258,26 +263,34 @@ class CronUploader extends Extension
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);
}
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);
}
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);
}
private function prep_root_dir(): string
{
global $user_config;
// Determine directory (none = default)
$dir = CronUploaderConfig::get_dir();
$dir = $user_config->get_string(CronUploaderConfig::DIR);
// Make the directory if it doesn't exist yet
if (!is_dir($this->get_queue_dir())) {
@ -295,35 +308,36 @@ class CronUploader extends Extension
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");
}
/**
* 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;
$this->set_headers();
if ($key!=CronUploaderConfig::get_key()) {
throw new SCoreException("Cron upload key incorrect");
}
$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");
if (!$config->get_bool(UserConfig::ENABLE_API_KEYS)) {
throw new SCoreException("User API keys are note enabled. Please enable them for the cron upload functionality to work.");
}
send_event(new UserLoginEvent($my_user));
$this->log_message(SCORE_LOG_INFO, "Logged in as user {$my_user->name}");
if ($user->is_anonymous()) {
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");
if (!flock($lockfile, LOCK_EX | LOCK_NB)) {
@ -335,7 +349,7 @@ class CronUploader extends Extension
//set_time_limit(0);
$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
//shuffle($this->image_queue);
@ -349,6 +363,9 @@ class CronUploader extends Extension
$execution_time = microtime(true) - $_shm_load_start;
if ($execution_time>$max_time) {
break;
} else {
$remaining = $max_time - $execution_time;
$this->log_message(SCORE_LOG_DEBUG, "Max run time remaining: $remaining");
}
try {
$database->begin_transaction();
@ -374,7 +391,7 @@ class CronUploader extends Extension
$failed++;
$this->log_message(SCORE_LOG_ERROR, "(" . gettype($e) . ") " . $e->getMessage());
$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;
} else {
$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)
{
$rootDir = CronUploaderConfig::get_dir();
global $user_config;
$rootDir = $user_config->get_string(CronUploaderConfig::DIR);
$rootLength = strlen($rootDir);
if ($rootDir[$rootLength-1]=="/"||$rootDir[$rootLength-1]=="\\") {
$rootLength--;
@ -540,7 +559,11 @@ class CronUploader extends Extension
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

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);
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
{
public function display_documentation(
@ -11,11 +24,19 @@ class CronUploaderTheme extends Themelet
string $cron_url,
?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>
<table style='width:470px;'>
" . ($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>
</tr></table>
<br>Cron Command: <input type='text' size='60' value='$cron_cmd'><br>
Create a cron job with the command above.<br/>
Read the documentation if you're not sure what to do.<br>";
<div>Cron Command: <input type='text' size='60' value='$cron_cmd' id='cron_command'>
<button onclick='copyInputToClipboard(\"cron_command\")'>Copy</button></div>
<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 = "
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>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>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>
";
$page->set_title("Cron Uploader");
$page->set_heading("Cron Uploader");
$block = new Block("Cron Uploader", $info_html, "main", 10);
$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)
{
global $page;

View file

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

View file

@ -12,10 +12,9 @@ class Downtime extends Extension
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_longtext_option("downtime_message", "<br>");
$event->panel->add_block($sb);
}
public function onPageRequest(PageRequestEvent $event)

View file

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

View file

@ -62,13 +62,12 @@ class Forum extends Extension
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("forumThreadsPerPage", "<br>Threads per page: ");
$sb->add_int_option("forumPostsPerPage", "<br>Posts per page: ");
$sb->add_int_option("forumMaxCharsPerPost", "<br>Max chars per post: ");
$event->panel->add_block($sb);
}
public function onUserPageBuilding(UserPageBuildingEvent $event)

View file

@ -5,10 +5,9 @@ class GoogleAnalytics extends Extension
# Add analytics to config
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_label("<br>(eg. UA-xxxxxxxx-x)");
$event->panel->add_block($sb);
}
# Load Analytics tracking code on page request

View file

@ -12,11 +12,10 @@ class ArchiveFileHandler extends DataHandlerExtension
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_extract_command", "<br>Extraction command: ");
$sb->add_label("<br>%f for archive, %d for temporary directory");
$event->panel->add_block($sb);
}
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' ".
"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));
}
}

View file

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

View file

@ -13,9 +13,8 @@ class Holiday extends Extension
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");
$event->panel->add_block($sb);
}
public function onPageRequest(PageRequestEvent $event)

View file

@ -27,11 +27,10 @@ class Home extends Extension
$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_text", "<br>Page Text:<br>");
$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;
$sb = new SetupBlock("Post Options");
$sb = $event->panel->create_new_block("Post Options");
$sb->start_table();
$sb->position = 30;
// advanced only
@ -270,9 +270,8 @@ class ImageIO extends Extension
$sb->add_bool_option(ImageConfig::SHOW_META, "Show metadata", true);
}
$sb->end_table();
$event->panel->add_block($sb);
$sb = new SetupBlock("Thumbnailing");
$sb = $event->panel->create_new_block("Thumbnailing");
$sb->start_table();
$sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine", true);
$sb->add_choice_option(ImageConfig::THUMB_MIME, self::THUMBNAIL_TYPES, "Filetype", true);
@ -294,8 +293,6 @@ class ImageIO extends Extension
}
$sb->end_table();
$event->panel->add_block($sb);
}
public function onParseLinkTemplate(ParseLinkTemplateEvent $event)

View file

@ -9,11 +9,11 @@ class ImageIOTheme extends Themelet
*/
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",
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;
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)
{
global $database, $user;

View file

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

View file

@ -209,7 +209,7 @@ class IPBan extends Extension
{
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)');
if ($config->get_string("ipban_message_ghost")) {
$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")) {
$sb->add_longtext_option("ipban_message_anon-ghost", 'Message to show to ghost anons:');
}
$event->panel->add_block($sb);
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)

View file

@ -13,9 +13,8 @@ class LinkImage extends Extension
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: ");
$event->panel->add_block($sb);
}
public function onInitExt(InitExtEvent $event)

View file

@ -4,9 +4,8 @@ class LiveFeed extends Extension
{
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: ");
$event->panel->add_block($sb);
}
public function onUserCreation(UserCreationEvent $event)

View file

@ -235,7 +235,7 @@ class LogDatabase extends Extension
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", [
LOGGING_LEVEL_NAMES[SCORE_LOG_DEBUG] => SCORE_LOG_DEBUG,
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_CRITICAL] => SCORE_LOG_CRITICAL,
], "Debug Level: ");
$event->panel->add_block($sb);
}
public function onPageRequest(PageRequestEvent $event)

View file

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

View file

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

View file

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

View file

@ -184,6 +184,11 @@ class MimeMap
self::MAP_EXT => [FileExtension::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 => [
self::MAP_NAME => "Text",
self::MAP_EXT => [FileExtension::TEXT, FileExtension::ASC],

View file

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

View file

@ -177,7 +177,7 @@ class Pools extends Extension
// Add a block to the Board Config / Setup
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::IMAGES_PER_PAGE, "<br>Posts 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::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: ");
$event->panel->add_block($sb);
}
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)
{
$sb = new SetupBlock("Post Titles");
$sb = $event->panel->create_new_block("Post Titles");
$sb->start_table();
$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->end_table();
$event->panel->add_block($sb);
}
public function onBulkExport(BulkExportEvent $event)

View file

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

View file

@ -36,31 +36,4 @@ class PrivateImageTheme extends Themelet
</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)
{
$sb = new SetupBlock("Random Post");
$sb = $event->panel->create_new_block("Random Post");
$sb->add_bool_option("show_random_block", "Show Random Block: ");
$event->panel->add_block($sb);
}
public function onPostListBuilding(PostListBuildingEvent $event)

View file

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

View file

@ -143,15 +143,19 @@ class Ratings extends Extension
public function onUserOptionsBuilding(UserOptionsBuildingEvent $event)
{
global $user;
global $user, $_shm_ratings;
$event->add_html(
$this->theme->get_user_options(
$user,
self::get_user_default_ratings($user),
self::get_user_class_privs($user)
)
);
$levels = self::get_user_class_privs($user);
$options = [];
foreach ($levels as $level) {
$options[$_shm_ratings[$level]->name] = $level;
}
$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)
@ -165,7 +169,7 @@ class Ratings extends Extension
$options[$rating->name] = $rating->code;
}
$sb = new SetupBlock("Post Ratings");
$sb = $event->panel->create_new_block("Post Ratings");
$sb->start_table();
foreach (array_keys($_shm_user_classes) as $key) {
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->end_table();
$event->panel->add_block($sb);
}
public function onDisplayingImage(DisplayingImageEvent $event)
@ -417,33 +419,6 @@ class Ratings extends Extension
$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
@ -464,9 +439,9 @@ class Ratings extends Extension
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);
$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'>
<table style='width: 300px;'>
<thead>
<tr><th colspan='2'>Default Rating Filter</th></tr>
<tr><th colspan='2'></th></tr>
</thead>
<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>

View file

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

View file

@ -58,7 +58,7 @@ class ResolutionLimit extends Extension
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = new SetupBlock("Resolution Limits");
$sb = $event->panel->create_new_block("Resolution Limits");
$sb->add_label("Min ");
$sb->add_int_option("upload_min_width");
@ -77,7 +77,5 @@ class ResolutionLimit extends Extension
$sb->add_label("<br>Ratios ");
$sb->add_text_option("upload_ratios");
$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)
{
$sb = new SetupBlock("Image Resize");
$sb = $event->panel->create_new_block("Image Resize");
$sb->start_table();
$sb->add_choice_option(ResizeConfig::ENGINE, MediaEngine::IMAGE_ENGINES, "Engine", 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("<tr><td></td><td>(enter 0 for no default)</td></tr>");
$sb->end_table();
$event->panel->add_block($sb);
}
public function onDataUpload(DataUploadEvent $event)

View file

@ -38,12 +38,11 @@ class RotateImage extends Extension
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_label("<br>Default Orientation: ");
$sb->add_int_option("rotate_default_deg");
$sb->add_label(" deg");
$event->panel->add_block($sb);
}
public function onPageRequest(PageRequestEvent $event)

View file

@ -40,10 +40,19 @@ class SetupPanel
{
/** @var SetupBlock[] */
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;
return $block;
}
}
@ -53,10 +62,13 @@ class SetupBlock extends Block
public $header;
/** @var string */
public $body;
/** @var BaseConfig */
public $config;
public function __construct(string $title)
public function __construct(string $title, BaseConfig $config)
{
parent::__construct($title, "", "main", 50);
$this->config = $config;
}
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)
{
global $config;
$val = html_escape($config->get_string($name));
$val = html_escape($this->config->get_string($name));
$html = "<input type='text' id='{$name}' name='_config_{$name}' value='{$val}'>\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)
{
global $config;
$val = html_escape($config->get_string($name));
$val = html_escape($this->config->get_string($name));
$rows = max(3, min(10, count(explode("\n", $val))));
$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)
{
global $config;
$checked = $config->get_bool($name) ? " checked" : "";
$checked = $this->config->get_bool($name) ? " checked" : "";
$html = "";
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)
{
global $config;
$val = $config->get_int($name);
$val = $this->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='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)
{
global $config;
$val = to_shorthand_int($config->get_int($name));
$val = to_shorthand_int($this->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='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)
{
global $config;
if (is_int(array_values($options)[0])) {
$current = $config->get_int($name);
$current = $this->config->get_int($name);
} else {
$current = $config->get_string($name);
$current = $this->config->get_string($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)
{
global $config;
$current = $config->get_array($name);
$current = $this->config->get_array($name);
$html = "<select id='$name' name='_config_{$name}[]' multiple size='5'>";
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)
{
global $config;
$val = html_escape($config->get_string($name));
$val = html_escape($this->config->get_string($name));
$html = "<input type='color' id='{$name}' name='_config_{$name}' value='{$val}'>\n";
$html .= "<input type='hidden' name='_type_{$name}' value='string'>\n";
@ -320,7 +324,7 @@ class Setup extends Extension
$this->theme->display_permission_denied();
} else {
if ($event->count_args() == 0) {
$panel = new SetupPanel();
$panel = new SetupPanel($config);
send_event(new SetupBuildingEvent($panel));
$this->theme->display_page($page, $panel);
} elseif ($event->get_arg(0) == "save" && $user->check_auth_token()) {
@ -370,7 +374,7 @@ class Setup extends Extension
}
});
</script>";
$sb = new SetupBlock("General");
$sb = $event->panel->create_new_block("General");
$sb->position = 0;
$sb->add_text_option(SetupConfig::TITLE, "Site title: ");
$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_bool_option("nice_urls", "<br>Nice URLs: ");
$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_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_text_option("api_recaptcha_privkey", "<br>Secret key: ");
$sb->add_text_option("api_recaptcha_pubkey", "<br>Site key: ");
$event->panel->add_block($sb);
}
public function onConfigSave(ConfigSaveEvent $event)
{
global $config;
$config = $event->config;
foreach ($_POST as $_name => $junk) {
if (substr($_name, 0, 6) == "_type_") {
$name = substr($_name, 6);

View file

@ -8,7 +8,7 @@ class SetupTheme extends Themelet
* $panel = the container of the blocks
* $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");
*
* 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)
{
$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_keywords", "<br>Keywords: ");
$event->panel->add_block($sb);
}
}

View file

@ -28,13 +28,11 @@ class XMLSitemap extends Extension
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_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)");
$event->panel->add_block($sb);
}
// 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
// they /really/ want to
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_int_option("history_limit");
$sb->add_label(" entires per image");
$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'];
$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_choice_option("tageditcloud_sort", $sort_by, "<br>Sort the tags by:");
$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("<br><b>Relevance sort</b>:<br>Ignore tags (space separated): ");
$sb->add_text_option("tageditcloud_ignoretags");
$event->panel->add_block($sb);
}
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
// they /really/ want to
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_int_option("history_limit");
$sb->add_label(" entires per image");
$sb->add_label("<br>(-1 for unlimited)");
$event->panel->add_block($sb);
}
*/

View file

@ -97,13 +97,12 @@ class TagList extends Extension
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_label(" times");
$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_label(" related tags");
$sb->add_int_option(TagListConfig::POPULAR_TAG_LIST_LENGTH, "<br>Show top ");
@ -131,7 +130,6 @@ class TagList extends Extension
);
$sb->add_bool_option("tag_list_numbers", "Show tag counts", true);
$sb->end_table();
$event->panel->add_block($sb);
}
/**

View file

@ -6,36 +6,6 @@ class TagTools extends Extension
/** @var TagToolsTheme */
protected $theme;
public function onPageRequest(PageRequestEvent $event)
{
global $database, $page, $user;
if ($event->page_matches("admin")) {
if (!$user->can(Permissions::MANAGE_ADMINTOOLS)) {
$this->theme->display_permission_denied();
} else {
if ($event->count_args() == 0) {
send_event(new AdminBuildingEvent($page));
} else {
$action = $event->get_arg(0);
$aae = new AdminActionEvent($action);
if ($user->check_auth_token()) {
log_info("admin", "Util: $action");
set_time_limit(0);
$database->set_timeout(300000);
send_event($aae);
}
if ($aae->redirect) {
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("admin"));
}
}
}
}
}
public function onAdminBuilding(AdminBuildingEvent $event)
{
$this->theme->display_form();

View file

@ -25,7 +25,8 @@ class TranscodeImage extends Extension
"PPM" => MimeType::PPM,
"PSD" => MimeType::PSD,
"TIFF" => MimeType::TIFF,
"WEBP" => MimeType::WEBP
"WEBP" => MimeType::WEBP,
"TGA" => MimeType::TGA
];
const OUTPUT_MIMES = [
@ -141,7 +142,7 @@ class TranscodeImage extends Extension
$engine = $config->get_string(TranscodeConfig::ENGINE);
$sb = new SetupBlock("Image Transcode");
$sb = $event->panel->create_new_block("Image Transcode");
$sb->start_table();
$sb->add_bool_option(TranscodeConfig::ENABLED, "Allow transcoding images", 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_color_option(TranscodeConfig::ALPHA_COLOR, "Alpha Conversion Color", true);
$sb->end_table();
$event->panel->add_block($sb);
}
public function onDataUpload(DataUploadEvent $event)

View file

@ -54,12 +54,13 @@ class TranscodeVideo extends Extension
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = new SetupBlock("Video Transcode");
global $config;
$sb = $event->panel->create_new_block("Video Transcode");
$sb->start_table();
$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->end_table();
$event->panel->add_block($sb);
}
public function onDataUpload(DataUploadEvent $event)

View file

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

View file

@ -132,7 +132,7 @@ class Upload extends Extension
$tes["fopen"] = "fopen";
$tes["WGet"] = "wget";
$sb = new SetupBlock("Upload");
$sb = $event->panel->create_new_block("Upload");
$sb->position = 10;
// Output the limits from PHP so the user has an idea of what they can set.
$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_choice_option(UploadConfig::TRANSLOAD_ENGINE, $tes, "<br/>Transload: ");
$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 */
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)
{

View file

@ -240,10 +240,11 @@ class UserPage extends Extension
if (!$user->is_anonymous()) {
if ($user->id == $event->display_user->id || $user->can("edit_user_info")) {
$uobe = new UserOptionsBuildingEvent();
send_event($uobe);
$user_config = UserConfig::get_for_user($event->display_user->id);
$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"
];
$sb = new SetupBlock("User Options");
$sb = $event->panel->create_new_block("User Options");
$sb->start_table();
$sb->add_bool_option(UserConfig::ENABLE_API_KEYS, "Enable user API keys", true);
$sb->add_bool_option("login_signup_enabled", "Allow new signups", true);
@ -316,8 +317,6 @@ class UserPage extends Extension
);
}
$sb->end_table();
$event->panel->add_block($sb);
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
@ -361,8 +360,8 @@ class UserPage extends Extension
}
}
public const USER_SEARCH_REGEX = "/^(?:poster|user)[=|:](.*)$/i";
public const USER_ID_SEARCH_REGEX = "/^(?:poster|user)_id[=|:]([0-9]+)$/i";
public const USER_SEARCH_REGEX = "/^(?:poster|user)(!?)[=|:](.*)$/i";
public const USER_ID_SEARCH_REGEX = "/^(?:poster|user)_id(!?)[=|:]([0-9]+)$/i";
public static function has_user_query(array $context): bool
{
@ -385,11 +384,11 @@ class UserPage extends Extension
$matches = [];
if (preg_match(self::USER_SEARCH_REGEX, $event->term, $matches)) {
$user_id = User::name_to_id($matches[1]);
$event->add_querylet(new Querylet("images.owner_id = $user_id"));
$user_id = User::name_to_id($matches[2]);
$event->add_querylet(new Querylet("images.owner_id ${matches[1]}= $user_id"));
} elseif (preg_match(self::USER_ID_SEARCH_REGEX, $event->term, $matches)) {
$user_id = int_escape($matches[1]);
$event->add_querylet(new Querylet("images.owner_id = $user_id"));
$user_id = int_escape($matches[2]);
$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)) {
$user_ip = $matches[1]; // FIXME: ip_escape?
$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->assert_title("Not Logged In");
$this->assert_no_text("Options");
$this->assert_no_text("More Options");
$this->assert_no_text("Stats");
$this->get_page('user/demo');
$this->assert_title("demo's Page");
$this->assert_text("Joined:");
$this->assert_no_text("Operations");
$this->get_page('user/MauMau');
$this->assert_title("No Such User");
@ -19,7 +19,7 @@ class UserPageTest extends ShimmiePHPUnitTestCase
// should be on the user page
$this->get_page('user/test');
$this->assert_title("test's Page");
$this->assert_text("Options");
$this->assert_text("Operations");
// FIXME: check class
//$this->assert_no_text("Admin:");
$this->log_out();
@ -28,7 +28,7 @@ class UserPageTest extends ShimmiePHPUnitTestCase
// should be on the user page
$this->get_page('user/demo');
$this->assert_title("demo's Page");
$this->assert_text("Options");
$this->assert_text("Operations");
// FIXME: check class
//$this->assert_text("Admin:");
$this->log_out();

View file

@ -229,7 +229,8 @@ class UserPageTheme extends Themelet
$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;
$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
{
/** @var UserConfigTheme */
@ -36,10 +57,20 @@ class UserConfig extends Extension
public function onUserLogin(UserLoginEvent $event)
{
global $database, $user_config;
global $user_config;
$user_config = new DatabaseConfig($database, "user_config", "user_id", "{$event->user->id}");
send_event(new InitUserConfigEvent($event->user, $user_config));
$user_config = self::get_for_user($event->user->id);
}
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
@ -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)
{
global $user, $database, $config, $page;
global $user, $database, $config, $page, $user_config;
if ($config->get_bool(self::ENABLE_API_KEYS)) {
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 (!$user->check_auth_token()) {
return;
@ -96,22 +133,58 @@ class UserConfig extends Extension
}
}
}
}
public function onUserOptionsBuilding(UserOptionsBuildingEvent $event)
{
global $config, $user_config;
if ($event->page_matches("user_config")) {
if (!$user->can(Permissions::CHANGE_USER_SETTING)) {
$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)) {
$key = $user_config->get_string(self::API_KEY, "");
if (empty($key)) {
$key = generate_key();
$user_config->set_string(self::API_KEY, $key);
if ($user->id!=$display_user->id && !$user->can(Permissions::CHANGE_OTHER_USER_SETTING)) {
$this->theme->display_permission_denied();
return;
}
$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
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
{
public function get_user_options(string $key): string
public function get_user_operations(string $key): string
{
$html = "
<p>".make_form(make_link("user_admin/reset_api_key"))."
@ -22,4 +22,57 @@ class UserConfigTheme extends Themelet
";
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)) {
send_event(new ImageInfoSetEvent($image));
$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 {
$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', () => {
if(document.location.hash.length > 3) {
var query = document.location.hash.substring(1);
$('LINK#prevlink').attr('href', function(i, attr) {
return attr + '?' + query;
return joinUrlSegments(attr,query);
});
$('LINK#nextlink').attr('href', function(i, attr) {
return attr + '?' + query;
return joinUrlSegments(attr,query);
});
$('A#prevlink').attr('href', function(i, attr) {
return attr + '?' + query;
return joinUrlSegments(attr,query);
});
$('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

@ -117,11 +117,9 @@ class Wiki extends Extension
// Add a block to the Board Config / Setup
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->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)

View file

@ -16,10 +16,9 @@ class WordFilter extends Extension
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_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

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">
<tr>
<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>
</tr>