diff --git a/assets/less/classes/forum/post.less b/assets/less/classes/forum/post.less index 64838130..e288f69c 100644 --- a/assets/less/classes/forum/post.less +++ b/assets/less/classes/forum/post.less @@ -46,6 +46,7 @@ &__text { margin: 2px; line-height: 1.2em; + flex: 1 1 auto; @media (max-width: @site-mobile-width) { margin: 4px; @@ -178,4 +179,31 @@ &__parser { margin: 0; } + + &__actions { + display: flex; + padding: 1px; + } + + &__action { + padding: 5px 10px; + margin: 1px; + color: inherit; + text-decoration: none; + transition: background-color .2s; + border-radius: 3px; + + &:hover { + background-color: #fff2; + } + } + + &__signature { + background-color: rgba(0, 0, 0, .2); + padding: 2px; + + img { + vertical-align: middle; + } + } } diff --git a/database/2018_12_30_025222_automatically_mark_edited.php b/database/2018_12_30_025222_automatically_mark_edited.php new file mode 100644 index 00000000..316acbdf --- /dev/null +++ b/database/2018_12_30_025222_automatically_mark_edited.php @@ -0,0 +1,20 @@ +exec(" + ALTER TABLE `msz_forum_posts` + CHANGE COLUMN `post_edited` `post_edited` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP AFTER `post_created`; + "); +} + +function migrate_down(PDO $conn): void +{ + $conn->exec(" + ALTER TABLE `msz_forum_posts` + CHANGE COLUMN `post_edited` `post_edited` TIMESTAMP NULL DEFAULT NULL AFTER `post_created`; + "); +} diff --git a/public/forum/posting.php b/public/forum/posting.php index 74b9ff1e..7493719d 100644 --- a/public/forum/posting.php +++ b/public/forum/posting.php @@ -11,28 +11,35 @@ if (user_warning_check_restriction(user_session_current('user_id', 0))) { return; } +$forumPostingModes = [ + 'create', 'edit', 'quote', + 'delete', 'restore', 'nuke', +]; + if (!empty($_POST)) { + $mode = $_POST['post']['mode'] ?? 'create'; + $postId = max(0, (int)($_POST['post']['id'] ?? 0)); $topicId = max(0, (int)($_POST['post']['topic'] ?? 0)); $forumId = max(0, (int)($_POST['post']['forum'] ?? 0)); } else { + $mode = $_GET['m'] ?? 'create'; $postId = max(0, (int)($_GET['p'] ?? 0)); $topicId = max(0, (int)($_GET['t'] ?? 0)); $forumId = max(0, (int)($_GET['f'] ?? 0)); } +if (!in_array($mode, $forumPostingModes, true)) { + echo render_error(400); + return; +} + if (empty($postId) && empty($topicId) && empty($forumId)) { echo render_error(404); return; } if (!empty($postId)) { - $getPost = db_prepare(' - SELECT `post_id`, `topic_id` - FROM `msz_forum_posts` - WHERE `post_id` = :post_id - '); - $getPost->bindValue('post_id', $postId); - $post = $getPost->execute() ? $getPost->fetch(PDO::FETCH_ASSOC) : false; + $post = forum_post_get($postId); if (isset($post['topic_id'])) { // should automatic cross-quoting be a thing? if so, check if $topicId is < 1 first $topicId = (int)$post['topic_id']; @@ -68,7 +75,7 @@ if (empty($forum)) { return; } -$perms = forum_perms_get_user(MSZ_FORUM_PERMS_GENERAL, $forum['forum_id'], user_session_current('user_id', 0)); +$perms = forum_perms_get_user(MSZ_FORUM_PERMS_GENERAL, $forum['forum_id'], user_session_current('user_id')); if ($forum['forum_archived'] || !empty($topic['topic_locked']) @@ -83,6 +90,19 @@ if (!forum_may_have_topics($forum['forum_type'])) { return; } +// edit mode stuff +if ($mode === 'edit') { + if (empty($post)) { + echo render_error(404); + return; + } + + if (!perms_check($perms, $post['poster_id'] === user_session_current('user_id') ? MSZ_FORUM_PERM_EDIT_POST : MSZ_FORUM_PERM_EDIT_ANY_POST)) { + echo render_error(403); + return; + } +} + $notices = []; if (!empty($_POST)) { @@ -122,24 +142,36 @@ if (!empty($_POST)) { } if (empty($notices)) { - if (!empty($topic)) { - forum_topic_bump($topic['topic_id']); - } else { - $topicId = forum_topic_create($forum['forum_id'], user_session_current('user_id', 0), $topicTitle); + switch ($mode) { + case 'create': + if (!empty($topic)) { + forum_topic_bump($topic['topic_id']); + } else { + $topicId = forum_topic_create($forum['forum_id'], user_session_current('user_id', 0), $topicTitle); + } + + $postId = forum_post_create( + $topicId, + $forum['forum_id'], + user_session_current('user_id', 0), + ip_remote_address(), + $postText, + $postParser + ); + forum_topic_mark_read(user_session_current('user_id', 0), $topicId, $forum['forum_id']); + break; + + case 'edit': + if (!forum_post_edit($postId, ip_remote_address(), $postText, $postParser)) { + $notices[] = 'Post edit failed.'; + } + break; } - $postId = forum_post_create( - $topicId, - $forum['forum_id'], - user_session_current('user_id', 0), - ip_remote_address(), - $postText, - $postParser - ); - forum_topic_mark_read(user_session_current('user_id', 0), $topicId, $forum['forum_id']); - - header("Location: /forum/topic.php?p={$postId}#p{$postId}"); - return; + if (empty($notices)) { + header("Location: /forum/topic.php?p={$postId}#p{$postId}"); + return; + } } } } @@ -148,6 +180,10 @@ if (!empty($topic)) { tpl_var('posting_topic', $topic); } +if ($mode === 'edit') { // $post is pretty much sure to be populated at this point + tpl_var('posting_post', $post); +} + // fetches additional data for simulating a forum post $getDisplayInfo = db_prepare(' SELECT u.`user_country`, u.`user_created`, ( @@ -167,4 +203,5 @@ echo tpl_render('forum.posting', [ 'posting_forum' => $forum, 'posting_info' => $displayInfo, 'posting_notices' => $notices, + 'posting_mode' => $mode, ]); diff --git a/src/Forum/post.php b/src/Forum/post.php index c49664b8..6472d61a 100644 --- a/src/Forum/post.php +++ b/src/Forum/post.php @@ -23,6 +23,31 @@ function forum_post_create( return $createPost->execute() ? db_last_insert_id() : 0; } +function forum_post_edit( + int $postId, + string $ipAddress, + string $text, + int $parser = MSZ_PARSER_PLAIN +): bool { + if ($postId < 1) { + return false; + } + + $updatePost = db_prepare(' + UPDATE `msz_forum_posts` + SET `post_ip` = INET6_ATON(:post_ip), + `post_text` = :post_text, + `post_parse` = :post_parse + WHERE `post_id` = :post_id + '); + $updatePost->bindValue('post_id', $postId); + $updatePost->bindValue('post_ip', $ipAddress); + $updatePost->bindValue('post_text', $text); + $updatePost->bindValue('post_parse', $parser); + + return $updatePost->execute(); +} + function forum_post_find(int $postId): array { $getPostInfo = db_prepare(' @@ -43,7 +68,42 @@ function forum_post_find(int $postId): array '); $getPostInfo->bindValue('post_id', $postId); - return $getPostInfo->execute() ? $getPostInfo->fetch() : []; + return $getPostInfo->execute() ? $getPostInfo->fetch(PDO::FETCH_ASSOC) : []; +} + +function forum_post_get(int $postId, bool $allowDeleted = false): array +{ + $getPost = db_prepare(sprintf( + ' + SELECT + p.`post_id`, p.`post_text`, p.`post_created`, p.`post_parse`, + p.`topic_id`, p.`post_deleted`, p.`post_edited`, + INET6_NTOA(p.`post_ip`) as `post_ip`, + u.`user_id` as `poster_id`, + u.`username` as `poster_name`, + u.`user_created` as `poster_joined`, + u.`user_country` as `poster_country`, + COALESCE(u.`user_colour`, r.`role_colour`) as `poster_colour`, + ( + SELECT COUNT(`post_id`) + FROM `msz_forum_posts` + WHERE `user_id` = p.`user_id` + AND `post_deleted` IS NULL + ) as `poster_post_count` + FROM `msz_forum_posts` as p + LEFT JOIN `msz_users` as u + ON u.`user_id` = p.`user_id` + LEFT JOIN `msz_roles` as r + ON r.`role_id` = u.`display_role` + WHERE `post_id` = :post_id + %1$s + ORDER BY `post_id` + ', + $allowDeleted ? '' : 'AND `post_deleted` IS NULL' + )); + $getPost->bindValue('post_id', $postId); + $post = $getPost->execute() ? $getPost->fetch(PDO::FETCH_ASSOC) : false; + return $post ? $post : []; } function forum_post_listing(int $topicId, int $offset = 0, int $take = 0, bool $showDeleted = false): array @@ -86,5 +146,5 @@ function forum_post_listing(int $topicId, int $offset = 0, int $take = 0, bool $ $getPosts->bindValue('take', $take); } - return $getPosts->execute() ? $getPosts->fetchAll() : []; + return $getPosts->execute() ? $getPosts->fetchAll(PDO::FETCH_ASSOC) : []; } diff --git a/templates/forum/macros.twig b/templates/forum/macros.twig index 841b75f5..1a812790 100644 --- a/templates/forum/macros.twig +++ b/templates/forum/macros.twig @@ -321,19 +321,24 @@ {% endmacro %} -{% macro forum_post_listing(posts, opening_post_id) %} +{% macro forum_post_listing(posts, opening_post_id, user_id, perms) %} {% from _self import forum_post_entry %} {% for post in posts %} - {{ forum_post_entry(post, post.post_id == opening_post_id) }} + {{ forum_post_entry( + post, + post.post_id == opening_post_id, + perms|perms_check(constant('MSZ_FORUM_PERM_CREATE_POST')), + perms|perms_check(constant(user_id == post.poster_id ? 'MSZ_FORUM_PERM_EDIT_POST' : 'MSZ_FORUM_PERM_EDIT_ANY_POST')), + perms|perms_check(constant(user_id == post.poster_id ? 'MSZ_FORUM_PERM_DELETE_POST' : 'MSZ_FORUM_PERM_DELETE_ANY_POST')) + ) }} {% endfor %} {% endmacro %} -{% macro forum_post_entry(post, is_original_post, is_original_poster) %} - {% set is_original_post = is_original_post|default(false) %} - {% set is_original_poster = is_original_poster|default(false) %} +{% macro forum_post_entry(post, is_original_post, can_post, can_edit, can_delete) %} + {% set is_deleted = post.post_deleted is not null %} -