2022-09-13 13:14:49 +00:00
|
|
|
<?php
|
|
|
|
namespace Misuzu;
|
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
use stdClass;
|
|
|
|
use RuntimeException;
|
2024-12-02 02:28:08 +00:00
|
|
|
use Misuzu\Forum\{ForumCategoryInfo,ForumPostInfo,ForumTopicInfo};
|
2022-09-13 13:14:49 +00:00
|
|
|
use Misuzu\Parsers\Parser;
|
2024-08-04 21:37:12 +00:00
|
|
|
use Index\XDateTime;
|
|
|
|
use Carbon\CarbonImmutable;
|
2022-09-13 13:14:49 +00:00
|
|
|
|
2024-12-02 02:28:08 +00:00
|
|
|
if(!isset($msz) || !($msz instanceof \Misuzu\MisuzuContext))
|
|
|
|
die('Script must be called through the Misuzu route dispatcher.');
|
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
if(!$msz->authInfo->isLoggedIn)
|
2023-08-31 15:59:53 +00:00
|
|
|
Template::throwError(401);
|
2022-09-13 13:14:49 +00:00
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
$currentUser = $msz->authInfo->userInfo;
|
2024-11-30 04:20:20 +00:00
|
|
|
$currentUserId = $currentUser->id;
|
2024-11-30 04:09:29 +00:00
|
|
|
if($msz->usersCtx->hasActiveBan($currentUser))
|
2023-09-06 20:06:07 +00:00
|
|
|
Template::throwError(403);
|
|
|
|
|
2022-09-13 13:14:49 +00:00
|
|
|
$forumPostingModes = [
|
|
|
|
'create', 'edit', 'quote', 'preview',
|
|
|
|
];
|
|
|
|
|
|
|
|
if(!empty($_POST)) {
|
|
|
|
$mode = !empty($_POST['post']['mode']) && is_string($_POST['post']['mode']) ? $_POST['post']['mode'] : 'create';
|
|
|
|
$postId = !empty($_POST['post']['id']) && is_string($_POST['post']['id']) ? (int)$_POST['post']['id'] : 0;
|
|
|
|
$topicId = !empty($_POST['post']['topic']) && is_string($_POST['post']['topic']) ? (int)$_POST['post']['topic'] : 0;
|
|
|
|
$forumId = !empty($_POST['post']['forum']) && is_string($_POST['post']['forum']) ? (int)$_POST['post']['forum'] : 0;
|
|
|
|
} else {
|
|
|
|
$mode = !empty($_GET['m']) && is_string($_GET['m']) ? $_GET['m'] : 'create';
|
|
|
|
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
|
|
|
|
$topicId = !empty($_GET['t']) && is_string($_GET['t']) ? (int)$_GET['t'] : 0;
|
|
|
|
$forumId = !empty($_GET['f']) && is_string($_GET['f']) ? (int)$_GET['f'] : 0;
|
|
|
|
}
|
|
|
|
|
2023-08-31 15:59:53 +00:00
|
|
|
if(!in_array($mode, $forumPostingModes, true))
|
|
|
|
Template::throwError(400);
|
2022-09-13 13:14:49 +00:00
|
|
|
|
|
|
|
if($mode === 'preview') {
|
|
|
|
header('Content-Type: text/plain; charset=utf-8');
|
|
|
|
|
|
|
|
$postText = (string)($_POST['post']['text']);
|
|
|
|
$postParser = (int)($_POST['post']['parser']);
|
|
|
|
|
|
|
|
if(!Parser::isValid($postParser)) {
|
|
|
|
http_response_code(400);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
http_response_code(200);
|
|
|
|
echo Parser::instance($postParser)->parseText(htmlspecialchars($postText));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-08-31 15:59:53 +00:00
|
|
|
if(empty($postId) && empty($topicId) && empty($forumId))
|
|
|
|
Template::throwError(404);
|
2022-09-13 13:14:49 +00:00
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
if(empty($postId)) {
|
|
|
|
$hasPostInfo = false;
|
|
|
|
} else {
|
|
|
|
try {
|
2024-12-02 02:28:08 +00:00
|
|
|
$postInfo = $msz->forumCtx->posts->getPost(postId: (string)$postId);
|
2023-08-28 01:17:34 +00:00
|
|
|
} catch(RuntimeException $ex) {
|
2023-08-31 15:59:53 +00:00
|
|
|
Template::throwError(404);
|
2023-08-28 01:17:34 +00:00
|
|
|
}
|
2022-09-13 13:14:49 +00:00
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
if($postInfo->deleted)
|
2023-08-31 15:59:53 +00:00
|
|
|
Template::throwError(404);
|
2023-08-28 01:17:34 +00:00
|
|
|
|
|
|
|
// should automatic cross-quoting be a thing? if so, check if $topicId is < 1 first <-- what did i mean by this?
|
2024-11-30 04:09:29 +00:00
|
|
|
$topicId = $postInfo->topicId;
|
2023-08-28 01:17:34 +00:00
|
|
|
$hasPostInfo = true;
|
2022-09-13 13:14:49 +00:00
|
|
|
}
|
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
if(empty($topicId)) {
|
|
|
|
$hasTopicInfo = false;
|
|
|
|
} else {
|
|
|
|
try {
|
2024-11-30 04:09:29 +00:00
|
|
|
$topicInfo = $msz->forumCtx->topics->getTopic(topicId: $topicId);
|
2023-08-28 01:17:34 +00:00
|
|
|
} catch(RuntimeException $ex) {
|
2023-08-31 15:59:53 +00:00
|
|
|
Template::throwError(404);
|
2023-08-28 01:17:34 +00:00
|
|
|
}
|
2022-09-13 13:14:49 +00:00
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
if($topicInfo->deleted)
|
2023-08-31 15:59:53 +00:00
|
|
|
Template::throwError(404);
|
2022-09-13 13:14:49 +00:00
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
$forumId = $topicInfo->categoryId;
|
|
|
|
$originalPostInfo = $msz->forumCtx->posts->getPost(topicInfo: $topicInfo);
|
2023-08-28 01:17:34 +00:00
|
|
|
$hasTopicInfo = true;
|
2022-09-13 13:14:49 +00:00
|
|
|
}
|
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
if(empty($forumId)) {
|
|
|
|
$hasCategoryInfo = false;
|
|
|
|
} else {
|
|
|
|
try {
|
2024-11-30 04:09:29 +00:00
|
|
|
$categoryInfo = $msz->forumCtx->categories->getCategory(categoryId: $forumId);
|
2023-08-28 01:17:34 +00:00
|
|
|
} catch(RuntimeException $ex) {
|
2023-08-31 15:59:53 +00:00
|
|
|
Template::throwError(404);
|
2023-08-28 01:17:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$hasCategoryInfo = true;
|
2022-09-13 13:14:49 +00:00
|
|
|
}
|
|
|
|
|
2024-12-02 02:28:08 +00:00
|
|
|
if(!isset($categoryInfo) || !($categoryInfo instanceof ForumCategoryInfo))
|
|
|
|
Template::throwError(404);
|
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
$perms = $msz->authInfo->getPerms('forum', $categoryInfo);
|
2022-09-13 13:14:49 +00:00
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
if($categoryInfo->archived
|
|
|
|
|| (isset($topicInfo) && $topicInfo->locked && !$perms->check(Perm::F_TOPIC_LOCK))
|
2023-08-30 22:37:21 +00:00
|
|
|
|| !$perms->check(Perm::F_CATEGORY_VIEW)
|
|
|
|
|| !$perms->check(Perm::F_POST_CREATE)
|
2023-08-31 15:59:53 +00:00
|
|
|
|| (!isset($topicInfo) && !$perms->check(Perm::F_TOPIC_CREATE)))
|
|
|
|
Template::throwError(403);
|
2022-09-13 13:14:49 +00:00
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
if(!$categoryInfo->mayHaveTopics)
|
2023-08-31 15:59:53 +00:00
|
|
|
Template::throwError(400);
|
2022-09-13 13:14:49 +00:00
|
|
|
|
|
|
|
$topicTypes = [];
|
|
|
|
|
|
|
|
if($mode === 'create' || $mode === 'edit') {
|
2023-08-28 01:17:34 +00:00
|
|
|
$topicTypes['discussion'] = 'Normal discussion';
|
|
|
|
|
2023-08-30 22:37:21 +00:00
|
|
|
if($perms->check(Perm::F_TOPIC_STICKY))
|
2023-08-28 01:17:34 +00:00
|
|
|
$topicTypes['sticky'] = 'Sticky topic';
|
2023-08-30 22:37:21 +00:00
|
|
|
if($perms->check(Perm::F_TOPIC_ANNOUNCE_LOCAL))
|
2023-08-28 01:17:34 +00:00
|
|
|
$topicTypes['announce'] = 'Announcement';
|
2023-08-30 22:37:21 +00:00
|
|
|
if($perms->check(Perm::F_TOPIC_ANNOUNCE_GLOBAL))
|
2023-08-28 01:17:34 +00:00
|
|
|
$topicTypes['global'] = 'Global Announcement';
|
2022-09-13 13:14:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// edit mode stuff
|
2024-12-02 02:28:08 +00:00
|
|
|
if($mode === 'edit') {
|
|
|
|
if(!isset($postInfo) || !($postInfo instanceof ForumPostInfo))
|
|
|
|
Template::throwError(404);
|
|
|
|
if(!$perms->check($postInfo->userId === $currentUserId ? Perm::F_POST_EDIT_OWN : Perm::F_POST_EDIT_ANY))
|
|
|
|
Template::throwError(403);
|
|
|
|
}
|
2022-09-13 13:14:49 +00:00
|
|
|
|
|
|
|
$notices = [];
|
|
|
|
|
|
|
|
if(!empty($_POST)) {
|
|
|
|
$topicTitle = $_POST['post']['title'] ?? '';
|
|
|
|
$postText = $_POST['post']['text'] ?? '';
|
|
|
|
$postParser = (int)($_POST['post']['parser'] ?? Parser::BBCODE);
|
2023-08-28 01:17:34 +00:00
|
|
|
$topicType = isset($_POST['post']['type']) ? $_POST['post']['type'] : null;
|
2022-09-13 13:14:49 +00:00
|
|
|
$postSignature = isset($_POST['post']['signature']);
|
|
|
|
|
|
|
|
if(!CSRF::validateRequest()) {
|
|
|
|
$notices[] = 'Could not verify request.';
|
|
|
|
} else {
|
2024-11-30 04:09:29 +00:00
|
|
|
$isEditingTopic = empty($topicInfo) || ($mode === 'edit' && $originalPostInfo->id == $postInfo->id);
|
2023-08-28 01:17:34 +00:00
|
|
|
|
2022-09-13 13:14:49 +00:00
|
|
|
if($mode === 'create') {
|
2023-08-28 01:17:34 +00:00
|
|
|
$postTimeout = $cfg->getInteger('forum.posting.timeout', 5);
|
|
|
|
if($postTimeout > 0) {
|
2024-08-04 21:37:12 +00:00
|
|
|
$postTimeoutThreshold = new CarbonImmutable(sprintf('-%d seconds', $postTimeout));
|
2024-11-30 04:09:29 +00:00
|
|
|
$lastPostCreatedAt = $msz->forumCtx->posts->getUserLastPostCreatedAt($currentUser);
|
2023-08-28 01:17:34 +00:00
|
|
|
|
2024-08-04 21:37:12 +00:00
|
|
|
if(XDateTime::compare($lastPostCreatedAt, $postTimeoutThreshold) > 0) {
|
|
|
|
$waitSeconds = $postTimeout + ((int)$lastPostCreatedAt->format('U') - time());
|
2022-09-13 13:14:49 +00:00
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
$notices[] = sprintf("You're posting too quickly! Please wait %s seconds before posting again.", number_format($waitSeconds));
|
|
|
|
$notices[] = "It's possible that your post went through successfully and you pressed the submit button twice by accident.";
|
|
|
|
}
|
2022-09-13 13:14:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if($isEditingTopic) {
|
2024-12-02 02:28:08 +00:00
|
|
|
$originalTopicTitle = $topicInfo?->title ?? null; // @phpstan-ignore-line: nope it can be null
|
2022-09-13 13:14:49 +00:00
|
|
|
$topicTitleChanged = $topicTitle !== $originalTopicTitle;
|
2024-12-02 02:28:08 +00:00
|
|
|
$originalTopicType = $topicInfo?->typeString ?? 'discussion'; // @phpstan-ignore-line: this also
|
2022-09-13 13:14:49 +00:00
|
|
|
$topicTypeChanged = $topicType !== null && $topicType !== $originalTopicType;
|
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
$topicTitleLengths = $cfg->getValues([
|
|
|
|
['forum.topic.minLength:i', 3],
|
|
|
|
['forum.topic.maxLength:i', 100],
|
|
|
|
]);
|
2022-09-13 13:14:49 +00:00
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
$topicTitleLength = mb_strlen(trim($topicTitle));
|
|
|
|
if($topicTitleLength < $topicTitleLengths['forum.topic.minLength'])
|
|
|
|
$notices[] = 'Topic title was too short.';
|
|
|
|
elseif($topicTitleLength > $topicTitleLengths['forum.topic.maxLength'])
|
|
|
|
$notices[] = 'Topic title was too long.';
|
2022-09-13 13:14:49 +00:00
|
|
|
|
|
|
|
if($mode === 'create' && $topicType === null) {
|
|
|
|
$topicType = array_key_first($topicTypes);
|
|
|
|
} elseif(!array_key_exists($topicType, $topicTypes) && $topicTypeChanged) {
|
|
|
|
$notices[] = 'You are not allowed to set this topic type.';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
if(!Parser::isValid($postParser))
|
2022-09-13 13:14:49 +00:00
|
|
|
$notices[] = 'Invalid parser selected.';
|
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
$postTextLengths = $cfg->getValues([
|
|
|
|
['forum.post.minLength:i', 1],
|
|
|
|
['forum.post.maxLength:i', 60000],
|
|
|
|
]);
|
2022-09-13 13:14:49 +00:00
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
$postTextLength = mb_strlen(trim($postText));
|
|
|
|
if($postTextLength < $postTextLengths['forum.post.minLength'])
|
|
|
|
$notices[] = 'Post content was too short.';
|
|
|
|
elseif($postTextLength > $postTextLengths['forum.post.maxLength'])
|
|
|
|
$notices[] = 'Post content was too long.';
|
2022-09-13 13:14:49 +00:00
|
|
|
|
|
|
|
if(empty($notices)) {
|
|
|
|
switch($mode) {
|
|
|
|
case 'create':
|
2023-08-28 01:17:34 +00:00
|
|
|
if(empty($topicInfo)) {
|
2024-11-30 04:09:29 +00:00
|
|
|
$topicInfo = $msz->forumCtx->topics->createTopic(
|
2023-08-28 01:17:34 +00:00
|
|
|
$categoryInfo,
|
|
|
|
$currentUser,
|
2022-09-13 13:14:49 +00:00
|
|
|
$topicTitle,
|
|
|
|
$topicType
|
|
|
|
);
|
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
$topicId = $topicInfo->id;
|
|
|
|
$msz->forumCtx->categories->incrementCategoryTopics($categoryInfo);
|
2023-08-28 01:17:34 +00:00
|
|
|
} else
|
2024-11-30 04:09:29 +00:00
|
|
|
$msz->forumCtx->topics->bumpTopic($topicInfo);
|
2023-08-28 01:17:34 +00:00
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
$postInfo = $msz->forumCtx->posts->createPost(
|
2022-09-13 13:14:49 +00:00
|
|
|
$topicId,
|
2023-08-28 01:17:34 +00:00
|
|
|
$currentUser,
|
2023-01-05 18:33:03 +00:00
|
|
|
$_SERVER['REMOTE_ADDR'],
|
2022-09-13 13:14:49 +00:00
|
|
|
$postText,
|
|
|
|
$postParser,
|
2023-08-28 01:17:34 +00:00
|
|
|
$postSignature,
|
|
|
|
$categoryInfo
|
2022-09-13 13:14:49 +00:00
|
|
|
);
|
2023-08-28 01:17:34 +00:00
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
$postId = $postInfo->id;
|
|
|
|
$msz->forumCtx->categories->incrementCategoryPosts($categoryInfo);
|
2022-09-13 13:14:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'edit':
|
2024-11-30 04:09:29 +00:00
|
|
|
$markUpdated = $postInfo->userId === $currentUserId
|
|
|
|
&& $postInfo->shouldMarkAsEdited
|
|
|
|
&& $postText !== $postInfo->body;
|
2023-08-28 01:17:34 +00:00
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
$msz->forumCtx->posts->updatePost(
|
2024-12-02 02:28:08 +00:00
|
|
|
(string)$postId,
|
2023-08-28 01:17:34 +00:00
|
|
|
remoteAddr: $_SERVER['REMOTE_ADDR'],
|
|
|
|
body: $postText,
|
|
|
|
bodyParser: $postParser,
|
|
|
|
displaySignature: $postSignature,
|
|
|
|
bumpEdited: $markUpdated
|
|
|
|
);
|
|
|
|
|
|
|
|
if($isEditingTopic && ($topicTitleChanged || $topicTypeChanged))
|
2024-11-30 04:09:29 +00:00
|
|
|
$msz->forumCtx->topics->updateTopic(
|
2023-08-28 01:17:34 +00:00
|
|
|
$topicId,
|
|
|
|
title: $topicTitle,
|
|
|
|
type: $topicType
|
|
|
|
);
|
2022-09-13 13:14:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-12-02 02:28:08 +00:00
|
|
|
if(empty($notices)) { // @phpstan-ignore-line: i'm guessing it gets the type confused at this point
|
2023-08-28 01:17:34 +00:00
|
|
|
// does this ternary ever return forum-topic?
|
2024-11-30 04:09:29 +00:00
|
|
|
$redirect = $msz->urls->format(empty($topicInfo) ? 'forum-topic' : 'forum-post', [
|
2024-12-02 02:28:08 +00:00
|
|
|
'topic' => $topicId,
|
|
|
|
'post' => $postId,
|
2022-09-13 13:14:49 +00:00
|
|
|
]);
|
2023-09-08 20:40:48 +00:00
|
|
|
Tools::redirect($redirect);
|
2022-09-13 13:14:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
if(!empty($topicInfo))
|
|
|
|
Template::set('posting_topic', $topicInfo);
|
2022-09-13 13:14:49 +00:00
|
|
|
|
|
|
|
if($mode === 'edit') { // $post is pretty much sure to be populated at this point
|
2023-08-28 01:17:34 +00:00
|
|
|
$post = new stdClass;
|
|
|
|
$post->info = $postInfo;
|
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
if($postInfo->userId !== null) {
|
|
|
|
$post->user = $msz->usersCtx->getUserInfo($postInfo->userId);
|
|
|
|
$post->colour = $msz->usersCtx->getUserColour($post->user);
|
|
|
|
$post->postsCount = $msz->forumCtx->countTotalUserPosts($post->user);
|
2023-08-28 01:17:34 +00:00
|
|
|
}
|
|
|
|
|
2024-11-30 04:09:29 +00:00
|
|
|
$post->isOriginalPost = $originalPostInfo->id == $postInfo->id;
|
2024-12-01 01:37:53 +00:00
|
|
|
$post->isOriginalPoster = $originalPostInfo->userId !== null && $postInfo->userId !== null
|
2024-11-30 04:09:29 +00:00
|
|
|
&& $originalPostInfo->userId === $postInfo->userId;
|
2023-08-28 01:17:34 +00:00
|
|
|
|
2022-09-13 13:14:49 +00:00
|
|
|
Template::set('posting_post', $post);
|
|
|
|
}
|
|
|
|
|
2023-08-28 01:17:34 +00:00
|
|
|
try {
|
2024-11-30 04:09:29 +00:00
|
|
|
$lastPostInfo = $msz->forumCtx->posts->getPost(userInfo: $currentUser, getLast: true, deleted: false);
|
|
|
|
$selectedParser = $lastPostInfo->parser;
|
2023-08-28 01:17:34 +00:00
|
|
|
} catch(RuntimeException $ex) {
|
|
|
|
$selectedParser = Parser::BBCODE;
|
|
|
|
}
|
|
|
|
|
2022-09-13 13:14:49 +00:00
|
|
|
Template::render('forum.posting', [
|
2024-11-30 04:09:29 +00:00
|
|
|
'posting_breadcrumbs' => iterator_to_array($msz->forumCtx->categories->getCategoryAncestry($categoryInfo)),
|
|
|
|
'global_accent_colour' => $msz->forumCtx->categories->getCategoryColour($categoryInfo),
|
2023-08-28 01:17:34 +00:00
|
|
|
'posting_user' => $currentUser,
|
2024-11-30 04:09:29 +00:00
|
|
|
'posting_user_colour' => $msz->usersCtx->getUserColour($currentUser),
|
|
|
|
'posting_user_posts_count' => $msz->forumCtx->countTotalUserPosts($currentUser),
|
2023-08-28 01:17:34 +00:00
|
|
|
'posting_user_preferred_parser' => $selectedParser,
|
|
|
|
'posting_forum' => $categoryInfo,
|
2022-09-13 13:14:49 +00:00
|
|
|
'posting_notices' => $notices,
|
|
|
|
'posting_mode' => $mode,
|
|
|
|
'posting_types' => $topicTypes,
|
|
|
|
'posting_defaults' => [
|
|
|
|
'title' => $topicTitle ?? null,
|
|
|
|
'type' => $topicType ?? null,
|
|
|
|
'text' => $postText ?? null,
|
|
|
|
'parser' => $postParser ?? null,
|
|
|
|
'signature' => $postSignature ?? null,
|
|
|
|
],
|
|
|
|
]);
|