From 308ba3337745a68c1a644394b405f2d36dbe8860 Mon Sep 17 00:00:00 2001 From: flashwave Date: Wed, 18 Dec 2024 03:07:48 +0000 Subject: [PATCH] Replaced confirm pages with dynamic requests on the forum. --- assets/misuzu.css/forum/post.css | 3 + assets/misuzu.js/main.js | 62 +++ public-legacy/forum/index.php | 39 -- public-legacy/forum/post.php | 139 ------- public-legacy/forum/topic.php | 169 +-------- src/Forum/ForumCategories.php | 5 +- src/Forum/ForumCategoriesRoutes.php | 78 ++++ src/Forum/ForumPostsRoutes.php | 348 +++++++++++++++++ src/Forum/ForumTopics.php | 6 +- src/Forum/ForumTopicsRoutes.php | 568 ++++++++++++++++++++++++++++ src/LegacyRoutes.php | 20 +- src/MisuzuContext.php | 19 + templates/confirm.twig | 24 -- templates/forum/confirm.twig | 24 -- templates/forum/forum.twig | 12 +- templates/forum/index.twig | 2 +- templates/forum/macros.twig | 8 +- templates/forum/topic.twig | 70 +++- 18 files changed, 1159 insertions(+), 437 deletions(-) delete mode 100644 public-legacy/forum/post.php create mode 100644 src/Forum/ForumCategoriesRoutes.php create mode 100644 src/Forum/ForumPostsRoutes.php create mode 100644 src/Forum/ForumTopicsRoutes.php delete mode 100644 templates/confirm.twig delete mode 100644 templates/forum/confirm.twig diff --git a/assets/misuzu.css/forum/post.css b/assets/misuzu.css/forum/post.css index 0a2577b..8d05a13 100644 --- a/assets/misuzu.css/forum/post.css +++ b/assets/misuzu.css/forum/post.css @@ -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 { diff --git a/assets/misuzu.js/main.js b/assets/misuzu.js/main.js index d7913b7..d1e4b22 100644 --- a/assets/misuzu.js/main.js +++ b/assets/misuzu.js/main.js @@ -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'); diff --git a/public-legacy/forum/index.php b/public-legacy/forum/index.php index 03d2779..6eec488 100644 --- a/public-legacy/forum/index.php +++ b/public-legacy/forum/index.php @@ -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) { diff --git a/public-legacy/forum/post.php b/public-legacy/forum/post.php deleted file mode 100644 index 368a177..0000000 --- a/public-legacy/forum/post.php +++ /dev/null @@ -1,139 +0,0 @@ -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; -} diff --git a/public-legacy/forum/topic.php b/public-legacy/forum/topic.php index 0bdfd71..4bb9994 100644 --- a/public-legacy/forum/topic.php +++ b/public-legacy/forum/topic.php @@ -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, diff --git a/src/Forum/ForumCategories.php b/src/Forum/ForumCategories.php index 6fe94e9..f8cebbf 100644 --- a/src/Forum/ForumCategories.php +++ b/src/Forum/ForumCategories.php @@ -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(...)); diff --git a/src/Forum/ForumCategoriesRoutes.php b/src/Forum/ForumCategoriesRoutes.php new file mode 100644 index 0000000..eb4ac55 --- /dev/null +++ b/src/Forum/ForumCategoriesRoutes.php @@ -0,0 +1,78 @@ + '', 'rec' => ''])] + 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; + } +} diff --git a/src/Forum/ForumPostsRoutes.php b/src/Forum/ForumPostsRoutes.php new file mode 100644 index 0000000..600a1b1 --- /dev/null +++ b/src/Forum/ForumPostsRoutes.php @@ -0,0 +1,348 @@ +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/')] + 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//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//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; + } +} diff --git a/src/Forum/ForumTopics.php b/src/Forum/ForumTopics.php index 2aaf90e..c1ecd91 100644 --- a/src/Forum/ForumTopics.php +++ b/src/Forum/ForumTopics.php @@ -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); diff --git a/src/Forum/ForumTopicsRoutes.php b/src/Forum/ForumTopicsRoutes.php new file mode 100644 index 0000000..96ee786 --- /dev/null +++ b/src/Forum/ForumTopicsRoutes.php @@ -0,0 +1,568 @@ +redirect($this->urls->format('forum-topic', ['topic' => $topicId])); + } + + #[HttpDelete('/forum/topics/([0-9]+)')] + #[UrlFormat('forum-topic-delete', '/forum/topics/')] + 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//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//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//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//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//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; + } +} diff --git a/src/LegacyRoutes.php b/src/LegacyRoutes.php index 6caf549..20306c5 100644 --- a/src/LegacyRoutes.php +++ b/src/LegacyRoutes.php @@ -31,25 +31,14 @@ class LegacyRoutes implements RouteHandler, UrlSource { $urls->register('forum-index', '/forum'); $urls->register('forum-leaderboard', '/forum/leaderboard.php', ['id' => '', 'mode' => '']); - $urls->register('forum-mark-global', '/forum/index.php', ['m' => 'mark']); - $urls->register('forum-mark-single', '/forum/index.php', ['m' => 'mark', 'f' => '']); $urls->register('forum-topic-new', '/forum/posting.php', ['f' => '']); $urls->register('forum-reply-new', '/forum/posting.php', ['t' => '']); $urls->register('forum-category', '/forum/forum.php', ['f' => '', 'p' => '']); $urls->register('forum-category-root', '/forum/index.php', fragment: ''); - $urls->register('forum-topic', '/forum/topic.php', ['t' => '', 'page' => '']); + $urls->register('forum-topic', '/forum/topic.php', ['t' => '', 'page' => ''], ''); $urls->register('forum-topic-create', '/forum/posting.php', ['f' => '']); - $urls->register('forum-topic-bump', '/forum/topic.php', ['t' => '', 'm' => 'bump', 'csrf' => '']); - $urls->register('forum-topic-lock', '/forum/topic.php', ['t' => '', 'm' => 'lock', 'csrf' => '']); - $urls->register('forum-topic-unlock', '/forum/topic.php', ['t' => '', 'm' => 'unlock', 'csrf' => '']); - $urls->register('forum-topic-delete', '/forum/topic.php', ['t' => '', 'm' => 'delete', 'csrf' => '']); - $urls->register('forum-topic-restore', '/forum/topic.php', ['t' => '', 'm' => 'restore', 'csrf' => '']); - $urls->register('forum-topic-nuke', '/forum/topic.php', ['t' => '', 'm' => 'nuke', 'csrf' => '']); $urls->register('forum-post', '/forum/topic.php', ['p' => ''], 'p'); $urls->register('forum-post-create', '/forum/posting.php', ['t' => '']); - $urls->register('forum-post-delete', '/forum/post.php', ['p' => '', 'm' => 'delete']); - $urls->register('forum-post-restore', '/forum/post.php', ['p' => '', 'm' => 'restore']); - $urls->register('forum-post-nuke', '/forum/post.php', ['p' => '', 'm' => 'nuke']); $urls->register('forum-post-quote', '/forum/posting.php', ['q' => '']); $urls->register('forum-post-edit', '/forum/posting.php', ['p' => '', '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); diff --git a/src/MisuzuContext.php b/src/MisuzuContext.php index 16bf6cf..b7389ae 100644 --- a/src/MisuzuContext.php +++ b/src/MisuzuContext.php @@ -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, diff --git a/templates/confirm.twig b/templates/confirm.twig deleted file mode 100644 index 7e4d0d7..0000000 --- a/templates/confirm.twig +++ /dev/null @@ -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 %} -
- {{ container_title(' ' ~ title) }} - {{ input_csrf() }} - {% for name, value in params|default([]) %} - {% if value is not empty %} - - {% endif %} - {% endfor %} -
- {{ message|default('Are you sure you w') }} -
-
- - No -
-
-{% endblock %} diff --git a/templates/forum/confirm.twig b/templates/forum/confirm.twig deleted file mode 100644 index 636e00a..0000000 --- a/templates/forum/confirm.twig +++ /dev/null @@ -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 %} -
- {{ container_title(' ' ~ title) }} - {{ input_csrf('csrf') }} - {% for name, value in params %} - - {% endfor %} - -
- {{ message|default('Are you sure you w') }} -
- -
- - -
-
-{% endblock %} diff --git a/templates/forum/forum.twig b/templates/forum/forum.twig index df3021c..52d52af 100644 --- a/templates/forum/forum.twig +++ b/templates/forum/forum.twig @@ -10,10 +10,14 @@ {% block content %} {{ forum_header(forum_info.name, forum_breadcrumbs, true, canonical_url, [ { - 'html': ' Mark as Read', - 'url': url('forum-mark-single', {'forum': forum_info.id}), - 'display': forum_show_mark_as_read, - 'method': 'POST', + html: ' Mark as Read', + 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, } ]) }} diff --git a/templates/forum/index.twig b/templates/forum/index.twig index a9a6d73..91f640e 100644 --- a/templates/forum/index.twig +++ b/templates/forum/index.twig @@ -13,7 +13,7 @@ {% if forum_show_mark_as_read %}
- Mark All Read +
{% endif %} {% else %} diff --git a/templates/forum/macros.twig b/templates/forum/macros.twig index 03435c9..c0aef37 100644 --- a/templates/forum/macros.twig +++ b/templates/forum/macros.twig @@ -71,7 +71,7 @@
{% for action in actions %} {% if action.display is not defined or action.display %} - + {{ action.html|raw }} {% endif %} @@ -549,8 +549,8 @@ {% if perms.can_create_post|default(false) or can_edit or can_delete %}
{% if post_is_deleted %} - Restore - Permanently Delete + + {% else %} {# if perms.can_create_post|default(false) %} Quote @@ -559,7 +559,7 @@ Edit {% endif %} {% if can_delete %} - Delete + {% endif %} {% endif %}
diff --git a/templates/forum/topic.twig b/templates/forum/topic.twig index 4cebe2f..3a833b5 100644 --- a/templates/forum/topic.twig +++ b/templates/forum/topic.twig @@ -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': ' Delete', - 'url': url('forum-topic-delete', { topic: topic_info.id, csrf: csrf_token() }), - 'display': topic_can_delete, + html: ' Delete', + 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': ' Restore', - 'url': url('forum-topic-restore', { topic: topic_info.id, csrf: csrf_token() }), - 'display': topic_can_nuke_or_restore, + html: ' Restore', + 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': ' Permanently Delete', - 'url': url('forum-topic-nuke', { topic: topic_info.id, csrf: csrf_token() }), - 'display': topic_can_nuke_or_restore, + html: ' Permanently Delete', + 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': ' Bump', - 'url': url('forum-topic-bump', { topic: topic_info.id, csrf: csrf_token() }), - 'display': topic_can_bump, + html: ' Bump', + display: topic_can_bump, + method: 'POST', + url: url('forum-topic-bump', { topic: topic_info.id }), + disableWith: 'Bumping...', + disableWithTarget: '.js-action-text', + withCsrf: true, }, { - 'html': ' Lock', - 'url': url('forum-topic-lock', { topic: topic_info.id, csrf: csrf_token() }), - 'display': topic_can_lock and not topic_info.locked, + html: ' Lock', + 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': ' Unlock', - 'url': url('forum-topic-unlock', { topic: topic_info.id, csrf: csrf_token() }), - 'display': topic_can_lock and topic_info.locked, + html: ' Unlock', + 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, }, ] %}