misuzu/src/Forum/ForumPostsRoutes.php

352 lines
12 KiB
PHP
Raw Normal View History

<?php
namespace Misuzu\Forum;
use RuntimeException;
use Index\Http\{HttpRequest,HttpResponseBuilder};
2025-01-29 23:13:17 +00:00
use Index\Http\Routing\{HttpDelete,HttpGet,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\{CSRF,Perm};
2025-01-29 23:36:35 +00:00
use Misuzu\AuditLog\AuditLogData;
use Misuzu\Auth\AuthInfo;
use Misuzu\Users\UsersContext;
class ForumPostsRoutes implements RouteHandler, UrlSource {
2025-01-29 23:13:17 +00:00
use RouteHandlerCommon, UrlSourceCommon;
public function __construct(
private UrlRegistry $urls,
private ForumContext $forum,
private UsersContext $usersCtx,
2025-01-29 23:36:35 +00:00
private AuditLogData $auditLog,
private AuthInfo $authInfo,
) {}
#[HttpGet('/forum/posts/([0-9]+)')]
2024-12-20 00:29:14 +00:00
#[UrlFormat('forum-post', '/forum/posts/<post>')]
2025-01-29 23:13:17 +00:00
public function getPost(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
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
);
2024-12-19 01:22:26 +00:00
$pageNumber = ((int)floor($postsCount / 10)) + 1; // epic magic number
2025-01-29 23:13:17 +00:00
$response->redirect($this->urls->format('forum-topic', [
'topic' => $post->topicId,
'page' => $pageNumber,
2024-12-19 01:22:26 +00:00
'topic_fragment' => sprintf('p%s', $post->id),
]));
2025-01-29 23:13:17 +00:00
return 302;
}
#[HttpDelete('/forum/posts/([0-9]+)')]
#[UrlFormat('forum-post-delete', '/forum/posts/<post>')]
2025-01-29 23:13:17 +00:00
public function deletePost(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
2025-02-02 02:09:56 +00:00
if(!$this->authInfo->loggedIn)
return 401;
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
return 403;
$response->setHeader('X-CSRF-Token', CSRF::token());
2025-01-29 23:13:17 +00:00
if($this->usersCtx->hasActiveBan($this->authInfo->userInfo)) {
$response->statusCode = 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) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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)) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 404;
return [
'error' => [
'name' => 'forum:post:none',
'text' => "Couldn't find that forum post.",
],
];
}
if($topic->locked) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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)) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 403;
return [
'error' => [
'name' => 'forum:post:delete:access',
'text' => "You aren't allowed to delete that post.",
],
];
}
if($post->userId !== $this->authInfo->userId) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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],
2025-01-29 23:13:17 +00:00
$request->remoteAddress,
$request->countryCode
);
return 204;
}
#[HttpPost('/forum/posts/([0-9]+)/nuke')]
#[UrlFormat('forum-post-nuke', '/forum/posts/<post>/nuke')]
2025-01-29 23:13:17 +00:00
public function postPostNuke(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
2025-02-02 02:09:56 +00:00
if(!$this->authInfo->loggedIn)
return 401;
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
return 403;
$response->setHeader('X-CSRF-Token', CSRF::token());
2025-01-29 23:13:17 +00:00
if($this->usersCtx->hasActiveBan($this->authInfo->userInfo)) {
$response->statusCode = 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) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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)) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 403;
return [
'error' => [
'name' => 'forum:post:access',
'text' => "You aren't allowed to access that post.",
],
];
}
if(!$perms->check(Perm::F_POST_DELETE_ANY)) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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],
2025-01-29 23:13:17 +00:00
$request->remoteAddress,
$request->countryCode
);
return 204;
}
#[HttpPost('/forum/posts/([0-9]+)/restore')]
#[UrlFormat('forum-post-restore', '/forum/posts/<post>/restore')]
2025-01-29 23:13:17 +00:00
public function postPostRestore(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
2025-02-02 02:09:56 +00:00
if(!$this->authInfo->loggedIn)
return 401;
if(!CSRF::validate($request->getHeaderLine('X-CSRF-token')))
return 403;
$response->setHeader('X-CSRF-Token', CSRF::token());
2025-01-29 23:13:17 +00:00
if($this->usersCtx->hasActiveBan($this->authInfo->userInfo)) {
$response->statusCode = 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) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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)) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 403;
return [
'error' => [
'name' => 'forum:post:access',
'text' => "You aren't allowed to access that post.",
],
];
}
if(!$perms->check(Perm::F_POST_DELETE_ANY)) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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) {
2025-01-29 23:13:17 +00:00
$response->statusCode = 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],
2025-01-29 23:13:17 +00:00
$request->remoteAddress,
$request->countryCode
);
return 204;
}
}