Added some topic moderation tools.

This commit is contained in:
flash 2019-01-12 00:00:53 +01:00
parent 93357f90e7
commit 9869190b2a
14 changed files with 571 additions and 45 deletions

View file

@ -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);
}
}
}

View file

@ -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)
)
: [];

View file

@ -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
);
}

View file

@ -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;
}

View file

@ -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'];

View file

@ -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,
]);

View file

@ -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);

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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).',

View file

@ -8,9 +8,10 @@
{% block content %}
<form action="" method="get" class="container forum__confirm">
{{ container_title('<i class="' ~ class|default('fas fa-exclamation-circle') ~ ' fa-fw"></i> ' ~ title) }}
{{ input_csrf('forum_post') }}
<input type="hidden" name="p" value="{{ post.post_id|default(0) }}">
<input type="hidden" name="m" value="{{ mode|default('') }}">
{{ input_csrf(csrf|default('forum_post')) }}
{% for name, value in params %}
<input type="hidden" name="{{ name }}" value="{{ value }}">
{% endfor %}
<div class="forum__confirm__message">
{{ message|default('Are you sure you w') }}

View file

@ -21,7 +21,7 @@
</div>
{% endmacro %}
{% macro forum_header(title, breadcrumbs, omit_last_breadcrumb, title_url) %}
{% macro forum_header(title, breadcrumbs, omit_last_breadcrumb, title_url, actions) %}
<div class="container forum__header">
{% if breadcrumbs is iterable and breadcrumbs|length > 0 %}
<div class="forum__header__breadcrumbs">
@ -50,6 +50,18 @@
</div>
{% endif %}
{% endif %}
{% if actions is iterable and actions|length > 0 %}
<div class="forum__header__actions">
{% for action in actions %}
{% if action.display is not defined or action.display %}
<a class="forum__header__action{% if action.class is defined %}{{ action.class }}{% endif %}" href="{{ action.url }}">
{{ action.html|raw }}
</a>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}

View file

@ -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': '<i class="far fa-trash-alt fa-fw"></i> 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': '<i class="fas fa-magic fa-fw"></i> 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': '<i class="fas fa-radiation-alt fa-fw"></i> 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': '<i class="fas fa-plus-circle fa-fw"></i> 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': '<i class="fas fa-lock fa-fw"></i> 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': '<i class="fas fa-lock-open fa-fw"></i> 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) }}

View file

@ -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);