summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Kohl <git@jonaskohl.de>2024-09-14 17:59:13 +0200
committerJonas Kohl <git@jonaskohl.de>2024-09-14 17:59:13 +0200
commitd8b74761c6cefdd83360d3f2add0e9ccdc6064c7 (patch)
tree5e2c947b4a90ae475a7e75897077246e5ea0a948
parent948cead0f11d33adbcf0d08773c716e1b6ebb101 (diff)
Even more changes (devel commit messages are useless)
-rw-r--r--.env.example4
-rw-r--r--src/application/mystic/forum/orm/Post.php1
-rw-r--r--src/application/views/form_login.php4
-rw-r--r--src/application/views/form_newtopic.php34
-rw-r--r--src/application/views/form_register.php40
-rw-r--r--src/application/views/nav_logged_in.php4
-rw-r--r--src/application/views/template_end.php25
-rw-r--r--src/application/views/template_navigation_start.php2
-rw-r--r--src/application/views/template_start.php14
-rw-r--r--src/application/views/view_post.php32
-rw-r--r--src/application/views/view_topic_start.php96
-rw-r--r--src/application/views/view_user.php29
-rw-r--r--src/composer.json4
-rw-r--r--src/composer.lock1151
-rw-r--r--src/index.php166
-rw-r--r--src/themes/default/theme.css10
-rw-r--r--src/themes/slate/theme.css15
-rw-r--r--src/ui/site.css2
-rw-r--r--src/ui/theme-default.css3
-rw-r--r--src/ui/theme-slate.css8
20 files changed, 1545 insertions, 99 deletions
diff --git a/.env.example b/.env.example
index 4b62cb4..0eba495 100644
--- a/.env.example
+++ b/.env.example
@@ -1,3 +1,7 @@
POSTGRES_USER=postgres
POSTGRES_DB=postgres
POSTGRES_PASSWORD=postgres
+#MYSTIC_FORUM_THEME=default
+#REGISTRATION_ENABLED=1
+#MYSTIC_FORUM_TITLE=
+#MYSTIC_FORUM_COPYRIGHT=
diff --git a/src/application/mystic/forum/orm/Post.php b/src/application/mystic/forum/orm/Post.php
index becbb9d..186ea61 100644
--- a/src/application/mystic/forum/orm/Post.php
+++ b/src/application/mystic/forum/orm/Post.php
@@ -16,4 +16,5 @@ class Post extends Entity {
public \DateTimeImmutable $postDate;
#[References("public.topics", onDelete: References::CASCADE)] public string $topicId;
#[DefaultValue("false")] public bool $deleted;
+ #[DefaultValue("false")] public bool $edited;
}
diff --git a/src/application/views/form_login.php b/src/application/views/form_login.php
index 8ddb22e..27924e8 100644
--- a/src/application/views/form_login.php
+++ b/src/application/views/form_login.php
@@ -9,7 +9,9 @@ if ($lastFormUri !== $_SERVER["REQUEST_URI"]) $lastForm = [];
RequestUtils::clearLastForm();
?>
-<h1>Log in</h1>
+<div class="page-header margin-top-0">
+ <h1>Log in</h1>
+</div>
<div class="col-md-4"></div>
<div class="well col-md-4">
<?php
diff --git a/src/application/views/form_newtopic.php b/src/application/views/form_newtopic.php
index d5cbfbd..2e58268 100644
--- a/src/application/views/form_newtopic.php
+++ b/src/application/views/form_newtopic.php
@@ -3,22 +3,32 @@
use mystic\forum\Messaging;
use mystic\forum\utils\RequestUtils;
-if (($_formError = RequestUtils::getAndClearFormError()) !== null) {
- Messaging::error($_formError);
-}
-
$lastFormUri = "";
$lastForm = RequestUtils::getLastForm($lastFormUri) ?? [];
if ($lastFormUri !== $_SERVER["REQUEST_URI"]) $lastForm = [];
RequestUtils::clearLastForm();
?>
-<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>" method="post">
-<strong>Topic title:</strong><br>
-<input type="text" name="title" value="<?= htmlentities($lastForm["title"] ?? "") ?>" required><br>
-
-<strong>Message:</strong><br>
-<textarea name="message" rows="12" cols="60" required></textarea><br>
-
-<button type="submit">Create topic</button>
+<div class="page-header margin-top-0">
+ <h1>New topic</h1>
+</div>
+<?php
+if (($_formError = RequestUtils::getAndClearFormError()) !== null) {
+ _view("alert_error", ["message" => $_formError]);
+}
+?>
+<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>#form" method="post" enctype="multipart/form-data">
+<div class="form-group">
+ <label for="i_message">Topic title:</label>
+ <input type="text" class="form-control" id="i_title" name="title" value="<?= htmlentities($lastForm["title"] ?? "") ?>" required autofocus>
+</div>
+<div class="form-group">
+ <label for="i_message">Message:</label>
+ <textarea class="form-control" id="i_message" name="message" required rows="12" cols="60" style="resize:vertical;max-height:499px"><?= htmlentities($lastForm["message"] ?? "") ?></textarea>
+</div>
+<div class="form-group">
+ <label for="i_files">Attachments: <small>(max. <?= htmlentities(MAX_ATTACHMENT_COUNT) ?> files, max. <?= htmlentities(MAX_ATTACHMENT_SIZE >> 20) ?> MiB each)</small></label>
+ <input type="file" name="files[]" id="i_files" multiple accept="*/*">
+</div>
+<button type="submit" class="btn btn-success">Create topic</button>
</form>
diff --git a/src/application/views/form_register.php b/src/application/views/form_register.php
index 221f37d..6f62652 100644
--- a/src/application/views/form_register.php
+++ b/src/application/views/form_register.php
@@ -9,7 +9,9 @@ if ($lastFormUri !== $_SERVER["REQUEST_URI"]) $lastForm = [];
RequestUtils::clearLastForm();
?>
-<h1>Register</h1>
+<div class="page-header margin-top-0">
+ <h1>Register</h1>
+</div>
<div class="col-md-4"></div>
<div class="well col-md-4">
<?php
@@ -18,31 +20,49 @@ if (($_formError = RequestUtils::getAndClearFormError()) !== null) {
}
?>
<form action="<?= htmlentities($_SERVER["REQUEST_URI"]) ?>" method="post">
-<div class="form-group">
+<div class="form-group" id="group0">
<label for="i_username">Username:</label>
<input class="form-control" id="i_username" type="text" name="username" value="<?= htmlentities($lastForm["username"] ?? "") ?>" required>
</div>
-<div class="form-group">
+<div class="form-group" id="group1">
+ <label for="i_username">Username:</label>
+ <input class="form-control" id="i_username" type="text" name="df82a9bc21" value="<?= htmlentities($lastForm["username"] ?? "") ?>" required>
+</div>
+
+<div class="form-group" id="group2">
<label for="i_display_name">Display name:</label>
<input class="form-control" id="i_display_name" type="text" name="display_name" value="<?= htmlentities($lastForm["display_name"] ?? "") ?>" required>
</div>
-<div class="form-group">
+<div class="form-group" id="group3">
<label for="i_password">Choose password:</label>
<input class="form-control" id="i_password" type="password" name="password" required>
</div>
-<div class="form-group">
+<div class="form-group" id="group4">
<label for="i_password_retype">Repeat password:</label>
<input class="form-control" id="i_password_retype" type="password" name="password_retype" required>
</div>
-<div class="form-group">
+<div class="form-group" id="group5">
<label for="i_email">Email address:</label>
<input class="form-control" id="i_email" type="email" name="email" value="<?= htmlentities($lastForm["email"] ?? "") ?>" required>
</div>
+<div class="form-group" id="group6">
+ <label for="i_email">CAPTCHA:</label>
+ <div class="text-center margin-bottom">
+ <img src="?_action=captcha&t=<?= htmlentities(strval(microtime(true) * 1000)) ?>" alt="CAPTCHA" width="192" height="48" id="captcha-img">
+ </div>
+ <div class="input-group">
+ <input type="text" name="captcha" id="i_captcha" class="form-control" required>
+ <div class="input-group-btn">
+ <button class="btn btn-default" type="button" id="btn-refresh-captcha"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span><span class="sr-only">New CAPTCHA</span></button>
+ </div>
+ </div>
+</div>
+
<div class="form-group">
<button class="btn btn-default" type="submit">Register now</button>
</div>
@@ -53,3 +73,11 @@ if (($_formError = RequestUtils::getAndClearFormError()) !== null) {
</form>
</div>
<div class="col-md-4"></div>
+
+<script>
+$(function() {
+ $("#btn-refresh-captcha").click(function() {
+ $("#captcha-img").attr("src", "?_action=captcha&t=" + new Date().getTime().toString());
+ });
+});
+</script>
diff --git a/src/application/views/nav_logged_in.php b/src/application/views/nav_logged_in.php
index f899ad8..8ed8d07 100644
--- a/src/application/views/nav_logged_in.php
+++ b/src/application/views/nav_logged_in.php
@@ -9,6 +9,6 @@ use mystic\forum\orm\User;
<strong><?= htmlentities($user->displayName) ?></strong>!
<?php endif; ?>
</p></li>
-<li><a href="?_action=viewuser&amp;user=<?= htmlentities(urlencode($user->id)) ?>"><span class="glyphicon glyphicon-user" aria-hidden="true"><span class="sr-only">View profile</span></a></li>
-<li><a href="?_action=logout&amp;next=<?= htmlentities(urlencode($_SERVER["REQUEST_URI"])) ?>"><span class="glyphicon glyphicon-log-out" aria-hidden="true"><span class="sr-only">Log out</span></a></li>
+<li><a href="?_action=viewuser&amp;user=<?= htmlentities(urlencode($user->id)) ?>"><span class="glyphicon glyphicon-user" aria-hidden="true"></span><span class="sr-only">View profile</span></a></li>
+<li><a href="?_action=logout&amp;next=<?= htmlentities(urlencode($_SERVER["REQUEST_URI"])) ?>"><span class="glyphicon glyphicon-log-out" aria-hidden="true"></span><span class="sr-only">Log out</span></a></li>
</ul>
diff --git a/src/application/views/template_end.php b/src/application/views/template_end.php
index 27fc3ba..7757780 100644
--- a/src/application/views/template_end.php
+++ b/src/application/views/template_end.php
@@ -3,7 +3,8 @@
<div class="container">
<div class="panel panel-default">
<div class="panel-body">
- &copy; <?= date("Y") ?>
+ &copy; <?= date("Y") ?> <?= htmlentities(env("MYSTIC_FORUM_COPYRIGHT") ?? env("MYSTIC_FORUM_TITLE") ?? "Forum") ?>.
+ Powered by <a href="https://git.jkohl.link/mystic-forum.git/">Mystic Forum</a>
</div>
</div>
</div>
@@ -23,6 +24,28 @@ $(function() {
var date = new Date($(e).text());
$(e).text(date.toLocaleTimeString());
});
+
+ $("input[type=file]").each(function(i, e) {
+ var isMultiple = !!$(e).prop("multiple");
+ var $input = $('<input type="text" readonly class="form-control" />').attr("placeholder", "No file" + (isMultiple ? "s" : "") + " selected");
+ $(e).after($('<div class="input-group file-input-group"></div>').append(
+ $input,
+ $('<span class="input-group-btn"></span>').append(
+ $('<button class="btn btn-default" type="button"></button>').text("Select file" + (isMultiple ? "s" : "") + "...").click(function() {
+ $(e).click();
+ })
+ )
+ )).addClass("sr-only");
+ $(e).on("change", function() {
+ var files = $(e)[0].files;
+ if (files.length < 1)
+ $input.val("");
+ else if (files.length === 1)
+ $input.val(files[0].name);
+ else
+ $input.val(files.length + " files selected");
+ });
+ })
});
</script>
diff --git a/src/application/views/template_navigation_start.php b/src/application/views/template_navigation_start.php
index 48cdb9a..3c69bf4 100644
--- a/src/application/views/template_navigation_start.php
+++ b/src/application/views/template_navigation_start.php
@@ -7,6 +7,6 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
- <a class="navbar-brand" href=".">Forum</a>
+ <a class="navbar-brand" href="."><?= htmlentities(env("MYSTIC_FORUM_TITLE") ?? "Forum") ?></a>
</div>
<div class="collapse navbar-collapse" id="nav-collapse">
diff --git a/src/application/views/template_start.php b/src/application/views/template_start.php
index 4cfdb74..56af9bf 100644
--- a/src/application/views/template_start.php
+++ b/src/application/views/template_start.php
@@ -1,3 +1,11 @@
+<?php
+
+$pageTitle = "";
+if (isset($_title) && $_title !== null)
+ $pageTitle = "$_title | ";
+$pageTitle .= env("MYSTIC_FORUM_TITLE") ?? "Forum";
+
+?>
<!DOCTYPE html>
<!--[if lt IE 7]> <html lang="de" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html lang="de" class="no-js lt-ie9 lt-ie8"> <![endif]-->
@@ -5,10 +13,10 @@
<!--[if gt IE 8]><!--> <html lang="de" class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
- <title><?= htmlentities($_title ?? "") ?></title>
- <link rel="stylesheet" href="/ui/theme-default.css">
+ <title><?= htmlentities($pageTitle) ?></title>
+ <link rel="stylesheet" href="/themes/<?= env("MYSTIC_FORUM_THEME") ?? "default" ?>/theme.css">
<script src="/ui/jquery-1.12.4.min.js"></script>
<script src="/ui/dist/js/bootstrap.min.js"></script>
<script src="/ui/modernizr-2.6.2.min.js"></script>
diff --git a/src/application/views/view_post.php b/src/application/views/view_post.php
index 5022d4a..26eec62 100644
--- a/src/application/views/view_post.php
+++ b/src/application/views/view_post.php
@@ -3,11 +3,17 @@
use mystic\forum\orm\UserPermissions;
use mystic\forum\orm\Attachment;
+/** @var mystic\forum\orm\Post $post */
+/** @var mystic\forum\orm\User $postAuthor */
+
$fileAttachments = array_filter($attachments, fn(Attachment $a) => !str_starts_with($a->mimeType, "image/"));
$imageAttachments = array_filter($attachments, fn(Attachment $a) => str_starts_with($a->mimeType, "image/"));
$canReply = $GLOBALS["currentUser"]?->hasPermission(UserPermissions::CREATE_OWN_POST) ?? false;
+$canEdit = ($GLOBALS["currentUser"]?->id === $postAuthor?->id && $postAuthor?->hasPermission(UserPermissions::EDIT_OWN_POST))
+ || ($GLOBALS["currentUser"]?->hasPermission(UserPermissions::EDIT_OTHER_POST));
+
$canDelete = ($GLOBALS["currentUser"]?->id === $postAuthor?->id && $postAuthor?->hasPermission(UserPermissions::DELETE_OWN_POST))
|| ($GLOBALS["currentUser"]?->hasPermission(UserPermissions::DELETE_OTHER_POST));
@@ -30,15 +36,15 @@ $your_are_the_author = $GLOBALS["currentUser"]?->id === $postAuthor?->id;
</div>
</div>
<?php else: ?>
-<div class="media" id="post-<?= htmlentities($post->id) ?>">
+<div class="media" id="post-<?= htmlentities($post->id) ?>" data-text="<?= htmlentities($post->content) ?>">
<?php if (!$hide_pfp): ?>
<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)) ?>" alt="" width="64" height="64">
+ <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)) ?>" alt="" width="64" height="64">
+ <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: ?>
@@ -49,12 +55,15 @@ $your_are_the_author = $GLOBALS["currentUser"]?->id === $postAuthor?->id;
<div class="media-body">
<div class="panel panel-default">
<div class="panel-heading">
- <h3 class="panel-title">
+ <div class="panel-title h3">
<?php if (!$hide_actions): ?>
<div class="pull-right">
- <?php if ($canReply): ?>
<a href="#post-<?= htmlentities(urlencode($post->id)) ?>" class="btn btn-default"><span class="glyphicon glyphicon-link" aria-hidden="true"></span><span class="sr-only">Permalink</span></a>
- <button data-text="<?= htmlentities($post->content) ?>" class="btn btn-default js-only _reply-post"><span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span><span class="sr-only">Reply to post</span></button>
+ <?php if ($canReply): ?>
+ <button data-post-id="<?= htmlentities($post->id) ?>" class="btn btn-default js-only _reply-post"><span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span><span class="sr-only">Reply to post</span></button>
+ <?php endif; ?>
+ <?php if ($canEdit): ?>
+ <button data-post-id="<?= htmlentities($post->id) ?>" class="btn btn-default js-only _edit-post"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span><span class="sr-only">Edit post</span></button>
<?php endif; ?>
<?php if ($canDelete): ?>
<form action="?_action=deletepost" method="post" class="seamless-inline">
@@ -76,21 +85,24 @@ $your_are_the_author = $GLOBALS["currentUser"]?->id === $postAuthor?->id;
<?php else: ?>
<em class="text-muted">(deleted)</em>
<?php endif; ?>
- </h3>
+ </div>
<span class="_time"><?= $post->postDate->format("c"); ?></span>
+ <?php if ($post->edited): ?>
+ <em class="text-muted">(edited)</em>
+ <?php endif; ?>
</div>
<div class="panel-body">
- <div class="post-content"><?= renderPost($post->content) ?></div>
+ <div class="post-content"><?= renderPost(trim($post->content)) ?></div>
<?php if (count($imageAttachments) > 0): ?>
<div class="post-images clearfix">
<?php /** @var Attachment $attachment */ foreach ($imageAttachments as $attachment): ?>
<?php if ($hide_actions): ?>
<span class="image-attachment" title="<?= htmlentities($attachment->name) ?>">
- <img class="image-attachment-image" src="?_action=thumb&amp;attachment=<?= htmlentities(urlencode($attachment->id)) ?>" alt="" width="110">
+ <img class="image-attachment-image" src="?_action=thumb&amp;attachment=<?= htmlentities(urlencode($attachment->id)) ?>" alt="" width="100">
</span>
<?php else: ?>
<a class="image-attachment" href="?_action=attachment&amp;attachment=<?= htmlentities(urlencode($attachment->id)) ?>" title="<?= htmlentities($attachment->name) ?>">
- <img class="image-attachment-image" src="?_action=thumb&amp;attachment=<?= htmlentities(urlencode($attachment->id)) ?>" alt="" width="110">
+ <img class="image-attachment-image" src="?_action=thumb&amp;attachment=<?= htmlentities(urlencode($attachment->id)) ?>" alt="" width="100">
</a>
<?php endif; ?>
<?php endforeach; ?>
diff --git a/src/application/views/view_topic_start.php b/src/application/views/view_topic_start.php
index 4006982..37e8cf8 100644
--- a/src/application/views/view_topic_start.php
+++ b/src/application/views/view_topic_start.php
@@ -9,30 +9,63 @@ $canEdit = ($GLOBALS["currentUser"]?->id === $topicAuthor->id && $topicAuthor->h
$canDelete = ($GLOBALS["currentUser"]?->id === $topicAuthor->id && $topicAuthor->hasPermission(UserPermissions::DELETE_OWN_TOPIC))
|| ($GLOBALS["currentUser"]?->hasPermission(UserPermissions::DELETE_OTHER_TOPIC));
?>
+<?php if ($canEdit): ?>
+ <div class="modal fade" tabindex="-1" role="dialog" id="diag-edit-post">
+ <form class="modal-dialog" role="document" action="?_action=updatepost" method="post">
+ <input type="hidden" id="i_edit_post" name="post">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title">Edit post</h4>
+ </div>
+ <div class="modal-body">
+ <div class="form-group">
+ <label class="sr-only" for="i_edit_message">Message:</label>
+ <textarea class="form-control" name="message" id="i_edit_message" rows="12" style="resize: vertical; max-height: 500px"></textarea>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Cancel</button>
+ <button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span> Save changes</button>
+ </div>
+ </div>
+ </form>
+ </div>
+<?php endif; ?>
+
<div class="page-header margin-top-0 clearfix">
- <div role="heading" class="h1 margin-top-0" id="displayHeading">
- <?= htmlentities($topic->title) ?>
- <div class="pull-right">
- <?php if ($canEdit): ?>
- <button id="btn-edit-title" class="btn btn-default js-only"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit title</button>
- <?php endif; ?>
- <?php if ($canReply): ?>
- <button id="btn-reply" class="btn btn-default js-only"><span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span> Reply</button>
- <?php endif; ?>
- <?php if ($canDelete): ?>
- <form action="?_action=deletetopic" method="post" class="seamless-inline">
- <input type="hidden" name="topic" value="<?= htmlentities($topic->id) ?>">
- <button type="submit" class="btn btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete topic</button>
- </form>
- <?php endif; ?>
- </div>
+ <div id="displayHeading">
+ <div role="heading" class="h1 seamless-inline">
+ <?= htmlentities($topic->title) ?>
+ <div class="pull-right text-normal">
+ <?php if ($canEdit): ?>
+ <button id="btn-edit-title" class="btn btn-default js-only"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit title</button>
+ <?php endif; ?>
+ <?php if ($canReply): ?>
+ <button id="btn-reply" class="btn btn-default js-only"><span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span> Reply</button>
+ <?php endif; ?>
+ <?php if ($canDelete): ?>
+ <form action="?_action=deletetopic" method="post" class="seamless-inline">
+ <input type="hidden" name="topic" value="<?= htmlentities($topic->id) ?>">
+ <button type="submit" class="btn btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete topic</button>
+ </form>
+ <?php endif; ?>
+ </div>
+ </div><br>
+ Started by
+ <?php if ($topicAuthor !== null): ?>
+ <a href="?_action=viewuser&amp;user=<?= htmlentities(urlencode($topicAuthor->id)) ?>"><?= htmlentities($topicAuthor->displayName) ?></a>
+ <?php else: ?>
+ <em>(deleted)</em>
+ <?php endif; ?>
+ on <span class="_time"><?= htmlentities($topic->creationDate->format("c")) ?></span>
</div>
<?php if ($canEdit): ?>
<form action="?_action=updatetopic" method="post" id="editHeading" style="display: none;" class="form-inline seamless-inline" style="display: block">
<input type="hidden" name="topic" value="<?= htmlentities(urlencode($topic->id)) ?>">
<div class="row">
<div class="col-md-8">
- <input type="text" class="form-control" name="title" id="i_edit_title" value="<?= htmlentities($topic->title) ?>" style="box-sizing: border-box; width: 100%">
+ <input type="text" class="form-control" name="title" id="i_edit_title" data-original-value="<?= htmlentities($topic->title) ?>" value="<?= htmlentities($topic->title) ?>" style="box-sizing: border-box; width: 100%; font-size: 36px; height: 56px">
</div>
<div class="col-md-4 text-right">
<button type="button" id="topicTitleEditCancel" class="btn btn-default"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Cancel</button>
@@ -42,22 +75,13 @@ $canDelete = ($GLOBALS["currentUser"]?->id === $topicAuthor->id && $topicAuthor-
</form>
<?php endif; ?>
</div>
-<p>
-Started by
-<?php if ($topicAuthor !== null): ?>
-<a href="?_action=viewuser&amp;user=<?= htmlentities(urlencode($topicAuthor->id)) ?>"><?= htmlentities($topicAuthor->displayName) ?></a>
-<?php else: ?>
-<em>(deleted)</em>
-<?php endif; ?>
-on <span class="_time"><?= htmlentities($topic->creationDate->format("c")) ?></span>
-</p>
<script>
<?php if ($canEdit): ?>
$(function() {
$("#btn-edit-title").click(function() {
$("#displayHeading").hide();
$("#editHeading").show();
- $("#i_edit_title").focus();
+ $("#i_edit_title").val($("#i_edit_title").attr("data-original-value")).focus();
});
$("#topicTitleEditCancel").click(function() {
$("#displayHeading").show();
@@ -76,7 +100,7 @@ $(function() {
focusReplyBox();
});
$("._reply-post").click(function() {
- var text = $(this).attr("data-text");
+ var text = $("#post-" + $(this).attr("data-post-id")).attr("data-text");
var val = $("#i_message").val();
var lines = text.split("\n");
for (var i = 0; i < lines.length; ++i)
@@ -86,5 +110,19 @@ $(function() {
focusReplyBox();
});
});
-</script>
<?php endif; ?>
+<?php if ($canEdit): ?>
+$(function() {
+ $("._edit-post").click(function() {
+ var $post = $("#post-" + $(this).attr("data-post-id"));
+ var $postContent = $post.find(".post-content");
+ $("#i_edit_message").css("height", "").val($post.attr("data-text"));
+ $("#i_edit_post").val($(this).attr("data-post-id"));
+ $("#diag-edit-post").modal();
+ });
+ $("#diag-edit-post").on("shown.bs.modal", function() {
+ $("#i_edit_message").focus();
+ });
+});
+<?php endif; ?>
+</script>
diff --git a/src/application/views/view_user.php b/src/application/views/view_user.php
index 23d2b71..79b57d2 100644
--- a/src/application/views/view_user.php
+++ b/src/application/views/view_user.php
@@ -26,13 +26,13 @@ $dateJoined = DateTime::createFromImmutable($user->created);
$dateJoined->setTime(0, 0, 0, 0);
?>
-<div class="clearfix">
+<div class="clearfix page-header margin-top-0">
<img class="pull-left margin-right" src="?_action=profilepicture&amp;user=<?= htmlentities(urlencode($user->id)) ?>">
<span class="h1"><?= htmlentities($user->displayName) ?></span>
<?php if ($isOwnProfile): ?>
<span class="label label-primary">You</span>
<?php endif; ?><br>
- @<?= htmlentities($user->name) ?> &bull; <span class="text-muted">Member since <span class="_date"><?= htmlentities($dateJoined->format("c")); ?></span>
+ @<?= htmlentities($user->name) ?> &bull; <span class="text-muted">Member since <span class="_date"><?= htmlentities($dateJoined->format("c")); ?></span></span>
</div>
<?php if ($canEdit): ?>
@@ -97,18 +97,22 @@ if (($_formError = RequestUtils::getAndClearFormError()) !== null) {
<label>Profile picture:</label>
<?php
$_checkbox_disabled = empty($user->profilePicture);
-$_checkbox_disabled_class = $_checkbox_disabled ? " disabled" : "";
+$_checkbox_disabled_class = $_checkbox_disabled ? " disabled text-muted" : "";
?>
<div class="radio margin-top-0 <?= $_checkbox_disabled_class ?>">
<label>
<input type="radio" name="pfp_action" id="pfp_action_1" value="keep"<?= !empty($user->profilePicture) ? ' checked' : ' disabled' ?>>
- Existing profile picture
+ Keep current profile picture
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="pfp_action" id="pfp_action_2" value="remove"<?= empty($user->profilePicture) ? ' checked' : '' ?>>
- No profile picture
+ <?php if (empty($user->profilePicture)): ?>
+ No profile picture
+ <?php else: ?>
+ Remove profile picture
+ <?php endif; ?>
</label>
</div>
<div class="radio">
@@ -141,12 +145,19 @@ $(function() {
});
<?php if ($canEdit): ?>
$(function() {
- $("#i_pfp").hide().prop("disabled", true).prop("required", false);
+ function _hide() {
+ $("#i_pfp").hide().prop("disabled", true).prop("required", false);
+ $("#i_pfp + .file-input-group").hide().find("button").prop("disabled", true);
+ }
+ _hide();
+ setTimeout(_hide, 10);
$("[name='pfp_action']").on("change input check click", function() {
- if ($("#pfp_action_3").is(":checked"))
+ if ($("#pfp_action_3").is(":checked")) {
$("#i_pfp").show().prop("disabled", false).prop("required", true);
- else
- $("#i_pfp").hide().prop("disabled", true).prop("required", false);
+ $("#i_pfp + .file-input-group").show().find("button").prop("disabled", false);
+ } else {
+ _hide();
+ }
})
});
<?php endif; ?>
diff --git a/src/composer.json b/src/composer.json
index 93c4e2f..82afc5b 100644
--- a/src/composer.json
+++ b/src/composer.json
@@ -3,5 +3,9 @@
"psr-4": {
"mystic\\forum\\": "application/mystic/forum/"
}
+ },
+ "require": {
+ "symfony/mailer": "^7.1",
+ "gregwar/captcha": "^1.2"
}
}
diff --git a/src/composer.lock b/src/composer.lock
new file mode 100644
index 0000000..048b760
--- /dev/null
+++ b/src/composer.lock
@@ -0,0 +1,1151 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "6a9be35a60a5746ba957ae334c8f378a",
+ "packages": [
+ {
+ "name": "doctrine/lexer",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/lexer.git",
+ "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
+ "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^12",
+ "phpstan/phpstan": "^1.10",
+ "phpunit/phpunit": "^10.5",
+ "psalm/plugin-phpunit": "^0.18.3",
+ "vimeo/psalm": "^5.21"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Lexer\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
+ "homepage": "https://www.doctrine-project.org/projects/lexer.html",
+ "keywords": [
+ "annotations",
+ "docblock",
+ "lexer",
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/lexer/issues",
+ "source": "https://github.com/doctrine/lexer/tree/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-02-05T11:56:58+00:00"
+ },
+ {
+ "name": "egulias/email-validator",
+ "version": "4.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/egulias/EmailValidator.git",
+ "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e",
+ "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/lexer": "^2.0 || ^3.0",
+ "php": ">=8.1",
+ "symfony/polyfill-intl-idn": "^1.26"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.2",
+ "vimeo/psalm": "^5.12"
+ },
+ "suggest": {
+ "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Egulias\\EmailValidator\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Eduardo Gulias Davis"
+ }
+ ],
+ "description": "A library for validating emails against several RFCs",
+ "homepage": "https://github.com/egulias/EmailValidator",
+ "keywords": [
+ "email",
+ "emailvalidation",
+ "emailvalidator",
+ "validation",
+ "validator"
+ ],
+ "support": {
+ "issues": "https://github.com/egulias/EmailValidator/issues",
+ "source": "https://github.com/egulias/EmailValidator/tree/4.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/egulias",
+ "type": "github"
+ }
+ ],
+ "time": "2023-10-06T06:47:41+00:00"
+ },
+ {
+ "name": "gregwar/captcha",
+ "version": "v1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Gregwar/Captcha.git",
+ "reference": "229d3cdfe33d6f1349e0aec94a26e9205a6db08e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/229d3cdfe33d6f1349e0aec94a26e9205a6db08e",
+ "reference": "229d3cdfe33d6f1349e0aec94a26e9205a6db08e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-gd": "*",
+ "ext-mbstring": "*",
+ "php": ">=5.3.0",
+ "symfony/finder": "*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Gregwar\\": "src/Gregwar"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Grégoire Passault",
+ "email": "g.passault@gmail.com",
+ "homepage": "http://www.gregwar.com/"
+ },
+ {
+ "name": "Jeremy Livingston",
+ "email": "jeremy.j.livingston@gmail.com"
+ }
+ ],
+ "description": "Captcha generator",
+ "homepage": "https://github.com/Gregwar/Captcha",
+ "keywords": [
+ "bot",
+ "captcha",
+ "spam"
+ ],
+ "support": {
+ "issues": "https://github.com/Gregwar/Captcha/issues",
+ "source": "https://github.com/Gregwar/Captcha/tree/v1.2.1"
+ },
+ "time": "2023-09-26T13:45:37+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "psr/event-dispatcher",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/event-dispatcher.git",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\EventDispatcher\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Standard interfaces for event handling.",
+ "keywords": [
+ "events",
+ "psr",
+ "psr-14"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/event-dispatcher/issues",
+ "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
+ },
+ "time": "2019-01-08T18:20:26+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.2"
+ },
+ "time": "2024-09-11T13:17:53+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
+ "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-04-18T09:32:20+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v7.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
+ "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/event-dispatcher-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<6.4",
+ "symfony/service-contracts": "<2.5"
+ },
+ "provide": {
+ "psr/event-dispatcher-implementation": "1.0",
+ "symfony/event-dispatcher-implementation": "2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/error-handler": "^6.4|^7.0",
+ "symfony/expression-language": "^6.4|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/stopwatch": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-05-31T14:57:53+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher-contracts",
+ "version": "v3.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+ "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50",
+ "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/event-dispatcher": "^1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\EventDispatcher\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to dispatching event",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-04-18T09:32:20+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v7.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "d95bbf319f7d052082fb7af147e0f835a695e823"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823",
+ "reference": "d95bbf319f7d052082fb7af147e0f835a695e823",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "symfony/filesystem": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Finds files and directories via an intuitive fluent interface",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/finder/tree/v7.1.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-08-13T14:28:19+00:00"
+ },
+ {
+ "name": "symfony/mailer",
+ "version": "v7.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/mailer.git",
+ "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/mailer/zipball/8fcff0af9043c8f8a8e229437cea363e282f9aee",
+ "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee",
+ "shasum": ""
+ },
+ "require": {
+ "egulias/email-validator": "^2.1.10|^3|^4",
+ "php": ">=8.2",
+ "psr/event-dispatcher": "^1",
+ "psr/log": "^1|^2|^3",
+ "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/mime": "^6.4|^7.0",
+ "symfony/service-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "symfony/http-client-contracts": "<2.5",
+ "symfony/http-kernel": "<6.4",
+ "symfony/messenger": "<6.4",
+ "symfony/mime": "<6.4",
+ "symfony/twig-bridge": "<6.4"
+ },
+ "require-dev": {
+ "symfony/console": "^6.4|^7.0",
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/twig-bridge": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Mailer\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Helps sending emails",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/mailer/tree/v7.1.2"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-06-28T08:00:31+00:00"
+ },
+ {
+ "name": "symfony/mime",
+ "version": "v7.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/mime.git",
+ "reference": "ccaa6c2503db867f472a587291e764d6a1e58758"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/ccaa6c2503db867f472a587291e764d6a1e58758",
+ "reference": "ccaa6c2503db867f472a587291e764d6a1e58758",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-intl-idn": "^1.10",
+ "symfony/polyfill-mbstring": "^1.0"
+ },
+ "conflict": {
+ "egulias/email-validator": "~3.0.0",
+ "phpdocumentor/reflection-docblock": "<3.2.2",
+ "phpdocumentor/type-resolver": "<1.4.0",
+ "symfony/mailer": "<6.4",
+ "symfony/serializer": "<6.4.3|>7.0,<7.0.3"
+ },
+ "require-dev": {
+ "egulias/email-validator": "^2.1.10|^3.1|^4",
+ "league/html-to-markdown": "^5.0",
+ "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/property-access": "^6.4|^7.0",
+ "symfony/property-info": "^6.4|^7.0",
+ "symfony/serializer": "^6.4.3|^7.0.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Mime\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Allows manipulating MIME messages",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "mime",
+ "mime-type"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/mime/tree/v7.1.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-08-13T14:28:19+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-idn",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-idn.git",
+ "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773",
+ "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2",
+ "symfony/polyfill-intl-normalizer": "^1.10"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Idn\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Laurent Bassin",
+ "email": "laurent@bassin.info"
+ },
+ {
+ "name": "Trevor Rowbotham",
+ "email": "trevor.rowbotham@pm.me"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "idn",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "intl",
+ "normalizer",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
+ "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.5.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-04-18T09:32:20+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "2.6.0"
+}
diff --git a/src/index.php b/src/index.php
index 6258131..c75d112 100644
--- a/src/index.php
+++ b/src/index.php
@@ -1,5 +1,6 @@
<?php
+use Gregwar\Captcha\CaptchaBuilder;
use mystic\forum\Database;
use mystic\forum\exceptions\DatabaseConnectionException;
use mystic\forum\Messaging;
@@ -14,12 +15,17 @@ use mystic\forum\utils\ValidationUtils;
header_remove("X-Powered-By");
+if (($_SERVER["HTTP_USER_AGENT"] ?? "") === "") {
+ http_response_code(403);
+ exit;
+}
+
// function exception_error_handler($errno, $errstr, $errfile, $errline ) {
// throw new ErrorException(html_entity_decode($errstr), $errno, 0, $errfile, $errline);
// }
// set_error_handler("exception_error_handler");
-define("REGISTRATION_ENABLED", true);
+define("REGISTRATION_ENABLED", isTrue(env("REGISTRATION_ENABLED") ?? ""));
session_name("fsid");
session_start();
@@ -27,12 +33,21 @@ session_start();
const MAX_ATTACHMENT_SIZE = 0x200000;
const MAX_ATTACHMENT_COUNT = 4;
const THUMB_MAX_DIM = 100;
+const CAPTCHA_PHRASE_LENGTH = 7;
+const CAPTCHA_CHARSET = 'ABCDEFGHKLMNPQRTWXYZ234789abdefghkmnpqr';
$_rq_method = $_SERVER["REQUEST_METHOD"] ?? "GET";
$_action = $_GET["_action"] ?? null;
$GLOBALS["action"] = $_action;
+function generateCaptchaText(): string {
+ $phrase = "";
+ for ($i = 0; $i < CAPTCHA_PHRASE_LENGTH; ++$i)
+ $phrase .= CAPTCHA_CHARSET[random_int(0, strlen(CAPTCHA_CHARSET) - 1)];
+ return $phrase;
+}
+
function _view(string $name, array $params = []): void {
$___NAME = $name;
$___PARAMS = &$params;
@@ -42,13 +57,18 @@ function _view(string $name, array $params = []): void {
echo "<!--{/" . htmlentities($name) . "}-->\n";
}
+function isTrue(string $str): bool {
+ $str = strtolower($str);
+ return in_array($str, ["yes","true","y","t","on","enabled","1","?1"]);
+}
+
function reArrayFiles(&$file_post) {
$file_ary = [];
$file_count = count($file_post['name']);
$file_keys = array_keys($file_post);
for ($i=0; $i<$file_count; $i++) {
- if ($file_post["error"][$i] === UPLOAD_ERR_NO_FILE)
+ if ($file_post["error"][$i] !== UPLOAD_ERR_OK)
continue;
foreach ($file_keys as $key) {
$file_ary[$i][$key] = $file_post[$key][$i];
@@ -93,6 +113,13 @@ function renderPost(string $contents): string {
return $contents;
}
+function env(string $key): ?string {
+ $val = getenv($key);
+ if ($val === false)
+ return null;
+ return $val;
+}
+
require_once __DIR__ . "/vendor/autoload.php";
$db = null;
@@ -169,7 +196,7 @@ if ($_action === "auth") {
RequestUtils::setAuthorizedUser($user);
header("Location: " . $_GET["next"] ?? ".");
} else {
- _view("template_start", ["_title" => "Forum"]);
+ _view("template_start", ["_title" => "Log in"]);
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
@@ -189,11 +216,22 @@ if ($_action === "auth") {
}
if (RequestUtils::isRequestMethod("POST")) {
- $username = RequestUtils::getRequiredField("username");
+ $doNotFill = $_POST["username"] ?? null;
+ if ($doNotFill !== null) {
+ sleep(10);
+ http_response_code(204);
+ exit;
+ }
+ $username = RequestUtils::getRequiredField("df82a9bc21");
$password = RequestUtils::getRequiredField("password");
$passwordRetype = RequestUtils::getRequiredField("password_retype");
$email = trim(RequestUtils::getRequiredField("email"));
$displayName = RequestUtils::getRequiredField("display_name");
+ $captcha = RequestUtils::getRequiredField("captcha");
+
+ if ($captcha !== $_SESSION["captchaPhrase"]) {
+ RequestUtils::triggerFormError("Incorrect CAPTCHA text!");
+ }
// usernames are always lowercase
$username = strtolower($username);
@@ -250,7 +288,7 @@ if ($_action === "auth") {
Messaging::html('<p>Please click <a href="?_action=auth">here</a> to log in!</p>'),
]);
} else {
- _view("template_start", ["_title" => "Forum"]);
+ _view("template_start", ["_title" => "Register"]);
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
@@ -302,6 +340,7 @@ if ($_action === "auth") {
$post->content = $message;
$post->postDate = new DateTimeImmutable();
$post->deleted = false;
+ $post->edited = false;
$db->insert($post);
@@ -335,7 +374,7 @@ if ($_action === "auth") {
}
}
- _view("template_start", ["_title" => "Forum"]);
+ _view("template_start", ["_title" => $topic->title]);
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
@@ -383,6 +422,18 @@ if ($_action === "auth") {
$title = trim(RequestUtils::getRequiredField("title"));
$message = trim(RequestUtils::getRequiredField("message"));
+ $attachments = reArrayFiles($_FILES["files"]);
+
+ if (count($attachments) > MAX_ATTACHMENT_COUNT)
+ RequestUtils::triggerFormError("Too many attachments");
+
+ // check all attachments before saving one
+ foreach ($attachments as $att) {
+ if ($att["size"] > MAX_ATTACHMENT_SIZE) {
+ RequestUtils::triggerFormError("Individual file size exceeded");
+ }
+ }
+
if (strlen($title) < 1 || strlen($title) > 255) {
RequestUtils::triggerFormError("Title too short or too long!");
}
@@ -406,12 +457,29 @@ if ($_action === "auth") {
$post->content = $message;
$post->postDate = $topic->creationDate;
$post->deleted = false;
+ $post->edited = false;
$db->insert($post);
+ foreach ($attachments as $att) {
+ [
+ "name" => $name,
+ "type" => $type,
+ "tmp_name" => $tmpName,
+ ] = $att;
+ $attachment = new Attachment();
+ $attachment->id = $db->generateId();
+ $attachment->name = $name;
+ $attachment->mimeType = $type;
+ $attachment->postId = $post->id;
+ $attachment->contents = file_get_contents($tmpName);
+
+ $db->insert($attachment);
+ }
+
header("Location: ?_action=viewtopic&topic=" . urlencode($topic->id));
} else {
- _view("template_start", ["_title" => "Forum"]);
+ _view("template_start", ["_title" => "New topic"]);
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
@@ -522,7 +590,7 @@ if ($_action === "auth") {
continue;
$topics[$post->topicId] = $topic;
}
- _view("template_start", ["_title" => "Forum"]);
+ _view("template_start", ["_title" => $user->displayName]);
_view("template_navigation_start");
_view("template_navigation", ["user" => $currentUser]);
_view("template_navigation_end");
@@ -549,12 +617,27 @@ if ($_action === "auth") {
Messaging::error("No attachment exists with this id");
exit;
}
+
+ $name = preg_replace('/[\r\n\t\/]/', '_', $attachment->name);
$extension = pathinfo($attachment->name, PATHINFO_EXTENSION);
- header("Content-Type: " . FileUtils::getMimeTypeForExtension($extension));
+
+ $mime = FileUtils::getMimeTypeForExtension($extension);
+ switch ($mime) {
+ case "text/html":
+ case "text/css":
+ case "text/javascript":
+ case "text/xml":
+ case "application/css":
+ case "application/javascript":
+ case "application/xml":
+ $mime = "text/plain";
+ break;
+ }
+ header("Content-Type: " . $mime);
header("Content-Length: " . strlen($attachment->contents));
header("Cache-Control: no-cache");
- header("Content-Disposition: inline; filename=\"" . $attachment->name . "\"");
+ header("Content-Disposition: inline; filename=\"" . $name . "\"");
echo $attachment->contents;
} elseif ($_action === "profilepicture") {
$userId = $_GET["user"] ?? throw new Exception("Missing user id");
@@ -722,7 +805,7 @@ if ($_action === "auth") {
header("Location: ?_action=viewtopic&topic=" . urlencode($post->topicId));
} else {
- _view("template_start", ["_title" => "Forum"]);
+ _view("template_start", ["_title" => "Delete post"]);
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
@@ -733,6 +816,54 @@ if ($_action === "auth") {
]);
_view("template_end");
}
+} elseif ($_action === "updatepost") {
+ RequestUtils::ensureRequestMethod("POST");
+
+ if (!$currentUser) {
+ http_response_code(403);
+ Messaging::error("You need to be logged in to update posts!");
+ exit;
+ }
+
+ $postId = RequestUtils::getRequiredField("post");
+ $message = RequestUtils::getRequiredField("message");
+
+ $post = new Post();
+ $post->id = $postId;
+
+ if (!$db->fetch($post) || $post->deleted) {
+ http_response_code(404);
+ Messaging::error("No post exists with this id");
+ exit;
+ }
+
+ $topicAuthor = new User();
+ $topicAuthor->id = $post->authorId;
+
+ if (!$db->fetch($topicAuthor))
+ $topicAuthor = null;
+
+ $canEdit = ($currentUser->id === $topicAuthor?->id && $topicAuthor?->hasPermission(UserPermissions::EDIT_OWN_POST))
+ || ($currentUser->hasPermission(UserPermissions::EDIT_OTHER_POST));
+
+ if (!$canEdit) {
+ http_response_code(403);
+ Messaging::error("You don't have permission to edit this post");
+ exit;
+ }
+
+ $confirm = $_POST["confirm"] ?? null;
+
+ $post->content = $message;
+ $post->edited = true;
+
+ if (!$db->update($post)) {
+ http_response_code(500);
+ Messaging::error("Failed to update post");
+ exit;
+ }
+
+ header("Location: ?_action=viewtopic&topic=" . urlencode($post->topicId) . "#post-" . urlencode($postId));
} elseif ($_action === "deletetopic") {
RequestUtils::ensureRequestMethod("POST");
@@ -785,7 +916,7 @@ if ($_action === "auth") {
header("Location: .");
} else {
- _view("template_start", ["_title" => "Forum"]);
+ _view("template_start", ["_title" => "Delete topic"]);
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
@@ -840,8 +971,17 @@ if ($_action === "auth") {
}
header("Location: ./?_action=viewtopic&topic=" . urlencode($topicId));
+} elseif ($_action === "captcha") {
+ $phrase = generateCaptchaText();
+ $builder = new CaptchaBuilder($phrase);
+ $builder->build(192, 48);
+ $_SESSION["captchaPhrase"] = $phrase;
+ header("Content-Type: image/jpeg");
+ header("Pragma: no-cache");
+ header("Cache-Control: no-cache");
+ $builder->save(null, 40);
} elseif ($_action === null) {
- _view("template_start", ["_title" => "Forum"]);
+ _view("template_start");
_view("template_navigation_start");
_view("template_navigation", ["user" => RequestUtils::getAuthorizedUser($db)]);
_view("template_navigation_end");
diff --git a/src/themes/default/theme.css b/src/themes/default/theme.css
new file mode 100644
index 0000000..8feb73e
--- /dev/null
+++ b/src/themes/default/theme.css
@@ -0,0 +1,10 @@
+/**
+ * @theme default
+ * @name Mystic Default
+ * @author Jonas Kohl
+ * @version 1.0.0
+ * @format 1
+ */
+@import url("../../ui/dist/css/bootstrap.min.css");
+@import url("../../ui/dist/css/bootstrap-theme.min.css");
+@import url("../../ui/site.css");
diff --git a/src/themes/slate/theme.css b/src/themes/slate/theme.css
new file mode 100644
index 0000000..a086ce7
--- /dev/null
+++ b/src/themes/slate/theme.css
@@ -0,0 +1,15 @@
+/**
+ * @theme slate
+ * @name Mystic Slate
+ * @author Jonas Kohl
+ * @version 1.0.0
+ * @format 1
+ */
+@import url("../../ui/slate.min.css");
+@import url("../../ui/site.css");
+.post-container-controls {
+ background-image: linear-gradient(transparent, #272b30 56%);
+}
+.post-container-controls::after {
+ background: rgba(0,0,0,0.6);
+}
diff --git a/src/ui/site.css b/src/ui/site.css
index 2979dd9..5e7829a 100644
--- a/src/ui/site.css
+++ b/src/ui/site.css
@@ -16,7 +16,7 @@
html.no-js .js-only.js-only.js-only {
display: none !important;
}
-.panel-body > blockquote {
+.post-content > blockquote {
font-size: 14px;
}
.post-images {
diff --git a/src/ui/theme-default.css b/src/ui/theme-default.css
deleted file mode 100644
index ea0bab8..0000000
--- a/src/ui/theme-default.css
+++ /dev/null
@@ -1,3 +0,0 @@
-@import url("./dist/css/bootstrap.min.css");
-@import url("./dist/css/bootstrap-theme.min.css");
-@import url("./site.css");
diff --git a/src/ui/theme-slate.css b/src/ui/theme-slate.css
deleted file mode 100644
index e72bf12..0000000
--- a/src/ui/theme-slate.css
+++ /dev/null
@@ -1,8 +0,0 @@
-@import url("./slate.min.css");
-@import url("./site.css");
-.post-container-controls {
- background-image: linear-gradient(transparent, #272b30 56%);
-}
-.post-container-controls::after {
- background: rgba(0,0,0,0.6);
-}