diff --git a/composer.lock b/composer.lock
index 3183e5b35..e67148368 100644
--- a/composer.lock
+++ b/composer.lock
@@ -62,16 +62,16 @@
},
{
"name": "aws/aws-sdk-php",
- "version": "3.295.4",
+ "version": "3.295.5",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "2372661db989fe4229abd95f4434b37252076d58"
+ "reference": "cd9d48ebfdfc8fb5f6df9fe95dced622287f3412"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2372661db989fe4229abd95f4434b37252076d58",
- "reference": "2372661db989fe4229abd95f4434b37252076d58",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/cd9d48ebfdfc8fb5f6df9fe95dced622287f3412",
+ "reference": "cd9d48ebfdfc8fb5f6df9fe95dced622287f3412",
"shasum": ""
},
"require": {
@@ -151,16 +151,16 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.295.4"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.295.5"
},
- "time": "2023-12-29T19:07:49+00:00"
+ "time": "2024-01-03T19:12:43+00:00"
},
{
"name": "bower-asset/jquery",
"version": "1.12.4",
"source": {
"type": "git",
- "url": "https://github.com/jquery/jquery-dist.git",
+ "url": "git@github.com:jquery/jquery-dist.git",
"reference": "5e89585e0121e72ff47de177c5ef604f3089a53d"
},
"dist": {
@@ -1322,21 +1322,21 @@
},
{
"name": "shish/ffsphp",
- "version": "v1.3.0",
+ "version": "v1.3.2",
"source": {
"type": "git",
"url": "https://github.com/shish/ffsphp.git",
- "reference": "26eea8149fda5f20bed7399b8b4a84946448bec0"
+ "reference": "d69223f4317de302b6cd485d0a43709788dd6f69"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/shish/ffsphp/zipball/26eea8149fda5f20bed7399b8b4a84946448bec0",
- "reference": "26eea8149fda5f20bed7399b8b4a84946448bec0",
+ "url": "https://api.github.com/repos/shish/ffsphp/zipball/d69223f4317de302b6cd485d0a43709788dd6f69",
+ "reference": "d69223f4317de302b6cd485d0a43709788dd6f69",
"shasum": ""
},
"require": {
"ext-pdo": "*",
- "php": "^8.0"
+ "php": "^8.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "3.41.1",
@@ -1365,9 +1365,9 @@
"homepage": "https://github.com/shish/ffsphp",
"support": {
"issues": "https://github.com/shish/ffsphp/issues",
- "source": "https://github.com/shish/ffsphp/tree/v1.3.0"
+ "source": "https://github.com/shish/ffsphp/tree/v1.3.2"
},
- "time": "2024-01-01T14:48:00+00:00"
+ "time": "2024-01-04T18:38:54+00:00"
},
{
"name": "shish/gqla",
@@ -1589,12 +1589,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
+ "reference": "2c438b99bb2753c1628c1e6f523991edea5b03a4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
- "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/2c438b99bb2753c1628c1e6f523991edea5b03a4",
+ "reference": "2c438b99bb2753c1628c1e6f523991edea5b03a4",
"shasum": ""
},
"require": {
@@ -1604,7 +1604,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.4-dev"
+ "dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -1633,7 +1633,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
+ "source": "https://github.com/symfony/deprecation-contracts/tree/main"
},
"funding": [
{
@@ -1649,7 +1649,7 @@
"type": "tidelift"
}
],
- "time": "2023-05-23T14:45:45+00:00"
+ "time": "2024-01-02T14:07:37+00:00"
},
{
"name": "symfony/polyfill-mbstring",
@@ -2418,12 +2418,12 @@
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/serializer.git",
- "reference": "4ff99e4cbba44cdbe57028ec2ccc874c8f61bbe9"
+ "reference": "39096dd64dd6c66afc7eb7f294918243fd80d383"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/4ff99e4cbba44cdbe57028ec2ccc874c8f61bbe9",
- "reference": "4ff99e4cbba44cdbe57028ec2ccc874c8f61bbe9",
+ "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/39096dd64dd6c66afc7eb7f294918243fd80d383",
+ "reference": "39096dd64dd6c66afc7eb7f294918243fd80d383",
"shasum": ""
},
"require": {
@@ -2446,6 +2446,7 @@
"phpstan/phpstan": "^1.0.2",
"phpunit/phpunit": "^9.0 || ^10.0",
"psr/container": "^1.0 || ^2.0",
+ "rector/rector": "^0.18.13",
"symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0",
"symfony/expression-language": "^5.4 || ^6.0 || ^7.0",
"symfony/filesystem": "^5.4 || ^6.0 || ^7.0",
@@ -2507,7 +2508,7 @@
"type": "github"
}
],
- "time": "2023-12-22T14:51:09+00:00"
+ "time": "2024-01-03T21:21:26+00:00"
},
{
"name": "myclabs/deep-copy",
@@ -2826,16 +2827,16 @@
},
{
"name": "phpstan/phpdoc-parser",
- "version": "1.24.5",
+ "version": "1.25.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc"
+ "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fedf211ff14ec8381c9bf5714e33a7a552dd1acc",
- "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bd84b629c8de41aa2ae82c067c955e06f1b00240",
+ "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240",
"shasum": ""
},
"require": {
@@ -2867,9 +2868,9 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.5"
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.25.0"
},
- "time": "2023-12-16T09:33:33+00:00"
+ "time": "2024-01-04T17:06:16+00:00"
},
{
"name": "phpstan/phpstan",
@@ -4716,12 +4717,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
- "reference": "a76aed96a42d2b521153fb382d418e30d18b59df"
+ "reference": "705c57c64120840dc3043ef1d43916f46af4f986"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df",
- "reference": "a76aed96a42d2b521153fb382d418e30d18b59df",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/705c57c64120840dc3043ef1d43916f46af4f986",
+ "reference": "705c57c64120840dc3043ef1d43916f46af4f986",
"shasum": ""
},
"require": {
@@ -4732,7 +4733,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.4-dev"
+ "dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -4769,7 +4770,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0"
+ "source": "https://github.com/symfony/event-dispatcher-contracts/tree/main"
},
"funding": [
{
@@ -4785,7 +4786,7 @@
"type": "tidelift"
}
],
- "time": "2023-05-23T14:45:45+00:00"
+ "time": "2024-01-02T14:07:37+00:00"
},
{
"name": "symfony/filesystem",
@@ -5462,12 +5463,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
- "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0"
+ "reference": "cea2eccfcd27ac3deb252bd67f78b9b8ffc4da84"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0",
- "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/cea2eccfcd27ac3deb252bd67f78b9b8ffc4da84",
+ "reference": "cea2eccfcd27ac3deb252bd67f78b9b8ffc4da84",
"shasum": ""
},
"require": {
@@ -5481,7 +5482,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.4-dev"
+ "dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -5521,7 +5522,7 @@
"standards"
],
"support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.4.1"
+ "source": "https://github.com/symfony/service-contracts/tree/main"
},
"funding": [
{
@@ -5537,7 +5538,7 @@
"type": "tidelift"
}
],
- "time": "2023-12-26T14:02:43+00:00"
+ "time": "2024-01-02T14:07:37+00:00"
},
{
"name": "symfony/stopwatch",
diff --git a/core/database.php b/core/database.php
index 5d53ae698..e09161c25 100644
--- a/core/database.php
+++ b/core/database.php
@@ -7,6 +7,8 @@
use FFSPHP\PDO;
use FFSPHP\PDOStatement;
+require_once __DIR__ . '/exceptions.php';
+
enum DatabaseDriverID: string
{
case MYSQL = "mysql";
@@ -14,6 +16,20 @@ enum DatabaseDriverID: string
case SQLITE = "sqlite";
}
+class DatabaseException extends SCoreException
+{
+ public string $query;
+ public array $args;
+
+ public function __construct(string $msg, string $query, array $args)
+ {
+ parent::__construct($msg);
+ $this->error = $msg;
+ $this->query = $query;
+ $this->args = $args;
+ }
+}
+
/**
* A class for controlled database access
*/
@@ -158,18 +174,13 @@ public function notify(string $channel, ?string $data = null): void
public function _execute(string $query, array $args = []): PDOStatement
{
try {
- $ret = $this->get_db()->execute(
+ return $this->get_db()->execute(
"-- " . str_replace("%2F", "/", urlencode($_GET['q'] ?? '')). "\n" .
$query,
$args
);
- if ($ret === false) {
- throw new SCoreException("Query failed", $query);
- }
- /** @noinspection PhpIncompatibleReturnTypeInspection */
- return $ret;
} catch (\PDOException $pdoe) {
- throw new SCoreException($pdoe->getMessage(), $query);
+ throw new DatabaseException($pdoe->getMessage(), $query, $args);
}
}
diff --git a/core/exceptions.php b/core/exceptions.php
index 139b86700..9683d33cf 100644
--- a/core/exceptions.php
+++ b/core/exceptions.php
@@ -9,15 +9,13 @@
*/
class SCoreException extends \RuntimeException
{
- public ?string $query;
public string $error;
public int $http_code = 500;
- public function __construct(string $msg, ?string $query = null)
+ public function __construct(string $msg)
{
parent::__construct($msg);
$this->error = $msg;
- $this->query = $query;
}
}
diff --git a/core/util.php b/core/util.php
index 62081a4df..432b89951 100644
--- a/core/util.php
+++ b/core/util.php
@@ -627,8 +627,6 @@ function _fatal_error(\Exception $e): void
$version = VERSION;
$message = $e->getMessage();
$phpver = phpversion();
- $query = is_subclass_of($e, "Shimmie2\SCoreException") ? $e->query : null;
- $code = is_subclass_of($e, "Shimmie2\SCoreException") ? $e->http_code : 500;
//$hash = exec("git rev-parse HEAD");
//$h_hash = $hash ? "
Hash: $hash" : "";
@@ -646,13 +644,21 @@ function _fatal_error(\Exception $e): void
print("Message: $message\n");
- if ($query) {
- print("Query: {$query}\n");
+ if (is_a($e, DatabaseException::class)) {
+ print("Query: {$e->query}\n");
+ print("Args: ".var_export($e->args, true)."\n");
}
print("Version: $version (on $phpver)\n");
} else {
- $q = $query ? "" : "
Query: " . html_escape($query);
+ $query = is_a($e, DatabaseException::class) ? $e->query : null;
+ $code = is_a($e, SCoreException::class) ? $e->http_code : 500;
+
+ $q = "";
+ if(is_a($e, DatabaseException::class)) {
+ $q .= "
Query: " . html_escape($query);
+ $q .= "
Args: " . html_escape(var_export($e->args, true));
+ }
if ($code >= 500) {
error_log("Shimmie Error: $message (Query: $query)\n{$e->getTraceAsString()}");
}
diff --git a/ext/cron_uploader/main.php b/ext/cron_uploader/main.php
index 1f97a0f91..de6a0de9d 100644
--- a/ext/cron_uploader/main.php
+++ b/ext/cron_uploader/main.php
@@ -464,7 +464,7 @@ private function move_uploaded(string $path, string $filename, string $output_su
/**
* Generate the necessary DataUploadEvent for a given image and tags.
*/
- private function add_image(string $tmpname, string $filename, string $tags): DataUploadEvent
+ private function add_image(string $tmpname, string $filename, array $tags): DataUploadEvent
{
$event = add_image($tmpname, $filename, $tags, null);
@@ -512,7 +512,7 @@ private function generate_image_queue(): \Generator
foreach (new \RecursiveIteratorIterator($ite) as $fullpath => $cur) {
if (!is_link($fullpath) && !is_dir($fullpath) && !$this->is_skippable_file($fullpath)) {
$relativePath = substr($fullpath, strlen($base));
- $tags = Tag::implode(path_to_tags($relativePath));
+ $tags = path_to_tags($relativePath);
yield [
0 => $fullpath,
diff --git a/ext/cron_uploader/theme.php b/ext/cron_uploader/theme.php
index 977f40f34..6d89890c5 100644
--- a/ext/cron_uploader/theme.php
+++ b/ext/cron_uploader/theme.php
@@ -34,7 +34,7 @@ public function display_documentation(
$page->set_heading("Cron Uploader");
if (!$config->get_bool(UserConfig::ENABLE_API_KEYS)) {
- $info_html .= "THIS EXTENSION REQUIRES USER API KEYS TO BE ENABLED IN BOARD ADMIN";
+ $info_html .= "THIS EXTENSION REQUIRES USER API KEYS TO BE ENABLED IN BOARD ADMIN
";
}
$info_html .= "Information
diff --git a/ext/forum/main.php b/ext/forum/main.php
index 4bba03985..060735a7f 100644
--- a/ext/forum/main.php
+++ b/ext/forum/main.php
@@ -111,10 +111,10 @@ public function onPageRequest(PageRequestEvent $event)
case "view":
$threadID = int_escape($event->get_arg(1));
// $pageNumber = int_escape($event->get_arg(2));
- list($errors) = $this->sanity_check_viewed_thread($threadID);
+ $errors = $this->sanity_check_viewed_thread($threadID);
- if ($errors != null) {
- $this->theme->display_error(500, "Error", $errors);
+ if (count($errors) > 0) {
+ $this->theme->display_error(500, "Error", implode("
", $errors));
break;
}
@@ -133,10 +133,10 @@ public function onPageRequest(PageRequestEvent $event)
case "create":
$redirectTo = "forum/index";
if (!$user->is_anonymous()) {
- list($errors) = $this->sanity_check_new_thread();
+ $errors = $this->sanity_check_new_thread();
- if ($errors != null) {
- $this->theme->display_error(500, "Error", $errors);
+ if (count($errors) > 0) {
+ $this->theme->display_error(500, "Error", implode("
", $errors));
break;
}
@@ -174,10 +174,10 @@ public function onPageRequest(PageRequestEvent $event)
$threadID = int_escape($_POST["threadID"]);
$total_pages = $this->get_total_pages_for_thread($threadID);
if (!$user->is_anonymous()) {
- list($errors) = $this->sanity_check_new_post();
+ $errors = $this->sanity_check_new_post();
- if ($errors != null) {
- $this->theme->display_error(500, "Error", $errors);
+ if (count($errors) > 0) {
+ $this->theme->display_error(500, "Error", implode("
", $errors));
break;
}
$this->save_new_post($threadID, $user);
@@ -197,63 +197,69 @@ public function onPageRequest(PageRequestEvent $event)
private function get_total_pages_for_thread(int $threadID): int
{
global $database, $config;
- $result = $database->get_row("SELECT COUNT(1) AS count FROM forum_posts WHERE thread_id = :thread_id", ['thread_id' => $threadID]);
+ $result = $database->get_row("
+ SELECT COUNT(1) AS count
+ FROM forum_posts
+ WHERE thread_id = :thread_id
+ ", ['thread_id' => $threadID]);
return (int)ceil($result["count"] / $config->get_int("forumPostsPerPage"));
}
private function sanity_check_new_thread(): array
{
- $errors = null;
+ $errors = [];
if (!array_key_exists("title", $_POST)) {
- $errors .= "
No title supplied.
";
+ $errors[] = "No title supplied.";
} elseif (strlen($_POST["title"]) == 0) {
- $errors .= "You cannot have an empty title.
";
- } elseif (strlen(html_escape($_POST["title"])) > 255) {
- $errors .= "Your title is too long.
";
+ $errors[] = "You cannot have an empty title.";
+ } elseif (strlen($_POST["title"]) > 255) {
+ $errors[] = "Your title is too long.";
}
if (!array_key_exists("message", $_POST)) {
- $errors .= "No message supplied.
";
+ $errors[] = "No message supplied.";
} elseif (strlen($_POST["message"]) == 0) {
- $errors .= "You cannot have an empty message.
";
+ $errors[] = "You cannot have an empty message.";
}
- return [$errors];
+ return $errors;
}
private function sanity_check_new_post(): array
{
- $errors = null;
+ $errors = [];
if (!array_key_exists("threadID", $_POST)) {
- $errors = "No thread ID supplied.
";
+ $errors[] = "No thread ID supplied.";
} elseif (strlen($_POST["threadID"]) == 0) {
- $errors = "No thread ID supplied.
";
+ $errors[] = "No thread ID supplied.";
} elseif (is_numeric($_POST["threadID"])) {
if (!array_key_exists("message", $_POST)) {
- $errors .= "No message supplied.
";
+ $errors[] = "No message supplied.";
} elseif (strlen($_POST["message"]) == 0) {
- $errors .= "You cannot have an empty message.
";
+ $errors[] = "You cannot have an empty message.";
}
}
- return [$errors];
+ return $errors;
}
+ /**
+ * @return string[]
+ */
private function sanity_check_viewed_thread(int $threadID): array
{
- $errors = null;
+ $errors = [];
if (!$this->threadExists($threadID)) {
- $errors = "Inexistent thread.
";
+ $errors[] = "Inexistent thread.";
}
- return [$errors];
+ return $errors;
}
private function get_thread_title(int $threadID): string
{
global $database;
- $result = $database->get_row("SELECT t.title FROM forum_threads AS t WHERE t.id = :id ", ['id' => $threadID]);
- return $result["title"];
+ return $database->get_one("SELECT t.title FROM forum_threads AS t WHERE t.id = :id ", ['id' => $threadID]);
}
private function show_last_threads(Page $page, PageRequestEvent $event, bool $showAdminOptions = false): void
@@ -312,7 +318,7 @@ private function show_posts(PageRequestEvent $event, bool $showAdminOptions = fa
private function save_new_thread(User $user): int
{
- $title = html_escape($_POST["title"]);
+ $title = $_POST["title"];
$sticky = !empty($_POST["sticky"]);
global $database;
@@ -336,7 +342,7 @@ private function save_new_post(int $threadID, User $user): void
{
global $config;
$userID = $user->id;
- $message = html_escape($_POST["message"]);
+ $message = $_POST["message"];
$max_characters = $config->get_int('forumMaxCharsPerPost');
$message = substr($message, 0, $max_characters);
diff --git a/ext/forum/theme.php b/ext/forum/theme.php
index 93def4775..2d49021dc 100644
--- a/ext/forum/theme.php
+++ b/ext/forum/theme.php
@@ -4,6 +4,10 @@
namespace Shimmie2;
+use MicroHTML\HTMLElement;
+
+use function MicroHTML\{INPUT, LABEL, SMALL, TEXTAREA, TR, TD, TABLE, TH, TBODY, THEAD, DIV, A, BR, emptyHTML, SUP, rawHTML};
+
class ForumTheme extends Themelet
{
public function display_thread_list(Page $page, $threads, $showAdminOptions, $pageNumber, $totalPages)
@@ -27,29 +31,41 @@ public function display_new_thread_composer(Page $page, $threadText = null, $thr
{
global $config, $user;
$max_characters = $config->get_int('forumMaxCharsPerPost');
- $html = make_form(make_link("forum/create"));
-
-
- if (!is_null($threadTitle)) {
- $threadTitle = html_escape($threadTitle);
- }
- if (!is_null($threadText)) {
- $threadText = html_escape($threadText);
- }
-
- $html .= "
-
-
- ";
+ $html = SHM_SIMPLE_FORM(
+ "forum/create",
+ TABLE(
+ ["style" => "width: 500px;"],
+ TR(
+ TD("Title:"),
+ TD(INPUT(["type" => "text", "name" => "title", "value" => $threadTitle]))
+ ),
+ TR(
+ TD("Message:"),
+ TD(TEXTAREA(
+ ["id" => "message", "name" => "message"],
+ $threadText
+ ))
+ ),
+ TR(
+ TD(),
+ TD(SMALL("Max characters allowed: $max_characters."))
+ ),
+ $user->can(Permissions::FORUM_ADMIN) ? TR(
+ TD(),
+ TD(
+ LABEL(["for" => "sticky"], "Sticky:"),
+ INPUT(["name" => "sticky", "id" => "sticky", "type" => "checkbox", "value" => "Y"])
+ )
+ ) : null,
+ TR(
+ TD(
+ ["colspan" => 2],
+ INPUT(["type" => "submit", "value" => "Submit"])
+ )
+ )
+ )
+ );
$blockTitle = "Write a new thread";
$page->set_title(html_escape($blockTitle));
@@ -65,20 +81,27 @@ public function display_new_post_composer(Page $page, $threadID)
$max_characters = $config->get_int('forumMaxCharsPerPost');
- $html = make_form(make_link("forum/answer"));
-
- $html .= '';
-
- $html .= "
-
-
- ";
+ $html = SHM_SIMPLE_FORM(
+ "forum/answer",
+ INPUT(["type" => "hidden", "name" => "threadID", "value" => $threadID]),
+ TABLE(
+ ["style" => "width: 500px;"],
+ TR(
+ TD("Message:"),
+ TD(TEXTAREA(["id" => "message", "name" => "message"]))
+ ),
+ TR(
+ TD(),
+ TD(SMALL("Max characters allowed: $max_characters."))
+ ),
+ TR(
+ TD(
+ ["colspan" => 2],
+ INPUT(["type" => "submit", "value" => "Submit"])
+ )
+ )
+ )
+ );
$blockTitle = "Answer to this thread";
$page->add_block(new Block($blockTitle, $html, "main", 130));
@@ -94,68 +117,70 @@ public function display_thread($posts, $showAdminOptions, $threadTitle, $threadI
$current_post = 0;
- $html =
- "
".
- "".
- "".
- "User | ".
- "Message | ".
- "
";
-
+ $tbody = TBODY();
foreach ($posts as $post) {
$current_post++;
- $message = $post["message"];
-
- $message = send_event(new TextFormattingEvent($message))->formatted;
- $message = str_replace('\n\r', '
', $message);
- $message = str_replace('\r\n', '
', $message);
- $message = str_replace('\n', '
', $message);
- $message = str_replace('\r', '
', $message);
-
- $message = stripslashes($message);
-
- $userLink = "".$post["user_name"]."";
-
- $poster = User::by_name($post["user_name"]);
- $gravatar = $poster->get_avatar_html();
-
- $rank = "{$post["user_class"]}";
-
- $postID = $post['id'];
-
- //if($user->can(Permissions::FORUM_ADMIN)){
- //$delete_link = "Delete";
- //} else {
- //$delete_link = "";
- //}
-
- if ($showAdminOptions) {
- $delete_link = "Delete";
- } else {
- $delete_link = "";
- }
$post_number = (($pageNumber - 1) * $posts_per_page) + $current_post;
- $html .= "
-
- |
- ".$delete_link." |
-
-
- ".$userLink." ".$rank." ".$gravatar."
|
-
- ".autodate($post['date'])."
- #".$post_number."
-
- ".$message." |
-
- ";
+ $tbody->appendChild(
+ emptyHTML(
+ TR(
+ ["class" => "postHead"],
+ TD(["class" => "forumSupuser"]),
+ TD(
+ ["class" => "forumSupmessage"],
+ DIV(
+ ["class" => "deleteLink"],
+ $showAdminOptions ? A(["href" => make_link("forum/delete/".$threadID."/".$post['id'])], "Delete") : null
+ )
+ )
+ ),
+ TR(
+ ["class" => "posBody"],
+ TD(
+ ["class" => "forumUser"],
+ A(["href" => make_link("user/".$post["user_name"])], $post["user_name"]),
+ BR(),
+ SUP(["class" => "user_rank"], $post["user_class"]),
+ BR(),
+ rawHTML(User::by_name($post["user_name"])->get_avatar_html()),
+ BR()
+ ),
+ TD(
+ ["class" => "forumMessage"],
+ DIV(["class" => "postDate"], SMALL(rawHTML(autodate($post['date'])))),
+ DIV(["class" => "postNumber"], " #".$post_number),
+ BR(),
+ DIV(["class" => "postMessage"], rawHTML(send_event(new TextFormattingEvent($post["message"]))->formatted))
+ )
+ ),
+ TR(
+ ["class" => "postFoot"],
+ TD(["class" => "forumSubuser"]),
+ TD(["class" => "forumSubmessage"])
+ )
+ )
+ );
}
- $html .= "
";
+ $html = emptyHTML(
+ DIV(
+ ["id" => "returnLink"],
+ A(["href" => make_link("forum/index/")], "Return")
+ ),
+ BR(),
+ BR(),
+ TABLE(
+ ["id" => "threadPosts", "class" => "zebra"],
+ THEAD(
+ TR(
+ TH(["id" => "threadHeadUser"], "User"),
+ TH("Message")
+ )
+ ),
+ $tbody
+ )
+ );
$this->display_paginator($page, "forum/view/".$threadID, null, $pageNumber, $totalPages);
@@ -164,37 +189,32 @@ public function display_thread($posts, $showAdminOptions, $threadTitle, $threadI
$page->add_block(new Block($threadTitle, $html, "main", 20));
}
-
-
public function add_actions_block(Page $page, $threadID)
{
- $html = 'Delete this thread and its posts.';
-
+ $html = A(["href" => make_link("forum/nuke/".$threadID)], "Delete this thread and its posts.");
$page->add_block(new Block("Admin Actions", $html, "main", 140));
}
-
-
- private function make_thread_list($threads, $showAdminOptions): string
+ private function make_thread_list($threads, $showAdminOptions): HTMLElement
{
- $html = "".
- "".
- "Title | ".
- "Author | ".
- "Updated | ".
- "Responses | ";
-
- if ($showAdminOptions) {
- $html .= "Actions | ";
- }
+ global $config;
- $html .= "
";
+ $tbody = TBODY();
+ $html = TABLE(
+ ["id" => "threadList", "class" => "zebra"],
+ THEAD(
+ TR(
+ TH("Title"),
+ TH("Author"),
+ TH("Updated"),
+ TH("Responses"),
+ $showAdminOptions ? TH("Actions") : null
+ )
+ ),
+ $tbody
+ );
- $current_post = 0;
foreach ($threads as $thread) {
- $oe = ($current_post++ % 2 == 0) ? "even" : "odd";
-
- global $config;
$titleSubString = $config->get_int('forumTitleSubString');
if ($titleSubString < strlen($thread["title"])) {
@@ -204,27 +224,23 @@ private function make_thread_list($threads, $showAdminOptions): string
$title = $thread["title"];
}
- if (bool_escape($thread["sticky"])) {
- $sticky = "Sticky: ";
- } else {
- $sticky = "";
- }
-
- $html .= "".
- ''.$sticky.''.$title." | ".
- ''.$thread["user_name"]." | ".
- "".autodate($thread["uptodate"])." | ".
- "".$thread["response_count"]." | ";
-
- if ($showAdminOptions) {
- $html .= 'Delete | ';
- }
-
- $html .= "
";
+ $tbody->appendChild(
+ TR(
+ TD(
+ ["class" => "left"],
+ bool_escape($thread["sticky"]) ? "Sticky: " : "",
+ A(["href" => make_link("forum/view/".$thread["id"])], $title)
+ ),
+ TD(
+ A(["href" => make_link("user/".$thread["user_name"])], $thread["user_name"])
+ ),
+ TD(rawHTML(autodate($thread["uptodate"]))),
+ TD($thread["response_count"]),
+ $showAdminOptions ? TD(A(["href" => make_link("forum/nuke/".$thread["id"])], "Delete")) : null
+ )
+ );
}
- $html .= "
";
-
return $html;
}
}
diff --git a/ext/notes/border-h.gif b/ext/notes/border-h.gif
deleted file mode 100644
index a2aa5b0d0..000000000
Binary files a/ext/notes/border-h.gif and /dev/null differ
diff --git a/ext/notes/border-v.gif b/ext/notes/border-v.gif
deleted file mode 100644
index 4bfd55564..000000000
Binary files a/ext/notes/border-v.gif and /dev/null differ
diff --git a/ext/notes/lib/jquery.imgareaselect-1.0.0-rc1.min.js b/ext/notes/lib/jquery.imgareaselect-1.0.0-rc1.min.js
deleted file mode 100644
index c842914da..000000000
--- a/ext/notes/lib/jquery.imgareaselect-1.0.0-rc1.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! imgareaselect 1.0.0-rc.1 */
-(function(e){function t(){return e("")}var o=Math.abs,n=Math.max,i=Math.min,s=Math.round;e.imgAreaSelect=function(a,c){function r(e){return e+bt.left-St.left}function d(e){return e+bt.top-St.top}function u(e){return e-bt.left+St.left}function h(e){return e-bt.top+St.top}function f(e){var t,o=m(e)||e;return(t=parseInt(o.pageX))?t-St.left:void 0}function l(e){var t,o=m(e)||e;return(t=parseInt(o.pageY))?t-St.top:void 0}function m(e){var t=e.originalEvent||{};return t.touches&&t.touches.length?t.touches[0]:!1}function p(e){var t=e||G,o=e||J;return{x1:s(At.x1*t),y1:s(At.y1*o),x2:s(At.x2*t)-1,y2:s(At.y2*o)-1,width:s(At.x2*t)-s(At.x1*t),height:s(At.y2*o)-s(At.y1*o)}}function v(e,t,o,n,i){var a=i||G,c=i||J;At={x1:s(e/a||0),y1:s(t/c||0),x2:s(++o/a||0),y2:s(++n/c||0)},At.width=At.x2-At.x1,At.height=At.y2-At.y1}function y(){$&&pt.width()&&(bt={left:s(pt.offset().left),top:s(pt.offset().top)},Q=pt.innerWidth(),R=pt.innerHeight(),bt.top+=pt.outerHeight()-R>>1,bt.left+=pt.outerWidth()-Q>>1,V=s(c.minWidth/G)||0,Z=s(c.minHeight/J)||0,_=s(i(c.maxWidth/G||1<<24,Q)),et=s(i(c.maxHeight/J||1<<24,R)),St="fixed"==kt?{left:e(document).scrollLeft(),top:e(document).scrollTop()}:/static|^$/.test(X.css("position"))?{left:0,top:0}:{left:s(X.offset().left)-X.scrollLeft(),top:s(X.offset().top)-X.scrollTop()},L=r(0),j=d(0),(At.x2>Q||At.y2>R)&&P())}function g(t){if(ot){switch(vt.css({left:r(At.x1),top:d(At.y1)}).add(yt).width(ft=At.width).height(lt=At.height),yt.add(gt).add(wt).css({left:0,top:0}),gt.add(xt).width(n(ft-gt.outerWidth()+gt.innerWidth(),0)).height(n(lt-gt.outerHeight()+gt.innerHeight(),0)),xt.css({left:L,top:j,width:ft,height:lt,borderStyle:"solid",borderWidth:At.y1+"px "+(Q-At.x2)+"px "+(R-At.y2)+"px "+At.x1+"px"}),ft-=wt.outerWidth(),lt-=wt.outerHeight(),wt.length){case 8:e(wt[4]).css({left:ft>>1}),e(wt[5]).css({left:ft,top:lt>>1}),e(wt[6]).css({left:ft>>1,top:lt}),e(wt[7]).css({top:lt>>1});case 4:wt.slice(1,3).css({left:ft}),wt.slice(2,4).css({top:lt})}t!==!1&&(e.imgAreaSelect.keyPress!=Pt&&e(document).unbind(e.imgAreaSelect.keyPress,e.imgAreaSelect.onKeyPress),c.keys&&e(document)[e.imgAreaSelect.keyPress](e.imgAreaSelect.onKeyPress=Pt))}}function x(e){y(),g(e),nt=r(At.x1),it=d(At.y1),st=r(At.x2),at=d(At.y2)}function w(e,t){c.fadeDuration?e.fadeOut(c.fadeDuration,t):e.hide()}function b(e){return ct&&!/^touch/.test(e.type)}function S(e){var t=u(f(e))-At.x1,o=h(l(e))-At.y1;U="",c.resizable&&(c.resizeMargin>=o?U="n":o>=At.height-c.resizeMargin&&(U="s"),c.resizeMargin>=t?U+="w":t>=At.width-c.resizeMargin&&(U+="e")),vt.css("cursor",U?U+"-resize":c.movable?"move":"")}function z(e){b(e)||(mt||(y(),mt=!0,vt.one("mouseout",function(){mt=!1})),S(e))}function k(t){ct=!1,e("body").css("cursor",""),(c.autoHide||0==At.width*At.height)&&w(vt.add(xt),function(){e(this).hide()}),e(document).off("mousemove touchmove",N),vt.on("mousemove touchmove",z),t&&c.onSelectEnd(a,p())}function A(t){return"mousedown"==t.type&&1!=t.which?!1:("touchstart"==t.type?(ct&&k(),ct=!0,S(t)):y(),U?(nt=r(At["x"+(1+/w/.test(U))]),it=d(At["y"+(1+/n/.test(U))]),st=r(At["x"+(1+!/w/.test(U))]),at=d(At["y"+(1+!/n/.test(U))]),B=st-f(t),F=at-l(t),e(document).on("mousemove touchmove",N).one("mouseup touchend",k),vt.off("mousemove touchmove",z)):c.movable?(Y=L+At.x1-f(t),q=j+At.y1-l(t),vt.off("mousemove touchmove",z),e(document).on("mousemove touchmove",H).one("mouseup touchend",function(){ct=!1,c.onSelectEnd(a,p()),e(document).off("mousemove touchmove",H),vt.on("mousemove touchmove",z)})):pt.mousedown(t),!1)}function I(e){tt&&(e?(st=n(L,i(L+Q,nt+o(at-it)*tt*(st>nt||-1))),at=s(n(j,i(j+R,it+o(st-nt)/tt*(at>it||-1)))),st=s(st)):(at=n(j,i(j+R,it+o(st-nt)/tt*(at>it||-1))),st=s(n(L,i(L+Q,nt+o(at-it)*tt*(st>nt||-1)))),at=s(at)))}function P(){nt=i(nt,L+Q),it=i(it,j+R),V>o(st-nt)&&(st=nt-V*(nt>st||-1),L>st?nt=L+V:st>L+Q&&(nt=L+Q-V)),Z>o(at-it)&&(at=it-Z*(it>at||-1),j>at?it=j+Z:at>j+R&&(it=j+R-Z)),st=n(L,i(st,L+Q)),at=n(j,i(at,j+R)),I(o(st-nt)_&&(st=nt-_*(nt>st||-1),I()),o(at-it)>et&&(at=it-et*(it>at||-1),I(!0)),At={x1:u(i(nt,st)),x2:u(n(nt,st)),y1:h(i(it,at)),y2:h(n(it,at)),width:o(st-nt),height:o(at-it)}}function K(){P(),g(),c.onSelectChange(a,p())}function N(e){return b(e)?void 0:(P(),st=/w|e|^$/.test(U)||tt?f(e)+B:r(At.x2),at=/n|s|^$/.test(U)||tt?l(e)+F:d(At.y2),K(),!1)}function C(t,o){st=(nt=t)+At.width,at=(it=o)+At.height,e.extend(At,{x1:u(nt),y1:h(it),x2:u(st),y2:h(at)}),g(),c.onSelectChange(a,p())}function H(e){return b(e)?void 0:(nt=n(L,i(Y+f(e),L+Q-At.width)),it=n(j,i(q+l(e),j+R-At.height)),C(nt,it),e.preventDefault(),!1)}function M(){e(document).off("mousemove touchmove",M),y(),st=nt,at=it,K(),U="",xt.is(":visible")||vt.add(xt).hide().fadeIn(c.fadeDuration||0),ot=!0,e(document).off("mouseup touchend",W).on("mousemove touchmove",N).one("mouseup touchend",k),vt.off("mousemove touchmove",z),c.onSelectStart(a,p())}function W(){e(document).off("mousemove touchmove",M).off("mouseup touchend",W),w(vt.add(xt)),v(u(nt),h(it),u(nt),h(it)),this instanceof e.imgAreaSelect||(c.onSelectChange(a,p()),c.onSelectEnd(a,p()))}function D(t){return"mousedown"==t.type&&1!=t.which||xt.is(":animated")?!1:(ct="touchstart"==t.type,y(),Y=nt=f(t),q=it=l(t),B=F=0,e(document).on({"mousemove touchmove":M,"mouseup touchend":W}),!1)}function E(){x(!1)}function O(){$=!0,T(c=e.extend({classPrefix:"imgareaselect",movable:!0,parent:"body",resizable:!0,resizeMargin:10,onInit:function(){},onSelectStart:function(){},onSelectChange:function(){},onSelectEnd:function(){}},c)),c.show&&(ot=!0,y(),g(),vt.add(xt).hide().fadeIn(c.fadeDuration||0)),setTimeout(function(){c.onInit(a,p())},0)}function T(o){if(o.parent&&(X=e(o.parent)).append(vt).append(xt),e.extend(c,o),y(),null!=o.handles){for(wt.remove(),wt=e([]),ut=o.handles?"corners"==o.handles?4:8:0;ut--;)wt=wt.add(t());wt.addClass(c.classPrefix+"-handle").css({position:"absolute",fontSize:0,zIndex:zt+1||1}),!parseInt(wt.css("width"))>=0&&wt.width(5).height(5)}for(G=c.imageWidth/Q||1,J=c.imageHeight/R||1,null!=o.x1&&(v(o.x1,o.y1,o.x2,o.y2),o.show=!o.hide),o.keys&&(c.keys=e.extend({shift:1,ctrl:"resize"},o.keys)),xt.addClass(c.classPrefix+"-outer"),yt.addClass(c.classPrefix+"-selection"),ut=0;4>ut++;)e(gt[ut-1]).addClass(c.classPrefix+"-border"+ut);vt.append(yt.add(gt)).append(wt),Kt&&((ht=(xt.css("filter")||"").match(/opacity=(\d+)/))&&xt.css("opacity",ht[1]/100),(ht=(gt.css("filter")||"").match(/opacity=(\d+)/))&>.css("opacity",ht[1]/100)),o.hide?w(vt.add(xt)):o.show&&$&&(ot=!0,vt.add(xt).fadeIn(c.fadeDuration||0),x()),tt=(dt=(c.aspectRatio||"").split(/:/))[0]/dt[1],pt.add(xt).off("mousedown touchstart",D),c.disable||c.enable===!1?(vt.off({"mousemove touchmove":z,"mousedown touchstart":A}),e(window).off("resize",E)):((c.enable||c.disable===!1)&&((c.resizable||c.movable)&&vt.on({"mousemove touchmove":z,"mousedown touchstart":A}),e(window).resize(E)),c.persistent||pt.add(xt).on("mousedown touchstart",D)),c.enable=c.disable=void 0}var $,L,j,Q,R,X,Y,q,B,F,G,J,U,V,Z,_,et,tt,ot,nt,it,st,at,ct,rt,dt,ut,ht,ft,lt,mt,pt=e(a),vt=t(),yt=t(),gt=t().add(t()).add(t()).add(t()),xt=t(),wt=e([]),bt={left:0,top:0},St={left:0,top:0},zt=0,kt="absolute",At={x1:0,y1:0,x2:0,y2:0,width:0,height:0},It=navigator.userAgent,Pt=function(e){var t,o,s=c.keys,a=e.keyCode;if(t=isNaN(s.alt)||!e.altKey&&!e.originalEvent.altKey?!isNaN(s.ctrl)&&e.ctrlKey?s.ctrl:!isNaN(s.shift)&&e.shiftKey?s.shift:isNaN(s.arrows)?10:s.arrows:s.alt,"resize"==s.arrows||"resize"==s.shift&&e.shiftKey||"resize"==s.ctrl&&e.ctrlKey||"resize"==s.alt&&(e.altKey||e.originalEvent.altKey)){switch(a){case 37:t=-t;case 39:o=n(nt,st),nt=i(nt,st),st=n(o+t,nt),I();break;case 38:t=-t;case 40:o=n(it,at),it=i(it,at),at=n(o+t,it),I(!0);break;default:return}K()}else switch(nt=i(nt,st),it=i(it,at),a){case 37:C(n(nt-t,L),it);break;case 38:C(nt,n(it-t,j));break;case 39:C(nt+i(t,Q-u(st)),it);break;case 40:C(nt,it+i(t,R-h(at)));break;default:return}return!1};this.remove=function(){T({disable:!0}),vt.add(xt).remove()},this.getOptions=function(){return c},this.setOptions=T,this.getSelection=p,this.setSelection=v,this.cancelSelection=W,this.update=x;var Kt=(/msie ([\w.]+)/i.exec(It)||[])[1],Nt=/webkit/i.test(It)&&!/chrome/i.test(It);for(rt=pt;rt.length;)zt=n(zt,isNaN(rt.css("z-index"))?zt:rt.css("z-index")),c.parent||"fixed"!=rt.css("position")||(kt="fixed"),rt=rt.parent(":not(body)");zt=c.zIndex||zt,e.imgAreaSelect.keyPress=Kt||Nt?"keydown":"keypress",vt.add(xt).hide().css({position:kt,overflow:"hidden",zIndex:zt||"0"}),vt.css({zIndex:zt+2||2}),yt.add(gt).css({position:"absolute",fontSize:0}),a.complete||"complete"==a.readyState||!pt.is("img")?O():pt.one("load",O),!$&&Kt&&Kt>=7&&(a.src=a.src)},e.fn.imgAreaSelect=function(t){return t=t||{},this.each(function(){e(this).data("imgAreaSelect")?t.remove?(e(this).data("imgAreaSelect").remove(),e(this).removeData("imgAreaSelect")):e(this).data("imgAreaSelect").setOptions(t):t.remove||(void 0===t.enable&&void 0===t.disable&&(t.enable=!0),e(this).data("imgAreaSelect",new e.imgAreaSelect(this,t)))}),t.instance?e(this).data("imgAreaSelect"):this}})(jQuery);
diff --git a/ext/notes/lib/jquery.imgnotes-1.0.min.css b/ext/notes/lib/jquery.imgnotes-1.0.min.css
deleted file mode 100644
index 214f3c7a0..000000000
--- a/ext/notes/lib/jquery.imgnotes-1.0.min.css
+++ /dev/null
@@ -1,2 +0,0 @@
-/** imgnotes jQuery plugin v1.0.0 **/
-p{font-family:"Times New Roman";font-size:14px}.ui-widget-content{font-family:"Times New Roman"}.ui-widget-content a{color:blue}.marker{position:absolute;width:27px;height:40px}.marker-text{position:absolute;top:10%;width:100%;margin:0 0 0 0;z-index:1;font-size:12px;font-weight:700;text-align:center;color:#fff}.pin{position:absolute;width:20px;height:30px}.pin-text{position:absolute;top:10%;width:100%;margin:0 0 0 0;font-size:14px;font-weight:700;text-align:center;color:#000}.tooltip{position:relative;display:inline-block}.tooltip .tooltiptext{visibility:visible;width:180px;background-color:#fff;color:#000;text-align:center;padding:5px 0;border-radius:6px;position:absolute;z-index:1;bottom:7px;left:50%;margin-left:-90px}.tooltip .tooltiptext::after{content:"";position:absolute;top:100%;left:50%;margin-left:-7px;border-width:7px;border-style:solid;border-color:#fff transparent transparent transparent}table.gridtable{font-family:verdana,arial,sans-serif;font-size:11px;color:#333;border-width:1px;border-color:#666;border-collapse:collapse}table.gridtable th{border-width:1px;padding:8px;border-style:solid;border-color:#666;background-color:#dedede}table.gridtable td{border-width:1px;padding:8px;border-style:solid;border-color:#666;background-color:#fff}
\ No newline at end of file
diff --git a/ext/notes/lib/jquery.imgnotes-1.0.min.js b/ext/notes/lib/jquery.imgnotes-1.0.min.js
deleted file mode 100644
index 241769f7e..000000000
--- a/ext/notes/lib/jquery.imgnotes-1.0.min.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * imgnotes jQuery plugin
- * version 1.0
- *
- * Copyright (c) 2008 - Dr. Tarique Sani
- *
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- * @URL http://www.sanisoft.com/blog/2008/05/26/img-notes-jquery-plugin/
- * @Example example.html
- *
- **/
-
-(function(e){function t(){e(".note").hover(function(){e(".note").show();e(this).next(".notep").show();e(this).next(".notep").css("z-index",1e4)},function(){e(".note").show();e(this).next(".notep").hide();e(this).next(".notep").css("z-index",0)})}function n(t){note_left=parseInt(imgOffset.left)+parseInt(t.x1);note_top=parseInt(imgOffset.top)+parseInt(t.y1);note_p_top=note_top+parseInt(t.height)+5;note_area_div=e("").css({left:note_left+"px",top:note_top+"px",width:t.width+"px",height:t.height+"px"});note_text_div=e(''+t.note+"
").css({left:note_left+"px",top:note_p_top+"px"});e("body").append(note_area_div);e("body").append(note_text_div)}function r(t){if(true!==t){return}notes_icon_left=parseInt(imgOffset.left)+parseInt(imgWidth)-36;notes_icon_top=parseInt(imgOffset.top)+parseInt(imgHieght)-40;notes_icon_div=note_area_div=e("").css({left:notes_icon_left+"px",top:notes_icon_top+"px"});e("body").append(notes_icon_div);e(".notesicon").toggle(function(){e.fn.imgNotes.showAll()},function(){e.fn.imgNotes.hideAll()})}e.fn.imgNotes=function(i){if(undefined==s){var s}if(undefined!=i.notes){s=i.notes}if(i.url){e.ajaxSetup({async:false});e.getJSON(i.url,function(e){s=e})}image=this;imgOffset=e(image).offset();imgHieght=e(image).height();imgWidth=e(image).width();e(s).each(function(){n(this)});e(image).hover(function(){e(".note").show()},function(){e(".note").hide();e(".notep").hide()});t();r(i.isMobile);e(window).resize(function(){e(".note").remove();e(".notep").remove();e(".notesicon").remove();imgOffset=e(image).offset();imgHieght=e(image).height();imgWidth=e(image).width();e(s).each(function(){n(this)});t();r(i.isMobile)})};e.fn.imgNotes.showAll=function(){e(".note").show();e(".notep").show()};e.fn.imgNotes.hideAll=function(){e(".note").hide();e(".notep").hide()}})(jQuery);
diff --git a/ext/notes/lib/spacer.gif b/ext/notes/lib/spacer.gif
deleted file mode 100644
index e565824aa..000000000
Binary files a/ext/notes/lib/spacer.gif and /dev/null differ
diff --git a/ext/notes/main.php b/ext/notes/main.php
index e6292e0b7..f190e374c 100644
--- a/ext/notes/main.php
+++ b/ext/notes/main.php
@@ -98,31 +98,14 @@ public function onPageRequest(PageRequestEvent $event)
if (!$user->is_anonymous()) {
$this->revert_history($noteID, $reviewID);
}
-
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("note/updated"));
break;
- case "add_note":
- if (!$user->is_anonymous()) {
- $this->add_new_note();
- }
- $page->set_mode(PageMode::REDIRECT);
- $page->set_redirect(make_link("post/view/".$_POST["image_id"]));
- break;
case "add_request":
if (!$user->is_anonymous()) {
$this->add_note_request();
}
-
- $page->set_mode(PageMode::REDIRECT);
- $page->set_redirect(make_link("post/view/".$_POST["image_id"]));
- break;
- case "nuke_notes":
- if ($user->can(Permissions::NOTES_ADMIN)) {
- $this->nuke_notes();
- }
-
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
break;
@@ -130,24 +113,43 @@ public function onPageRequest(PageRequestEvent $event)
if ($user->can(Permissions::NOTES_ADMIN)) {
$this->nuke_requests();
}
-
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("post/view/".$_POST["image_id"]));
break;
- case "edit_note":
+
+ case "create_note":
+ $page->set_mode(PageMode::DATA);
+ if (!$user->is_anonymous()) {
+ $note_id = $this->add_new_note();
+ $page->set_data(json_encode([
+ 'status' => 'success',
+ 'note_id' => $note_id,
+ ]));
+ }
+ break;
+ case "update_note":
+ $page->set_mode(PageMode::DATA);
if (!$user->is_anonymous()) {
$this->update_note();
- $page->set_mode(PageMode::REDIRECT);
- $page->set_redirect(make_link("post/view/" . $_POST["image_id"]));
+ $page->set_data(json_encode(['status' => 'success']));
}
break;
case "delete_note":
+ $page->set_mode(PageMode::DATA);
if ($user->can(Permissions::NOTES_ADMIN)) {
$this->delete_note();
- $page->set_mode(PageMode::REDIRECT);
- $page->set_redirect(make_link("post/view/".$_POST["image_id"]));
+ $page->set_data(json_encode(['status' => 'success']));
}
break;
+ case "nuke_notes":
+ if ($user->can(Permissions::NOTES_ADMIN)) {
+ $this->nuke_notes();
+ }
+
+ $page->set_mode(PageMode::REDIRECT);
+ $page->set_redirect(make_link("post/view/".$_POST["image_id"]));
+ break;
+
default:
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("note/list"));
@@ -243,32 +245,47 @@ private function get_notes(int $imageID): array
/*
* HERE WE ADD A NOTE TO DATABASE
*/
- private function add_new_note()
+ private function add_new_note(): int
{
global $database, $user;
- $imageID = int_escape($_POST["image_id"]);
- $user_id = $user->id;
- $noteX1 = int_escape($_POST["note_x1"]);
- $noteY1 = int_escape($_POST["note_y1"]);
- $noteHeight = int_escape($_POST["note_height"]);
- $noteWidth = int_escape($_POST["note_width"]);
- $noteText = html_escape($_POST["note_text"]);
+ $note = json_decode(file_get_contents('php://input'), true);
$database->execute(
"
INSERT INTO notes (enable, image_id, user_id, user_ip, date, x1, y1, height, width, note)
VALUES (:enable, :image_id, :user_id, :user_ip, now(), :x1, :y1, :height, :width, :note)",
- ['enable' => 1, 'image_id' => $imageID, 'user_id' => $user_id, 'user_ip' => get_real_ip(), 'x1' => $noteX1, 'y1' => $noteY1, 'height' => $noteHeight, 'width' => $noteWidth, 'note' => $noteText]
+ [
+ 'enable' => 1,
+ 'image_id' => $note['image_id'],
+ 'user_id' => $user->id,
+ 'user_ip' => get_real_ip(),
+ 'x1' => $note['x1'],
+ 'y1' => $note['y1'],
+ 'height' => $note['height'],
+ 'width' => $note['width'],
+ 'note' => $note['note'],
+ ]
);
$noteID = $database->get_last_insert_id('notes_id_seq');
log_info("notes", "Note added {$noteID} by {$user->name}");
- $database->execute("UPDATE images SET notes=(SELECT COUNT(*) FROM notes WHERE image_id=:id1) WHERE id=:id2", ['id1' => $imageID, 'id2' => $imageID]);
+ $database->execute("UPDATE images SET notes=(SELECT COUNT(*) FROM notes WHERE image_id=:id) WHERE id=:id", ['id' => $note['image_id']]);
+
+ $this->add_history(
+ 1,
+ $noteID,
+ $note['image_id'],
+ $note['x1'],
+ $note['y1'],
+ $note['height'],
+ $note['width'],
+ $note['note']
+ );
- $this->add_history(1, $noteID, $imageID, $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText);
+ return $noteID;
}
private function add_note_request()
@@ -294,15 +311,7 @@ private function update_note()
{
global $database;
- $note = [
- "x1" => int_escape($_POST["note_x1"]),
- "y1" => int_escape($_POST["note_y1"]),
- "height" => int_escape($_POST["note_height"]),
- "width" => int_escape($_POST["note_width"]),
- "note" => $_POST["note_text"],
- "image_id" => int_escape($_POST["image_id"]),
- "id" => int_escape($_POST["note_id"])
- ];
+ $note = json_decode(file_get_contents('php://input'), true);
// validate parameters
if (empty($note['note'])) {
@@ -312,29 +321,22 @@ private function update_note()
$database->execute("
UPDATE notes
SET x1 = :x1, y1 = :y1, height = :height, width = :width, note = :note
- WHERE image_id = :image_id AND id = :id", $note);
+ WHERE image_id = :image_id AND id = :note_id", $note);
- $this->add_history(1, $note['id'], $note['image_id'], $note['x1'], $note['y1'], $note['height'], $note['width'], $note['note']);
+ $this->add_history(1, $note['note_id'], $note['image_id'], $note['x1'], $note['y1'], $note['height'], $note['width'], $note['note']);
}
private function delete_note()
{
global $user, $database;
- $imageID = int_escape($_POST["image_id"]);
- $noteID = int_escape($_POST["note_id"]);
-
- // validate parameters
- if (is_null($imageID) || !is_numeric($imageID) || is_null($noteID) || !is_numeric($noteID)) {
- return;
- }
-
+ $note = json_decode(file_get_contents('php://input'), true);
$database->execute("
UPDATE notes SET enable = :enable
WHERE image_id = :image_id AND id = :id
- ", ['enable' => 0, 'image_id' => $imageID, 'id' => $noteID]);
+ ", ['enable' => 0, 'image_id' => $note["image_id"], 'id' => $note["note_id"]]);
- log_info("notes", "Note deleted {$noteID} by {$user->name}");
+ log_info("notes", "Note deleted {$note["note_id"]} by {$user->name}");
}
private function nuke_notes()
diff --git a/ext/notes/script.js b/ext/notes/script.js
index 7b5be0548..ea2bb87c3 100644
--- a/ext/notes/script.js
+++ b/ext/notes/script.js
@@ -1,81 +1,274 @@
-/*jshint bitwise:true, curly:true, forin:false, noarg:true, noempty:true, nonew:true, undef:true, strict:false, browser:true, jquery:true */
+let notesContainer = null;
+let noteImage = document.getElementById('main_image');
+let noteBeingEdited = null;
+let dragStart = null;
document.addEventListener('DOMContentLoaded', () => {
if(window.notes) {
- $('#main_image').load(function(){
- $('#main_image').imgNotes({notes: window.notes});
+ if(noteImage.complete) {
+ renderNotes();
+ } else {
+ noteImage.addEventListener('load', () => {
+ renderNotes();
+ });
+ }
- //Make sure notes are always shown
- $('#main_image').off('mouseenter mouseleave');
+ let resizeObserver = new ResizeObserver(entries => {
+ renderNotes();
});
+ resizeObserver.observe(noteImage);
}
+});
- $('#cancelnote').click(function(){
- $('#main_image').imgAreaSelect({ hide: true });
- $('#noteform').hide();
- });
+function renderNotes() {
+ // reset the DOM to empty
+ if(notesContainer) {
+ notesContainer.remove();
+ }
+
+ // check the image we're adding notes on top of
+ let br = noteImage.getBoundingClientRect();
+ let scale = br.width / noteImage.getAttribute("data-width");
+
+ // render a container full of notes
+ notesContainer = document.createElement('div');
+ notesContainer.className = 'notes-container';
+ notesContainer.style.left = window.scrollX + br.left + 'px';
+ notesContainer.style.top = window.scrollY + br.top + 'px';
+ notesContainer.style.width = br.width + 'px';
+ notesContainer.style.height = br.height + 'px';
+
+ // render each note
+ window.notes.forEach(note => {
+ let noteDiv = document.createElement('div');
+ noteDiv.classList.add('note');
+ noteDiv.style.left = note.x1 * scale + 'px';
+ noteDiv.style.top = note.y1 * scale + 'px';
+ noteDiv.style.width = note.width * scale + 'px';
+ noteDiv.style.height = note.height * scale + 'px';
+ let text = document.createElement('div');
+ text.innerText = note.note;
+ noteDiv.addEventListener('click', (e) => {
+ noteBeingEdited = note.note_id;
+ renderNotes();
+ });
+ noteDiv.appendChild(text);
+ notesContainer.appendChild(noteDiv);
- $('#EditCancelNote').click(function() {
- $('#main_image').imgAreaSelect({ hide: true });
- $('#noteEditForm').hide();
+ // if the current note is being edited, render the editor
+ if(note.note_id == noteBeingEdited) {
+ let editor = renderEditor(noteDiv, note);
+ notesContainer.appendChild(editor);
+ }
});
- $('#addnote').click(function(){
- $('#noteEditForm').hide();
- $('#main_image').imgAreaSelect({ onSelectChange: showaddnote, x1: 120, y1: 90, x2: 280, y2: 210 });
- return false;
+ noteImage.parentNode.appendChild(notesContainer);
+}
+
+/**
+ *
+ * @param {HTMLElement} noteDiv
+ * @param {*} note
+ * @returns
+ */
+function renderEditor(noteDiv, note) {
+ // check the image we're adding notes on top of
+ let br = noteImage.getBoundingClientRect();
+ let scale = br.width / noteImage.getAttribute("data-width");
+
+ // set the note itself into drag & resize mode
+ // NOTE: to avoid re-rendering the whole DOM every time the mouse
+ // moves, we directly edit the style of the noteDiv, and then when
+ // the mouse is released, we update the note object and re-render
+ noteDiv.classList.add('editing');
+ noteDiv.addEventListener('mousedown', (e) => {
+ dragStart = {
+ x: e.pageX,
+ y: e.pageY,
+ mode: getArea(e.offsetX, e.offsetY, noteDiv.offsetWidth, noteDiv.offsetHeight),
+ };
+ noteDiv.classList.add("dragging");
+ });
+ noteDiv.addEventListener('mousemove', (e) => {
+ if(dragStart) {
+ if(dragStart.mode == "c") {
+ noteDiv.style.left = (note.x1 * scale) + (e.pageX - dragStart.x) + 'px';
+ noteDiv.style.top = (note.y1 * scale) + (e.pageY - dragStart.y) + 'px';
+ }
+ if(dragStart.mode.indexOf("n") >= 0) {
+ noteDiv.style.top = (note.y1 * scale) + (e.pageY - dragStart.y) + 'px';
+ noteDiv.style.height = (note.height * scale) - (e.pageY - dragStart.y) + 'px';
+ }
+ if(dragStart.mode.indexOf("s") >= 0) {
+ noteDiv.style.height = (note.height * scale) + (e.pageY - dragStart.y) + 'px';
+ }
+ if(dragStart.mode.indexOf("w") >= 0) {
+ noteDiv.style.left = (note.x1 * scale) + (e.pageX - dragStart.x) + 'px';
+ noteDiv.style.width = (note.width * scale) - (e.pageX - dragStart.x) + 'px';
+ }
+ if(dragStart.mode.indexOf("e") >= 0) {
+ noteDiv.style.width = (note.width * scale) + (e.pageX - dragStart.x) + 'px';
+ }
+ } else {
+ let area = getArea(e.offsetX, e.offsetY, noteDiv.offsetWidth, noteDiv.offsetHeight);
+ if(area == "c") {
+ noteDiv.style.cursor = 'move';
+ } else {
+ noteDiv.style.cursor = area + '-resize';
+ }
+ }
});
+ function _commit() {
+ noteDiv.classList.remove("dragging");
+ dragStart = null;
+ note.x1 = noteDiv.offsetLeft / scale;
+ note.y1 = noteDiv.offsetTop / scale;
+ note.width = noteDiv.offsetWidth / scale;
+ note.height = noteDiv.offsetHeight / scale;
+ renderNotes();
+ }
+ noteDiv.addEventListener('mouseup', _commit);
+ noteDiv.addEventListener('mouseleave', _commit);
- $('.note').click(function() {
- $('#noteform').hide();
- var imgOffset = $('#main_image').offset();
+ // add textarea / save / cancel / delete buttons
+ let editor = document.createElement('div');
+ editor.classList.add('editor');
+ editor.style.left = note.x1 * scale + 'px';
+ editor.style.top = (note.y1 + note.height) * scale + 'px';
- var x1 = parseInt(this.style.left) - imgOffset.left;
- var y1 = parseInt(this.style.top) - imgOffset.top;
- var width = parseInt(this.style.width);
- var height = parseInt(this.style.height);
- var text = $(this).next('.notep').text().replace(/([^>]?)\\n{2}/g, '$1\\n');
- var id = $(this).next('.notep').next('.noteID').text();
+ let textarea = document.createElement('textarea');
+ textarea.value = note.note;
+ textarea.addEventListener('input', () => {
+ note.note = textarea.value;
+ });
+ editor.appendChild(textarea);
- $('#main_image').imgAreaSelect({ onSelectChange: showeditnote, x1: x1, y1: y1, x2: x1 + width, y2: y1 + height });
- setEditNoteData(x1, y1, width, height, text, id);
+ let save = document.createElement('button');
+ save.innerText = 'Save';
+ save.addEventListener('click', () => {
+ if(note.note_id == null) {
+ fetch('/note/create_note', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(note)
+ }).then(response => {
+ if(response.ok) {
+ return response.json();
+ } else {
+ throw new Error('Failed to create note');
+ }
+ }).then(data => {
+ note.note_id = data.note_id;
+ renderNotes();
+ }).catch(error => {
+ alert(error);
+ });
+ } else {
+ fetch('/note/update_note', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(note)
+ }).then(response => {
+ if(!response.ok) {
+ throw new Error('Failed to update note');
+ }
+ }).catch(error => {
+ alert(error);
+ });
+ }
+ noteBeingEdited = null;
+ renderNotes();
});
-});
-
-function showaddnote (img, area) {
- var imgOffset = $(img).offset();
- var form_left = parseInt(imgOffset.left) + parseInt(area.x1);
- var form_top = parseInt(imgOffset.top) + parseInt(area.y1) + parseInt(area.height)+5;
-
- $('#noteform').css({ left: form_left + 'px', top: form_top + 'px'});
- $('#noteform').show();
- $('#noteform').css('z-index', 10000);
- $('#NoteX1').val(area.x1);
- $('#NoteY1').val(area.y1);
- $('#NoteHeight').val(area.height);
- $('#NoteWidth').val(area.width);
+ editor.appendChild(save);
+
+ let cancel = document.createElement('button');
+ cancel.innerText = 'Cancel';
+ cancel.addEventListener('click', () => {
+ noteBeingEdited = null;
+ if(note.note_id == null) {
+ // delete the un-saved note
+ window.notes = window.notes.filter(n => n.note_id != null);
+ }
+ renderNotes();
+ });
+ editor.appendChild(cancel);
+
+ if(window.notes_admin && note.note_id != null) {
+ let deleteNote = document.createElement('button');
+ deleteNote.innerText = 'Delete';
+ deleteNote.addEventListener('click', () => {
+ // TODO: delete note from server
+ fetch('/note/delete_note', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(note)
+ }).then(response => {
+ if(!response.ok) {
+ throw new Error('Failed to delete note');
+ }
+ }).catch(error => {
+ alert(error);
+ });
+ noteBeingEdited = null;
+ window.notes = window.notes.filter(n => n.note_id != note.note_id);
+ renderNotes();
+ });
+ editor.appendChild(deleteNote);
+ }
+
+ return editor;
}
-function showeditnote (img, area) {
- var imgOffset = $(img).offset();
- var form_left = parseInt(imgOffset.left) + area.x1;
- var form_top = parseInt(imgOffset.top) + area.y2;
-
- $('#noteEditForm').css({ left: form_left + 'px', top: form_top + 'px'});
- $('#noteEditForm').show();
- $('#noteEditForm').css('z-index', 10000);
- $('#EditNoteX1').val(area.x1);
- $('#EditNoteY1').val(area.y1);
- $('#EditNoteHeight').val(area.height);
- $('#EditNoteWidth').val(area.width);
+function addNewNote() {
+ if(window.notes.filter(note => note.note_id == null).length > 0) {
+ alert("Please save all notes before adding a new one.");
+ return;
+ }
+ window.notes.push(
+ {
+ x1: 10,
+ y1: 10,
+ width: 100,
+ height: 40,
+ note: "new note",
+ note_id: null,
+ image_id: window.notes_image_id,
+ }
+ );
+ noteBeingEdited = null;
+ renderNotes();
}
-function setEditNoteData(x1, y1, width, height, text, id) {
- $('#EditNoteX1').val(x1);
- $('#EditNoteY1').val(y1);
- $('#EditNoteHeight').val(height);
- $('#EditNoteWidth').val(width);
- $('#EditNoteNote').text(text);
- $('#EditNoteID').val(id);
- $('#DeleteNoteNoteID').val(id);
+function getArea(x, y, width, height) {
+ let border = 10;
+
+ if(y < border) {
+ if(x < border) {
+ return "nw";
+ } else if(x > width - border) {
+ return "ne";
+ } else {
+ return "n";
+ }
+ } else if(y > height - border) {
+ if(x < border) {
+ return "sw";
+ } else if(x > width - border) {
+ return "se";
+ } else {
+ return "s";
+ }
+ } else if(x < border) {
+ return "w";
+ } else if(x > width - border) {
+ return "e";
+ } else {
+ return "c";
+ }
}
diff --git a/ext/notes/style.css b/ext/notes/style.css
index ccdb7e97c..112659370 100644
--- a/ext/notes/style.css
+++ b/ext/notes/style.css
@@ -1,87 +1,56 @@
-.note {
- display: block;
+.notes-container {
+ position: absolute;
+}
+.notes-container .note {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: black;
background-color: #FFE;
border: 1px dashed black;
overflow: hidden;
position: absolute;
- z-index: 0;
-
- filter:alpha(opacity=50);
- -moz-opacity:0.5;
- -khtml-opacity: 0.5;
opacity: 0.5;
+ z-index: 1;
}
-
-.notep {
- display: none;
- color: #412a21;
- background-color: #fffdef;
- border: #412a21 1px solid;
- font-size: 8pt;
- margin-top: 0;
- padding: 2px;
- position: absolute;
- width: 175px;
-}
-
-#noteform, #noteEditForm {
- display: none;
- position: absolute;
- width: 250px;
-}
-
-#noteform textarea, #noteEditForm textarea {
- width: 100%;
-}
-
-/*
- * imgAreaSelect default style
- */
-
-.imgareaselect-border1 {
- background: url(border-v.gif) repeat-y left top;
+.notes-container .note.editing {
+ opacity: 1;
+ border: 1px dashed red;
+ z-index: 2;
}
-
-.imgareaselect-border2 {
- background: url(border-h.gif) repeat-x left top;
-}
-
-.imgareaselect-border3 {
- background: url(border-v.gif) repeat-y right top;
-}
-
-.imgareaselect-border4 {
- background: url(border-h.gif) repeat-x left bottom;
-}
-
-.imgareaselect-border1, .imgareaselect-border2,
-.imgareaselect-border3, .imgareaselect-border4 {
- filter: alpha(opacity=50);
+.notes-container .note.editing.dragging {
opacity: 0.5;
+ z-index: 2;
}
-
-.imgareaselect-handle {
- background-color: #fff;
- border: solid 1px #000;
- filter: alpha(opacity=50);
- opacity: 0.5;
+.notes-container .note:hover {
+ opacity: 1;
+ z-index: 3;
}
-.imgareaselect-outer {
- /*background-color: #000;*/
- filter: alpha(opacity=50);
- opacity: 0.5;
+.notes-container .editor {
+ display: grid;
+ color: black;
+ background-color: #EFE;
+ border: 1px dashed blue;
+ position: absolute;
+ grid-template-columns: 1fr 1fr;
+ grid-template-areas:
+ "text text"
+ "save cancel"
+ "delete delete";
+ z-index: 4;
}
-
-.imgareaselect-selection {
+.notes-container .editor TEXTAREA {
+ grid-area: text;
+ // resize: none;
}
-
-/* Makes sure the note block is hidden */
-section#note_system {
- height: 0;
+.notes-container .editor BUTTON[value="Save"] {
+ grid-area: save;
+}
+.notes-container .editor BUTTON[value="Cancel"] {
+ grid-area: cancel;
}
-section#note_system > .blockbody {
- padding: 0;
- border: 0;
+.notes-container .editor BUTTON[value="Delete"] {
+ grid-area: delete;
}
diff --git a/ext/notes/theme.php b/ext/notes/theme.php
index a1204ea2f..0755eba66 100644
--- a/ext/notes/theme.php
+++ b/ext/notes/theme.php
@@ -4,41 +4,39 @@
namespace Shimmie2;
+use MicroHTML\HTMLElement;
+
+use function MicroHTML\INPUT;
+
class NotesTheme extends Themelet
{
- public function note_button(int $image_id): string
+ public function note_button(int $image_id): HTMLElement
{
- return '
-
-
- ';
+ return SHM_SIMPLE_FORM("", INPUT(["type" => "button", "value" => "Add Note", "onclick" => "addNewNote()"]));
}
- public function request_button(int $image_id): string
+ public function request_button(int $image_id): HTMLElement
{
- return make_form(make_link("note/add_request")) . '
-
-
-
- ';
+ return SHM_SIMPLE_FORM(
+ "note/add_request",
+ INPUT(["type" => "hidden", "name" => "image_id", "value" => $image_id]),
+ INPUT(["type" => "submit", "value" => "Add Note Request"]),
+ );
}
- public function nuke_notes_button(int $image_id): string
+ public function nuke_notes_button(int $image_id): HTMLElement
{
- return make_form(make_link("note/nuke_notes")) . '
-
-
-
- ';
+ return SHM_SIMPLE_FORM(
+ "note/nuke_notes",
+ INPUT(["type" => "hidden", "name" => "image_id", "value" => $image_id]),
+ INPUT(["type" => "submit", "value" => "Nuke Notes", "onclick" => "return confirm_action('Are you sure?')"]),
+ );
}
- public function nuke_requests_button(int $image_id): string
+ public function nuke_requests_button(int $image_id): HTMLElement
{
- return make_form(make_link("note/nuke_requests")) . '
-
-
-
- ';
+ return SHM_SIMPLE_FORM(
+ "note/nuke_requests",
+ INPUT(["type" => "hidden", "name" => "image_id", "value" => $image_id]),
+ INPUT(["type" => "submit", "value" => "Nuke Requests", "onclick" => "return confirm_action('Are you sure?')"]),
+ );
}
public function search_notes_page(Page $page): void
@@ -56,91 +54,23 @@ public function search_notes_page(Page $page): void
// check action POST on form
public function display_note_system(Page $page, int $image_id, array $recovered_notes, bool $adminOptions): void
{
- $base_href = get_base_href();
-
- $page->add_html_header("");
- $page->add_html_header("");
- $page->add_html_header("");
-
$to_json = [];
foreach ($recovered_notes as $note) {
- $parsedNote = $note["note"];
- $parsedNote = str_replace("\n", "\\n", $parsedNote);
- $parsedNote = str_replace("\r", "\\r", $parsedNote);
-
$to_json[] = [
+ 'image_id' => $image_id,
'x1' => $note["x1"],
'y1' => $note["y1"],
'height' => $note["height"],
'width' => $note["width"],
- 'note' => $parsedNote,
+ 'note' => $note["note"],
'note_id' => $note["id"],
];
}
-
- $html = "";
-
- $html .= "
-
- ";
-
- $page->add_block(new Block(null, $html, "main", 1, 'note_system'));
+ $page->add_html_header("");
}
diff --git a/ext/random_image/main.php b/ext/random_image/main.php
index 05c89294d..759512153 100644
--- a/ext/random_image/main.php
+++ b/ext/random_image/main.php
@@ -25,10 +25,7 @@ public function onPageRequest(PageRequestEvent $event)
}
$image = Image::by_random($search_terms);
if (!$image) {
- throw new SCoreException(
- "Couldn't find any posts randomly",
- Tag::implode($search_terms)
- );
+ throw new SCoreException("Couldn't find any posts randomly");
}
if ($action === "download") {
diff --git a/ext/tag_edit/main.php b/ext/tag_edit/main.php
index 309c4ae7f..986b54185 100644
--- a/ext/tag_edit/main.php
+++ b/ext/tag_edit/main.php
@@ -44,7 +44,7 @@ class TagSetException extends UserErrorException
public function __construct(string $msg, ?string $redirect = null)
{
- parent::__construct($msg, null);
+ parent::__construct($msg);
$this->redirect = $redirect;
}
}
diff --git a/ext/tag_editcloud/main.php b/ext/tag_editcloud/main.php
index 1158d8a4b..5f7b52ae2 100644
--- a/ext/tag_editcloud/main.php
+++ b/ext/tag_editcloud/main.php
@@ -88,6 +88,8 @@ private function build_tag_map(Image $image): ?HTMLElement
if (count($relevant_tags) == 0) {
return null;
}
+ $relevant_tag_ids = implode(',', array_map(fn ($t) => Tag::get_or_create_id($t), $relevant_tags));
+
$tag_data = $database->get_all(
"
SELECT t2.tag AS tag, COUNT(image_id) AS count, FLOOR(LN(LN(COUNT(image_id) - :tag_min1 + 1)+1)*150)/200 AS scaled
@@ -95,11 +97,11 @@ private function build_tag_map(Image $image): ?HTMLElement
JOIN image_tags it2 USING(image_id)
JOIN tags t1 ON it1.tag_id = t1.id
JOIN tags t2 ON it2.tag_id = t2.id
- WHERE t1.count >= :tag_min2 AND t1.tag IN(:relevant_tags)
+ WHERE t1.count >= :tag_min2 AND t1.tag_id IN ($relevant_tag_ids)
GROUP BY t2.tag
ORDER BY count DESC
LIMIT :limit",
- ["tag_min1" => $tags_min, "tag_min2" => $tags_min, "limit" => $max_count, "relevant_tags" => $relevant_tags]
+ ["tag_min1" => $tags_min, "tag_min2" => $tags_min, "limit" => $max_count]
);
break;
/** @noinspection PhpMissingBreakStatementInspection */
diff --git a/ext/upload/main.php b/ext/upload/main.php
index bc53e1d05..e64299abf 100644
--- a/ext/upload/main.php
+++ b/ext/upload/main.php
@@ -292,7 +292,17 @@ private function tags_for_upload_slot(int $id): array
private function source_for_upload_slot(int $id): ?string
{
- return $_POST["source$id"] ?? $_POST['source'] ?? null;
+ global $config;
+ if(!empty($_POST["source$id"])) {
+ return $_POST["source$id"];
+ }
+ if(!empty($_POST['source'])) {
+ return $_POST['source'];
+ }
+ if($config->get_bool(UploadConfig::TLSOURCE) && !empty($_POST["url$id"])) {
+ return $_POST["url$id"];
+ }
+ return null;
}
/**
@@ -409,7 +419,7 @@ private function try_transload(string $url, array $tags, string $source = null,
$metadata = [];
$metadata['filename'] = $filename;
$metadata['tags'] = $tags;
- $metadata['source'] = (($url == $source) && !$config->get_bool(UploadConfig::TLSOURCE) ? "" : $source);
+ $metadata['source'] = $source;
if ($user->can(Permissions::EDIT_IMAGE_LOCK) && !empty($_GET['locked'])) {
$metadata['locked'] = bool_escape($_GET['locked']) ? "on" : "";
}
diff --git a/ext/view/events/image_admin_block_building_event.php b/ext/view/events/image_admin_block_building_event.php
index 6a1d458d2..9f2fe701f 100644
--- a/ext/view/events/image_admin_block_building_event.php
+++ b/ext/view/events/image_admin_block_building_event.php
@@ -4,9 +4,11 @@
namespace Shimmie2;
+use MicroHTML\HTMLElement;
+
class ImageAdminBlockBuildingEvent extends Event
{
- /** @var string[] */
+ /** @var HTMLElement[]|string[] */
public array $parts = [];
public Image $image;
public User $user;
@@ -20,7 +22,7 @@ public function __construct(Image $image, User $user, string $context)
$this->context = $context;
}
- public function add_part(string $html, int $position = 50)
+ public function add_part(HTMLElement|string $html, int $position = 50)
{
while (isset($this->parts[$position])) {
$position++;
diff --git a/ext/view/test.php b/ext/view/test.php
index fc6898a07..e272c06dd 100644
--- a/ext/view/test.php
+++ b/ext/view/test.php
@@ -66,6 +66,24 @@ public function testPrevNext()
$this->assertEquals(404, $page->code);
}
+ public function testPrevNextDisabledWhenOrdered()
+ {
+ $this->log_in_as_user();
+ $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test");
+
+ $this->get_page("post/view/$image_id");
+ $this->assert_text("Prev");
+
+ $this->get_page("post/view/$image_id", ["search" => "test"]);
+ $this->assert_text("Prev");
+
+ $this->get_page("post/view/$image_id", ["search" => "cake_order:_the_cakening"]);
+ $this->assert_text("Prev");
+
+ $this->get_page("post/view/$image_id", ["search" => "order:score"]);
+ $this->assert_no_text("Prev");
+ }
+
public function testView404()
{
$this->log_in_as_user();
diff --git a/ext/view/theme.php b/ext/view/theme.php
index 657a5404f..84efbf014 100644
--- a/ext/view/theme.php
+++ b/ext/view/theme.php
@@ -35,8 +35,10 @@ public function display_page(Image $image, $editor_parts)
//$page->add_block(new Block(null, $this->build_pin($image), "main", 11));
$query = $this->get_query();
- $page->add_html_header("");
- $page->add_html_header("");
+ if(!$this->is_ordered_search()) {
+ $page->add_html_header("");
+ $page->add_html_header("");
+ }
}
public function display_admin_block(Page $page, $parts)
@@ -56,14 +58,35 @@ protected function get_query(): ?string
return $query;
}
+ /**
+ * prev/next only work for default-ordering searches - if the user
+ * has specified a custom order, we can't show prev/next.
+ */
+ protected function is_ordered_search(): bool
+ {
+ if(isset($_GET['search'])) {
+ $tags = Tag::explode($_GET['search']);
+ foreach($tags as $tag) {
+ if(preg_match("/^order[=:]/", $tag) == 1) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
protected function build_pin(Image $image): HTMLElement
{
$query = $this->get_query();
- return joinHTML(" | ", [
- A(["href" => make_link("post/prev/{$image->id}", $query), "id" => "prevlink"], "Prev"),
- A(["href" => make_link()], "Index"),
- A(["href" => make_link("post/next/{$image->id}", $query), "id" => "nextlink"], "Next"),
- ]);
+ if($this->is_ordered_search()) {
+ return A(["href" => make_link()], "Index");
+ } else {
+ return joinHTML(" | ", [
+ A(["href" => make_link("post/prev/{$image->id}", $query), "id" => "prevlink"], "Prev"),
+ A(["href" => make_link()], "Index"),
+ A(["href" => make_link("post/next/{$image->id}", $query), "id" => "nextlink"], "Next"),
+ ]);
+ }
}
protected function build_navigation(Image $image): string