diff --git a/assets/less/classes/forum/header.less b/assets/less/classes/forum/header.less index 6c9a1a35..71e1dc4c 100644 --- a/assets/less/classes/forum/header.less +++ b/assets/less/classes/forum/header.less @@ -47,4 +47,20 @@ font-size: .9em; } } + + &__actions { + display: flex; + } + + &__action { + margin-right: 10px; + color: inherit; + text-decoration: none; + transition: color .2s; + + &:hover, + &:focus { + color: var(--accent-colour); + } + } } diff --git a/public/forum/forum.php b/public/forum/forum.php index ced41c73..4737e74c 100644 --- a/public/forum/forum.php +++ b/public/forum/forum.php @@ -50,7 +50,7 @@ $topics = $forumMayHaveTopics $forumUserId, $topicsOffset, $forumPagination['range'], - perms_check($perms, MSZ_FORUM_PERM_DELETE_TOPIC | MSZ_FORUM_PERM_DELETE_ANY_POST) + perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST) ) : []; diff --git a/public/forum/index.php b/public/forum/index.php index 1279fb82..2bbdb4ea 100644 --- a/public/forum/index.php +++ b/public/forum/index.php @@ -23,7 +23,7 @@ switch ($_GET['m'] ?? '') { $categories[$key]['forum_subforums'] = forum_get_children( $category['forum_id'], user_session_current('user_id', 0), - perms_check($category['forum_permissions'], MSZ_FORUM_PERM_DELETE_TOPIC | MSZ_FORUM_PERM_DELETE_ANY_POST) + perms_check($category['forum_permissions'], MSZ_FORUM_PERM_DELETE_ANY_POST) ); foreach ($categories[$key]['forum_subforums'] as $skey => $sub) { @@ -35,7 +35,7 @@ switch ($_GET['m'] ?? '') { = forum_get_children( $sub['forum_id'], user_session_current('user_id', 0), - perms_check($sub['forum_permissions'], MSZ_FORUM_PERM_DELETE_TOPIC | MSZ_FORUM_PERM_DELETE_ANY_POST), + perms_check($sub['forum_permissions'], MSZ_FORUM_PERM_DELETE_ANY_POST), true ); } diff --git a/public/forum/post.php b/public/forum/post.php index 5c4b7906..d60a4a39 100644 --- a/public/forum/post.php +++ b/public/forum/post.php @@ -81,7 +81,7 @@ switch ($postMode) { break; case MSZ_E_FORUM_POST_DELETE_OLD: $responseCode = 401; - $canDeleteMsg = 'This post has existed for too long, ask a moderator to remove if it absolutely necessary.'; + $canDeleteMsg = 'This post has existed for too long. Ask a moderator to remove if it absolutely necessary.'; break; case MSZ_E_FORUM_POST_DELETE_PERM: $responseCode = 401; @@ -122,9 +122,11 @@ switch ($postMode) { echo tpl_render('forum.confirm', [ 'title' => 'Confirm post deletion', 'class' => 'far fa-trash-alt', - 'mode' => 'delete', 'message' => sprintf('You are about to delete post #%d. Are you sure about that?', $postInfo['post_id']), - 'post' => $postInfo, + 'params' => [ + 'p' => $postInfo['post_id'], + 'm' => 'delete', + ], ]); break; } @@ -163,9 +165,11 @@ switch ($postMode) { echo tpl_render('forum.confirm', [ 'title' => 'Confirm post nuke', 'class' => 'fas fa-radiation', - 'mode' => 'nuke', 'message' => sprintf('You are about to PERMANENTLY DELETE post #%d. Are you sure about that?', $postInfo['post_id']), - 'post' => $postInfo, + 'params' => [ + 'p' => $postInfo['post_id'], + 'm' => 'nuke', + ], ]); break; } @@ -199,9 +203,11 @@ switch ($postMode) { echo tpl_render('forum.confirm', [ 'title' => 'Confirm post restore', 'class' => 'fas fa-magic', - 'mode' => 'restore', 'message' => sprintf('You are about to restore post #%d. Are you sure about that?', $postInfo['post_id']), - 'post' => $postInfo, + 'params' => [ + 'p' => $postInfo['post_id'], + 'm' => 'restore', + ], ]); break; } diff --git a/public/forum/posting.php b/public/forum/posting.php index 12714136..27c56fc0 100644 --- a/public/forum/posting.php +++ b/public/forum/posting.php @@ -46,7 +46,7 @@ if (!empty($postId)) { } if (!empty($topicId)) { - $topic = forum_topic_fetch($topicId); + $topic = forum_topic_get($topicId); if (isset($topic['forum_id'])) { $forumId = (int)$topic['forum_id']; diff --git a/public/forum/topic.php b/public/forum/topic.php index e96916d7..06d5f150 100644 --- a/public/forum/topic.php +++ b/public/forum/topic.php @@ -14,7 +14,7 @@ if ($topicId < 1 && $postId > 0) { } } -$topic = forum_topic_fetch($topicId, $topicUserId); +$topic = forum_topic_get($topicId, true); $perms = $topic ? forum_perms_get_user(MSZ_FORUM_PERMS_GENERAL, $topic['forum_id'], $topicUserId) : 0; @@ -23,7 +23,10 @@ if (user_warning_check_restriction($topicUserId)) { $perms &= ~MSZ_FORUM_PERM_SET_WRITE; } -if (!$topic || ($topic['topic_deleted'] !== null && !perms_check($perms, MSZ_FORUM_PERM_DELETE_TOPIC))) { +$topicIsDeleted = !empty($topic['topic_deleted']); +$canDeleteAny = perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST); + +if (!$topic || ($topicIsDeleted && !$canDeleteAny)) { echo render_error(404); return; } @@ -33,7 +36,266 @@ if (!perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM)) { return; } -$topicPagination = pagination_create($topic['topic_post_count'], MSZ_FORUM_POSTS_PER_PAGE); +$topicIsLocked = !empty($topic['topic_locked']); +$topicIsArchived = !empty($topic['topic_archived']); +$topicPostsTotal = (int)($topic['topic_post_count'] + $topic['topic_deleted_post_count']); +$topicIsFrozen = $topicIsArchived || $topicIsDeleted; +$canDeleteOwn = !$topicIsFrozen && !$topicIsLocked && perms_check($perms, MSZ_FORUM_PERM_DELETE_POST); +$canBumpTopic = !$topicIsFrozen && perms_check($perms, MSZ_FORUM_PERM_BUMP_TOPIC); +$canLockTopic = !$topicIsFrozen && perms_check($perms, MSZ_FORUM_PERM_LOCK_TOPIC); +$canNukeOrRestore = $canDeleteAny && $topicIsDeleted; +$canDelete = !$topicIsDeleted && ( + $canDeleteAny || ( + $topicPostsTotal > 0 + && $topicPostsTotal <= MSZ_FORUM_TOPIC_DELETE_POST_LIMIT + && $canDeleteOwn + && $topic['author_user_id'] === $topicUserId + ) +); + +$moderationMode = (string)($_GET['m'] ?? ''); +$validModerationModes = [ + 'delete', 'restore', 'nuke', + 'bump', 'lock', 'unlock', +]; + +if (in_array($moderationMode, $validModerationModes, true)) { + $redirect = !empty($_SERVER['HTTP_REFERER']) && empty($_SERVER['HTTP_X_MISUZU_XHR']) ? $_SERVER['HTTP_REFERER'] : ''; + $isXHR = !$redirect; + + if ($isXHR) { + header('Content-Type: application/json; charset=utf-8'); + } elseif (!is_local_url($redirect)) { + echo render_info('Possible request forgery detected.', 403); + return; + } + + if (!csrf_verify('forum_post', $_GET['csrf'] ?? '') && !csrf_verify('forum_post', csrf_http_header_parse($_SERVER['HTTP_X_MISUZU_CSRF'] ?? '')['token'])) { + echo render_info_or_json($isXHR, "Couldn't verify this request, please refresh the page and try again.", 403); + return; + } + + header(csrf_http_header('forum_post')); + + if (!user_session_active()) { + echo render_info_or_json($isXHR, 'You must be logged in to manage posts.', 401); + return; + } + + if (user_warning_check_expiration($topicUserId, MSZ_WARN_BAN) > 0) { + echo render_info_or_json($isXHR, 'You have been banned, check your profile for more information.', 403); + return; + } + if (user_warning_check_expiration($topicUserId, MSZ_WARN_SILENCE) > 0) { + echo render_info_or_json($isXHR, 'You have been silenced, check your profile for more information.', 403); + return; + } + + switch ($_GET['m'] ?? '') { + case 'delete': + $canDeleteCode = forum_topic_can_delete($topic, $topicUserId); + $canDeleteMsg = ''; + $responseCode = 200; + + switch ($canDeleteCode) { + case MSZ_E_FORUM_TOPIC_DELETE_USER: + $responseCode = 401; + $canDeleteMsg = 'You must be logged in to delete topics.'; + break; + case MSZ_E_FORUM_TOPIC_DELETE_TOPIC: + $responseCode = 404; + $canDeleteMsg = "This topic doesn't exist."; + break; + case MSZ_E_FORUM_TOPIC_DELETE_DELETED: + $responseCode = 404; + $canDeleteMsg = 'This topic has already been marked as deleted.'; + break; + case MSZ_E_FORUM_TOPIC_DELETE_OWNER: + $responseCode = 403; + $canDeleteMsg = 'You can only delete your own topics.'; + break; + case MSZ_E_FORUM_TOPIC_DELETE_OLD: + $responseCode = 401; + $canDeleteMsg = 'This topic has existed for too long. Ask a moderator to remove if it absolutely necessary.'; + break; + case MSZ_E_FORUM_TOPIC_DELETE_PERM: + $responseCode = 401; + $canDeleteMsg = 'You are not allowed to delete topics.'; + break; + case MSZ_E_FORUM_TOPIC_DELETE_POSTS: + $responseCode = 403; + $canDeleteMsg = 'This topic already has replies, you may no longer delete it. Ask a moderator to remove if it absolutely necessary.'; + break; + case MSZ_E_FORUM_TOPIC_DELETE_OK: + break; + default: + $responseCode = 500; + $canDeleteMsg = sprintf('Unknown error \'%d\'', $canDelete); + } + + if ($canDeleteCode !== MSZ_E_FORUM_TOPIC_DELETE_OK) { + if ($isXHR) { + http_response_code($responseCode); + echo json_encode([ + 'success' => false, + 'topic_id' => $topic['topic_id'], + 'code' => $canDeleteCode, + 'message' => $canDeleteMsg, + ]); + break; + } + + echo render_info($canDeleteMsg, $responseCode); + break; + } + + if (!$isXHR) { + if (isset($_GET['confirm']) && $_GET['confirm'] !== '1') { + header("Location: /forum/topic.php?t={$topic['topic_id']}"); + break; + } elseif (!isset($_GET['confirm'])) { + echo tpl_render('forum.confirm', [ + 'title' => 'Confirm topic deletion', + 'class' => 'far fa-trash-alt', + 'message' => sprintf('You are about to delete topic #%d. Are you sure about that?', $topic['topic_id']), + 'params' => [ + 't' => $topic['topic_id'], + 'm' => 'delete', + ], + ]); + break; + } + } + + $deleteTopic = forum_topic_delete($topic['topic_id']); + + if ($isXHR) { + echo json_encode([ + 'success' => $deleteTopic, + 'topic_id' => $topic['topic_id'], + 'message' => $deleteTopic ? 'Topic deleted!' : 'Failed to delete topic.', + ]); + break; + } + + if (!$deleteTopic) { + echo render_error(500); + break; + } + + header('Location: /forum/forum.php?f=' . $topic['forum_id']); + break; + + case 'restore': + if (!$canNukeOrRestore) { + echo render_error(403); + break; + } + + if (!$isXHR) { + if (isset($_GET['confirm']) && $_GET['confirm'] !== '1') { + header("Location: /forum/topic.php?t={$topic['topic_id']}"); + break; + } elseif (!isset($_GET['confirm'])) { + echo tpl_render('forum.confirm', [ + 'title' => 'Confirm topic restore', + 'class' => 'fas fa-magic', + 'message' => sprintf('You are about to restore topic #%d. Are you sure about that?', $topic['topic_id']), + 'params' => [ + 't' => $topic['topic_id'], + 'm' => 'restore', + ], + ]); + break; + } + } + + $restoreTopic = forum_topic_restore($topic['topic_id']); + + if (!$restoreTopic) { + echo render_error(500); + break; + } + + http_response_code(204); + + if (!$isXHR) { + header('Location: /forum/forum.php?f=' . $topic['forum_id']); + } + break; + + case 'nuke': + if (!$canNukeOrRestore) { + echo render_error(403); + break; + } + + if (!$isXHR) { + if (isset($_GET['confirm']) && $_GET['confirm'] !== '1') { + header("Location: /forum/topic.php?t={$topic['topic_id']}"); + break; + } elseif (!isset($_GET['confirm'])) { + echo tpl_render('forum.confirm', [ + 'title' => 'Confirm topic nuke', + 'class' => 'fas fa-radiation', + 'message' => sprintf('You are about to PERMANENTLY DELETE topic #%d. Are you sure about that?', $topic['topic_id']), + 'params' => [ + 't' => $topic['topic_id'], + 'm' => 'nuke', + ], + ]); + break; + } + } + + $nukeTopic = forum_topic_nuke($topic['topic_id']); + + if (!$nukeTopic) { + echo render_error(500); + break; + } + + http_response_code(204); + + if (!$isXHR) { + header('Location: /forum/forum.php?f=' . $topic['forum_id']); + } + break; + + case 'bump': + if ($canBumpTopic) { + forum_topic_bump($topic['topic_id']); + } + + header('Location: /forum/topic.php?t=' . $topic['topic_id']); + break; + + case 'lock': + if ($canLockTopic && !$topicIsLocked) { + forum_topic_lock($topic['topic_id']); + } + + header('Location: /forum/topic.php?t=' . $topic['topic_id']); + break; + + case 'unlock': + if ($canLockTopic && $topicIsLocked) { + forum_topic_unlock($topic['topic_id']); + } + + header('Location: /forum/topic.php?t=' . $topic['topic_id']); + break; + } + return; +} + +$topicPosts = $topic['topic_post_count']; + +if ($canDeleteAny) { + $topicPosts += $topic['topic_deleted_post_count']; +} + +$topicPagination = pagination_create($topicPosts, MSZ_FORUM_POSTS_PER_PAGE); if (isset($postInfo['preceeding_post_count'])) { $postsPage = floor($postInfo['preceeding_post_count'] / $topicPagination['range']) + 1; @@ -60,8 +322,7 @@ if (!$posts) { return; } -$canReply = empty($topic['topic_archived']) && empty($topic['topic_locked']) && empty($topic['topic_deleted']) - && perms_check($perms, MSZ_FORUM_PERM_CREATE_POST); +$canReply = !$topicIsArchived && !$topicIsLocked && !$topicIsDeleted && perms_check($perms, MSZ_FORUM_PERM_CREATE_POST); forum_topic_mark_read($topicUserId, $topic['topic_id'], $topic['forum_id']); @@ -72,4 +333,8 @@ echo tpl_render('forum.topic', [ 'topic_posts' => $posts, 'can_reply' => $canReply, 'topic_pagination' => $topicPagination, + 'topic_can_delete' => $canDelete, + 'topic_can_nuke_or_restore' => $canNukeOrRestore, + 'topic_can_bump' => $canBumpTopic, + 'topic_can_lock' => $canLockTopic, ]); diff --git a/src/Forum/forum.php b/src/Forum/forum.php index 7b3ed293..536524cd 100644 --- a/src/Forum/forum.php +++ b/src/Forum/forum.php @@ -5,12 +5,13 @@ define('MSZ_FORUM_PERM_LIST_FORUM', 1); // can see stats, but will get error whe define('MSZ_FORUM_PERM_VIEW_FORUM', 1 << 1); define('MSZ_FORUM_PERM_CREATE_TOPIC', 1 << 10); -define('MSZ_FORUM_PERM_DELETE_TOPIC', 1 << 11); // how is this different from MSZ_FORUM_PERM_DELETE_ANY_POST? +//define('MSZ_FORUM_PERM_DELETE_TOPIC', 1 << 11); // use MSZ_FORUM_PERM_DELETE_ANY_POST instead define('MSZ_FORUM_PERM_MOVE_TOPIC', 1 << 12); define('MSZ_FORUM_PERM_LOCK_TOPIC', 1 << 13); define('MSZ_FORUM_PERM_STICKY_TOPIC', 1 << 14); define('MSZ_FORUM_PERM_ANNOUNCE_TOPIC', 1 << 15); define('MSZ_FORUM_PERM_GLOBAL_ANNOUNCE_TOPIC', 1 << 16); +define('MSZ_FORUM_PERM_BUMP_TOPIC', 1 << 17); define('MSZ_FORUM_PERM_CREATE_POST', 1 << 20); define('MSZ_FORUM_PERM_EDIT_POST', 1 << 21); @@ -23,7 +24,6 @@ define('MSZ_FORUM_PERM_SET_READ', MSZ_FORUM_PERM_LIST_FORUM | MSZ_FORUM_PERM_VIE define( 'MSZ_FORUM_PERM_SET_WRITE', MSZ_FORUM_PERM_CREATE_TOPIC - | MSZ_FORUM_PERM_DELETE_TOPIC | MSZ_FORUM_PERM_MOVE_TOPIC | MSZ_FORUM_PERM_LOCK_TOPIC | MSZ_FORUM_PERM_STICKY_TOPIC @@ -34,6 +34,7 @@ define( | MSZ_FORUM_PERM_EDIT_ANY_POST | MSZ_FORUM_PERM_DELETE_POST | MSZ_FORUM_PERM_DELETE_ANY_POST + | MSZ_FORUM_PERM_BUMP_TOPIC ); define('MSZ_FORUM_TYPE_DISCUSSION', 0); diff --git a/src/Forum/post.php b/src/Forum/post.php index 0f7686a4..7cf9409d 100644 --- a/src/Forum/post.php +++ b/src/Forum/post.php @@ -72,7 +72,7 @@ function forum_post_find(int $postId, int $userId): array WHERE p.`post_id` = :post_id ', forum_perms_get_user_sql(MSZ_FORUM_PERMS_GENERAL, 'p.`forum_id`'), - MSZ_FORUM_PERM_DELETE_TOPIC | MSZ_FORUM_PERM_DELETE_ANY_POST + MSZ_FORUM_PERM_DELETE_ANY_POST )); $getPostInfo->bindValue('post_id', $postId); $getPostInfo->bindValue('perm_user_id_user', $userId); @@ -183,7 +183,7 @@ define('MSZ_FORUM_POST_DELETE_LIMIT', 60 * 60 * 24 * 7); // $postId can also be a the return value of forum_post_get if you already grabbed it once before function forum_post_can_delete($postId, ?int $userId = null): int { - if (($userId !== null && $userId < 1) || $postId < 1) { + if ($userId !== null && $userId < 1) { return MSZ_E_FORUM_POST_DELETE_USER; } diff --git a/src/Forum/topic.php b/src/Forum/topic.php index e73b379f..93efa105 100644 --- a/src/Forum/topic.php +++ b/src/Forum/topic.php @@ -69,37 +69,41 @@ function forum_topic_update(int $topicId, ?string $title, ?int $type = null): bo return $updateTopic->execute(); } -function forum_topic_fetch(int $topicId, int $userId = 0): array +function forum_topic_get(int $topicId, bool $allowDeleted = false): array { $getTopic = db_prepare(sprintf( ' SELECT t.`topic_id`, t.`forum_id`, t.`topic_title`, t.`topic_type`, t.`topic_locked`, t.`topic_created`, f.`forum_archived` as `topic_archived`, t.`topic_deleted`, t.`topic_bumped`, - ((%s) & %d) as `can_view_deleted`, - ( - SELECT MIN(`post_id`) - FROM `msz_forum_posts` - WHERE `topic_id` = t.`topic_id` - AND (`can_view_deleted` OR `post_deleted` IS NULL) - ) as `topic_first_post_id`, + fp.`topic_id` as `author_post_id`, fp.`user_id` as `author_user_id`, ( SELECT COUNT(`post_id`) FROM `msz_forum_posts` WHERE `topic_id` = t.`topic_id` - AND (`can_view_deleted` OR `post_deleted` IS NULL) - ) as `topic_post_count` + AND `post_deleted` IS NULL + ) as `topic_post_count`, + ( + SELECT COUNT(`post_id`) + FROM `msz_forum_posts` + WHERE `topic_id` = t.`topic_id` + AND `post_deleted` IS NOT NULL + ) as `topic_deleted_post_count` FROM `msz_forum_topics` as t LEFT JOIN `msz_forum_categories` as f ON f.`forum_id` = t.`forum_id` + LEFT JOIN `msz_forum_posts` as fp + ON fp.`post_id` = ( + SELECT MIN(`post_id`) + FROM `msz_forum_posts` + WHERE `topic_id` = t.`topic_id` + ) WHERE t.`topic_id` = :topic_id + %s ', - forum_perms_get_user_sql(MSZ_FORUM_PERMS_GENERAL, 't.`forum_id`'), - MSZ_FORUM_PERM_DELETE_TOPIC | MSZ_FORUM_PERM_DELETE_ANY_POST + $allowDeleted ? '' : 'AND t.`topic_deleted` IS NULL' )); $getTopic->bindValue('topic_id', $topicId); - $getTopic->bindValue('perm_user_id_user', $userId); - $getTopic->bindValue('perm_user_id_role', $userId); return db_fetch($getTopic); } @@ -224,3 +228,191 @@ function forum_topic_listing(int $forumId, int $userId, int $offset = 0, int $ta return db_fetch_all($getTopics); } + +function forum_topic_lock(int $topicId): bool +{ + if ($topicId < 1) { + return false; + } + + $markLocked = db_prepare(' + UPDATE `msz_forum_topics` + SET `topic_locked` = NOW() + WHERE `topic_id` = :topic + AND `topic_locked` IS NULL + '); + $markLocked->bindValue('topic', $topicId); + + return $markLocked->execute(); +} + +function forum_topic_unlock(int $topicId): bool +{ + if ($topicId < 1) { + return false; + } + + $markUnlocked = db_prepare(' + UPDATE `msz_forum_topics` + SET `topic_locked` = NULL + WHERE `topic_id` = :topic + AND `topic_locked` IS NOT NULL + '); + $markUnlocked->bindValue('topic', $topicId); + + return $markUnlocked->execute(); +} + +define('MSZ_E_FORUM_TOPIC_DELETE_OK', 0); // deleting is fine +define('MSZ_E_FORUM_TOPIC_DELETE_USER', 1); // invalid user +define('MSZ_E_FORUM_TOPIC_DELETE_TOPIC', 2); // topic doesn't exist +define('MSZ_E_FORUM_TOPIC_DELETE_DELETED', 3); // topic is already marked as deleted +define('MSZ_E_FORUM_TOPIC_DELETE_OWNER', 4); // you may only delete your own topics +define('MSZ_E_FORUM_TOPIC_DELETE_OLD', 5); // topic has existed for too long to be deleted +define('MSZ_E_FORUM_TOPIC_DELETE_PERM', 6); // you aren't allowed to delete topics +define('MSZ_E_FORUM_TOPIC_DELETE_POSTS', 7); // the topic already has replies + +// only allow topics made within a day of posting to be deleted by normal users +define('MSZ_FORUM_TOPIC_DELETE_TIME_LIMIT', 60 * 60 * 24); + +// only allow topics with a single post to be deleted, includes soft deleted posts +define('MSZ_FORUM_TOPIC_DELETE_POST_LIMIT', 1); + +// set $userId to null for system request, make sure this is NEVER EVER null on user request +// $topicId can also be a the return value of forum_topic_get if you already grabbed it once before +function forum_topic_can_delete($topicId, ?int $userId = null): int +{ + if ($userId !== null && $userId < 1) { + return MSZ_E_FORUM_TOPIC_DELETE_USER; + } + + if (is_array($topicId)) { + $topic = $topicId; + } else { + $topic = forum_topic_get((int)$topicId, true); + } + + if (empty($topic)) { + return MSZ_E_FORUM_TOPIC_DELETE_TOPIC; + } + + $isSystemReq = $userId === null; + $perms = $isSystemReq ? 0 : forum_perms_get_user(MSZ_FORUM_PERMS_GENERAL, $topic['forum_id'], $userId); + $canDeleteAny = $isSystemReq ? true : perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST); + $canViewPost = $isSystemReq ? true : perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM); + $postIsDeleted = !empty($topic['topic_deleted']); + + if (!$canViewPost) { + return MSZ_E_FORUM_TOPIC_DELETE_TOPIC; + } + + if ($postIsDeleted) { + return $canDeleteAny ? MSZ_E_FORUM_TOPIC_DELETE_DELETED : MSZ_E_FORUM_TOPIC_DELETE_TOPIC; + } + + if ($isSystemReq) { + return MSZ_E_FORUM_TOPIC_DELETE_OK; + } + + if (!$canDeleteAny) { + if (!perms_check($perms, MSZ_FORUM_PERM_DELETE_POST)) { + return MSZ_E_FORUM_TOPIC_DELETE_PERM; + } + + if ($topic['author_user_id'] !== $userId) { + return MSZ_E_FORUM_TOPIC_DELETE_OWNER; + } + + if (strtotime($topic['topic_created']) <= time() - MSZ_FORUM_TOPIC_DELETE_TIME_LIMIT) { + return MSZ_E_FORUM_TOPIC_DELETE_OLD; + } + + $totalReplies = $topic['topic_post_count'] + $topic['topic_deleted_post_count']; + + if ($totalReplies > MSZ_E_FORUM_TOPIC_DELETE_POSTS) { + return MSZ_E_FORUM_TOPIC_DELETE_POSTS; + } + } + + return MSZ_E_FORUM_TOPIC_DELETE_OK; +} + +function forum_topic_delete(int $topicId): bool +{ + if ($topicId < 1) { + return false; + } + + $markTopicDeleted = db_prepare(' + UPDATE `msz_forum_topics` + SET `topic_deleted` = NOW() + WHERE `topic_id` = :topic + AND `topic_deleted` IS NULL + '); + $markTopicDeleted->bindValue('topic', $topicId); + + if (!$markTopicDeleted->execute()) { + return false; + } + + $markPostsDeleted = db_prepare(' + UPDATE `msz_forum_posts` as p + SET p.`post_deleted` = ( + SELECT `topic_deleted` + FROM `msz_forum_topics` + WHERE `topic_id` = p.`topic_id` + ) + WHERE p.`topic_id` = :topic + AND p.`post_deleted` IS NULL + '); + $markPostsDeleted->bindValue('topic', $topicId); + + return $markPostsDeleted->execute(); +} + +function forum_topic_restore(int $topicId): bool +{ + if ($topicId < 1) { + return false; + } + + $markPostsRestored = db_prepare(' + UPDATE `msz_forum_posts` as p + SET p.`post_deleted` = NULL + WHERE p.`topic_id` = :topic + AND p.`post_deleted` = ( + SELECT `topic_deleted` + FROM `msz_forum_topics` + WHERE `topic_id` = p.`topic_id` + ) + '); + $markPostsRestored->bindValue('topic', $topicId); + + if (!$markPostsRestored->execute()) { + return false; + } + + $markTopicRestored = db_prepare(' + UPDATE `msz_forum_topics` + SET `topic_deleted` = NULL + WHERE `topic_id` = :topic + AND `topic_deleted` IS NOT NULL + '); + $markTopicRestored->bindValue('topic', $topicId); + + return $markTopicRestored->execute(); +} + +function forum_topic_nuke(int $topicId): bool +{ + if ($topicId < 1) { + return false; + } + + $nukeTopic = db_prepare(' + DELETE FROM `msz_forum_topics` + WHERE `topic_id` = :topic + '); + $nukeTopic->bindValue('topic', $topicId); + return $nukeTopic->execute(); +} diff --git a/src/manage.php b/src/manage.php index b1b2692d..01fbbb00 100644 --- a/src/manage.php +++ b/src/manage.php @@ -357,11 +357,6 @@ function manage_forum_perms_list(array $rawPerms): array 'title' => 'Can create topics.', 'perm' => MSZ_FORUM_PERM_CREATE_TOPIC, ], - [ - 'section' => 'can-delete-topic', - 'title' => 'Can delete topics (required a post delete permission).', - 'perm' => MSZ_FORUM_PERM_DELETE_TOPIC, - ], [ 'section' => 'can-move-topic', 'title' => 'Can move topics between forums.', @@ -387,6 +382,11 @@ function manage_forum_perms_list(array $rawPerms): array 'title' => 'Can make topics global announcements.', 'perm' => MSZ_FORUM_PERM_GLOBAL_ANNOUNCE_TOPIC, ], + [ + 'section' => 'can-bump-topic', + 'title' => 'Can bump topics without posting a reply.', + 'perm' => MSZ_FORUM_PERM_BUMP_TOPIC, + ], [ 'section' => 'can-create-post', 'title' => 'Can make posts (reply only, if create topic is disallowed).', diff --git a/templates/forum/confirm.twig b/templates/forum/confirm.twig index e4bbf6bb..f299ef08 100644 --- a/templates/forum/confirm.twig +++ b/templates/forum/confirm.twig @@ -8,9 +8,10 @@ {% block content %}
{{ container_title(' ' ~ title) }} - {{ input_csrf('forum_post') }} - - + {{ input_csrf(csrf|default('forum_post')) }} + {% for name, value in params %} + + {% endfor %}
{{ message|default('Are you sure you w') }} diff --git a/templates/forum/macros.twig b/templates/forum/macros.twig index 52bceef1..a4eed6ab 100644 --- a/templates/forum/macros.twig +++ b/templates/forum/macros.twig @@ -21,7 +21,7 @@
{% endmacro %} -{% macro forum_header(title, breadcrumbs, omit_last_breadcrumb, title_url) %} +{% macro forum_header(title, breadcrumbs, omit_last_breadcrumb, title_url, actions) %}
{% if breadcrumbs is iterable and breadcrumbs|length > 0 %}
@@ -50,6 +50,18 @@
{% endif %} {% endif %} + + {% if actions is iterable and actions|length > 0 %} +
+ {% for action in actions %} + {% if action.display is not defined or action.display %} + + {{ action.html|raw }} + + {% endif %} + {% endfor %} +
+ {% endif %}
{% endmacro %} diff --git a/templates/forum/topic.twig b/templates/forum/topic.twig index d87beb43..b874885d 100644 --- a/templates/forum/topic.twig +++ b/templates/forum/topic.twig @@ -16,11 +16,44 @@ 'page': topic_pagination.page > 1 ? topic_pagination.page : 0, }) %} +{% set forum_post_csrf = csrf_token('forum_post') %} {% set topic_tools = forum_topic_tools(topic_info, topic_pagination, can_reply) %} {% set topic_notice = forum_topic_locked(topic_info.topic_locked, topic_info.topic_archived) %} +{% set topic_actions = [ + { + 'html': ' Delete', + 'url': url_construct('/forum/topic.php', {'t': topic_info.topic_id, 'm': 'delete', 'csrf[forum_post]': forum_post_csrf}), + 'display': topic_can_delete, + }, + { + 'html': ' Restore', + 'url': url_construct('/forum/topic.php', {'t': topic_info.topic_id, 'm': 'restore', 'csrf[forum_post]': forum_post_csrf}), + 'display': topic_can_nuke_or_restore, + }, + { + 'html': ' Permanently Delete', + 'url': url_construct('/forum/topic.php', {'t': topic_info.topic_id, 'm': 'nuke', 'csrf[forum_post]': forum_post_csrf}), + 'display': topic_can_nuke_or_restore, + }, + { + 'html': ' Bump', + 'url': url_construct('/forum/topic.php', {'t': topic_info.topic_id, 'm': 'bump', 'csrf[forum_post]': forum_post_csrf}), + 'display': topic_can_bump, + }, + { + 'html': ' Lock', + 'url': url_construct('/forum/topic.php', {'t': topic_info.topic_id, 'm': 'lock', 'csrf[forum_post]': forum_post_csrf}), + 'display': topic_can_lock and topic_info.topic_locked is null, + }, + { + 'html': ' Unlock', + 'url': url_construct('/forum/topic.php', {'t': topic_info.topic_id, 'm': 'unlock', 'csrf[forum_post]': forum_post_csrf}), + 'display': topic_can_lock and topic_info.topic_locked is not null, + }, +] %} {% block content %} - {{ forum_header(topic_info.topic_title, topic_breadcrumbs, false, canonical_url) }} + {{ forum_header(topic_info.topic_title, topic_breadcrumbs, false, canonical_url, topic_actions) }} {{ topic_notice }} {{ topic_tools }} {{ forum_post_listing(topic_posts, current_user.user_id|default(0), topic_perms) }} diff --git a/utility.php b/utility.php index 96f66d0e..6e27d671 100644 --- a/utility.php +++ b/utility.php @@ -258,7 +258,7 @@ function render_info_or_json(bool $json, string $message, int $httpCode = 200, s http_response_code($httpCode); if ($json) { - return json_encode([($error ? 'error' : 'message') => $message]); + return json_encode([($error ? 'error' : 'message') => $message, 'success' => $error]); } return render_info($message, $httpCode, $template);