Replaced confirm pages with dynamic requests on the forum.
This commit is contained in:
parent
3d8d0b7e88
commit
308ba33377
18 changed files with 1159 additions and 437 deletions
|
@ -164,6 +164,9 @@
|
|||
transition: background-color .2s;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
.forum__post__action:hover,
|
||||
.forum__post__action:focus {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include msgbox.jsx
|
||||
#include utility.js
|
||||
#include xhr.js
|
||||
#include embed/embed.js
|
||||
|
@ -64,12 +65,73 @@
|
|||
});
|
||||
};
|
||||
|
||||
const initXhrActions = () => {
|
||||
const targets = Array.from($qa('a[data-url], button[data-url]'));
|
||||
for(const target of targets) {
|
||||
target.onclick = async () => {
|
||||
if(target.disabled)
|
||||
return;
|
||||
|
||||
const url = target.dataset.url;
|
||||
if(typeof url !== 'string' || url.length < 1)
|
||||
return;
|
||||
|
||||
const disableWithTarget = typeof target.dataset.disableWithTarget === 'string'
|
||||
? (target.querySelector(target.dataset.disableWithTarget) ?? target)
|
||||
: target;
|
||||
const originalText = disableWithTarget.textContent;
|
||||
|
||||
try {
|
||||
target.disabled = true;
|
||||
if(target.dataset.disableWith)
|
||||
disableWithTarget.textContent = target.dataset.disableWith;
|
||||
|
||||
if(target.dataset.confirm && !await MszShowConfirmBox(target.dataset.confirm, 'Are you sure?'))
|
||||
return;
|
||||
|
||||
const { status, body } = await $x.send(
|
||||
target.dataset.method ?? 'GET',
|
||||
url,
|
||||
{
|
||||
type: 'json',
|
||||
authed: target.dataset.withAuth,
|
||||
csrf: target.dataset.withCsrf,
|
||||
}
|
||||
)
|
||||
|
||||
if(status >= 400)
|
||||
await MszShowMessageBox(
|
||||
body?.error?.text ?? `No additional information was provided. (HTTP ${status})`,
|
||||
'Failed to complete action'
|
||||
);
|
||||
else if(status >= 200 && status <= 299) {
|
||||
if(target.dataset.refreshOnSuccess)
|
||||
location.reload();
|
||||
else {
|
||||
const redirectUrl = target.dataset.redirectOnSuccess;
|
||||
if(typeof redirectUrl === 'string' && redirectUrl.startsWith('/') && !redirectUrl.startsWith('//'))
|
||||
location.assign(redirectUrl);
|
||||
}
|
||||
}
|
||||
} catch(ex) {
|
||||
console.error(ex);
|
||||
await MszShowMessageBox(ex, 'Failed to complete action');
|
||||
} finally {
|
||||
disableWithTarget.textContent = originalText;
|
||||
target.disabled = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
MszSakuya.trackElements($qa('time'));
|
||||
hljs.highlightAll();
|
||||
|
||||
MszEmbed.init(`${location.protocol}//uiharu.${location.host}`);
|
||||
|
||||
initXhrActions();
|
||||
|
||||
// only used by the forum posting form
|
||||
initQuickSubmit();
|
||||
const forumPostingForm = $q('.js-forum-posting');
|
||||
|
|
|
@ -7,46 +7,7 @@ use RuntimeException;
|
|||
if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
|
||||
die('Script must be called through the Misuzu route dispatcher.');
|
||||
|
||||
$mode = (string)filter_input(INPUT_GET, 'm');
|
||||
|
||||
$currentUser = $msz->authInfo->userInfo;
|
||||
$currentUserId = $currentUser === null ? '0' : $currentUser->id;
|
||||
|
||||
if($mode === 'mark') {
|
||||
if(!$msz->authInfo->isLoggedIn)
|
||||
Template::throwError(403);
|
||||
|
||||
$categoryId = filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||
$categoryInfos = $categoryId === null
|
||||
? $msz->forumCtx->categories->getCategories()
|
||||
: $msz->forumCtx->categories->getCategoryChildren(parentInfo: $categoryId, includeSelf: true);
|
||||
|
||||
foreach($categoryInfos as $categoryInfo) {
|
||||
$perms = $msz->authInfo->getPerms('forum', $categoryInfo);
|
||||
if($perms->check(Perm::F_CATEGORY_LIST))
|
||||
$msz->forumCtx->categories->updateUserReadCategory($currentUser, $categoryInfo);
|
||||
}
|
||||
|
||||
Tools::redirect($msz->urls->format($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]));
|
||||
return;
|
||||
}
|
||||
|
||||
Template::render('confirm', [
|
||||
'title' => 'Mark forum as read',
|
||||
'message' => 'Are you sure you want to mark ' . ($categoryId < 1 ? 'the entire' : 'this') . ' forum as read?',
|
||||
'return' => $msz->urls->format($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]),
|
||||
'params' => [
|
||||
'forum' => $categoryId,
|
||||
]
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if($mode !== '')
|
||||
Template::throwError(404);
|
||||
|
||||
$categories = $msz->forumCtx->categories->getCategories(hidden: false, asTree: true);
|
||||
|
||||
foreach($categories as $categoryId => $category) {
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
|
||||
die('Script must be called through the Misuzu route dispatcher.');
|
||||
|
||||
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (string)$_GET['p'] : '0';
|
||||
$postMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
||||
$submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1';
|
||||
|
||||
$postRequestVerified = CSRF::validateRequest();
|
||||
|
||||
if(!empty($postMode) && !$msz->authInfo->isLoggedIn)
|
||||
Template::displayInfo('You must be logged in to manage posts.', 401);
|
||||
|
||||
$currentUser = $msz->authInfo->userInfo;
|
||||
$currentUserId = $currentUser === null ? '0' : $currentUser->id;
|
||||
|
||||
if($postMode !== '' && $msz->usersCtx->hasActiveBan($currentUser))
|
||||
Template::displayInfo('You have been banned, check your profile for more information.', 403);
|
||||
|
||||
try {
|
||||
$postInfo = $msz->forumCtx->posts->getPost(postId: $postId);
|
||||
} catch(RuntimeException $ex) {
|
||||
Template::throwError(404);
|
||||
}
|
||||
|
||||
$perms = $msz->authInfo->getPerms('forum', $postInfo->categoryId);
|
||||
|
||||
if(!$perms->check(Perm::F_CATEGORY_VIEW))
|
||||
Template::throwError(403);
|
||||
|
||||
$canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
|
||||
|
||||
switch($postMode) {
|
||||
case 'delete':
|
||||
if($canDeleteAny) {
|
||||
if($postInfo->deleted)
|
||||
Template::displayInfo('This post has already been marked as deleted.', 404);
|
||||
} else {
|
||||
if($postInfo->deleted)
|
||||
Template::throwError(404);
|
||||
|
||||
if(!$perms->check(Perm::F_POST_DELETE_OWN))
|
||||
Template::displayInfo('You are not allowed to delete posts.', 403);
|
||||
|
||||
if($postInfo->userId !== $currentUser->id)
|
||||
Template::displayInfo('You can only delete your own posts.', 403);
|
||||
|
||||
// posts may only be deleted within a week of creation, this should be a config value
|
||||
$deleteTimeFrame = 60 * 60 * 24 * 7;
|
||||
if($postInfo->createdTime < time() - $deleteTimeFrame)
|
||||
Template::displayInfo('This post has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403);
|
||||
}
|
||||
|
||||
$originalPostInfo = $msz->forumCtx->posts->getPost(topicInfo: $postInfo->topicId);
|
||||
if($originalPostInfo->id === $postInfo->id)
|
||||
Template::displayInfo('This is the opening post of the topic it belongs to, it may not be deleted without deleting the entire topic as well.', 403);
|
||||
|
||||
if($postRequestVerified && !$submissionConfirmed) {
|
||||
Tools::redirect($msz->urls->format('forum-post', ['post' => $postInfo->id]));
|
||||
break;
|
||||
} elseif(!$postRequestVerified) {
|
||||
Template::render('forum.confirm', [
|
||||
'title' => 'Confirm post deletion',
|
||||
'class' => 'far fa-trash-alt',
|
||||
'message' => sprintf('You are about to delete post #%d. Are you sure about that?', $postInfo->id),
|
||||
'params' => [
|
||||
'p' => $postInfo->id,
|
||||
'm' => 'delete',
|
||||
],
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
$msz->forumCtx->posts->deletePost($postInfo);
|
||||
$msz->createAuditLog('FORUM_POST_DELETE', [$postInfo->id]);
|
||||
|
||||
Tools::redirect($msz->urls->format('forum-topic', ['topic' => $postInfo->topicId]));
|
||||
break;
|
||||
|
||||
case 'nuke':
|
||||
if(!$canDeleteAny)
|
||||
Template::throwError(403);
|
||||
|
||||
if($postRequestVerified && !$submissionConfirmed) {
|
||||
Tools::redirect($msz->urls->format('forum-post', ['post' => $postInfo->id]));
|
||||
break;
|
||||
} elseif(!$postRequestVerified) {
|
||||
Template::render('forum.confirm', [
|
||||
'title' => 'Confirm post nuke',
|
||||
'class' => 'fas fa-radiation',
|
||||
'message' => sprintf('You are about to PERMANENTLY DELETE post #%d. Are you sure about that?', $postInfo->id),
|
||||
'params' => [
|
||||
'p' => $postInfo->id,
|
||||
'm' => 'nuke',
|
||||
],
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
$msz->forumCtx->posts->nukePost($postInfo->id);
|
||||
$msz->createAuditLog('FORUM_POST_NUKE', [$postInfo->id]);
|
||||
|
||||
Tools::redirect($msz->urls->format('forum-topic', ['topic' => $postInfo->topicId]));
|
||||
break;
|
||||
|
||||
case 'restore':
|
||||
if(!$canDeleteAny)
|
||||
Template::throwError(403);
|
||||
|
||||
if($postRequestVerified && !$submissionConfirmed) {
|
||||
Tools::redirect($msz->urls->format('forum-post', ['post' => $postInfo->id]));
|
||||
break;
|
||||
} elseif(!$postRequestVerified) {
|
||||
Template::render('forum.confirm', [
|
||||
'title' => 'Confirm post restore',
|
||||
'class' => 'fas fa-magic',
|
||||
'message' => sprintf('You are about to restore post #%d. Are you sure about that?', $postInfo->id),
|
||||
'params' => [
|
||||
'p' => $postInfo->id,
|
||||
'm' => 'restore',
|
||||
],
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
$msz->forumCtx->posts->restorePost($postInfo->id);
|
||||
$msz->createAuditLog('FORUM_POST_RESTORE', [$postInfo->id]);
|
||||
|
||||
Tools::redirect($msz->urls->format('forum-topic', ['topic' => $postInfo->topicId]));
|
||||
break;
|
||||
|
||||
default: // function as an alt for topic.php?p= by default
|
||||
Tools::redirect($msz->urls->format('forum-post', ['post' => $postInfo->id]));
|
||||
break;
|
||||
}
|
|
@ -10,8 +10,6 @@ if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
|
|||
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
|
||||
$topicId = !empty($_GET['t']) && is_string($_GET['t']) ? (int)$_GET['t'] : 0;
|
||||
$categoryId = null;
|
||||
$moderationMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
||||
$submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1';
|
||||
|
||||
$currentUser = $msz->authInfo->userInfo;
|
||||
$currentUserId = $currentUser === null ? '0' : $currentUser->id;
|
||||
|
@ -70,7 +68,7 @@ if($topicIsNuked || $topicIsDeleted) {
|
|||
}
|
||||
}
|
||||
|
||||
if(empty($topicRedirectInfo))
|
||||
if(empty($topicRedirectInfo) && !$canDeleteAny)
|
||||
Template::throwError(404);
|
||||
}
|
||||
|
||||
|
@ -99,170 +97,6 @@ $canDelete = !$topicIsDeleted && (
|
|||
)
|
||||
);
|
||||
|
||||
$validModerationModes = [
|
||||
'delete', 'restore', 'nuke',
|
||||
'bump', 'lock', 'unlock',
|
||||
];
|
||||
|
||||
if(in_array($moderationMode, $validModerationModes, true)) {
|
||||
if(!CSRF::validateRequest())
|
||||
Template::displayInfo("Couldn't verify this request, please refresh the page and try again.", 403);
|
||||
|
||||
if(!$msz->authInfo->isLoggedIn)
|
||||
Template::displayInfo('You must be logged in to manage posts.', 401);
|
||||
|
||||
if($msz->usersCtx->hasActiveBan($currentUser))
|
||||
Template::displayInfo('You have been banned, check your profile for more information.', 403);
|
||||
|
||||
switch($moderationMode) {
|
||||
case 'delete':
|
||||
if($canDeleteAny) {
|
||||
if($topicInfo->deleted)
|
||||
Template::displayInfo('This topic has already been marked as deleted.', 404);
|
||||
} else {
|
||||
if($topicInfo->deleted)
|
||||
Template::throwError(404);
|
||||
|
||||
if(!$canDeleteOwn)
|
||||
Template::displayInfo("You aren't allowed to delete topics.", 403);
|
||||
|
||||
if($topicInfo->userId !== $currentUser->id)
|
||||
Template::displayInfo('You can only delete your own topics.', 403);
|
||||
|
||||
// topics may only be deleted within a day of creation, this should be a config value
|
||||
$deleteTimeFrame = 60 * 60 * 24;
|
||||
if($topicInfo->createdTime < time() - $deleteTimeFrame)
|
||||
Template::displayInfo('This topic has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403);
|
||||
|
||||
// deleted posts are intentionally included
|
||||
$topicPostCount = $msz->forumCtx->posts->countPosts(topicInfo: $topicInfo);
|
||||
if($topicPostCount > $deletePostThreshold)
|
||||
Template::displayInfo('This topic already has replies, you may no longer delete it. Ask a moderator to remove if it absolutely necessary.', 403);
|
||||
}
|
||||
|
||||
if(!isset($_GET['confirm'])) {
|
||||
Template::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?', $topicInfo->id),
|
||||
'params' => [
|
||||
't' => $topicInfo->id,
|
||||
'm' => 'delete',
|
||||
],
|
||||
]);
|
||||
break;
|
||||
} elseif(!$submissionConfirmed) {
|
||||
Tools::redirect($msz->urls->format(
|
||||
'forum-topic',
|
||||
['topic' => $topicInfo->id]
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
$msz->forumCtx->topics->deleteTopic($topicInfo->id);
|
||||
$msz->createAuditLog('FORUM_TOPIC_DELETE', [$topicInfo->id]);
|
||||
|
||||
Tools::redirect($msz->urls->format('forum-category', [
|
||||
'forum' => $categoryInfo->id,
|
||||
]));
|
||||
break;
|
||||
|
||||
case 'restore':
|
||||
if(!$canNukeOrRestore)
|
||||
Template::throwError(403);
|
||||
|
||||
if(!isset($_GET['confirm'])) {
|
||||
Template::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?', $topicInfo->id),
|
||||
'params' => [
|
||||
't' => $topicInfo->id,
|
||||
'm' => 'restore',
|
||||
],
|
||||
]);
|
||||
break;
|
||||
} elseif(!$submissionConfirmed) {
|
||||
Tools::redirect($msz->urls->format('forum-topic', [
|
||||
'topic' => $topicInfo->id,
|
||||
]));
|
||||
break;
|
||||
}
|
||||
|
||||
$msz->forumCtx->topics->restoreTopic($topicInfo->id);
|
||||
$msz->createAuditLog('FORUM_TOPIC_RESTORE', [$topicInfo->id]);
|
||||
|
||||
Tools::redirect($msz->urls->format('forum-category', [
|
||||
'forum' => $categoryInfo->id,
|
||||
]));
|
||||
break;
|
||||
|
||||
case 'nuke':
|
||||
if(!$canNukeOrRestore)
|
||||
Template::throwError(403);
|
||||
|
||||
if(!isset($_GET['confirm'])) {
|
||||
Template::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?', $topicInfo->id),
|
||||
'params' => [
|
||||
't' => $topicInfo->id,
|
||||
'm' => 'nuke',
|
||||
],
|
||||
]);
|
||||
break;
|
||||
} elseif(!$submissionConfirmed) {
|
||||
Tools::redirect($msz->urls->format('forum-topic', [
|
||||
'topic' => $topicInfo->id,
|
||||
]));
|
||||
break;
|
||||
}
|
||||
|
||||
$msz->forumCtx->topics->nukeTopic($topicInfo->id);
|
||||
$msz->createAuditLog('FORUM_TOPIC_NUKE', [$topicInfo->id]);
|
||||
|
||||
Tools::redirect($msz->urls->format('forum-category', [
|
||||
'forum' => $categoryInfo->id,
|
||||
]));
|
||||
break;
|
||||
|
||||
case 'bump':
|
||||
if($canBumpTopic) {
|
||||
$msz->forumCtx->topics->bumpTopic($topicInfo->id);
|
||||
$msz->createAuditLog('FORUM_TOPIC_BUMP', [$topicInfo->id]);
|
||||
}
|
||||
|
||||
Tools::redirect($msz->urls->format('forum-topic', [
|
||||
'topic' => $topicInfo->id,
|
||||
]));
|
||||
break;
|
||||
|
||||
case 'lock':
|
||||
if($canLockTopic && !$topicIsLocked) {
|
||||
$msz->forumCtx->topics->lockTopic($topicInfo->id);
|
||||
$msz->createAuditLog('FORUM_TOPIC_LOCK', [$topicInfo->id]);
|
||||
}
|
||||
|
||||
Tools::redirect($msz->urls->format('forum-topic', [
|
||||
'topic' => $topicInfo->id,
|
||||
]));
|
||||
break;
|
||||
|
||||
case 'unlock':
|
||||
if($canLockTopic && $topicIsLocked) {
|
||||
$msz->forumCtx->topics->unlockTopic($topicInfo->id);
|
||||
$msz->createAuditLog('FORUM_TOPIC_UNLOCK', [$topicInfo->id]);
|
||||
}
|
||||
|
||||
Tools::redirect($msz->urls->format('forum-topic', [
|
||||
'topic' => $topicInfo->id,
|
||||
]));
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$topicPosts = $topicInfo->postsCount;
|
||||
if($canDeleteAny)
|
||||
$topicPosts += $topicInfo->deletedPostsCount;
|
||||
|
@ -331,6 +165,7 @@ Template::render('forum.topic', [
|
|||
'can_reply' => $canReply,
|
||||
'topic_pagination' => $topicPagination,
|
||||
'topic_can_delete' => $canDelete,
|
||||
'topic_can_delete_any' => $canDeleteAny,
|
||||
'topic_can_nuke_or_restore' => $canNukeOrRestore,
|
||||
'topic_can_bump' => $canBumpTopic,
|
||||
'topic_can_lock' => $canLockTopic,
|
||||
|
|
|
@ -308,11 +308,10 @@ class ForumCategories {
|
|||
$query .= sprintf(' %s forum_hidden %s 0', ++$args > 1 ? 'AND' : 'WHERE', $hidden ? '<>' : '=');
|
||||
$query .= ' ORDER BY forum_parent, forum_order';
|
||||
|
||||
$args = 0;
|
||||
$stmt = $this->cache->get($query);
|
||||
$stmt->addParameter(++$args, $parentInfo);
|
||||
$stmt->nextParameter($parentInfo);
|
||||
if(!$includeSelf)
|
||||
$stmt->addParameter(++$args, $parentInfo);
|
||||
$stmt->nextParameter($parentInfo);
|
||||
$stmt->execute();
|
||||
|
||||
$cats = $stmt->getResult()->getIterator(ForumCategoryInfo::fromResult(...));
|
||||
|
|
78
src/Forum/ForumCategoriesRoutes.php
Normal file
78
src/Forum/ForumCategoriesRoutes.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
namespace Misuzu\Forum;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\Http\{HttpRequest,HttpResponseBuilder};
|
||||
use Index\Http\Routing\{HttpPost,RouteHandler,RouteHandlerTrait};
|
||||
use Index\Urls\{UrlFormat,UrlSource,UrlSourceTrait};
|
||||
use Misuzu\{CSRF,Perm};
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
|
||||
class ForumCategoriesRoutes implements RouteHandler, UrlSource {
|
||||
use RouteHandlerTrait, UrlSourceTrait;
|
||||
|
||||
public function __construct(
|
||||
private ForumContext $forum,
|
||||
private AuthInfo $authInfo,
|
||||
) {}
|
||||
|
||||
#[HttpPost('/forum/mark-as-read')]
|
||||
#[UrlFormat('forum-mark-as-read', '/forum/mark-as-read', ['cat' => '<category>', 'rec' => '<recursive>'])]
|
||||
public function postMarkAsRead(HttpResponseBuilder $response, HttpRequest $request) {
|
||||
if(!$this->authInfo->isLoggedIn)
|
||||
return 401;
|
||||
|
||||
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
|
||||
return 403;
|
||||
$response->setHeader('X-CSRF-Token', CSRF::token());
|
||||
|
||||
$catId = (string)$request->getParam('cat', FILTER_SANITIZE_NUMBER_INT);
|
||||
$recursive = !empty($request->getParam('rec'));
|
||||
|
||||
// root category purge must be recursive
|
||||
if($categoryId === '')
|
||||
return 400;
|
||||
|
||||
if($catId === '')
|
||||
$cats = $this->forum->categories->getCategories();
|
||||
elseif($recursive)
|
||||
$cats = $this->forum->categories->getCategoryChildren(parentInfo: $catId, includeSelf: true);
|
||||
else
|
||||
try {
|
||||
$cats = [$this->forum->categories->getCategory(categoryId: $catId)];
|
||||
} catch(RuntimeException $ex) {
|
||||
$cats = [];
|
||||
}
|
||||
|
||||
if(empty($cats)) {
|
||||
$response->setStatusCode(404);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:category:none',
|
||||
'text' => "Couldn't find that forum category.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$success = false;
|
||||
foreach($cats as $category) {
|
||||
$perms = $this->authInfo->getPerms('forum', $category);
|
||||
if($perms->check(Perm::F_CATEGORY_LIST)) {
|
||||
$this->forum->categories->updateUserReadCategory($this->authInfo->userInfo, $category);
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$success) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:category:access',
|
||||
'text' => "You're not allowed to access this forum category.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return 204;
|
||||
}
|
||||
}
|
348
src/Forum/ForumPostsRoutes.php
Normal file
348
src/Forum/ForumPostsRoutes.php
Normal file
|
@ -0,0 +1,348 @@
|
|||
<?php
|
||||
namespace Misuzu\Forum;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\Http\{HttpRequest,HttpResponseBuilder};
|
||||
use Index\Http\Routing\{HttpDelete,HttpGet,HttpPost,RouteHandler,RouteHandlerTrait};
|
||||
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceTrait};
|
||||
use Misuzu\{CSRF,Perm};
|
||||
use Misuzu\AuditLog\AuditLog;
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Users\UsersContext;
|
||||
|
||||
class ForumPostsRoutes implements RouteHandler, UrlSource {
|
||||
use RouteHandlerTrait, UrlSourceTrait;
|
||||
|
||||
public function __construct(
|
||||
private UrlRegistry $urls,
|
||||
private ForumContext $forum,
|
||||
private UsersContext $usersCtx,
|
||||
private AuditLog $auditLog,
|
||||
private AuthInfo $authInfo,
|
||||
) {}
|
||||
|
||||
#[HttpGet('/forum/posts/([0-9]+)')]
|
||||
public function getPost(HttpResponseBuilder $response, HttpRequest $request, string $postId) {
|
||||
try {
|
||||
$post = $this->forum->posts->getPost(postId: $postId);
|
||||
} catch(RuntimeException $ex) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
$perms = $this->authInfo->getPerms('forum', $post->categoryId);
|
||||
if(!$perms->check(Perm::F_CATEGORY_VIEW))
|
||||
return 403;
|
||||
|
||||
$canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY);
|
||||
if($post->deleted && !$canDeleteAny)
|
||||
return 404;
|
||||
|
||||
$postsCount = $this->forum->posts->countPosts(
|
||||
topicInfo: $post->topicId,
|
||||
upToPostInfo: $post,
|
||||
deleted: $canDeleteAny ? null : false
|
||||
);
|
||||
$pageNumber = (int)ceil($postsCount / 10); // epic magic number
|
||||
|
||||
return $response->redirect($this->urls->format('forum-topic', [
|
||||
'topic' => $post->topicId,
|
||||
'page' => $pageNumber,
|
||||
'post' => sprintf('p%s', $post->id),
|
||||
]));
|
||||
}
|
||||
|
||||
#[HttpDelete('/forum/posts/([0-9]+)')]
|
||||
#[UrlFormat('forum-post-delete', '/forum/posts/<post>')]
|
||||
public function deletePost(HttpResponseBuilder $response, HttpRequest $request, string $postId) {
|
||||
if(!$this->authInfo->isLoggedIn)
|
||||
return 401;
|
||||
|
||||
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
|
||||
return 403;
|
||||
$response->setHeader('X-CSRF-Token', CSRF::token());
|
||||
|
||||
if($this->usersCtx->hasActiveBan($this->authInfo->authInfo)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'user:banned',
|
||||
'text' => "You aren't allowed to do that while banned.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$post = $this->forum->posts->getPost(
|
||||
postId: $postId,
|
||||
deleted: false,
|
||||
);
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(404);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:none',
|
||||
'text' => "Couldn't find that forum post.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$perms = $this->authInfo->getPerms('forum', $post->categoryId);
|
||||
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:access',
|
||||
'text' => "You aren't allowed to access that post.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$perms->check(Perm::F_POST_DELETE_ANY)) {
|
||||
$topic = $this->forum->topics->getTopic(postInfo: $post);
|
||||
if($topic->deleted) {
|
||||
$response->setStatusCode(404);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:none',
|
||||
'text' => "Couldn't find that forum post.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if($topic->locked) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:delete:lock',
|
||||
'text' => "The forum topic that post belongs is locked.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$perms->check(Perm::F_POST_DELETE_OWN)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:delete:access',
|
||||
'text' => "You aren't allowed to delete that post.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if($post->userId !== $this->authInfo->userId) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:delete:own',
|
||||
'text' => "You aren't allowed to delete posts made by other people.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// posts may only be deleted within a week of creation, this should be a config value
|
||||
$deleteTimeFrame = 60 * 60 * 24 * 7;
|
||||
if($post->createdTime < time() - $deleteTimeFrame) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:delete:age',
|
||||
'text' => "This post has existed for too long. Ask a moderator to remove if it absolutely necessary.",
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$originalPost = $this->forum->posts->getPost(topicInfo: $post->topicId);
|
||||
if($originalPost->id === $post->id) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:delete:opening',
|
||||
'text' => "This is the opening post of the topic it belongs to, it may not be deleted without deleting the entire topic as well.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$category = $this->forum->categories->getCategory(postInfo: $post);
|
||||
if($category->archived) {
|
||||
$response->setStatusCode(400);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:archived',
|
||||
'text' => "The forum category this topic belongs to is archived.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$this->forum->posts->deletePost($post);
|
||||
$this->auditLog->createLog(
|
||||
$this->authInfo->userInfo,
|
||||
'FORUM_POST_DELETE',
|
||||
[$post->id],
|
||||
$request->getRemoteAddress(),
|
||||
$request->getCountryCode()
|
||||
);
|
||||
|
||||
return 204;
|
||||
}
|
||||
|
||||
#[HttpPost('/forum/posts/([0-9]+)/nuke')]
|
||||
#[UrlFormat('forum-post-nuke', '/forum/posts/<post>/nuke')]
|
||||
public function postPostNuke(HttpResponseBuilder $response, HttpRequest $request, string $postId) {
|
||||
if(!$this->authInfo->isLoggedIn)
|
||||
return 401;
|
||||
|
||||
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
|
||||
return 403;
|
||||
$response->setHeader('X-CSRF-Token', CSRF::token());
|
||||
|
||||
if($this->usersCtx->hasActiveBan($this->authInfo->authInfo)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'user:banned',
|
||||
'text' => "You aren't allowed to do that while banned.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$post = $this->forum->posts->getPost(
|
||||
postId: $postId,
|
||||
deleted: true,
|
||||
);
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(404);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:none',
|
||||
'text' => "Couldn't find that forum post.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$perms = $this->authInfo->getPerms('forum', $post->categoryId);
|
||||
|
||||
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:access',
|
||||
'text' => "You aren't allowed to access that post.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$perms->check(Perm::F_POST_DELETE_ANY)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:nuke:access',
|
||||
'text' => "You aren't allowed to nuke that post.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$category = $this->forum->categories->getCategory(postInfo: $post);
|
||||
if($category->archived) {
|
||||
$response->setStatusCode(400);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:archived',
|
||||
'text' => "The forum category this post belongs to is archived.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$this->forum->posts->nukePost($post);
|
||||
$this->auditLog->createLog(
|
||||
$this->authInfo->userInfo,
|
||||
'FORUM_POST_NUKE',
|
||||
[$post->id],
|
||||
$request->getRemoteAddress(),
|
||||
$request->getCountryCode()
|
||||
);
|
||||
|
||||
return 204;
|
||||
}
|
||||
|
||||
#[HttpPost('/forum/posts/([0-9]+)/restore')]
|
||||
#[UrlFormat('forum-post-restore', '/forum/posts/<post>/restore')]
|
||||
public function postPostRestore(HttpResponseBuilder $response, HttpRequest $request, string $postId) {
|
||||
if(!$this->authInfo->isLoggedIn)
|
||||
return 401;
|
||||
|
||||
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
|
||||
return 403;
|
||||
$response->setHeader('X-CSRF-Token', CSRF::token());
|
||||
|
||||
if($this->usersCtx->hasActiveBan($this->authInfo->authInfo)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'user:banned',
|
||||
'text' => "You aren't allowed to do that while banned.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$post = $this->forum->posts->getPost(
|
||||
postId: $postId,
|
||||
deleted: true,
|
||||
);
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(404);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:none',
|
||||
'text' => "Couldn't find that forum post.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$perms = $this->authInfo->getPerms('forum', $post->categoryId);
|
||||
|
||||
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:access',
|
||||
'text' => "You aren't allowed to access that post.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$perms->check(Perm::F_POST_DELETE_ANY)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:restore:access',
|
||||
'text' => "You aren't allowed to restore that post.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$category = $this->forum->categories->getCategory(postInfo: $post);
|
||||
if($category->archived) {
|
||||
$response->setStatusCode(400);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:post:archived',
|
||||
'text' => "The forum category this post belongs to is archived.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$this->forum->posts->restorePost($post);
|
||||
$this->auditLog->createLog(
|
||||
$this->authInfo->userInfo,
|
||||
'FORUM_POST_RESTORE',
|
||||
[$post->id],
|
||||
$request->getRemoteAddress(),
|
||||
$request->getCountryCode()
|
||||
);
|
||||
|
||||
return 204;
|
||||
}
|
||||
}
|
|
@ -203,10 +203,12 @@ class ForumTopics {
|
|||
|
||||
public function getTopic(
|
||||
?string $topicId = null,
|
||||
ForumPostInfo|string|null $postInfo = null
|
||||
ForumPostInfo|string|null $postInfo = null,
|
||||
?bool $deleted = null
|
||||
): ForumTopicInfo {
|
||||
$hasTopicId = $topicId !== null;
|
||||
$hasPostInfo = $postInfo !== null;
|
||||
$hasDeleted = $deleted !== null;
|
||||
|
||||
if(!$hasTopicId && !$hasPostInfo)
|
||||
throw new InvalidArgumentException('At least one argument must be specified.');
|
||||
|
@ -228,6 +230,8 @@ class ForumTopics {
|
|||
$value = $postInfo;
|
||||
}
|
||||
}
|
||||
if($hasDeleted)
|
||||
$query .= sprintf(' AND topic_deleted %s NULL', $deleted ? 'IS NOT' : 'IS');
|
||||
|
||||
$stmt = $this->cache->get($query);
|
||||
$stmt->addParameter(1, $value);
|
||||
|
|
568
src/Forum/ForumTopicsRoutes.php
Normal file
568
src/Forum/ForumTopicsRoutes.php
Normal file
|
@ -0,0 +1,568 @@
|
|||
<?php
|
||||
namespace Misuzu\Forum;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\Http\{HttpRequest,HttpResponseBuilder};
|
||||
use Index\Http\Routing\{HttpDelete,HttpGet,HttpPost,RouteHandler,RouteHandlerTrait};
|
||||
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceTrait};
|
||||
use Misuzu\{CSRF,Perm};
|
||||
use Misuzu\AuditLog\AuditLog;
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Users\UsersContext;
|
||||
|
||||
class ForumTopicsRoutes implements RouteHandler, UrlSource {
|
||||
use RouteHandlerTrait, UrlSourceTrait;
|
||||
|
||||
public function __construct(
|
||||
private UrlRegistry $urls,
|
||||
private ForumContext $forum,
|
||||
private UsersContext $usersCtx,
|
||||
private AuditLog $auditLog,
|
||||
private AuthInfo $authInfo,
|
||||
) {}
|
||||
|
||||
#[HttpGet('/forum/topics/([0-9]+)')]
|
||||
public function getTopic(HttpResponseBuilder $response, HttpRequest $request, string $topicId) {
|
||||
$response->redirect($this->urls->format('forum-topic', ['topic' => $topicId]));
|
||||
}
|
||||
|
||||
#[HttpDelete('/forum/topics/([0-9]+)')]
|
||||
#[UrlFormat('forum-topic-delete', '/forum/topics/<topic>')]
|
||||
public function deleteTopic(HttpResponseBuilder $response, HttpRequest $request, string $topicId) {
|
||||
if(!$this->authInfo->isLoggedIn)
|
||||
return 401;
|
||||
|
||||
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
|
||||
return 403;
|
||||
$response->setHeader('X-CSRF-Token', CSRF::token());
|
||||
|
||||
if($this->usersCtx->hasActiveBan($this->authInfo->authInfo)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'user:banned',
|
||||
'text' => "You aren't allowed to do that while banned.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$topic = $this->forum->topics->getTopic(
|
||||
topicId: $topicId,
|
||||
deleted: false,
|
||||
);
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(404);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:none',
|
||||
'text' => "Couldn't find that forum topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$perms = $this->authInfo->getPerms('forum', $topic->categoryId);
|
||||
|
||||
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:access',
|
||||
'text' => "You aren't allowed to access that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$perms->check(Perm::F_POST_DELETE_ANY)) {
|
||||
if($topic->locked || !$perms->check(Perm::F_POST_DELETE_OWN)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:delete:access',
|
||||
'text' => "You aren't allowed to delete that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if($topic->userId !== $this->authInfo->userId) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:delete:own',
|
||||
'text' => "You aren't allowed to delete topics made by other people.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// topics may only be deleted within a day of creation, this should be a config value
|
||||
$deleteTimeFrame = 60 * 60 * 24;
|
||||
if($topic->createdTime < time() - $deleteTimeFrame) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:delete:age',
|
||||
'text' => "This topic has existed for too long. Ask a moderator to remove if it absolutely necessary.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// Maximum amount of posts a topic may contain to still be deletable by the author
|
||||
// this should be in the config
|
||||
$deletePostThreshold = 1;
|
||||
|
||||
// deleted posts are intentionally included
|
||||
$topicPostCount = $this->forum->posts->countPosts(topicInfo: $topic);
|
||||
if($topicPostCount > $deletePostThreshold) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:delete:replies',
|
||||
'text' => "This topic already has replies, you may no longer delete it. Ask a moderator to remove if it absolutely necessary.",
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$category = $this->forum->categories->getCategory(topicInfo: $topic);
|
||||
if($category->archived) {
|
||||
$response->setStatusCode(400);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:archived',
|
||||
'text' => "The forum category this topic belongs to is archived.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$this->forum->topics->deleteTopic($topic);
|
||||
$this->auditLog->createLog(
|
||||
$this->authInfo->userInfo,
|
||||
'FORUM_TOPIC_DELETE',
|
||||
[$topic->id],
|
||||
$request->getRemoteAddress(),
|
||||
$request->getCountryCode()
|
||||
);
|
||||
|
||||
return 204;
|
||||
}
|
||||
|
||||
#[HttpPost('/forum/topics/([0-9]+)/restore')]
|
||||
#[UrlFormat('forum-topic-restore', '/forum/topics/<topic>/restore')]
|
||||
public function postTopicRestore(HttpResponseBuilder $response, HttpRequest $request, string $topicId) {
|
||||
if(!$this->authInfo->isLoggedIn)
|
||||
return 401;
|
||||
|
||||
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
|
||||
return 403;
|
||||
$response->setHeader('X-CSRF-Token', CSRF::token());
|
||||
|
||||
if($this->usersCtx->hasActiveBan($this->authInfo->authInfo)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'user:banned',
|
||||
'text' => "You aren't allowed to do that while banned.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$topic = $this->forum->topics->getTopic(
|
||||
topicId: $topicId,
|
||||
deleted: true,
|
||||
);
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(404);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:none',
|
||||
'text' => "Couldn't find that forum topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$perms = $this->authInfo->getPerms('forum', $topic->categoryId);
|
||||
|
||||
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:access',
|
||||
'text' => "You aren't allowed to access that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$perms->check(Perm::F_POST_DELETE_ANY)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:restore:access',
|
||||
'text' => "You aren't allowed to restore that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$category = $this->forum->categories->getCategory(topicInfo: $topic);
|
||||
if($category->archived) {
|
||||
$response->setStatusCode(400);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:archived',
|
||||
'text' => "The forum category this topic belongs to is archived.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$this->forum->topics->restoreTopic($topic);
|
||||
$this->auditLog->createLog(
|
||||
$this->authInfo->userInfo,
|
||||
'FORUM_TOPIC_RESTORE',
|
||||
[$topic->id],
|
||||
$request->getRemoteAddress(),
|
||||
$request->getCountryCode()
|
||||
);
|
||||
|
||||
return 204;
|
||||
}
|
||||
|
||||
#[HttpPost('/forum/topics/([0-9]+)/nuke')]
|
||||
#[UrlFormat('forum-topic-nuke', '/forum/topics/<topic>/nuke')]
|
||||
public function postTopicNuke(HttpResponseBuilder $response, HttpRequest $request, string $topicId) {
|
||||
if(!$this->authInfo->isLoggedIn)
|
||||
return 401;
|
||||
|
||||
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
|
||||
return 403;
|
||||
$response->setHeader('X-CSRF-Token', CSRF::token());
|
||||
|
||||
if($this->usersCtx->hasActiveBan($this->authInfo->authInfo)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'user:banned',
|
||||
'text' => "You aren't allowed to do that while banned.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$topic = $this->forum->topics->getTopic(
|
||||
topicId: $topicId,
|
||||
deleted: true,
|
||||
);
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(404);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:none',
|
||||
'text' => "Couldn't find that forum topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$perms = $this->authInfo->getPerms('forum', $topic->categoryId);
|
||||
|
||||
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:access',
|
||||
'text' => "You aren't allowed to access that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$perms->check(Perm::F_POST_DELETE_ANY)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:nuke:access',
|
||||
'text' => "You aren't allowed to nuke that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$category = $this->forum->categories->getCategory(topicInfo: $topic);
|
||||
if($category->archived) {
|
||||
$response->setStatusCode(400);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:archived',
|
||||
'text' => "The forum category this topic belongs to is archived.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$this->forum->topics->nukeTopic($topic);
|
||||
$this->auditLog->createLog(
|
||||
$this->authInfo->userInfo,
|
||||
'FORUM_TOPIC_NUKE',
|
||||
[$topic->id],
|
||||
$request->getRemoteAddress(),
|
||||
$request->getCountryCode()
|
||||
);
|
||||
|
||||
return 204;
|
||||
}
|
||||
|
||||
#[HttpPost('/forum/topics/([0-9]+)/bump')]
|
||||
#[UrlFormat('forum-topic-bump', '/forum/topics/<topic>/bump')]
|
||||
public function postTopicBump(HttpResponseBuilder $response, HttpRequest $request, string $topicId) {
|
||||
if(!$this->authInfo->isLoggedIn)
|
||||
return 401;
|
||||
|
||||
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
|
||||
return 403;
|
||||
$response->setHeader('X-CSRF-Token', CSRF::token());
|
||||
|
||||
if($this->usersCtx->hasActiveBan($this->authInfo->authInfo)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'user:banned',
|
||||
'text' => "You aren't allowed to do that while banned.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$topic = $this->forum->topics->getTopic(
|
||||
topicId: $topicId,
|
||||
deleted: false,
|
||||
);
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(404);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:none',
|
||||
'text' => "Couldn't find that forum topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$perms = $this->authInfo->getPerms('forum', $topic->categoryId);
|
||||
|
||||
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:access',
|
||||
'text' => "You aren't allowed to access that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$perms->check(Perm::F_TOPIC_BUMP)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:bump:access',
|
||||
'text' => "You aren't allowed to bump that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$category = $this->forum->categories->getCategory(topicInfo: $topic);
|
||||
if($category->archived) {
|
||||
$response->setStatusCode(400);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:archived',
|
||||
'text' => "The forum category this topic belongs to is archived.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$this->forum->topics->bumpTopic($topic);
|
||||
$this->auditLog->createLog(
|
||||
$this->authInfo->userInfo,
|
||||
'FORUM_TOPIC_BUMP',
|
||||
[$topic->id],
|
||||
$request->getRemoteAddress(),
|
||||
$request->getCountryCode()
|
||||
);
|
||||
|
||||
return 204;
|
||||
}
|
||||
|
||||
#[HttpPost('/forum/topics/([0-9]+)/lock')]
|
||||
#[UrlFormat('forum-topic-lock', '/forum/topics/<topic>/lock')]
|
||||
public function postTopicLock(HttpResponseBuilder $response, HttpRequest $request, string $topicId) {
|
||||
if(!$this->authInfo->isLoggedIn)
|
||||
return 401;
|
||||
|
||||
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
|
||||
return 403;
|
||||
$response->setHeader('X-CSRF-Token', CSRF::token());
|
||||
|
||||
if($this->usersCtx->hasActiveBan($this->authInfo->authInfo)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'user:banned',
|
||||
'text' => "You aren't allowed to do that while banned.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$topic = $this->forum->topics->getTopic(
|
||||
topicId: $topicId,
|
||||
deleted: false,
|
||||
);
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(404);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:none',
|
||||
'text' => "Couldn't find that forum topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$perms = $this->authInfo->getPerms('forum', $topic->categoryId);
|
||||
|
||||
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:access',
|
||||
'text' => "You aren't allowed to access that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$perms->check(Perm::F_TOPIC_LOCK)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:lock:access',
|
||||
'text' => "You aren't allowed to lock that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if($topic->locked) {
|
||||
$response->setStatusCode(400);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:lock:already',
|
||||
'text' => "That forum topic has already been locked.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$category = $this->forum->categories->getCategory(topicInfo: $topic);
|
||||
if($category->archived) {
|
||||
$response->setStatusCode(400);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:archived',
|
||||
'text' => "The forum category this topic belongs to is archived.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$this->forum->topics->lockTopic($topic);
|
||||
$this->auditLog->createLog(
|
||||
$this->authInfo->userInfo,
|
||||
'FORUM_TOPIC_LOCK',
|
||||
[$topic->id],
|
||||
$request->getRemoteAddress(),
|
||||
$request->getCountryCode()
|
||||
);
|
||||
|
||||
return 204;
|
||||
}
|
||||
|
||||
#[HttpPost('/forum/topics/([0-9]+)/unlock')]
|
||||
#[UrlFormat('forum-topic-unlock', '/forum/topics/<topic>/unlock')]
|
||||
public function postTopicUnlock(HttpResponseBuilder $response, HttpRequest $request, string $topicId) {
|
||||
if(!$this->authInfo->isLoggedIn)
|
||||
return 401;
|
||||
|
||||
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
|
||||
return 403;
|
||||
$response->setHeader('X-CSRF-Token', CSRF::token());
|
||||
|
||||
if($this->usersCtx->hasActiveBan($this->authInfo->authInfo)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'user:banned',
|
||||
'text' => "You aren't allowed to do that while banned.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$topic = $this->forum->topics->getTopic(
|
||||
topicId: $topicId,
|
||||
deleted: false,
|
||||
);
|
||||
} catch(RuntimeException $ex) {
|
||||
$response->setStatusCode(404);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:none',
|
||||
'text' => "Couldn't find that forum topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$perms = $this->authInfo->getPerms('forum', $topic->categoryId);
|
||||
|
||||
if(!$perms->check(Perm::F_CATEGORY_VIEW)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:access',
|
||||
'text' => "You aren't allowed to access that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$perms->check(Perm::F_TOPIC_LOCK)) {
|
||||
$response->setStatusCode(403);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:lock:access',
|
||||
'text' => "You aren't allowed to lock that topic.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if(!$topic->locked) {
|
||||
$response->setStatusCode(400);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:lock:not',
|
||||
'text' => "This forum topic hasn't been locked yet.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$category = $this->forum->categories->getCategory(topicInfo: $topic);
|
||||
if($category->archived) {
|
||||
$response->setStatusCode(400);
|
||||
return [
|
||||
'error' => [
|
||||
'name' => 'forum:topic:archived',
|
||||
'text' => "The forum category this topic belongs to is archived.",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$this->forum->topics->unlockTopic($topic);
|
||||
$this->auditLog->createLog(
|
||||
$this->authInfo->userInfo,
|
||||
'FORUM_TOPIC_UNLOCK',
|
||||
[$topic->id],
|
||||
$request->getRemoteAddress(),
|
||||
$request->getCountryCode()
|
||||
);
|
||||
|
||||
return 204;
|
||||
}
|
||||
}
|
|
@ -31,25 +31,14 @@ class LegacyRoutes implements RouteHandler, UrlSource {
|
|||
|
||||
$urls->register('forum-index', '/forum');
|
||||
$urls->register('forum-leaderboard', '/forum/leaderboard.php', ['id' => '<id>', 'mode' => '<mode>']);
|
||||
$urls->register('forum-mark-global', '/forum/index.php', ['m' => 'mark']);
|
||||
$urls->register('forum-mark-single', '/forum/index.php', ['m' => 'mark', 'f' => '<forum>']);
|
||||
$urls->register('forum-topic-new', '/forum/posting.php', ['f' => '<forum>']);
|
||||
$urls->register('forum-reply-new', '/forum/posting.php', ['t' => '<topic>']);
|
||||
$urls->register('forum-category', '/forum/forum.php', ['f' => '<forum>', 'p' => '<page>']);
|
||||
$urls->register('forum-category-root', '/forum/index.php', fragment: '<forum>');
|
||||
$urls->register('forum-topic', '/forum/topic.php', ['t' => '<topic>', 'page' => '<page>']);
|
||||
$urls->register('forum-topic', '/forum/topic.php', ['t' => '<topic>', 'page' => '<page>'], '<post>');
|
||||
$urls->register('forum-topic-create', '/forum/posting.php', ['f' => '<forum>']);
|
||||
$urls->register('forum-topic-bump', '/forum/topic.php', ['t' => '<topic>', 'm' => 'bump', 'csrf' => '<csrf>']);
|
||||
$urls->register('forum-topic-lock', '/forum/topic.php', ['t' => '<topic>', 'm' => 'lock', 'csrf' => '<csrf>']);
|
||||
$urls->register('forum-topic-unlock', '/forum/topic.php', ['t' => '<topic>', 'm' => 'unlock', 'csrf' => '<csrf>']);
|
||||
$urls->register('forum-topic-delete', '/forum/topic.php', ['t' => '<topic>', 'm' => 'delete', 'csrf' => '<csrf>']);
|
||||
$urls->register('forum-topic-restore', '/forum/topic.php', ['t' => '<topic>', 'm' => 'restore', 'csrf' => '<csrf>']);
|
||||
$urls->register('forum-topic-nuke', '/forum/topic.php', ['t' => '<topic>', 'm' => 'nuke', 'csrf' => '<csrf>']);
|
||||
$urls->register('forum-post', '/forum/topic.php', ['p' => '<post>'], 'p<post>');
|
||||
$urls->register('forum-post-create', '/forum/posting.php', ['t' => '<topic>']);
|
||||
$urls->register('forum-post-delete', '/forum/post.php', ['p' => '<post>', 'm' => 'delete']);
|
||||
$urls->register('forum-post-restore', '/forum/post.php', ['p' => '<post>', 'm' => 'restore']);
|
||||
$urls->register('forum-post-nuke', '/forum/post.php', ['p' => '<post>', 'm' => 'nuke']);
|
||||
$urls->register('forum-post-quote', '/forum/posting.php', ['q' => '<post>']);
|
||||
$urls->register('forum-post-edit', '/forum/posting.php', ['p' => '<post>', 'm' => 'edit']);
|
||||
|
||||
|
@ -196,6 +185,13 @@ class LegacyRoutes implements RouteHandler, UrlSource {
|
|||
), true);
|
||||
}
|
||||
|
||||
#[HttpGet('/forum/post.php')]
|
||||
public function getForumPostPHP(HttpResponseBuilder $response, HttpRequest $request): void {
|
||||
$response->redirect($this->urls->format('forum-post', [
|
||||
'post' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
|
||||
]), true);
|
||||
}
|
||||
|
||||
#[HttpGet('/changelog.php')]
|
||||
public function getChangelogPHP(HttpResponseBuilder $response, HttpRequest $request): void {
|
||||
$changeId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
|
|
@ -186,6 +186,25 @@ class MisuzuContext {
|
|||
$this->perms
|
||||
));
|
||||
|
||||
$routingCtx->register(new \Misuzu\Forum\ForumCategoriesRoutes(
|
||||
$this->forumCtx,
|
||||
$this->authInfo,
|
||||
));
|
||||
$routingCtx->register(new \Misuzu\Forum\ForumTopicsRoutes(
|
||||
$this->urls,
|
||||
$this->forumCtx,
|
||||
$this->usersCtx,
|
||||
$this->auditLog,
|
||||
$this->authInfo,
|
||||
));
|
||||
$routingCtx->register(new \Misuzu\Forum\ForumPostsRoutes(
|
||||
$this->urls,
|
||||
$this->forumCtx,
|
||||
$this->usersCtx,
|
||||
$this->auditLog,
|
||||
$this->authInfo,
|
||||
));
|
||||
|
||||
$routingCtx->register(new \Misuzu\Changelog\ChangelogRoutes(
|
||||
$this->siteInfo,
|
||||
$this->urls,
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
{% extends 'master.twig' %}
|
||||
{% from 'macros.twig' import container_title %}
|
||||
{% from '_layout/input.twig' import input_csrf %}
|
||||
|
||||
{% set title = title|default('Confirm your action') %}
|
||||
|
||||
{% block content %}
|
||||
<form action="{{ action|default('') }}" method="{{ method|default('post') }}" class="container confirm">
|
||||
{{ container_title('<i class="' ~ class|default('fas fa-exclamation-circle') ~ ' fa-fw"></i> ' ~ title) }}
|
||||
{{ input_csrf() }}
|
||||
{% for name, value in params|default([]) %}
|
||||
{% if value is not empty %}
|
||||
<input type="hidden" name="{{ name }}" value="{{ value }}"/>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="confirm__message">
|
||||
{{ message|default('Are you sure you w') }}
|
||||
</div>
|
||||
<div class="confirm__buttons">
|
||||
<input type="submit" class="input__button confirm__button" value="Yes">
|
||||
<a href="{{ return|default('/') }}" class="input__button confirm__button">No</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,24 +0,0 @@
|
|||
{% extends 'forum/master.twig' %}
|
||||
{% from 'macros.twig' import container_title %}
|
||||
{% from '_layout/input.twig' import input_csrf %}
|
||||
|
||||
{% set title = title|default('Confirm your action') %}
|
||||
|
||||
{% 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('csrf') }}
|
||||
{% 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') }}
|
||||
</div>
|
||||
|
||||
<div class="forum__confirm__buttons">
|
||||
<button name="confirm" value="1" class="input__button forum__confirm__button">Yes</button>
|
||||
<button name="confirm" value="0" class="input__button forum__confirm__button">No</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -10,10 +10,14 @@
|
|||
{% block content %}
|
||||
{{ forum_header(forum_info.name, forum_breadcrumbs, true, canonical_url, [
|
||||
{
|
||||
'html': '<i class="far fa-check-circle"></i> Mark as Read',
|
||||
'url': url('forum-mark-single', {'forum': forum_info.id}),
|
||||
'display': forum_show_mark_as_read,
|
||||
'method': 'POST',
|
||||
html: '<i class="far fa-check-circle"></i> <span class="js-action-text">Mark as Read</span>',
|
||||
display: forum_show_mark_as_read,
|
||||
method: 'POST',
|
||||
url: url('forum-mark-as-read', { category: forum_info.id }),
|
||||
disableWith: 'Marking as read...',
|
||||
disableWithTarget: '.js-action-text',
|
||||
withCsrf: true,
|
||||
refreshOnSuccess: true,
|
||||
}
|
||||
]) }}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
{% if forum_show_mark_as_read %}
|
||||
<div class="container forum__actions">
|
||||
<a href="{{ url('forum-mark-global') }}" class="input__button forum__actions__button">Mark All Read</a>
|
||||
<button class="input__button forum__actions__button" type="button" data-method="POST" data-url="{{ url('forum-mark-as-read', { recursive: true }) }}" data-disable-with="Marking as read..." data-with-csrf="1" data-refresh-on-success="1">Mark All Read</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
<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 }}">
|
||||
<a class="{{ html_classes('forum__header__action', action.class|default('')) }}" {% if action.method is defined %}href="javascript:;" data-method="{{ action.method }}" data-url="{{ action.url }}"{% if action.disableWith is defined %} data-disable-with="{{ action.disableWith }}"{% endif %}{% if action.disableWithTarget is defined %} data-disable-with-target="{{ action.disableWithTarget }}"{% endif %}{% if action.withCsrf|default(false) %} data-with-csrf="1"{% endif %}{% if action.refreshOnSuccess|default(false) %} data-refresh-on-success="1"{% endif %}{% if action.redirectOnSuccess is defined and action.redirectOnSuccess is not empty %} data-redirect-on-success="{{ action.redirectOnSuccess }}"{% endif %}{% if action.confirm is defined %} data-confirm="{{ action.confirm }}"{% endif %}{% else %}href="{{ action.url }}"{% endif %}>
|
||||
{{ action.html|raw }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -549,8 +549,8 @@
|
|||
{% if perms.can_create_post|default(false) or can_edit or can_delete %}
|
||||
<div class="forum__post__actions">
|
||||
{% if post_is_deleted %}
|
||||
<a href="{{ url('forum-post-restore', {'post': post_id}) }}" class="forum__post__action forum__post__action--restore"><i class="fas fa-magic fa-fw"></i> Restore</a>
|
||||
<a href="{{ url('forum-post-nuke', {'post': post_id}) }}" class="forum__post__action forum__post__action--nuke"><i class="fas fa-radiation-alt fa-fw"></i> Permanently Delete</a>
|
||||
<button class="forum__post__action forum__post__action--restore" data-method="POST" data-url="{{ url('forum-post-restore', {'post': post_id}) }}" data-refresh-on-success="1" data-with-csrf="1" data-disable-with="Restoring..." data-disable-with-target=".js-action-text" data-confirm="Are you sure you want to restore this post?"><i class="fas fa-magic fa-fw"></i> <span class="js-action-text">Restore</span></button>
|
||||
<button class="forum__post__action forum__post__action--nuke" data-method="POST" data-url="{{ url('forum-post-nuke', {'post': post_id}) }}" data-redirect-on-success="{{ url('forum-topic', { topic: post.info.topicId }) }}" data-with-csrf="1" data-disable-with="Nuking..." data-disable-with-target=".js-action-text" data-confirm="Are you sure you want to PERMANENTLY DELETE this post?"><i class="fas fa-radiation-alt fa-fw"></i> <span class="js-action-text">Permanently Delete</span></button>
|
||||
{% else %}
|
||||
{# if perms.can_create_post|default(false) %}
|
||||
<a href="{{ url('forum-post-quote', {'post': post_id}) }}" class="forum__post__action forum__post__action--quote"><i class="fas fa-quote-left fa-fw"></i> Quote</a>
|
||||
|
@ -559,7 +559,7 @@
|
|||
<a href="{{ url('forum-post-edit', {'post': post_id}) }}" class="forum__post__action forum__post__action--edit"><i class="fas fa-edit fa-fw"></i> Edit</a>
|
||||
{% endif %}
|
||||
{% if can_delete %}
|
||||
<a href="{{ url('forum-post-delete', {'post': post_id}) }}" class="forum__post__action forum__post__action--delete"><i class="far fa-trash-alt fa-fw"></i> Delete</a>
|
||||
<button class="forum__post__action forum__post__action--delete" data-method="DELETE" data-url="{{ url('forum-post-delete', {'post': post_id}) }}" data-redirect-on-success="{{ perms.can_delete_any_post ? url('forum-post', { post: post.info.id }) : url('forum-topic', { topic: post.info.topicId }) }}" data-with-csrf="1" data-disable-with="Deleting..." data-disable-with-target=".js-action-text" data-confirm="Are you sure you want to delete this post?"><i class="fas fa-trash-alt fa-fw"></i> <span class="js-action-text">Delete</span></button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -17,39 +17,71 @@
|
|||
'page': topic_pagination.page > 1 ? topic_pagination.page : 0,
|
||||
}) %}
|
||||
|
||||
{% set forum_post_csrf = csrf_token() %}
|
||||
{% set topic_tools = forum_topic_tools(topic_info, topic_pagination, can_reply) %}
|
||||
{% set topic_notice = forum_topic_locked(topic_info.lockedTime, category_info.archived) ~ forum_topic_redirect(topic_redir_info|default(null)) %}
|
||||
{% set topic_actions = [
|
||||
{
|
||||
'html': '<i class="far fa-trash-alt fa-fw"></i> Delete',
|
||||
'url': url('forum-topic-delete', { topic: topic_info.id, csrf: csrf_token() }),
|
||||
'display': topic_can_delete,
|
||||
html: '<i class="fas fa-trash-alt fa-fw"></i> <span class="js-action-text">Delete</span>',
|
||||
display: topic_can_delete,
|
||||
method: 'DELETE',
|
||||
url: url('forum-topic-delete', { topic: topic_info.id }),
|
||||
confirm: 'Are you sure you want to delete this topic?',
|
||||
disableWith: 'Deleting...',
|
||||
disableWithTarget: '.js-action-text',
|
||||
withCsrf: true,
|
||||
refreshOnSuccess: topic_can_delete_any,
|
||||
redirectOnSuccess: (topic_can_delete_any ? false : url('forum-category', { forum: category_info.id })),
|
||||
},
|
||||
{
|
||||
'html': '<i class="fas fa-magic fa-fw"></i> Restore',
|
||||
'url': url('forum-topic-restore', { topic: topic_info.id, csrf: csrf_token() }),
|
||||
'display': topic_can_nuke_or_restore,
|
||||
html: '<i class="fas fa-magic fa-fw"></i> <span class="js-action-text">Restore</span>',
|
||||
display: topic_can_nuke_or_restore,
|
||||
method: 'POST',
|
||||
url: url('forum-topic-restore', { topic: topic_info.id }),
|
||||
confirm: 'Are you sure you want to restore this topic?',
|
||||
disableWith: 'Restoring...',
|
||||
disableWithTarget: '.js-action-text',
|
||||
withCsrf: true,
|
||||
refreshOnSuccess: true,
|
||||
},
|
||||
{
|
||||
'html': '<i class="fas fa-radiation-alt fa-fw"></i> Permanently Delete',
|
||||
'url': url('forum-topic-nuke', { topic: topic_info.id, csrf: csrf_token() }),
|
||||
'display': topic_can_nuke_or_restore,
|
||||
html: '<i class="fas fa-radiation-alt fa-fw"></i> <span class="js-action-text">Permanently Delete</span>',
|
||||
display: topic_can_nuke_or_restore,
|
||||
method: 'POST',
|
||||
url: url('forum-topic-nuke', { topic: topic_info.id }),
|
||||
confirm: 'Are you sure you want to PERMANENTLY DELETE this topic?',
|
||||
disableWith: 'Nuking...',
|
||||
disableWithTarget: '.js-action-text',
|
||||
withCsrf: true,
|
||||
redirectOnSuccess: url('forum-category', { forum: category_info.id }),
|
||||
},
|
||||
{
|
||||
'html': '<i class="fas fa-plus-circle fa-fw"></i> Bump',
|
||||
'url': url('forum-topic-bump', { topic: topic_info.id, csrf: csrf_token() }),
|
||||
'display': topic_can_bump,
|
||||
html: '<i class="fas fa-arrow-alt-circle-up fa-fw"></i> <span class="js-action-text">Bump</span>',
|
||||
display: topic_can_bump,
|
||||
method: 'POST',
|
||||
url: url('forum-topic-bump', { topic: topic_info.id }),
|
||||
disableWith: 'Bumping...',
|
||||
disableWithTarget: '.js-action-text',
|
||||
withCsrf: true,
|
||||
},
|
||||
{
|
||||
'html': '<i class="fas fa-lock fa-fw"></i> Lock',
|
||||
'url': url('forum-topic-lock', { topic: topic_info.id, csrf: csrf_token() }),
|
||||
'display': topic_can_lock and not topic_info.locked,
|
||||
html: '<i class="fas fa-lock fa-fw"></i> <span class="js-action-text">Lock</span>',
|
||||
display: topic_can_lock and not topic_info.locked,
|
||||
method: 'POST',
|
||||
url: url('forum-topic-lock', { topic: topic_info.id }),
|
||||
disableWith: 'Locking...',
|
||||
disableWithTarget: '.js-action-text',
|
||||
withCsrf: true,
|
||||
refreshOnSuccess: true,
|
||||
},
|
||||
{
|
||||
'html': '<i class="fas fa-lock-open fa-fw"></i> Unlock',
|
||||
'url': url('forum-topic-unlock', { topic: topic_info.id, csrf: csrf_token() }),
|
||||
'display': topic_can_lock and topic_info.locked,
|
||||
html: '<i class="fas fa-lock-open fa-fw"></i> <span class="js-action-text">Unlock</span>',
|
||||
display: topic_can_lock and topic_info.locked,
|
||||
method: 'POST',
|
||||
url: url('forum-topic-unlock', { topic: topic_info.id }),
|
||||
disableWith: 'Unlocking...',
|
||||
disableWithTarget: '.js-action-text',
|
||||
withCsrf: true,
|
||||
refreshOnSuccess: true,
|
||||
},
|
||||
] %}
|
||||
|
||||
|
|
Reference in a new issue