misuzu/src/Forum/ForumPostsRoutes.php
2025-03-31 15:35:24 +00:00

292 lines
10 KiB
PHP

<?php
namespace Misuzu\Forum;
use RuntimeException;
use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Processors\Before;
use Index\Http\Routing\Routes\PatternRoute;
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\Perm;
use Misuzu\Auth\AuthInfo;
use Misuzu\Logs\LogsContext;
class ForumPostsRoutes implements RouteHandler, UrlSource {
use RouteHandlerCommon, UrlSourceCommon;
public function __construct(
private UrlRegistry $urls,
private ForumContext $forumCtx,
private LogsContext $logsCtx,
private AuthInfo $authInfo,
) {}
#[PatternRoute('GET', '/forum/posts/([0-9]+)')]
#[Before('authz:cookie')]
#[UrlFormat('forum-post', '/forum/posts/<post>')]
public function getPost(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
try {
$post = $this->forumCtx->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->forumCtx->posts->countPosts(
topicInfo: $post->topicId,
upToPostInfo: $post,
deleted: $canDeleteAny ? null : false
);
$pageNumber = ((int)floor($postsCount / 10)) + 1; // epic magic number
$response->redirect($this->urls->format('forum-topic', [
'topic' => $post->topicId,
'page' => $pageNumber,
'topic_fragment' => sprintf('p%s', $post->id),
]));
return 302;
}
#[PatternRoute('DELETE', '/forum/posts/([0-9]+)')]
#[Before('authz:cookie', type: 'json', required: true)]
#[Before('csrf:header', type: 'json')]
#[Before('authz:banned', type: 'json')]
#[UrlFormat('forum-post-delete', '/forum/posts/<post>')]
public function deletePost(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
try {
$post = $this->forumCtx->posts->getPost(
postId: $postId,
deleted: false,
);
} catch(RuntimeException $ex) {
$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)) {
$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->forumCtx->topics->getTopic(postInfo: $post);
if($topic->deleted) {
$response->statusCode = 404;
return [
'error' => [
'name' => 'forum:post:none',
'text' => "Couldn't find that forum post.",
],
];
}
if($topic->locked) {
$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)) {
$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) {
$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) {
$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->forumCtx->posts->getPost(topicInfo: $post->topicId);
if($originalPost->id === $post->id) {
$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->forumCtx->categories->getCategory(postInfo: $post);
if($category->archived) {
$response->statusCode = 400;
return [
'error' => [
'name' => 'forum:topic:archived',
'text' => "The forum category this topic belongs to is archived.",
],
];
}
$this->forumCtx->posts->deletePost($post);
$this->logsCtx->createAuthedLog('FORUM_POST_DELETE', [$post->id]);
return 204;
}
#[PatternRoute('POST', '/forum/posts/([0-9]+)/nuke')]
#[Before('authz:cookie', type: 'json', required: true)]
#[Before('csrf:header', type: 'json')]
#[Before('authz:banned', type: 'json')]
#[UrlFormat('forum-post-nuke', '/forum/posts/<post>/nuke')]
public function postPostNuke(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
try {
$post = $this->forumCtx->posts->getPost(
postId: $postId,
deleted: true,
);
} catch(RuntimeException $ex) {
$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)) {
$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)) {
$response->statusCode = 403;
return [
'error' => [
'name' => 'forum:post:nuke:access',
'text' => "You aren't allowed to nuke that post.",
],
];
}
$category = $this->forumCtx->categories->getCategory(postInfo: $post);
if($category->archived) {
$response->statusCode = 400;
return [
'error' => [
'name' => 'forum:post:archived',
'text' => "The forum category this post belongs to is archived.",
],
];
}
$this->forumCtx->posts->nukePost($post);
$this->logsCtx->createAuthedLog('FORUM_POST_NUKE', [$post->id]);
return 204;
}
#[PatternRoute('POST', '/forum/posts/([0-9]+)/restore')]
#[Before('authz:cookie', type: 'json', required: true)]
#[Before('csrf:header', type: 'json')]
#[Before('authz:banned', type: 'json')]
#[UrlFormat('forum-post-restore', '/forum/posts/<post>/restore')]
public function postPostRestore(HttpResponseBuilder $response, HttpRequest $request, string $postId): mixed {
try {
$post = $this->forumCtx->posts->getPost(
postId: $postId,
deleted: true,
);
} catch(RuntimeException $ex) {
$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)) {
$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)) {
$response->statusCode = 403;
return [
'error' => [
'name' => 'forum:post:restore:access',
'text' => "You aren't allowed to restore that post.",
],
];
}
$category = $this->forumCtx->categories->getCategory(postInfo: $post);
if($category->archived) {
$response->statusCode = 400;
return [
'error' => [
'name' => 'forum:post:archived',
'text' => "The forum category this post belongs to is archived.",
],
];
}
$this->forumCtx->posts->restorePost($post);
$this->logsCtx->createAuthedLog('FORUM_POST_RESTORE', [$post->id]);
return 204;
}
}