// whoa i can't believe it's more progress!
This commit is contained in:
parent
ceb05fc3f7
commit
0633a48f09
17 changed files with 456 additions and 759 deletions
|
@ -76,9 +76,6 @@ require_once 'src/perms.php';
|
|||
require_once 'src/manage.php';
|
||||
require_once 'src/url.php';
|
||||
require_once 'src/Forum/perms.php';
|
||||
require_once 'src/Forum/forum.php';
|
||||
require_once 'src/Forum/post.php';
|
||||
require_once 'src/Forum/topic.php';
|
||||
|
||||
$dbConfig = parse_ini_file(MSZ_CONFIG . '/config.ini', true, INI_SCANNER_TYPED);
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use Misuzu\AuditLog;
|
||||
use Misuzu\Forum\ForumPost;
|
||||
use Misuzu\Forum\ForumPostNotFoundException;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserSession;
|
||||
|
||||
|
@ -55,59 +57,34 @@ if($isXHR) {
|
|||
header(CSRF::header());
|
||||
}
|
||||
|
||||
$postInfo = forum_post_get($postId, true);
|
||||
$perms = empty($postInfo)
|
||||
? 0
|
||||
: forum_perms_get_user($postInfo['forum_id'], $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
try {
|
||||
$postInfo = ForumPost::byId($postId);
|
||||
$perms = forum_perms_get_user($postInfo->getCategoryId(), $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
} catch(ForumPostNotFoundException $ex) {
|
||||
$postInfo = null;
|
||||
$perms = 0;
|
||||
}
|
||||
|
||||
switch($postMode) {
|
||||
case 'delete':
|
||||
$canDelete = forum_post_can_delete($postInfo, $currentUserId);
|
||||
$canDeleteMsg = '';
|
||||
$responseCode = 200;
|
||||
$canDeleteCodes = [
|
||||
'view' => 404,
|
||||
'deleted' => 404,
|
||||
'owner' => 403,
|
||||
'age' => 403,
|
||||
'permission' => 403,
|
||||
'' => 200,
|
||||
];
|
||||
$canDelete = $postInfo->canBeDeleted($currentUser);
|
||||
$canDeleteMsg = ForumPost::canBeDeletedErrorString($canDelete);
|
||||
$responseCode = $canDeleteCodes[$canDelete] ?? 500;
|
||||
|
||||
switch($canDelete) {
|
||||
case MSZ_E_FORUM_POST_DELETE_USER: // i don't think this is ever reached but we may as well have it
|
||||
$responseCode = 401;
|
||||
$canDeleteMsg = 'You must be logged in to delete posts.';
|
||||
break;
|
||||
case MSZ_E_FORUM_POST_DELETE_POST:
|
||||
$responseCode = 404;
|
||||
$canDeleteMsg = "This post doesn't exist.";
|
||||
break;
|
||||
case MSZ_E_FORUM_POST_DELETE_DELETED:
|
||||
$responseCode = 404;
|
||||
$canDeleteMsg = 'This post has already been marked as deleted.';
|
||||
break;
|
||||
case MSZ_E_FORUM_POST_DELETE_OWNER:
|
||||
$responseCode = 403;
|
||||
$canDeleteMsg = 'You can only delete your own posts.';
|
||||
break;
|
||||
case MSZ_E_FORUM_POST_DELETE_OLD:
|
||||
$responseCode = 401;
|
||||
$canDeleteMsg = 'This post has existed for too long. Ask a moderator to remove if it absolutely necessary.';
|
||||
break;
|
||||
case MSZ_E_FORUM_POST_DELETE_PERM:
|
||||
$responseCode = 401;
|
||||
$canDeleteMsg = 'You are not allowed to delete posts.';
|
||||
break;
|
||||
case MSZ_E_FORUM_POST_DELETE_OP:
|
||||
$responseCode = 403;
|
||||
$canDeleteMsg = 'This is the opening post of a topic, it may not be deleted without deleting the entire topic as well.';
|
||||
break;
|
||||
case MSZ_E_FORUM_POST_DELETE_OK:
|
||||
break;
|
||||
default:
|
||||
$responseCode = 500;
|
||||
$canDeleteMsg = sprintf('Unknown error \'%d\'', $canDelete);
|
||||
}
|
||||
|
||||
if($canDelete !== MSZ_E_FORUM_POST_DELETE_OK) {
|
||||
if($canDelete !== '') {
|
||||
if($isXHR) {
|
||||
http_response_code($responseCode);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'post_id' => $postInfo['post_id'],
|
||||
'post_id' => $postInfo->getId(),
|
||||
'code' => $canDelete,
|
||||
'message' => $canDeleteMsg,
|
||||
]);
|
||||
|
@ -121,17 +98,17 @@ switch($postMode) {
|
|||
if(!$isXHR) {
|
||||
if($postRequestVerified && !$submissionConfirmed) {
|
||||
url_redirect('forum-post', [
|
||||
'post' => $postInfo['post_id'],
|
||||
'post_fragment' => 'p' . $postInfo['post_id'],
|
||||
'post' => $postInfo->getId(),
|
||||
'post_fragment' => 'p' . $postInfo->getId(),
|
||||
]);
|
||||
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['post_id']),
|
||||
'message' => sprintf('You are about to delete post #%d. Are you sure about that?', $postInfo->getId()),
|
||||
'params' => [
|
||||
'p' => $postInfo['post_id'],
|
||||
'p' => $postInfo->getId(),
|
||||
'm' => 'delete',
|
||||
],
|
||||
]);
|
||||
|
@ -139,16 +116,13 @@ switch($postMode) {
|
|||
}
|
||||
}
|
||||
|
||||
$deletePost = forum_post_delete($postInfo['post_id']);
|
||||
|
||||
if($deletePost) {
|
||||
AuditLog::create(AuditLog::FORUM_POST_DELETE, [$postInfo['post_id']]);
|
||||
}
|
||||
$postInfo->delete();
|
||||
AuditLog::create(AuditLog::FORUM_POST_DELETE, [$postInfo->getId()]);
|
||||
|
||||
if($isXHR) {
|
||||
echo json_encode([
|
||||
'success' => $deletePost,
|
||||
'post_id' => $postInfo['post_id'],
|
||||
'post_id' => $postInfo->getId(),
|
||||
'message' => $deletePost ? 'Post deleted!' : 'Failed to delete post.',
|
||||
]);
|
||||
break;
|
||||
|
@ -159,7 +133,7 @@ switch($postMode) {
|
|||
break;
|
||||
}
|
||||
|
||||
url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]);
|
||||
url_redirect('forum-topic', ['topic' => $postInfo->getTopicId()]);
|
||||
break;
|
||||
|
||||
case 'nuke':
|
||||
|
@ -171,17 +145,17 @@ switch($postMode) {
|
|||
if(!$isXHR) {
|
||||
if($postRequestVerified && !$submissionConfirmed) {
|
||||
url_redirect('forum-post', [
|
||||
'post' => $postInfo['post_id'],
|
||||
'post_fragment' => 'p' . $postInfo['post_id'],
|
||||
'post' => $postInfo->getId(),
|
||||
'post_fragment' => 'p' . $postInfo->getId(),
|
||||
]);
|
||||
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['post_id']),
|
||||
'message' => sprintf('You are about to PERMANENTLY DELETE post #%d. Are you sure about that?', $postInfo->getId()),
|
||||
'params' => [
|
||||
'p' => $postInfo['post_id'],
|
||||
'p' => $postInfo->getId(),
|
||||
'm' => 'nuke',
|
||||
],
|
||||
]);
|
||||
|
@ -189,18 +163,12 @@ switch($postMode) {
|
|||
}
|
||||
}
|
||||
|
||||
$nukePost = forum_post_nuke($postInfo['post_id']);
|
||||
|
||||
if(!$nukePost) {
|
||||
echo render_error(500);
|
||||
break;
|
||||
}
|
||||
|
||||
AuditLog::create(AuditLog::FORUM_POST_NUKE, [$postInfo['post_id']]);
|
||||
$postInfo->nuke();
|
||||
AuditLog::create(AuditLog::FORUM_POST_NUKE, [$postInfo->getId()]);
|
||||
http_response_code(204);
|
||||
|
||||
if(!$isXHR) {
|
||||
url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]);
|
||||
url_redirect('forum-topic', ['topic' => $postInfo->getTopicId()]);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -213,17 +181,17 @@ switch($postMode) {
|
|||
if(!$isXHR) {
|
||||
if($postRequestVerified && !$submissionConfirmed) {
|
||||
url_redirect('forum-post', [
|
||||
'post' => $postInfo['post_id'],
|
||||
'post_fragment' => 'p' . $postInfo['post_id'],
|
||||
'post' => $postInfo->getId(),
|
||||
'post_fragment' => 'p' . $postInfo->getId(),
|
||||
]);
|
||||
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['post_id']),
|
||||
'message' => sprintf('You are about to restore post #%d. Are you sure about that?', $postInfo->getId()),
|
||||
'params' => [
|
||||
'p' => $postInfo['post_id'],
|
||||
'p' => $postInfo->getId(),
|
||||
'm' => 'restore',
|
||||
],
|
||||
]);
|
||||
|
@ -231,49 +199,12 @@ switch($postMode) {
|
|||
}
|
||||
}
|
||||
|
||||
$restorePost = forum_post_restore($postInfo['post_id']);
|
||||
|
||||
if(!$restorePost) {
|
||||
echo render_error(500);
|
||||
break;
|
||||
}
|
||||
|
||||
AuditLog::create(AuditLog::FORUM_POST_RESTORE, [$postInfo['post_id']]);
|
||||
$postInfo->restore();
|
||||
AuditLog::create(AuditLog::FORUM_POST_RESTORE, [$postInfo->getId()]);
|
||||
http_response_code(204);
|
||||
|
||||
if(!$isXHR) {
|
||||
url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]);
|
||||
url_redirect('forum-topic', ['topic' => $postInfo->getTopicId()]);
|
||||
}
|
||||
break;
|
||||
|
||||
default: // function as an alt for topic.php?p= by default
|
||||
$canDeleteAny = perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST);
|
||||
|
||||
if(!empty($postInfo['post_deleted']) && !$canDeleteAny) {
|
||||
echo render_error(404);
|
||||
break;
|
||||
}
|
||||
|
||||
$postFind = forum_post_find($postInfo['post_id'], $currentUserId);
|
||||
|
||||
if(empty($postFind)) {
|
||||
echo render_error(404);
|
||||
break;
|
||||
}
|
||||
|
||||
if($canDeleteAny) {
|
||||
$postInfo['preceeding_post_count'] += $postInfo['preceeding_post_deleted_count'];
|
||||
}
|
||||
|
||||
unset($postInfo['preceeding_post_deleted_count']);
|
||||
|
||||
if($isXHR) {
|
||||
echo json_encode($postFind);
|
||||
break;
|
||||
}
|
||||
|
||||
url_redirect('forum-topic', [
|
||||
'topic' => $postFind['topic_id'],
|
||||
'page' => floor($postFind['preceeding_post_count'] / \Misuzu\Forum\ForumPost::PER_PAGE) + 1,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ use Misuzu\Forum\ForumTopicNotFoundException;
|
|||
use Misuzu\Forum\ForumTopicCreationFailedException;
|
||||
use Misuzu\Forum\ForumTopicUpdateFailedException;
|
||||
use Misuzu\Forum\ForumPost;
|
||||
use Misuzu\Forum\ForumPostCreationFailedException;
|
||||
use Misuzu\Forum\ForumPostUpdateFailedException;
|
||||
use Misuzu\Forum\ForumPostNotFoundException;
|
||||
use Misuzu\Net\IPAddress;
|
||||
use Misuzu\Parsers\Parser;
|
||||
|
@ -71,13 +73,11 @@ if(empty($postId) && empty($topicId) && empty($forumId)) {
|
|||
return;
|
||||
}
|
||||
|
||||
if(!empty($postId)) {
|
||||
$post = forum_post_get($postId);
|
||||
|
||||
if(isset($post['topic_id'])) { // should automatic cross-quoting be a thing? if so, check if $topicId is < 1 first
|
||||
$topicId = (int)$post['topic_id'];
|
||||
}
|
||||
}
|
||||
if(!empty($postId))
|
||||
try {
|
||||
$postInfo = ForumPost::byId($postId);
|
||||
$topicId = $postInfo->getTopicId();
|
||||
} catch(ForumPostNotFoundException $ex) {}
|
||||
|
||||
if(!empty($topicId))
|
||||
try {
|
||||
|
@ -122,12 +122,12 @@ if($mode === 'create' || $mode === 'edit') {
|
|||
|
||||
// edit mode stuff
|
||||
if($mode === 'edit') {
|
||||
if(empty($post)) {
|
||||
if(empty($postInfo)) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!perms_check($perms, $post['poster_id'] === $currentUserId ? MSZ_FORUM_PERM_EDIT_POST : MSZ_FORUM_PERM_EDIT_ANY_POST)) {
|
||||
if(!perms_check($perms, $postInfo->getUserId() === $currentUserId ? MSZ_FORUM_PERM_EDIT_POST : MSZ_FORUM_PERM_EDIT_ANY_POST)) {
|
||||
echo render_error(403);
|
||||
return;
|
||||
}
|
||||
|
@ -140,16 +140,16 @@ if(!empty($_POST)) {
|
|||
$topicTitle = $_POST['post']['title'] ?? '';
|
||||
$postText = $_POST['post']['text'] ?? '';
|
||||
$postParser = (int)($_POST['post']['parser'] ?? Parser::BBCODE);
|
||||
$topicType = isset($_POST['post']['type']) ? (int)$_POST['post']['type'] : null;
|
||||
$topicType = isset($_POST['post']['type']) ? (int)$_POST['post']['type'] : ForumTopic::TYPE_DISCUSSION;
|
||||
$postSignature = isset($_POST['post']['signature']);
|
||||
|
||||
if(!CSRF::validateRequest()) {
|
||||
$notices[] = 'Could not verify request.';
|
||||
} else {
|
||||
$isEditingTopic = $isNewTopic || ($mode === 'edit' && $post['is_opening_post']);
|
||||
$isEditingTopic = $isNewTopic || ($mode === 'edit' && $postInfo->isOpeningPost());
|
||||
|
||||
if($mode === 'create') {
|
||||
$timeoutCheck = max(1, forum_timeout($forumInfo->getId(), $currentUserId));
|
||||
$timeoutCheck = max(1, $forumInfo->checkCooldown($currentUser));
|
||||
|
||||
if($timeoutCheck < 5) {
|
||||
$notices[] = sprintf("You're posting too quickly! Please wait %s seconds before posting again.", number_format($timeoutCheck));
|
||||
|
@ -192,21 +192,25 @@ if(!empty($_POST)) {
|
|||
$topicId = $topicInfo->getId();
|
||||
}
|
||||
|
||||
$postId = forum_post_create(
|
||||
$topicId,
|
||||
$forumInfo->getId(),
|
||||
$currentUserId,
|
||||
IPAddress::remote(),
|
||||
$postText,
|
||||
$postParser,
|
||||
$postSignature
|
||||
);
|
||||
forum_topic_mark_read($currentUserId, $topicId, $forumInfo->getId());
|
||||
$postInfo = ForumPost::create($topicInfo, $currentUser, IPAddress::remote(), $postText, $postParser, $postSignature);
|
||||
$postId = $postInfo->getId();
|
||||
|
||||
$topicInfo->markRead($currentUser);
|
||||
$forumInfo->increaseTopicPostCount($isNewTopic);
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
if(!forum_post_update($postId, IPAddress::remote(), $postText, $postParser, $postSignature, $postText !== $post['post_text'])) {
|
||||
if($postText !== $postInfo->getBody() && $postInfo->shouldBumpEdited())
|
||||
$postInfo->bumpEdited();
|
||||
|
||||
$postInfo->setRemoteAddress(IPAddress::remote())
|
||||
->setBody($postText)
|
||||
->setBodyParser($postParser)
|
||||
->setDisplaySignature($postSignature);
|
||||
|
||||
try {
|
||||
$postInfo->update();
|
||||
} catch(ForumPostUpdateFailedException $ex) {
|
||||
$notices[] = 'Post edit failed.';
|
||||
}
|
||||
|
||||
|
@ -240,7 +244,7 @@ if(!$isNewTopic && !empty($topicInfo)) {
|
|||
}
|
||||
|
||||
if($mode === 'edit') { // $post is pretty much sure to be populated at this point
|
||||
Template::set('posting_post', $post);
|
||||
Template::set('posting_post', $postInfo);
|
||||
}
|
||||
|
||||
Template::render('forum.posting', [
|
||||
|
|
|
@ -19,12 +19,14 @@ $submissionConfirmed = filter_input(INPUT_GET, 'confirm') === '1';
|
|||
$topicUser = User::getCurrent();
|
||||
$topicUserId = $topicUser === null ? 0 : $topicUser->getId();
|
||||
|
||||
if($topicId < 1 && $postId > 0) {
|
||||
$postInfo = forum_post_find($postId, $topicUserId);
|
||||
|
||||
if(!empty($postInfo['topic_id']))
|
||||
$topicId = (int)$postInfo['topic_id'];
|
||||
}
|
||||
if($topicId < 1 && $postId > 0)
|
||||
try {
|
||||
$postInfo = ForumPost::byId($postId);
|
||||
$topicId = $postInfo->getTopicId();
|
||||
} catch(ForumPostNotFoundException $ex) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$topicInfo = ForumTopic::byId($topicId);
|
||||
|
@ -113,8 +115,8 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
|||
'posts' => 403,
|
||||
'' => 200,
|
||||
];
|
||||
$canDelete = $topicInfo->canDelete($topicUser);
|
||||
$canDeleteMsg = ForumTopic::canDeleteErrorString($canDelete);
|
||||
$canDelete = $topicInfo->canBeDeleted($topicUser);
|
||||
$canDeleteMsg = ForumTopic::canBeDeletedErrorString($canDelete);
|
||||
$responseCode = $canDeleteCodes[$canDelete] ?? 500;
|
||||
|
||||
if($canDelete !== '') {
|
||||
|
@ -283,15 +285,8 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
|||
|
||||
$topicPagination = new Pagination($topicInfo->getActualPostCount($canDeleteAny), \Misuzu\Forum\ForumPost::PER_PAGE, 'page');
|
||||
|
||||
if(isset($postInfo['preceeding_post_count'])) {
|
||||
$preceedingPosts = $postInfo['preceeding_post_count'];
|
||||
|
||||
if($canDeleteAny) {
|
||||
$preceedingPosts += $postInfo['preceeding_post_deleted_count'];
|
||||
}
|
||||
|
||||
$topicPagination->setPage(floor($preceedingPosts / $topicPagination->getRange()), true);
|
||||
}
|
||||
if(isset($postInfo))
|
||||
$topicPagination->setPage($postInfo->getTopicPage($canDeleteAny, $topicPagination->getRange()));
|
||||
|
||||
if(!$topicPagination->hasValidOffset()) {
|
||||
echo render_error(404);
|
||||
|
@ -300,7 +295,7 @@ if(!$topicPagination->hasValidOffset()) {
|
|||
|
||||
$canReply = !$topicInfo->isArchived() && !$topicInfo->isLocked() && !$topicInfo->isDeleted() && perms_check($perms, MSZ_FORUM_PERM_CREATE_POST);
|
||||
|
||||
forum_topic_mark_read($topicUserId, $topicInfo->getId(), $topicInfo->getCategoryId());
|
||||
$topicInfo->markRead($topicUser);
|
||||
|
||||
Template::render('forum.topic', [
|
||||
'topic_perms' => $perms,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Forum\ForumTopic;
|
||||
use Misuzu\Forum\ForumPost;
|
||||
use Misuzu\News\NewsPost;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
|
@ -11,7 +12,7 @@ $searchQuery = !empty($_GET['q']) && is_string($_GET['q']) ? $_GET['q'] : '';
|
|||
|
||||
if(!empty($searchQuery)) {
|
||||
$forumTopics = ForumTopic::bySearchQuery($searchQuery);
|
||||
$forumPosts = forum_post_search($searchQuery);
|
||||
$forumPosts = ForumPost::bySearchQuery($searchQuery);
|
||||
$newsPosts = NewsPost::bySearchQuery($searchQuery);
|
||||
|
||||
$findUsers = DB::prepare(sprintf(
|
||||
|
|
|
@ -28,11 +28,19 @@ class CronCommand implements CommandInterface {
|
|||
case 'func':
|
||||
call_user_func($task['command']);
|
||||
break;
|
||||
|
||||
case 'selffunc':
|
||||
call_user_func(self::class . '::' . $task['command']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function syncForum(): void {
|
||||
\Misuzu\Forum\ForumCategory::root()->synchronise(true);
|
||||
}
|
||||
|
||||
private const TASKS = [
|
||||
[
|
||||
'name' => 'Ensures main role exists.',
|
||||
|
@ -147,9 +155,9 @@ class CronCommand implements CommandInterface {
|
|||
],
|
||||
[
|
||||
'name' => 'Recount forum topics and posts.',
|
||||
'type' => 'func',
|
||||
'type' => 'selffunc',
|
||||
'slow' => true,
|
||||
'command' => 'forum_count_synchronise',
|
||||
'command' => 'syncForum',
|
||||
],
|
||||
[
|
||||
'name' => 'Clean up expired tfa tokens.',
|
||||
|
|
|
@ -351,15 +351,77 @@ class ForumCategory {
|
|||
return $this->checkLegacyPermission($user, MSZ_FORUM_PERM_SET_READ);
|
||||
}
|
||||
|
||||
public function hasUnread(?User $user): bool {
|
||||
if($user === null)
|
||||
return false;
|
||||
return forum_topics_unread($this->getId(), $user->getId());
|
||||
public function hasRead(User $user): bool {
|
||||
static $cache = [];
|
||||
|
||||
$cacheId = $user->getId() . ':' . $this->getId();
|
||||
if(isset($cache[$cacheId]))
|
||||
return $cache[$cacheId];
|
||||
|
||||
if(!$this->canView($user))
|
||||
return $cache[$cacheId] = true;
|
||||
|
||||
$countUnread = (int)DB::prepare(
|
||||
'SELECT COUNT(*) FROM `' . DB::PREFIX . ForumTopic::TABLE . '` AS ti'
|
||||
. ' LEFT JOIN `' . DB::PREFIX . ForumTopicTrack::TABLE . '` AS tt'
|
||||
. ' ON tt.`topic_id` = ti.`topic_id` AND tt.`user_id` = :user'
|
||||
. ' WHERE ti.`forum_id` = :forum AND ti.`topic_deleted` IS NULL'
|
||||
. ' AND ti.`topic_bumped` >= NOW() - INTERVAL :limit SECOND'
|
||||
. ' AND (tt.`track_last_read` IS NULL OR tt.`track_last_read` < ti.`topic_bumped`)'
|
||||
)->bind('forum', $this->getId())
|
||||
->bind('user', $user->getId())
|
||||
->bind('limit', ForumTopic::UNREAD_TIME_LIMIT)
|
||||
->fetchColumn();
|
||||
|
||||
if($countUnread > 0)
|
||||
return $cache[$cacheId] = false;
|
||||
|
||||
foreach($this->getChildren() as $child)
|
||||
if(!$child->hasRead($user))
|
||||
return $cache[$cacheId] = false;
|
||||
|
||||
return $cache[$cacheId] = true;
|
||||
}
|
||||
|
||||
public function markAsRead(User $user, bool $recursive = true): void {
|
||||
// Recursion is implied for now
|
||||
// Also forego recursion if we're root and just mark the entire forum as expected
|
||||
forum_mark_read($this->isRoot() ? null : $this->getId(), $user->getId());
|
||||
if($this->isRoot()) {
|
||||
if(!$recursive)
|
||||
return;
|
||||
$recursive = false;
|
||||
}
|
||||
|
||||
if($recursive) {
|
||||
$children = $this->getChildren($user);
|
||||
foreach($children as $child)
|
||||
$child->markAsRead($user, true);
|
||||
}
|
||||
|
||||
$mark = DB::prepare(
|
||||
'INSERT INTO `' . DB::PREFIX . ForumTopicTrack::TABLE . '`'
|
||||
. ' (`user_id`, `topic_id`, `forum_id`, `track_last_read`)'
|
||||
. ' SELECT u.`user_id`, t.`topic_id`, t.`forum_id`, NOW()'
|
||||
. ' FROM `msz_forum_topics` AS t'
|
||||
. ' LEFT JOIN `msz_users` AS u ON u.`user_id` = :user'
|
||||
. ' WHERE t.`topic_deleted` IS NULL'
|
||||
. ' AND t.`topic_bumped` >= NOW() - INTERVAL :limit SECOND'
|
||||
. ($this->isRoot() ? '' : ' AND t.`forum_id` = :forum')
|
||||
. ' GROUP BY t.`topic_id`'
|
||||
. ' ON DUPLICATE KEY UPDATE `track_last_read` = NOW()'
|
||||
)->bind('user', $user->getId())
|
||||
->bind('limit', ForumTopic::UNREAD_TIME_LIMIT);
|
||||
|
||||
if(!$this->isRoot())
|
||||
$mark->bind('forum', $this->getId());
|
||||
|
||||
$mark->execute();
|
||||
}
|
||||
|
||||
public function checkCooldown(User $user): int {
|
||||
return (int)DB::prepare(
|
||||
'SELECT TIMESTAMPDIFF(SECOND, COALESCE(MAX(`post_created`), NOW() - INTERVAL 1 YEAR), NOW())'
|
||||
. ' FROM `' . DB::PREFIX . ForumPost::TABLE . '`'
|
||||
. ' WHERE `forum_id` = :forum AND `user_id` = :user'
|
||||
)->bind('forum', $this->getId())->bind('user', $user->getId())->fetchColumn();
|
||||
}
|
||||
|
||||
public function getLatestTopic(?User $viewer = null): ?ForumTopic {
|
||||
|
|
|
@ -11,6 +11,7 @@ use Misuzu\Users\UserNotFoundException;
|
|||
class ForumPostException extends ForumException {}
|
||||
class ForumPostNotFoundException extends ForumPostException {}
|
||||
class ForumPostCreationFailedException extends ForumPostException {}
|
||||
class ForumPostUpdateFailedException extends ForumPostException {}
|
||||
|
||||
class ForumPost {
|
||||
public const PER_PAGE = 10;
|
||||
|
@ -18,6 +19,10 @@ class ForumPost {
|
|||
public const BODY_MIN_LENGTH = 1;
|
||||
public const BODY_MAX_LENGTH = 60000;
|
||||
|
||||
public const EDIT_BUMP_THRESHOLD = 60 * 5;
|
||||
|
||||
public const DELETE_AGE_LIMIT = 60 * 60 * 24 * 7;
|
||||
|
||||
// Database fields
|
||||
private $post_id = -1;
|
||||
private $topic_id = -1;
|
||||
|
@ -117,6 +122,10 @@ class ForumPost {
|
|||
public function getRemoteAddress(): string {
|
||||
return $this->post_ip;
|
||||
}
|
||||
public function setRemoteAddress(string $remoteAddress): self {
|
||||
$this->post_ip = $remoteAddress;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBody(): string {
|
||||
return $this->post_text;
|
||||
|
@ -157,6 +166,12 @@ class ForumPost {
|
|||
public function getCreatedTime(): int {
|
||||
return $this->post_created === null ? -1 : $this->post_created;
|
||||
}
|
||||
public function getAge(): int {
|
||||
return time() - $this->getCreatedTime();
|
||||
}
|
||||
public function shouldBumpEdited(): bool {
|
||||
return $this->getAge() > self::EDIT_BUMP_THRESHOLD;
|
||||
}
|
||||
|
||||
public function getEditedTime(): int {
|
||||
return $this->post_edited === null ? -1 : $this->post_edited;
|
||||
|
@ -188,6 +203,17 @@ class ForumPost {
|
|||
return $this->getTopic()->isTopicAuthor($this->getUser());
|
||||
}
|
||||
|
||||
public function getTopicOffset(bool $includeDeleted = false): int {
|
||||
return (int)DB::prepare(
|
||||
'SELECT COUNT(`post_id`) FROM `' . DB::PREFIX . self::TABLE . '`'
|
||||
. ' WHERE `topic_id` = :topic AND `post_id` < :post'
|
||||
. ($includeDeleted ? '' : ' AND `post_deleted` IS NULL')
|
||||
)->bind('topic', $this->getTopicId())->bind('post', $this->getId())->fetchColumn();
|
||||
}
|
||||
public function getTopicPage(bool $includeDeleted = false, int $postsPerPage = self::PER_PAGE): int {
|
||||
return floor($this->getTopicOffset() / $postsPerPage) + 1;
|
||||
}
|
||||
|
||||
public function canBeSeen(?User $user): bool {
|
||||
if($user === null && $this->isDeleted())
|
||||
return false;
|
||||
|
@ -202,13 +228,6 @@ class ForumPost {
|
|||
return $this->getUser()->getId() === $user->getId();
|
||||
}
|
||||
|
||||
// complete this implementation
|
||||
public function canBeDeleted(?User $user): bool {
|
||||
if($user === null)
|
||||
return false;
|
||||
return $this->getUser()->getId() === $user->getId();
|
||||
}
|
||||
|
||||
public static function validateBody(string $body): string {
|
||||
$length = mb_strlen(trim($body));
|
||||
if($length < self::BODY_MIN_LENGTH)
|
||||
|
@ -230,6 +249,73 @@ class ForumPost {
|
|||
}
|
||||
}
|
||||
|
||||
public function canBeDeleted(User $user): string {
|
||||
if(false) // check if viewable
|
||||
return 'view';
|
||||
|
||||
if($this->isOpeningPost())
|
||||
return 'opening';
|
||||
|
||||
// check if user can view deleted posts/is mod
|
||||
$canDeleteAny = false;
|
||||
|
||||
if($this->isDeleted())
|
||||
return $canDeleteAny ? 'deleted' : 'view';
|
||||
|
||||
if(!$canDeleteAny) {
|
||||
if(false) // check if user can delete posts
|
||||
return 'permission';
|
||||
if($user->getId() !== $this->getUserId())
|
||||
return 'owner';
|
||||
if($this->getCreatedTime() <= time() - self::DELETE_AGE_LIMIT)
|
||||
return 'age';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
public static function canBeDeletedErrorString(string $error): string {
|
||||
switch($error) {
|
||||
case 'view':
|
||||
return 'This post doesn\'t exist.';
|
||||
case 'deleted':
|
||||
return 'This post has already been marked as deleted.';
|
||||
case 'permission':
|
||||
return 'You aren\'t allowed to this post.';
|
||||
case 'owner':
|
||||
return 'You can only delete your own posts.';
|
||||
case 'age':
|
||||
return 'This post is too old to be deleted. Ask a moderator to remove it if you deem it absolutely necessary.';
|
||||
case '':
|
||||
return 'Post can be deleted!';
|
||||
default:
|
||||
return 'Post cannot be deleted.';
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(): void {
|
||||
if($this->isDeleted())
|
||||
return;
|
||||
$this->post_deleted = time();
|
||||
DB::prepare('UPDATE `' . DB::PREFIX . self::TABLE . '` SET `post_deleted` = NOW() WHERE `post_id` = :post')
|
||||
->bind('post', $this->getId())
|
||||
->execute();
|
||||
}
|
||||
public function restore(): void {
|
||||
if(!$this->isDeleted())
|
||||
return;
|
||||
$this->post_deleted = null;
|
||||
DB::prepare('UPDATE `' . DB::PREFIX . self::TABLE . '` SET `post_deleted` = NULL WHERE `post_id` = :post')
|
||||
->bind('post', $this->getId())
|
||||
->execute();
|
||||
}
|
||||
public function nuke(): void {
|
||||
if(!$this->isDeleted())
|
||||
return;
|
||||
DB::prepare('DELETE FROM `' . DB::PREFIX . self::TABLE . '` WHERE `post_id` = :post')
|
||||
->bind('post', $this->getId())
|
||||
->execute();
|
||||
}
|
||||
|
||||
public static function deleteTopic(ForumTopic $topic): void {
|
||||
// Deleting posts should only be possible while the topic is already in a deleted state
|
||||
if(!$topic->isDeleted())
|
||||
|
@ -261,6 +347,62 @@ class ForumPost {
|
|||
->execute();
|
||||
}
|
||||
|
||||
public static function create(
|
||||
ForumTopic $topic,
|
||||
User $user,
|
||||
string $ipAddress,
|
||||
string $text,
|
||||
int $parser = Parser::PLAIN,
|
||||
bool $displaySignature = true
|
||||
): ForumPost {
|
||||
$create = DB::prepare(
|
||||
'INSERT INTO `msz_forum_posts` ('
|
||||
. '`topic_id`, `forum_id`, `user_id`, `post_ip`, `post_text`, `post_parse`, `post_display_signature`'
|
||||
. ') VALUES (:topic, :forum, :user, INET6_ATON(:ip), :body, :parser, :display_signature)'
|
||||
)->bind('topic', $topic->getId())
|
||||
->bind('forum', $topic->getCategoryId())
|
||||
->bind('user', $user->getId())
|
||||
->bind('ip', $ipAddress)
|
||||
->bind('body', $text)
|
||||
->bind('parser', $parser)
|
||||
->bind('display_signature', $displaySignature ? 1 : 0)
|
||||
->execute();
|
||||
if(!$create)
|
||||
throw new ForumPostCreationFailedException;
|
||||
|
||||
$postId = DB::lastId();
|
||||
if($postId < 1)
|
||||
throw new ForumPostCreationFailedException;
|
||||
|
||||
try {
|
||||
return self::byId($postId);
|
||||
} catch(ForumPostNotFoundException $ex) {
|
||||
throw new ForumPostCreationFailedException;
|
||||
}
|
||||
}
|
||||
|
||||
public function update(): void {
|
||||
if($this->getId() < 1)
|
||||
throw new ForumPostUpdateFailedException;
|
||||
|
||||
if(!DB::prepare(
|
||||
'UPDATE `' . DB::PREFIX . self::TABLE . '`'
|
||||
. ' SET `post_ip` = INET6_ATON(:ip),'
|
||||
. ' `post_text` = :body,'
|
||||
. ' `post_parse` = :parser,'
|
||||
. ' `post_display_signature` = :display_signature,'
|
||||
. ' `post_edited` = FROM_UNIXTIME(:edited)'
|
||||
. ' WHERE `post_id` = :post'
|
||||
)->bind('post', $this->getId())
|
||||
->bind('ip', $this->getRemoteAddress())
|
||||
->bind('body', $this->getBody())
|
||||
->bind('parser', $this->getBodyParser())
|
||||
->bind('display_signature', $this->shouldDisplaySignature() ? 1 : 0)
|
||||
->bind('edited', $this->isEdited() ? $this->getEditedTime() : null)
|
||||
->execute())
|
||||
throw new ForumPostUpdateFailedException;
|
||||
}
|
||||
|
||||
private static function countQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf('COUNT(*)', self::TABLE));
|
||||
}
|
||||
|
@ -321,4 +463,26 @@ class ForumPost {
|
|||
$memoizer->insert($objects[] = $object);
|
||||
return $objects;
|
||||
}
|
||||
public static function bySearchQuery(string $search, bool $includeDeleted = false, ?Pagination $pagination = null): array {
|
||||
$query = self::byQueryBase()
|
||||
. ' WHERE MATCH(`post_text`) AGAINST (:search IN NATURAL LANGUAGE MODE)'
|
||||
. ($includeDeleted ? '' : ' AND `post_deleted` IS NULL')
|
||||
. ' ORDER BY `post_id`';
|
||||
|
||||
if($pagination !== null)
|
||||
$query .= ' LIMIT :range OFFSET :offset';
|
||||
|
||||
$getObjects = DB::prepare($query)
|
||||
->bind('search', $search);
|
||||
|
||||
if($pagination !== null)
|
||||
$getObjects->bind('range', $pagination->getRange())
|
||||
->bind('offset', $pagination->getOffset());
|
||||
|
||||
$objects = [];
|
||||
$memoizer = self::memoizer();
|
||||
while($object = $getObjects->fetchObject(self::class))
|
||||
$memoizer->insert($objects[] = $object);
|
||||
return $objects;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ class ForumTopic {
|
|||
public const DELETE_AGE_LIMIT = 60 * 60 * 24;
|
||||
public const DELETE_POST_LIMIT = 2;
|
||||
|
||||
public const UNREAD_TIME_LIMIT = 60 * 60 * 24 * 31;
|
||||
|
||||
// Database fields
|
||||
private $topic_id = -1;
|
||||
private $forum_id = -1;
|
||||
|
@ -155,7 +157,7 @@ class ForumTopic {
|
|||
if($this->hasPriorityVoting())
|
||||
return 'far fa-star fa-fw';
|
||||
|
||||
return ($this->hasUnread($viewer) ? 'fas' : 'far') . ' fa-comment fa-fw';
|
||||
return ($viewer === null || $this->hasRead($viewer) ? 'far' : 'fas') . ' fa-comment fa-fw';
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
|
@ -180,6 +182,12 @@ class ForumTopic {
|
|||
public function getViewCount(): int {
|
||||
return $this->topic_count_views;
|
||||
}
|
||||
public function incrementViewCount(): void {
|
||||
++$this->topic_count_views;
|
||||
DB::prepare('UPDATE `msz_forum_topics` SET `topic_count_views` = `topic_count_views` + 1 WHERE `topic_id` = :topic')
|
||||
->bind('topic', $this->getId())
|
||||
->execute();
|
||||
}
|
||||
|
||||
public function getFirstPostId(): int {
|
||||
return $this->topic_post_first < 1 ? -1 : $this->topic_post_first;
|
||||
|
@ -274,10 +282,24 @@ class ForumTopic {
|
|||
return $this->polls;
|
||||
}
|
||||
|
||||
public function hasUnread(?User $user): bool {
|
||||
if($user === null)
|
||||
public function isAbandoned(): bool {
|
||||
return $this->getBumpedTime() < time() - self::UNREAD_TIME_LIMIT;
|
||||
}
|
||||
public function hasRead(User $user): bool {
|
||||
if($this->isAbandoned())
|
||||
return true;
|
||||
|
||||
try {
|
||||
$trackInfo = ForumTopicTrack::byTopicAndUser($this, $user);
|
||||
return $trackInfo->getReadTime() >= $this->getBumpedTime();
|
||||
} catch(ForumTopicTrackNotFoundException $ex) {
|
||||
return false;
|
||||
return mt_rand(0, 10) >= 5;
|
||||
}
|
||||
}
|
||||
public function markRead(User $user): void {
|
||||
if(!$this->hasRead($user))
|
||||
$this->incrementViewCount();
|
||||
ForumTopicTrack::bump($this, $user);
|
||||
}
|
||||
|
||||
public function hasParticipated(?User $user): bool {
|
||||
|
@ -307,7 +329,7 @@ class ForumTopic {
|
|||
return $this->getCategory()->canView($user);
|
||||
}
|
||||
|
||||
public function canDelete(User $user): string {
|
||||
public function canBeDeleted(User $user): string {
|
||||
if(false) // check if viewable
|
||||
return 'view';
|
||||
|
||||
|
@ -330,7 +352,7 @@ class ForumTopic {
|
|||
|
||||
return '';
|
||||
}
|
||||
public static function canDeleteErrorString(string $error): string {
|
||||
public static function canBeDeletedErrorString(string $error): string {
|
||||
switch($error) {
|
||||
case 'view':
|
||||
return 'This topic doesn\'t exist.';
|
||||
|
@ -402,7 +424,7 @@ class ForumTopic {
|
|||
throw new ForumTopicUpdateFailedException;
|
||||
|
||||
if(!DB::prepare(
|
||||
'UPDATE `msz_forum_topics`'
|
||||
'UPDATE `' . DB::PREFIX . self::TABLE . '`'
|
||||
. ' SET `topic_title` = :title,'
|
||||
. ' `topic_type` = :type'
|
||||
. ' WHERE `topic_id` = :topic'
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
namespace Misuzu\Forum;
|
||||
|
||||
use Misuzu\DB;
|
||||
use Misuzu\Memoizer;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
class ForumTopicTrackException extends ForumException {}
|
||||
class ForumTopicTrackNotFoundException extends ForumTopicTrackException {}
|
||||
|
||||
class ForumTopicTrack {
|
||||
// Database fields
|
||||
private $user_id = -1;
|
||||
|
@ -50,4 +54,52 @@ class ForumTopicTrack {
|
|||
public function getReadTime(): int {
|
||||
return $this->track_last_read === null ? -1 : $this->track_last_read;
|
||||
}
|
||||
|
||||
public static function bump(ForumTopic $topic, User $user): void {
|
||||
DB::prepare(
|
||||
'REPLACE INTO `' . DB::PREFIX . self::TABLE . '`'
|
||||
. ' (`user_id`, `topic_id`, `forum_id`, `track_last_read`)'
|
||||
. ' VALUES (:user, :topic, :forum, NOW())'
|
||||
)->bind('user', $user->getId())
|
||||
->bind('topic', $topic->getId())
|
||||
->bind('forum', $topic->getCategoryId())
|
||||
->execute();
|
||||
}
|
||||
|
||||
private static function memoizer() {
|
||||
static $memoizer = null;
|
||||
if($memoizer === null)
|
||||
$memoizer = new Memoizer;
|
||||
return $memoizer;
|
||||
}
|
||||
|
||||
private static function byQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE));
|
||||
}
|
||||
public static function byTopicAndUser(ForumTopic $topic, User $user): ForumTopicTrack {
|
||||
return self::memoizer()->find(function($track) use ($topic, $user) {
|
||||
return $track->getTopicId() === $topic->getId() && $track->getUserId() === $user->getId();
|
||||
}, function() use ($topic, $user) {
|
||||
$obj = DB::prepare(self::byQueryBase() . ' WHERE `topic_id` = :topic AND `user_id` = :user')
|
||||
->bind('topic', $topic->getId())
|
||||
->bind('user', $user->getId())
|
||||
->fetchObject(self::class);
|
||||
if(!$obj)
|
||||
throw new ForumTopicTrackNotFoundException;
|
||||
return $obj;
|
||||
});
|
||||
}
|
||||
public static function byCategoryAndUser(ForumCategory $category, User $user): ForumTopicTrack {
|
||||
return self::memoizer()->find(function($track) use ($category, $user) {
|
||||
return $track->getCategoryId() === $category->getId() && $track->getUserId() === $user->getId();
|
||||
}, function() use ($category, $user) {
|
||||
$obj = DB::prepare(self::byQueryBase() . ' WHERE `forum_id` = :category AND `user_id` = :user')
|
||||
->bind('category', $category->getId())
|
||||
->bind('user', $user->getId())
|
||||
->fetchObject(self::class);
|
||||
if(!$obj)
|
||||
throw new ForumTopicTrackNotFoundException;
|
||||
return $obj;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,147 +0,0 @@
|
|||
<?php
|
||||
function forum_get_parent_id(int $forumId): int {
|
||||
if($forumId < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static $memoized = [];
|
||||
|
||||
if(array_key_exists($forumId, $memoized)) {
|
||||
return $memoized[$forumId];
|
||||
}
|
||||
|
||||
$getParent = \Misuzu\DB::prepare('
|
||||
SELECT `forum_parent`
|
||||
FROM `msz_forum_categories`
|
||||
WHERE `forum_id` = :forum_id
|
||||
');
|
||||
$getParent->bind('forum_id', $forumId);
|
||||
|
||||
return (int)$getParent->fetchColumn();
|
||||
}
|
||||
|
||||
function forum_get_child_ids(int $forumId): array {
|
||||
if($forumId < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
static $memoized = [];
|
||||
|
||||
if(array_key_exists($forumId, $memoized)) {
|
||||
return $memoized[$forumId];
|
||||
}
|
||||
|
||||
$getChildren = \Misuzu\DB::prepare('
|
||||
SELECT `forum_id`
|
||||
FROM `msz_forum_categories`
|
||||
WHERE `forum_parent` = :forum_id
|
||||
');
|
||||
$getChildren->bind('forum_id', $forumId);
|
||||
$children = $getChildren->fetchAll();
|
||||
|
||||
return $memoized[$forumId] = array_column($children, 'forum_id');
|
||||
}
|
||||
|
||||
function forum_topics_unread(int $forumId, int $userId): int {
|
||||
if($userId < 1 || $forumId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static $memoized = [];
|
||||
$memoId = "{$forumId}-{$userId}";
|
||||
|
||||
if(array_key_exists($memoId, $memoized)) {
|
||||
return $memoized[$memoId];
|
||||
}
|
||||
|
||||
$memoized[$memoId] = 0;
|
||||
$children = forum_get_child_ids($forumId);
|
||||
|
||||
foreach($children as $child) {
|
||||
$memoized[$memoId] += forum_topics_unread($child, $userId);
|
||||
}
|
||||
|
||||
if(forum_perms_check_user(MSZ_FORUM_PERMS_GENERAL, $forumId, $userId, MSZ_FORUM_PERM_SET_READ)) {
|
||||
$countUnread = \Misuzu\DB::prepare('
|
||||
SELECT COUNT(ti.`topic_id`)
|
||||
FROM `msz_forum_topics` AS ti
|
||||
LEFT JOIN `msz_forum_topics_track` AS tt
|
||||
ON tt.`topic_id` = ti.`topic_id` AND tt.`user_id` = :user_id
|
||||
WHERE ti.`forum_id` = :forum_id
|
||||
AND ti.`topic_deleted` IS NULL
|
||||
AND ti.`topic_bumped` >= NOW() - INTERVAL 1 MONTH
|
||||
AND (
|
||||
tt.`track_last_read` IS NULL
|
||||
OR tt.`track_last_read` < ti.`topic_bumped`
|
||||
)
|
||||
');
|
||||
$countUnread->bind('forum_id', $forumId);
|
||||
$countUnread->bind('user_id', $userId);
|
||||
$memoized[$memoId] += (int)$countUnread->fetchColumn();
|
||||
}
|
||||
|
||||
return $memoized[$memoId];
|
||||
}
|
||||
|
||||
function forum_timeout(int $forumId, int $userId): int {
|
||||
$checkTimeout = \Misuzu\DB::prepare('
|
||||
SELECT TIMESTAMPDIFF(SECOND, COALESCE(MAX(`post_created`), NOW() - INTERVAL 1 YEAR), NOW())
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `forum_id` = :forum_id
|
||||
AND `user_id` = :user_id
|
||||
');
|
||||
$checkTimeout->bind('forum_id', $forumId);
|
||||
$checkTimeout->bind('user_id', $userId);
|
||||
|
||||
return (int)$checkTimeout->fetchColumn();
|
||||
}
|
||||
|
||||
// $forumId == null marks all forums as read
|
||||
function forum_mark_read(?int $forumId, int $userId): void {
|
||||
if(($forumId !== null && $forumId < 1) || $userId < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entireForum = $forumId === null;
|
||||
|
||||
if(!$entireForum) {
|
||||
$children = forum_get_child_ids($forumId);
|
||||
|
||||
foreach($children as $child) {
|
||||
forum_mark_read($child, $userId);
|
||||
}
|
||||
}
|
||||
|
||||
$doMark = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
INSERT INTO `msz_forum_topics_track`
|
||||
(`user_id`, `topic_id`, `forum_id`, `track_last_read`)
|
||||
SELECT u.`user_id`, t.`topic_id`, t.`forum_id`, NOW()
|
||||
FROM `msz_forum_topics` AS t
|
||||
LEFT JOIN `msz_users` AS u
|
||||
ON u.`user_id` = :user
|
||||
WHERE t.`topic_deleted` IS NULL
|
||||
AND t.`topic_bumped` >= NOW() - INTERVAL 1 MONTH
|
||||
%1$s
|
||||
GROUP BY t.`topic_id`
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`track_last_read` = NOW()
|
||||
',
|
||||
$entireForum ? '' : 'AND t.`forum_id` = :forum'
|
||||
));
|
||||
$doMark->bind('user', $userId);
|
||||
|
||||
if(!$entireForum) {
|
||||
$doMark->bind('forum', $forumId);
|
||||
}
|
||||
|
||||
$doMark->execute();
|
||||
}
|
||||
|
||||
function forum_count_synchronise(int $forumId = \Misuzu\Forum\ForumCategory::ROOT_ID, bool $save = true): array {
|
||||
try {
|
||||
return \Misuzu\Forum\ForumCategory::byId($forumId)->synchronise($save);
|
||||
} catch(\Misuzu\Forum\ForumCategoryNotFoundException $ex) {
|
||||
return ['topics' => 0, 'posts' => 0];
|
||||
}
|
||||
}
|
|
@ -43,6 +43,27 @@ define('MSZ_FORUM_PERM_MODES', [
|
|||
MSZ_FORUM_PERMS_GENERAL,
|
||||
]);
|
||||
|
||||
function forum_get_parent_id(int $forumId): int {
|
||||
if($forumId < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static $memoized = [];
|
||||
|
||||
if(array_key_exists($forumId, $memoized)) {
|
||||
return $memoized[$forumId];
|
||||
}
|
||||
|
||||
$getParent = \Misuzu\DB::prepare('
|
||||
SELECT `forum_parent`
|
||||
FROM `msz_forum_categories`
|
||||
WHERE `forum_id` = :forum_id
|
||||
');
|
||||
$getParent->bind('forum_id', $forumId);
|
||||
|
||||
return (int)$getParent->fetchColumn();
|
||||
}
|
||||
|
||||
function forum_perms_get_user(?int $forum, int $user): array {
|
||||
$perms = perms_get_blank(MSZ_FORUM_PERM_MODES);
|
||||
|
||||
|
|
|
@ -1,358 +0,0 @@
|
|||
<?php
|
||||
function forum_post_create(
|
||||
int $topicId,
|
||||
int $forumId,
|
||||
int $userId,
|
||||
string $ipAddress,
|
||||
string $text,
|
||||
int $parser = \Misuzu\Parsers\Parser::PLAIN,
|
||||
bool $displaySignature = true
|
||||
): int {
|
||||
$createPost = \Misuzu\DB::prepare('
|
||||
INSERT INTO `msz_forum_posts`
|
||||
(`topic_id`, `forum_id`, `user_id`, `post_ip`, `post_text`, `post_parse`, `post_display_signature`)
|
||||
VALUES
|
||||
(:topic_id, :forum_id, :user_id, INET6_ATON(:post_ip), :post_text, :post_parse, :post_display_signature)
|
||||
');
|
||||
$createPost->bind('topic_id', $topicId);
|
||||
$createPost->bind('forum_id', $forumId);
|
||||
$createPost->bind('user_id', $userId);
|
||||
$createPost->bind('post_ip', $ipAddress);
|
||||
$createPost->bind('post_text', $text);
|
||||
$createPost->bind('post_parse', $parser);
|
||||
$createPost->bind('post_display_signature', $displaySignature ? 1 : 0);
|
||||
|
||||
return $createPost->execute() ? \Misuzu\DB::lastId() : 0;
|
||||
}
|
||||
|
||||
function forum_post_update(
|
||||
int $postId,
|
||||
string $ipAddress,
|
||||
string $text,
|
||||
int $parser = \Misuzu\Parsers\Parser::PLAIN,
|
||||
bool $displaySignature = true,
|
||||
bool $bumpUpdate = true
|
||||
): bool {
|
||||
if($postId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$updatePost = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_posts`
|
||||
SET `post_ip` = INET6_ATON(:post_ip),
|
||||
`post_text` = :post_text,
|
||||
`post_parse` = :post_parse,
|
||||
`post_display_signature` = :post_display_signature,
|
||||
`post_edited` = IF(:bump, NOW(), `post_edited`)
|
||||
WHERE `post_id` = :post_id
|
||||
');
|
||||
$updatePost->bind('post_id', $postId);
|
||||
$updatePost->bind('post_ip', $ipAddress);
|
||||
$updatePost->bind('post_text', $text);
|
||||
$updatePost->bind('post_parse', $parser);
|
||||
$updatePost->bind('post_display_signature', $displaySignature ? 1 : 0);
|
||||
$updatePost->bind('bump', $bumpUpdate ? 1 : 0);
|
||||
|
||||
return $updatePost->execute();
|
||||
}
|
||||
|
||||
function forum_post_find(int $postId, int $userId): array {
|
||||
$getPostInfo = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT
|
||||
p.`post_id`, p.`topic_id`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
AND `post_id` < p.`post_id`
|
||||
AND `post_deleted` IS NULL
|
||||
ORDER BY `post_id`
|
||||
) as `preceeding_post_count`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
AND `post_id` < p.`post_id`
|
||||
AND `post_deleted` IS NOT NULL
|
||||
ORDER BY `post_id`
|
||||
) as `preceeding_post_deleted_count`
|
||||
FROM `msz_forum_posts` AS p
|
||||
WHERE p.`post_id` = :post_id
|
||||
'));
|
||||
$getPostInfo->bind('post_id', $postId);
|
||||
return $getPostInfo->fetch();
|
||||
}
|
||||
|
||||
function forum_post_get(int $postId, bool $allowDeleted = false): array {
|
||||
$getPost = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT
|
||||
p.`post_id`, p.`post_text`, p.`post_created`, p.`post_parse`, p.`post_display_signature`,
|
||||
p.`topic_id`, p.`post_deleted`, p.`post_edited`, p.`topic_id`, p.`forum_id`,
|
||||
INET6_NTOA(p.`post_ip`) AS `post_ip`,
|
||||
u.`user_id` AS `poster_id`, u.`username` AS `poster_name`,
|
||||
u.`user_created` AS `poster_joined`, u.`user_country` AS `poster_country`,
|
||||
COALESCE(u.`user_colour`, r.`role_colour`) AS `poster_colour`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `user_id` = p.`user_id`
|
||||
AND `post_deleted` IS NULL
|
||||
) AS `poster_post_count`,
|
||||
(
|
||||
SELECT MIN(`post_id`) = p.`post_id`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
) AS `is_opening_post`,
|
||||
(
|
||||
SELECT `user_id` = u.`user_id`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
ORDER BY `post_id`
|
||||
LIMIT 1
|
||||
) AS `is_original_poster`
|
||||
FROM `msz_forum_posts` AS p
|
||||
LEFT JOIN `msz_users` AS u
|
||||
ON u.`user_id` = p.`user_id`
|
||||
LEFT JOIN `msz_roles` AS r
|
||||
ON r.`role_id` = u.`display_role`
|
||||
WHERE `post_id` = :post_id
|
||||
%1$s
|
||||
ORDER BY `post_id`
|
||||
',
|
||||
$allowDeleted ? '' : 'AND `post_deleted` IS NULL'
|
||||
));
|
||||
$getPost->bind('post_id', $postId);
|
||||
return $getPost->fetch();
|
||||
}
|
||||
|
||||
function forum_post_search(string $query): array {
|
||||
$searchPosts = \Misuzu\DB::prepare('
|
||||
SELECT
|
||||
p.`post_id`, p.`post_text`, p.`post_created`, p.`post_parse`, p.`post_display_signature`,
|
||||
p.`topic_id`, p.`post_deleted`, p.`post_edited`, p.`topic_id`, p.`forum_id`,
|
||||
INET6_NTOA(p.`post_ip`) AS `post_ip`,
|
||||
u.`user_id` AS `poster_id`, u.`username` AS `poster_name`,
|
||||
u.`user_created` AS `poster_joined`, u.`user_country` AS `poster_country`,
|
||||
u.`user_signature_content` AS `poster_signature_content`, u.`user_signature_parser` AS `poster_signature_parser`,
|
||||
COALESCE(u.`user_colour`, r.`role_colour`) AS `poster_colour`,
|
||||
COALESCE(u.`user_title`, r.`role_title`) AS `poster_title`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `user_id` = p.`user_id`
|
||||
AND `post_deleted` IS NULL
|
||||
) AS `poster_post_count`,
|
||||
(
|
||||
SELECT MIN(`post_id`) = p.`post_id`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
) AS `is_opening_post`,
|
||||
(
|
||||
SELECT `user_id` = u.`user_id`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
ORDER BY `post_id`
|
||||
LIMIT 1
|
||||
) AS `is_original_poster`
|
||||
FROM `msz_forum_posts` AS p
|
||||
LEFT JOIN `msz_users` AS u
|
||||
ON u.`user_id` = p.`user_id`
|
||||
LEFT JOIN `msz_roles` AS r
|
||||
ON r.`role_id` = u.`display_role`
|
||||
WHERE MATCH(p.`post_text`)
|
||||
AGAINST (:query IN NATURAL LANGUAGE MODE)
|
||||
AND `post_deleted` IS NULL
|
||||
ORDER BY `post_id`
|
||||
');
|
||||
$searchPosts->bind('query', $query);
|
||||
return $searchPosts->fetchAll();
|
||||
}
|
||||
|
||||
function forum_post_count_user(int $userId, bool $showDeleted = false): int {
|
||||
$getPosts = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT COUNT(p.`post_id`)
|
||||
FROM `msz_forum_posts` AS p
|
||||
WHERE `user_id` = :user_id
|
||||
%1$s
|
||||
',
|
||||
$showDeleted ? '' : 'AND `post_deleted` IS NULL'
|
||||
));
|
||||
$getPosts->bind('user_id', $userId);
|
||||
|
||||
return (int)$getPosts->fetchColumn();
|
||||
}
|
||||
|
||||
function forum_post_listing(
|
||||
int $topicId,
|
||||
int $offset = 0,
|
||||
int $take = 0,
|
||||
bool $showDeleted = false,
|
||||
bool $selectAuthor = false
|
||||
): array {
|
||||
$hasPagination = $offset >= 0 && $take > 0;
|
||||
$getPosts = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT
|
||||
p.`post_id`, p.`post_text`, p.`post_created`, p.`post_parse`,
|
||||
p.`topic_id`, p.`post_deleted`, p.`post_edited`, p.`post_display_signature`,
|
||||
INET6_NTOA(p.`post_ip`) AS `post_ip`,
|
||||
u.`user_id` AS `poster_id`, u.`username` AS `poster_name`,
|
||||
u.`user_created` AS `poster_joined`, u.`user_country` AS `poster_country`,
|
||||
u.`user_signature_content` AS `poster_signature_content`, u.`user_signature_parser` AS `poster_signature_parser`,
|
||||
COALESCE(u.`user_colour`, r.`role_colour`) AS `poster_colour`,
|
||||
COALESCE(u.`user_title`, r.`role_title`) AS `poster_title`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `user_id` = p.`user_id`
|
||||
AND `post_deleted` IS NULL
|
||||
) AS `poster_post_count`,
|
||||
(
|
||||
SELECT MIN(`post_id`) = p.`post_id`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
) AS `is_opening_post`,
|
||||
(
|
||||
SELECT `user_id` = u.`user_id`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
ORDER BY `post_id`
|
||||
LIMIT 1
|
||||
) AS `is_original_poster`
|
||||
FROM `msz_forum_posts` AS p
|
||||
LEFT JOIN `msz_users` AS u
|
||||
ON u.`user_id` = p.`user_id`
|
||||
LEFT JOIN `msz_roles` AS r
|
||||
ON r.`role_id` = u.`display_role`
|
||||
WHERE %3$s = :topic_id
|
||||
%1$s
|
||||
ORDER BY `post_id`
|
||||
%2$s
|
||||
',
|
||||
$showDeleted ? '' : 'AND `post_deleted` IS NULL',
|
||||
$hasPagination ? 'LIMIT :offset, :take' : '',
|
||||
$selectAuthor ? 'p.`user_id`' : 'p.`topic_id`'
|
||||
));
|
||||
$getPosts->bind('topic_id', $topicId);
|
||||
|
||||
if($hasPagination) {
|
||||
$getPosts->bind('offset', $offset);
|
||||
$getPosts->bind('take', $take);
|
||||
}
|
||||
|
||||
return $getPosts->fetchAll();
|
||||
}
|
||||
|
||||
define('MSZ_E_FORUM_POST_DELETE_OK', 0); // deleting is fine
|
||||
define('MSZ_E_FORUM_POST_DELETE_USER', 1); // invalid user
|
||||
define('MSZ_E_FORUM_POST_DELETE_POST', 2); // post doesn't exist
|
||||
define('MSZ_E_FORUM_POST_DELETE_DELETED', 3); // post is already marked as deleted
|
||||
define('MSZ_E_FORUM_POST_DELETE_OWNER', 4); // you may only delete your own posts
|
||||
define('MSZ_E_FORUM_POST_DELETE_OLD', 5); // posts has existed for too long to be deleted
|
||||
define('MSZ_E_FORUM_POST_DELETE_PERM', 6); // you aren't allowed to delete posts
|
||||
define('MSZ_E_FORUM_POST_DELETE_OP', 7); // this is the opening post of a topic
|
||||
|
||||
// only allow posts made within a week of posting to be deleted by normal users
|
||||
define('MSZ_FORUM_POST_DELETE_LIMIT', 60 * 60 * 24 * 7);
|
||||
|
||||
// set $userId to null for system request, make sure this is NEVER EVER null on user request
|
||||
// $postId can also be a the return value of forum_post_get if you already grabbed it once before
|
||||
function forum_post_can_delete($postId, ?int $userId = null): int {
|
||||
if($userId !== null && $userId < 1) {
|
||||
return MSZ_E_FORUM_POST_DELETE_USER;
|
||||
}
|
||||
|
||||
if(is_array($postId)) {
|
||||
$post = $postId;
|
||||
} else {
|
||||
$post = forum_post_get((int)$postId, true);
|
||||
}
|
||||
|
||||
if(empty($post)) {
|
||||
return MSZ_E_FORUM_POST_DELETE_POST;
|
||||
}
|
||||
|
||||
$isSystemReq = $userId === null;
|
||||
$perms = $isSystemReq ? 0 : forum_perms_get_user($post['forum_id'], $userId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
$canDeleteAny = $isSystemReq ? true : perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST);
|
||||
$canViewPost = $isSystemReq ? true : perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM);
|
||||
$postIsDeleted = !empty($post['post_deleted']);
|
||||
|
||||
if(!$canViewPost) {
|
||||
return MSZ_E_FORUM_POST_DELETE_POST;
|
||||
}
|
||||
|
||||
if($post['is_opening_post']) {
|
||||
return MSZ_E_FORUM_POST_DELETE_OP;
|
||||
}
|
||||
|
||||
if($postIsDeleted) {
|
||||
return $canDeleteAny ? MSZ_E_FORUM_POST_DELETE_DELETED : MSZ_E_FORUM_POST_DELETE_POST;
|
||||
}
|
||||
|
||||
if($isSystemReq) {
|
||||
return MSZ_E_FORUM_POST_DELETE_OK;
|
||||
}
|
||||
|
||||
if(!$canDeleteAny) {
|
||||
if(!perms_check($perms, MSZ_FORUM_PERM_DELETE_POST)) {
|
||||
return MSZ_E_FORUM_POST_DELETE_PERM;
|
||||
}
|
||||
|
||||
if($post['poster_id'] !== $userId) {
|
||||
return MSZ_E_FORUM_POST_DELETE_OWNER;
|
||||
}
|
||||
|
||||
if(strtotime($post['post_created']) <= time() - MSZ_FORUM_POST_DELETE_LIMIT) {
|
||||
return MSZ_E_FORUM_POST_DELETE_OLD;
|
||||
}
|
||||
}
|
||||
|
||||
return MSZ_E_FORUM_POST_DELETE_OK;
|
||||
}
|
||||
|
||||
function forum_post_delete(int $postId): bool {
|
||||
if($postId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markDeleted = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_posts`
|
||||
SET `post_deleted` = NOW()
|
||||
WHERE `post_id` = :post
|
||||
AND `post_deleted` IS NULL
|
||||
');
|
||||
$markDeleted->bind('post', $postId);
|
||||
return $markDeleted->execute();
|
||||
}
|
||||
|
||||
function forum_post_restore(int $postId): bool {
|
||||
if($postId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markDeleted = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_posts`
|
||||
SET `post_deleted` = NULL
|
||||
WHERE `post_id` = :post
|
||||
AND `post_deleted` IS NOT NULL
|
||||
');
|
||||
$markDeleted->bind('post', $postId);
|
||||
return $markDeleted->execute();
|
||||
}
|
||||
|
||||
function forum_post_nuke(int $postId): bool {
|
||||
if($postId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markDeleted = \Misuzu\DB::prepare('
|
||||
DELETE FROM `msz_forum_posts`
|
||||
WHERE `post_id` = :post
|
||||
');
|
||||
$markDeleted->bind('post', $postId);
|
||||
return $markDeleted->execute();
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
function forum_topic_views_increment(int $topicId): void {
|
||||
if($topicId < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bumpViews = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_topics`
|
||||
SET `topic_count_views` = `topic_count_views` + 1
|
||||
WHERE `topic_id` = :topic_id
|
||||
');
|
||||
$bumpViews->bind('topic_id', $topicId);
|
||||
$bumpViews->execute();
|
||||
}
|
||||
|
||||
function forum_topic_mark_read(int $userId, int $topicId, int $forumId): void {
|
||||
if($userId < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// previously a TRIGGER was used to achieve this behaviour,
|
||||
// but those explode when running on a lot of queries (like forum_mark_read() does)
|
||||
// so instead we get to live with this garbage now
|
||||
// JUST TO CLARIFY: "this behaviour" refers to forum_topic_views_increment only being executed when the topic is viewed for the first time
|
||||
try {
|
||||
$markAsRead = \Misuzu\DB::prepare('
|
||||
INSERT INTO `msz_forum_topics_track`
|
||||
(`user_id`, `topic_id`, `forum_id`, `track_last_read`)
|
||||
VALUES
|
||||
(:user_id, :topic_id, :forum_id, NOW())
|
||||
');
|
||||
$markAsRead->bind('user_id', $userId);
|
||||
$markAsRead->bind('topic_id', $topicId);
|
||||
$markAsRead->bind('forum_id', $forumId);
|
||||
|
||||
if($markAsRead->execute()) {
|
||||
forum_topic_views_increment($topicId);
|
||||
}
|
||||
} catch(PDOException $ex) {
|
||||
if($ex->getCode() != '23000') {
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$markAsRead = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_topics_track`
|
||||
SET `track_last_read` = NOW(),
|
||||
`forum_id` = :forum_id
|
||||
WHERE `user_id` = :user_id
|
||||
AND `topic_id` = :topic_id
|
||||
');
|
||||
$markAsRead->bind('user_id', $userId);
|
||||
$markAsRead->bind('topic_id', $topicId);
|
||||
$markAsRead->bind('forum_id', $forumId);
|
||||
$markAsRead->execute();
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ use Misuzu\HasRankInterface;
|
|||
use Misuzu\Memoizer;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\TOTP;
|
||||
use Misuzu\Forum\ForumCategory;
|
||||
use Misuzu\Net\IPAddress;
|
||||
use Misuzu\Parsers\Parser;
|
||||
use Misuzu\Users\Assets\UserAvatarAsset;
|
||||
|
|
|
@ -156,7 +156,7 @@
|
|||
<a href="{{ url('forum-category', {'forum': category.id}) }}" class="forum__category__link"></a>
|
||||
|
||||
<div class="forum__category__container">
|
||||
<div class="forum__category__icon forum__category__icon--{{ category.unread(user) ? 'un' : '' }}read">
|
||||
<div class="forum__category__icon forum__category__icon--{{ user is null or category.hasRead(user) ? '' : 'un' }}read">
|
||||
<span class="{{ category.icon }}"></span>
|
||||
</div>
|
||||
|
||||
|
@ -176,7 +176,7 @@
|
|||
{% for child in category.children %}
|
||||
{% if child.canView(user) %}
|
||||
<a href="{{ url('forum-category', {'forum': child.id}) }}"
|
||||
class="forum__category__subforum{% if child.unread(user) %} forum__category__subforum--unread{% endif %}">
|
||||
class="forum__category__subforum{% if user is not null and not child.hasRead(user) %} forum__category__subforum--unread{% endif %}">
|
||||
{{ child.name }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -401,7 +401,7 @@
|
|||
<a href="{{ url('forum-topic', {'topic': topic.id}) }}" class="forum__topic__link"></a>
|
||||
|
||||
<div class="forum__topic__container">
|
||||
<div class="forum__topic__icon forum__topic__icon--{{ topic.hasUnread(user) ? 'un' : '' }}read{% if topic.hasPriorityVoting %} forum__topic__icon--wide{% endif %}">
|
||||
<div class="forum__topic__icon forum__topic__icon--{{ user is null or topic.hasRead(user) ? '' : 'un' }}read{% if topic.hasPriorityVoting %} forum__topic__icon--wide{% endif %}">
|
||||
<i class="{{ topic.icon(user) }}{% if topic.hasPriorityVoting %} forum__topic__icon--faded{% endif %}"></i>
|
||||
|
||||
{% if topic.hasPriorityVoting %}
|
||||
|
@ -727,7 +727,7 @@
|
|||
perms|perms_check(constant('MSZ_FORUM_PERM_DELETE_ANY_POST')) or (
|
||||
user_id == post.poster_id
|
||||
and perms|perms_check(constant('MSZ_FORUM_PERM_DELETE_POST'))
|
||||
and post.post_created|date('U') > ''|date('U') - constant('MSZ_FORUM_POST_DELETE_LIMIT')
|
||||
and post.post_created|date('U') > ''|date('U') - constant('\\Misuzu\\Forum\\ForumPost::DELETE_AGE_LIMIT')
|
||||
)
|
||||
) %}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
{% set title = 'Posting' %}
|
||||
{% set is_reply = posting_topic is defined %}
|
||||
{% set is_opening = not is_reply or posting_post.is_opening_post|default(false) %}
|
||||
{% set is_opening = not is_reply or posting_post.isOpeningPost|default(false) %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" action="{{ url('forum-' ~ (is_reply ? 'post' : 'topic') ~ '-create') }}">
|
||||
|
@ -32,7 +32,7 @@
|
|||
) }}
|
||||
|
||||
{% if posting_post is defined %}
|
||||
{{ input_hidden('post[id]', posting_post.post_id) }}
|
||||
{{ input_hidden('post[id]', posting_post.id) }}
|
||||
{% endif %}
|
||||
|
||||
{% if posting_notices|length > 0 %}
|
||||
|
@ -45,21 +45,21 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="container forum__post js-forum-posting" style="{{ posting_post.poster_colour|default(current_user.colour.raw)|html_colour('--accent-colour') }}">
|
||||
<div class="container forum__post js-forum-posting" style="{{ posting_post.user.colour.raw|default(current_user.colour.raw)|html_colour('--accent-colour') }}">
|
||||
<div class="forum__post__info">
|
||||
<div class="forum__post__info__background"></div>
|
||||
<div class="forum__post__info__content">
|
||||
<span class="forum__post__avatar">{{ avatar(posting_post.poster_id|default(current_user.id), 120, posting_post.poster_name|default(current_user.username)) }}</span>
|
||||
<span class="forum__post__avatar">{{ avatar(posting_post.user.id|default(current_user.id), 120, posting_post.user.username|default(current_user.username)) }}</span>
|
||||
|
||||
<span class="forum__post__username">{{ posting_post.poster_name|default(current_user.username) }}</span>
|
||||
<span class="forum__post__username">{{ posting_post.user.username|default(current_user.username) }}</span>
|
||||
|
||||
<div class="forum__post__icons">
|
||||
<div class="flag flag--{{ posting_post.poster_country|default(posting_user.country)|lower }}" title="{{ posting_post.poster_country|default(posting_user.country)|country_name }}"></div>
|
||||
<div class="forum__post__posts-count">{{ posting_post.poster_post_count|default(posting_user.forumPostCount)|number_format }} posts</div>
|
||||
<div class="flag flag--{{ posting_post.user.country|default(posting_user.country)|lower }}" title="{{ posting_post.user.country|default(posting_user.country)|country_name }}"></div>
|
||||
<div class="forum__post__posts-count">{{ posting_post.user.forumPostCount|default(posting_user.forumPostCount)|number_format }} posts</div>
|
||||
</div>
|
||||
|
||||
<div class="forum__post__joined">
|
||||
joined <time datetime="{{ posting_post.poster_joined|default(posting_user.createdTime)|date('c') }}" title="{{ posting_post.poster_joined|default(posting_user.createdTime)|date('r') }}">{{ posting_post.poster_joined|default(posting_user.createdTime)|time_diff }}</time>
|
||||
joined <time datetime="{{ posting_post.user.createdTime|default(posting_user.createdTime)|date('c') }}" title="{{ posting_post.user.createdTime|default(posting_user.createdTime)|date('r') }}">{{ posting_post.user.createdTime|default(posting_user.createdTime)|time_diff }}</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -77,7 +77,7 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<textarea name="post[text]" class="forum__post__text forum__post__text--edit js-forum-posting-text js-ctrl-enter-submit" placeholder="Type your post content here...">{{ posting_defaults.text|default(posting_post.post_text|default('')) }}</textarea>
|
||||
<textarea name="post[text]" class="forum__post__text forum__post__text--edit js-forum-posting-text js-ctrl-enter-submit" placeholder="Type your post content here...">{{ posting_defaults.text|default(posting_post.body|default('')) }}</textarea>
|
||||
<div class="forum__post__text js-forum-posting-preview" hidden></div>
|
||||
|
||||
<div class="forum__post__actions forum__post__actions--bbcode" hidden>
|
||||
|
@ -142,7 +142,7 @@
|
|||
{{ input_select(
|
||||
'post[parser]',
|
||||
constant('\\Misuzu\\Parsers\\Parser::NAMES'),
|
||||
posting_defaults.parser|default(posting_post.post_parse|default(posting_user.preferredParser)),
|
||||
posting_defaults.parser|default(posting_post.bodyParser|default(posting_user.preferredParser)),
|
||||
null, null, false, 'forum__post__dropdown js-forum-posting-parser'
|
||||
) }}
|
||||
{% if is_opening and posting_types|length > 1 %}
|
||||
|
@ -158,8 +158,8 @@
|
|||
'Display Signature',
|
||||
posting_defaults.signature is not null
|
||||
? posting_defaults.signature : (
|
||||
posting_post.post_display_signature is defined
|
||||
? posting_post.post_display_signature
|
||||
posting_post.shouldDisplaySignature is defined
|
||||
? posting_post.shouldDisplaySignature
|
||||
: true
|
||||
)
|
||||
) }}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue