2024-12-18 03:07:48 +00:00
|
|
|
<?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};
|
2024-12-18 03:07:48 +00:00
|
|
|
use Misuzu\{CSRF,Perm};
|
2025-01-29 23:36:35 +00:00
|
|
|
use Misuzu\AuditLog\AuditLogData;
|
2024-12-18 03:07:48 +00:00
|
|
|
use Misuzu\Auth\AuthInfo;
|
|
|
|
use Misuzu\Users\UsersContext;
|
|
|
|
|
|
|
|
class ForumPostsRoutes implements RouteHandler, UrlSource {
|
2025-01-29 23:13:17 +00:00
|
|
|
use RouteHandlerCommon, UrlSourceCommon;
|
2024-12-18 03:07:48 +00:00
|
|
|
|
|
|
|
public function __construct(
|
|
|
|
private UrlRegistry $urls,
|
|
|
|
private ForumContext $forum,
|
|
|
|
private UsersContext $usersCtx,
|
2025-01-29 23:36:35 +00:00
|
|
|
private AuditLogData $auditLog,
|
2024-12-18 03:07:48 +00:00
|
|
|
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 {
|
2024-12-18 03:07:48 +00:00
|
|
|
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
|
2024-12-18 03:07:48 +00:00
|
|
|
|
2025-01-29 23:13:17 +00:00
|
|
|
$response->redirect($this->urls->format('forum-topic', [
|
2024-12-18 03:07:48 +00:00
|
|
|
'topic' => $post->topicId,
|
|
|
|
'page' => $pageNumber,
|
2024-12-19 01:22:26 +00:00
|
|
|
'topic_fragment' => sprintf('p%s', $post->id),
|
2024-12-18 03:07:48 +00:00
|
|
|
]));
|
2025-01-29 23:13:17 +00:00
|
|
|
|
|
|
|
return 302;
|
2024-12-18 03:07:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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)
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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
|
2024-12-18 03:07:48 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
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)
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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
|
2024-12-18 03:07:48 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
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)
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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;
|
2024-12-18 03:07:48 +00:00
|
|
|
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
|
2024-12-18 03:07:48 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
return 204;
|
|
|
|
}
|
|
|
|
}
|