diff options
| author | Jonas Kohl | 2024-09-14 17:59:13 +0200 | 
|---|---|---|
| committer | Jonas Kohl | 2024-09-14 17:59:13 +0200 | 
| commit | d8b74761c6cefdd83360d3f2add0e9ccdc6064c7 (patch) | |
| tree | 5e2c947b4a90ae475a7e75897077246e5ea0a948 | |
| parent | 948cead0f11d33adbcf0d08773c716e1b6ebb101 (diff) | |
Even more changes (devel commit messages are useless)
| -rw-r--r-- | .env.example | 4 | ||||
| -rw-r--r-- | src/application/mystic/forum/orm/Post.php | 1 | ||||
| -rw-r--r-- | src/application/views/form_login.php | 4 | ||||
| -rw-r--r-- | src/application/views/form_newtopic.php | 34 | ||||
| -rw-r--r-- | src/application/views/form_register.php | 40 | ||||
| -rw-r--r-- | src/application/views/nav_logged_in.php | 4 | ||||
| -rw-r--r-- | src/application/views/template_end.php | 25 | ||||
| -rw-r--r-- | src/application/views/template_navigation_start.php | 2 | ||||
| -rw-r--r-- | src/application/views/template_start.php | 14 | ||||
| -rw-r--r-- | src/application/views/view_post.php | 32 | ||||
| -rw-r--r-- | src/application/views/view_topic_start.php | 96 | ||||
| -rw-r--r-- | src/application/views/view_user.php | 29 | ||||
| -rw-r--r-- | src/composer.json | 4 | ||||
| -rw-r--r-- | src/composer.lock | 1151 | ||||
| -rw-r--r-- | src/index.php | 166 | ||||
| -rw-r--r-- | src/themes/default/theme.css | 10 | ||||
| -rw-r--r-- | src/themes/slate/theme.css | 15 | ||||
| -rw-r--r-- | src/ui/site.css | 2 | ||||
| -rw-r--r-- | src/ui/theme-default.css | 3 | ||||
| -rw-r--r-- | src/ui/theme-slate.css | 8 | 
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&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&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&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&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"> -    © <?= date("Y") ?> +    © <?= 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&user=<?= htmlentities(urlencode($postAuthor->id)) ?>" alt="" width="64" height="64"> +                <img class="media-object" alt="Profile picture" src="?_action=profilepicture&user=<?= htmlentities(urlencode($postAuthor->id)) ?>" width="64" height="64">              <?php else: ?>                  <a href="?_action=viewuser&user=<?= htmlentities(urlencode($postAuthor->id)) ?>"> -                    <img class="media-object" alt="Profile picture" src="?_action=profilepicture&user=<?= htmlentities(urlencode($postAuthor->id)) ?>" alt="" width="64" height="64"> +                    <img class="media-object" alt="Profile picture" src="?_action=profilepicture&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&attachment=<?= htmlentities(urlencode($attachment->id)) ?>" alt="" width="110"> +                                <img class="image-attachment-image" src="?_action=thumb&attachment=<?= htmlentities(urlencode($attachment->id)) ?>" alt="" width="100">                              </span>                          <?php else: ?>                              <a class="image-attachment" href="?_action=attachment&attachment=<?= htmlentities(urlencode($attachment->id)) ?>" title="<?= htmlentities($attachment->name) ?>"> -                                <img class="image-attachment-image" src="?_action=thumb&attachment=<?= htmlentities(urlencode($attachment->id)) ?>" alt="" width="110"> +                                <img class="image-attachment-image" src="?_action=thumb&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">×</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&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&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&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) ?> • <span class="text-muted">Member since <span class="_date"><?= htmlentities($dateJoined->format("c")); ?></span> +    @<?= htmlentities($user->name) ?> • <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); -} |