summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Kohl <git@jonaskohl.de>2024-09-19 16:32:18 +0200
committerJonas Kohl <git@jonaskohl.de>2024-09-19 16:32:18 +0200
commit4a6e12bf1fc7837699f780674c33cba5f2b1223c (patch)
tree0636da538f47b285774525418c1730789c650607
parent5c707597ba936b1b82ee9a1cf546e720d1a490bd (diff)
Add log messages
-rw-r--r--src/application/messages/de.msg9
-rw-r--r--src/application/mystic/forum/Database.php9
-rw-r--r--src/application/mystic/forum/orm/TopicLogMessage.php23
-rw-r--r--src/application/views/view_topiclog.php67
-rw-r--r--src/index.php186
5 files changed, 220 insertions, 74 deletions
diff --git a/src/application/messages/de.msg b/src/application/messages/de.msg
index d1b2389..46b48b3 100644
--- a/src/application/messages/de.msg
+++ b/src/application/messages/de.msg
@@ -464,3 +464,12 @@ metadata({
: "Unlock topic"
= "Thema entsperren"
+
+: "%user% locked this topic"
+= "%user% hat dieses Thema gesperrt"
+
+: "%user% unlocked this topic"
+= "%user% hat dieses Thema entsperrt"
+
+: "%user% changed the title of this topic from %old_title% to %new_title%"
+= "%user% hat den Titel dieses Themas von %old_title% zu %new_title% geƤndert"
diff --git a/src/application/mystic/forum/Database.php b/src/application/mystic/forum/Database.php
index 8ebc36b..7a417be 100644
--- a/src/application/mystic/forum/Database.php
+++ b/src/application/mystic/forum/Database.php
@@ -142,6 +142,7 @@ class Database {
return "text";
case "object":
case "array":
+ return "jsonb";
case "iterable":
case "callable":
case "mixed":
@@ -241,8 +242,10 @@ class Database {
return strval($value);
elseif (is_a($value, \DateTimeInterface::class))
return $value->format("c");
+ elseif (is_array($value) || is_object($value))
+ return json_encode($value, JSON_UNESCAPED_SLASHES);
else
- throw new \RuntimeException("Don't know how to stringify " . is_object($value) ? get_class($value) : gettype($value));
+ throw new \RuntimeException("Don't know how to stringify " . ((is_object($value) && !is_array($value)) ? get_class($value) : gettype($value)));
}
private static function assignValue(Entity &$entity, array $colProps, ?string $value): void {
@@ -285,7 +288,11 @@ class Database {
}
break;
case "object":
+ $typedValue = json_decode($value);
+ break;
case "array":
+ $typedValue = json_decode($value, true);
+ break;
case "iterable":
case "callable":
case "mixed":
diff --git a/src/application/mystic/forum/orm/TopicLogMessage.php b/src/application/mystic/forum/orm/TopicLogMessage.php
new file mode 100644
index 0000000..1caa3c5
--- /dev/null
+++ b/src/application/mystic/forum/orm/TopicLogMessage.php
@@ -0,0 +1,23 @@
+<?php
+declare(strict_types=1);
+
+namespace mystic\forum\orm;
+
+use mystic\forum\attributes\DefaultValue;
+use mystic\forum\attributes\PrimaryKey;
+use mystic\forum\attributes\References;
+use mystic\forum\attributes\Table;
+
+#[Table("public.topic_log")]
+class TopicLogMessage extends Entity {
+ public const TITLE_CHANGED = 200;
+ public const LOCKED = 201;
+ public const UNLOCKED = 202;
+
+ #[PrimaryKey] public string $id;
+ #[References("public.topics", onDelete: References::CASCADE)] public string $topicId;
+ public int $type;
+ public \DateTimeImmutable $postDate;
+ public array $params;
+ #[References("public.users", onDelete: References::SET_NULL)] public ?string $authorId;
+}
diff --git a/src/application/views/view_topiclog.php b/src/application/views/view_topiclog.php
new file mode 100644
index 0000000..be3f78d
--- /dev/null
+++ b/src/application/views/view_topiclog.php
@@ -0,0 +1,67 @@
+<?php
+
+/** @var \mystic\forum\orm\TopicLogMessage $logMessage */
+/** @var ?\mystic\forum\orm\User $postAuthor */
+
+use mystic\forum\orm\TopicLogMessage;
+
+$hide_actions ??= false;
+$hide_pfp ??= false;
+
+$user = "";
+if ($postAuthor === null) {
+ $user = __("(deleted)");
+} else {
+ $user = '<a href="?_action=viewuser&user=' . htmlentities(urlencode($postAuthor->id)) . '">' . htmlentities($postAuthor->displayName) . '</a>';
+}
+
+?>
+<div class="media" id="post-<?= htmlentities($logMessage->id) ?>">
+<div class="media-left hidden-sm hidden-xs">
+ <?php if ($postAuthor): ?>
+ <?php if ($hide_actions): ?>
+ <img class="media-object" alt="<?= __("Profile picture") ?>" src="?_action=profilepicture&amp;user=<?= htmlentities(urlencode($postAuthor->id)) ?>" width="64" height="64">
+ <?php else: ?>
+ <a href="?_action=viewuser&amp;user=<?= htmlentities(urlencode($postAuthor->id)) ?>">
+ <img class="media-object" alt="<?= __("Profile picture") ?>" src="?_action=profilepicture&amp;user=<?= htmlentities(urlencode($postAuthor->id)) ?>" width="64" height="64">
+ </a>
+ <?php endif; ?>
+ <?php else: ?>
+ <div class="media-object" style="width:64px;height:64px"></div>
+ <?php endif; ?>
+</div>
+<div class="media-body">
+ <?php if ($logMessage->type === TopicLogMessage::LOCKED): ?>
+ <div class="well icon-well text-info">
+ <span class="glyphicon glyphicon-lock text-info" aria-hidden="true"></span>
+ <em><?= __("%user% locked this topic", [
+ "user" => $user,
+ ]) ?></em>
+ <br>
+ <small class="_time"><?= $logMessage->postDate->format("c") ?></small>
+ </div>
+ <?php elseif ($logMessage->type === TopicLogMessage::UNLOCKED): ?>
+ <div class="well icon-well text-success">
+ <span class="glyphicon glyphicon-globe text-success" aria-hidden="true"></span>
+ <em><?= __("%user% unlocked this topic", [
+ "user" => $user,
+ ]) ?></em>
+ <br>
+ <small class="_time"><?= $logMessage->postDate->format("c") ?></small>
+ </div>
+ <?php elseif ($logMessage->type === TopicLogMessage::TITLE_CHANGED): ?>
+ <div class="well icon-well text-info">
+ <span class="glyphicon glyphicon-pencil text-info" aria-hidden="true"></span>
+ <em><?= __("%user% changed the title of this topic from %old_title% to %new_title%", [
+ "user" => $user,
+ "old_title" => '<strong>' . htmlentities($logMessage->params["old_value"] ?? __("unknown")) . '</strong>',
+ "new_title" => '<strong>' . htmlentities($logMessage->params["new_value"] ?? __("unknown")) . '</strong>',
+ ]) ?></em>
+ <br>
+ <small class="_time"><?= $logMessage->postDate->format("c") ?></small>
+ </div>
+ <?php else: ?>
+ <?= __("unknown") ?>
+ <?php endif; ?>
+</div>
+</div>
diff --git a/src/index.php b/src/index.php
index 9c5d678..f2d0f86 100644
--- a/src/index.php
+++ b/src/index.php
@@ -10,6 +10,7 @@ use mystic\forum\Messaging;
use mystic\forum\orm\Attachment;
use mystic\forum\orm\Post;
use mystic\forum\orm\Topic;
+use mystic\forum\orm\TopicLogMessage;
use mystic\forum\orm\User;
use mystic\forum\orm\UserPermissions;
use mystic\forum\utils\FileUtils;
@@ -23,7 +24,7 @@ use Symfony\Contracts\Service\Attribute\Required;
header_remove("X-Powered-By");
-const MYSTICBB_VERSION = "0.1.1";
+const MYSTICBB_VERSION = "0.2.0-dev";
if (($_SERVER["HTTP_USER_AGENT"] ?? "") === "") {
http_response_code(403);
@@ -256,6 +257,7 @@ $db->ensureTable(User::class);
$db->ensureTable(Topic::class);
$db->ensureTable(Post::class);
$db->ensureTable(Attachment::class);
+$db->ensureTable(TopicLogMessage::class);
$superuser = new User();
$superuser->id = "SUPERUSER";
@@ -599,16 +601,16 @@ if ($_action === "auth") {
RequestUtils::triggerFormError(__("Message too short or too long!"), $formId);
}
- $post = new Post();
- $post->id = $db->generateId();
- $post->authorId = $currentUser->id;
- $post->topicId = $topicId;
- $post->content = $message;
- $post->postDate = new DateTimeImmutable();
- $post->deleted = false;
- $post->edited = false;
+ $item = new Post();
+ $item->id = $db->generateId();
+ $item->authorId = $currentUser->id;
+ $item->topicId = $topicId;
+ $item->content = $message;
+ $item->postDate = new DateTimeImmutable();
+ $item->deleted = false;
+ $item->edited = false;
- $db->insert($post);
+ $db->insert($item);
foreach ($attachments as $att) {
[
@@ -620,7 +622,7 @@ if ($_action === "auth") {
$attachment->id = $db->generateId();
$attachment->name = $name;
$attachment->mimeType = $type;
- $attachment->postId = $post->id;
+ $attachment->postId = $item->id;
$attachment->contents = file_get_contents($tmpName);
$db->insert($attachment);
@@ -628,7 +630,10 @@ if ($_action === "auth") {
header("Location: ?_action=viewtopic&topic=" . urlencode($topicId) . "#form");
} else {
+ /** @var Post[] $posts */
$posts = $db->fetchCustom(Post::class, 'WHERE topic_id = $1 ORDER BY post_date', [ $topicId ]);
+ /** @var TopicLogMessage[] $logMessages */
+ $logMessages = $db->fetchCustom(TopicLogMessage::class, 'WHERE topic_id = $1 ORDER BY post_date', [ $topicId ]);
$userCache = [];
$topicAuthor = null;
@@ -640,34 +645,45 @@ if ($_action === "auth") {
}
}
+ $allItems = [...$posts, ...$logMessages];
+ usort($allItems, fn(Post|TopicLogMessage $a, Post|TopicLogMessage $b): int => $a->postDate <=> $b->postDate);
+
_view("template_start", ["_title" => $topic->title]);
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
_view("view_topic_start", ["topic" => $topic, "topicAuthor" => $topicAuthor]);
- /** @var Post $post */
- foreach ($posts as $post) {
+ foreach ($allItems as $item) {
/** @var ?User $postAuthor */
$postAuthor = null;
- if ($post->authorId !== null && !isset($userCache[$post->authorId])) {
+ if ($item->authorId !== null && !isset($userCache[$item->authorId])) {
$usr = new User();
- $usr->id = $post->authorId;
+ $usr->id = $item->authorId;
if ($db->fetch($usr))
- $userCache[$post->authorId] = &$usr;
+ $userCache[$item->authorId] = &$usr;
+ }
+ if (isset($userCache[$item->authorId]))
+ $postAuthor = &$userCache[$item->authorId];
+
+ if ($item instanceof Post) {
+ $attachments = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $item->id ]);
+
+ _view("view_post", [
+ "post" => $item,
+ "postAuthor" => $postAuthor,
+ "topicAuthor" => $topicAuthor,
+ "attachments" => $attachments,
+ "topic" => $topic,
+ ]);
+ } else {
+ _view("view_topiclog", [
+ "logMessage" => $item,
+ "postAuthor" => $postAuthor,
+ "topicAuthor" => $topicAuthor,
+ "topic" => $topic,
+ ]);
}
- if (isset($userCache[$post->authorId]))
- $postAuthor = &$userCache[$post->authorId];
-
- $attachments = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $post->id ]);
-
- _view("view_post", [
- "post" => $post,
- "postAuthor" => $postAuthor,
- "topicAuthor" => $topicAuthor,
- "attachments" => $attachments,
- "topic" => $topic,
- ]);
}
_view("view_topic_end");
@@ -724,16 +740,16 @@ if ($_action === "auth") {
$db->insert($topic);
- $post = new Post();
- $post->id = $db->generateId();
- $post->authorId = $currentUser->id;
- $post->topicId = $topic->id;
- $post->content = $message;
- $post->postDate = $topic->creationDate;
- $post->deleted = false;
- $post->edited = false;
+ $item = new Post();
+ $item->id = $db->generateId();
+ $item->authorId = $currentUser->id;
+ $item->topicId = $topic->id;
+ $item->content = $message;
+ $item->postDate = $topic->creationDate;
+ $item->deleted = false;
+ $item->edited = false;
- $db->insert($post);
+ $db->insert($item);
foreach ($attachments as $att) {
[
@@ -745,7 +761,7 @@ if ($_action === "auth") {
$attachment->id = $db->generateId();
$attachment->name = $name;
$attachment->mimeType = $type;
- $attachment->postId = $post->id;
+ $attachment->postId = $item->id;
$attachment->contents = file_get_contents($tmpName);
$db->insert($attachment);
@@ -960,15 +976,15 @@ if ($_action === "auth") {
$posts = $db->fetchCustom(Post::class, 'WHERE author_id = $1 ORDER BY post_date DESC', [ $userId ]);
$topics = [];
$attachments = [];
- foreach ($posts as $post) {
- if (!isset($topics[$post->topicId])) {
+ foreach ($posts as $item) {
+ if (!isset($topics[$item->topicId])) {
$topic = new Topic();
- $topic->id = $post->topicId;
+ $topic->id = $item->topicId;
if ($db->fetch($topic))
- $topics[$post->topicId] = $topic;
+ $topics[$item->topicId] = $topic;
}
- $attachs = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $post->id ]);
- $attachments[$post->id] = $attachs;
+ $attachs = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $item->id ]);
+ $attachments[$item->id] = $attachs;
}
_view("template_start", ["_title" => $user->displayName]);
_view("template_navigation_start");
@@ -1166,23 +1182,23 @@ if ($_action === "auth") {
$formId = "deletepost";
$postId = RequestUtils::getRequiredField("post", $formId);
- $post = new Post();
- $post->id = $postId;
+ $item = new Post();
+ $item->id = $postId;
- if (!$db->fetch($post) || $post->deleted) {
+ if (!$db->fetch($item) || $item->deleted) {
http_response_code(404);
msg_error("No post exists with this id");
exit;
}
$topicAuthor = new User();
- $topicAuthor->id = $post->authorId;
+ $topicAuthor->id = $item->authorId;
if (!$db->fetch($topicAuthor))
$topicAuthor = null;
$topic = new Topic();
- $topic->id = $post->topicId;
+ $topic->id = $item->topicId;
if (!$db->fetch($topic))
$topic = null;
@@ -1196,21 +1212,21 @@ if ($_action === "auth") {
exit;
}
- $attachments = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $post->id ]);
+ $attachments = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $item->id ]);
$confirm = $_POST["confirm"] ?? null;
if ($confirm !== null) {
- $expectedConfirm = base64_encode(hash("sha256", "confirm" . $post->id, true));
+ $expectedConfirm = base64_encode(hash("sha256", "confirm" . $item->id, true));
if ($confirm !== $expectedConfirm) {
http_response_code(400);
msg_error("Invalid confirmation");
exit;
}
- $post->deleted = true;
- $post->content = "";
+ $item->deleted = true;
+ $item->content = "";
- if (!$db->update($post)) {
+ if (!$db->update($item)) {
http_response_code(500);
msg_error("Failed to delete post");
exit;
@@ -1224,14 +1240,14 @@ if ($_action === "auth") {
}
}
- header("Location: ?_action=viewtopic&topic=" . urlencode($post->topicId));
+ header("Location: ?_action=viewtopic&topic=" . urlencode($item->topicId));
} else {
_view("template_start", ["_title" => __("Delete post")]);
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
_view("form_delete_post_confirm", [
- "post" => $post,
+ "post" => $item,
"postAuthor" => $topicAuthor,
"topicAuthor" => null,
"attachments" => $attachments,
@@ -1252,17 +1268,17 @@ if ($_action === "auth") {
$postId = RequestUtils::getRequiredField("post", $formId);
$message = RequestUtils::getRequiredField("message", $formId);
- $post = new Post();
- $post->id = $postId;
+ $item = new Post();
+ $item->id = $postId;
- if (!$db->fetch($post) || $post->deleted) {
+ if (!$db->fetch($item) || $item->deleted) {
http_response_code(404);
msg_error(__("No post exists with this id"));
exit;
}
$topicAuthor = new User();
- $topicAuthor->id = $post->authorId;
+ $topicAuthor->id = $item->authorId;
if (!$db->fetch($topicAuthor))
$topicAuthor = null;
@@ -1271,7 +1287,7 @@ if ($_action === "auth") {
|| ($currentUser->hasPermission(UserPermissions::EDIT_OTHER_POST));
$topic = new Topic();
- $topic->id = $post->topicId;
+ $topic->id = $item->topicId;
if (!$db->fetch($topic))
$topic = null;
@@ -1290,16 +1306,16 @@ if ($_action === "auth") {
$confirm = $_POST["confirm"] ?? null;
- $post->content = $message;
- $post->edited = true;
+ $item->content = $message;
+ $item->edited = true;
- if (!$db->update($post)) {
+ if (!$db->update($item)) {
http_response_code(500);
msg_error(__("Failed to update post"));
exit;
}
- header("Location: ?_action=viewtopic&topic=" . urlencode($post->topicId) . "#post-" . urlencode($postId));
+ header("Location: ?_action=viewtopic&topic=" . urlencode($item->topicId) . "#post-" . urlencode($postId));
} elseif ($_action === "deletetopic") {
RequestUtils::ensureRequestMethod("POST");
@@ -1406,8 +1422,22 @@ if ($_action === "auth") {
exit;
}
+ $prevTitle = $topic->title;
$topic->title = $title;
+ $log = new TopicLogMessage();
+ $log->id = $db->generateId();
+ $log->topicId = $topic->id;
+ $log->authorId = $currentUser->id;
+ $log->params = [
+ "old_value" => $prevTitle,
+ "new_value" => $title,
+ ];
+ $log->type = TopicLogMessage::TITLE_CHANGED;
+ $log->postDate = new \DateTimeImmutable();
+
+ $db->insert($log);
+
if (!$db->update($topic)) {
http_response_code(500);
msg_error(__("Failed to update topic"));
@@ -1465,6 +1495,16 @@ if ($_action === "auth") {
$topic->isLocked = $locked;
+ $log = new TopicLogMessage();
+ $log->id = $db->generateId();
+ $log->topicId = $topic->id;
+ $log->authorId = $currentUser->id;
+ $log->params = [];
+ $log->type = $locked ? TopicLogMessage::LOCKED : TopicLogMessage::UNLOCKED;
+ $log->postDate = new \DateTimeImmutable();
+
+ $db->insert($log);
+
if (!$db->update($topic)) {
http_response_code(500);
msg_error(__("Failed to lock or unlock topic"));
@@ -1489,21 +1529,21 @@ if ($_action === "auth") {
$topicLookup = [];
$attachmentLookup = [];
$userLookup = [];
- foreach ($posts as $post) {
- if (!isset($topicLookup[$post->topicId])) {
+ foreach ($posts as $item) {
+ if (!isset($topicLookup[$item->topicId])) {
$topic = new Topic;
- $topic->id = $post->topicId;
+ $topic->id = $item->topicId;
if ($db->fetch($topic))
$topicLookup[$topic->id] = &$topic;
}
- if (!isset($attachmentLookup[$post->id])) {
- $attachmentLookup[$post->id] = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $post->id ]);
+ if (!isset($attachmentLookup[$item->id])) {
+ $attachmentLookup[$item->id] = $db->fetchCustom(Attachment::class, 'WHERE post_id = $1', [ $item->id ]);
}
- if (!isset($userLookup[$post->authorId])) {
+ if (!isset($userLookup[$item->authorId])) {
$user = new User;
- $user->id = $post->authorId;
+ $user->id = $item->authorId;
if ($db->fetch($user))
- $userLookup[$post->authorId] = &$user;
+ $userLookup[$item->authorId] = &$user;
}
}
$end_time = microtime(true);